private void AnalyzeMethodReference(DocumentData documentData, InvokeFunctionReferenceData refData) { var nameNode = refData.ReferenceNameNode; // Find the actual usage of the method var currNode = nameNode.Parent; var ascend = true; while (ascend) { ascend = false; switch (currNode.Kind()) { case SyntaxKind.ConditionalExpression: break; case SyntaxKind.InvocationExpression: AnalyzeInvocationExpression(documentData, (InvocationExpressionSyntax)currNode, refData); break; case SyntaxKind.Argument: AnalyzeArgumentExpression(currNode, nameNode, refData); break; case SyntaxKind.AddAssignmentExpression: refData.Ignore = true; Logger.Warn($"Cannot attach an async method to an event (void async is not an option as cannot be awaited):\r\n{nameNode.Parent}\r\n"); break; case SyntaxKind.VariableDeclaration: refData.Ignore = true; Logger.Warn($"Assigning async method to a variable is not supported:\r\n{nameNode.Parent}\r\n"); break; case SyntaxKind.CastExpression: refData.AwaitInvocation = true; ascend = true; break; case SyntaxKind.ReturnStatement: break; // skip case SyntaxKind.VariableDeclarator: case SyntaxKind.EqualsValueClause: case SyntaxKind.SimpleMemberAccessExpression: case SyntaxKind.ArgumentList: case SyntaxKind.ObjectCreationExpression: ascend = true; break; default: throw new NotSupportedException($"Unknown node kind: {currNode.Kind()}"); } if (ascend) { currNode = currNode.Parent; } } refData.ReferenceNode = currNode; if (!refData.AwaitInvocation.HasValue) { refData.AwaitInvocation = !refData.Ignore; } }
private void AnalyzeInvocationExpression(DocumentData documentData, InvocationExpressionSyntax node, InvokeFunctionReferenceData functionReferenceData) { var functionData = functionReferenceData.FunctionData; var methodSymbol = functionReferenceData.ReferenceSymbol; var functionNode = functionData.GetNode(); var functionBodyNode = functionData.GetBodyNode(); var queryExpression = node.Ancestors() .TakeWhile(o => o != functionNode) .OfType <QueryExpressionSyntax>() .FirstOrDefault(); if (queryExpression != null) // Await is not supported in a linq query { functionReferenceData.Ignore = true; Logger.Warn($"Cannot await async method in a query expression:\r\n{queryExpression}\r\n"); return; } var searchOptions = AsyncCounterpartsSearchOptions.Default; if (_configuration.UseCancellationTokenOverload) { searchOptions |= AsyncCounterpartsSearchOptions.HasCancellationToken; } functionReferenceData.ReferenceAsyncSymbols = new HashSet <IMethodSymbol>(GetAsyncCounterparts(methodSymbol.OriginalDefinition, searchOptions)); if (functionReferenceData.ReferenceAsyncSymbols.Any()) { if (functionReferenceData.ReferenceAsyncSymbols.All(o => o.ReturnsVoid || !o.ReturnType.IsTaskType())) { functionReferenceData.AwaitInvocation = false; Logger.Info($"Cannot await method that is either void or do not return a Task:\r\n{methodSymbol}\r\n"); } var nameGroups = functionReferenceData.ReferenceAsyncSymbols.GroupBy(o => o.Name).ToList(); if (nameGroups.Count == 1) { functionReferenceData.AsyncCounterpartName = nameGroups[0].Key; } } else if (!ProjectData.Contains(functionReferenceData.ReferenceSymbol)) { // If we are dealing with an external method and there are no async counterparts for it, we cannot convert it to async functionReferenceData.Ignore = true; Logger.Info($"Method {methodSymbol} can not be async as there is no async counterparts for it"); return; } else if (functionReferenceData.ReferenceFunctionData != null) { functionReferenceData.AsyncCounterpartName = functionReferenceData.ReferenceSymbol.Name + "Async"; } // If the invocation returns a Task then we need to analyze it further to see how the Task is handled if (methodSymbol.ReturnType.IsTaskType()) { var retrunType = (INamedTypeSymbol)methodSymbol.ReturnType; var canBeAwaited = false; var currNode = node.Parent; while (true) { var memberExpression = currNode as MemberAccessExpressionSyntax; if (memberExpression == null) { break; } var memberName = memberExpression.Name.ToString(); if (retrunType.IsGenericType && memberName == "Result") { canBeAwaited = true; break; } if (memberName == "ConfigureAwait") { var invocationNode = currNode.Parent as InvocationExpressionSyntax; if (invocationNode != null) { functionReferenceData.ConfigureAwaitParameter = invocationNode.ArgumentList.Arguments.First().Expression; currNode = invocationNode.Parent; continue; } break; } if (memberName == "GetAwaiter") { var invocationNode = currNode.Parent as InvocationExpressionSyntax; if (invocationNode != null) { currNode = invocationNode.Parent; continue; } break; } if (_taskResultMethods.Contains(memberName)) { var invocationNode = currNode.Parent as InvocationExpressionSyntax; if (invocationNode != null) { canBeAwaited = true; } } break; } if (!canBeAwaited) { functionReferenceData.AwaitInvocation = false; Logger.Info( $"Cannot await invocation of a method that returns a Task without be synchronously awaited:\r\n{methodSymbol}\r\n"); } else { functionReferenceData.SynchronouslyAwaited = true; } } if (node.Parent.IsKind(SyntaxKind.ReturnStatement)) { functionReferenceData.UseAsReturnValue = true; } // Calculate if node is the last statement if (node.Parent.Equals(functionBodyNode) || //eg. bool ExpressionReturn() => SimpleFile.Write(); node.Equals(functionBodyNode) // eg. Func<bool> fn = () => SimpleFile.Write(); ) { functionReferenceData.LastInvocation = true; functionReferenceData.UseAsReturnValue = !methodSymbol.ReturnsVoid; } var bodyBlock = functionBodyNode as BlockSyntax; if (bodyBlock?.Statements.Last() == node.Parent) { functionReferenceData.LastInvocation = true; } // Set CancellationTokenRequired if we detect that one of the async counterparts has a cancellation token as a parameter if (_configuration.UseCancellationTokenOverload && functionReferenceData.ReferenceAsyncSymbols.Any(o => o.Parameters.Length > methodSymbol.Parameters.Length)) { functionReferenceData.CancellationTokenRequired = true; } foreach (var analyzer in _configuration.InvocationExpressionAnalyzers) { analyzer.Analyze(node, functionReferenceData, documentData.SemanticModel); } // Propagate CancellationTokenRequired to the method data only if the invocation can be async if (functionReferenceData.CancellationTokenRequired && functionReferenceData.GetConversion() == ReferenceConversion.ToAsync) { // We need to set CancellationTokenRequired to true for the method that contains this invocation var methodData = functionReferenceData.FunctionData.GetMethodData(); methodData.CancellationTokenRequired = true; } }
private void AnalyzeArgumentExpression(SyntaxNode node, SimpleNameSyntax nameNode, InvokeFunctionReferenceData result) { var documentData = result.FunctionData.TypeData.NamespaceData.DocumentData; var methodArgTypeInfo = documentData.SemanticModel.GetTypeInfo(nameNode); if (methodArgTypeInfo.ConvertedType?.TypeKind != TypeKind.Delegate) { // TODO: debug and document return; } var delegateMethod = (IMethodSymbol)methodArgTypeInfo.ConvertedType.GetMembers("Invoke").First(); if (!delegateMethod.IsAsync) { result.Ignore = true; Logger.Warn($"Cannot pass an async method as parameter to a non async Delegate method:\r\n{delegateMethod}\r\n"); } else { var argumentMethodSymbol = (IMethodSymbol)documentData.SemanticModel.GetSymbolInfo(nameNode).Symbol; if (!argumentMethodSymbol.ReturnType.Equals(delegateMethod.ReturnType)) // i.e IList<T> -> IEnumerable<T> { result.AwaitInvocation = true; } } }
private async Task ScanAllMethodReferenceLocations(IMethodSymbol methodSymbol, int depth) { if (_scannedMethodReferenceSymbols.Contains(methodSymbol.OriginalDefinition)) { return; } _scannedMethodReferenceSymbols.TryAdd(methodSymbol.OriginalDefinition); var references = await SymbolFinder.FindReferencesAsync(methodSymbol.OriginalDefinition, _solution, _analyzeDocuments).ConfigureAwait(false); depth++; foreach (var refLocation in references.SelectMany(o => o.Locations)) { if (refLocation.Document.Project != ProjectData.Project) { throw new InvalidOperationException($"Reference {refLocation} is located in a document from another project"); } var documentData = ProjectData.GetDocumentData(refLocation.Document); if (documentData == null) { continue; } var symbol = documentData.GetEnclosingSymbol(refLocation); if (symbol == null) { Logger.Debug($"Symbol not found for reference ${refLocation}"); continue; } var refMethodSymbol = symbol as IMethodSymbol; if (refMethodSymbol == null) { if (symbol.Kind != SymbolKind.NamedType) { continue; } // A cref can be on a method or type trivia but we get always the type symbol var crefTypeData = documentData.GetAllTypeDatas(o => o.Symbol.Equals(symbol)).FirstOrDefault(); if (crefTypeData == null) { continue; } // Try to find the real node where the cref is located var crefReferenceNameNode = crefTypeData.Node.GetSimpleName(refLocation.Location.SourceSpan, true); var crefReferenceSymbol = (IMethodSymbol)documentData.SemanticModel.GetSymbolInfo(crefReferenceNameNode).Symbol; var crefReferenceMethodData = await ProjectData.GetMethodData(crefReferenceSymbol).ConfigureAwait(false); var crefReferenceData = new CrefReferenceData(refLocation, crefReferenceNameNode, crefReferenceSymbol, crefReferenceMethodData); var memberNode = crefReferenceNameNode.Ancestors().OfType <MemberDeclarationSyntax>().First(); var methodNode = memberNode as MethodDeclarationSyntax; if (methodNode != null) { var crefMethodData = (MethodData)documentData.GetNodeData(methodNode, typeData: crefTypeData); crefMethodData.CrefReferences.TryAdd(crefReferenceData); } else { crefTypeData.CrefReferences.TryAdd(crefReferenceData); } continue; } var baseMethodData = await documentData.GetAnonymousFunctionOrMethodData(refMethodSymbol).ConfigureAwait(false); if (baseMethodData == null) { continue; } // Find the real method on that reference as FindReferencesAsync will also find references to base and interface methods // Save the reference as it can be made async var nameNode = baseMethodData.GetNode().GetSimpleName(refLocation.Location.SourceSpan); var invokedSymbol = (IMethodSymbol)documentData.SemanticModel.GetSymbolInfo(nameNode).Symbol; var invokedMethodData = await ProjectData.GetMethodData(invokedSymbol).ConfigureAwait(false); invokedMethodData?.InvokedBy.Add(baseMethodData); var methodReferenceData = new InvokeFunctionReferenceData(baseMethodData, refLocation, nameNode, invokedSymbol, invokedMethodData); if (!baseMethodData.InvokedMethodReferences.TryAdd(methodReferenceData)) { Logger.Debug($"Performance hit: method reference {invokedSymbol} already processed"); continue; // Reference already processed } var methodData = baseMethodData as MethodData; if (methodData != null) { await ScanMethodData(methodData, depth).ConfigureAwait(false); } } }