public async Task <ImmutableArray <ValueTrackedItem> > TrackValueSourceAsync(
            Solution solution,
            ValueTrackedItem previousTrackedItem,
            CancellationToken cancellationToken)
        {
            using var logger = Logger.LogBlock(FunctionId.ValueTracking_TrackValueSource, cancellationToken, LogLevel.Information);
            var project = solution.GetRequiredProject(previousTrackedItem.DocumentId.ProjectId);
            var client  = await RemoteHostClient.TryGetClientAsync(project, cancellationToken).ConfigureAwait(false);

            if (client != null)
            {
                var dehydratedItem = SerializableValueTrackedItem.Dehydrate(solution, previousTrackedItem, cancellationToken);
                var result         = await client.TryInvokeAsync <IRemoteValueTrackingService, ImmutableArray <SerializableValueTrackedItem> >(
                    solution,
                    (service, solutionInfo, cancellationToken) => service.TrackValueSourceAsync(solutionInfo, dehydratedItem, cancellationToken),
                    cancellationToken).ConfigureAwait(false);

                if (!result.HasValue)
                {
                    return(ImmutableArray <ValueTrackedItem> .Empty);
                }

                return(await result.Value.SelectAsArrayAsync(
                           (item, cancellationToken) => item.RehydrateAsync(solution, cancellationToken), cancellationToken).ConfigureAwait(false));
            }

            var progressTracker = new ValueTrackingProgressCollector();
            await ValueTracker.TrackValueSourceAsync(solution, previousTrackedItem, progressTracker, cancellationToken).ConfigureAwait(false);

            return(progressTracker.GetItems());
        }
Exemple #2
0
        public static async Task TrackValueSourceAsync(
            TextSpan selection,
            Document document,
            ValueTrackingProgressCollector progressCollector,
            CancellationToken cancellationToken)
        {
            var(symbol, node) = await GetSelectedSymbolAsync(selection, document, cancellationToken).ConfigureAwait(false);

            var operationCollector = new OperationCollector(progressCollector, document.Project.Solution);

            if (symbol
                is IPropertySymbol
                or IFieldSymbol
                or ILocalSymbol
                or IParameterSymbol)
            {
                RoslynDebug.AssertNotNull(node);

                var solution = document.Project.Solution;
                var declaringSyntaxReferences = symbol.DeclaringSyntaxReferences;
                var syntaxFacts = document.GetRequiredLanguageService <ISyntaxFactsService>();

                // If the selection is within a declaration of the symbol, we want to include
                // all declarations and assignments of the symbol
                if (declaringSyntaxReferences.Any(static (r, selection) => r.Span.IntersectsWith(selection), selection))
            /// <summary>
            /// Clone the current collector into a new one with
            /// the same parent but a separate progress collector.
            /// This allows collection of items given the same state
            /// as this collector while also keeping them "grouped" separately.
            /// </summary>
            /// <remarks>
            /// This is useful for cases such as tracking arguments, where each
            /// argument may be an expression or something else. We want to track each
            /// argument expression in the correct order, but a single argument may produce
            /// multiple items. By cloning we can track the items for each argument and then
            /// gather them all at the end to report in the correct order.
            /// </remarks>
            private OperationCollector Clone()
            {
                var collector = new ValueTrackingProgressCollector
                {
                    Parent = ProgressCollector.Parent
                };

                return(new OperationCollector(collector, Solution));
            }
        public static async Task TrackValueSourceAsync(
            TextSpan selection,
            Document document,
            ValueTrackingProgressCollector progressCollector,
            CancellationToken cancellationToken)
        {
            var(symbol, node) = await GetSelectedSymbolAsync(selection, document, cancellationToken).ConfigureAwait(false);

            var operationCollector = new OperationCollector(progressCollector, document.Project.Solution);

            if (symbol
                is IPropertySymbol
                or IFieldSymbol
                or ILocalSymbol
                or IParameterSymbol)
            {
                RoslynDebug.AssertNotNull(node);

                var solution = document.Project.Solution;
                var declaringSyntaxReferences = symbol.DeclaringSyntaxReferences;
                var syntaxFacts = document.GetRequiredLanguageService <ISyntaxFactsService>();

                // If the selection is within a declaration of the symbol, we want to include
                // all declarations and assignments of the symbol
                if (declaringSyntaxReferences.Any(r => r.Span.IntersectsWith(selection)))
                {
                    // Add all initializations of the symbol. Those are not caught in
                    // the reference finder but should still show up in the tree
                    foreach (var syntaxRef in declaringSyntaxReferences)
                    {
                        var location = Location.Create(syntaxRef.SyntaxTree, syntaxRef.Span);
                        await progressCollector.TryReportAsync(solution, location, symbol, cancellationToken).ConfigureAwait(false);
                    }

                    await TrackVariableReferencesAsync(symbol, operationCollector, cancellationToken).ConfigureAwait(false);
                }
                // The selection is not on a declaration, check that the node
                // is on the left side of an assignment. If so, populate so we can
                // track the RHS values that contribute to this value
                else if (syntaxFacts.IsLeftSideOfAnyAssignment(node))
                {
                    await AddItemsFromAssignmentAsync(document, node, operationCollector, cancellationToken).ConfigureAwait(false);
                }
                // Not on the left part of an assignment? Then just add an item with the statement
                // and the symbol. It should be the top item, and children will find the sources
                // of the value. A good example is a return statement, such as "return $$x",
                // where $$ is the cursor position. The top item should have the return statement for
                // context, and the remaining items should expand into the assignments of x
                else
                {
                    await progressCollector.TryReportAsync(document.Project.Solution, node.GetLocation(), symbol, cancellationToken).ConfigureAwait(false);
                }
            }
        }
        public async Task <ImmutableArray <ValueTrackedItem> > TrackValueSourceAsync(
            Solution solution,
            ValueTrackedItem previousTrackedItem,
            CancellationToken cancellationToken)
        {
            using var logger = Logger.LogBlock(FunctionId.ValueTracking_TrackValueSource, cancellationToken, LogLevel.Information);
            var project = solution.GetRequiredProject(previousTrackedItem.DocumentId.ProjectId);
            var client  = await RemoteHostClient.TryGetClientAsync(project, cancellationToken).ConfigureAwait(false);

            if (client != null)
            {
                var dehydratedItem = SerializableValueTrackedItem.Dehydrate(solution, previousTrackedItem, cancellationToken);
                var result         = await client.TryInvokeAsync <IRemoteValueTrackingService, ImmutableArray <SerializableValueTrackedItem> >(
                    solution,
                    (service, solutionInfo, cancellationToken) => service.TrackValueSourceAsync(solutionInfo, dehydratedItem, cancellationToken),
                    cancellationToken).ConfigureAwait(false);

                if (!result.HasValue)
                {
                    return(ImmutableArray <ValueTrackedItem> .Empty);
                }

                using var _ = PooledObjects.ArrayBuilder <ValueTrackedItem> .GetInstance(out var builder);

                foreach (var item in result.Value)
                {
                    var rehydratedItem = await item.RehydrateAsync(solution, cancellationToken).ConfigureAwait(false);

                    if (rehydratedItem is null)
                    {
                        throw new InvalidOperationException();
                    }

                    builder.Add(rehydratedItem);
                }

                return(builder.ToImmutable());
            }

            var progressTracker = new ValueTrackingProgressCollector();
            await ValueTracker.TrackValueSourceAsync(solution, previousTrackedItem, progressTracker, cancellationToken).ConfigureAwait(false);

            return(progressTracker.GetItems());
        }
        public async Task <ImmutableArray <ValueTrackedItem> > TrackValueSourceAsync(
            TextSpan selection,
            Document document,
            CancellationToken cancellationToken)
        {
            using var logger = Logger.LogBlock(FunctionId.ValueTracking_TrackValueSource, cancellationToken, LogLevel.Information);
            var client = await RemoteHostClient.TryGetClientAsync(document.Project, cancellationToken).ConfigureAwait(false);

            if (client != null)
            {
                var solution = document.Project.Solution;

                var result = await client.TryInvokeAsync <IRemoteValueTrackingService, ImmutableArray <SerializableValueTrackedItem> >(
                    solution,
                    (service, solutionInfo, cancellationToken) => service.TrackValueSourceAsync(solutionInfo, selection, document.Id, cancellationToken),
                    cancellationToken).ConfigureAwait(false);

                if (!result.HasValue)
                {
                    return(ImmutableArray <ValueTrackedItem> .Empty);
                }

                using var _ = PooledObjects.ArrayBuilder <ValueTrackedItem> .GetInstance(out var builder);

                foreach (var item in result.Value)
                {
                    var rehydratedItem = await item.RehydrateAsync(document.Project.Solution, cancellationToken).ConfigureAwait(false);

                    Contract.ThrowIfNull(rehydratedItem);
                    builder.Add(rehydratedItem);
                }

                return(builder.ToImmutable());
            }

            var progressTracker = new ValueTrackingProgressCollector();
            await ValueTracker.TrackValueSourceAsync(selection, document, progressTracker, cancellationToken).ConfigureAwait(false);

            return(progressTracker.GetItems());
        }
        public static async Task TrackValueSourceAsync(
            Solution solution,
            ValueTrackedItem previousTrackedItem,
            ValueTrackingProgressCollector progressCollector,
            CancellationToken cancellationToken)
        {
            progressCollector.Parent = previousTrackedItem;
            var operationCollector = new OperationCollector(progressCollector, solution);
            var symbol             = await GetSymbolAsync(previousTrackedItem, solution, cancellationToken).ConfigureAwait(false);

            switch (symbol)
            {
            case ILocalSymbol:
            case IPropertySymbol:
            case IFieldSymbol:
            {
                // The "output" is a variable assignment, track places where it gets assigned and defined
                await TrackVariableDefinitionsAsync(symbol, operationCollector, cancellationToken).ConfigureAwait(false);
                await TrackVariableReferencesAsync(symbol, operationCollector, cancellationToken).ConfigureAwait(false);
            }

            break;

            case IParameterSymbol parameterSymbol:
            {
                var previousSymbol = await GetSymbolAsync(previousTrackedItem.Parent, solution, cancellationToken).ConfigureAwait(false);

                // If the current parameter is a parameter symbol for the previous tracked method it should be treated differently.
                // For example:
                // string PrependString(string pre, string s) => pre + s;
                //        ^--- previously tracked          ^---- current parameter being tracked
                //
                // In this case, s is being tracked because it contributed to the return of the method. We only
                // want to track assignments to s that could impact the return rather than tracking the same method
                // twice.
                var isParameterForPreviousTrackedMethod = previousSymbol?.Equals(parameterSymbol.ContainingSymbol, SymbolEqualityComparer.Default) == true;

                // For Ref or Out parameters, they contribute data across method calls through assignments
                // within the method. No need to track returns
                // Ex: TryGetValue("mykey", out var [|v|])
                // [|v|] is the interesting part, we don't care what the method returns
                var isRefOrOut = parameterSymbol.IsRefOrOut();

                // Always track the parameter assignments as variables, in case they are assigned anywhere in the method
                await TrackVariableReferencesAsync(parameterSymbol, operationCollector, cancellationToken).ConfigureAwait(false);

                var trackMethod = !(isParameterForPreviousTrackedMethod || isRefOrOut);
                if (trackMethod)
                {
                    await TrackParameterSymbolAsync(parameterSymbol, operationCollector, cancellationToken).ConfigureAwait(false);
                }
            }

            break;

            case IMethodSymbol methodSymbol:
            {
                // The "output" is from a method, meaning it has a return or out param that is used. Track those
                await TrackMethodSymbolAsync(methodSymbol, operationCollector, cancellationToken).ConfigureAwait(false);
            }

            break;
            }
        }
 public OperationCollector(ValueTrackingProgressCollector progressCollector, Solution solution)
 {
     ProgressCollector = progressCollector;
     Solution          = solution;
 }