/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/EditorFeatures/Core/Navigation/AbstractDefinitionLocationService.cs
311 строк
16 KB
Cyrus Najmabadi
Compute if a doc is generated code or not in OOP not on the host
17 окт 2025, 03:37
17 окт 2025, 03:37
6419c22
Код
Авторство
О чём код?
// 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.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Classification; using Microsoft.CodeAnalysis.Editor.Host; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.FindUsages; using Microsoft.CodeAnalysis.GoToDefinition; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.Navigation; internal abstract partial class AbstractDefinitionLocationService( IThreadingContext threadingContext, IStreamingFindUsagesPresenter streamingPresenter) : IDefinitionLocationService { private readonly IThreadingContext _threadingContext = threadingContext; private readonly IStreamingFindUsagesPresenter _streamingPresenter = streamingPresenter; private static Task<INavigableLocation?> GetNavigableLocationAsync( Document document, int position, CancellationToken cancellationToken) { var solution = document.Project.Solution; var workspace = solution.Workspace; var service = workspace.Services.GetRequiredService<IDocumentNavigationService>(); return service.GetLocationForPositionAsync( workspace, document.Id, position, cancellationToken); } public async Task<DefinitionLocation?> GetDefinitionLocationAsync(Document document, int position, CancellationToken cancellationToken) { var symbolService = document.GetRequiredLanguageService<IGoToDefinitionSymbolService>(); // We want to compute this as quickly as possible so that the symbol be squiggled and navigated to. We // don't want to wait on expensive operations like computing source-generators or skeletons if we can avoid // it. So first try with a frozen document, then fallback to a normal document. This mirrors how go-to-def // works as well. return await GetDefinitionLocationWorkerAsync(document.WithFrozenPartialSemantics(cancellationToken)).ConfigureAwait(false) ?? await GetDefinitionLocationWorkerAsync(document).ConfigureAwait(false); async ValueTask<DefinitionLocation?> GetDefinitionLocationWorkerAsync(Document document) { // We don't need nullable information to compute the symbol. So avoid expensive work computing this. var semanticModel = await document.GetRequiredNullableDisabledSemanticModelAsync(cancellationToken).ConfigureAwait(false); return await GetControlFlowTargetLocationAsync(document, semanticModel).ConfigureAwait(false) ?? await GetSymbolLocationAsync(document, semanticModel).ConfigureAwait(false); } async ValueTask<DefinitionLocation?> GetControlFlowTargetLocationAsync( Document document, SemanticModel semanticModel) { var (controlFlowTarget, controlFlowSpan) = await symbolService.GetTargetIfControlFlowAsync( document, semanticModel, position, cancellationToken).ConfigureAwait(false); if (controlFlowTarget == null) return null; var location = await GetNavigableLocationAsync( document, controlFlowTarget.Value, cancellationToken).ConfigureAwait(false); return location is null ? null : new DefinitionLocation(location, new DocumentSpan(document, controlFlowSpan)); } async ValueTask<DefinitionLocation?> GetSymbolLocationAsync( Document document, SemanticModel semanticModel) { // Try to compute the referenced symbol and attempt to go to definition for the symbol. var (symbol, project, span) = await symbolService.GetSymbolProjectAndBoundSpanAsync( document, semanticModel, position, cancellationToken).ConfigureAwait(false); if (symbol is null) return null; // if the symbol only has a single source location, and we're already on it, // try to see if there's a better symbol we could navigate to. var remappedLocation = await GetAlternativeLocationIfAlreadyOnDefinitionAsync( project, position, symbol, originalDocument: document, cancellationToken).ConfigureAwait(false); if (remappedLocation != null) return new DefinitionLocation(remappedLocation, new DocumentSpan(document, span)); var isThirdPartyNavigationAllowed = await IsThirdPartyNavigationAllowedAsync( symbol, position, document, cancellationToken).ConfigureAwait(false); var solution = project.Solution; var regularDefinitions = await GoToDefinitionFeatureHelpers.GetDefinitionsAsync( symbol, solution, isThirdPartyNavigationAllowed, cancellationToken).ConfigureAwait(false); var interceptorDefinitions = await GetInterceptorDefinitionsAsync( solution, document, span, cancellationToken).ConfigureAwait(false); var symbolDisplayName = FindUsagesHelpers.GetDisplayName(symbol); var title = interceptorDefinitions.Length == 0 ? string.Format(EditorFeaturesResources._0_declarations, symbolDisplayName) : string.Format(EditorFeaturesResources._0_declarations_and_interceptors, symbolDisplayName); var allDefinitions = regularDefinitions.Concat(interceptorDefinitions); var location = await _streamingPresenter.GetStreamingLocationAsync( _threadingContext, solution.Workspace, title, allDefinitions, cancellationToken).ConfigureAwait(false); if (location is null) return null; return new DefinitionLocation(location, new DocumentSpan(document, span)); } async ValueTask<ImmutableArray<DefinitionItem>> GetInterceptorDefinitionsAsync( Solution solution, Document document, TextSpan span, CancellationToken cancellationToken) { var semanticFacts = document.GetRequiredLanguageService<ISemanticFactsService>(); var interceptorSymbol = await semanticFacts.GetInterceptorSymbolAsync(document, span.Start, cancellationToken).ConfigureAwait(false); return await GoToDefinitionFeatureHelpers.GetDefinitionsAsync( interceptorSymbol, solution, thirdPartyNavigationAllowed: false, cancellationToken).ConfigureAwait(false); } } /// <summary> /// Attempts to find a better definition for the symbol, if the user is already on the definition of it. /// </summary> /// <param name="project">The project context to use for finding symbols</param> /// <param name="originalDocument">The document the user is navigating from. This may not be part of the project supplied.</param> private async Task<INavigableLocation?> GetAlternativeLocationIfAlreadyOnDefinitionAsync( Project project, int position, ISymbol symbol, Document originalDocument, CancellationToken cancellationToken) { var solution = project.Solution; if (symbol.DeclaringSyntaxReferences is not [{ SyntaxTree: { } definitionTree, Span: var definitionSpan }]) return null; if (!definitionSpan.IntersectsWith(position)) return null; var definitionDocument = solution.GetDocument(definitionTree); if (definitionDocument != originalDocument) return null; // Ok, we were already on the definition. Look for better symbols we could show results for instead. This can be // expanded with other mappings in the future if appropriate. return await TryGetExplicitInterfaceLocationAsync().ConfigureAwait(false) ?? await TryGetInterceptedLocationAsync().ConfigureAwait(false) ?? await TryGetOtherPartOfPartialAsync().ConfigureAwait(false); async ValueTask<INavigableLocation?> TryGetExplicitInterfaceLocationAsync() { var interfaceImpls = symbol.ExplicitOrImplicitInterfaceImplementations(); if (interfaceImpls.Length == 0) return null; var title = string.Format(EditorFeaturesResources._0_implemented_members, FindUsagesHelpers.GetDisplayName(symbol)); using var _ = ArrayBuilder<DefinitionItem>.GetInstance(out var builder); foreach (var impl in interfaceImpls) { builder.AddRange(await GoToDefinitionFeatureHelpers.GetDefinitionsAsync( impl, solution, thirdPartyNavigationAllowed: false, cancellationToken).ConfigureAwait(false)); } var definitions = builder.ToImmutable(); return await _streamingPresenter.GetStreamingLocationAsync( _threadingContext, solution.Workspace, title, definitions, cancellationToken).ConfigureAwait(false); } async ValueTask<INavigableLocation?> TryGetInterceptedLocationAsync() { if (symbol is not IMethodSymbol method) return null; // Find attributes of the form: [InterceptsLocationAttribute(version: 1, data: "...")]; var attributes = method.GetAttributes(); var interceptsLocationDatas = InterceptsLocationUtilities.GetInterceptsLocationData(attributes); if (interceptsLocationDatas.Length == 0) return null; using var _ = ArrayBuilder<DocumentSpan>.GetInstance(out var documentSpans); foreach (var (contentHash, position) in interceptsLocationDatas) { var document = await project.GetDocumentAsync(contentHash, cancellationToken).ConfigureAwait(false); if (document != null) { var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); if (position >= 0 && position < root.FullSpan.Length) { var token = root.FindToken(position); documentSpans.Add(new DocumentSpan(document, token.Span)); } } } documentSpans.RemoveDuplicates(); if (documentSpans.Count == 0) { return null; } else if (documentSpans.Count == 1) { // Just one document span this mapped to. Navigate directly do that. return await documentSpans[0].GetNavigableLocationAsync(cancellationToken).ConfigureAwait(false); } else { var title = string.Format(EditorFeaturesResources._0_intercepted_locations, FindUsagesHelpers.GetDisplayName(method)); var definitionItem = await method.ToNonClassifiedDefinitionItemAsync( solution, includeHiddenLocations: true, cancellationToken).ConfigureAwait(false); var referenceItems = new List<SourceReferenceItem>(capacity: documentSpans.Count); var classificationOptions = ClassificationOptions.Default with { ClassifyObsoleteSymbols = false }; foreach (var documentSpan in documentSpans) { var classifiedSpans = await ClassifiedSpansAndHighlightSpanFactory.ClassifyAsync( documentSpan, classifiedSpans: null, classificationOptions, cancellationToken).ConfigureAwait(false); referenceItems.Add(new SourceReferenceItem( definitionItem, documentSpan, classifiedSpans, SymbolUsageInfo.None, additionalProperties: [])); } // Multiple document spans this mapped to. Show them all. return new NavigableLocation(async (options, cancellationToken) => { // Can only navigate or present items on UI thread. await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); // We have multiple definitions, or we have definitions with multiple locations. Present this to the // user so they can decide where they want to go to. // // We ignore the cancellation token returned by StartSearch as we're in a context where // we've computed all the results and we're synchronously populating the UI with it. var (context, _) = _streamingPresenter.StartSearch(title, new StreamingFindUsagesPresenterOptions(SupportsReferences: true)); try { await context.OnDefinitionFoundAsync(definitionItem, cancellationToken).ConfigureAwait(false); await context.OnReferencesFoundAsync(referenceItems.AsAsyncEnumerable(), cancellationToken).ConfigureAwait(false); } finally { await context.OnCompletedAsync(cancellationToken).ConfigureAwait(false); } return true; }); } } async ValueTask<INavigableLocation?> TryGetOtherPartOfPartialAsync() { ISymbol? otherPart = symbol is IMethodSymbol method ? method.PartialDefinitionPart ?? method.PartialImplementationPart : null; otherPart ??= symbol is IPropertySymbol property ? property.PartialDefinitionPart ?? property.PartialImplementationPart : null; if (otherPart is null || Equals(symbol, otherPart)) return null; if (otherPart.Locations is not [{ SourceTree: { } sourceTree, SourceSpan: var span }]) return null; var document = solution.GetDocument(sourceTree); if (document is null) return null; var documentSpan = new DocumentSpan(document, span); return await documentSpan.GetNavigableLocationAsync(cancellationToken).ConfigureAwait(false); } } private static async Task<bool> IsThirdPartyNavigationAllowedAsync( ISymbol symbolToNavigateTo, int caretPosition, Document document, CancellationToken cancellationToken) { var syntaxRoot = document.GetRequiredSyntaxRootSynchronously(cancellationToken); var syntaxFactsService = document.GetRequiredLanguageService<ISyntaxFactsService>(); var containingTypeDeclaration = syntaxFactsService.GetContainingTypeDeclaration(syntaxRoot, caretPosition); if (containingTypeDeclaration != null) { var semanticModel = await document.GetRequiredNullableDisabledSemanticModelAsync(cancellationToken).ConfigureAwait(false); // Allow third parties to navigate to all symbols except types/constructors // if we are navigating from the corresponding type. if (semanticModel.GetDeclaredSymbol(containingTypeDeclaration, cancellationToken) is ITypeSymbol containingTypeSymbol && (symbolToNavigateTo is ITypeSymbol || symbolToNavigateTo.IsConstructor())) { var candidateTypeSymbol = symbolToNavigateTo is ITypeSymbol ? symbolToNavigateTo : symbolToNavigateTo.ContainingType; if (Equals(containingTypeSymbol, candidateTypeSymbol)) { // We are navigating from the same type, so don't allow third parties to perform the navigation. // This ensures that if we navigate to a class from within that class, we'll stay in the same file // rather than navigate to, say, XAML. return false; } } } return true; } }