File: LanguageServiceIndexFormat\SymbolMoniker.cs
Web Access
Project: src\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.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;
 
namespace Microsoft.CodeAnalysis.LanguageServerIndexFormat;
 
internal sealed class SymbolMoniker(string scheme, string identifier)
{
    public string Scheme { get; } = scheme;
 
    public string Identifier { get; } = identifier;
 
    public static bool HasMoniker(ISymbol symbol)
    {
        // We don't create monikers for symbols with type substitutions
        if (!symbol.OriginalDefinition.Equals(symbol))
            return false;
 
        // Don't create monikers for all local things that cannot escape outside of a single file: downstream consumers simply treat this as meaning a references/definition result
        // doesn't need to be stitched together across files or multiple projects or repositories.
        if (symbol.Kind is SymbolKind.Local or
            SymbolKind.RangeVariable or
            SymbolKind.Label or
            SymbolKind.Alias)
        {
            return false;
        }
 
        // Don't create monikers for built in-operators. We could pick some sort of moniker for these, but I doubt anybody really needs to search for all uses of
        // + in the world's projects at once.
        if (symbol is IMethodSymbol method && method.MethodKind == MethodKind.BuiltinOperator)
            return false;
 
        // TODO: some symbols for things some things in crefs don't have a ContainingAssembly. We'll skip those for now but do
        // want those to work.
        if (symbol.Kind != SymbolKind.Namespace && symbol.ContainingAssembly == null)
            return false;
 
        return true;
    }
 
    public static SymbolMoniker Create(ISymbol symbol)
    {
        // This uses the existing format that earlier prototypes of the Roslyn LSIF tool implemented; a different format may make more sense long term, but changing the
        // moniker makes it difficult for other systems that have older LSIF indexes to the connect the two indexes together.
 
        if (!HasMoniker(symbol))
            throw new InvalidOperationException($"'{symbol}' does not have a moniker.");
 
        // Namespaces are special: they're just a name that exists in the ether between compilations
        if (symbol.Kind == SymbolKind.Namespace)
        {
            return new SymbolMoniker(WellKnownSymbolMonikerSchemes.DotnetNamespace, symbol.ToDisplayString());
        }
 
        var symbolMoniker = symbol.ContainingAssembly.Name + "#";
 
        if (symbol.Kind == SymbolKind.Parameter)
        {
            symbolMoniker += GetRequiredDocumentationCommentId(symbol.ContainingSymbol) + "#" + symbol.Name;
        }
        else
        {
            symbolMoniker += GetRequiredDocumentationCommentId(symbol);
        }
 
        return new SymbolMoniker(WellKnownSymbolMonikerSchemes.DotnetXmlDoc, symbolMoniker);
 
        static string GetRequiredDocumentationCommentId(ISymbol symbol)
        {
            symbol = symbol.OriginalDefinition;
            var documentationCommentId = symbol.GetDocumentationCommentId();
 
            if (documentationCommentId == null)
            {
                throw new Exception($"Unable to get documentation comment ID for {symbol.ToDisplayString()}");
            }
 
            return documentationCommentId;
        }
    }
}