AbstractDocumentHighlightsService.cs 15.5 KB
Newer Older
1 2
// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

3
using System;
4 5
using System.Collections.Generic;
using System.Collections.Immutable;
6
using System.Diagnostics;
7 8 9
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
10
using Microsoft.CodeAnalysis.ErrorReporting;
11
using Microsoft.CodeAnalysis.Experiments;
12 13
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.LanguageServices;
14
using Microsoft.CodeAnalysis.Remote;
15 16 17 18
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;

19
namespace Microsoft.CodeAnalysis.DocumentHighlighting
20
{
21
    internal abstract partial class AbstractDocumentHighlightsService : IDocumentHighlightsService
22
    {
23 24
        public async Task<ImmutableArray<DocumentHighlights>> GetDocumentHighlightsAsync(
            Document document, int position, IImmutableSet<Document> documentsToSearch, CancellationToken cancellationToken)
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
        {
            var (succeeded, highlights) = await GetDocumentHighlightsInRemoteProcessAsync(
                document, position, documentsToSearch, cancellationToken).ConfigureAwait(false);

            if (succeeded)
            {
                return highlights;
            }

            return await GetDocumentHighlightsInCurrentProcessAsync(
                document, position, documentsToSearch, cancellationToken).ConfigureAwait(false);
        }

        private async Task<(bool succeeded, ImmutableArray<DocumentHighlights> highlights)> GetDocumentHighlightsInRemoteProcessAsync(
            Document document, int position, IImmutableSet<Document> documentsToSearch, CancellationToken cancellationToken)
        {
41 42 43 44
            var result = await document.Project.Solution.TryRunCodeAnalysisRemoteAsync<ImmutableArray<SerializableDocumentHighlights>>(
                RemoteFeatureOptions.DocumentHighlightingEnabled,
                nameof(IRemoteDocumentHighlights.GetDocumentHighlightsAsync),
                new object[]
45 46 47
                {
                    document.Id,
                    position,
48 49 50 51 52 53 54
                    documentsToSearch.Select(d => d.Id).ToArray()
                },
                cancellationToken).ConfigureAwait(false);

            if (result.IsDefault)
            {
                return (succeeded: false, ImmutableArray<DocumentHighlights>.Empty);
55
            }
56 57

            return (true, result.SelectAsArray(h => h.Rehydrate(document.Project.Solution)));
58 59 60 61
        }

        private async Task<ImmutableArray<DocumentHighlights>> GetDocumentHighlightsInCurrentProcessAsync(
            Document document, int position, IImmutableSet<Document> documentsToSearch, CancellationToken cancellationToken)
62 63 64 65
        {
            // use speculative semantic model to see whether we are on a symbol we can do HR
            var span = new TextSpan(position, 0);
            var solution = document.Project.Solution;
66

67
            var semanticModel = await document.GetSemanticModelForSpanAsync(span, cancellationToken).ConfigureAwait(false);
68 69
            var symbol = await SymbolFinder.FindSymbolAtPositionAsync(
                semanticModel, position, solution.Workspace, cancellationToken).ConfigureAwait(false);
70 71
            if (symbol == null)
            {
72
                return ImmutableArray<DocumentHighlights>.Empty;
73 74 75
            }

            symbol = await GetSymbolToSearchAsync(document, position, semanticModel, symbol, cancellationToken).ConfigureAwait(false);
76 77
            if (symbol == null)
            {
78
                return ImmutableArray<DocumentHighlights>.Empty;
79
            }
80 81

            // Get unique tags for referenced symbols
82
            return await GetTagsForReferencedSymbolAsync(
83 84
                new SymbolAndProjectId(symbol, document.Project.Id),
                document, documentsToSearch, cancellationToken).ConfigureAwait(false);
85 86
        }

87
        private static async Task<ISymbol> GetSymbolToSearchAsync(Document document, int position, SemanticModel semanticModel, ISymbol symbol, CancellationToken cancellationToken)
88
        {
89
            // see whether we can use the symbol as it is
90 91 92
            var currentSemanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
            if (currentSemanticModel == semanticModel)
            {
93
                return symbol;
94 95 96
            }

            // get symbols from current document again
C
Cyrus Najmabadi 已提交
97
            return await SymbolFinder.FindSymbolAtPositionAsync(currentSemanticModel, position, document.Project.Solution.Workspace, cancellationToken).ConfigureAwait(false);
98 99
        }

100 101
        private async Task<ImmutableArray<DocumentHighlights>> GetTagsForReferencedSymbolAsync(
            SymbolAndProjectId symbolAndProjectId,
102
            Document document,
103 104 105
            IImmutableSet<Document> documentsToSearch,
            CancellationToken cancellationToken)
        {
106
            var symbol = symbolAndProjectId.Symbol;
107 108 109
            Contract.ThrowIfNull(symbol);
            if (ShouldConsiderSymbol(symbol))
            {
110 111 112
                var progress = new StreamingProgressCollector(
                    StreamingFindReferencesProgress.Instance);
                await SymbolFinder.FindReferencesAsync(
113
                    symbolAndProjectId, document.Project.Solution, progress,
114 115 116
                    documentsToSearch, cancellationToken).ConfigureAwait(false);

                return await FilterAndCreateSpansAsync(
117
                    progress.GetReferencedSymbols(), document, documentsToSearch,
118
                    symbol, cancellationToken).ConfigureAwait(false);
119 120
            }

121
            return ImmutableArray<DocumentHighlights>.Empty;
122 123
        }

124
        private static bool ShouldConsiderSymbol(ISymbol symbol)
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
        {
            switch (symbol.Kind)
            {
                case SymbolKind.Method:
                    switch (((IMethodSymbol)symbol).MethodKind)
                    {
                        case MethodKind.AnonymousFunction:
                        case MethodKind.PropertyGet:
                        case MethodKind.PropertySet:
                        case MethodKind.EventAdd:
                        case MethodKind.EventRaise:
                        case MethodKind.EventRemove:
                            return false;

                        default:
                            return true;
                    }

                default:
                    return true;
            }
        }

148
        private async Task<ImmutableArray<DocumentHighlights>> FilterAndCreateSpansAsync(
C
CyrusNajmabadi 已提交
149
            IEnumerable<ReferencedSymbol> references, Document startingDocument,
150
            IImmutableSet<Document> documentsToSearch, ISymbol symbol,
151
            CancellationToken cancellationToken)
152
        {
C
CyrusNajmabadi 已提交
153
            var solution = startingDocument.Project.Solution;
154

C
CyrusNajmabadi 已提交
155
            references = references.FilterToItemsToShow();
156 157 158 159 160 161 162 163 164 165
            references = references.FilterNonMatchingMethodNames(solution, symbol);
            references = references.FilterToAliasMatches(symbol as IAliasSymbol);

            if (symbol.IsConstructor())
            {
                references = references.Where(r => r.Definition.OriginalDefinition.Equals(symbol.OriginalDefinition));
            }

            var additionalReferences = new List<Location>();

C
CyrusNajmabadi 已提交
166
            foreach (var currentDocument in documentsToSearch)
167
            {
168 169 170 171
                // 'documentsToSearch' may contain documents from languages other than our own
                // (for example cshtml files when we're searching the cs document).  Since we're
                // delegating to a virtual method for this language type, we have to make sure
                // we only process the document if it's also our language.
C
CyrusNajmabadi 已提交
172
                if (currentDocument.Project.Language == startingDocument.Project.Language)
173
                {
C
CyrusNajmabadi 已提交
174
                    additionalReferences.AddRange(await GetAdditionalReferencesAsync(currentDocument, symbol, cancellationToken).ConfigureAwait(false));
175
                }
176 177
            }

178
            return await CreateSpansAsync(
179
                solution, symbol, references, additionalReferences,
180
                documentsToSearch, cancellationToken).ConfigureAwait(false);
181 182
        }

183
        protected virtual Task<ImmutableArray<Location>> GetAdditionalReferencesAsync(
184 185
            Document document, ISymbol symbol, CancellationToken cancellationToken)
        {
186
            return SpecializedTasks.EmptyImmutableArray<Location>();
187 188
        }

189
        private static async Task<ImmutableArray<DocumentHighlights>> CreateSpansAsync(
190 191 192 193 194 195 196
            Solution solution,
            ISymbol symbol,
            IEnumerable<ReferencedSymbol> references,
            IEnumerable<Location> additionalReferences,
            IImmutableSet<Document> documentToSearch,
            CancellationToken cancellationToken)
        {
C
CyrusNajmabadi 已提交
197
            var spanSet = new HashSet<DocumentSpan>();
198 199 200 201
            var tagMap = new MultiDictionary<Document, HighlightSpan>();
            bool addAllDefinitions = true;

            // Add definitions
202
            // Filter out definitions that cannot be highlighted. e.g: alias symbols defined via project property pages.
203 204 205 206
            if (symbol.Kind == SymbolKind.Alias &&
                symbol.Locations.Length > 0)
            {
                addAllDefinitions = false;
207 208 209 210 211 212

                if (symbol.Locations.First().IsInSource)
                {
                    // For alias symbol we want to get the tag only for the alias definition, not the target symbol's definition.
                    await AddLocationSpan(symbol.Locations.First(), solution, spanSet, tagMap, HighlightSpanKind.Definition, cancellationToken).ConfigureAwait(false);
                }
213 214 215 216 217 218 219 220 221
            }

            // Add references and definitions
            foreach (var reference in references)
            {
                if (addAllDefinitions && ShouldIncludeDefinition(reference.Definition))
                {
                    foreach (var location in reference.Definition.Locations)
                    {
222
                        if (location.IsInSource)
223
                        {
224 225 226 227 228 229 230
                            var document = solution.GetDocument(location.SourceTree);

                            // GetDocument will return null for locations in #load'ed trees.
                            // TODO:  Remove this check and add logic to fetch the #load'ed tree's
                            // Document once https://github.com/dotnet/roslyn/issues/5260 is fixed.
                            if (document == null)
                            {
231
                                Debug.Assert(solution.Workspace.Kind == WorkspaceKind.Interactive || solution.Workspace.Kind == WorkspaceKind.MiscellaneousFiles);
232 233 234 235
                                continue;
                            }

                            if (documentToSearch.Contains(document))
236
                            {
237 238
                                await AddLocationSpan(location, solution, spanSet, tagMap, HighlightSpanKind.Definition, cancellationToken).ConfigureAwait(false);
                            }
239 240 241 242 243 244
                        }
                    }
                }

                foreach (var referenceLocation in reference.Locations)
                {
245
                    var referenceKind = referenceLocation.IsWrittenTo ? HighlightSpanKind.WrittenReference : HighlightSpanKind.Reference;
246
                    await AddLocationSpan(referenceLocation.Location, solution, spanSet, tagMap, referenceKind, cancellationToken).ConfigureAwait(false);
247 248 249 250 251 252
                }
            }

            // Add additional references
            foreach (var location in additionalReferences)
            {
253
                await AddLocationSpan(location, solution, spanSet, tagMap, HighlightSpanKind.Reference, cancellationToken).ConfigureAwait(false);
254 255
            }

256
            var list = ArrayBuilder<DocumentHighlights>.GetInstance(tagMap.Count);
257 258
            foreach (var kvp in tagMap)
            {
259
                var spans = ArrayBuilder<HighlightSpan>.GetInstance(kvp.Value.Count);
260 261 262 263 264
                foreach (var span in kvp.Value)
                {
                    spans.Add(span);
                }

265
                list.Add(new DocumentHighlights(kvp.Key, spans.ToImmutableAndFree()));
266 267
            }

268
            return list.ToImmutableAndFree();
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296
        }

        private static bool ShouldIncludeDefinition(ISymbol symbol)
        {
            switch (symbol.Kind)
            {
                case SymbolKind.Namespace:
                    return false;

                case SymbolKind.NamedType:
                    return !((INamedTypeSymbol)symbol).IsScriptClass;

                case SymbolKind.Parameter:

                    // If it's an indexer parameter, we will have also cascaded to the accessor
                    // one that actually receives the references
                    var containingProperty = symbol.ContainingSymbol as IPropertySymbol;
                    if (containingProperty != null && containingProperty.IsIndexer)
                    {
                        return false;
                    }

                    break;
            }

            return true;
        }

297
        private static async Task AddLocationSpan(Location location, Solution solution, HashSet<DocumentSpan> spanSet, MultiDictionary<Document, HighlightSpan> tagList, HighlightSpanKind kind, CancellationToken cancellationToken)
298 299 300 301 302
        {
            var span = await GetLocationSpanAsync(solution, location, cancellationToken).ConfigureAwait(false);
            if (span != null && !spanSet.Contains(span.Value))
            {
                spanSet.Add(span.Value);
C
CyrusNajmabadi 已提交
303
                tagList.Add(span.Value.Document, new HighlightSpan(span.Value.SourceSpan, kind));
304 305 306
            }
        }

307
        private static async Task<DocumentSpan?> GetLocationSpanAsync(
308
            Solution solution, Location location, CancellationToken cancellationToken)
309
        {
310
            try
311
            {
312 313 314
                if (location != null && location.IsInSource)
                {
                    var tree = location.SourceTree;
315

316 317
                    var document = solution.GetDocument(tree);
                    var syntaxFacts = document.Project.LanguageServices.GetService<ISyntaxFactsService>();
318

319 320 321 322 323
                    if (syntaxFacts != null)
                    {
                        // Specify findInsideTrivia: true to ensure that we search within XML doc comments.
                        var root = await tree.GetRootAsync(cancellationToken).ConfigureAwait(false);
                        var token = root.FindToken(location.SourceSpan.Start, findInsideTrivia: true);
324

325
                        return syntaxFacts.IsGenericName(token.Parent) || syntaxFacts.IsIndexerMemberCRef(token.Parent)
C
CyrusNajmabadi 已提交
326 327
                            ? new DocumentSpan(document, token.Span)
                            : new DocumentSpan(document, location.SourceSpan);
328
                    }
329 330
                }
            }
331 332 333 334 335 336 337 338 339 340
            catch (NullReferenceException e) when (FatalError.ReportWithoutCrash(e))
            {
                // We currently are seeing a strange null references crash in this code.  We have
                // a strong belief that this is recoverable, but we'd like to know why it is 
                // happening.  This exception filter allows us to report the issue and continue
                // without damaging the user experience.  Once we get more crash reports, we
                // can figure out the root cause and address appropriately.  This is preferable
                // to just using conditionl access operators to be resilient (as we won't actually
                // know why this is happening).
            }
341

342
            return null;
343 344
        }
    }
345
}