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))
private static async Task TrackVariableReferencesAsync(ISymbol symbol, OperationCollector collector, CancellationToken cancellationToken) { var findReferenceProgressCollector = new FindReferencesProgress(collector); await SymbolFinder.FindReferencesAsync( symbol, collector.Solution, findReferenceProgressCollector, documents : null, FindReferencesSearchOptions.Default, cancellationToken).ConfigureAwait(false); }
private static async Task TrackMethodSymbolAsync(IMethodSymbol methodSymbol, OperationCollector collector, CancellationToken cancellationToken) { var hasAnyOutData = HasAValueReturn(methodSymbol) || HasAnOutOrRefParam(methodSymbol); if (!hasAnyOutData) { // With no out data, there's nothing to do here return; } // TODO: Use DFA to find meaningful returns? https://github.com/dotnet/roslyn-analyzers/blob/9e5f533cbafcc5579e4d758bc9bde27b7611ca54/docs/Writing%20dataflow%20analysis%20based%20analyzers.md if (HasAValueReturn(methodSymbol)) { foreach (var location in methodSymbol.GetDefinitionLocationsToShow()) { if (location.SourceTree is null) { continue; } var node = location.FindNode(cancellationToken); var sourceDoc = collector.Solution.GetRequiredDocument(location.SourceTree); var syntaxFacts = sourceDoc.GetRequiredLanguageService <ISyntaxFactsService>(); var semanticModel = await sourceDoc.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var operation = semanticModel.GetOperation(node, cancellationToken); // In VB the parent node contains the operation (IBlockOperation) instead of the one returned // by the symbol location. if (operation is null && node.Parent is not null) { operation = semanticModel.GetOperation(node.Parent, cancellationToken); } if (operation is null) { continue; } await collector.VisitAsync(operation, cancellationToken).ConfigureAwait(false); } } if (HasAnOutOrRefParam(methodSymbol)) { foreach (var outOrRefParam in methodSymbol.Parameters.Where(p => p.IsRefOrOut())) { if (!outOrRefParam.IsFromSource()) { continue; } await TrackVariableReferencesAsync(outOrRefParam, collector, cancellationToken).ConfigureAwait(false); } }
public static IEnumerable<IOperation> DescendantsAndSelf(this IOperation operation) { if (operation == null) { return SpecializedCollections.EmptyEnumerable<IOperation>(); } var list = new List<IOperation>(); var collector = new OperationCollector(list); collector.Visit(operation); return list; }
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 static IEnumerable <IOperation> DescendantsAndSelf(this IOperation operation) { if (operation == null) { return(SpecializedCollections.EmptyEnumerable <IOperation>()); } List <IOperation> list = new List <IOperation>(); OperationCollector collector = new OperationCollector(list); collector.Visit(operation); return(list); }
public static IEnumerable <IOperation> Descendants(this IOperation operation) { if (operation == null) { return(SpecializedCollections.EmptyEnumerable <IOperation>()); } var list = new List <IOperation>(); var collector = new OperationCollector(list); collector.Visit(operation); list.RemoveAt(0); return(list); }
private static async Task TrackParameterSymbolAsync( IParameterSymbol parameterSymbol, OperationCollector collector, CancellationToken cancellationToken) { var containingSymbol = parameterSymbol.ContainingSymbol; var findReferenceProgressCollector = new FindReferencesProgress(collector); await SymbolFinder.FindReferencesAsync( containingSymbol, collector.Solution, findReferenceProgressCollector, documents : null, FindReferencesSearchOptions.Default, cancellationToken).ConfigureAwait(false); }
public FindReferencesProgress(OperationCollector valueTrackingProgressCollector) { _operationCollector = valueTrackingProgressCollector; }
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; } }
private static async Task AddItemsFromAssignmentAsync(Document document, SyntaxNode lhsNode, OperationCollector collector, CancellationToken cancellationToken) { var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var operation = semanticModel.GetOperation(lhsNode, cancellationToken); if (operation is null) { return; } IAssignmentOperation?assignmentOperation = null; while (assignmentOperation is null && operation is not null) { assignmentOperation = operation as IAssignmentOperation; operation = operation.Parent; } if (assignmentOperation is null) { return; } await collector.VisitAsync(assignmentOperation, cancellationToken).ConfigureAwait(false); }