| File: ObsoleteSymbol\AbstractObsoleteSymbolService.cs | Web Access |
| Project: src\roslyn\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces) |
// 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.Immutable; using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.ObsoleteSymbol; internal abstract class AbstractObsoleteSymbolService(int? dimKeywordKind) : IObsoleteSymbolService { /// <summary> /// The <see cref="SyntaxToken.RawKind"/> of the <see langword="Dim"/> keyword in Visual Basic, or /// <see langword="null"/> for C# scenarios. This value is used to improve performance in the token classification /// fast-path by avoiding unnecessary calls to <see cref="ProcessDimKeyword"/>. /// </summary> private readonly int? _dimKeywordKind = dimKeywordKind; protected virtual void ProcessDimKeyword(ref ArrayBuilder<TextSpan>? result, SemanticModel semanticModel, SyntaxToken token, CancellationToken cancellationToken) { // Take no action by default } public async Task<ImmutableArray<TextSpan>> GetLocationsAsync(Document document, ImmutableArray<TextSpan> textSpans, CancellationToken cancellationToken) { var syntaxFacts = document.GetRequiredLanguageService<ISyntaxFactsService>(); // Obsolete analysis doesn't need nullable information. This saves substantial time computing obsoletion information. var semanticModel = await document.GetRequiredNullableDisabledSemanticModelAsync(cancellationToken).ConfigureAwait(false); var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); // Avoid taking a builder from the pool in the common case where there are no references to obsolete symbols // currently on screen. ArrayBuilder<TextSpan>? result = null; try { foreach (var span in textSpans) { Recurse(span, semanticModel); } if (result is null) return []; result.RemoveDuplicates(); return result.ToImmutableAndClear(); } finally { result?.Free(); } void Recurse(TextSpan span, SemanticModel semanticModel) { using var _ = ArrayBuilder<SyntaxNode>.GetInstance(out var stack); // Walk through all the nodes in the provided span. Directly analyze local or parameter declaration. And // also analyze any identifiers which might be reference to locals or parameters. Note that we might hit // locals/parameters without any references in the span, or references that don't have the declarations in // the span stack.Add(root.FindNode(span)); // Use a stack so we don't blow out the stack with recursion. while (stack.TryPop(out var current)) { if (current.Span.IntersectsWith(span)) { var tokenFromNode = ProcessNode(semanticModel, current); foreach (var child in current.ChildNodesAndTokens()) { if (child.AsNode(out var childNode)) stack.Add(childNode); var token = child.AsToken(); if (token != tokenFromNode) ProcessToken(semanticModel, child.AsToken()); ExtractStructureFromTrivia(stack, token.LeadingTrivia); ExtractStructureFromTrivia(stack, token.TrailingTrivia); } } } } static void ExtractStructureFromTrivia(ArrayBuilder<SyntaxNode> stack, SyntaxTriviaList triviaList) { foreach (var trivia in triviaList) { if (trivia.HasStructure) { stack.Add(trivia.GetStructure()!); } } } void AddResult(TextSpan span) { result ??= ArrayBuilder<TextSpan>.GetInstance(); result.Add(span); } SyntaxToken ProcessNode(SemanticModel semanticModel, SyntaxNode node) { if (syntaxFacts.IsUsingAliasDirective(node)) { syntaxFacts.GetPartsOfUsingAliasDirective(node, out _, out var aliasToken, out var name); if (!aliasToken.Span.IsEmpty) { // Use 'name.Parent' because VB can't resolve the declared symbol directly from 'node' var symbol = semanticModel.GetDeclaredSymbol(name.GetRequiredParent(), cancellationToken); if (IsSymbolObsolete(symbol)) AddResult(aliasToken.Span); } return aliasToken; } else if (syntaxFacts.IsObjectCreationExpression(node)) { syntaxFacts.GetPartsOfObjectCreationExpression(node, out var creationKeyword, out _, out _, out _); if (!creationKeyword.Span.IsEmpty) { // For syntax like the following // // SomeType value = new SomeType(); // // We classify 'new' as obsolete only if the specific constructor is obsolete. If the containing // type is obsolete, the classification will be applied to 'SomeType' instead. var symbol = semanticModel.GetSymbolInfo(node, cancellationToken).Symbol; if (IsSymbolObsolete(symbol)) AddResult(creationKeyword.Span); } } else if (syntaxFacts.IsImplicitObjectCreationExpression(node)) { syntaxFacts.GetPartsOfImplicitObjectCreationExpression(node, out var creationKeyword, out _, out _); if (!creationKeyword.Span.IsEmpty) { // For syntax like the following // // SomeType value = new(); // // We classify 'new' as obsolete if either the type or the specific constructor is obsolete. var symbol = semanticModel.GetSymbolInfo(node, cancellationToken).Symbol; if (IsSymbolObsolete(symbol) || IsSymbolObsolete(symbol?.ContainingType)) AddResult(creationKeyword.Span); } } return default; } void ProcessToken(SemanticModel semanticModel, SyntaxToken token) { if (syntaxFacts.IsIdentifier(token)) { ProcessIdentifier(semanticModel, token); } else if (token.RawKind == _dimKeywordKind) { ProcessDimKeyword(ref result, semanticModel, token, cancellationToken); } } void ProcessIdentifier(SemanticModel semanticModel, SyntaxToken token) { if (syntaxFacts.IsDeclaration(token.Parent)) { var symbol = semanticModel.GetDeclaredSymbol(token.Parent, cancellationToken); if (IsSymbolObsolete(symbol)) AddResult(token.Span); } else { var symbol = semanticModel.GetSymbolInfo(token, cancellationToken).Symbol; if (IsSymbolObsolete(symbol)) AddResult(token.Span); } } } protected static bool IsSymbolObsolete([NotNullWhen(true)] ISymbol? symbol) { // Avoid infinite recursion. Iteration limit chosen arbitrarily; cases are generally expected to complete on // the first iteration or fail completely. for (var i = 0; i < 5; i++) { if (symbol is IAliasSymbol alias) { symbol = alias.Target; continue; } if (symbol is INamedTypeSymbol { OriginalDefinition.SpecialType: SpecialType.System_Nullable_T, TypeArguments: [var valueType] }) { symbol = valueType; continue; } return symbol?.IsObsolete() ?? false; } // Unable to determine whether the symbol is considered obsolete return false; } }