AbstractDocumentHighlightsService.cs 15.8 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.ErrorReporting;
11 12
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.LanguageServices;
T
Tomas Matousek 已提交
13
using Microsoft.CodeAnalysis.PooledObjects;
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)
        {
H
Heejae Chang 已提交
41
            var result = await document.Project.Solution.TryRunCodeAnalysisRemoteAsync<IList<SerializableDocumentHighlights>>(
42 43 44
                RemoteFeatureOptions.DocumentHighlightingEnabled,
                nameof(IRemoteDocumentHighlights.GetDocumentHighlightsAsync),
                new object[]
45 46 47
                {
                    document.Id,
                    position,
48 49 50 51
                    documentsToSearch.Select(d => d.Id).ToArray()
                },
                cancellationToken).ConfigureAwait(false);

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

H
Heejae Chang 已提交
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 66 67 68
            var result = await TryGetRegexPatternHighlightsAsync(document, position, cancellationToken).ConfigureAwait(false);
            if (!result.IsDefaultOrEmpty)
            {
                return result;
            }

69 70 71
            // 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;
72

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

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

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

93
        private static async Task<ISymbol> GetSymbolToSearchAsync(Document document, int position, SemanticModel semanticModel, ISymbol symbol, CancellationToken cancellationToken)
94
        {
95
            // see whether we can use the symbol as it is
96 97 98
            var currentSemanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
            if (currentSemanticModel == semanticModel)
            {
99
                return symbol;
100 101 102
            }

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

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

                return await FilterAndCreateSpansAsync(
123
                    progress.GetReferencedSymbols(), document, documentsToSearch,
124
                    symbol, cancellationToken).ConfigureAwait(false);
125 126
            }

127
            return ImmutableArray<DocumentHighlights>.Empty;
128 129
        }

130
        private static bool ShouldConsiderSymbol(ISymbol symbol)
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
        {
            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;
            }
        }

154
        private async Task<ImmutableArray<DocumentHighlights>> FilterAndCreateSpansAsync(
C
CyrusNajmabadi 已提交
155
            IEnumerable<ReferencedSymbol> references, Document startingDocument,
156
            IImmutableSet<Document> documentsToSearch, ISymbol symbol,
157
            CancellationToken cancellationToken)
158
        {
C
CyrusNajmabadi 已提交
159
            var solution = startingDocument.Project.Solution;
160

C
CyrusNajmabadi 已提交
161
            references = references.FilterToItemsToShow();
162 163 164 165 166 167 168 169 170 171
            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 已提交
172
            foreach (var currentDocument in documentsToSearch)
173
            {
174 175 176 177
                // '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 已提交
178
                if (currentDocument.Project.Language == startingDocument.Project.Language)
179
                {
C
CyrusNajmabadi 已提交
180
                    additionalReferences.AddRange(await GetAdditionalReferencesAsync(currentDocument, symbol, cancellationToken).ConfigureAwait(false));
181
                }
182 183
            }

184
            return await CreateSpansAsync(
185
                solution, symbol, references, additionalReferences,
186
                documentsToSearch, cancellationToken).ConfigureAwait(false);
187 188
        }

189
        protected virtual Task<ImmutableArray<Location>> GetAdditionalReferencesAsync(
190 191
            Document document, ISymbol symbol, CancellationToken cancellationToken)
        {
192
            return SpecializedTasks.EmptyImmutableArray<Location>();
193 194
        }

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

            // Add definitions
208
            // Filter out definitions that cannot be highlighted. e.g: alias symbols defined via project property pages.
209 210 211 212
            if (symbol.Kind == SymbolKind.Alias &&
                symbol.Locations.Length > 0)
            {
                addAllDefinitions = false;
213 214 215 216 217 218

                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);
                }
219 220 221 222 223 224 225 226 227
            }

            // Add references and definitions
            foreach (var reference in references)
            {
                if (addAllDefinitions && ShouldIncludeDefinition(reference.Definition))
                {
                    foreach (var location in reference.Definition.Locations)
                    {
228
                        if (location.IsInSource)
229
                        {
230 231 232 233 234 235 236
                            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)
                            {
237
                                Debug.Assert(solution.Workspace.Kind == WorkspaceKind.Interactive || solution.Workspace.Kind == WorkspaceKind.MiscellaneousFiles);
238 239 240 241
                                continue;
                            }

                            if (documentToSearch.Contains(document))
242
                            {
243 244
                                await AddLocationSpan(location, solution, spanSet, tagMap, HighlightSpanKind.Definition, cancellationToken).ConfigureAwait(false);
                            }
245 246 247 248 249 250
                        }
                    }
                }

                foreach (var referenceLocation in reference.Locations)
                {
251
                    var referenceKind = referenceLocation.IsWrittenTo ? HighlightSpanKind.WrittenReference : HighlightSpanKind.Reference;
252
                    await AddLocationSpan(referenceLocation.Location, solution, spanSet, tagMap, referenceKind, cancellationToken).ConfigureAwait(false);
253 254 255 256 257 258
                }
            }

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

262
            var list = ArrayBuilder<DocumentHighlights>.GetInstance(tagMap.Count);
263 264
            foreach (var kvp in tagMap)
            {
265
                var spans = ArrayBuilder<HighlightSpan>.GetInstance(kvp.Value.Count);
266 267 268 269 270
                foreach (var span in kvp.Value)
                {
                    spans.Add(span);
                }

271
                list.Add(new DocumentHighlights(kvp.Key, spans.ToImmutableAndFree()));
272 273
            }

274
            return list.ToImmutableAndFree();
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302
        }

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

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

313
        private static async Task<DocumentSpan?> GetLocationSpanAsync(
314
            Solution solution, Location location, CancellationToken cancellationToken)
315
        {
316
            try
317
            {
318 319 320
                if (location != null && location.IsInSource)
                {
                    var tree = location.SourceTree;
321

322
                    var document = solution.GetDocument(tree);
323
                    var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
324

325 326 327 328 329
                    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);
330

331
                        return syntaxFacts.IsGenericName(token.Parent) || syntaxFacts.IsIndexerMemberCRef(token.Parent)
C
CyrusNajmabadi 已提交
332 333
                            ? new DocumentSpan(document, token.Span)
                            : new DocumentSpan(document, location.SourceSpan);
334
                    }
335 336
                }
            }
337 338 339 340 341 342 343 344 345 346
            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).
            }
347

348
            return null;
349 350
        }
    }
T
Tomas Matousek 已提交
351
}