File: EditorConfigSettings\Updater\NamingStyles\SourceTextExtensions.cs
Web Access
Project: src\src\EditorFeatures\Core\Microsoft.CodeAnalysis.EditorFeatures.csproj (Microsoft.CodeAnalysis.EditorFeatures)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles;
using Microsoft.CodeAnalysis.EditorConfig;
using Microsoft.CodeAnalysis.EditorConfig.Parsing;
using Microsoft.CodeAnalysis.NamingStyles;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
using NamingStylesParser = Microsoft.CodeAnalysis.EditorConfig.Parsing.NamingStyles.EditorConfigNamingStylesParser;
 
namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Updater;
 
internal static class SourceTextExtensions
{
    public static SourceText WithNamingStyles(this SourceText sourceText, IGlobalOptionService globalOptions)
    {
        var (common, csharp, visualBasic) = GetPreferencesForAllLanguages(globalOptions);
 
        sourceText = WithNamingStyles(sourceText, csharp, Language.CSharp);
        sourceText = WithNamingStyles(sourceText, visualBasic, Language.VisualBasic);
        return WithNamingStyles(sourceText, common, Language.CSharp | Language.VisualBasic);
    }
 
    private static SourceText WithNamingStyles(SourceText sourceText, IEnumerable<NamingRule> rules, Language language)
    {
        if (rules.Any())
        {
            var parseResult = NamingStylesParser.Parse(sourceText, null); // file path unnecessary here
            var newNamingStyleSection = new StringBuilder();
            if (parseResult.TryGetSectionForLanguage(language, out var existingSection))
            {
                var span = new TextSpan(existingSection.Span.End, 0);
                AppendNamingStylePreferencesToEditorConfig(rules, newNamingStyleSection, GetLanguageString(language));
                return WithChanges(sourceText, span, newNamingStyleSection.ToString());
            }
            else
            {
                var span = new TextSpan(sourceText.Length, 0);
                newNamingStyleSection.Append("\r\n");
                newNamingStyleSection.Append(Section.GetHeaderTextForLanguage(language));
                AppendNamingStylePreferencesToEditorConfig(rules, newNamingStyleSection, GetLanguageString(language));
                return WithChanges(sourceText, span, newNamingStyleSection.ToString());
            }
        }
 
        return sourceText;
 
        static SourceText WithChanges(SourceText sourceText, TextSpan span, string newText)
        {
            var textChange = new TextChange(span, newText);
            return sourceText.WithChanges(textChange);
        }
 
        static string? GetLanguageString(Language language)
        {
            if (language.HasFlag(Language.CSharp) && language.HasFlag(Language.VisualBasic))
            {
                return null;
            }
            else if (language.HasFlag(Language.CSharp))
            {
                return LanguageNames.CSharp;
            }
            else if (language.HasFlag(Language.VisualBasic))
            {
                return LanguageNames.VisualBasic;
            }
 
            throw new InvalidOperationException("Invalid language specified");
        }
    }
 
    private static void AppendNamingStylePreferencesToEditorConfig(IEnumerable<NamingRule> namingRules, StringBuilder editorconfig, string? language = null)
    {
        var symbolSpecifications = namingRules.Select(x => x.SymbolSpecification).ToImmutableArray();
        var namingStyles = namingRules.Select(x => x.NamingStyle).ToImmutableArray();
        var serializedNamingRules = namingRules.Select(x => new SerializableNamingRule()
        {
            EnforcementLevel = x.EnforcementLevel,
            NamingStyleID = x.NamingStyle.ID,
            SymbolSpecificationID = x.SymbolSpecification.ID
        }).ToImmutableArray();
 
        language ??= LanguageNames.CSharp;
 
        NamingStylePreferencesEditorConfigSerializer.AppendNamingStylePreferencesToEditorConfig(
            symbolSpecifications,
            namingStyles,
            serializedNamingRules,
            language,
            editorconfig);
    }
 
    private static (IEnumerable<NamingRule> Common, IEnumerable<NamingRule> CSharp, IEnumerable<NamingRule> VisualBasic) GetPreferencesForAllLanguages(IGlobalOptionService globalOptions)
    {
        var csharpNamingStylePreferences = globalOptions.GetOption(NamingStyleOptions.NamingPreferences, LanguageNames.CSharp);
        var vbNamingStylePreferences = globalOptions.GetOption(NamingStyleOptions.NamingPreferences, LanguageNames.VisualBasic);
 
        var commonOptions = GetCommonOptions(csharpNamingStylePreferences, vbNamingStylePreferences);
        var csharpOnlyOptions = GetOptionsUniqueOptions(csharpNamingStylePreferences, commonOptions);
        var vbOnlyOptions = GetOptionsUniqueOptions(vbNamingStylePreferences, commonOptions);
        return (commonOptions, csharpOnlyOptions, vbOnlyOptions);
 
        static IEnumerable<NamingRule> GetCommonOptions(NamingStylePreferences csharp, NamingStylePreferences visualBasic)
            => csharp.Rules.NamingRules.Intersect(visualBasic.Rules.NamingRules, NamingRuleComparerIgnoreGUIDs.Instance);
 
        static IEnumerable<NamingRule> GetOptionsUniqueOptions(NamingStylePreferences csharp, IEnumerable<NamingRule> common)
            => csharp.Rules.NamingRules.Except(common, NamingRuleComparerIgnoreGUIDs.Instance);
    }
 
    private class NamingRuleComparerIgnoreGUIDs : IEqualityComparer<NamingRule>
    {
        private static readonly Lazy<NamingRuleComparerIgnoreGUIDs> s_lazyInstance = new(() => new NamingRuleComparerIgnoreGUIDs());
 
        public static NamingRuleComparerIgnoreGUIDs Instance => s_lazyInstance.Value;
 
        public bool Equals(NamingRule left, NamingRule right)
        {
            return left.EnforcementLevel == right.EnforcementLevel &&
                   NamingStyleComparerIgnoreGUIDs.Instance.Equals(left.NamingStyle, right.NamingStyle) &&
                   SymbolSpecificationComparerIgnoreGUIDs.Instance.Equals(left.SymbolSpecification, right.SymbolSpecification);
        }
 
        public int GetHashCode(NamingRule rule)
        {
            var enforcementLevelHashCode = (int)rule.EnforcementLevel;
            var namingStyleHashCode = NamingStyleComparerIgnoreGUIDs.Instance.GetHashCode(rule.NamingStyle);
            var symbolSpecificationHashCode = SymbolSpecificationComparerIgnoreGUIDs.Instance.GetHashCode(rule.SymbolSpecification);
            return Hash.Combine(enforcementLevelHashCode, Hash.Combine(namingStyleHashCode, symbolSpecificationHashCode));
        }
 
        private class NamingStyleComparerIgnoreGUIDs : IEqualityComparer<NamingStyle>
        {
            private static readonly Lazy<NamingStyleComparerIgnoreGUIDs> s_lazyInstance = new(() => new NamingStyleComparerIgnoreGUIDs());
 
            public static NamingStyleComparerIgnoreGUIDs Instance => s_lazyInstance.Value;
 
            public bool Equals(NamingStyle left, NamingStyle right)
            {
                return StringComparer.OrdinalIgnoreCase.Equals(left.Name, right.Name) &&
                       StringComparer.Ordinal.Equals(left.Prefix, right.Prefix) &&
                       StringComparer.Ordinal.Equals(left.Suffix, right.Suffix) &&
                       StringComparer.Ordinal.Equals(left.WordSeparator, right.WordSeparator) &&
                       left.CapitalizationScheme == right.CapitalizationScheme;
            }
 
            public int GetHashCode(NamingStyle style)
            {
                return Hash.Combine(StringComparer.OrdinalIgnoreCase.GetHashCode(style.Name),
                    Hash.Combine(StringComparer.Ordinal.GetHashCode(style.Prefix),
                        Hash.Combine(StringComparer.Ordinal.GetHashCode(style.Suffix),
                            Hash.Combine(StringComparer.Ordinal.GetHashCode(style.WordSeparator),
                                (int)style.CapitalizationScheme))));
            }
        }
 
        private class SymbolSpecificationComparerIgnoreGUIDs : IEqualityComparer<SymbolSpecification>
        {
            private static readonly Lazy<SymbolSpecificationComparerIgnoreGUIDs> s_lazyInstance = new(() => new SymbolSpecificationComparerIgnoreGUIDs());
 
            public static SymbolSpecificationComparerIgnoreGUIDs Instance => s_lazyInstance.Value;
 
            public bool Equals(SymbolSpecification? left, SymbolSpecification? right)
            {
                if (left is null && right is null)
                {
                    return true;
                }
 
                if (left is null || right is null)
                {
                    return false;
                }
 
                return StringComparer.OrdinalIgnoreCase.Equals(left!.Name, right!.Name) &&
                       left.RequiredModifierList.SequenceEqual(right.RequiredModifierList) &&
                       left.ApplicableAccessibilityList.SequenceEqual(right.ApplicableAccessibilityList) &&
                       left.ApplicableSymbolKindList.SequenceEqual(right.ApplicableSymbolKindList);
            }
 
            public int GetHashCode(SymbolSpecification symbolSpecification)
            {
                return Hash.Combine(StringComparer.OrdinalIgnoreCase.GetHashCode(symbolSpecification.Name),
                    Hash.Combine(Hash.CombineValues(symbolSpecification.RequiredModifierList),
                        Hash.Combine(Hash.CombineValues(symbolSpecification.ApplicableAccessibilityList),
                            Hash.CombineValues(symbolSpecification.ApplicableSymbolKindList))));
            }
        }
    }
}