File: SolutionExplorer\CSharpSolutionExplorerSymbolTreeItemProvider.cs
Web Access
Project: src\src\Features\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.Features.csproj (Microsoft.CodeAnalysis.CSharp.Features)
// 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.Composition;
using System.Diagnostics;
using System.Reflection.Metadata;
using System.Text;
using System.Threading;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.SolutionExplorer;
using static Microsoft.CodeAnalysis.CSharp.FindSymbols.FindSymbolsUtilities;
 
namespace Microsoft.CodeAnalysis.CSharp.SolutionExplorer;
 
[ExportLanguageService(typeof(ISolutionExplorerSymbolTreeItemProvider), LanguageNames.CSharp), Shared]
[method: ImportingConstructor]
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
internal sealed class CSharpSolutionExplorerSymbolTreeItemProvider()
    : AbstractSolutionExplorerSymbolTreeItemProvider<
        CompilationUnitSyntax,
        MemberDeclarationSyntax,
        BaseNamespaceDeclarationSyntax,
        EnumDeclarationSyntax,
        TypeDeclarationSyntax>
{
    protected override SyntaxList<MemberDeclarationSyntax> GetMembers(CompilationUnitSyntax root)
        => root.Members;
 
    protected override SyntaxList<MemberDeclarationSyntax> GetMembers(BaseNamespaceDeclarationSyntax baseNamespace)
        => baseNamespace.Members;
 
    protected override SyntaxList<MemberDeclarationSyntax> GetMembers(TypeDeclarationSyntax typeDeclaration)
        => typeDeclaration.Members;
 
    protected override bool TryAddType(
        DocumentId documentId, MemberDeclarationSyntax member, ArrayBuilder<SymbolTreeItemData> items, StringBuilder nameBuilder)
    {
        switch (member)
        {
            case ExtensionBlockDeclarationSyntax extensionBlock:
                AddExtensionBlock(extensionBlock);
                return true;
 
            case TypeDeclarationSyntax typeDeclaration:
                AddTypeDeclaration(typeDeclaration);
                return true;
 
            case EnumDeclarationSyntax enumDeclaration:
                AddEnumDeclaration(enumDeclaration);
                return true;
 
            case DelegateDeclarationSyntax delegateDeclaration:
                AddDelegateDeclaration(delegateDeclaration);
                return true;
        }
 
        return false;
 
        void AddExtensionBlock(ExtensionBlockDeclarationSyntax extensionBlock)
        {
            nameBuilder.Append("extension");
            AppendTypeParameterList(nameBuilder, extensionBlock.TypeParameterList);
            AppendParameterList(nameBuilder, extensionBlock.ParameterList);
 
            items.Add(new(
                documentId,
                nameBuilder.ToStringAndClear(),
                Glyph.ClassPublic,
                hasItems: extensionBlock.Members.Count > 0,
                extensionBlock,
                extensionBlock.Keyword));
        }
 
        void AddTypeDeclaration(TypeDeclarationSyntax typeDeclaration)
        {
            nameBuilder.Append(typeDeclaration.Identifier.ValueText);
            AppendTypeParameterList(nameBuilder, typeDeclaration.TypeParameterList);
 
            var glyph = GlyphExtensions.GetGlyph(
                GetDeclaredSymbolInfoKind(typeDeclaration),
                GetAccessibility(typeDeclaration.GetRequiredParent(), typeDeclaration.Modifiers));
            items.Add(new(
                documentId,
                nameBuilder.ToStringAndClear(),
                glyph,
                hasItems: typeDeclaration.Members.Count > 0,
                typeDeclaration,
                typeDeclaration.Identifier));
        }
 
        void AddEnumDeclaration(EnumDeclarationSyntax enumDeclaration)
        {
            var glyph = GlyphExtensions.GetGlyph(
                DeclaredSymbolInfoKind.Enum, GetAccessibility(enumDeclaration.GetRequiredParent(), enumDeclaration.Modifiers));
 
            items.Add(new(
                documentId,
                enumDeclaration.Identifier.ValueText,
                glyph,
                hasItems: enumDeclaration.Members.Count > 0,
                enumDeclaration,
                enumDeclaration.Identifier));
        }
 
        void AddDelegateDeclaration(DelegateDeclarationSyntax delegateDeclaration)
        {
            nameBuilder.Append(delegateDeclaration.Identifier.ValueText);
            AppendTypeParameterList(nameBuilder, delegateDeclaration.TypeParameterList);
            AppendParameterList(nameBuilder, delegateDeclaration.ParameterList);
 
            nameBuilder.Append(" : ");
            AppendType(delegateDeclaration.ReturnType, nameBuilder);
 
            var glyph = GlyphExtensions.GetGlyph(
                DeclaredSymbolInfoKind.Delegate, GetAccessibility(delegateDeclaration.GetRequiredParent(), delegateDeclaration.Modifiers));
 
            items.Add(new(
                documentId,
                nameBuilder.ToStringAndClear(),
                glyph,
                hasItems: false,
                delegateDeclaration,
                delegateDeclaration.Identifier));
        }
    }
 
    protected override void AddMemberDeclaration(
        DocumentId documentId, MemberDeclarationSyntax member, ArrayBuilder<SymbolTreeItemData> items, StringBuilder nameBuilder)
    {
        switch (member)
        {
            case BaseFieldDeclarationSyntax fieldDeclaration:
                AddFieldDeclaration(fieldDeclaration);
                return;
 
            case MethodDeclarationSyntax methodDeclaration:
                AddMethodDeclaration(methodDeclaration);
                return;
 
            case OperatorDeclarationSyntax operatorDeclaration:
                AddOperatorDeclaration(operatorDeclaration);
                return;
 
            case ConversionOperatorDeclarationSyntax conversionOperatorDeclaration:
                AddConversionOperatorDeclaration(conversionOperatorDeclaration);
                return;
 
            case ConstructorDeclarationSyntax constructorDeclaration:
                AddConstructorOrDestructorDeclaration(constructorDeclaration, constructorDeclaration.Identifier);
                return;
 
            case DestructorDeclarationSyntax destructorDeclaration:
                AddConstructorOrDestructorDeclaration(destructorDeclaration, destructorDeclaration.Identifier);
                return;
 
            case PropertyDeclarationSyntax propertyDeclaration:
                AddPropertyDeclaration(propertyDeclaration);
                return;
 
            case EventDeclarationSyntax eventDeclaration:
                AddEventDeclaration(eventDeclaration);
                return;
 
            case IndexerDeclarationSyntax indexerDeclaration:
                AddIndexerDeclaration(indexerDeclaration);
                return;
        }
 
        void AddMethodDeclaration(MethodDeclarationSyntax methodDeclaration)
        {
            nameBuilder.Append(methodDeclaration.Identifier.ValueText);
            AppendTypeParameterList(nameBuilder, methodDeclaration.TypeParameterList);
            AppendParameterList(nameBuilder, methodDeclaration.ParameterList);
            nameBuilder.Append(" : ");
            AppendType(methodDeclaration.ReturnType, nameBuilder);
 
            var accessibility = GetAccessibility(methodDeclaration.GetRequiredParent(), methodDeclaration.Modifiers);
            var isExtension = methodDeclaration.IsParentKind(SyntaxKind.ExtensionBlockDeclaration) ||
                (methodDeclaration.ParameterList is { Parameters: [var parameter, ..] } && parameter.Modifiers.Any(SyntaxKind.ThisKeyword));
            var glyph = GlyphExtensions.GetGlyph(
                isExtension ? DeclaredSymbolInfoKind.ExtensionMethod : DeclaredSymbolInfoKind.Method, accessibility);
 
            items.Add(new(
                documentId,
                nameBuilder.ToStringAndClear(),
                glyph,
                hasItems: false,
                methodDeclaration,
                methodDeclaration.Identifier));
        }
 
        void AddFieldDeclaration(BaseFieldDeclarationSyntax fieldDeclaration)
        {
            foreach (var variable in fieldDeclaration.Declaration.Variables)
            {
                nameBuilder.Append(variable.Identifier.ValueText);
                nameBuilder.Append(" : ");
                AppendType(fieldDeclaration.Declaration.Type, nameBuilder);
 
                var accessibility = GetAccessibility(fieldDeclaration.GetRequiredParent(), fieldDeclaration.Modifiers);
                var kind = fieldDeclaration is EventFieldDeclarationSyntax
                    ? DeclaredSymbolInfoKind.Event
                    : DeclaredSymbolInfoKind.Field;
 
                items.Add(new(
                    documentId,
                    nameBuilder.ToStringAndClear(),
                    GlyphExtensions.GetGlyph(kind, accessibility),
                    hasItems: false,
                    variable,
                    variable.Identifier));
            }
        }
 
        void AddOperatorDeclaration(OperatorDeclarationSyntax operatorDeclaration)
        {
            nameBuilder.Append("operator ");
            nameBuilder.Append(operatorDeclaration.OperatorToken.ToString());
            AppendParameterList(nameBuilder, operatorDeclaration.ParameterList);
            nameBuilder.Append(" : ");
            AppendType(operatorDeclaration.ReturnType, nameBuilder);
 
            var accessibility = GetAccessibility(operatorDeclaration.GetRequiredParent(), operatorDeclaration.Modifiers);
            var glyph = GlyphExtensions.GetGlyph(DeclaredSymbolInfoKind.Operator, accessibility);
 
            items.Add(new(
                documentId,
                nameBuilder.ToStringAndClear(),
                glyph,
                hasItems: false,
                operatorDeclaration,
                operatorDeclaration.OperatorToken));
        }
 
        void AddConversionOperatorDeclaration(ConversionOperatorDeclarationSyntax operatorDeclaration)
        {
            nameBuilder.Append(operatorDeclaration.ImplicitOrExplicitKeyword.Kind() == SyntaxKind.ImplicitKeyword
                ? "implicit operator "
                : "explicit operator ");
            AppendType(operatorDeclaration.Type, nameBuilder);
            AppendParameterList(nameBuilder, operatorDeclaration.ParameterList);
 
            var accessibility = GetAccessibility(operatorDeclaration.GetRequiredParent(), operatorDeclaration.Modifiers);
            var glyph = GlyphExtensions.GetGlyph(DeclaredSymbolInfoKind.Operator, accessibility);
 
            items.Add(new(
                documentId,
                nameBuilder.ToStringAndClear(),
                glyph,
                hasItems: false,
                operatorDeclaration,
                operatorDeclaration.Type.GetFirstToken()));
        }
 
        void AddConstructorOrDestructorDeclaration(BaseMethodDeclarationSyntax declaration, SyntaxToken identifier)
        {
            if (declaration.Kind() == SyntaxKind.DestructorDeclaration)
                nameBuilder.Append('~');
 
            nameBuilder.Append(identifier.ValueText);
            AppendParameterList(nameBuilder, declaration.ParameterList);
 
            var accessibility = GetAccessibility(declaration.GetRequiredParent(), declaration.Modifiers);
            var glyph = GlyphExtensions.GetGlyph(DeclaredSymbolInfoKind.Constructor, accessibility);
 
            items.Add(new(
                documentId,
                nameBuilder.ToStringAndClear(),
                glyph,
                hasItems: false,
                declaration,
                identifier));
        }
 
        void AddPropertyDeclaration(PropertyDeclarationSyntax propertyDeclaration)
        {
            nameBuilder.Append(propertyDeclaration.Identifier.ValueText);
            nameBuilder.Append(" : ");
            AppendType(propertyDeclaration.Type, nameBuilder);
 
            var accessibility = GetAccessibility(propertyDeclaration.GetRequiredParent(), propertyDeclaration.Modifiers);
            var glyph = GlyphExtensions.GetGlyph(DeclaredSymbolInfoKind.Property, accessibility);
 
            items.Add(new(
                documentId,
                nameBuilder.ToStringAndClear(),
                glyph,
                hasItems: false,
                propertyDeclaration,
                propertyDeclaration.Identifier));
        }
 
        void AddEventDeclaration(EventDeclarationSyntax eventDeclaration)
        {
            nameBuilder.Append(eventDeclaration.Identifier.ValueText);
            nameBuilder.Append(" : ");
            AppendType(eventDeclaration.Type, nameBuilder);
 
            var accessibility = GetAccessibility(eventDeclaration.GetRequiredParent(), eventDeclaration.Modifiers);
            var glyph = GlyphExtensions.GetGlyph(DeclaredSymbolInfoKind.Event, accessibility);
 
            items.Add(new(
                documentId,
                nameBuilder.ToStringAndClear(),
                glyph,
                hasItems: false,
                eventDeclaration,
                eventDeclaration.Identifier));
        }
 
        void AddIndexerDeclaration(IndexerDeclarationSyntax indexerDeclaration)
        {
            nameBuilder.Append("this");
            AppendCommaSeparatedList(
                nameBuilder, "[", "]",
                indexerDeclaration.ParameterList.Parameters,
                static (parameter, nameBuilder) => AppendType(parameter.Type, nameBuilder));
            nameBuilder.Append(" : ");
            AppendType(indexerDeclaration.Type, nameBuilder);
 
            var accessibility = GetAccessibility(indexerDeclaration.GetRequiredParent(), indexerDeclaration.Modifiers);
            var glyph = GlyphExtensions.GetGlyph(DeclaredSymbolInfoKind.Indexer, accessibility);
 
            items.Add(new(
                documentId,
                nameBuilder.ToStringAndClear(),
                glyph,
                hasItems: false,
                indexerDeclaration,
                indexerDeclaration.ThisKeyword));
        }
    }
 
    protected override void AddEnumDeclarationMembers(
        DocumentId documentId,
        EnumDeclarationSyntax enumDeclaration,
        ArrayBuilder<SymbolTreeItemData> items,
        CancellationToken cancellationToken)
    {
        foreach (var member in enumDeclaration.Members)
        {
            cancellationToken.ThrowIfCancellationRequested();
            items.Add(new(
                documentId,
                member.Identifier.ValueText,
                Glyph.EnumMemberPublic,
                hasItems: false,
                member,
                member.Identifier));
        }
    }
 
    private static void AppendTypeParameterList(
        StringBuilder builder,
        TypeParameterListSyntax? typeParameterList)
    {
        AppendCommaSeparatedList(
            builder, "<", ">", typeParameterList,
            static typeParameterList => typeParameterList.Parameters,
            static (parameter, builder) => builder.Append(parameter.Identifier.ValueText));
    }
 
    private static void AppendParameterList(
        StringBuilder builder,
        ParameterListSyntax? parameterList)
    {
        AppendCommaSeparatedList(
            builder, "(", ")", parameterList,
            static parameterList => parameterList.Parameters,
            static (parameter, builder) => AppendType(parameter.Type, builder));
    }
 
    private static void AppendType(TypeSyntax? type, StringBuilder builder)
    {
        if (type is null)
            return;
 
        if (type is ArrayTypeSyntax arrayType)
        {
            AppendType(arrayType.ElementType, builder);
            foreach (var rankSpecifier in arrayType.RankSpecifiers)
            {
                builder.Append('[');
                AppendCommaSeparatedList(
                    builder, "", "", rankSpecifier.Sizes,
                    static (_, _) => { }, ",");
                builder.Append(']');
            }
        }
        else if (type is PointerTypeSyntax pointerType)
        {
            AppendType(pointerType.ElementType, builder);
            builder.Append('*');
        }
        else if (type is NullableTypeSyntax nullableType)
        {
            AppendType(nullableType.ElementType, builder);
            builder.Append('?');
        }
        else if (type is TupleTypeSyntax tupleType)
        {
            AppendCommaSeparatedList(
                builder, "(", ")", tupleType.Elements, AppendTupleElement);
        }
        else if (type is RefTypeSyntax refType)
        {
            builder.Append("ref ");
            AppendType(refType.Type, builder);
        }
        else if (type is ScopedTypeSyntax scopedType)
        {
            builder.Append("scoped ");
            AppendType(scopedType.Type, builder);
        }
        else if (type is PredefinedTypeSyntax predefinedType)
        {
            builder.Append(predefinedType.ToString());
        }
        else if (type is FunctionPointerTypeSyntax functionPointerType)
        {
            builder.Append("delegate*");
            AppendCommaSeparatedList(
                builder, "<", ">", functionPointerType.ParameterList.Parameters,
                static (parameter, builder) => AppendType(parameter.Type, builder));
        }
        else if (type is OmittedTypeArgumentSyntax)
        {
            // nothing to do here.
        }
        else if (type is QualifiedNameSyntax qualifiedName)
        {
            AppendType(qualifiedName.Right, builder);
        }
        else if (type is AliasQualifiedNameSyntax aliasQualifiedName)
        {
            AppendType(aliasQualifiedName.Name, builder);
        }
        else if (type is IdentifierNameSyntax identifierName)
        {
            builder.Append(identifierName.Identifier.ValueText);
        }
        else if (type is GenericNameSyntax genericName)
        {
            builder.Append(genericName.Identifier.ValueText);
            AppendCommaSeparatedList(
                builder, "<", ">", genericName.TypeArgumentList.Arguments, AppendType);
        }
        else
        {
            Debug.Fail("Unhandled type: " + type.GetType().FullName);
        }
    }
 
    private static void AppendTupleElement(TupleElementSyntax tupleElement, StringBuilder builder)
    {
        AppendType(tupleElement.Type, builder);
        if (tupleElement.Identifier != default)
        {
            builder.Append(' ');
            builder.Append(tupleElement.Identifier.ValueText);
        }
    }
}