AbstractGoToDefinitionService.cs 5.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10
// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor.Host;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.LanguageServices;
11
using Microsoft.CodeAnalysis.Navigation;
12 13 14
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;

15
namespace Microsoft.CodeAnalysis.Editor.GoToDefinition
16 17 18
{
    internal abstract class AbstractGoToDefinitionService : IGoToDefinitionService
    {
19
        private readonly IEnumerable<Lazy<IStreamingFindUsagesPresenter>> _streamingPresenters;
20 21 22

        protected abstract ISymbol FindRelatedExplicitlyDeclaredSymbol(ISymbol symbol, Compilation compilation);

C
CyrusNajmabadi 已提交
23
        protected AbstractGoToDefinitionService(
24
            IEnumerable<Lazy<IStreamingFindUsagesPresenter>> streamingPresenters)
25
        {
26
            _streamingPresenters = streamingPresenters;
27 28 29 30
        }

        private async Task<ISymbol> FindSymbolAsync(Document document, int position, CancellationToken cancellationToken)
        {
31 32 33 34 35
            if (!document.SupportsSemanticModel)
            {
                return null;
            }

36 37 38
            var workspace = document.Project.Solution.Workspace;

            var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
39
            var semanticInfo = await SymbolFinder.GetSemanticInfoAtPositionAsync(semanticModel, position, workspace, cancellationToken).ConfigureAwait(false);
40 41 42 43 44 45 46 47 48

            // 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;
49 50 51 52

            return FindRelatedExplicitlyDeclaredSymbol(symbol, semanticModel.Compilation);
        }

53 54
        public async Task<IEnumerable<INavigableItem>> FindDefinitionsAsync(
            Document document, int position, CancellationToken cancellationToken)
55 56 57
        {
            var symbol = await FindSymbolAsync(document, position, cancellationToken).ConfigureAwait(false);

58
            // Try to compute source definitions from symbol.
59
            var items = symbol != null
60
                ? NavigableItemFactory.GetItemsFromPreferredSourceLocations(document.Project.Solution, symbol, displayTaggedParts: null, cancellationToken: cancellationToken)
61
                : null;
62

63 64
            // realize the list here so that the consumer await'ing the result doesn't lazily cause
            // them to be created on an inappropriate thread.
65
            return items?.ToList();
66 67 68 69
        }

        public bool TryGoToDefinition(Document document, int position, CancellationToken cancellationToken)
        {
70
            // First try to compute the referenced symbol and attempt to go to definition for the symbol.
71
            var symbol = FindSymbolAsync(document, position, cancellationToken).WaitAndGetResult(cancellationToken);
C
CyrusNajmabadi 已提交
72
            if (symbol == null)
73
            {
C
CyrusNajmabadi 已提交
74
                return false;
75 76
            }

C
CyrusNajmabadi 已提交
77 78 79 80
            var isThirdPartyNavigationAllowed = IsThirdPartyNavigationAllowed(symbol, position, document, cancellationToken);

            return GoToDefinitionHelpers.TryGoToDefinition(symbol,
                document.Project,
81
                _streamingPresenters,
C
CyrusNajmabadi 已提交
82 83 84
                thirdPartyNavigationAllowed: isThirdPartyNavigationAllowed,
                throwOnHiddenDefinition: true,
                cancellationToken: cancellationToken);
85 86
        }

87
        private static bool IsThirdPartyNavigationAllowed(ISymbol symbolToNavigateTo, int caretPosition, Document document, CancellationToken cancellationToken)
88
        {
C
CyrusNajmabadi 已提交
89
            var syntaxRoot = document.GetSyntaxRootSynchronously(cancellationToken);
90 91 92 93 94 95
            var syntaxFactsService = document.GetLanguageService<ISyntaxFactsService>();
            var containingTypeDeclaration = syntaxFactsService.GetContainingTypeDeclaration(syntaxRoot, caretPosition);

            if (containingTypeDeclaration != null)
            {
                var semanticModel = document.GetSemanticModelAsync(cancellationToken).WaitAndGetResult(cancellationToken);
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
                var containingTypeSymbol = semanticModel.GetDeclaredSymbol(containingTypeDeclaration, cancellationToken) as ITypeSymbol;

                // Allow third parties to navigate to all symbols except types/constructors
                // if we are navigating from the corresponding type.

                if (containingTypeSymbol != null &&
                    (symbolToNavigateTo is ITypeSymbol || symbolToNavigateTo.IsConstructor()))
                {
                    var candidateTypeSymbol = symbolToNavigateTo is ITypeSymbol
                        ? symbolToNavigateTo
                        : symbolToNavigateTo.ContainingType;

                    if (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;
                    }
                }
116 117
            }

118
            return true;
119 120 121
        }
    }
}