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

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.EmbeddedLanguages.LanguageServices;
11
using Microsoft.CodeAnalysis.ErrorReporting;
C
Cyrus Najmabadi 已提交
12
using Microsoft.CodeAnalysis.Features.EmbeddedLanguages;
13 14
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.LanguageServices;
T
Tomas Matousek 已提交
15
using Microsoft.CodeAnalysis.PooledObjects;
16
using Microsoft.CodeAnalysis.Remote;
17 18 19 20
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;

21
namespace Microsoft.CodeAnalysis.DocumentHighlighting
22
{
23
    internal abstract partial class AbstractDocumentHighlightsService : IDocumentHighlightsService
24
    {
25 26
        public async Task<ImmutableArray<DocumentHighlights>> GetDocumentHighlightsAsync(
            Document document, int position, IImmutableSet<Document> documentsToSearch, CancellationToken cancellationToken)
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
        {
            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)
        {
H
Heejae Chang 已提交
43
            var result = await document.Project.Solution.TryRunCodeAnalysisRemoteAsync<IList<SerializableDocumentHighlights>>(
44 45
                nameof(IRemoteDocumentHighlights.GetDocumentHighlightsAsync),
                new object[]
46 47 48
                {
                    document.Id,
                    position,
49 50 51 52
                    documentsToSearch.Select(d => d.Id).ToArray()
                },
                cancellationToken).ConfigureAwait(false);

H
Heejae Chang 已提交
53
            if (result == null)
54 55
            {
                return (succeeded: false, ImmutableArray<DocumentHighlights>.Empty);
56
            }
57

H
Heejae Chang 已提交
58
            return (true, result.SelectAsArray(h => h.Rehydrate(document.Project.Solution)));
59 60 61 62
        }

        private async Task<ImmutableArray<DocumentHighlights>> GetDocumentHighlightsInCurrentProcessAsync(
            Document document, int position, IImmutableSet<Document> documentsToSearch, CancellationToken cancellationToken)
63
        {
C
Cyrus Najmabadi 已提交
64 65
            var result = await TryGetEmbeddedLanguageHighlightsAsync(
                document, position, documentsToSearch, cancellationToken).ConfigureAwait(false);
66 67 68 69 70
            if (!result.IsDefaultOrEmpty)
            {
                return result;
            }

71 72 73
            // 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;
74

75
            var semanticModel = await document.GetSemanticModelForSpanAsync(span, cancellationToken).ConfigureAwait(false);
76 77
            var symbol = await SymbolFinder.FindSymbolAtPositionAsync(
                semanticModel, position, solution.Workspace, cancellationToken).ConfigureAwait(false);
78 79
            if (symbol == null)
            {
80
                return ImmutableArray<DocumentHighlights>.Empty;
81 82 83
            }

            symbol = await GetSymbolToSearchAsync(document, position, semanticModel, symbol, cancellationToken).ConfigureAwait(false);
84 85
            if (symbol == null)
            {
86
                return ImmutableArray<DocumentHighlights>.Empty;
87
            }
88 89

            // Get unique tags for referenced symbols
90
            return await GetTagsForReferencedSymbolAsync(
91 92
                new SymbolAndProjectId(symbol, document.Project.Id),
                document, documentsToSearch, cancellationToken).ConfigureAwait(false);
93 94
        }

95
        private async Task<ImmutableArray<DocumentHighlights>> TryGetEmbeddedLanguageHighlightsAsync(
C
Cyrus Najmabadi 已提交
96
            Document document, int position, IImmutableSet<Document> documentsToSearch, CancellationToken cancellationToken)
97
        {
98
            var languagesProvider = document.GetLanguageService<IEmbeddedLanguagesProvider>();
C
Cyrus Najmabadi 已提交
99
            if (languagesProvider != null)
100
            {
C
Cyrus Najmabadi 已提交
101
                foreach (var language in languagesProvider.Languages)
102
                {
103
                    var highlighter = (language as IEmbeddedLanguageFeatures)?.DocumentHighlightsService;
104
                    if (highlighter != null)
105
                    {
C
Cyrus Najmabadi 已提交
106 107
                        var highlights = await highlighter.GetDocumentHighlightsAsync(
                            document, position, documentsToSearch, cancellationToken).ConfigureAwait(false);
108 109

                        if (!highlights.IsDefaultOrEmpty)
110
                        {
C
Cyrus Najmabadi 已提交
111
                            return highlights;
112 113 114 115 116 117 118 119
                        }
                    }
                }
            }

            return default;
        }

120
        private static async Task<ISymbol> GetSymbolToSearchAsync(Document document, int position, SemanticModel semanticModel, ISymbol symbol, CancellationToken cancellationToken)
121
        {
122
            // see whether we can use the symbol as it is
123 124 125
            var currentSemanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
            if (currentSemanticModel == semanticModel)
            {
126
                return symbol;
127 128 129
            }

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

133 134
        private async Task<ImmutableArray<DocumentHighlights>> GetTagsForReferencedSymbolAsync(
            SymbolAndProjectId symbolAndProjectId,
135
            Document document,
136 137 138
            IImmutableSet<Document> documentsToSearch,
            CancellationToken cancellationToken)
        {
139
            var symbol = symbolAndProjectId.Symbol;
140 141 142
            Contract.ThrowIfNull(symbol);
            if (ShouldConsiderSymbol(symbol))
            {
143 144
                var progress = new StreamingProgressCollector(
                    StreamingFindReferencesProgress.Instance);
145

146
                var options = FindReferencesSearchOptions.GetFeatureOptionsForStartingSymbol(symbol);
147
                await SymbolFinder.FindReferencesAsync(
148
                    symbolAndProjectId, document.Project.Solution, progress,
149
                    documentsToSearch, options, cancellationToken).ConfigureAwait(false);
150 151

                return await FilterAndCreateSpansAsync(
152
                    progress.GetReferencedSymbols(), document, documentsToSearch,
153
                    symbol, options, cancellationToken).ConfigureAwait(false);
154 155
            }

156
            return ImmutableArray<DocumentHighlights>.Empty;
157 158
        }

159
        private static bool ShouldConsiderSymbol(ISymbol symbol)
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
        {
            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;
            }
        }

183
        private async Task<ImmutableArray<DocumentHighlights>> FilterAndCreateSpansAsync(
C
CyrusNajmabadi 已提交
184
            IEnumerable<ReferencedSymbol> references, Document startingDocument,
185
            IImmutableSet<Document> documentsToSearch, ISymbol symbol,
186
            FindReferencesSearchOptions options, CancellationToken cancellationToken)
187
        {
C
CyrusNajmabadi 已提交
188
            var solution = startingDocument.Project.Solution;
189

190
            references = references.FilterToItemsToShow(options);
191 192 193 194 195 196 197 198 199 200
            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 已提交
201
            foreach (var currentDocument in documentsToSearch)
202
            {
203 204 205 206
                // '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 已提交
207
                if (currentDocument.Project.Language == startingDocument.Project.Language)
208
                {
C
CyrusNajmabadi 已提交
209
                    additionalReferences.AddRange(await GetAdditionalReferencesAsync(currentDocument, symbol, cancellationToken).ConfigureAwait(false));
210
                }
211 212
            }

213
            return await CreateSpansAsync(
214
                solution, symbol, references, additionalReferences,
215
                documentsToSearch, cancellationToken).ConfigureAwait(false);
216 217
        }

218
        protected virtual Task<ImmutableArray<Location>> GetAdditionalReferencesAsync(
219 220
            Document document, ISymbol symbol, CancellationToken cancellationToken)
        {
221
            return SpecializedTasks.EmptyImmutableArray<Location>();
222 223
        }

224
        private static async Task<ImmutableArray<DocumentHighlights>> CreateSpansAsync(
225 226 227 228 229 230 231
            Solution solution,
            ISymbol symbol,
            IEnumerable<ReferencedSymbol> references,
            IEnumerable<Location> additionalReferences,
            IImmutableSet<Document> documentToSearch,
            CancellationToken cancellationToken)
        {
C
CyrusNajmabadi 已提交
232
            var spanSet = new HashSet<DocumentSpan>();
233
            var tagMap = new MultiDictionary<Document, HighlightSpan>();
234
            var addAllDefinitions = true;
235 236

            // Add definitions
237
            // Filter out definitions that cannot be highlighted. e.g: alias symbols defined via project property pages.
238 239 240 241
            if (symbol.Kind == SymbolKind.Alias &&
                symbol.Locations.Length > 0)
            {
                addAllDefinitions = false;
242 243 244 245 246 247

                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);
                }
248 249 250 251 252 253 254 255 256
            }

            // Add references and definitions
            foreach (var reference in references)
            {
                if (addAllDefinitions && ShouldIncludeDefinition(reference.Definition))
                {
                    foreach (var location in reference.Definition.Locations)
                    {
257
                        if (location.IsInSource)
258
                        {
259 260 261 262 263 264 265
                            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)
                            {
266
                                Debug.Assert(solution.Workspace.Kind == WorkspaceKind.Interactive || solution.Workspace.Kind == WorkspaceKind.MiscellaneousFiles);
267 268 269 270
                                continue;
                            }

                            if (documentToSearch.Contains(document))
271
                            {
272 273
                                await AddLocationSpan(location, solution, spanSet, tagMap, HighlightSpanKind.Definition, cancellationToken).ConfigureAwait(false);
                            }
274 275 276 277 278 279
                        }
                    }
                }

                foreach (var referenceLocation in reference.Locations)
                {
280
                    var referenceKind = referenceLocation.IsWrittenTo ? HighlightSpanKind.WrittenReference : HighlightSpanKind.Reference;
281
                    await AddLocationSpan(referenceLocation.Location, solution, spanSet, tagMap, referenceKind, cancellationToken).ConfigureAwait(false);
282 283 284 285 286 287
                }
            }

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

291
            using var listDisposer = ArrayBuilder<DocumentHighlights>.GetInstance(tagMap.Count, out var list);
292 293
            foreach (var kvp in tagMap)
            {
294
                using var spansDisposer = ArrayBuilder<HighlightSpan>.GetInstance(kvp.Value.Count, out var spans);
295 296 297 298 299
                foreach (var span in kvp.Value)
                {
                    spans.Add(span);
                }

300
                list.Add(new DocumentHighlights(kvp.Key, spans.ToImmutable()));
301 302
            }

303
            return list.ToImmutable();
304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331
        }

        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;
        }

332
        private static async Task AddLocationSpan(Location location, Solution solution, HashSet<DocumentSpan> spanSet, MultiDictionary<Document, HighlightSpan> tagList, HighlightSpanKind kind, CancellationToken cancellationToken)
333 334 335 336 337
        {
            var span = await GetLocationSpanAsync(solution, location, cancellationToken).ConfigureAwait(false);
            if (span != null && !spanSet.Contains(span.Value))
            {
                spanSet.Add(span.Value);
C
CyrusNajmabadi 已提交
338
                tagList.Add(span.Value.Document, new HighlightSpan(span.Value.SourceSpan, kind));
339 340 341
            }
        }

342
        private static async Task<DocumentSpan?> GetLocationSpanAsync(
343
            Solution solution, Location location, CancellationToken cancellationToken)
344
        {
345
            try
346
            {
347 348 349
                if (location != null && location.IsInSource)
                {
                    var tree = location.SourceTree;
350

351
                    var document = solution.GetDocument(tree);
352
                    var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
353

354 355 356 357 358
                    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);
359

360
                        return syntaxFacts.IsGenericName(token.Parent) || syntaxFacts.IsIndexerMemberCRef(token.Parent)
C
CyrusNajmabadi 已提交
361 362
                            ? new DocumentSpan(document, token.Span)
                            : new DocumentSpan(document, location.SourceSpan);
363
                    }
364 365
                }
            }
366 367 368 369 370 371 372 373 374 375
            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).
            }
376

377
            return null;
378 379
        }
    }
T
Tomas Matousek 已提交
380
}