File: FindSymbols\CSharpDeclaredSymbolInfoFactoryService.cs
Web Access
Project: ..\..\..\src\Workspaces\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.Workspaces.csproj (Microsoft.CodeAnalysis.CSharp.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.
 
#nullable disable
 
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.LanguageService;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.FindSymbols
{
    [ExportLanguageService(typeof(IDeclaredSymbolInfoFactoryService), LanguageNames.CSharp), Shared]
    internal class CSharpDeclaredSymbolInfoFactoryService : AbstractDeclaredSymbolInfoFactoryService<
        CompilationUnitSyntax,
        UsingDirectiveSyntax,
        BaseNamespaceDeclarationSyntax,
        TypeDeclarationSyntax,
        EnumDeclarationSyntax,
        MethodDeclarationSyntax,
        MemberDeclarationSyntax,
        NameSyntax,
        QualifiedNameSyntax,
        IdentifierNameSyntax>
    {
        [ImportingConstructor]
        [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
        public CSharpDeclaredSymbolInfoFactoryService()
        {
        }
 
        private static ImmutableArray<string> GetInheritanceNames(StringTable stringTable, BaseListSyntax baseList)
        {
            if (baseList == null)
            {
                return ImmutableArray<string>.Empty;
            }
 
            var builder = ArrayBuilder<string>.GetInstance(baseList.Types.Count);
 
            // It's not sufficient to just store the textual names we see in the inheritance list
            // of a type.  For example if we have:
            //
            //   using Y = X;
            //      ...
            //      using Z = Y;
            //      ...
            //      class C : Z
            //
            // It's insufficient to just state that 'C' derives from 'Z'.  If we search for derived
            // types from 'B' we won't examine 'C'.  To solve this, we keep track of the aliasing
            // that occurs in containing scopes.  Then, when we're adding an inheritance name we 
            // walk the alias maps and we also add any names that these names alias to.  In the
            // above example we'd put Z, Y, and X in the inheritance names list for 'C'.
 
            // Each dictionary in this list is a mapping from alias name to the name of the thing
            // it aliases.  Then, each scope with alias mapping gets its own entry in this list.
            // For the above example, we would produce:  [{Z => Y}, {Y => X}]
            var aliasMaps = AllocateAliasMapList();
            try
            {
                AddAliasMaps(baseList, aliasMaps);
 
                foreach (var baseType in baseList.Types)
                {
                    AddInheritanceName(builder, baseType.Type, aliasMaps);
                }
 
                Intern(stringTable, builder);
                return builder.ToImmutableAndFree();
            }
            finally
            {
                FreeAliasMapList(aliasMaps);
            }
        }
 
        private static void AddAliasMaps(SyntaxNode node, List<Dictionary<string, string>> aliasMaps)
        {
            for (var current = node; current != null; current = current.Parent)
            {
                if (current is BaseNamespaceDeclarationSyntax nsDecl)
                {
                    ProcessUsings(aliasMaps, nsDecl.Usings);
                }
                else if (current is CompilationUnitSyntax compilationUnit)
                {
                    ProcessUsings(aliasMaps, compilationUnit.Usings);
                }
            }
        }
 
        private static void ProcessUsings(List<Dictionary<string, string>> aliasMaps, SyntaxList<UsingDirectiveSyntax> usings)
        {
            Dictionary<string, string> aliasMap = null;
 
            foreach (var usingDecl in usings)
            {
                if (usingDecl.Alias != null)
                {
                    var mappedName = GetTypeName(usingDecl.Name);
                    if (mappedName != null)
                    {
                        aliasMap ??= AllocateAliasMap();
 
                        // If we have:  using X = Goo, then we store a mapping from X -> Goo
                        // here.  That way if we see a class that inherits from X we also state
                        // that it inherits from Goo as well.
                        aliasMap[usingDecl.Alias.Name.Identifier.ValueText] = mappedName;
                    }
                }
            }
 
            if (aliasMap != null)
            {
                aliasMaps.Add(aliasMap);
            }
        }
 
        private static void AddInheritanceName(
            ArrayBuilder<string> builder, TypeSyntax type,
            List<Dictionary<string, string>> aliasMaps)
        {
            var name = GetTypeName(type);
            if (name != null)
            {
                // First, add the name that the typename that the type directly says it inherits from.
                builder.Add(name);
 
                // Now, walk the alias chain and add any names this alias may eventually map to.
                var currentName = name;
                foreach (var aliasMap in aliasMaps)
                {
                    if (aliasMap.TryGetValue(currentName, out var mappedName))
                    {
                        // Looks like this could be an alias.  Also include the name the alias points to
                        builder.Add(mappedName);
 
                        // Keep on searching.  An alias in an inner namespcae can refer to an 
                        // alias in an outer namespace.  
                        currentName = mappedName;
                    }
                }
            }
        }
 
        protected override void AddLocalFunctionInfos(
            MemberDeclarationSyntax memberDeclaration,
            StringTable stringTable,
            ArrayBuilder<DeclaredSymbolInfo> declaredSymbolInfos,
            string containerDisplayName,
            string fullyQualifiedContainerName,
            CancellationToken cancellationToken)
        {
            using var pooledQueue = SharedPools.Default<Queue<SyntaxNodeOrToken>>().GetPooledObject();
            var queue = pooledQueue.Object;
            queue.Enqueue(memberDeclaration);
            while (!queue.IsEmpty())
            {
                cancellationToken.ThrowIfCancellationRequested();
                var node = queue.Dequeue();
                foreach (var child in node.ChildNodesAndTokens())
                {
                    if (child.IsNode)
                    {
                        queue.Enqueue(child);
                    }
                }
 
                if (node.AsNode() is LocalFunctionStatementSyntax localFunction)
                {
                    declaredSymbolInfos.Add(DeclaredSymbolInfo.Create(
                        stringTable,
                        localFunction.Identifier.ValueText, GetMethodSuffix(localFunction),
                        containerDisplayName,
                        fullyQualifiedContainerName,
                        isPartial: false,
                        hasAttributes: localFunction.AttributeLists.Any(),
                        DeclaredSymbolInfoKind.Method,
                        Accessibility.Private,
                        localFunction.Identifier.Span,
                        inheritanceNames: ImmutableArray<string>.Empty,
                        parameterCount: localFunction.ParameterList.Parameters.Count,
                        typeParameterCount: localFunction.TypeParameterList?.Parameters.Count ?? 0));
                }
            }
        }
 
        protected override DeclaredSymbolInfo? GetTypeDeclarationInfo(
            SyntaxNode container,
            TypeDeclarationSyntax typeDeclaration,
            StringTable stringTable,
            string containerDisplayName,
            string fullyQualifiedContainerName)
        {
            // If this is a part of partial type that only contains nested types, then we don't make an info type for
            // it. That's because we effectively think of this as just being a virtual container just to hold the nested
            // types, and not something someone would want to explicitly navigate to itself.  Similar to how we think of
            // namespaces.
            if (typeDeclaration.Modifiers.Any(SyntaxKind.PartialKeyword) &&
                typeDeclaration.Members.Any() &&
                typeDeclaration.Members.All(m => m is BaseTypeDeclarationSyntax))
            {
                return null;
            }
 
            return DeclaredSymbolInfo.Create(
                stringTable,
                typeDeclaration.Identifier.ValueText,
                GetTypeParameterSuffix(typeDeclaration.TypeParameterList),
                containerDisplayName,
                fullyQualifiedContainerName,
                typeDeclaration.Modifiers.Any(SyntaxKind.PartialKeyword),
                typeDeclaration.AttributeLists.Any(),
                typeDeclaration.Kind() switch
                {
                    SyntaxKind.ClassDeclaration => DeclaredSymbolInfoKind.Class,
                    SyntaxKind.InterfaceDeclaration => DeclaredSymbolInfoKind.Interface,
                    SyntaxKind.StructDeclaration => DeclaredSymbolInfoKind.Struct,
                    SyntaxKind.RecordDeclaration => DeclaredSymbolInfoKind.Record,
                    SyntaxKind.RecordStructDeclaration => DeclaredSymbolInfoKind.RecordStruct,
                    _ => throw ExceptionUtilities.UnexpectedValue(typeDeclaration.Kind()),
                },
                GetAccessibility(container, typeDeclaration.Modifiers),
                typeDeclaration.Identifier.Span,
                GetInheritanceNames(stringTable, typeDeclaration.BaseList),
                IsNestedType(typeDeclaration),
                typeParameterCount: typeDeclaration.TypeParameterList?.Parameters.Count ?? 0);
        }
 
        protected override DeclaredSymbolInfo GetEnumDeclarationInfo(
            SyntaxNode container,
            EnumDeclarationSyntax enumDeclaration,
            StringTable stringTable,
            string containerDisplayName,
            string fullyQualifiedContainerName)
        {
            return DeclaredSymbolInfo.Create(
                stringTable,
                enumDeclaration.Identifier.ValueText, null,
                containerDisplayName,
                fullyQualifiedContainerName,
                enumDeclaration.Modifiers.Any(SyntaxKind.PartialKeyword),
                enumDeclaration.AttributeLists.Any(),
                DeclaredSymbolInfoKind.Enum,
                GetAccessibility(container, enumDeclaration.Modifiers),
                enumDeclaration.Identifier.Span,
                inheritanceNames: ImmutableArray<string>.Empty,
                isNestedType: IsNestedType(enumDeclaration));
        }
 
        protected override void AddMemberDeclarationInfos(
            SyntaxNode container,
            MemberDeclarationSyntax node,
            StringTable stringTable,
            ArrayBuilder<DeclaredSymbolInfo> declaredSymbolInfos,
            string containerDisplayName,
            string fullyQualifiedContainerName)
        {
            Contract.ThrowIfTrue(node is TypeDeclarationSyntax);
            switch (node.Kind())
            {
                case SyntaxKind.ConstructorDeclaration:
                    var ctorDecl = (ConstructorDeclarationSyntax)node;
                    declaredSymbolInfos.Add(DeclaredSymbolInfo.Create(
                        stringTable,
                        ctorDecl.Identifier.ValueText,
                        GetConstructorSuffix(ctorDecl),
                        containerDisplayName,
                        fullyQualifiedContainerName,
                        ctorDecl.Modifiers.Any(SyntaxKind.PartialKeyword),
                        ctorDecl.AttributeLists.Any(),
                        DeclaredSymbolInfoKind.Constructor,
                        GetAccessibility(container, ctorDecl.Modifiers),
                        ctorDecl.Identifier.Span,
                        inheritanceNames: ImmutableArray<string>.Empty,
                        parameterCount: ctorDecl.ParameterList?.Parameters.Count ?? 0));
                    return;
                case SyntaxKind.DelegateDeclaration:
                    var delegateDecl = (DelegateDeclarationSyntax)node;
                    declaredSymbolInfos.Add(DeclaredSymbolInfo.Create(
                        stringTable,
                        delegateDecl.Identifier.ValueText,
                        GetTypeParameterSuffix(delegateDecl.TypeParameterList),
                        containerDisplayName,
                        fullyQualifiedContainerName,
                        delegateDecl.Modifiers.Any(SyntaxKind.PartialKeyword),
                        delegateDecl.AttributeLists.Any(),
                        DeclaredSymbolInfoKind.Delegate,
                        GetAccessibility(container, delegateDecl.Modifiers),
                        delegateDecl.Identifier.Span,
                        inheritanceNames: ImmutableArray<string>.Empty));
                    return;
                case SyntaxKind.EnumMemberDeclaration:
                    var enumMember = (EnumMemberDeclarationSyntax)node;
                    declaredSymbolInfos.Add(DeclaredSymbolInfo.Create(
                        stringTable,
                        enumMember.Identifier.ValueText, null,
                        containerDisplayName,
                        fullyQualifiedContainerName,
                        enumMember.Modifiers.Any(SyntaxKind.PartialKeyword),
                        enumMember.AttributeLists.Any(),
                        DeclaredSymbolInfoKind.EnumMember,
                        Accessibility.Public,
                        enumMember.Identifier.Span,
                        inheritanceNames: ImmutableArray<string>.Empty));
                    return;
                case SyntaxKind.EventDeclaration:
                    var eventDecl = (EventDeclarationSyntax)node;
                    declaredSymbolInfos.Add(DeclaredSymbolInfo.Create(
                        stringTable,
                        eventDecl.Identifier.ValueText, null,
                        containerDisplayName,
                        fullyQualifiedContainerName,
                        eventDecl.Modifiers.Any(SyntaxKind.PartialKeyword),
                        eventDecl.AttributeLists.Any(),
                        DeclaredSymbolInfoKind.Event,
                        GetAccessibility(container, eventDecl.Modifiers),
                        eventDecl.Identifier.Span,
                        inheritanceNames: ImmutableArray<string>.Empty));
                    return;
                case SyntaxKind.IndexerDeclaration:
                    var indexerDecl = (IndexerDeclarationSyntax)node;
                    declaredSymbolInfos.Add(DeclaredSymbolInfo.Create(
                        stringTable,
                        "this", GetIndexerSuffix(indexerDecl),
                        containerDisplayName,
                        fullyQualifiedContainerName,
                        indexerDecl.Modifiers.Any(SyntaxKind.PartialKeyword),
                        indexerDecl.AttributeLists.Any(),
                        DeclaredSymbolInfoKind.Indexer,
                        GetAccessibility(container, indexerDecl.Modifiers),
                        indexerDecl.ThisKeyword.Span,
                        inheritanceNames: ImmutableArray<string>.Empty));
                    return;
                case SyntaxKind.MethodDeclaration:
                    var method = (MethodDeclarationSyntax)node;
                    var isExtensionMethod = IsExtensionMethod(method);
                    declaredSymbolInfos.Add(DeclaredSymbolInfo.Create(
                        stringTable,
                        method.Identifier.ValueText, GetMethodSuffix(method),
                        containerDisplayName,
                        fullyQualifiedContainerName,
                        method.Modifiers.Any(SyntaxKind.PartialKeyword),
                        method.AttributeLists.Any(),
                        isExtensionMethod ? DeclaredSymbolInfoKind.ExtensionMethod : DeclaredSymbolInfoKind.Method,
                        GetAccessibility(container, method.Modifiers),
                        method.Identifier.Span,
                        inheritanceNames: ImmutableArray<string>.Empty,
                        parameterCount: method.ParameterList?.Parameters.Count ?? 0,
                        typeParameterCount: method.TypeParameterList?.Parameters.Count ?? 0));
                    return;
                case SyntaxKind.PropertyDeclaration:
                    var property = (PropertyDeclarationSyntax)node;
                    declaredSymbolInfos.Add(DeclaredSymbolInfo.Create(
                        stringTable,
                        property.Identifier.ValueText, null,
                        containerDisplayName,
                        fullyQualifiedContainerName,
                        property.Modifiers.Any(SyntaxKind.PartialKeyword),
                        property.AttributeLists.Any(),
                        DeclaredSymbolInfoKind.Property,
                        GetAccessibility(container, property.Modifiers),
                        property.Identifier.Span,
                        inheritanceNames: ImmutableArray<string>.Empty));
                    return;
                case SyntaxKind.FieldDeclaration:
                case SyntaxKind.EventFieldDeclaration:
                    var fieldDeclaration = (BaseFieldDeclarationSyntax)node;
                    foreach (var variableDeclarator in fieldDeclaration.Declaration.Variables)
                    {
                        var kind = fieldDeclaration is EventFieldDeclarationSyntax
                            ? DeclaredSymbolInfoKind.Event
                            : fieldDeclaration.Modifiers.Any(m => m.Kind() == SyntaxKind.ConstKeyword)
                                ? DeclaredSymbolInfoKind.Constant
                                : DeclaredSymbolInfoKind.Field;
 
                        declaredSymbolInfos.Add(DeclaredSymbolInfo.Create(
                            stringTable,
                            variableDeclarator.Identifier.ValueText, null,
                            containerDisplayName,
                            fullyQualifiedContainerName,
                            fieldDeclaration.Modifiers.Any(SyntaxKind.PartialKeyword),
                            fieldDeclaration.AttributeLists.Any(),
                            kind,
                            GetAccessibility(container, fieldDeclaration.Modifiers),
                            variableDeclarator.Identifier.Span,
                            inheritanceNames: ImmutableArray<string>.Empty));
                    }
 
                    return;
            }
        }
 
        protected override void AddSynthesizedDeclaredSymbolInfos(
            SyntaxNode container,
            MemberDeclarationSyntax memberDeclaration,
            StringTable stringTable,
            ArrayBuilder<DeclaredSymbolInfo> declaredSymbolInfos,
            string containerDisplayName,
            string fullyQualifiedContainerName,
            CancellationToken cancellationToken)
        {
            // Add synthesized properties for record primary constructors that are not backed by an existing field
            // or property.
            if (memberDeclaration is RecordDeclarationSyntax { ParameterList: { Parameters.Count: > 0 } parameterList } recordDeclaration)
            {
                using var _ = PooledHashSet<string>.GetInstance(out var existingFieldPropNames);
 
                foreach (var member in recordDeclaration.Members)
                {
                    switch (member)
                    {
                        case PropertyDeclarationSyntax property:
                            existingFieldPropNames.Add(property.Identifier.ValueText);
                            continue;
                        case FieldDeclarationSyntax fieldDeclaration:
                            foreach (var variable in fieldDeclaration.Declaration.Variables)
                                existingFieldPropNames.Add(variable.Identifier.ValueText);
                            continue;
                    }
                }
 
                foreach (var parameter in parameterList.Parameters)
                {
                    if (!existingFieldPropNames.Contains(parameter.Identifier.ValueText))
                    {
                        declaredSymbolInfos.Add(DeclaredSymbolInfo.Create(
                            stringTable,
                            parameter.Identifier.ValueText, null,
                            containerDisplayName,
                            fullyQualifiedContainerName,
                            isPartial: false,
                            parameter.AttributeLists.Any(),
                            DeclaredSymbolInfoKind.Property,
                            Accessibility.Public,
                            parameter.Identifier.Span,
                            inheritanceNames: ImmutableArray<string>.Empty));
                    }
                }
            }
        }
 
        protected override SyntaxList<MemberDeclarationSyntax> GetChildren(CompilationUnitSyntax node)
            => node.Members;
 
        protected override SyntaxList<MemberDeclarationSyntax> GetChildren(BaseNamespaceDeclarationSyntax node)
            => node.Members;
 
        protected override SyntaxList<MemberDeclarationSyntax> GetChildren(TypeDeclarationSyntax node)
            => node.Members;
 
        protected override IEnumerable<MemberDeclarationSyntax> GetChildren(EnumDeclarationSyntax node)
            => node.Members;
 
        protected override SyntaxList<UsingDirectiveSyntax> GetUsingAliases(CompilationUnitSyntax node)
            => node.Usings;
 
        protected override SyntaxList<UsingDirectiveSyntax> GetUsingAliases(BaseNamespaceDeclarationSyntax node)
            => node.Usings;
 
        protected override NameSyntax GetName(BaseNamespaceDeclarationSyntax node)
            => node.Name;
 
        protected override NameSyntax GetLeft(QualifiedNameSyntax node)
            => node.Left;
 
        protected override NameSyntax GetRight(QualifiedNameSyntax node)
            => node.Right;
 
        protected override SyntaxToken GetIdentifier(IdentifierNameSyntax node)
            => node.Identifier;
 
        private static bool IsNestedType(BaseTypeDeclarationSyntax typeDecl)
            => typeDecl.Parent is BaseTypeDeclarationSyntax;
 
        private static string GetConstructorSuffix(ConstructorDeclarationSyntax constructor)
            => constructor.Modifiers.Any(SyntaxKind.StaticKeyword)
                ? ".static " + constructor.Identifier + "()"
                : GetSuffix('(', ')', constructor.ParameterList.Parameters);
 
        private static string GetMethodSuffix(MethodDeclarationSyntax method)
            => GetTypeParameterSuffix(method.TypeParameterList) +
               GetSuffix('(', ')', method.ParameterList.Parameters);
 
        private static string GetMethodSuffix(LocalFunctionStatementSyntax method)
            => GetTypeParameterSuffix(method.TypeParameterList) +
               GetSuffix('(', ')', method.ParameterList.Parameters);
 
        private static string GetIndexerSuffix(IndexerDeclarationSyntax indexer)
            => GetSuffix('[', ']', indexer.ParameterList.Parameters);
 
        private static string GetTypeParameterSuffix(TypeParameterListSyntax typeParameterList)
        {
            if (typeParameterList == null)
            {
                return null;
            }
 
            var pooledBuilder = PooledStringBuilder.GetInstance();
 
            var builder = pooledBuilder.Builder;
            builder.Append('<');
 
            var first = true;
            foreach (var parameter in typeParameterList.Parameters)
            {
                if (!first)
                {
                    builder.Append(", ");
                }
 
                builder.Append(parameter.Identifier.Text);
                first = false;
            }
 
            builder.Append('>');
 
            return pooledBuilder.ToStringAndFree();
        }
 
        /// <summary>
        /// Builds up the suffix to show for something with parameters in navigate-to.
        /// While it would be nice to just use the compiler SymbolDisplay API for this,
        /// it would be too expensive as it requires going back to Symbols (which requires
        /// creating compilations, etc.) in a perf sensitive area.
        /// 
        /// So, instead, we just build a reasonable suffix using the pure syntax that a 
        /// user provided.  That means that if they wrote "Method(System.Int32 i)" we'll 
        /// show that as "Method(System.Int32)" not "Method(int)".  Given that this is
        /// actually what the user wrote, and it saves us from ever having to go back to
        /// symbols/compilations, this is well worth it, even if it does mean we have to
        /// create our own 'symbol display' logic here.
        /// </summary>
        private static string GetSuffix(
            char openBrace, char closeBrace, SeparatedSyntaxList<ParameterSyntax> parameters)
        {
            var pooledBuilder = PooledStringBuilder.GetInstance();
 
            var builder = pooledBuilder.Builder;
            builder.Append(openBrace);
            AppendParameters(parameters, builder);
            builder.Append(closeBrace);
 
            return pooledBuilder.ToStringAndFree();
        }
 
        private static void AppendParameters(SeparatedSyntaxList<ParameterSyntax> parameters, StringBuilder builder)
        {
            var first = true;
            foreach (var parameter in parameters)
            {
                if (!first)
                {
                    builder.Append(", ");
                }
 
                foreach (var modifier in parameter.Modifiers)
                {
                    builder.Append(modifier.Text);
                    builder.Append(' ');
                }
 
                if (parameter.Type != null)
                {
                    builder.Append(parameter.Type.ConvertToSingleLine().ToString());
                }
                else
                {
                    builder.Append(parameter.Identifier.Text);
                }
 
                first = false;
            }
        }
 
        protected override string GetContainerDisplayName(MemberDeclarationSyntax node)
            => CSharpSyntaxFacts.Instance.GetDisplayName(node, DisplayNameOptions.IncludeTypeParameters);
 
        protected override string GetFullyQualifiedContainerName(MemberDeclarationSyntax node, string rootNamespace)
            => CSharpSyntaxFacts.Instance.GetDisplayName(node, DisplayNameOptions.IncludeNamespaces);
 
        private static Accessibility GetAccessibility(SyntaxNode container, SyntaxTokenList modifiers)
        {
            var sawInternal = false;
            foreach (var modifier in modifiers)
            {
                switch (modifier.Kind())
                {
                    case SyntaxKind.PublicKeyword: return Accessibility.Public;
                    case SyntaxKind.PrivateKeyword: return Accessibility.Private;
                    case SyntaxKind.ProtectedKeyword: return Accessibility.Protected;
                    case SyntaxKind.InternalKeyword:
                        sawInternal = true;
                        continue;
                }
            }
 
            if (sawInternal)
                return Accessibility.Internal;
 
            // No accessibility modifiers:
            switch (container.Kind())
            {
                case SyntaxKind.ClassDeclaration:
                case SyntaxKind.RecordDeclaration:
                case SyntaxKind.StructDeclaration:
                case SyntaxKind.RecordStructDeclaration:
                    // Anything without modifiers is private if it's in a class/struct declaration.
                    return Accessibility.Private;
                case SyntaxKind.InterfaceDeclaration:
                    // Anything without modifiers is public if it's in an interface declaration.
                    return Accessibility.Public;
                case SyntaxKind.CompilationUnit:
                    // Things are private by default in script
                    if (((CSharpParseOptions)container.SyntaxTree.Options).Kind == SourceCodeKind.Script)
                        return Accessibility.Private;
 
                    return Accessibility.Internal;
 
                default:
                    // Otherwise it's internal
                    return Accessibility.Internal;
            }
        }
 
        private static string GetTypeName(TypeSyntax type)
        {
            if (type is SimpleNameSyntax simpleName)
            {
                return GetSimpleTypeName(simpleName);
            }
            else if (type is QualifiedNameSyntax qualifiedName)
            {
                return GetSimpleTypeName(qualifiedName.Right);
            }
            else if (type is AliasQualifiedNameSyntax aliasName)
            {
                return GetSimpleTypeName(aliasName.Name);
            }
 
            return null;
        }
 
        private static string GetSimpleTypeName(SimpleNameSyntax name)
            => name.Identifier.ValueText;
 
        private static bool IsExtensionMethod(MethodDeclarationSyntax method)
            => method.ParameterList.Parameters is [var parameter, ..] && parameter.Modifiers.Any(SyntaxKind.ThisKeyword);
 
        // Root namespace is a VB only concept, which basically means root namespace is always global in C#.
        protected override string GetRootNamespace(CompilationOptions compilationOptions)
            => string.Empty;
 
        protected override bool TryGetAliasesFromUsingDirective(
            UsingDirectiveSyntax usingDirectiveNode, out ImmutableArray<(string aliasName, string name)> aliases)
        {
            if (usingDirectiveNode.Alias != null)
            {
                if (TryGetSimpleTypeName(usingDirectiveNode.Alias.Name, typeParameterNames: null, out var aliasName, out _) &&
                    TryGetSimpleTypeName(usingDirectiveNode.NamespaceOrType, typeParameterNames: null, out var name, out _))
                {
                    aliases = ImmutableArray.Create<(string, string)>((aliasName, name));
                    return true;
                }
            }
 
            aliases = default;
            return false;
        }
 
        protected override string GetReceiverTypeName(MethodDeclarationSyntax methodDeclaration)
        {
            Debug.Assert(IsExtensionMethod(methodDeclaration));
 
            var typeParameterNames = methodDeclaration.TypeParameterList?.Parameters.SelectAsArray(p => p.Identifier.Text);
            TryGetSimpleTypeName(methodDeclaration.ParameterList.Parameters[0].Type, typeParameterNames, out var targetTypeName, out var isArray);
            return CreateReceiverTypeString(targetTypeName, isArray);
        }
 
        private static bool TryGetSimpleTypeName(SyntaxNode node, ImmutableArray<string>? typeParameterNames, out string simpleTypeName, out bool isArray)
        {
            isArray = false;
 
            if (node is TypeSyntax typeNode)
            {
                switch (typeNode)
                {
                    case IdentifierNameSyntax identifierNameNode:
                        // We consider it a complex method if the receiver type is a type parameter.
                        var text = identifierNameNode.Identifier.Text;
                        simpleTypeName = typeParameterNames?.Contains(text) == true ? null : text;
                        return simpleTypeName != null;
 
                    case ArrayTypeSyntax arrayTypeNode:
                        isArray = true;
                        return TryGetSimpleTypeName(arrayTypeNode.ElementType, typeParameterNames, out simpleTypeName, out _);
 
                    case GenericNameSyntax genericNameNode:
                        var name = genericNameNode.Identifier.Text;
                        var arity = genericNameNode.Arity;
                        simpleTypeName = arity == 0 ? name : name + ArityUtilities.GetMetadataAritySuffix(arity);
                        return true;
 
                    case PredefinedTypeSyntax predefinedTypeNode:
                        simpleTypeName = GetSpecialTypeName(predefinedTypeNode);
                        return simpleTypeName != null;
 
                    case AliasQualifiedNameSyntax aliasQualifiedNameNode:
                        return TryGetSimpleTypeName(aliasQualifiedNameNode.Name, typeParameterNames, out simpleTypeName, out _);
 
                    case QualifiedNameSyntax qualifiedNameNode:
                        // For an identifier to the right of a '.', it can't be a type parameter,
                        // so we don't need to check for it further.
                        return TryGetSimpleTypeName(qualifiedNameNode.Right, typeParameterNames: null, out simpleTypeName, out _);
 
                    case NullableTypeSyntax nullableNode:
                        // Ignore nullability, becase nullable reference type might not be enabled universally.
                        // In the worst case we just include more methods to check in out filter.
                        return TryGetSimpleTypeName(nullableNode.ElementType, typeParameterNames, out simpleTypeName, out isArray);
 
                    case TupleTypeSyntax tupleType:
                        simpleTypeName = CreateValueTupleTypeString(tupleType.Elements.Count);
                        return true;
                }
            }
 
            simpleTypeName = null;
            return false;
        }
 
        private static string GetSpecialTypeName(PredefinedTypeSyntax predefinedTypeNode)
        {
            var kind = predefinedTypeNode.Keyword.Kind();
            return kind switch
            {
                SyntaxKind.BoolKeyword => "Boolean",
                SyntaxKind.ByteKeyword => "Byte",
                SyntaxKind.SByteKeyword => "SByte",
                SyntaxKind.ShortKeyword => "Int16",
                SyntaxKind.UShortKeyword => "UInt16",
                SyntaxKind.IntKeyword => "Int32",
                SyntaxKind.UIntKeyword => "UInt32",
                SyntaxKind.LongKeyword => "Int64",
                SyntaxKind.ULongKeyword => "UInt64",
                SyntaxKind.DoubleKeyword => "Double",
                SyntaxKind.FloatKeyword => "Single",
                SyntaxKind.DecimalKeyword => "Decimal",
                SyntaxKind.StringKeyword => "String",
                SyntaxKind.CharKeyword => "Char",
                SyntaxKind.ObjectKeyword => "Object",
                _ => null,
            };
        }
    }
}