File: GoToDefinition\AbstractGoToDefinitionSymbolService.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.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.SymbolMapping;
using Microsoft.CodeAnalysis.Text;
 
namespace Microsoft.CodeAnalysis.GoToDefinition;
 
internal abstract class AbstractGoToDefinitionSymbolService : IGoToDefinitionSymbolService
{
    protected abstract Task<ISymbol> FindRelatedExplicitlyDeclaredSymbolAsync(Project project, ISymbol symbol, CancellationToken cancellationToken);
 
    protected abstract int? GetTargetPositionIfControlFlow(SemanticModel semanticModel, SyntaxToken token);
 
    public async Task<(ISymbol? symbol, Project project, TextSpan boundSpan)> GetSymbolProjectAndBoundSpanAsync(
        Document document, SemanticModel semanticModel, int position, CancellationToken cancellationToken)
    {
        var project = document.Project;
        var services = document.Project.Solution.Services;
 
        var semanticInfo = await SymbolFinder.GetSemanticInfoAtPositionAsync(semanticModel, position, services, cancellationToken).ConfigureAwait(false);
 
        // Prefer references to declarations. It's more likely that the user is attempting to 
        // go to a definition at some other location, rather than the definition they're on. 
        // This can happen when a token is at a location that is both a reference and a definition.
        // For example, on an anonymous type member declaration.
        var symbol = semanticInfo.AliasSymbol
            ?? semanticInfo.ReferencedSymbols.FirstOrDefault()
            ?? semanticInfo.DeclaredSymbol
            ?? semanticInfo.Type;
 
        if (symbol is null)
        {
            return (null, project, semanticInfo.Span);
        }
 
        // If this document is not in the primary workspace, we may want to search for results
        // in a solution different from the one we started in. Use the starting workspace's
        // ISymbolMappingService to get a context for searching in the proper solution.
        // For example when looking at a file from Source Link, it could be a partial type that
        // only has a subset of the type actually part of the project (because the rest hasn't been
        // downloaded) so we want to ensure we're navigating based on the original metadata symbol.
        var mappingService = services.GetRequiredService<ISymbolMappingService>();
        var mapping = await mappingService.MapSymbolAsync(document, symbol, cancellationToken).ConfigureAwait(false);
 
        // If the mapping fails, we proceed as normal with the symbol we originally found.
        if (mapping is not null)
        {
            symbol = mapping.Symbol;
            project = mapping.Project;
        }
 
        var explicitlyDeclaredSymbol = await FindRelatedExplicitlyDeclaredSymbolAsync(project, symbol, cancellationToken).ConfigureAwait(false);
        return (explicitlyDeclaredSymbol, project, semanticInfo.Span);
    }
 
    public async Task<(int? targetPosition, TextSpan tokenSpan)> GetTargetIfControlFlowAsync(
        Document document, SemanticModel semanticModel, int position, CancellationToken cancellationToken)
    {
        var syntaxTree = semanticModel.SyntaxTree;
        var syntaxFacts = document.GetRequiredLanguageService<ISyntaxFactsService>();
        var token = await syntaxTree.GetTouchingTokenAsync(
            semanticModel, position, syntaxFacts.IsBindableToken, cancellationToken, findInsideTrivia: true).ConfigureAwait(false);
 
        if (token == default)
            return default;
 
        return (GetTargetPositionIfControlFlow(semanticModel, token), token.Span);
    }
}