private bool IsContractInvocation( InvocationExpressionSyntax invocationExpression, ContractMethodNames allowedMethodNames, INamedTypeSymbol contractTypeSymbol) { MemberAccessExpressionSyntax?memberAccess = invocationExpression .Expression.As(x => x as MemberAccessExpressionSyntax); if ((ParseContractMethodName(memberAccess?.Name.Identifier.Text) & allowedMethodNames) == ContractMethodNames.None) { return(false); } var memberSymbol = memberAccess ?.As(x => _semanticModel.GetSymbolInfo(x).Symbol as IMethodSymbol); // TODO: ToMetadataFullName() on every call is probably somewhat expensive if (memberSymbol == null || !memberSymbol.ContainingType.Equals(contractTypeSymbol)) { // This is not Contract. return(false); } return(true); }
internal static void InspectMemberAccess( SyntaxNodeAnalysisContext context, MemberAccessExpressionSyntax?memberAccessSyntax, DiagnosticDescriptor descriptor, IEnumerable <CommonInterest.SyncBlockingMethod> problematicMethods, bool ignoreIfInsideAnonymousDelegate = false) { if (descriptor is null) { throw new ArgumentNullException(nameof(descriptor)); } if (memberAccessSyntax is null) { return; } if (ShouldIgnoreContext(context)) { return; } if (ignoreIfInsideAnonymousDelegate && context.Node.FirstAncestorOrSelf <AnonymousFunctionExpressionSyntax>() is object) { // We do not analyze JTF.Run inside anonymous functions because // they are so often used as callbacks where the signature is constrained. return; } if (CSharpUtils.IsWithinNameOf(context.Node as ExpressionSyntax)) { // We do not consider arguments to nameof( ) because they do not represent invocations of code. return; } ITypeSymbol?typeReceiver = context.SemanticModel.GetTypeInfo(memberAccessSyntax.Expression).Type; if (typeReceiver is object) { foreach (CommonInterest.SyncBlockingMethod item in problematicMethods) { if (memberAccessSyntax.Name.Identifier.Text == item.Method.Name && typeReceiver.Name == item.Method.ContainingType.Name && typeReceiver.BelongsToNamespace(item.Method.ContainingType.Namespace)) { if (HasTaskCompleted(context, memberAccessSyntax)) { return; } Location?location = memberAccessSyntax.Name.GetLocation(); context.ReportDiagnostic(Diagnostic.Create(descriptor, location)); } } } }
private static void ReplaceMethodCall(MemberAccessExpressionSyntax?memberExpression, DocumentEditor docEditor) { if (memberExpression is null) { return; } var parsedExpression = ParseExpression("Deserialize"); var identifierExpression = memberExpression.DescendantNodes().OfType <IdentifierNameSyntax>().Last(); docEditor.ReplaceNode(identifierExpression, parsedExpression); }
private static void DropNullParameter(MemberAccessExpressionSyntax?memberExpression, DocumentEditor docEditor) { if (memberExpression is null || memberExpression.Parent is null) { return; } var argumentListSyntaxOriginal = memberExpression.Parent.DescendantNodes().OfType <ArgumentListSyntax>().Last(); var modified = argumentListSyntaxOriginal.WithArguments(argumentListSyntaxOriginal.Arguments.RemoveAt(1)); docEditor.ReplaceNode(argumentListSyntaxOriginal, modified); }
private void InspectMemberAccess( SyntaxNodeAnalysisContext context, MemberAccessExpressionSyntax?memberAccessSyntax, IEnumerable <CommonInterest.SyncBlockingMethod> problematicMethods, INamedTypeSymbol taskSymbol) { if (memberAccessSyntax == null) { return; } // Are we in the context of an anonymous function that is passed directly in as an argument to another method? var anonymousFunctionSyntax = context.Node.FirstAncestorOrSelf <AnonymousFunctionExpressionSyntax>(); var anonFuncAsArgument = anonymousFunctionSyntax?.Parent as ArgumentSyntax; var invocationPassingExpression = anonFuncAsArgument?.Parent?.Parent as InvocationExpressionSyntax; var invokedMemberAccess = invocationPassingExpression?.Expression as MemberAccessExpressionSyntax; if (invokedMemberAccess?.Name != null) { // Does the anonymous function appear as the first argument to Task.ContinueWith? var invokedMemberSymbol = context.SemanticModel.GetSymbolInfo(invokedMemberAccess.Name, context.CancellationToken).Symbol as IMethodSymbol; if (invokedMemberSymbol?.Name == nameof(Task.ContinueWith) && Utils.IsEqualToOrDerivedFrom(invokedMemberSymbol?.ContainingType, taskSymbol) && invocationPassingExpression?.ArgumentList?.Arguments.FirstOrDefault() == anonFuncAsArgument) { // Does the member access being analyzed belong to the Task that just completed? var firstParameter = GetFirstParameter(anonymousFunctionSyntax); if (firstParameter != null) { // Are we accessing a member of the completed task? ISymbol invokedObjectSymbol = context.SemanticModel.GetSymbolInfo(memberAccessSyntax.Expression, context.CancellationToken).Symbol; IParameterSymbol completedTask = context.SemanticModel.GetDeclaredSymbol(firstParameter); if (EqualityComparer <ISymbol> .Default.Equals(invokedObjectSymbol, completedTask)) { // Skip analysis since Task.Result (et. al) of a completed Task is fair game. return; } } } } CSharpCommonInterest.InspectMemberAccess(context, memberAccessSyntax, Descriptor, problematicMethods); }
private void InspectMemberAccess(SyntaxNodeAnalysisContext context, MemberAccessExpressionSyntax?memberAccessSyntax, IEnumerable <CommonInterest.SyncBlockingMethod> problematicMethods) { if (memberAccessSyntax == null) { return; } if (this.diagnosticReported) { // Don't report more than once per method. return; } if (context.Node.FirstAncestorOrSelf <AnonymousFunctionExpressionSyntax>() != null) { // We do not analyze JTF.Run inside anonymous functions because // they are so often used as callbacks where the signature is constrained. return; } if (Utils.IsWithinNameOf(context.Node as ExpressionSyntax)) { // We do not consider arguments to nameof( ) because they do not represent invocations of code. return; } var invokedMember = context.SemanticModel.GetSymbolInfo(memberAccessSyntax, context.CancellationToken).Symbol; if (invokedMember != null) { foreach (var item in problematicMethods) { if (item.Method.IsMatch(invokedMember)) { var location = memberAccessSyntax.Name.GetLocation(); context.ReportDiagnostic(Diagnostic.Create(Descriptor, location)); this.diagnosticReported = true; } } } }
private static bool IsTaskCompletedWithWhenAll(SyntaxNodeAnalysisContext context, InvocationExpressionSyntax invocationExpr, string taskVariableName) { // We only care about awaited invocations, because an un-awaited Task.WhenAll will be an error. if (invocationExpr.Parent is not AwaitExpressionSyntax) { return(false); } IEnumerable <MemberAccessExpressionSyntax>?memberAccessList = invocationExpr.ChildNodes().OfType <MemberAccessExpressionSyntax>(); if (memberAccessList.Count() != 1) { return(false); } MemberAccessExpressionSyntax?memberAccess = memberAccessList.First(); // Does the invocation have the expected `Task.WhenAll` syntax? This is cheaper to verify before looking up its semantic type. var correctSyntax = ((IdentifierNameSyntax)memberAccess.Expression).Identifier.ValueText == Types.Task.TypeName && ((IdentifierNameSyntax)memberAccess.Name).Identifier.ValueText == Types.Task.WhenAll; if (!correctSyntax) { return(false); } // Is this `Task.WhenAll` invocation from the System.Threading.Tasks.Task type? ITypeSymbol?classType = context.SemanticModel.GetTypeInfo(memberAccess.Expression).Type; var correctType = classType.Name == Types.Task.TypeName && classType.BelongsToNamespace(Types.Task.Namespace); if (!correctType) { return(false); } // Is the task variable passed as an argument to `Task.WhenAll`? return(IsVariablePassedToInvocation(invocationExpr, taskVariableName, byRef: false)); }
private static bool InspectMemberAccess(SyntaxNodeAnalysisContext context, [NotNullWhen(true)] MemberAccessExpressionSyntax?memberAccessSyntax, IEnumerable <CommonInterest.SyncBlockingMethod> problematicMethods) { if (memberAccessSyntax == null) { return(false); } var memberSymbol = context.SemanticModel.GetSymbolInfo(memberAccessSyntax, context.CancellationToken).Symbol; if (memberSymbol != null) { foreach (var item in problematicMethods) { if (item.Method.IsMatch(memberSymbol)) { var location = memberAccessSyntax.Name.GetLocation(); var properties = ImmutableDictionary <string, string> .Empty .Add(ExtensionMethodNamespaceKeyName, item.ExtensionMethodNamespace != null?string.Join(".", item.ExtensionMethodNamespace) : string.Empty); DiagnosticDescriptor descriptor; var messageArgs = new List <object>(2); messageArgs.Add(item.Method.Name); if (item.AsyncAlternativeMethodName != null) { properties = properties.Add(AsyncMethodKeyName, item.AsyncAlternativeMethodName); descriptor = Descriptor; messageArgs.Add(item.AsyncAlternativeMethodName); } else { properties = properties.Add(AsyncMethodKeyName, string.Empty); descriptor = DescriptorNoAlternativeMethod; } Diagnostic diagnostic = Diagnostic.Create(descriptor, location, properties, messageArgs.ToArray()); context.ReportDiagnostic(diagnostic); return(true); } } } return(false); }
public override async Task RegisterCodeFixesAsync(CodeFixContext context) { var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); if (root is null) { return; } context.CancellationToken.ThrowIfCancellationRequested(); var node = root.FindNode(context.Span); if (node is not ArgumentSyntax actualArgument || actualArgument.Expression is not MemberAccessExpressionSyntax actualExpression || actualArgument.Parent is not ArgumentListSyntax argumentList || argumentList.Arguments.Count <= 1 || argumentList.Arguments[1] is not ArgumentSyntax constraintArgument) { return; } // We have either a MemberAccessExpression (Is.Zero) or an InvocationExpression (Is.EqualTo(0)) var constraintMemberExpression = constraintArgument.Expression as MemberAccessExpressionSyntax; var constraintExpression = constraintArgument.Expression as InvocationExpressionSyntax; if (constraintExpression is not null) { constraintMemberExpression = constraintExpression.Expression as MemberAccessExpressionSyntax; } if (constraintMemberExpression is null) { return; } ExpressionSyntax innerConstraintExpression = FindLeftMostTerm(constraintMemberExpression); if (innerConstraintExpression is not SimpleNameSyntax simple || simple.Identifier.ValueText != NUnitFrameworkConstants.NameOfIs) { return; } var nodesToReplace = new Dictionary <SyntaxNode, SyntaxNode>() { // Replace <expression>.<property> with <expression> in first argument { actualArgument.Expression, actualExpression.Expression } }; string description; MemberAccessExpressionSyntax?emptyOrNotEmptyExpression = MatchWithEmpty(constraintMemberExpression, constraintExpression, innerConstraintExpression); if (emptyOrNotEmptyExpression is not null && await IsCollectionType(context, actualExpression.Expression).ConfigureAwait(false)) { nodesToReplace.Add(constraintArgument.Expression, emptyOrNotEmptyExpression); description = $"Use {emptyOrNotEmptyExpression}"; }
protected override async Task <Solution> GetChangedSolutionAsync(CancellationToken cancellationToken) { Document? document = this.document; SyntaxNode?root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); // Find the synchronously blocking call member, // and bookmark it so we can find it again after some mutations have taken place. var syncAccessBookmark = new SyntaxAnnotation(); SimpleNameSyntax syncMethodName = (SimpleNameSyntax)root.FindNode(this.diagnostic.Location.SourceSpan); if (syncMethodName is null) { MemberAccessExpressionSyntax?syncMemberAccess = root.FindNode(this.diagnostic.Location.SourceSpan).FirstAncestorOrSelf <MemberAccessExpressionSyntax>(); syncMethodName = syncMemberAccess.Name; } // When we give the Document a modified SyntaxRoot, yet another is created. So we first assign it to the Document, // then we query for the SyntaxRoot from the Document. document = document.WithSyntaxRoot( root.ReplaceNode(syncMethodName, syncMethodName.WithAdditionalAnnotations(syncAccessBookmark))); root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); syncMethodName = (SimpleNameSyntax)root.GetAnnotatedNodes(syncAccessBookmark).Single(); // We'll need the semantic model later. But because we've annotated a node, that changes the SyntaxRoot // and that renders the default semantic model broken (even though we've already updated the document's SyntaxRoot?!). // So after acquiring the semantic model, update it with the new method body. SemanticModel?semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); AnonymousFunctionExpressionSyntax?originalAnonymousMethodContainerIfApplicable = syncMethodName.FirstAncestorOrSelf <AnonymousFunctionExpressionSyntax>(); MethodDeclarationSyntax? originalMethodDeclaration = syncMethodName.FirstAncestorOrSelf <MethodDeclarationSyntax>(); ISymbol?enclosingSymbol = semanticModel.GetEnclosingSymbol(this.diagnostic.Location.SourceSpan.Start, cancellationToken); var hasReturnValue = ((enclosingSymbol as IMethodSymbol)?.ReturnType as INamedTypeSymbol)?.IsGenericType ?? false; // Ensure that the method or anonymous delegate is using the async keyword. MethodDeclarationSyntax updatedMethod; if (originalAnonymousMethodContainerIfApplicable is object) { updatedMethod = originalMethodDeclaration.ReplaceNode( originalAnonymousMethodContainerIfApplicable, originalAnonymousMethodContainerIfApplicable.MakeMethodAsync(hasReturnValue, semanticModel, cancellationToken)); } else { (document, updatedMethod) = await originalMethodDeclaration.MakeMethodAsync(document, cancellationToken).ConfigureAwait(false); semanticModel = null; // out-dated } if (updatedMethod != originalMethodDeclaration) { // Re-discover our synchronously blocking member. syncMethodName = (SimpleNameSyntax)updatedMethod.GetAnnotatedNodes(syncAccessBookmark).Single(); } ExpressionSyntax?syncExpression = GetSynchronousExpression(syncMethodName); ExpressionSyntax awaitExpression; if (!string.IsNullOrEmpty(this.AlternativeAsyncMethod)) { // Replace the member being called and await the invocation expression. // While doing so, move leading trivia to the surrounding await expression. SimpleNameSyntax?asyncMethodName = syncMethodName.WithIdentifier(SyntaxFactory.Identifier(this.diagnostic.Properties[VSTHRD103UseAsyncOptionAnalyzer.AsyncMethodKeyName])); awaitExpression = SyntaxFactory.AwaitExpression( syncExpression.ReplaceNode(syncMethodName, asyncMethodName).WithoutLeadingTrivia()) .WithLeadingTrivia(syncExpression.GetLeadingTrivia()); } else { // Remove the member being accessed that causes a synchronous block and simply await the object. MemberAccessExpressionSyntax?syncMemberAccess = syncMethodName.FirstAncestorOrSelf <MemberAccessExpressionSyntax>(); ExpressionSyntax? syncMemberStrippedExpression = syncMemberAccess.Expression; // Special case a common pattern of calling task.GetAwaiter().GetResult() and remove both method calls. var expressionMethodCall = (syncMemberStrippedExpression as InvocationExpressionSyntax)?.Expression as MemberAccessExpressionSyntax; if (expressionMethodCall?.Name.Identifier.Text == nameof(Task.GetAwaiter)) { syncMemberStrippedExpression = expressionMethodCall.Expression; } awaitExpression = SyntaxFactory.AwaitExpression(syncMemberStrippedExpression.WithoutLeadingTrivia()) .WithLeadingTrivia(syncMemberStrippedExpression.GetLeadingTrivia()); } if (!(syncExpression.Parent is ExpressionStatementSyntax)) { awaitExpression = SyntaxFactory.ParenthesizedExpression(awaitExpression) .WithAdditionalAnnotations(Simplifier.Annotation); } updatedMethod = updatedMethod .ReplaceNode(syncExpression, awaitExpression); SyntaxNode?newRoot = root.ReplaceNode(originalMethodDeclaration, updatedMethod); Document? newDocument = document.WithSyntaxRoot(newRoot); return(newDocument.Project.Solution); }