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