File: ObjectBrowser\DescriptionBuilder.cs
Web Access
Project: src\src\VisualStudio\CSharp\Impl\Microsoft.VisualStudio.LanguageServices.CSharp_sivdbxd3_wpftmp.csproj (Microsoft.VisualStudio.LanguageServices.CSharp)
// 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.Collections.Immutable;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.VisualStudio.LanguageServices.Implementation.Library.ObjectBrowser;
using Microsoft.VisualStudio.Shell.Interop;
 
namespace Microsoft.VisualStudio.LanguageServices.CSharp.ObjectBrowser;
 
internal sealed class DescriptionBuilder(
    IVsObjectBrowserDescription3 description,
    ObjectBrowserLibraryManager libraryManager,
    ObjectListItem listItem,
    Project project) : AbstractDescriptionBuilder(description, libraryManager, listItem, project)
{
    protected override void BuildNamespaceDeclaration(INamespaceSymbol namespaceSymbol, _VSOBJDESCOPTIONS options)
    {
        AddText("namespace ");
        AddName(namespaceSymbol.ToDisplayString());
    }
 
    protected override async Task BuildDelegateDeclarationAsync(
        INamedTypeSymbol typeSymbol, _VSOBJDESCOPTIONS options, CancellationToken cancellationToken)
    {
        Debug.Assert(typeSymbol.TypeKind == TypeKind.Delegate);
 
        BuildTypeModifiers(typeSymbol);
        AddText("delegate ");
 
        var delegateInvokeMethod = typeSymbol.DelegateInvokeMethod;
 
        await AddTypeLinkAsync(delegateInvokeMethod.ReturnType, LinkFlags.None, cancellationToken).ConfigureAwait(true);
        AddText(" ");
 
        var typeQualificationStyle = (options & _VSOBJDESCOPTIONS.ODO_USEFULLNAME) != 0
            ? SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces
            : SymbolDisplayTypeQualificationStyle.NameOnly;
 
        var typeNameFormat = new SymbolDisplayFormat(
            typeQualificationStyle: typeQualificationStyle,
            genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters | SymbolDisplayGenericsOptions.IncludeVariance);
 
        AddName(typeSymbol.ToDisplayString(typeNameFormat));
 
        AddText("(");
        await BuildParameterListAsync(delegateInvokeMethod.Parameters, cancellationToken).ConfigureAwait(true);
        AddText(")");
 
        if (typeSymbol.IsGenericType)
            await BuildGenericConstraintsAsync(typeSymbol, cancellationToken).ConfigureAwait(true);
    }
 
    protected override async Task BuildTypeDeclarationAsync(
        INamedTypeSymbol typeSymbol, _VSOBJDESCOPTIONS options, CancellationToken cancellationToken)
    {
        BuildTypeModifiers(typeSymbol);
 
        switch (typeSymbol.TypeKind)
        {
            case TypeKind.Enum:
                AddText("enum ");
                break;
 
            case TypeKind.Struct:
                AddText("struct ");
                break;
 
            case TypeKind.Interface:
                AddText("interface ");
                break;
 
            case TypeKind.Class:
                AddText("class ");
                break;
 
            default:
                Debug.Fail("Invalid type kind encountered: " + typeSymbol.TypeKind.ToString());
                break;
        }
 
        var typeNameFormat = new SymbolDisplayFormat(
            genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters | SymbolDisplayGenericsOptions.IncludeVariance);
 
        AddName(typeSymbol.ToDisplayString(typeNameFormat));
 
        if (typeSymbol.TypeKind == TypeKind.Enum)
        {
            var underlyingType = typeSymbol.EnumUnderlyingType;
            if (underlyingType != null)
            {
                if (underlyingType.SpecialType != SpecialType.System_Int32)
                {
                    AddText(" : ");
                    await AddTypeLinkAsync(underlyingType, LinkFlags.None, cancellationToken).ConfigureAwait(true);
                }
            }
        }
        else
        {
            var baseType = typeSymbol.BaseType;
            if (baseType != null)
            {
                if (baseType.SpecialType is not SpecialType.System_Object and
                    not SpecialType.System_Delegate and
                    not SpecialType.System_MulticastDelegate and
                    not SpecialType.System_Enum and
                    not SpecialType.System_ValueType)
                {
                    AddText(" : ");
                    await AddTypeLinkAsync(baseType, LinkFlags.None, cancellationToken).ConfigureAwait(true);
                }
            }
        }
 
        if (typeSymbol.IsGenericType)
            await BuildGenericConstraintsAsync(typeSymbol, cancellationToken).ConfigureAwait(true);
    }
 
    private void BuildAccessibility(ISymbol symbol)
    {
        switch (symbol.DeclaredAccessibility)
        {
            case Accessibility.Public:
                AddText("public ");
                break;
 
            case Accessibility.Private:
                AddText("private ");
                break;
 
            case Accessibility.Protected:
                AddText("protected ");
                break;
 
            case Accessibility.Internal:
                AddText("internal ");
                break;
 
            case Accessibility.ProtectedOrInternal:
                AddText("protected internal ");
                break;
 
            case Accessibility.ProtectedAndInternal:
                AddText("private protected ");
                break;
 
            default:
                AddText("internal ");
                break;
        }
    }
 
    private void BuildTypeModifiers(INamedTypeSymbol typeSymbol)
    {
        BuildAccessibility(typeSymbol);
 
        if (typeSymbol.IsStatic)
        {
            AddText("static ");
        }
 
        if (typeSymbol.IsAbstract &&
            typeSymbol.TypeKind != TypeKind.Interface)
        {
            AddText("abstract ");
        }
 
        if (typeSymbol.IsSealed &&
            typeSymbol.TypeKind != TypeKind.Struct &&
            typeSymbol.TypeKind != TypeKind.Enum &&
            typeSymbol.TypeKind != TypeKind.Delegate)
        {
            AddText("sealed ");
        }
    }
 
    protected override async Task BuildMethodDeclarationAsync(
        IMethodSymbol methodSymbol, _VSOBJDESCOPTIONS options, CancellationToken cancellationToken)
    {
        BuildMemberModifiers(methodSymbol);
 
        if (methodSymbol.MethodKind is not MethodKind.Constructor and
            not MethodKind.Destructor and
            not MethodKind.StaticConstructor and
            not MethodKind.Conversion)
        {
            await AddTypeLinkAsync(methodSymbol.ReturnType, LinkFlags.None, cancellationToken).ConfigureAwait(true);
            AddText(" ");
        }
 
        if (methodSymbol.MethodKind == MethodKind.Conversion)
        {
            switch (methodSymbol.Name)
            {
                case WellKnownMemberNames.ImplicitConversionName:
                    AddName("implicit operator ");
                    break;
 
                case WellKnownMemberNames.ExplicitConversionName:
                    AddName("explicit operator ");
                    break;
            }
 
            await AddTypeLinkAsync(methodSymbol.ReturnType, LinkFlags.None, cancellationToken).ConfigureAwait(true);
        }
        else
        {
            var methodNameFormat = new SymbolDisplayFormat(
                genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters | SymbolDisplayGenericsOptions.IncludeVariance);
 
            AddName(methodSymbol.ToDisplayString(methodNameFormat));
        }
 
        AddText("(");
 
        if (methodSymbol.IsExtensionMethod)
        {
            AddText("this ");
        }
 
        await BuildParameterListAsync(methodSymbol.Parameters, cancellationToken).ConfigureAwait(true);
        AddText(")");
 
        if (methodSymbol.IsGenericMethod)
            await BuildGenericConstraintsAsync(methodSymbol, cancellationToken).ConfigureAwait(true);
    }
 
    private void BuildMemberModifiers(ISymbol memberSymbol)
    {
        if (memberSymbol.ContainingType != null && memberSymbol.ContainingType.TypeKind == TypeKind.Interface)
        {
            return;
        }
 
        var methodSymbol = memberSymbol as IMethodSymbol;
        var fieldSymbol = memberSymbol as IFieldSymbol;
 
        if (methodSymbol != null &&
            methodSymbol.MethodKind == MethodKind.Destructor)
        {
            return;
        }
 
        if (fieldSymbol != null &&
            fieldSymbol.ContainingType.TypeKind == TypeKind.Enum)
        {
            return;
        }
 
        // TODO: 'new' modifier isn't exposed on symbols. Do we need it?
 
        // Note: we don't display the access modifier for static constructors
        if (methodSymbol == null ||
            methodSymbol.MethodKind != MethodKind.StaticConstructor)
        {
            BuildAccessibility(memberSymbol);
        }
 
        if (memberSymbol.RequiresUnsafeModifier())
        {
            AddText("unsafe ");
        }
 
        // Note: we don't display 'static' for constant fields
        if (memberSymbol.IsStatic &&
            (fieldSymbol == null || !fieldSymbol.IsConst))
        {
            AddText("static ");
        }
 
        if (memberSymbol.IsExtern)
        {
            AddText("extern ");
        }
 
        if (fieldSymbol != null && fieldSymbol.IsReadOnly)
        {
            AddText("readonly ");
        }
 
        if (fieldSymbol != null && fieldSymbol.IsConst)
        {
            AddText("const ");
        }
 
        if (fieldSymbol != null && fieldSymbol.IsVolatile)
        {
            AddText("volatile ");
        }
 
        if (memberSymbol.IsAbstract)
        {
            AddText("abstract ");
        }
        else if (memberSymbol.IsOverride)
        {
            if (memberSymbol.IsSealed)
            {
                AddText("sealed ");
            }
 
            AddText("override ");
        }
        else if (memberSymbol.IsVirtual)
        {
            AddText("virtual ");
        }
    }
 
    private async Task BuildGenericConstraintsAsync(INamedTypeSymbol typeSymbol, CancellationToken cancellationToken)
    {
        foreach (var typeParameterSymbol in typeSymbol.TypeParameters)
            await BuildConstraintsAsync(typeParameterSymbol, cancellationToken).ConfigureAwait(true);
    }
 
    private async Task BuildGenericConstraintsAsync(IMethodSymbol methodSymbol, CancellationToken cancellationToken)
    {
        foreach (var typeParameterSymbol in methodSymbol.TypeParameters)
            await BuildConstraintsAsync(typeParameterSymbol, cancellationToken).ConfigureAwait(true);
    }
 
    private async Task BuildConstraintsAsync(ITypeParameterSymbol typeParameterSymbol, CancellationToken cancellationToken)
    {
        if (typeParameterSymbol.ConstraintTypes.Length == 0 &&
            !typeParameterSymbol.HasConstructorConstraint &&
            !typeParameterSymbol.HasReferenceTypeConstraint &&
            !typeParameterSymbol.HasValueTypeConstraint &&
            !typeParameterSymbol.AllowsRefLikeType)
        {
            return;
        }
 
        AddLineBreak();
        AddText("\t");
        AddText("where ");
        AddName(typeParameterSymbol.Name);
        AddText(" : ");
 
        var isFirst = true;
 
        if (typeParameterSymbol.HasReferenceTypeConstraint)
        {
            if (!isFirst)
            {
                AddComma();
            }
 
            AddText("class");
            isFirst = false;
        }
 
        if (typeParameterSymbol.HasValueTypeConstraint)
        {
            if (!isFirst)
            {
                AddComma();
            }
 
            AddText("struct");
            isFirst = false;
        }
 
        foreach (var constraintType in typeParameterSymbol.ConstraintTypes)
        {
            if (!isFirst)
            {
                AddComma();
            }
 
            await AddTypeLinkAsync(constraintType, LinkFlags.None, cancellationToken).ConfigureAwait(true);
            isFirst = false;
        }
 
        if (typeParameterSymbol.HasConstructorConstraint)
        {
            if (!isFirst)
            {
                AddComma();
            }
 
            AddText("new()");
            isFirst = false;
        }
 
        if (typeParameterSymbol.AllowsRefLikeType)
        {
            if (!isFirst)
            {
                AddComma();
            }
 
            AddText("allows ref struct");
        }
    }
 
    private async Task BuildParameterListAsync(
        ImmutableArray<IParameterSymbol> parameters, CancellationToken cancellationToken)
    {
        var count = parameters.Length;
        if (count == 0)
        {
            return;
        }
 
        for (var i = 0; i < count; i++)
        {
            if (i > 0)
            {
                AddComma();
            }
 
            var current = parameters[i];
 
            if (current.IsOptional)
            {
                AddText("[");
            }
 
            if (current.RefKind == RefKind.Ref)
            {
                AddText("ref ");
            }
            else if (current.RefKind == RefKind.Out)
            {
                AddText("out ");
            }
 
            if (current.IsParams)
            {
                AddText("params ");
            }
 
            await AddTypeLinkAsync(current.Type, LinkFlags.None, cancellationToken).ConfigureAwait(true);
            AddText(" ");
            AddParam(current.Name);
 
            if (current.HasExplicitDefaultValue)
            {
                AddText(" = ");
                if (current.ExplicitDefaultValue == null)
                {
                    AddText("null");
                }
                else
                {
                    AddText(current.ExplicitDefaultValue.ToString());
                }
            }
 
            if (current.IsOptional)
            {
                AddText("]");
            }
        }
    }
 
    protected override async Task BuildFieldDeclarationAsync(
        IFieldSymbol fieldSymbol, _VSOBJDESCOPTIONS options, CancellationToken cancellationToken)
    {
        BuildMemberModifiers(fieldSymbol);
 
        if (fieldSymbol.ContainingType.TypeKind != TypeKind.Enum)
        {
            await AddTypeLinkAsync(fieldSymbol.Type, LinkFlags.None, cancellationToken).ConfigureAwait(true);
            AddText(" ");
        }
 
        AddName(fieldSymbol.Name);
    }
 
    protected override async Task BuildPropertyDeclarationAsync(
        IPropertySymbol propertySymbol, _VSOBJDESCOPTIONS options, CancellationToken cancellationToken)
    {
        BuildMemberModifiers(propertySymbol);
 
        await AddTypeLinkAsync(propertySymbol.Type, LinkFlags.None, cancellationToken).ConfigureAwait(true);
        AddText(" ");
 
        if (propertySymbol.IsIndexer)
        {
            AddName("this");
            AddText("[");
            await BuildParameterListAsync(propertySymbol.Parameters, cancellationToken).ConfigureAwait(true);
            AddText("]");
        }
        else
        {
            AddName(propertySymbol.Name);
        }
 
        AddText(" { ");
 
        if (propertySymbol.GetMethod != null)
        {
            if (propertySymbol.GetMethod.DeclaredAccessibility != propertySymbol.DeclaredAccessibility)
            {
                BuildAccessibility(propertySymbol.GetMethod);
            }
 
            AddText("get; ");
        }
 
        if (propertySymbol.SetMethod != null)
        {
            if (propertySymbol.SetMethod.DeclaredAccessibility != propertySymbol.DeclaredAccessibility)
            {
                BuildAccessibility(propertySymbol.SetMethod);
            }
 
            AddText("set; ");
        }
 
        AddText("}");
    }
 
    protected override async Task BuildEventDeclarationAsync(
        IEventSymbol eventSymbol, _VSOBJDESCOPTIONS options, CancellationToken cancellationToken)
    {
        BuildMemberModifiers(eventSymbol);
 
        AddText("event ");
 
        await AddTypeLinkAsync(eventSymbol.Type, LinkFlags.None, cancellationToken).ConfigureAwait(true);
        AddText(" ");
 
        AddName(eventSymbol.Name);
    }
}