File: CodeLens\CSharpCodeLensDisplayInfoService.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.
 
#nullable disable
 
using System;
using System.Composition;
using Microsoft.CodeAnalysis.CodeLens;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.LanguageService;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Host.Mef;
 
namespace Microsoft.CodeAnalysis.CSharp.CodeLens;
 
[ExportLanguageService(typeof(ICodeLensDisplayInfoService), LanguageNames.CSharp), Shared]
internal sealed class CSharpCodeLensDisplayInfoService : ICodeLensDisplayInfoService
{
    private static readonly SymbolDisplayFormat Format =
        SymbolDisplayFormat.CSharpErrorMessageFormat.RemoveMemberOptions(
            SymbolDisplayMemberOptions.IncludeExplicitInterface);
 
    [ImportingConstructor]
    [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
    public CSharpCodeLensDisplayInfoService()
    {
    }
 
    /// <summary>
    /// Returns the node that should be displayed
    /// </summary>
    public SyntaxNode GetDisplayNode(SyntaxNode node)
    {
        while (true)
        {
            switch (node.Kind())
            {
                // LocalDeclarations do not have symbols themselves, you need a variable declarator
                case SyntaxKind.LocalDeclarationStatement:
                    var localDeclarationNode = (LocalDeclarationStatementSyntax)node;
                    node = localDeclarationNode.Declaration.Variables.FirstOrDefault();
                    continue;
 
                // Field and event declarations do not have symbols themselves, you need a variable declarator
                case SyntaxKind.FieldDeclaration:
                case SyntaxKind.EventFieldDeclaration:
                    var fieldNode = (BaseFieldDeclarationSyntax)node;
                    node = fieldNode.Declaration.Variables.FirstOrDefault();
                    continue;
 
                // Variable is a field without access modifier. Parent is FieldDeclaration
                case SyntaxKind.VariableDeclaration:
                    node = node.Parent;
                    continue;
 
                // Built in types   
                case SyntaxKind.PredefinedType:
                    node = node.Parent;
                    continue;
 
                case SyntaxKind.MultiLineDocumentationCommentTrivia:
                case SyntaxKind.SingleLineDocumentationCommentTrivia:
                    // For DocumentationCommentTrivia node, node.Parent is null. Obtain parent through ParentTrivia.Token
                    if (node.IsStructuredTrivia)
                    {
                        var structuredTriviaSyntax = (StructuredTriviaSyntax)node;
                        node = structuredTriviaSyntax.ParentTrivia.Token.Parent;
                        continue;
                    }
 
                    return null;
 
                default:
                    return node;
            }
        }
    }
 
    /// <summary>
    /// Gets the DisplayName for the given node.
    /// </summary>
    public string GetDisplayName(SemanticModel semanticModel, SyntaxNode node)
    {
        if (node == null)
        {
            return FeaturesResources.paren_Unknown_paren;
        }
 
        if (CSharpSyntaxFacts.Instance.IsGlobalAssemblyAttribute(node))
        {
            return "assembly: " + node.ConvertToSingleLine();
        }
 
        // Don't discriminate between getters and setters for indexers
        if (node.Parent.IsKind(SyntaxKind.AccessorList) &&
            node.Parent.Parent.IsKind(SyntaxKind.IndexerDeclaration))
        {
            return GetDisplayName(semanticModel, node.Parent.Parent);
        }
 
        switch (node.Kind())
        {
            case SyntaxKind.ConstructorDeclaration:
                {
                    // The constructor's name will be the name of the class, not ctor like we want
                    var symbol = semanticModel.GetDeclaredSymbol(node);
                    var displayName = symbol.ToDisplayString(Format);
                    var openParenIndex = displayName.IndexOf('(');
                    var lastDotBeforeOpenParenIndex = displayName.LastIndexOf('.', openParenIndex, openParenIndex);
 
                    var constructorName = symbol.IsStatic ? "cctor" : "ctor";
 
                    return displayName[..(lastDotBeforeOpenParenIndex + 1)] +
                           constructorName +
                           displayName[openParenIndex..];
                }
 
            case SyntaxKind.IndexerDeclaration:
                {
                    // The name will be "namespace.class.this[type] - we want "namespace.class[type] Indexer"
                    var symbol = semanticModel.GetDeclaredSymbol(node);
                    var displayName = symbol.ToDisplayString(Format);
                    var openBracketIndex = displayName.IndexOf('[');
                    var lastDotBeforeOpenBracketIndex = displayName.LastIndexOf('.', openBracketIndex, openBracketIndex);
 
                    return displayName[..lastDotBeforeOpenBracketIndex] +
                           displayName[openBracketIndex..] +
                           " Indexer";
                }
 
            case SyntaxKind.OperatorDeclaration:
                {
                    // The name will be "namespace.class.operator +(type)" - we want namespace.class.+(type) Operator
                    var symbol = semanticModel.GetDeclaredSymbol(node);
                    var displayName = symbol.ToDisplayString(Format);
                    var spaceIndex = displayName.IndexOf(' ');
                    var lastDotBeforeSpaceIndex = displayName.LastIndexOf('.', spaceIndex, spaceIndex);
 
                    return displayName[..(lastDotBeforeSpaceIndex + 1)] +
                           displayName[(spaceIndex + 1)..] +
                           " Operator";
                }
 
            case SyntaxKind.ConversionOperatorDeclaration:
                {
                    // The name will be "namespace.class.operator +(type)" - we want namespace.class.+(type) Operator
                    var symbol = semanticModel.GetDeclaredSymbol(node);
                    var displayName = symbol.ToDisplayString(Format);
                    var firstSpaceIndex = displayName.IndexOf(' ');
                    var secondSpaceIndex = displayName.IndexOf(' ', firstSpaceIndex + 1);
                    var lastDotBeforeSpaceIndex = displayName.LastIndexOf('.', firstSpaceIndex, firstSpaceIndex);
 
                    return displayName[..(lastDotBeforeSpaceIndex + 1)] +
                           displayName[(secondSpaceIndex + 1)..] +
                           " Operator";
                }
 
            case SyntaxKind.UsingDirective:
                {
                    // We want to see usings formatted as simply "Using", prefaced by the namespace they are in
                    var enclosingScopeString = GetEnclosingScopeString(node, semanticModel, Format);
                    return string.IsNullOrEmpty(enclosingScopeString) ? "Using" : enclosingScopeString + " Using";
                }
 
            case SyntaxKind.ExternAliasDirective:
                {
                    // We want to see aliases formatted as "Alias", prefaced by their enclosing scope, if any
                    var enclosingScopeString = GetEnclosingScopeString(node, semanticModel, Format);
                    return string.IsNullOrEmpty(enclosingScopeString) ? "Alias" : enclosingScopeString + " Alias";
                }
 
            default:
                {
                    var symbol = semanticModel.GetDeclaredSymbol(node);
                    return symbol != null ? symbol.ToDisplayString(Format) : FeaturesResources.paren_Unknown_paren;
                }
        }
    }
 
    private static string GetEnclosingScopeString(SyntaxNode node, SemanticModel semanticModel, SymbolDisplayFormat symbolDisplayFormat)
    {
        var scopeNode = node;
        while (scopeNode != null && !SyntaxFacts.IsNamespaceMemberDeclaration(scopeNode.Kind()))
        {
            scopeNode = scopeNode.Parent;
        }
 
        if (scopeNode == null)
        {
            return null;
        }
 
        var scopeSymbol = semanticModel.GetDeclaredSymbol(scopeNode);
        return scopeSymbol.ToDisplayString(symbolDisplayFormat);
    }
}