File: EditorConfigNamingStyleParser_SymbolSpec.cs
Web Access
Project: ..\..\..\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces)
// 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.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.CodeAnalysis.EditorConfig.Parsing;
using Microsoft.CodeAnalysis.EditorConfig.Parsing.NamingStyles;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
using static Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles.SymbolSpecification;
 
namespace Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles
{
    internal static partial class EditorConfigNamingStyleParser
    {
        internal static bool TryGetSymbolSpec(
            Section section,
            string namingRuleTitle,
            IReadOnlyDictionary<string, (string value, TextLine? line)> properties,
            [NotNullWhen(true)] out ApplicableSymbolInfo? applicableSymbolInfo)
        {
            return TryGetSymbolSpec(
                namingRuleTitle,
                properties,
                s => (s.value, s.line),
                () => null,
                (nameTuple, kindsTuple, accessibilitiesTuple, modifiersTuple) =>
                {
                    var (name, nameTextLine) = nameTuple;
                    var (kinds, kindsTextLine) = kindsTuple;
                    var (accessibilities, accessibilitiesTextLine) = accessibilitiesTuple;
                    var (modifiers, modifiersTextLine) = modifiersTuple;
                    return new ApplicableSymbolInfo(
                        OptionName: (section, nameTextLine?.Span, name),
                        SymbolKinds: (section, kindsTextLine?.Span, kinds),
                        Accessibilities: (section, accessibilitiesTextLine?.Span, accessibilities),
                        Modifiers: (section, modifiersTextLine?.Span, modifiers));
                },
                out applicableSymbolInfo);
        }
 
        private static bool TryGetSymbolSpec(
            string namingRuleTitle,
            IReadOnlyDictionary<string, string> conventionsDictionary,
            [NotNullWhen(true)] out SymbolSpecification? symbolSpec)
        {
            return TryGetSymbolSpec<string, object?, SymbolSpecification>(
                namingRuleTitle,
                conventionsDictionary,
                s => (s, null),
                () => null,
                (t0, t1, t2, t3) => new SymbolSpecification(
                        Guid.NewGuid(),
                        t0.name,
                        t1.kinds,
                        t2.accessibilities,
                        t3.modifiers),
                out symbolSpec);
        }
 
        private static bool TryGetSymbolSpec<T, TData, TResult>(
            string namingRuleTitle,
            IReadOnlyDictionary<string, T> conventionsDictionary,
            Func<T, (string value, TData data)> tupleSelector,
            Func<TData> defaultValue,
            Func<(string name, TData data),
                 (ImmutableArray<SymbolKindOrTypeKind> kinds, TData data),
                 (ImmutableArray<Accessibility> accessibilities, TData data),
                 (ImmutableArray<ModifierKind> modifiers, TData data),
                TResult> constructor,
            [NotNullWhen(true)] out TResult? symbolSpec)
        {
            symbolSpec = default;
            if (!TryGetSymbolSpecNameForNamingRule(namingRuleTitle, conventionsDictionary, tupleSelector, out var symbolSpecName))
            {
                return false;
            }
 
            var applicableKinds = GetSymbolsApplicableKinds(symbolSpecName.name, conventionsDictionary, tupleSelector, defaultValue);
            var applicableAccessibilities = GetSymbolsApplicableAccessibilities(symbolSpecName.name, conventionsDictionary, tupleSelector, defaultValue);
            var requiredModifiers = GetSymbolsRequiredModifiers(symbolSpecName.name, conventionsDictionary, tupleSelector, defaultValue);
 
            symbolSpec = constructor(symbolSpecName, applicableKinds, applicableAccessibilities, requiredModifiers);
            return symbolSpec is not null;
        }
 
        private static bool TryGetSymbolSpecNameForNamingRule<T, TData>(
            string namingRuleName,
            IReadOnlyDictionary<string, T> conventionsDictionary,
            Func<T, (string symbolSpecName, TData data)> tupleSelector,
            out (string name, TData data) result)
        {
            if (conventionsDictionary.TryGetValue($"dotnet_naming_rule.{namingRuleName}.symbols", out var symbolSpecName))
            {
                result = tupleSelector(symbolSpecName);
                return result.name != null;
            }
 
            result = default;
            return false;
        }
 
        private static (ImmutableArray<SymbolKindOrTypeKind> kinds, TData data) GetSymbolsApplicableKinds<T, TData>(
            string symbolSpecName,
            IReadOnlyDictionary<string, T> conventionsDictionary,
            Func<T, (string value, TData data)> tupleSelector,
            Func<TData> defaultValue)
        {
            if (conventionsDictionary.TryGetValue($"dotnet_naming_symbols.{symbolSpecName}.applicable_kinds", out var result))
            {
                var (symbolSpecApplicableKinds, data) = tupleSelector(result);
                var kinds = ParseSymbolKindList(symbolSpecApplicableKinds ?? string.Empty);
                return (kinds, data);
            }
 
            return (_all, defaultValue());
        }
 
        private static readonly SymbolKindOrTypeKind _namespace = new(SymbolKind.Namespace);
        private static readonly SymbolKindOrTypeKind _class = new(TypeKind.Class);
        private static readonly SymbolKindOrTypeKind _struct = new(TypeKind.Struct);
        private static readonly SymbolKindOrTypeKind _interface = new(TypeKind.Interface);
        private static readonly SymbolKindOrTypeKind _enum = new(TypeKind.Enum);
        private static readonly SymbolKindOrTypeKind _property = new(SymbolKind.Property);
        private static readonly SymbolKindOrTypeKind _method = new(MethodKind.Ordinary);
        private static readonly SymbolKindOrTypeKind _localFunction = new(MethodKind.LocalFunction);
        private static readonly SymbolKindOrTypeKind _field = new(SymbolKind.Field);
        private static readonly SymbolKindOrTypeKind _event = new(SymbolKind.Event);
        private static readonly SymbolKindOrTypeKind _delegate = new(TypeKind.Delegate);
        private static readonly SymbolKindOrTypeKind _parameter = new(SymbolKind.Parameter);
        private static readonly SymbolKindOrTypeKind _typeParameter = new(SymbolKind.TypeParameter);
        private static readonly SymbolKindOrTypeKind _local = new(SymbolKind.Local);
        private static readonly ImmutableArray<SymbolKindOrTypeKind> _all =
            ImmutableArray.Create(
                _namespace,
                _class,
                _struct,
                _interface,
                _enum,
                _property,
                _method,
                _localFunction,
                _field,
                _event,
                _delegate,
                _parameter,
                _typeParameter,
                _local);
 
        private static ImmutableArray<SymbolKindOrTypeKind> ParseSymbolKindList(string symbolSpecApplicableKinds)
        {
            if (symbolSpecApplicableKinds == null)
            {
                return ImmutableArray<SymbolKindOrTypeKind>.Empty;
            }
 
            if (symbolSpecApplicableKinds.Trim() == "*")
            {
                return _all;
            }
 
            var builder = ArrayBuilder<SymbolKindOrTypeKind>.GetInstance();
            foreach (var symbolSpecApplicableKind in symbolSpecApplicableKinds.Split(',').Select(x => x.Trim()))
            {
                switch (symbolSpecApplicableKind)
                {
                    case "class":
                        builder.Add(_class);
                        break;
                    case "struct":
                        builder.Add(_struct);
                        break;
                    case "interface":
                        builder.Add(_interface);
                        break;
                    case "enum":
                        builder.Add(_enum);
                        break;
                    case "property":
                        builder.Add(_property);
                        break;
                    case "method":
                        builder.Add(_method);
                        break;
                    case "local_function":
                        builder.Add(_localFunction);
                        break;
                    case "field":
                        builder.Add(_field);
                        break;
                    case "event":
                        builder.Add(_event);
                        break;
                    case "delegate":
                        builder.Add(_delegate);
                        break;
                    case "parameter":
                        builder.Add(_parameter);
                        break;
                    case "type_parameter":
                        builder.Add(_typeParameter);
                        break;
                    case "namespace":
                        builder.Add(_namespace);
                        break;
                    case "local":
                        builder.Add(_local);
                        break;
                    default:
                        break;
                }
            }
 
            return builder.ToImmutableAndFree();
        }
 
        private static (ImmutableArray<Accessibility> accessibilities, TData data) GetSymbolsApplicableAccessibilities<T, TData>(
            string symbolSpecName,
            IReadOnlyDictionary<string, T> conventionsDictionary,
            Func<T, (string value, TData data)> tupleSelector,
            Func<TData> defaultValue)
        {
            if (conventionsDictionary.TryGetValue($"dotnet_naming_symbols.{symbolSpecName}.applicable_accessibilities", out var result))
            {
                var (symbolSpecApplicableAccessibilities, data) = tupleSelector(result);
                return (ParseAccessibilityKindList(symbolSpecApplicableAccessibilities ?? string.Empty), data);
            }
 
            return (s_allAccessibility, defaultValue());
        }
 
        private static readonly ImmutableArray<Accessibility> s_allAccessibility = ImmutableArray.Create(Accessibility.NotApplicable, Accessibility.Public, Accessibility.Internal, Accessibility.Private, Accessibility.Protected, Accessibility.ProtectedAndInternal, Accessibility.ProtectedOrInternal);
 
        private static ImmutableArray<Accessibility> ParseAccessibilityKindList(string symbolSpecApplicableAccessibilities)
        {
            if (symbolSpecApplicableAccessibilities == null)
            {
                return ImmutableArray<Accessibility>.Empty;
            }
 
            if (symbolSpecApplicableAccessibilities.Trim() == "*")
            {
                return s_allAccessibility;
            }
 
            var builder = ArrayBuilder<Accessibility>.GetInstance();
            foreach (var symbolSpecApplicableAccessibility in symbolSpecApplicableAccessibilities.Split(',').Select(x => x.Trim()))
            {
                switch (symbolSpecApplicableAccessibility)
                {
                    case "public":
                        builder.Add(Accessibility.Public);
                        break;
                    case "internal":
                    case "friend":
                        builder.Add(Accessibility.Internal);
                        break;
                    case "private":
                        builder.Add(Accessibility.Private);
                        break;
                    case "protected":
                        builder.Add(Accessibility.Protected);
                        break;
                    case "protected_internal":
                    case "protected_friend":
                        builder.Add(Accessibility.ProtectedOrInternal);
                        break;
                    case "private_protected":
                        builder.Add(Accessibility.ProtectedAndInternal);
                        break;
                    case "local":
                        builder.Add(Accessibility.NotApplicable);
                        break;
                    default:
                        break;
                }
            }
 
            return builder.ToImmutableAndFree();
        }
 
        private static (ImmutableArray<ModifierKind> modifiers, TData data) GetSymbolsRequiredModifiers<T, TData>(
            string symbolSpecName,
            IReadOnlyDictionary<string, T> conventionsDictionary,
            Func<T, (string value, TData data)> tupleSelector,
            Func<TData> defaultValue)
        {
            if (conventionsDictionary.TryGetValue($"dotnet_naming_symbols.{symbolSpecName}.required_modifiers", out var result))
            {
                var (symbolSpecRequiredModifiers, data) = tupleSelector(result);
                return (ParseModifiers(symbolSpecRequiredModifiers ?? string.Empty), data);
            }
 
            return (ImmutableArray<ModifierKind>.Empty, defaultValue());
        }
 
        private static readonly ModifierKind s_abstractModifierKind = new(ModifierKindEnum.IsAbstract);
        private static readonly ModifierKind s_asyncModifierKind = new(ModifierKindEnum.IsAsync);
        private static readonly ModifierKind s_constModifierKind = new(ModifierKindEnum.IsConst);
        private static readonly ModifierKind s_readonlyModifierKind = new(ModifierKindEnum.IsReadOnly);
        private static readonly ModifierKind s_staticModifierKind = new(ModifierKindEnum.IsStatic);
        private static readonly ImmutableArray<ModifierKind> _allModifierKind = ImmutableArray.Create(s_abstractModifierKind, s_asyncModifierKind, s_constModifierKind, s_readonlyModifierKind, s_staticModifierKind);
 
        private static ImmutableArray<ModifierKind> ParseModifiers(string symbolSpecRequiredModifiers)
        {
            if (symbolSpecRequiredModifiers == null)
            {
                return ImmutableArray<ModifierKind>.Empty;
            }
 
            if (symbolSpecRequiredModifiers.Trim() == "*")
            {
                return _allModifierKind;
            }
 
            var builder = ArrayBuilder<ModifierKind>.GetInstance();
            foreach (var symbolSpecRequiredModifier in symbolSpecRequiredModifiers.Split(',').Select(x => x.Trim()))
            {
                switch (symbolSpecRequiredModifier)
                {
                    case "abstract":
                    case "must_inherit":
                        builder.Add(s_abstractModifierKind);
                        break;
                    case "async":
                        builder.Add(s_asyncModifierKind);
                        break;
                    case "const":
                        builder.Add(s_constModifierKind);
                        break;
                    case "readonly":
                        builder.Add(s_readonlyModifierKind);
                        break;
                    case "static":
                    case "shared":
                        builder.Add(s_staticModifierKind);
                        break;
                    default:
                        break;
                }
            }
 
            return builder.ToImmutableAndFree();
        }
 
        public static string ToEditorConfigString(this ImmutableArray<SymbolKindOrTypeKind> symbols)
        {
            if (symbols.IsDefaultOrEmpty)
            {
                return "";
            }
 
            if (_all.All(symbols.Contains) && symbols.All(_all.Contains))
            {
                return "*";
            }
 
            return string.Join(", ", symbols.Select(symbol => symbol.ToEditorConfigString()));
        }
 
        private static string ToEditorConfigString(this SymbolKindOrTypeKind symbol)
        {
            switch (symbol.MethodKind)
            {
                case MethodKind.Ordinary:
                    return "method";
 
                case MethodKind.LocalFunction:
                    return "local_function";
 
                case null:
                    break;
 
                default:
                    throw ExceptionUtilities.UnexpectedValue(symbol);
            }
 
            switch (symbol.TypeKind)
            {
                case TypeKind.Class:
                    return "class";
 
                case TypeKind.Struct:
                    return "struct";
 
                case TypeKind.Interface:
                    return "interface";
 
                case TypeKind.Enum:
                    return "enum";
 
                case TypeKind.Delegate:
                    return "delegate";
 
                case TypeKind.Module:
                    return "module";
 
                case TypeKind.Pointer:
                    return "pointer";
 
                case TypeKind.TypeParameter:
                    return "type_parameter";
 
                case null:
                    break;
 
                default:
                    throw ExceptionUtilities.UnexpectedValue(symbol);
            }
 
            switch (symbol.SymbolKind)
            {
                case SymbolKind.Namespace:
                    return "namespace";
 
                case SymbolKind.Property:
                    return "property";
 
                case SymbolKind.Field:
                    return "field";
 
                case SymbolKind.Event:
                    return "event";
 
                case SymbolKind.Parameter:
                    return "parameter";
 
                case SymbolKind.TypeParameter:
                    return "type_parameter";
 
                case SymbolKind.Local:
                    return "local";
 
                case null:
                    break;
 
                default:
                    throw ExceptionUtilities.UnexpectedValue(symbol);
            }
 
            throw ExceptionUtilities.UnexpectedValue(symbol);
        }
 
        public static string ToEditorConfigString(this ImmutableArray<Accessibility> accessibilities, string languageName)
        {
            if (accessibilities.IsDefaultOrEmpty)
            {
                return "";
            }
 
            if (s_allAccessibility.All(accessibilities.Contains) && accessibilities.All(s_allAccessibility.Contains))
            {
                return "*";
            }
 
            return string.Join(", ", accessibilities.Select(accessibility => accessibility.ToEditorConfigString(languageName)));
        }
 
        private static string ToEditorConfigString(this Accessibility accessibility, string languageName)
        {
            switch (accessibility)
            {
                case Accessibility.NotApplicable:
                    return "local";
 
                case Accessibility.Private:
                    return "private";
 
                case Accessibility.ProtectedAndInternal:
                    return "private_protected";
 
                case Accessibility.Protected:
                    return "protected";
 
                case Accessibility.Internal:
                    if (languageName == LanguageNames.VisualBasic)
                    {
                        return "friend";
                    }
                    else
                    {
                        return "internal";
                    }
 
                case Accessibility.ProtectedOrInternal:
                    if (languageName == LanguageNames.VisualBasic)
                    {
                        return "protected_friend";
                    }
                    else
                    {
                        return "protected_internal";
                    }
 
                case Accessibility.Public:
                    return "public";
 
                default:
                    throw ExceptionUtilities.UnexpectedValue(accessibility);
            }
        }
 
        public static string ToEditorConfigString(this ImmutableArray<ModifierKind> modifiers, string languageName)
        {
            if (modifiers.IsDefaultOrEmpty)
            {
                return "";
            }
 
            if (_allModifierKind.All(modifiers.Contains) && modifiers.All(_allModifierKind.Contains))
            {
                return "*";
            }
 
            return string.Join(", ", modifiers.Select(modifier => modifier.ToEditorConfigString(languageName)));
        }
 
        private static string ToEditorConfigString(this ModifierKind modifier, string languageName)
        {
            switch (modifier.ModifierKindWrapper)
            {
                case ModifierKindEnum.IsAbstract:
                    if (languageName == LanguageNames.VisualBasic)
                    {
                        return "must_inherit";
                    }
                    else
                    {
                        return "abstract";
                    }
 
                case ModifierKindEnum.IsStatic:
                    if (languageName == LanguageNames.VisualBasic)
                    {
                        return "shared";
                    }
                    else
                    {
                        return "static";
                    }
 
                case ModifierKindEnum.IsAsync:
                    return "async";
 
                case ModifierKindEnum.IsReadOnly:
                    return "readonly";
 
                case ModifierKindEnum.IsConst:
                    return "const";
 
                default:
                    throw ExceptionUtilities.UnexpectedValue(modifier);
            }
        }
    }
}