File: CodeLens\CSharpCodeLensDisplayInfoService.cs
Web Access
Project: ..\..\..\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);
        }
    }
}