File: ValueTracking\ValueTracker.FindReferencesProgress.cs
Web Access
Project: src\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.Features)
// 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.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Operations;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
 
namespace Microsoft.CodeAnalysis.ValueTracking;
 
internal static partial class ValueTracker
{
    private sealed class FindReferencesProgress(OperationCollector valueTrackingProgressCollector, IParameterSymbol? parameterSymbol = null) : IStreamingFindReferencesProgress, IStreamingProgressTracker
    {
        private readonly OperationCollector _operationCollector = valueTrackingProgressCollector;
        private readonly IParameterSymbol? _parameterSymbol = parameterSymbol;
 
        public IStreamingProgressTracker ProgressTracker => this;
 
        public ValueTask AddItemsAsync(int count, CancellationToken _) => new();
 
        public ValueTask ItemsCompletedAsync(int count, CancellationToken _) => new();
 
        public ValueTask OnCompletedAsync(CancellationToken _) => new();
 
        public ValueTask OnDefinitionFoundAsync(SymbolGroup symbolGroup, CancellationToken _) => new();
 
        public async ValueTask OnReferencesFoundAsync(
            ImmutableArray<(SymbolGroup group, ISymbol symbol, ReferenceLocation location)> references,
            CancellationToken cancellationToken)
        {
            foreach (var (_, symbol, location) in references)
                await OnReferenceFoundAsync(symbol, location, cancellationToken).ConfigureAwait(false);
        }
 
        private async ValueTask OnReferenceFoundAsync(
            ISymbol symbol, ReferenceLocation location, CancellationToken cancellationToken)
        {
            if (!location.Location.IsInSource)
            {
                return;
            }
 
            if (symbol is IMethodSymbol methodSymbol)
            {
                if (methodSymbol.IsConstructor())
                {
                    await TrackConstructorAsync(location, cancellationToken).ConfigureAwait(false);
                }
                else
                {
                    // If we're searching for references to a method, we don't want to store the symbol as that method again. Instead
                    // we want to track the invocations and how to follow their source
                    await TrackMethodInvocationArgumentsAsync(location, cancellationToken).ConfigureAwait(false);
                }
            }
            else if (location.IsWrittenTo)
            {
                var syntaxFacts = location.Document.GetRequiredLanguageService<ISyntaxFactsService>();
                var node = location.Location.FindNode(cancellationToken);
 
                // Assignments to a member using a "this." or "Me." result in the node being an
                // identifier and the parent of the node being the member access expression. The member
                // access expression gives the right value for "IsLeftSideOfAnyAssignment" but also
                // gives the correct operation, where as the IdentifierSyntax does not.
                if (syntaxFacts.IsMemberAccessExpression(node.Parent))
                {
                    node = node.Parent;
                }
 
                if (syntaxFacts.IsLeftSideOfAnyAssignment(node))
                {
                    await AddItemsFromAssignmentAsync(location.Document, node, _operationCollector, cancellationToken).ConfigureAwait(false);
                }
                else
                {
                    var semanticModel = await location.Document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
                    var operation = semanticModel.GetOperation(node, cancellationToken);
                    if (operation is null)
                    {
                        return;
                    }
 
                    await _operationCollector.VisitAsync(operation, cancellationToken).ConfigureAwait(false);
                }
            }
            else if (symbol is IPropertySymbol { IsIndexer: true } propertySymbol)
            {
                // If the location isn't written to but it's an index accessor we still want to follow
                // it as a source to be tracked for the property itself. Specifically, we want to check
                // invocation sites and track the arguments being used in the invocation and potentially
                // the expression being indexed to, if it is relavent (as determined by the OperationCollector).
                var node = location.Location.FindNode(cancellationToken);
                var syntaxFacts = location.Document.GetRequiredLanguageService<ISyntaxFactsService>();
 
                var elementAccess = node.FirstAncestorOrSelf<SyntaxNode>(syntaxFacts.IsElementAccessExpression);
                if (elementAccess is null)
                {
                    return;
                }
 
                syntaxFacts.GetPartsOfElementAccessExpression(elementAccess, out var expression, out var argumentList);
                var semanticModel = await location.Document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
 
                if (argumentList is not null)
                {
                    foreach (var argument in syntaxFacts.GetArgumentsOfArgumentList(argumentList))
                    {
                        var argumentOperation = semanticModel.GetOperation(argument, cancellationToken);
                        if (argumentOperation is not null)
                        {
                            await _operationCollector.VisitAsync(argumentOperation, cancellationToken).ConfigureAwait(false);
                        }
                    }
                }
 
                if (expression is not null)
                {
                    // We do not want to track "this" as part of an index operation, but we do 
                    // want to track other variables that are accessed. Arguably they "contribute" even if
                    // not specifically to the argument of the element access.
                    if (syntaxFacts.IsThisExpression(expression))
                    {
                        return;
                    }
 
                    var expressionOperation = semanticModel.GetOperation(expression, cancellationToken);
                    if (expressionOperation is not null)
                    {
                        await _operationCollector.VisitAsync(expressionOperation, cancellationToken).ConfigureAwait(false);
                    }
                }
            }
        }
 
        public ValueTask OnStartedAsync(CancellationToken _) => new();
 
        private async Task TrackConstructorAsync(ReferenceLocation referenceLocation, CancellationToken cancellationToken)
        {
            var document = referenceLocation.Document;
            var span = referenceLocation.Location.SourceSpan;
 
            var syntaxRoot = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
            var originalNode = syntaxRoot.FindNode(span);
 
            if (originalNode is null || originalNode.Parent is null)
            {
                return;
            }
 
            var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
            var operation = semanticModel.GetOperation(originalNode.Parent, cancellationToken);
            if (operation is not IObjectCreationOperation)
            {
                return;
            }
 
            await _operationCollector.VisitAsync(operation, cancellationToken).ConfigureAwait(false);
        }
 
        private async Task TrackMethodInvocationArgumentsAsync(ReferenceLocation referenceLocation, CancellationToken cancellationToken)
        {
            var document = referenceLocation.Document;
            var span = referenceLocation.Location.SourceSpan;
 
            var syntaxRoot = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
            var originalNode = syntaxRoot.FindNode(span);
 
            if (originalNode is null)
                return;
 
            var syntaxFacts = document.GetRequiredLanguageService<ISyntaxFactsService>();
            var invocationSyntax = originalNode.FirstAncestorOrSelf<SyntaxNode>(syntaxFacts.IsInvocationExpression);
            if (invocationSyntax is null)
                return;
 
            var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
            var operation = semanticModel.GetOperation(invocationSyntax, cancellationToken);
            if (operation is not IInvocationOperation invocationOperation)
                return;
 
            if (_parameterSymbol is not null)
            {
                // Filter out invocations not containing an argument matching _parameterSymbol
                if (invocationOperation.Arguments.Any(
                    static (argOp, parameterSymbol) => argOp.Parameter == parameterSymbol && argOp.IsImplicit,
                    _parameterSymbol))
                {
                    return;
                }
            }
 
            await _operationCollector.VisitAsync(operation, cancellationToken).ConfigureAwait(false);
        }
    }
}