From 9d71274c539def47b4dfc5afadb53a8e03008273 Mon Sep 17 00:00:00 2001 From: carlossanlop Date: Mon, 18 May 2020 15:35:52 -0700 Subject: [PATCH 01/20] Analyzer + Fixer: Forward cancellation token to async methods --- ...rdCancellationTokenToAsyncMethods.Fixer.cs | 50 + .../Core/AnalyzerReleases.Unshipped.md | 1 + .../MicrosoftNetCoreAnalyzersResources.resx | 9 + ...rdCancellationTokenToAsyncMethods.Fixer.cs | 120 + .../ForwardCancellationTokenToAsyncMethods.cs | 283 +++ .../MicrosoftNetCoreAnalyzersResources.cs.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.de.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.es.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.fr.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.it.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.ja.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.ko.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.pl.xlf | 15 + ...crosoftNetCoreAnalyzersResources.pt-BR.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.ru.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.tr.xlf | 15 + ...osoftNetCoreAnalyzersResources.zh-Hans.xlf | 15 + ...osoftNetCoreAnalyzersResources.zh-Hant.xlf | 15 + ...ardCancellationTokenToAsyncMethodsTests.cs | 2189 +++++++++++++++++ ...rdCancellationTokenToAsyncMethods.Fixer.vb | 62 + .../DiagnosticCategoryAndIdRanges.txt | 2 +- 21 files changed, 2910 insertions(+), 1 deletion(-) create mode 100644 src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToAsyncMethods.Fixer.cs create mode 100644 src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Fixer.cs create mode 100644 src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.cs create mode 100644 src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethodsTests.cs create mode 100644 src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToAsyncMethods.Fixer.vb diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToAsyncMethods.Fixer.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToAsyncMethods.Fixer.cs new file mode 100644 index 0000000000..075e3c25fa --- /dev/null +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToAsyncMethods.Fixer.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.NetCore.Analyzers.Runtime; + +namespace Microsoft.NetCore.CSharp.Analyzers.Runtime +{ + [ExportCodeFixProvider(LanguageNames.CSharp)] + public sealed class CSharpForwardCancellationTokenToAsyncMethodsFixer : ForwardCancellationTokenToAsyncMethodsFixer + { + private static bool IsCancellationTokenParameter(ParameterSyntax parameter) => + parameter.Type is IdentifierNameSyntax typeIdentifier && typeIdentifier.Identifier.ValueText.Equals(CancellationTokenName, StringComparison.Ordinal); + + private static string? GetCancellationTokenName(SeparatedSyntaxList parameters) => + parameters.FirstOrDefault(p => IsCancellationTokenParameter(p))?.Identifier.ValueText; + + protected override bool TryGetAncestorDeclarationCancellationTokenParameterName(SyntaxNode node, out string? parameterName) + { + parameterName = null; + + SyntaxNode currentNode = node.Parent; + while (currentNode != null) + { + if (currentNode is ParenthesizedLambdaExpressionSyntax lambdaNode) + { + parameterName = GetCancellationTokenName(lambdaNode.ParameterList.Parameters); + break; + } + else if (currentNode is LocalFunctionStatementSyntax localNode) + { + parameterName = GetCancellationTokenName(localNode.ParameterList.Parameters); + break; + } + else if (currentNode is MethodDeclarationSyntax methodNode) + { + parameterName = GetCancellationTokenName(methodNode.ParameterList.Parameters); + break; + } + + currentNode = currentNode.Parent; + } + + return !string.IsNullOrEmpty(parameterName); + } + } +} diff --git a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md index 5784461b06..97d7944c92 100644 --- a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md +++ b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md @@ -23,6 +23,7 @@ CA2012 | Reliability | Hidden | UseValueTasksCorrectlyAnalyzer, [Documentation]( CA2013 | Reliability | Warning | DoNotUseReferenceEqualsWithValueTypesAnalyzer, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca2013) CA2014 | Reliability | Warning | DoNotUseStackallocInLoopsAnalyzer, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca2014) CA2015 | Reliability | Warning | DoNotDefineFinalizersForTypesDerivedFromMemoryManager, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca2015) +CA2016 | Reliability | Warning | ForwardCancellationTokenToAsyncMethodsAnalyzer, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca2016) CA2247 | Usage | Warning | DoNotCreateTaskCompletionSourceWithWrongArguments, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca2247) CA2248 | Usage | Info | DoNotCheckFlagFromDifferentEnum, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca2248) CA2249 | Usage | Info | PreferStringContainsOverIndexOfAnalyzer, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca2249) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx index a7cdfe4655..af4ac78a47 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx @@ -1287,6 +1287,15 @@ Change the '{0}' method call to use the '{1}' overload + + Description - Forward cancellation token to async methods + + + Message - Forward cancellation token to async methods + + + Title - Forward cancellation token to async methods + Change to call the two argument constructor, pass null for the message. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Fixer.cs new file mode 100644 index 0000000000..4795c1c4f3 --- /dev/null +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Fixer.cs @@ -0,0 +1,120 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Analyzer.Utilities; +using Analyzer.Utilities.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Operations; + +namespace Microsoft.NetCore.Analyzers.Runtime +{ + /// + /// + public abstract class ForwardCancellationTokenToAsyncMethodsFixer : CodeFixProvider + { + // Looks for a ct parameter in the ancestor method or function declaration. If one is found, retrieve the name of the parameter. + // Returns true if a ct parameter was found and parameterName is not null or empty. Returns false otherwise. + protected abstract bool TryGetAncestorDeclarationCancellationTokenParameterName(SyntaxNode node, out string? parameterName); + + public override ImmutableArray FixableDiagnosticIds => + ImmutableArray.Create(ForwardCancellationTokenToAsyncMethodsAnalyzer.RuleId); + + public sealed override FixAllProvider GetFixAllProvider() => + WellKnownFixAllProviders.BatchFixer; + + protected const string CancellationTokenName = "CancellationToken"; + + public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + Document doc = context.Document; + CancellationToken ct = context.CancellationToken; + SyntaxNode root = await doc.GetSyntaxRootAsync(ct).ConfigureAwait(false); + + if (!(root.FindNode(context.Span, getInnermostNodeForTie: true) is SyntaxNode node)) + { + return; + } + + SemanticModel model = await doc.GetSemanticModelAsync(ct).ConfigureAwait(false); + if (!(model.GetOperation(node, ct) is IInvocationOperation invocation)) + { + return; + } + + if (!TryGetAncestorDeclarationCancellationTokenParameterName(node, out string? parameterName)) + { + return; + } + + string title = MicrosoftNetCoreAnalyzersResources.ForwardCancellationTokenToAsyncMethodsTitle; + + Task createChangedDocument(CancellationToken _) => FixInvocation(doc, root, invocation, parameterName); + + context.RegisterCodeFix( + new MyCodeAction( + title: title, + createChangedDocument, + equivalenceKey: title), + context.Diagnostics); + } + + private static Task FixInvocation(Document doc, SyntaxNode root, IInvocationOperation invocation, string cancellationTokenParamenterName) + { + SyntaxGenerator generator = SyntaxGenerator.GetGenerator(doc); + + // Create a new ct argument to pass to the invocation, using the ancestor method parameter name + SyntaxNode cancellationTokenIdentifier = generator.IdentifierName(cancellationTokenParamenterName); + SyntaxNode cancellationTokenNode = generator.Argument(cancellationTokenIdentifier); + + // Pass the same arguments and add the ct + List newArguments = new List(); + foreach (IArgumentOperation argument in invocation.Arguments) + { + if (argument.Parameter.Type.Name != CancellationTokenName || !argument.IsImplicit) + { + newArguments.Add(argument.Syntax); + } + } + newArguments.Add(cancellationTokenNode); + + + SyntaxNode newInvocation; + // The instance is null when calling a static method from another type + if (invocation.Instance == null) + { + SyntaxNode staticType = generator.TypeExpressionForStaticMemberAccess(invocation.TargetMethod.ContainingType); + newInvocation = generator.MemberAccessExpression(staticType, invocation.TargetMethod.Name); + } + // The instance is implicit when calling a method from the same type, call the method directly + else if (invocation.Instance.IsImplicit) + { + newInvocation = invocation.GetInstance(); // GetInstance will return the method name + } + // Calling a method from an object, we must include the instance variable name + else + { + newInvocation = generator.MemberAccessExpression(invocation.GetInstance(), invocation.TargetMethod.Name); + } + // Insert the new arguments to the new invocation + SyntaxNode newInvocationWithArguments = generator.InvocationExpression(newInvocation, newArguments); + + SyntaxNode newRoot = generator.ReplaceNode(root, invocation.Syntax, newInvocationWithArguments); + return Task.FromResult(doc.WithSyntaxRoot(newRoot)); + } + + // Needed for Telemetry (https://github.com/dotnet/roslyn-analyzers/issues/192) + private class MyCodeAction : DocumentChangeAction + { + public MyCodeAction(string title, Func> createChangedDocument, string equivalenceKey) + : base(title, createChangedDocument, equivalenceKey) + { + } + } + } +} \ No newline at end of file diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.cs new file mode 100644 index 0000000000..bbc246df2f --- /dev/null +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.cs @@ -0,0 +1,283 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Immutable; +using System.Linq; +using Analyzer.Utilities; +using Analyzer.Utilities.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; + +namespace Microsoft.NetCore.Analyzers.Runtime +{ + /// + /// CA2016: Forward CancellationToken to async methods. + /// + /// Conditions for positive cases: + /// - The node to analyze is an awaited invocation, or is the origin invocation of a ConfigureAwait. + /// - The parent method signature has a ct parameter. + /// - The invocation is not receiving a ct argument. + /// - The invocation method either: + /// - Only has one method version, but the signature has one ct, set to default. + /// - Has a method overload with the exact same arguments in the same order, plus one ct parameter at the end. + /// - An Action/Func instance that receives a ct, and there's an awaited invocation inside. + /// + /// Conditions for negative cases: + /// - The parent method signature does not have a ct parameter. + /// - The node to analyze is not an awaited invocation. + /// - The awaited invocation is already receiving a ct argument. + /// - The invocation method does not have an overload with the exact same arguments that also receives a ct. + /// - Includes the case where the user is *not* passing the parent method ct argument, but rather creating a new ct inside the method and passing that as argument, like with CancellationTokenSource. + /// - Includes the case where the user is explicitly passing `default`, `default(CancellationToken)` or `CancellationToken.None` to the invocation. + /// - If the overload that receives a ct receives more than one ct, do not suggest it. + /// + /// Future improvements: + /// - Finding an overload with one ct parameter, but not in last position. + /// - Passing a named ct in a different position than default. + /// + [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] + public sealed class ForwardCancellationTokenToAsyncMethodsAnalyzer : DiagnosticAnalyzer + { + internal const string RuleId = "CA2016"; + + private static readonly LocalizableString s_localizableDescription = new LocalizableResourceString( + nameof(MicrosoftNetCoreAnalyzersResources.ForwardCancellationTokenToAsyncMethodsDescription), + MicrosoftNetCoreAnalyzersResources.ResourceManager, + typeof(MicrosoftNetCoreAnalyzersResources) + ); + + private static readonly LocalizableString s_localizableMessage = new LocalizableResourceString( + nameof(MicrosoftNetCoreAnalyzersResources.ForwardCancellationTokenToAsyncMethodsMessage), + MicrosoftNetCoreAnalyzersResources.ResourceManager, + typeof(MicrosoftNetCoreAnalyzersResources) + ); + + private static readonly LocalizableString s_localizableTitle = new LocalizableResourceString( + nameof(MicrosoftNetCoreAnalyzersResources.ForwardCancellationTokenToAsyncMethodsTitle), + MicrosoftNetCoreAnalyzersResources.ResourceManager, + typeof(MicrosoftNetCoreAnalyzersResources) + ); + internal static DiagnosticDescriptor ForwardCancellationTokenToAsyncMethodsRule = DiagnosticDescriptorHelper.Create( + RuleId, + s_localizableTitle, + s_localizableMessage, + DiagnosticCategory.Reliability, + RuleLevel.BuildWarning, + s_localizableDescription, + isPortedFxCopRule: false, + isDataflowRule: false + ); + + public override ImmutableArray SupportedDiagnostics => + ImmutableArray.Create(ForwardCancellationTokenToAsyncMethodsRule); + + public override void Initialize(AnalysisContext context) + { + context.EnableConcurrentExecution(); + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.RegisterCompilationStartAction(AnalyzeCompilationStart); + } + + private void AnalyzeCompilationStart(CompilationStartAnalysisContext context) + { + if (!context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingCancellationToken, out INamedTypeSymbol? cancellationTokenType)) + { + return; + } + + // Retrieve the ConfigureAwait methods that could also be detected, which can come from: + // - A Task + // - A generic Task + // - A ValueTask + // - A generic ValueTask + if (!context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingTasksTask, out INamedTypeSymbol? taskType) || + !context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingTasksValueTask, out INamedTypeSymbol? valueTaskType) || + !context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingTasksGenericTask, out INamedTypeSymbol? genericTaskType) || + !context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingTasksGenericValueTask, out INamedTypeSymbol? genericValueTaskType)) + { + return; + } + + const string configureAwaitName = "ConfigureAwait"; + if (!(taskType.GetMembers(configureAwaitName).OfType().FirstOrDefault() is IMethodSymbol taskConfigureAwaitMethod) || + !(genericTaskType.GetMembers(configureAwaitName).OfType().FirstOrDefault() is IMethodSymbol genericTaskConfigureAwaitMethod) || + !(valueTaskType.GetMembers(configureAwaitName).OfType().FirstOrDefault() is IMethodSymbol valueTaskConfigureAwaitMethod) || + !(genericValueTaskType.GetMembers(configureAwaitName).OfType().FirstOrDefault() is IMethodSymbol genericValueTaskConfigureAwaitMethod)) + { + return; + } + + context.RegisterOperationAction(context => + { + if (ShouldAnalyze( + (IAwaitOperation)context.Operation, + cancellationTokenType, + taskConfigureAwaitMethod, + genericTaskConfigureAwaitMethod, + valueTaskConfigureAwaitMethod, + genericValueTaskConfigureAwaitMethod, + out IInvocationOperation? invocation)) + { + context.ReportDiagnostic(invocation!.CreateDiagnostic(ForwardCancellationTokenToAsyncMethodsRule)); + } + }, + OperationKind.Await); + } + + private static bool ShouldAnalyze(IAwaitOperation awaitOperation, INamedTypeSymbol cancellationTokenType, + IMethodSymbol taskConfigureAwaitMethod, + IMethodSymbol genericTaskConfigureAwaitMethod, + IMethodSymbol valueTaskConfigureAwaitMethod, + IMethodSymbol genericValueTaskConfigureAwaitMethod, + out IInvocationOperation? invocation) + { + invocation = null; + + if (!(awaitOperation.Operation is IInvocationOperation awaitedInvocation)) + { + return false; + } + invocation = awaitedInvocation; + IMethodSymbol method = awaitedInvocation.TargetMethod; + + // Check if the child operation of the await is ConfigureAwait + // in which case we should analyze the grandchild operation + if (method.OriginalDefinition.Equals(taskConfigureAwaitMethod) || + method.OriginalDefinition.Equals(genericTaskConfigureAwaitMethod) || + method.OriginalDefinition.Equals(valueTaskConfigureAwaitMethod) || + method.OriginalDefinition.Equals(genericValueTaskConfigureAwaitMethod)) + { + if (awaitedInvocation.Instance is IInvocationOperation instanceOperation) + { + invocation = instanceOperation; + method = instanceOperation.TargetMethod; + } + else + { + return false; + } + } + + // If the invocation has an optional implicit ct or an overload that takes a ct, continue + if (!InvocationIgnoresOptionalCancellationToken(cancellationTokenType, invocation, method) && + !MethodHasCancellationTokenOverload(cancellationTokenType, method)) + { + return false; + } + + // Find the ancestor method that contains this invocation + if (!TryGetAncestorDeclaration(invocation, out IMethodSymbol? methodDeclaration)) + { + return false; + } + + // Check if the ancestor method has a ct that we can pass to the invocation + if (!VerifyMethodOnlyHasOneCancellationTokenAsLastArgument(cancellationTokenType, methodDeclaration!)) + { + return false; + } + + return true; + } + + // Looks for an ancestor that could be a method or function declaration. + private static bool TryGetAncestorDeclaration(IInvocationOperation invocation, out IMethodSymbol? declaration) + { + declaration = null; + + IOperation currentOperation = invocation.Parent; + while (currentOperation != null) + { + if (currentOperation.Kind == OperationKind.AnonymousFunction && currentOperation is IAnonymousFunctionOperation anonymousFunction) + { + declaration = anonymousFunction.Symbol; + break; + } + else if (currentOperation.Kind == OperationKind.LocalFunction && currentOperation is ILocalFunctionOperation localFunction) + { + declaration = localFunction.Symbol; + break; + } + else if (currentOperation.Kind == OperationKind.MethodBody && currentOperation is IMethodBodyOperation methodBody) + { + if (methodBody.SemanticModel.GetDeclaredSymbol(methodBody.Syntax) is IMethodSymbol method) + { + declaration = method; + break; + } + } + else if (currentOperation.Kind == OperationKind.Block && currentOperation is IBlockOperation methodBaseBody) + { + if (methodBaseBody.SemanticModel.GetDeclaredSymbol(methodBaseBody.Syntax) is IMethodSymbol method) + { + declaration = method; + // There are many kinds of blocks, so only break if we found a method symbol for this block. + // Otherwise, blocks inside anonymous or local functions would not be detected - those would be the parent of the current operation. + break; + } + } + + currentOperation = currentOperation.Parent; + } + + return declaration != null; + } + + // Check if the method only takes one ct and is the last parameter in the method signature. + // We want to compare the current method signature to any others with the exact same arguments in the exact same order. + private static bool VerifyMethodOnlyHasOneCancellationTokenAsLastArgument(ITypeSymbol cancellationTokenType, IMethodSymbol methodDeclaration) + { + return methodDeclaration.Parameters.Count(x => x.Type.Equals(cancellationTokenType)) == 1 && + methodDeclaration.Parameters.Last().Type.Equals(cancellationTokenType); + } + + // Check if the currently used overload is the one that takes the ct, but is utilizing the default value offered in the method signature. + // We want to offer a diagnostic for this case, so the user explicitly passes the ancestor's ct. + private static bool InvocationIgnoresOptionalCancellationToken(ITypeSymbol cancellationTokenType, IInvocationOperation invocation, IMethodSymbol method) + { + if (method.Parameters.Any() && method.Parameters.Last() is IParameterSymbol lastParameter && lastParameter.Type.Equals(cancellationTokenType)) + { + // If the ct parameter has a default value, return true if a value is not being explicitly passed in the invocation + return lastParameter.IsOptional && // Has a default value + invocation.Arguments.Last() is IArgumentOperation lastArgument && + lastArgument.IsImplicit; // The default value is being used + } + + return false; + } + + // Check if there's a method overload with the same parameters as this one, in the same order, plus a ct at the end. + private static bool MethodHasCancellationTokenOverload(ITypeSymbol cancellationTokenType, IMethodSymbol method) + { + IMethodSymbol? overload = method.ContainingType.GetMembers(method.Name).OfType().FirstOrDefault(x => HasSameParametersPlusCancellationToken(cancellationTokenType, method, x)); + + return overload != null; + } + + // Checks if the parameters of the two passed methods only differ in a ct. + private static bool HasSameParametersPlusCancellationToken(ITypeSymbol cancellationTokenType, IMethodSymbol originalMethod, IMethodSymbol methodToCompare) + { + if (!originalMethod.Name.Equals(methodToCompare.Name, StringComparison.Ordinal) || + originalMethod.Equals(methodToCompare) || + methodToCompare.Parameters.Length == 0 || + methodToCompare.Parameters.Length != originalMethod.Parameters.Length + 1 || + !methodToCompare.Parameters[methodToCompare.Parameters.Length - 1].Type.Equals(cancellationTokenType)) + { + return false; + } + + for (int i = 0; i < originalMethod.Parameters.Length; i++) + { + IParameterSymbol? originalParameter = originalMethod.Parameters[i]; + IParameterSymbol? comparedParameter = methodToCompare.Parameters[i]; + if (originalParameter == null || comparedParameter == null || !originalParameter.Type.Equals(comparedParameter.Type)) + { + return false; + } + } + + return true; + } + } +} \ No newline at end of file diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf index 5983e3b3d2..8adeee3692 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf @@ -877,6 +877,21 @@ Finalizační metody by měly volat finalizační metodu základní třídy + + Description - Forward cancellation token to async methods + Description - Forward cancellation token to async methods + + + + Message - Forward cancellation token to async methods + Message - Forward cancellation token to async methods + + + + Title - Forward cancellation token to async methods + Title - Forward cancellation token to async methods + + Avoid hardcoding SecurityProtocolType {0}, and instead use SecurityProtocolType.SystemDefault to allow the operating system to choose the best Transport Layer Security protocol to use. Vyhněte se pevnému zakódování hodnoty SecurityProtocolType {0}. Místo toho použijte SecurityProtocolType.SystemDefault, aby mohl operační systém sám zvolit nejlepší protokol TLS (Transport Layer Security), který se má použít. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf index 97356fb45f..7285433e06 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf @@ -877,6 +877,21 @@ Finalizer sollten Basisklassen-Finalizer aufrufen + + Description - Forward cancellation token to async methods + Description - Forward cancellation token to async methods + + + + Message - Forward cancellation token to async methods + Message - Forward cancellation token to async methods + + + + Title - Forward cancellation token to async methods + Title - Forward cancellation token to async methods + + Avoid hardcoding SecurityProtocolType {0}, and instead use SecurityProtocolType.SystemDefault to allow the operating system to choose the best Transport Layer Security protocol to use. Vermeiden Sie eine Hartcodierung von SecurityProtocolType "{0}", und verwenden Sie stattdessen "SecurityProtocolType.SystemDefault", um dem Betriebssystem die Auswahl des besten TLS-Protokolls (Transport Layer Security) zu ermöglichen. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf index ae065c1fde..2f9e7010dd 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf @@ -877,6 +877,21 @@ Los finalizadores deben llamar al finalizador de la clase base + + Description - Forward cancellation token to async methods + Description - Forward cancellation token to async methods + + + + Message - Forward cancellation token to async methods + Message - Forward cancellation token to async methods + + + + Title - Forward cancellation token to async methods + Title - Forward cancellation token to async methods + + Avoid hardcoding SecurityProtocolType {0}, and instead use SecurityProtocolType.SystemDefault to allow the operating system to choose the best Transport Layer Security protocol to use. Evite codificar SecurityProtocolType {0} de forma rígida y, en su lugar, use SecurityProtocolType.SystemDefault para permitir que el sistema operativo elija el mejor protocolo de Seguridad de la capa de transporte que se puede usar. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf index be42d6a212..0ac54c485d 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf @@ -877,6 +877,21 @@ Les finaliseurs doivent appeler un finaliseur de classe de base + + Description - Forward cancellation token to async methods + Description - Forward cancellation token to async methods + + + + Message - Forward cancellation token to async methods + Message - Forward cancellation token to async methods + + + + Title - Forward cancellation token to async methods + Title - Forward cancellation token to async methods + + Avoid hardcoding SecurityProtocolType {0}, and instead use SecurityProtocolType.SystemDefault to allow the operating system to choose the best Transport Layer Security protocol to use. Évitez tout codage en dur du SecurityProtocolType {0}, et utilisez plutôt SecurityProtocolType.SystemDefault pour permettre au système d'exploitation de choisir le meilleur protocole TLS à utiliser. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf index 31071226dd..94c0bc4260 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf @@ -877,6 +877,21 @@ I finalizzatori devono chiamare il finalizzatore della classe di base + + Description - Forward cancellation token to async methods + Description - Forward cancellation token to async methods + + + + Message - Forward cancellation token to async methods + Message - Forward cancellation token to async methods + + + + Title - Forward cancellation token to async methods + Title - Forward cancellation token to async methods + + Avoid hardcoding SecurityProtocolType {0}, and instead use SecurityProtocolType.SystemDefault to allow the operating system to choose the best Transport Layer Security protocol to use. Evitare di impostare SecurityProtocolType {0} come hardcoded e usare SecurityProtocolType.SystemDefault per consentire al sistema operativo di scegliere il migliore protocollo Transport Layer Security da usare. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf index bf5417bb3a..df5013eae2 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf @@ -877,6 +877,21 @@ ファイナライザーは基底クラスのファイナライザーを呼び出さなければなりません + + Description - Forward cancellation token to async methods + Description - Forward cancellation token to async methods + + + + Message - Forward cancellation token to async methods + Message - Forward cancellation token to async methods + + + + Title - Forward cancellation token to async methods + Title - Forward cancellation token to async methods + + Avoid hardcoding SecurityProtocolType {0}, and instead use SecurityProtocolType.SystemDefault to allow the operating system to choose the best Transport Layer Security protocol to use. SecurityProtocolType {0} をハードコードしないでください。代わりに、SecurityProtocolType.SystemDefault を使用して、オペレーティング システムが使用する最適なトランスポート層セキュリティ プロトコルを選択できるようにします。 diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf index 72f5141f5e..cb2cf7cdf5 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf @@ -877,6 +877,21 @@ 종료자가 기본 클래스 종료자를 호출해야 합니다. + + Description - Forward cancellation token to async methods + Description - Forward cancellation token to async methods + + + + Message - Forward cancellation token to async methods + Message - Forward cancellation token to async methods + + + + Title - Forward cancellation token to async methods + Title - Forward cancellation token to async methods + + Avoid hardcoding SecurityProtocolType {0}, and instead use SecurityProtocolType.SystemDefault to allow the operating system to choose the best Transport Layer Security protocol to use. 운영 체제에서 사용할 최상의 전송 계층 보안 프로토콜을 선택할 수 있게 하려면 SecurityProtocolType {0}을(를) 하드 코딩하지 말고 대신 SecurityProtocolType.SystemDefault를 사용하세요. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf index 67cb824d60..21ab00fede 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf @@ -878,6 +878,21 @@ Finalizatory powinny wywoływać finalizator klasy podstawowej + + Description - Forward cancellation token to async methods + Description - Forward cancellation token to async methods + + + + Message - Forward cancellation token to async methods + Message - Forward cancellation token to async methods + + + + Title - Forward cancellation token to async methods + Title - Forward cancellation token to async methods + + Avoid hardcoding SecurityProtocolType {0}, and instead use SecurityProtocolType.SystemDefault to allow the operating system to choose the best Transport Layer Security protocol to use. Unikaj kodowania na stałe wartości SecurityProtocolType {0} i zamiast tego użyj wartości SecurityProtocolType.SystemDefault, aby umożliwić systemowi operacyjnemu wybranie najlepszego protokołu Transport Layer Security do użycia. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf index ec1b564e88..61819bf6da 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf @@ -877,6 +877,21 @@ Os finalizadores devem chamar o finalizador da classe base + + Description - Forward cancellation token to async methods + Description - Forward cancellation token to async methods + + + + Message - Forward cancellation token to async methods + Message - Forward cancellation token to async methods + + + + Title - Forward cancellation token to async methods + Title - Forward cancellation token to async methods + + Avoid hardcoding SecurityProtocolType {0}, and instead use SecurityProtocolType.SystemDefault to allow the operating system to choose the best Transport Layer Security protocol to use. Evite codificar SecurityProtocolType {0}. Em vez disso, use SecurityProtocolType.SystemDefault para permitir que o sistema operacional escolha o melhor protocolo TLS a ser usado. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf index 4682a22f5b..d612c80fc0 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf @@ -877,6 +877,21 @@ Методы завершения должны вызывать метод завершения базового класса + + Description - Forward cancellation token to async methods + Description - Forward cancellation token to async methods + + + + Message - Forward cancellation token to async methods + Message - Forward cancellation token to async methods + + + + Title - Forward cancellation token to async methods + Title - Forward cancellation token to async methods + + Avoid hardcoding SecurityProtocolType {0}, and instead use SecurityProtocolType.SystemDefault to allow the operating system to choose the best Transport Layer Security protocol to use. Не встраивайте SecurityProtocolType {0}. Используйте SecurityProtocolType.SystemDefault, чтобы операционная система выбрала оптимальный протокол TSL. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf index 08eba9b4ad..a6ccab7c54 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf @@ -877,6 +877,21 @@ Sonlandırıcılar temel sınıf sonlandırıcısını çağırmalıdır + + Description - Forward cancellation token to async methods + Description - Forward cancellation token to async methods + + + + Message - Forward cancellation token to async methods + Message - Forward cancellation token to async methods + + + + Title - Forward cancellation token to async methods + Title - Forward cancellation token to async methods + + Avoid hardcoding SecurityProtocolType {0}, and instead use SecurityProtocolType.SystemDefault to allow the operating system to choose the best Transport Layer Security protocol to use. SecurityProtocolType {0} değerini sabit kodlamaktan kaçının ve bunun yerine işletim sisteminin kullanılacak en iyi Aktarım Katmanı Güvenlik protokolünü seçmesine izin vermek için SecurityProtocolType.SystemDefault kullanın. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf index 0a2f0cdcef..155800313a 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf @@ -877,6 +877,21 @@ 终结器应调用基类的终结器 + + Description - Forward cancellation token to async methods + Description - Forward cancellation token to async methods + + + + Message - Forward cancellation token to async methods + Message - Forward cancellation token to async methods + + + + Title - Forward cancellation token to async methods + Title - Forward cancellation token to async methods + + Avoid hardcoding SecurityProtocolType {0}, and instead use SecurityProtocolType.SystemDefault to allow the operating system to choose the best Transport Layer Security protocol to use. 避免 hardcoding SecurityProtocolType {0}, 转而使用 SecurityProtocolType. SystemDefault 允许操作系统选择要使用的最佳传输层安全性协议。 diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf index 2f4c5b6130..bed3baa5ae 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf @@ -877,6 +877,21 @@ 完成項應呼叫基底類別完成項 + + Description - Forward cancellation token to async methods + Description - Forward cancellation token to async methods + + + + Message - Forward cancellation token to async methods + Message - Forward cancellation token to async methods + + + + Title - Forward cancellation token to async methods + Title - Forward cancellation token to async methods + + Avoid hardcoding SecurityProtocolType {0}, and instead use SecurityProtocolType.SystemDefault to allow the operating system to choose the best Transport Layer Security protocol to use. 請避免對 SecurityProtocolType {0} 進行硬式編碼,並改為使用 SecurityProtocolType.SystemDefault 以便作業系統針對要使用的傳輸層安全性通訊協定進行最佳選擇。 diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethodsTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethodsTests.cs new file mode 100644 index 0000000000..fdce63f683 --- /dev/null +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethodsTests.cs @@ -0,0 +1,2189 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Xunit; +using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< + Microsoft.NetCore.Analyzers.Runtime.ForwardCancellationTokenToAsyncMethodsAnalyzer, + Microsoft.NetCore.CSharp.Analyzers.Runtime.CSharpForwardCancellationTokenToAsyncMethodsFixer>; +using VerifyVB = Test.Utilities.VisualBasicCodeFixVerifier< + Microsoft.NetCore.Analyzers.Runtime.ForwardCancellationTokenToAsyncMethodsAnalyzer, + Microsoft.NetCore.VisualBasic.Analyzers.Runtime.BasicForwardCancellationTokenToAsyncMethodsFixer>; + +namespace Microsoft.NetCore.Analyzers.Runtime.UnitTests +{ + public class ForwardCancellationTokenToAsyncMethodsTests + { + #region No Diagnostic - C# + + [Fact] + public Task CS_NoDiagnostic_NoParentToken_NoToken() + { + return VerifyCS.VerifyAnalyzerAsync(@" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M() + { + await MethodAsync(); + } + Task MethodAsync() => Task.CompletedTask; +} + "); + } + + [Fact] + public Task CS_NoDiagnostic_NoParentToken_TokenDefault() + { + return VerifyCS.VerifyAnalyzerAsync(@" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M() + { + await MethodAsync(); + } + Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; +} + "); + } + + [Fact] + public Task CS_NoDiagnostic_NoToken() + { + return VerifyCS.VerifyAnalyzerAsync(@" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + await MethodAsync(); + } + Task MethodAsync() => Task.CompletedTask; +} + "); + } + + [Fact] + public Task CS_NoDiagnostic_NoAwait() + { + return VerifyCS.VerifyAnalyzerAsync(@" +using System.Threading; +using System.Threading.Tasks; +class C +{ + void M(CancellationToken ct) + { + MethodAsync(); + } + Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; +} + "); + } + + [Fact] + public Task CS_NoDiagnostic_SaveTask() + { + return VerifyCS.VerifyAnalyzerAsync(@" +using System.Threading; +using System.Threading.Tasks; +class C +{ + void M(CancellationToken ct) + { + Task t = MethodAsync(); + } + Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; +} + "); + } + + [Fact] + public Task CS_NoDiagnostic_OverloadArgumentsDontMatch() + { + return VerifyCS.VerifyAnalyzerAsync(@" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + await MethodAsync(5, ""Hello world""); + } + Task MethodAsync(int i, string s) => Task.CompletedTask; + Task MethodAsync(int i, CancellationToken ct) => Task.CompletedTask; +} + "); + } + + [Fact] + public Task CS_NoDiagnostic_AlreadyPassingToken() + { + return VerifyCS.VerifyAnalyzerAsync(@" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + await MethodAsync(ct); + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c) => Task.CompletedTask; +} + "); + } + + [Fact] + public Task CS_NoDiagnostic_PassingTokenFromSource() + { + return VerifyCS.VerifyAnalyzerAsync(@" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + CancellationTokenSource cts = new CancellationTokenSource(); + await MethodAsync(cts.Token); + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c) => Task.CompletedTask; +} + "); + } + + [Fact] + public Task CS_NoDiagnostic_PassingExplicitDefault() + { + return VerifyCS.VerifyAnalyzerAsync(@" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + await MethodAsync(default); + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c) => Task.CompletedTask; +} + "); + } + + [Fact] + public Task CS_NoDiagnostic_PassingExplicitDefaultCancellationToken() + { + return VerifyCS.VerifyAnalyzerAsync(@" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + await MethodAsync(default(CancellationToken)); + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c) => Task.CompletedTask; +} + "); + } + + [Fact] + public Task CS_NoDiagnostic_PassingExpliciCancellationTokenNone() + { + return VerifyCS.VerifyAnalyzerAsync(@" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + await MethodAsync(CancellationToken.None); + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c) => Task.CompletedTask; +} + "); + } + + [Fact] + public Task CS_NoDiagnostic_ActionDelegateNoAwait() + { + return VerifyCS.VerifyAnalyzerAsync(@" +using System; +using System.Threading; +using System.Threading.Tasks; +class C +{ + void M(CancellationToken ct) + { + Action a = (CancellationToken c) => MethodAsync(); + a(ct); + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c) => Task.CompletedTask; +} + "); + } + + [Fact] + public Task CS_NoDiagnostic_FuncDelegateAwaitOutside() + { + return VerifyCS.VerifyAnalyzerAsync(@" +using System; +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + Func f = (CancellationToken c) => MethodAsync(); + await f(ct); + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c) => Task.CompletedTask; +} + "); + } + + [Fact] + public Task CS_NoDiagnostic_NestedFunctionNoAwait() + { + return VerifyCS.VerifyAnalyzerAsync(@" +using System; +using System.Threading; +using System.Threading.Tasks; +class C +{ + void M(CancellationToken ct) + { + void LocalMethod(CancellationToken token) + { + MethodAsync(); + } + LocalMethod(ct); + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c) => Task.CompletedTask; +} + "); + } + + [Fact] + public Task CS_NoDiagnostic_NestedFunctionAwaitOutside() + { + return VerifyCS.VerifyAnalyzerAsync(@" +using System; +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + Task LocalMethod(CancellationToken token) + { + return MethodAsync(); + } + await LocalMethod(ct); + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c) => Task.CompletedTask; +} + "); + } + + [Fact] + public Task CS_NoDiagnostic_OverloadTokenNotLastParameter() + { + return VerifyCS.VerifyAnalyzerAsync(@" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + await MethodAsync(); + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(int x, CancellationToken ct, string s) => Task.CompletedTask; +} + "); + } + + [Fact] + public Task CS_NoDiagnostic_OverloadWithMultipleTokens() + { + return VerifyCS.VerifyAnalyzerAsync(@" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + await MethodAsync(); + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c1, CancellationToken ct2) => Task.CompletedTask; +} + "); + } + + [Fact] + public Task CS_NoDiagnostic_OverloadWithMultipleTokensSeparated() + { + return VerifyCS.VerifyAnalyzerAsync(@" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + await MethodAsync(); + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(int x, CancellationToken c1, string s, CancellationToken ct2) => Task.CompletedTask; +} + "); + } + + #endregion + + #region Diagnostic = C# + + [Fact] + public Task CS_Diagnostic_Class_TokenDefault() + { + string originalCode = @" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + await [|MethodAsync()|]; + } + Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; +} + "; + string fixedCode = @" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + await MethodAsync(ct); + } + Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; +} + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task CS_Diagnostic_Class_TokenDefault_WithConfigureAwait() + { + string originalCode = @" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + await [|MethodAsync()|].ConfigureAwait(false); + } + Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; +} + "; + string fixedCode = @" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + await MethodAsync(ct).ConfigureAwait(false); + } + Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; +} + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task CS_Diagnostic_ClassStaticMethod_TokenDefault() + { + string originalCode = @" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + await [|MethodAsync()|]; + } + static Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; +} + "; + string fixedCode = @" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + await MethodAsync(ct); + } + static Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; +} + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task CS_Diagnostic_ClassStaticMethod_TokenDefault_WithConfigureAwait() + { + string originalCode = @" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + await [|MethodAsync()|].ConfigureAwait(false); + } + static Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; +} + "; + string fixedCode = @" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + await MethodAsync(ct).ConfigureAwait(false); + } + static Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; +} + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task CS_Diagnostic_OtherClass_TokenDefault() + { + string originalCode = @" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + O o = new O(); + await [|o.MethodAsync()|]; + } +} +class O +{ + public Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; +} + "; + string fixedCode = @" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + O o = new O(); + await o.MethodAsync(ct); + } +} +class O +{ + public Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; +} + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task CS_Diagnostic_OtherClass_TokenDefault_WithConfigureAwait() + { + string originalCode = @" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + O o = new O(); + await [|o.MethodAsync()|]; + } +} +class O +{ + public Task MethodAsync() => Task.CompletedTask; + public Task MethodAsync(CancellationToken c) => Task.CompletedTask; +} + "; + string fixedCode = @" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + O o = new O(); + await o.MethodAsync(ct); + } +} +class O +{ + public Task MethodAsync() => Task.CompletedTask; + public Task MethodAsync(CancellationToken c) => Task.CompletedTask; +} + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task CS_Diagnostic_OtherClassStaticMethod_TokenDefault() + { + string originalCode = @" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + await [|O.MethodAsync()|]; + } +} +class O +{ + public static Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; +} + "; + string fixedCode = @" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + await O.MethodAsync(ct); + } +} +class O +{ + public static Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; +} + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task CS_Diagnostic_OtherClassStaticMethod_TokenDefault_WithConfigureAwait() + { + string originalCode = @" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + await [|O.MethodAsync()|]; + } +} +class O +{ + static public Task MethodAsync() => Task.CompletedTask; + static public Task MethodAsync(CancellationToken c) => Task.CompletedTask; +} + "; + string fixedCode = @" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + await O.MethodAsync(ct); + } +} +class O +{ + static public Task MethodAsync() => Task.CompletedTask; + static public Task MethodAsync(CancellationToken c) => Task.CompletedTask; +} + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task CS_Diagnostic_Struct_TokenDefault() + { + string originalCode = @" +using System.Threading; +using System.Threading.Tasks; +struct S +{ + async void M(CancellationToken ct) + { + await [|MethodAsync()|]; + } + Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; +} + "; + string fixedCode = @" +using System.Threading; +using System.Threading.Tasks; +struct S +{ + async void M(CancellationToken ct) + { + await MethodAsync(ct); + } + Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; +} + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task CS_Diagnostic_Struct_TokenDefault_WithConfigureAwait() + { + string originalCode = @" +using System.Threading; +using System.Threading.Tasks; +struct S +{ + async void M(CancellationToken ct) + { + await [|MethodAsync()|].ConfigureAwait(false); + } + Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; +} + "; + string fixedCode = @" +using System.Threading; +using System.Threading.Tasks; +struct S +{ + async void M(CancellationToken ct) + { + await MethodAsync(ct).ConfigureAwait(false); + } + Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; +} + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task CS_Diagnostic_OverloadToken() + { + string originalCode = @" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + await [|MethodAsync()|]; + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c) => Task.CompletedTask; +} + "; + string fixedCode = @" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + await MethodAsync(ct); + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c) => Task.CompletedTask; +} + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task CS_Diagnostic_OverloadToken_WithConfigureAwait() + { + string originalCode = @" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + await [|MethodAsync()|]; + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c) => Task.CompletedTask; +} + "; + string fixedCode = @" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + await MethodAsync(ct); + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c) => Task.CompletedTask; +} + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task CS_Diagnostic_OverloadTokenDefault() + { + string originalCode = @" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + await [|MethodAsync()|]; + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; +} + "; + string fixedCode = @" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + await MethodAsync(ct); + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; +} + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task CS_Diagnostic_OverloadTokenDefault_WithConfigureAwait() + { + string originalCode = @" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + await [|MethodAsync()|].ConfigureAwait(false); + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; +} + "; + string fixedCode = @" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + await MethodAsync(ct).ConfigureAwait(false); + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; +} + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task CS_Diagnostic_OverloadsArgumentsMatch() + { + string originalCode = @" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + await [|MethodAsync(5, ""Hello world"")|]; + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c) => Task.CompletedTask; + Task MethodAsync(int x, string s) => Task.CompletedTask; + Task MethodAsync(int x, string s, CancellationToken ct) => Task.CompletedTask; +} + "; + string fixedCode = @" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + await MethodAsync(5, ""Hello world"", ct); + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c) => Task.CompletedTask; + Task MethodAsync(int x, string s) => Task.CompletedTask; + Task MethodAsync(int x, string s, CancellationToken ct) => Task.CompletedTask; +} + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task CS_Diagnostic_OverloadsArgumentsMatch_WithConfigureAwait() + { + string originalCode = @" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + await [|MethodAsync(5, ""Hello world"")|].ConfigureAwait(true); + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c) => Task.CompletedTask; + Task MethodAsync(int x, string s) => Task.CompletedTask; + Task MethodAsync(int x, string s, CancellationToken ct) => Task.CompletedTask; +} + "; + string fixedCode = @" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + await MethodAsync(5, ""Hello world"", ct).ConfigureAwait(true); + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c) => Task.CompletedTask; + Task MethodAsync(int x, string s) => Task.CompletedTask; + Task MethodAsync(int x, string s, CancellationToken ct) => Task.CompletedTask; +} + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task CS_Diagnostic_ActionDelegateAwait() + { + string originalCode = @" +using System; +using System.Threading; +using System.Threading.Tasks; +class C +{ + void M(CancellationToken ct) + { + Action a = async (CancellationToken token) => await [|MethodAsync()|]; + a(ct); + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c) => Task.CompletedTask; +} + "; + string fixedCode = @" +using System; +using System.Threading; +using System.Threading.Tasks; +class C +{ + void M(CancellationToken ct) + { + Action a = async (CancellationToken token) => await MethodAsync(token); + a(ct); + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c) => Task.CompletedTask; +} + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task CS_Diagnostic_ActionDelegateAwait_WithConfigureAwait() + { + string originalCode = @" +using System; +using System.Threading; +using System.Threading.Tasks; +class C +{ + void M(CancellationToken ct) + { + Action a = async (CancellationToken token) => await [|MethodAsync()|].ConfigureAwait(false); + a(ct); + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c) => Task.CompletedTask; +} + "; + string fixedCode = @" +using System; +using System.Threading; +using System.Threading.Tasks; +class C +{ + void M(CancellationToken ct) + { + Action a = async (CancellationToken token) => await MethodAsync(token).ConfigureAwait(false); + a(ct); + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c) => Task.CompletedTask; +} + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task CS_Diagnostic_FuncDelegateAwaitInside() + { + string originalCode = @" +using System; +using System.Threading; +using System.Threading.Tasks; +class C +{ + void M(CancellationToken ct) + { + Func> f = async (CancellationToken token) => + { + await [|MethodAsync()|]; + return true; + }; + f(ct); + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c) => Task.CompletedTask; +} + "; + string fixedCode = @" +using System; +using System.Threading; +using System.Threading.Tasks; +class C +{ + void M(CancellationToken ct) + { + Func> f = async (CancellationToken token) => + { + await MethodAsync(token); + return true; + }; + f(ct); + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c) => Task.CompletedTask; +} + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task CS_Diagnostic_FuncDelegateAwaitInside_WithConfigureAwait() + { + string originalCode = @" +using System; +using System.Threading; +using System.Threading.Tasks; +class C +{ + void M(CancellationToken ct) + { + Func> f = async (CancellationToken token) => + { + await [|MethodAsync()|].ConfigureAwait(true); + return true; + }; + f(ct); + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c) => Task.CompletedTask; +} + "; + string fixedCode = @" +using System; +using System.Threading; +using System.Threading.Tasks; +class C +{ + void M(CancellationToken ct) + { + Func> f = async (CancellationToken token) => + { + await MethodAsync(token).ConfigureAwait(true); + return true; + }; + f(ct); + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c) => Task.CompletedTask; +} + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task CS_Diagnostic_NestedFunctionAwait() + { + string originalCode = @" +using System; +using System.Threading; +using System.Threading.Tasks; +class C +{ + void M(CancellationToken ct) + { + async void LocalMethod(CancellationToken token) + { + await [|MethodAsync()|]; + } + LocalMethod(ct); + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c) => Task.CompletedTask; +} + "; + string fixedCode = @" +using System; +using System.Threading; +using System.Threading.Tasks; +class C +{ + void M(CancellationToken ct) + { + async void LocalMethod(CancellationToken token) + { + await MethodAsync(token); + } + LocalMethod(ct); + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c) => Task.CompletedTask; +} + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task CS_Diagnostic_NestedFunctionAwait_WithConfigureAwait() + { + string originalCode = @" +using System; +using System.Threading; +using System.Threading.Tasks; +class C +{ + void M(CancellationToken ct) + { + async void LocalMethod(CancellationToken token) + { + await [|MethodAsync()|].ConfigureAwait(false); + } + LocalMethod(ct); + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c) => Task.CompletedTask; +} + "; + string fixedCode = @" +using System; +using System.Threading; +using System.Threading.Tasks; +class C +{ + void M(CancellationToken ct) + { + async void LocalMethod(CancellationToken token) + { + await MethodAsync(token).ConfigureAwait(false); + } + LocalMethod(ct); + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c) => Task.CompletedTask; +} + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + + #endregion + + #region No Diagnostic - VB + + [Fact] + public Task VB_NoDiagnostic_NoParentToken_NoToken() + { + return VerifyVB.VerifyAnalyzerAsync(@" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M() + Await MethodAsync() + End Sub + Private Function MethodAsync() As Task + Return Task.CompletedTask + End Function +End Class + "); + } + + [Fact] + public Task VB_NoDiagnostic_NoParentToken_TokenDefault() + { + return VerifyVB.VerifyAnalyzerAsync(@" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M() + Await MethodAsync() + End Sub + Private Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task + Return Task.CompletedTask + End Function +End Class + "); + } + + [Fact] + public Task VB_NoDiagnostic_NoToken() + { + return VerifyVB.VerifyAnalyzerAsync(@" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await MethodAsync() + End Sub + Private Function MethodAsync() As Task + Return Task.CompletedTask + End Function +End Class + "); + } + + [Fact] + public Task VB_NoDiagnostic_NoAwait() + { + return VerifyVB.VerifyAnalyzerAsync(@" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Sub M(ByVal ct As CancellationToken) + MethodAsync() + End Sub + Private Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task + Return Task.CompletedTask + End Function +End Class + "); + } + + [Fact] + public Task VB_NoDiagnostic_SaveTask() + { + return VerifyVB.VerifyAnalyzerAsync(@" +Imports System.Threading +Imports System.Threading.Tasks + +Class C + Private Sub M(ByVal ct As CancellationToken) + Dim t As Task = MethodAsync() + End Sub + Private Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task + Return Task.CompletedTask + End Function +End Class + "); + } + + [Fact] + public Task VB_NoDiagnostic_OverloadArgumentsDontMatch() + { + return VerifyVB.VerifyAnalyzerAsync(@" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await MethodAsync(5, ""Hello, world"") + End Sub + Private Function MethodAsync(ByVal i As Integer, ByVal s As String) As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal i As Integer, ByVal ct As CancellationToken) As Task + Return Task.CompletedTask + End Function +End Class + "); + } + + [Fact] + public Task VB_NoDiagnostic_AlreadyPassingToken() + { + return VerifyVB.VerifyAnalyzerAsync(@" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await MethodAsync(ct) + End Sub + Private Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal c As CancellationToken) As Task + Return Task.CompletedTask + End Function +End Class + "); + } + + [Fact] + public Task VB_NoDiagnostic_PassingTokenFromSource() + { + return VerifyVB.VerifyAnalyzerAsync(@" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Dim cts As CancellationTokenSource = New CancellationTokenSource() + Await MethodAsync(cts.Token) + End Sub + Private Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal c As CancellationToken) As Task + Return Task.CompletedTask + End Function +End Class + "); + } + + // There is no default keyword in VB, must use Nothing instead. + // The following test method covers the two cases for: `default` and `default(CancellationToken)` + [Fact] + public Task VB_NoDiagnostic_PassingExplicitNothing() + { + return VerifyVB.VerifyAnalyzerAsync(@" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await MethodAsync(Nothing) + End Sub + Private Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal c As CancellationToken) As Task + Return Task.CompletedTask + End Function +End Class + "); + } + + [Fact] + public Task VB_NoDiagnostic_PassingExpliciCancellationTokenNone() + { + return VerifyVB.VerifyAnalyzerAsync(@" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await MethodAsync(CancellationToken.None) + End Sub + Private Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal c As CancellationToken) As Task + Return Task.CompletedTask + End Function +End Class + "); + } + + [Fact] + public Task VB_NoDiagnostic_ActionDelegateNoAwait() + { + return VerifyVB.VerifyAnalyzerAsync(@" +Imports System +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Sub M(ByVal ct As CancellationToken) + Dim a As Action(Of CancellationToken) = Sub(ByVal c As CancellationToken) MethodAsync() + a(ct) + End Sub + Private Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal c As CancellationToken) As Task + Return Task.CompletedTask + End Function +End Class + "); + } + + [Fact] + public Task VB_NoDiagnostic_FuncDelegateAwaitOutside() + { + return VerifyVB.VerifyAnalyzerAsync(@" +Imports System +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Dim f As Func(Of CancellationToken, Task) = Function(ByVal c As CancellationToken) MethodAsync() + Await f(ct) + End Sub + Private Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal c As CancellationToken) As Task + Return Task.CompletedTask + End Function +End Class + "); + } + + // Nested functions not available in VB: + // VB_NoDiagnostic_NestedFunctionNoAwait + // VB_NoDiagnostic_NestedFunctionAwaitOutside + + [Fact] + public Task VB_NoDiagnostic_OverloadTokenNotLastParameter() + { + return VerifyVB.VerifyAnalyzerAsync(@" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await MethodAsync() + End Sub + Private Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal x As Integer, ByVal ct As CancellationToken, ByVal s As String) As Task + Return Task.CompletedTask + End Function +End Class + "); + } + + [Fact] + public Task VB_NoDiagnostic_OverloadWithMultipleTokens() + { + return VerifyVB.VerifyAnalyzerAsync(@" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await MethodAsync() + End Sub + Private Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal c1 As CancellationToken, ByVal ct2 As CancellationToken) As Task + Return Task.CompletedTask + End Function +End Class + "); + } + + [Fact] + public Task VB_NoDiagnostic_OverloadWithMultipleTokensSeparated() + { + return VerifyVB.VerifyAnalyzerAsync(@" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await MethodAsync() + End Sub + Private Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal x As Integer, ByVal c1 As CancellationToken, ByVal s As String, ByVal ct2 As CancellationToken) As Task + Return Task.CompletedTask + End Function +End Class + "); + } + + #endregion + + #region Diagnostic = VB + + [Fact] + public Task VB_Diagnostic_Class_TokenDefault() + { + string originalCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await [|MethodAsync()|] + End Sub + Private Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task + Return Task.CompletedTask + End Function +End Class + "; + string fixedCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await MethodAsync(ct) + End Sub + Private Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task + Return Task.CompletedTask + End Function +End Class + "; + return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task VB_Diagnostic_Class_TokenDefault_WithConfigureAwait() + { + string originalCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await [|MethodAsync()|].ConfigureAwait(False) + End Sub + Private Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task + Return Task.CompletedTask + End Function +End Class + "; + string fixedCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await MethodAsync(ct).ConfigureAwait(False) + End Sub + Private Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task + Return Task.CompletedTask + End Function +End Class + "; + return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task VB_Diagnostic_ClassStaticMethod_TokenDefault() + { + string originalCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await [|MethodAsync()|] + End Sub + Private Shared Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task + Return Task.CompletedTask + End Function +End Class + "; + string fixedCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await MethodAsync(ct) + End Sub + Private Shared Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task + Return Task.CompletedTask + End Function +End Class + "; + return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task VB_Diagnostic_ClassStaticMethod_TokenDefault_WithConfigureAwait() + { + string originalCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await [|MethodAsync()|].ConfigureAwait(False) + End Sub + Private Shared Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task + Return Task.CompletedTask + End Function +End Class + "; + string fixedCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await MethodAsync(ct).ConfigureAwait(False) + End Sub + Private Shared Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task + Return Task.CompletedTask + End Function +End Class + "; + return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task VB_Diagnostic_OtherClass_TokenDefault() + { + string originalCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Dim o As O = New O() + Await [|o.MethodAsync()|] + End Sub +End Class +Class O + Public Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task + Return Task.CompletedTask + End Function +End Class + "; + string fixedCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Dim o As O = New O() + Await o.MethodAsync(ct) + End Sub +End Class +Class O + Public Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task + Return Task.CompletedTask + End Function +End Class + "; + return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task VB_Diagnostic_OtherClass_TokenDefault_WithConfigureAwait() + { + string originalCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Dim o As O = New O() + Await [|o.MethodAsync()|] + End Sub +End Class +Class O + Public Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Public Function MethodAsync(ByVal c As CancellationToken) As Task + Return Task.CompletedTask + End Function +End Class + "; + string fixedCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Dim o As O = New O() + Await o.MethodAsync(ct) + End Sub +End Class +Class O + Public Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Public Function MethodAsync(ByVal c As CancellationToken) As Task + Return Task.CompletedTask + End Function +End Class + "; + return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task VB_Diagnostic_OtherClassStaticMethod_TokenDefault() + { + string originalCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await [|O.MethodAsync()|] + End Sub +End Class +Class O + Public Shared Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task + Return Task.CompletedTask + End Function +End Class + "; + string fixedCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await O.MethodAsync(ct) + End Sub +End Class +Class O + Public Shared Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task + Return Task.CompletedTask + End Function +End Class + "; + return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task VB_Diagnostic_OtherClassStaticMethod_TokenDefault_WithConfigureAwait() + { + string originalCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Dim o As O = New O() + Await [|o.MethodAsync()|] + End Sub +End Class +Class O + Public Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Public Function MethodAsync(ByVal c As CancellationToken) As Task + Return Task.CompletedTask + End Function +End Class + "; + string fixedCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Dim o As O = New O() + Await o.MethodAsync(ct) + End Sub +End Class +Class O + Public Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Public Function MethodAsync(ByVal c As CancellationToken) As Task + Return Task.CompletedTask + End Function +End Class + "; + return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task VB_Diagnostic_Struct_TokenDefault() + { + string originalCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Structure S + Private Async Sub M(ByVal ct As CancellationToken) + Await [|MethodAsync()|] + End Sub + Private Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task + Return Task.CompletedTask + End Function +End Structure + "; + string fixedCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Structure S + Private Async Sub M(ByVal ct As CancellationToken) + Await MethodAsync(ct) + End Sub + Private Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task + Return Task.CompletedTask + End Function +End Structure + "; + return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task VB_Diagnostic_Struct_TokenDefault_WithConfigureAwait() + { + string originalCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Structure S + Private Async Sub M(ByVal ct As CancellationToken) + Await [|MethodAsync()|].ConfigureAwait(False) + End Sub + Private Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task + Return Task.CompletedTask + End Function +End Structure + "; + string fixedCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Structure S + Private Async Sub M(ByVal ct As CancellationToken) + Await MethodAsync(ct).ConfigureAwait(False) + End Sub + Private Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task + Return Task.CompletedTask + End Function +End Structure + "; + return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task VB_Diagnostic_OverloadToken() + { + string originalCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await [|MethodAsync()|] + End Sub + Private Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal c As CancellationToken) As Task + Return Task.CompletedTask + End Function +End Class + "; + string fixedCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await MethodAsync(ct) + End Sub + Private Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal c As CancellationToken) As Task + Return Task.CompletedTask + End Function +End Class + "; + return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task VB_Diagnostic_OverloadToken_WithConfigureAwait() + { + string originalCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await [|MethodAsync()|] + End Sub + Private Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal c As CancellationToken) As Task + Return Task.CompletedTask + End Function +End Class + "; + string fixedCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await MethodAsync(ct) + End Sub + Private Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal c As CancellationToken) As Task + Return Task.CompletedTask + End Function +End Class + "; + return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task VB_Diagnostic_OverloadTokenDefault() + { + string originalCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await [|MethodAsync()|] + End Sub + Private Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task + Return Task.CompletedTask + End Function +End Class + "; + string fixedCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await MethodAsync(ct) + End Sub + Private Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task + Return Task.CompletedTask + End Function +End Class + "; + return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task VB_Diagnostic_OverloadTokenDefault_WithConfigureAwait() + { + string originalCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await [|MethodAsync()|].ConfigureAwait(False) + End Sub + Private Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task + Return Task.CompletedTask + End Function +End Class + "; + string fixedCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await MethodAsync(ct).ConfigureAwait(False) + End Sub + Private Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task + Return Task.CompletedTask + End Function +End Class + "; + return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task VB_Diagnostic_OverloadsArgumentsMatch() + { + string originalCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await [|MethodAsync(5, ""Hello, world"")|] + End Sub + Private Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal c As CancellationToken) As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal x As Integer, ByVal s As String) As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal x As Integer, ByVal s As String, ByVal ct As CancellationToken) As Task + Return Task.CompletedTask + End Function +End Class + "; + string fixedCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await MethodAsync(5, ""Hello, world"", ct) + End Sub + Private Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal c As CancellationToken) As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal x As Integer, ByVal s As String) As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal x As Integer, ByVal s As String, ByVal ct As CancellationToken) As Task + Return Task.CompletedTask + End Function +End Class + "; + return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task VB_Diagnostic_OverloadsArgumentsMatch_WithConfigureAwait() + { + string originalCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await [|MethodAsync(5, ""Hello, world"")|].ConfigureAwait(True) + End Sub + Private Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal c As CancellationToken) As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal x As Integer, ByVal s As String) As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal x As Integer, ByVal s As String, ByVal ct As CancellationToken) As Task + Return Task.CompletedTask + End Function +End Class + "; + string fixedCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await MethodAsync(5, ""Hello, world"", ct).ConfigureAwait(True) + End Sub + Private Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal c As CancellationToken) As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal x As Integer, ByVal s As String) As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal x As Integer, ByVal s As String, ByVal ct As CancellationToken) As Task + Return Task.CompletedTask + End Function +End Class + "; + return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task VB_Diagnostic_ActionDelegateAwait() + { + string originalCode = @" +Imports System +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Sub M(ByVal ct As CancellationToken) + Dim a As Action(Of CancellationToken) = Async Sub(ByVal token As CancellationToken) Await [|MethodAsync()|] + a(ct) + End Sub + Private Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal c As CancellationToken) As Task + Return Task.CompletedTask + End Function +End Class + "; + string fixedCode = @" +Imports System +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Sub M(ByVal ct As CancellationToken) + Dim a As Action(Of CancellationToken) = Async Sub(ByVal token As CancellationToken) Await MethodAsync(token) + a(ct) + End Sub + Private Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal c As CancellationToken) As Task + Return Task.CompletedTask + End Function +End Class + "; + return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task VB_Diagnostic_ActionDelegateAwait_WithConfigureAwait() + { + string originalCode = @" +Imports System +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Sub M(ByVal ct As CancellationToken) + Dim a As Action(Of CancellationToken) = Async Sub(ByVal token As CancellationToken) Await [|MethodAsync()|].ConfigureAwait(False) + a(ct) + End Sub + Private Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal c As CancellationToken) As Task + Return Task.CompletedTask + End Function +End Class + "; + string fixedCode = @" +Imports System +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Sub M(ByVal ct As CancellationToken) + Dim a As Action(Of CancellationToken) = Async Sub(ByVal token As CancellationToken) Await MethodAsync(token).ConfigureAwait(False) + a(ct) + End Sub + Private Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal c As CancellationToken) As Task + Return Task.CompletedTask + End Function +End Class + "; + return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task VB_Diagnostic_FuncDelegateAwaitInside() + { + string originalCode = @" +Imports System +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Sub M(ByVal ct As CancellationToken) + Dim f As Func(Of CancellationToken, Task(Of Boolean)) = Async Function(ByVal token As CancellationToken) + Await [|MethodAsync()|] + Return True + End Function + f(ct) + End Sub + Private Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal c As CancellationToken) As Task + Return Task.CompletedTask + End Function +End Class + "; + string fixedCode = @" +Imports System +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Sub M(ByVal ct As CancellationToken) + Dim f As Func(Of CancellationToken, Task(Of Boolean)) = Async Function(ByVal token As CancellationToken) + Await MethodAsync(token) + Return True + End Function + f(ct) + End Sub + Private Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal c As CancellationToken) As Task + Return Task.CompletedTask + End Function +End Class + "; + return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task VB_Diagnostic_FuncDelegateAwaitInside_WithConfigureAwait() + { + string originalCode = @" +Imports System +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Sub M(ByVal ct As CancellationToken) + Dim f As Func(Of CancellationToken, Task(Of Boolean)) = Async Function(ByVal token As CancellationToken) + Await [|MethodAsync()|].ConfigureAwait(True) + Return True + End Function + f(ct) + End Sub + Private Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal c As CancellationToken) As Task + Return Task.CompletedTask + End Function +End Class + "; + string fixedCode = @" +Imports System +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Sub M(ByVal ct As CancellationToken) + Dim f As Func(Of CancellationToken, Task(Of Boolean)) = Async Function(ByVal token As CancellationToken) + Await MethodAsync(token).ConfigureAwait(True) + Return True + End Function + f(ct) + End Sub + Private Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal c As CancellationToken) As Task + Return Task.CompletedTask + End Function +End Class + "; + return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); + } + + // Nested functions not available in VB: + // VB_Diagnostic_NestedFunctionAwait + // VB_Diagnostic_NestedFunctionAwait_WithConfigureAwait + + #endregion + } +} \ No newline at end of file diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToAsyncMethods.Fixer.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToAsyncMethods.Fixer.vb new file mode 100644 index 0000000000..d41408070a --- /dev/null +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToAsyncMethods.Fixer.vb @@ -0,0 +1,62 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports System.Collections.Immutable +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.CodeFixes +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax +Imports Microsoft.NetCore.Analyzers.Runtime + +Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime + + + Public NotInheritable Class BasicForwardCancellationTokenToAsyncMethodsFixer + + Inherits ForwardCancellationTokenToAsyncMethodsFixer + + Private Function IsCancellationTokenParameter(parameter As ParameterSyntax) As Boolean + Dim type As SimpleNameSyntax = TryCast(parameter.AsClause.Type, SimpleNameSyntax) + Return type IsNot Nothing AndAlso type.Identifier.ValueText.Equals(CancellationTokenName, StringComparison.Ordinal) + End Function + + Private Function GetCancellationTokenName(parameterList As SeparatedSyntaxList(Of ParameterSyntax)) As String + Return parameterList.FirstOrDefault(Function(p) IsCancellationTokenParameter(p))?.Identifier.Identifier.ValueText + End Function + + Protected Overrides Function TryGetAncestorDeclarationCancellationTokenParameterName(node As SyntaxNode, ByRef parameterName As String) As Boolean + + parameterName = Nothing + + Dim currentNode As SyntaxNode = node.Parent + While currentNode IsNot Nothing + + Dim singleLineLambdaNode As SingleLineLambdaExpressionSyntax = TryCast(currentNode, SingleLineLambdaExpressionSyntax) + Dim multiLineLambdaNode As MultiLineLambdaExpressionSyntax = TryCast(currentNode, MultiLineLambdaExpressionSyntax) + Dim methodNode As MethodStatementSyntax = TryCast(currentNode, MethodStatementSyntax) + Dim methodBlockNode As MethodBlockSyntax = TryCast(currentNode, MethodBlockSyntax) + + If singleLineLambdaNode IsNot Nothing Then + parameterName = GetCancellationTokenName(singleLineLambdaNode.SubOrFunctionHeader.ParameterList.Parameters) + Exit While + ElseIf multiLineLambdaNode IsNot Nothing Then + parameterName = GetCancellationTokenName(multiLineLambdaNode.SubOrFunctionHeader.ParameterList.Parameters) + Exit While + ElseIf methodNode IsNot Nothing Then + parameterName = GetCancellationTokenName(methodNode.ParameterList.Parameters) + Exit While + ElseIf methodBlockNode IsNot Nothing Then + parameterName = GetCancellationTokenName(methodBlockNode.SubOrFunctionStatement.ParameterList.Parameters) + Exit While + End If + + currentNode = currentNode.Parent + + End While + + Return Not String.IsNullOrEmpty(parameterName) + End Function + End Class +End Namespace + + + + diff --git a/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt b/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt index 49d9e0082d..1825484ac2 100644 --- a/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt +++ b/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt @@ -17,7 +17,7 @@ Security: CA2100-CA2153, CA2300-CA2330, CA3000-CA3147, CA5300-CA5403 Usage: CA1801, CA1806, CA1816, CA2200-CA2209, CA2211-CA2249 Naming: CA1700-CA1726 Maintainability: CA1500-CA1509 -Reliability: CA9999, CA2000-CA2015 +Reliability: CA9999, CA2000-CA2016 Documentation: CA1200-CA1200 # Microsoft CodeAnalysis API rules From 4cc0fba74f50ed92f7eabada58a7757963c5ce99 Mon Sep 17 00:00:00 2001 From: carlossanlop Date: Wed, 20 May 2020 13:56:17 -0700 Subject: [PATCH 02/20] Address PR suggestions: - Do not limit to only await. Detect all invocations. - Fix the resource messages. - Fix some warnings. --- .../MicrosoftNetCoreAnalyzersResources.resx | 6 +- ...rdCancellationTokenToAsyncMethods.Fixer.cs | 2 +- .../ForwardCancellationTokenToAsyncMethods.cs | 123 ++-- .../MicrosoftNetCoreAnalyzersResources.cs.xlf | 12 +- .../MicrosoftNetCoreAnalyzersResources.de.xlf | 12 +- .../MicrosoftNetCoreAnalyzersResources.es.xlf | 12 +- .../MicrosoftNetCoreAnalyzersResources.fr.xlf | 12 +- .../MicrosoftNetCoreAnalyzersResources.it.xlf | 12 +- .../MicrosoftNetCoreAnalyzersResources.ja.xlf | 12 +- .../MicrosoftNetCoreAnalyzersResources.ko.xlf | 12 +- .../MicrosoftNetCoreAnalyzersResources.pl.xlf | 12 +- ...crosoftNetCoreAnalyzersResources.pt-BR.xlf | 12 +- .../MicrosoftNetCoreAnalyzersResources.ru.xlf | 12 +- .../MicrosoftNetCoreAnalyzersResources.tr.xlf | 12 +- ...osoftNetCoreAnalyzersResources.zh-Hans.xlf | 12 +- ...osoftNetCoreAnalyzersResources.zh-Hant.xlf | 12 +- ...ardCancellationTokenToAsyncMethodsTests.cs | 582 ++++++++++++------ ...rdCancellationTokenToAsyncMethods.Fixer.vb | 7 +- 18 files changed, 521 insertions(+), 355 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx index af4ac78a47..c09ab7925e 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx @@ -1288,13 +1288,13 @@ Change the '{0}' method call to use the '{1}' overload - Description - Forward cancellation token to async methods + Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. - Message - Forward cancellation token to async methods + Consider forwarding the '{0}' parameter to the '{1}' method. - Title - Forward cancellation token to async methods + Consider forwarding the 'CancellationToken' parameter to methods that take one. Change to call the two argument constructor, pass null for the message. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Fixer.cs index 4795c1c4f3..d363f457d9 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Fixer.cs @@ -54,7 +54,7 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) string title = MicrosoftNetCoreAnalyzersResources.ForwardCancellationTokenToAsyncMethodsTitle; - Task createChangedDocument(CancellationToken _) => FixInvocation(doc, root, invocation, parameterName); + Task createChangedDocument(CancellationToken _) => FixInvocation(doc, root, invocation, parameterName!); context.RegisterCodeFix( new MyCodeAction( diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.cs index bbc246df2f..78387937bb 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.cs @@ -15,22 +15,18 @@ namespace Microsoft.NetCore.Analyzers.Runtime /// CA2016: Forward CancellationToken to async methods. /// /// Conditions for positive cases: - /// - The node to analyze is an awaited invocation, or is the origin invocation of a ConfigureAwait. - /// - The parent method signature has a ct parameter. - /// - The invocation is not receiving a ct argument. + /// - The containing method signature receives a ct parameter. It can be a method, a nested method, an action or a func. + /// - The invocation is not receiving a ct argument, and... /// - The invocation method either: - /// - Only has one method version, but the signature has one ct, set to default. + /// - Has no overloads but its current signature receives an optional ct=default, being used right now, or... /// - Has a method overload with the exact same arguments in the same order, plus one ct parameter at the end. - /// - An Action/Func instance that receives a ct, and there's an awaited invocation inside. /// /// Conditions for negative cases: - /// - The parent method signature does not have a ct parameter. - /// - The node to analyze is not an awaited invocation. - /// - The awaited invocation is already receiving a ct argument. - /// - The invocation method does not have an overload with the exact same arguments that also receives a ct. - /// - Includes the case where the user is *not* passing the parent method ct argument, but rather creating a new ct inside the method and passing that as argument, like with CancellationTokenSource. - /// - Includes the case where the user is explicitly passing `default`, `default(CancellationToken)` or `CancellationToken.None` to the invocation. - /// - If the overload that receives a ct receives more than one ct, do not suggest it. + /// - The containing method signature does not receive a ct parameter. + /// - The invocation is explicitly receiving a ct argument. + /// - The invocation method signature receives a ct but one is already being explicitly passed, or... + /// - The invocation method does not have an overload with the exact same arguments that also receives a ct, or... + /// - The invocation method has overloads that receive more than one ct. /// /// Future improvements: /// - Finding an overload with one ct parameter, but not in last position. @@ -86,80 +82,34 @@ private void AnalyzeCompilationStart(CompilationStartAnalysisContext context) return; } - // Retrieve the ConfigureAwait methods that could also be detected, which can come from: - // - A Task - // - A generic Task - // - A ValueTask - // - A generic ValueTask - if (!context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingTasksTask, out INamedTypeSymbol? taskType) || - !context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingTasksValueTask, out INamedTypeSymbol? valueTaskType) || - !context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingTasksGenericTask, out INamedTypeSymbol? genericTaskType) || - !context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingTasksGenericValueTask, out INamedTypeSymbol? genericValueTaskType)) - { - return; - } - - const string configureAwaitName = "ConfigureAwait"; - if (!(taskType.GetMembers(configureAwaitName).OfType().FirstOrDefault() is IMethodSymbol taskConfigureAwaitMethod) || - !(genericTaskType.GetMembers(configureAwaitName).OfType().FirstOrDefault() is IMethodSymbol genericTaskConfigureAwaitMethod) || - !(valueTaskType.GetMembers(configureAwaitName).OfType().FirstOrDefault() is IMethodSymbol valueTaskConfigureAwaitMethod) || - !(genericValueTaskType.GetMembers(configureAwaitName).OfType().FirstOrDefault() is IMethodSymbol genericValueTaskConfigureAwaitMethod)) - { - return; - } - context.RegisterOperationAction(context => { - if (ShouldAnalyze( - (IAwaitOperation)context.Operation, + if (!ShouldAnalyze( + (IInvocationOperation)context.Operation, cancellationTokenType, - taskConfigureAwaitMethod, - genericTaskConfigureAwaitMethod, - valueTaskConfigureAwaitMethod, - genericValueTaskConfigureAwaitMethod, - out IInvocationOperation? invocation)) + out string? cancellationTokenParameterName, + out string? methodName)) { - context.ReportDiagnostic(invocation!.CreateDiagnostic(ForwardCancellationTokenToAsyncMethodsRule)); + return; } + + context.ReportDiagnostic(context.Operation.CreateDiagnostic(ForwardCancellationTokenToAsyncMethodsRule, cancellationTokenParameterName!, methodName!)); }, - OperationKind.Await); + OperationKind.Invocation); } - private static bool ShouldAnalyze(IAwaitOperation awaitOperation, INamedTypeSymbol cancellationTokenType, - IMethodSymbol taskConfigureAwaitMethod, - IMethodSymbol genericTaskConfigureAwaitMethod, - IMethodSymbol valueTaskConfigureAwaitMethod, - IMethodSymbol genericValueTaskConfigureAwaitMethod, - out IInvocationOperation? invocation) + private static bool ShouldAnalyze( + IInvocationOperation invocation, + INamedTypeSymbol cancellationTokenType, + out string? cancellationTokenParameterName, + out string? methodName) { - invocation = null; + IMethodSymbol method = invocation.TargetMethod; - if (!(awaitOperation.Operation is IInvocationOperation awaitedInvocation)) - { - return false; - } - invocation = awaitedInvocation; - IMethodSymbol method = awaitedInvocation.TargetMethod; + cancellationTokenParameterName = null; + methodName = method.Name; - // Check if the child operation of the await is ConfigureAwait - // in which case we should analyze the grandchild operation - if (method.OriginalDefinition.Equals(taskConfigureAwaitMethod) || - method.OriginalDefinition.Equals(genericTaskConfigureAwaitMethod) || - method.OriginalDefinition.Equals(valueTaskConfigureAwaitMethod) || - method.OriginalDefinition.Equals(genericValueTaskConfigureAwaitMethod)) - { - if (awaitedInvocation.Instance is IInvocationOperation instanceOperation) - { - invocation = instanceOperation; - method = instanceOperation.TargetMethod; - } - else - { - return false; - } - } - - // If the invocation has an optional implicit ct or an overload that takes a ct, continue + // Check if the invocation has an optional implicit ct or an overload that takes one ct if (!InvocationIgnoresOptionalCancellationToken(cancellationTokenType, invocation, method) && !MethodHasCancellationTokenOverload(cancellationTokenType, method)) { @@ -173,12 +123,12 @@ private static bool ShouldAnalyze(IAwaitOperation awaitOperation, INamedTypeSymb } // Check if the ancestor method has a ct that we can pass to the invocation - if (!VerifyMethodOnlyHasOneCancellationTokenAsLastArgument(cancellationTokenType, methodDeclaration!)) + if (!VerifyMethodOnlyHasOneCancellationTokenAsLastArgument(cancellationTokenType, methodDeclaration!, out cancellationTokenParameterName)) { return false; } - return true; + return cancellationTokenParameterName != null && methodName != null; } // Looks for an ancestor that could be a method or function declaration. @@ -226,15 +176,26 @@ private static bool TryGetAncestorDeclaration(IInvocationOperation invocation, o // Check if the method only takes one ct and is the last parameter in the method signature. // We want to compare the current method signature to any others with the exact same arguments in the exact same order. - private static bool VerifyMethodOnlyHasOneCancellationTokenAsLastArgument(ITypeSymbol cancellationTokenType, IMethodSymbol methodDeclaration) + private static bool VerifyMethodOnlyHasOneCancellationTokenAsLastArgument( + INamedTypeSymbol cancellationTokenType, + IMethodSymbol methodDeclaration, + out string? cancellationTokenParameterName) { - return methodDeclaration.Parameters.Count(x => x.Type.Equals(cancellationTokenType)) == 1 && - methodDeclaration.Parameters.Last().Type.Equals(cancellationTokenType); + cancellationTokenParameterName = null; + + if (methodDeclaration.Parameters.Count(x => x.Type.Equals(cancellationTokenType)) == 1 && + methodDeclaration.Parameters.Last() is IParameterSymbol lastParameter && + lastParameter.Type.Equals(cancellationTokenType)) + { + cancellationTokenParameterName = lastParameter.Name; + } + + return cancellationTokenParameterName != null; } // Check if the currently used overload is the one that takes the ct, but is utilizing the default value offered in the method signature. // We want to offer a diagnostic for this case, so the user explicitly passes the ancestor's ct. - private static bool InvocationIgnoresOptionalCancellationToken(ITypeSymbol cancellationTokenType, IInvocationOperation invocation, IMethodSymbol method) + private static bool InvocationIgnoresOptionalCancellationToken(INamedTypeSymbol cancellationTokenType, IInvocationOperation invocation, IMethodSymbol method) { if (method.Parameters.Any() && method.Parameters.Last() is IParameterSymbol lastParameter && lastParameter.Type.Equals(cancellationTokenType)) { diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf index 8adeee3692..26ddd722d1 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf @@ -878,18 +878,18 @@ - Description - Forward cancellation token to async methods - Description - Forward cancellation token to async methods + Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. + Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. - Message - Forward cancellation token to async methods - Message - Forward cancellation token to async methods + Consider forwarding the '{0}' parameter to the '{1}' method. + Consider forwarding the '{0}' parameter to the '{1}' method. - Title - Forward cancellation token to async methods - Title - Forward cancellation token to async methods + Consider forwarding the 'CancellationToken' parameter to methods that take one. + Consider forwarding the 'CancellationToken' parameter to methods that take one. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf index 7285433e06..5fb6b7529d 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf @@ -878,18 +878,18 @@ - Description - Forward cancellation token to async methods - Description - Forward cancellation token to async methods + Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. + Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. - Message - Forward cancellation token to async methods - Message - Forward cancellation token to async methods + Consider forwarding the '{0}' parameter to the '{1}' method. + Consider forwarding the '{0}' parameter to the '{1}' method. - Title - Forward cancellation token to async methods - Title - Forward cancellation token to async methods + Consider forwarding the 'CancellationToken' parameter to methods that take one. + Consider forwarding the 'CancellationToken' parameter to methods that take one. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf index 2f9e7010dd..88f954fa0a 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf @@ -878,18 +878,18 @@ - Description - Forward cancellation token to async methods - Description - Forward cancellation token to async methods + Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. + Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. - Message - Forward cancellation token to async methods - Message - Forward cancellation token to async methods + Consider forwarding the '{0}' parameter to the '{1}' method. + Consider forwarding the '{0}' parameter to the '{1}' method. - Title - Forward cancellation token to async methods - Title - Forward cancellation token to async methods + Consider forwarding the 'CancellationToken' parameter to methods that take one. + Consider forwarding the 'CancellationToken' parameter to methods that take one. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf index 0ac54c485d..f28e378cc3 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf @@ -878,18 +878,18 @@ - Description - Forward cancellation token to async methods - Description - Forward cancellation token to async methods + Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. + Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. - Message - Forward cancellation token to async methods - Message - Forward cancellation token to async methods + Consider forwarding the '{0}' parameter to the '{1}' method. + Consider forwarding the '{0}' parameter to the '{1}' method. - Title - Forward cancellation token to async methods - Title - Forward cancellation token to async methods + Consider forwarding the 'CancellationToken' parameter to methods that take one. + Consider forwarding the 'CancellationToken' parameter to methods that take one. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf index 94c0bc4260..1849ed2bc4 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf @@ -878,18 +878,18 @@ - Description - Forward cancellation token to async methods - Description - Forward cancellation token to async methods + Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. + Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. - Message - Forward cancellation token to async methods - Message - Forward cancellation token to async methods + Consider forwarding the '{0}' parameter to the '{1}' method. + Consider forwarding the '{0}' parameter to the '{1}' method. - Title - Forward cancellation token to async methods - Title - Forward cancellation token to async methods + Consider forwarding the 'CancellationToken' parameter to methods that take one. + Consider forwarding the 'CancellationToken' parameter to methods that take one. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf index df5013eae2..549acce376 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf @@ -878,18 +878,18 @@ - Description - Forward cancellation token to async methods - Description - Forward cancellation token to async methods + Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. + Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. - Message - Forward cancellation token to async methods - Message - Forward cancellation token to async methods + Consider forwarding the '{0}' parameter to the '{1}' method. + Consider forwarding the '{0}' parameter to the '{1}' method. - Title - Forward cancellation token to async methods - Title - Forward cancellation token to async methods + Consider forwarding the 'CancellationToken' parameter to methods that take one. + Consider forwarding the 'CancellationToken' parameter to methods that take one. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf index cb2cf7cdf5..cc2970f39a 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf @@ -878,18 +878,18 @@ - Description - Forward cancellation token to async methods - Description - Forward cancellation token to async methods + Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. + Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. - Message - Forward cancellation token to async methods - Message - Forward cancellation token to async methods + Consider forwarding the '{0}' parameter to the '{1}' method. + Consider forwarding the '{0}' parameter to the '{1}' method. - Title - Forward cancellation token to async methods - Title - Forward cancellation token to async methods + Consider forwarding the 'CancellationToken' parameter to methods that take one. + Consider forwarding the 'CancellationToken' parameter to methods that take one. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf index 21ab00fede..81f3235973 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf @@ -879,18 +879,18 @@ - Description - Forward cancellation token to async methods - Description - Forward cancellation token to async methods + Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. + Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. - Message - Forward cancellation token to async methods - Message - Forward cancellation token to async methods + Consider forwarding the '{0}' parameter to the '{1}' method. + Consider forwarding the '{0}' parameter to the '{1}' method. - Title - Forward cancellation token to async methods - Title - Forward cancellation token to async methods + Consider forwarding the 'CancellationToken' parameter to methods that take one. + Consider forwarding the 'CancellationToken' parameter to methods that take one. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf index 61819bf6da..9525a7287b 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf @@ -878,18 +878,18 @@ - Description - Forward cancellation token to async methods - Description - Forward cancellation token to async methods + Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. + Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. - Message - Forward cancellation token to async methods - Message - Forward cancellation token to async methods + Consider forwarding the '{0}' parameter to the '{1}' method. + Consider forwarding the '{0}' parameter to the '{1}' method. - Title - Forward cancellation token to async methods - Title - Forward cancellation token to async methods + Consider forwarding the 'CancellationToken' parameter to methods that take one. + Consider forwarding the 'CancellationToken' parameter to methods that take one. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf index d612c80fc0..fd71873775 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf @@ -878,18 +878,18 @@ - Description - Forward cancellation token to async methods - Description - Forward cancellation token to async methods + Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. + Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. - Message - Forward cancellation token to async methods - Message - Forward cancellation token to async methods + Consider forwarding the '{0}' parameter to the '{1}' method. + Consider forwarding the '{0}' parameter to the '{1}' method. - Title - Forward cancellation token to async methods - Title - Forward cancellation token to async methods + Consider forwarding the 'CancellationToken' parameter to methods that take one. + Consider forwarding the 'CancellationToken' parameter to methods that take one. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf index a6ccab7c54..c4c6b444aa 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf @@ -878,18 +878,18 @@ - Description - Forward cancellation token to async methods - Description - Forward cancellation token to async methods + Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. + Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. - Message - Forward cancellation token to async methods - Message - Forward cancellation token to async methods + Consider forwarding the '{0}' parameter to the '{1}' method. + Consider forwarding the '{0}' parameter to the '{1}' method. - Title - Forward cancellation token to async methods - Title - Forward cancellation token to async methods + Consider forwarding the 'CancellationToken' parameter to methods that take one. + Consider forwarding the 'CancellationToken' parameter to methods that take one. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf index 155800313a..28eb20f8ee 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf @@ -878,18 +878,18 @@ - Description - Forward cancellation token to async methods - Description - Forward cancellation token to async methods + Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. + Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. - Message - Forward cancellation token to async methods - Message - Forward cancellation token to async methods + Consider forwarding the '{0}' parameter to the '{1}' method. + Consider forwarding the '{0}' parameter to the '{1}' method. - Title - Forward cancellation token to async methods - Title - Forward cancellation token to async methods + Consider forwarding the 'CancellationToken' parameter to methods that take one. + Consider forwarding the 'CancellationToken' parameter to methods that take one. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf index bed3baa5ae..f713b24c29 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf @@ -878,18 +878,18 @@ - Description - Forward cancellation token to async methods - Description - Forward cancellation token to async methods + Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. + Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. - Message - Forward cancellation token to async methods - Message - Forward cancellation token to async methods + Consider forwarding the '{0}' parameter to the '{1}' method. + Consider forwarding the '{0}' parameter to the '{1}' method. - Title - Forward cancellation token to async methods - Title - Forward cancellation token to async methods + Consider forwarding the 'CancellationToken' parameter to methods that take one. + Consider forwarding the 'CancellationToken' parameter to methods that take one. diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethodsTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethodsTests.cs index fdce63f683..b357b5a222 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethodsTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethodsTests.cs @@ -66,40 +66,6 @@ async void M(CancellationToken ct) "); } - [Fact] - public Task CS_NoDiagnostic_NoAwait() - { - return VerifyCS.VerifyAnalyzerAsync(@" -using System.Threading; -using System.Threading.Tasks; -class C -{ - void M(CancellationToken ct) - { - MethodAsync(); - } - Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; -} - "); - } - - [Fact] - public Task CS_NoDiagnostic_SaveTask() - { - return VerifyCS.VerifyAnalyzerAsync(@" -using System.Threading; -using System.Threading.Tasks; -class C -{ - void M(CancellationToken ct) - { - Task t = MethodAsync(); - } - Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; -} - "); - } - [Fact] public Task CS_NoDiagnostic_OverloadArgumentsDontMatch() { @@ -192,7 +158,7 @@ async void M(CancellationToken ct) } [Fact] - public Task CS_NoDiagnostic_PassingExpliciCancellationTokenNone() + public Task CS_NoDiagnostic_PassingExplicitCancellationTokenNone() { return VerifyCS.VerifyAnalyzerAsync(@" using System.Threading; @@ -210,160 +176,134 @@ async void M(CancellationToken ct) } [Fact] - public Task CS_NoDiagnostic_ActionDelegateNoAwait() + public Task CS_NoDiagnostic_OverloadTokenNotLastParameter() { return VerifyCS.VerifyAnalyzerAsync(@" -using System; using System.Threading; using System.Threading.Tasks; class C { - void M(CancellationToken ct) + async void M(CancellationToken ct) { - Action a = (CancellationToken c) => MethodAsync(); - a(ct); + await MethodAsync(); } Task MethodAsync() => Task.CompletedTask; - Task MethodAsync(CancellationToken c) => Task.CompletedTask; + Task MethodAsync(int x, CancellationToken ct, string s) => Task.CompletedTask; } "); } [Fact] - public Task CS_NoDiagnostic_FuncDelegateAwaitOutside() + public Task CS_NoDiagnostic_OverloadWithMultipleTokens() { return VerifyCS.VerifyAnalyzerAsync(@" -using System; using System.Threading; using System.Threading.Tasks; class C { async void M(CancellationToken ct) { - Func f = (CancellationToken c) => MethodAsync(); - await f(ct); + await MethodAsync(); } Task MethodAsync() => Task.CompletedTask; - Task MethodAsync(CancellationToken c) => Task.CompletedTask; + Task MethodAsync(CancellationToken c1, CancellationToken ct2) => Task.CompletedTask; } "); } [Fact] - public Task CS_NoDiagnostic_NestedFunctionNoAwait() + public Task CS_NoDiagnostic_OverloadWithMultipleTokensSeparated() { return VerifyCS.VerifyAnalyzerAsync(@" -using System; using System.Threading; using System.Threading.Tasks; class C { - void M(CancellationToken ct) + async void M(CancellationToken ct) { - void LocalMethod(CancellationToken token) - { - MethodAsync(); - } - LocalMethod(ct); + await MethodAsync(); } Task MethodAsync() => Task.CompletedTask; - Task MethodAsync(CancellationToken c) => Task.CompletedTask; + Task MethodAsync(int x, CancellationToken c1, string s, CancellationToken ct2) => Task.CompletedTask; } "); } + #endregion + + #region Diagnostic = C# + [Fact] - public Task CS_NoDiagnostic_NestedFunctionAwaitOutside() + public Task CS_Diagnostic_Class_TokenDefault() { - return VerifyCS.VerifyAnalyzerAsync(@" -using System; + string originalCode = @" using System.Threading; using System.Threading.Tasks; class C { async void M(CancellationToken ct) { - Task LocalMethod(CancellationToken token) - { - return MethodAsync(); - } - await LocalMethod(ct); + await [|MethodAsync()|]; } - Task MethodAsync() => Task.CompletedTask; - Task MethodAsync(CancellationToken c) => Task.CompletedTask; + Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; } - "); - } - - [Fact] - public Task CS_NoDiagnostic_OverloadTokenNotLastParameter() - { - return VerifyCS.VerifyAnalyzerAsync(@" + "; + string fixedCode = @" using System.Threading; using System.Threading.Tasks; class C { async void M(CancellationToken ct) { - await MethodAsync(); + await MethodAsync(ct); } - Task MethodAsync() => Task.CompletedTask; - Task MethodAsync(int x, CancellationToken ct, string s) => Task.CompletedTask; + Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; } - "); + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); } [Fact] - public Task CS_NoDiagnostic_OverloadWithMultipleTokens() + public Task CS_Diagnostic_Class_TokenDefault_WithConfigureAwait() { - return VerifyCS.VerifyAnalyzerAsync(@" + string originalCode = @" using System.Threading; using System.Threading.Tasks; class C { async void M(CancellationToken ct) { - await MethodAsync(); + await [|MethodAsync()|].ConfigureAwait(false); } - Task MethodAsync() => Task.CompletedTask; - Task MethodAsync(CancellationToken c1, CancellationToken ct2) => Task.CompletedTask; + Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; } - "); - } - - [Fact] - public Task CS_NoDiagnostic_OverloadWithMultipleTokensSeparated() - { - return VerifyCS.VerifyAnalyzerAsync(@" + "; + string fixedCode = @" using System.Threading; using System.Threading.Tasks; class C { async void M(CancellationToken ct) { - await MethodAsync(); + await MethodAsync(ct).ConfigureAwait(false); } - Task MethodAsync() => Task.CompletedTask; - Task MethodAsync(int x, CancellationToken c1, string s, CancellationToken ct2) => Task.CompletedTask; + Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; } - "); + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); } - #endregion - - #region Diagnostic = C# - [Fact] - public Task CS_Diagnostic_Class_TokenDefault() + public Task CS_Diagnostic_NoAwait() { string originalCode = @" using System.Threading; using System.Threading.Tasks; class C { - async void M(CancellationToken ct) + void M(CancellationToken ct) { - await [|MethodAsync()|]; + [|MethodAsync()|]; } Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; } @@ -373,9 +313,9 @@ async void M(CancellationToken ct) using System.Threading.Tasks; class C { - async void M(CancellationToken ct) + void M(CancellationToken ct) { - await MethodAsync(ct); + MethodAsync(ct); } Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; } @@ -384,16 +324,16 @@ async void M(CancellationToken ct) } [Fact] - public Task CS_Diagnostic_Class_TokenDefault_WithConfigureAwait() + public Task CS_Diagnostic_SaveTask() { string originalCode = @" using System.Threading; using System.Threading.Tasks; class C { - async void M(CancellationToken ct) + void M(CancellationToken ct) { - await [|MethodAsync()|].ConfigureAwait(false); + Task t = [|MethodAsync()|]; } Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; } @@ -403,9 +343,9 @@ async void M(CancellationToken ct) using System.Threading.Tasks; class C { - async void M(CancellationToken ct) + void M(CancellationToken ct) { - await MethodAsync(ct).ConfigureAwait(false); + Task t = MethodAsync(ct); } Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; } @@ -921,6 +861,42 @@ void M(CancellationToken ct) return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); } + [Fact] + public Task CS_NoDiagnostic_ActionDelegateNoAwait() + { + string originalCode = @" +using System; +using System.Threading; +using System.Threading.Tasks; +class C +{ + void M(CancellationToken ct) + { + Action a = (CancellationToken c) => [|MethodAsync()|]; + a(ct); + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c) => Task.CompletedTask; +} + "; + string fixedCode = @" +using System; +using System.Threading; +using System.Threading.Tasks; +class C +{ + void M(CancellationToken ct) + { + Action a = (CancellationToken c) => MethodAsync(c); + a(ct); + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c) => Task.CompletedTask; +} + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + [Fact] public Task CS_Diagnostic_ActionDelegateAwait_WithConfigureAwait() { @@ -958,7 +934,7 @@ void M(CancellationToken ct) } [Fact] - public Task CS_Diagnostic_FuncDelegateAwaitInside() + public Task CS_Diagnostic_FuncDelegateAwait() { string originalCode = @" using System; @@ -1002,7 +978,7 @@ void M(CancellationToken ct) } [Fact] - public Task CS_Diagnostic_FuncDelegateAwaitInside_WithConfigureAwait() + public Task CS_Diagnostic_FuncDelegateAwait_WithConfigureAwait() { string originalCode = @" using System; @@ -1045,6 +1021,42 @@ void M(CancellationToken ct) return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); } + [Fact] + public Task CS_Diagnostic_FuncDelegateAwaitOutside() + { + string originalCode = @" +using System; +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + Func f = (CancellationToken c) => [|MethodAsync()|]; + await f(ct); + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c) => Task.CompletedTask; +} + "; + string fixedCode = @" +using System; +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + Func f = (CancellationToken c) => MethodAsync(c); + await f(ct); + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c) => Task.CompletedTask; +} + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + [Fact] public Task CS_Diagnostic_NestedFunctionAwait() { @@ -1087,6 +1099,90 @@ async void LocalMethod(CancellationToken token) return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); } + [Fact] + public Task CS_Diagnostic_NestedFunctionNoAwait() + { + string originalCode = @" +using System; +using System.Threading; +using System.Threading.Tasks; +class C +{ + void M(CancellationToken ct) + { + void LocalMethod(CancellationToken token) + { + [|MethodAsync()|]; + } + LocalMethod(ct); + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c) => Task.CompletedTask; +} + "; + string fixedCode = @" +using System; +using System.Threading; +using System.Threading.Tasks; +class C +{ + void M(CancellationToken ct) + { + void LocalMethod(CancellationToken token) + { + MethodAsync(token); + } + LocalMethod(ct); + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c) => Task.CompletedTask; +} + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task CS_Diagnostic_NestedFunctionAwaitOutside() + { + string originalCode = @" +using System; +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + Task LocalMethod(CancellationToken token) + { + return [|MethodAsync()|]; + } + await LocalMethod(ct); + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c) => Task.CompletedTask; +} + "; + string fixedCode = @" +using System; +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + Task LocalMethod(CancellationToken token) + { + return MethodAsync(token); + } + await LocalMethod(ct); + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c) => Task.CompletedTask; +} + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + [Fact] public Task CS_Diagnostic_NestedFunctionAwait_WithConfigureAwait() { @@ -1184,41 +1280,6 @@ End Class "); } - [Fact] - public Task VB_NoDiagnostic_NoAwait() - { - return VerifyVB.VerifyAnalyzerAsync(@" -Imports System.Threading -Imports System.Threading.Tasks -Class C - Private Sub M(ByVal ct As CancellationToken) - MethodAsync() - End Sub - Private Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task - Return Task.CompletedTask - End Function -End Class - "); - } - - [Fact] - public Task VB_NoDiagnostic_SaveTask() - { - return VerifyVB.VerifyAnalyzerAsync(@" -Imports System.Threading -Imports System.Threading.Tasks - -Class C - Private Sub M(ByVal ct As CancellationToken) - Dim t As Task = MethodAsync() - End Sub - Private Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task - Return Task.CompletedTask - End Function -End Class - "); - } - [Fact] public Task VB_NoDiagnostic_OverloadArgumentsDontMatch() { @@ -1303,7 +1364,7 @@ End Class } [Fact] - public Task VB_NoDiagnostic_PassingExpliciCancellationTokenNone() + public Task VB_NoDiagnostic_PassingExplicitCancellationTokenNone() { return VerifyVB.VerifyAnalyzerAsync(@" Imports System.Threading @@ -1323,21 +1384,19 @@ End Class } [Fact] - public Task VB_NoDiagnostic_ActionDelegateNoAwait() + public Task VB_NoDiagnostic_OverloadTokenNotLastParameter() { return VerifyVB.VerifyAnalyzerAsync(@" -Imports System Imports System.Threading Imports System.Threading.Tasks Class C - Private Sub M(ByVal ct As CancellationToken) - Dim a As Action(Of CancellationToken) = Sub(ByVal c As CancellationToken) MethodAsync() - a(ct) + Private Async Sub M(ByVal ct As CancellationToken) + Await MethodAsync() End Sub Private Function MethodAsync() As Task Return Task.CompletedTask End Function - Private Function MethodAsync(ByVal c As CancellationToken) As Task + Private Function MethodAsync(ByVal x As Integer, ByVal ct As CancellationToken, ByVal s As String) As Task Return Task.CompletedTask End Function End Class @@ -1345,33 +1404,27 @@ End Class } [Fact] - public Task VB_NoDiagnostic_FuncDelegateAwaitOutside() + public Task VB_NoDiagnostic_OverloadWithMultipleTokens() { return VerifyVB.VerifyAnalyzerAsync(@" -Imports System Imports System.Threading Imports System.Threading.Tasks Class C Private Async Sub M(ByVal ct As CancellationToken) - Dim f As Func(Of CancellationToken, Task) = Function(ByVal c As CancellationToken) MethodAsync() - Await f(ct) + Await MethodAsync() End Sub Private Function MethodAsync() As Task Return Task.CompletedTask End Function - Private Function MethodAsync(ByVal c As CancellationToken) As Task + Private Function MethodAsync(ByVal c1 As CancellationToken, ByVal ct2 As CancellationToken) As Task Return Task.CompletedTask End Function End Class "); } - // Nested functions not available in VB: - // VB_NoDiagnostic_NestedFunctionNoAwait - // VB_NoDiagnostic_NestedFunctionAwaitOutside - [Fact] - public Task VB_NoDiagnostic_OverloadTokenNotLastParameter() + public Task VB_NoDiagnostic_OverloadWithMultipleTokensSeparated() { return VerifyVB.VerifyAnalyzerAsync(@" Imports System.Threading @@ -1383,66 +1436,87 @@ End Sub Private Function MethodAsync() As Task Return Task.CompletedTask End Function - Private Function MethodAsync(ByVal x As Integer, ByVal ct As CancellationToken, ByVal s As String) As Task + Private Function MethodAsync(ByVal x As Integer, ByVal c1 As CancellationToken, ByVal s As String, ByVal ct2 As CancellationToken) As Task Return Task.CompletedTask End Function End Class "); } + #endregion + + #region Diagnostic = VB + [Fact] - public Task VB_NoDiagnostic_OverloadWithMultipleTokens() + public Task VB_Diagnostic_Class_TokenDefault() { - return VerifyVB.VerifyAnalyzerAsync(@" + string originalCode = @" Imports System.Threading Imports System.Threading.Tasks Class C Private Async Sub M(ByVal ct As CancellationToken) - Await MethodAsync() + Await [|MethodAsync()|] End Sub - Private Function MethodAsync() As Task + Private Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task Return Task.CompletedTask End Function - Private Function MethodAsync(ByVal c1 As CancellationToken, ByVal ct2 As CancellationToken) As Task +End Class + "; + string fixedCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await MethodAsync(ct) + End Sub + Private Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task Return Task.CompletedTask End Function End Class - "); + "; + return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); } + [Fact] - public Task VB_NoDiagnostic_OverloadWithMultipleTokensSeparated() + public Task VB_Diagnostic_Class_TokenDefault_WithConfigureAwait() { - return VerifyVB.VerifyAnalyzerAsync(@" + string originalCode = @" Imports System.Threading Imports System.Threading.Tasks Class C Private Async Sub M(ByVal ct As CancellationToken) - Await MethodAsync() + Await [|MethodAsync()|].ConfigureAwait(False) End Sub - Private Function MethodAsync() As Task + Private Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task Return Task.CompletedTask End Function - Private Function MethodAsync(ByVal x As Integer, ByVal c1 As CancellationToken, ByVal s As String, ByVal ct2 As CancellationToken) As Task +End Class + "; + string fixedCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await MethodAsync(ct).ConfigureAwait(False) + End Sub + Private Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task Return Task.CompletedTask End Function End Class - "); + "; + return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); } - #endregion - - #region Diagnostic = VB - [Fact] - public Task VB_Diagnostic_Class_TokenDefault() + public Task VB_Diagnostic_NoAwait() { string originalCode = @" Imports System.Threading Imports System.Threading.Tasks Class C - Private Async Sub M(ByVal ct As CancellationToken) - Await [|MethodAsync()|] + Private Sub M(ByVal ct As CancellationToken) + [|MethodAsync()|] End Sub Private Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task Return Task.CompletedTask @@ -1453,8 +1527,8 @@ End Class Imports System.Threading Imports System.Threading.Tasks Class C - Private Async Sub M(ByVal ct As CancellationToken) - Await MethodAsync(ct) + Private Sub M(ByVal ct As CancellationToken) + MethodAsync(ct) End Sub Private Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task Return Task.CompletedTask @@ -1465,14 +1539,15 @@ End Class } [Fact] - public Task VB_Diagnostic_Class_TokenDefault_WithConfigureAwait() + public Task VB_Diagnostic_SaveTask() { string originalCode = @" Imports System.Threading Imports System.Threading.Tasks + Class C - Private Async Sub M(ByVal ct As CancellationToken) - Await [|MethodAsync()|].ConfigureAwait(False) + Private Sub M(ByVal ct As CancellationToken) + Dim t As Task = [|MethodAsync()|] End Sub Private Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task Return Task.CompletedTask @@ -1482,9 +1557,10 @@ End Class string fixedCode = @" Imports System.Threading Imports System.Threading.Tasks + Class C - Private Async Sub M(ByVal ct As CancellationToken) - Await MethodAsync(ct).ConfigureAwait(False) + Private Sub M(ByVal ct As CancellationToken) + Dim t As Task = MethodAsync(ct) End Sub Private Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task Return Task.CompletedTask @@ -2048,6 +2124,46 @@ End Class return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); } + [Fact] + public Task VB_Diagnostic_ActionDelegateNoAwait() + { + string originalCode = @" +Imports System +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Sub M(ByVal ct As CancellationToken) + Dim a As Action(Of CancellationToken) = Sub(ByVal c As CancellationToken) [|MethodAsync()|] + a(ct) + End Sub + Private Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal c As CancellationToken) As Task + Return Task.CompletedTask + End Function +End Class + "; + string fixedCode = @" +Imports System +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Sub M(ByVal ct As CancellationToken) + Dim a As Action(Of CancellationToken) = Sub(ByVal c As CancellationToken) MethodAsync(c) + a(ct) + End Sub + Private Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal c As CancellationToken) As Task + Return Task.CompletedTask + End Function +End Class + "; + return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); + } + [Fact] public Task VB_Diagnostic_ActionDelegateAwait_WithConfigureAwait() { @@ -2089,7 +2205,7 @@ End Class } [Fact] - public Task VB_Diagnostic_FuncDelegateAwaitInside() + public Task VB_Diagnostic_FuncDelegateAwait() { string originalCode = @" Imports System @@ -2135,7 +2251,93 @@ End Class } [Fact] - public Task VB_Diagnostic_FuncDelegateAwaitInside_WithConfigureAwait() + public Task VB_Diagnostic_FuncDelegateNoAwait() + { + string originalCode = @" +Imports System +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Sub M(ByVal ct As CancellationToken) + Dim f As Func(Of CancellationToken, Boolean) = Function(ByVal token As CancellationToken) + [|MethodAsync()|] + Return True + End Function + f(ct) + End Sub + Private Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal c As CancellationToken) As Task + Return Task.CompletedTask + End Function +End Class + "; + string fixedCode = @" +Imports System +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Sub M(ByVal ct As CancellationToken) + Dim f As Func(Of CancellationToken, Boolean) = Function(ByVal token As CancellationToken) + MethodAsync(token) + Return True + End Function + f(ct) + End Sub + Private Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal c As CancellationToken) As Task + Return Task.CompletedTask + End Function +End Class + "; + return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task VB_Diagnostic_FuncDelegateAwaitOutside() + { + string originalCode = @" +Imports System +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Dim f As Func(Of CancellationToken, Task) = Function(ByVal c As CancellationToken) [|MethodAsync()|] + Await f(ct) + End Sub + Private Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal c As CancellationToken) As Task + Return Task.CompletedTask + End Function +End Class + "; + string fixedCode = @" +Imports System +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Dim f As Func(Of CancellationToken, Task) = Function(ByVal c As CancellationToken) MethodAsync(c) + Await f(ct) + End Sub + Private Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal c As CancellationToken) As Task + Return Task.CompletedTask + End Function +End Class + "; + return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task VB_Diagnostic_FuncDelegateAwait_WithConfigureAwait() { string originalCode = @" Imports System @@ -2182,6 +2384,8 @@ End Class // Nested functions not available in VB: // VB_Diagnostic_NestedFunctionAwait + // VB_Diagnostic_NestedFunctionNoAwait + // VB_Diagnostic_NestedFunctionAwaitOutside // VB_Diagnostic_NestedFunctionAwait_WithConfigureAwait #endregion diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToAsyncMethods.Fixer.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToAsyncMethods.Fixer.vb index d41408070a..e6bd5ec818 100644 --- a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToAsyncMethods.Fixer.vb +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToAsyncMethods.Fixer.vb @@ -1,6 +1,5 @@ ' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -Imports System.Collections.Immutable Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.CodeFixes Imports Microsoft.CodeAnalysis.VisualBasic.Syntax @@ -13,12 +12,12 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime Inherits ForwardCancellationTokenToAsyncMethodsFixer - Private Function IsCancellationTokenParameter(parameter As ParameterSyntax) As Boolean + Private Shared Function IsCancellationTokenParameter(parameter As ParameterSyntax) As Boolean Dim type As SimpleNameSyntax = TryCast(parameter.AsClause.Type, SimpleNameSyntax) Return type IsNot Nothing AndAlso type.Identifier.ValueText.Equals(CancellationTokenName, StringComparison.Ordinal) End Function - Private Function GetCancellationTokenName(parameterList As SeparatedSyntaxList(Of ParameterSyntax)) As String + Private Shared Function GetCancellationTokenName(parameterList As SeparatedSyntaxList(Of ParameterSyntax)) As String Return parameterList.FirstOrDefault(Function(p) IsCancellationTokenParameter(p))?.Identifier.Identifier.ValueText End Function @@ -54,7 +53,9 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime Return Not String.IsNullOrEmpty(parameterName) End Function + End Class + End Namespace From 084dc36534ff22e9b06ca9504ee195586be203d0 Mon Sep 17 00:00:00 2001 From: carlossanlop Date: Wed, 20 May 2020 16:32:01 -0700 Subject: [PATCH 03/20] Address case where CancellationToken is an aliased using --- ...rdCancellationTokenToAsyncMethods.Fixer.cs | 24 +- ...rdCancellationTokenToAsyncMethods.Fixer.cs | 14 +- ...ardCancellationTokenToAsyncMethodsTests.cs | 376 ++++++++++++++++++ ...rdCancellationTokenToAsyncMethods.Fixer.vb | 43 +- 4 files changed, 419 insertions(+), 38 deletions(-) diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToAsyncMethods.Fixer.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToAsyncMethods.Fixer.cs index 075e3c25fa..abd196ce0c 100644 --- a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToAsyncMethods.Fixer.cs +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToAsyncMethods.Fixer.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeFixes; @@ -12,32 +13,33 @@ namespace Microsoft.NetCore.CSharp.Analyzers.Runtime [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class CSharpForwardCancellationTokenToAsyncMethodsFixer : ForwardCancellationTokenToAsyncMethodsFixer { - private static bool IsCancellationTokenParameter(ParameterSyntax parameter) => - parameter.Type is IdentifierNameSyntax typeIdentifier && typeIdentifier.Identifier.ValueText.Equals(CancellationTokenName, StringComparison.Ordinal); + private static string? GetCancellationTokenName(SemanticModel model, IEnumerable parameters) => + parameters.FirstOrDefault(p => IsCancellationTokenParameter(model, p))?.Identifier.ValueText; - private static string? GetCancellationTokenName(SeparatedSyntaxList parameters) => - parameters.FirstOrDefault(p => IsCancellationTokenParameter(p))?.Identifier.ValueText; - - protected override bool TryGetAncestorDeclarationCancellationTokenParameterName(SyntaxNode node, out string? parameterName) + protected override bool TryGetAncestorDeclarationCancellationTokenParameterName(SemanticModel model, SyntaxNode node, out string? parameterName) { parameterName = null; SyntaxNode currentNode = node.Parent; + IEnumerable? parameters = null; while (currentNode != null) { if (currentNode is ParenthesizedLambdaExpressionSyntax lambdaNode) { - parameterName = GetCancellationTokenName(lambdaNode.ParameterList.Parameters); - break; + parameters = lambdaNode.ParameterList.Parameters; } else if (currentNode is LocalFunctionStatementSyntax localNode) { - parameterName = GetCancellationTokenName(localNode.ParameterList.Parameters); - break; + parameters = localNode.ParameterList.Parameters; } else if (currentNode is MethodDeclarationSyntax methodNode) { - parameterName = GetCancellationTokenName(methodNode.ParameterList.Parameters); + parameters = methodNode.ParameterList.Parameters; + } + + if (parameters != null) + { + parameterName = GetCancellationTokenName(model, parameters); break; } diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Fixer.cs index d363f457d9..050b04be38 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Fixer.cs @@ -18,9 +18,17 @@ namespace Microsoft.NetCore.Analyzers.Runtime /// public abstract class ForwardCancellationTokenToAsyncMethodsFixer : CodeFixProvider { + protected const string CancellationTokenName = "CancellationToken"; + // Looks for a ct parameter in the ancestor method or function declaration. If one is found, retrieve the name of the parameter. // Returns true if a ct parameter was found and parameterName is not null or empty. Returns false otherwise. - protected abstract bool TryGetAncestorDeclarationCancellationTokenParameterName(SyntaxNode node, out string? parameterName); + protected abstract bool TryGetAncestorDeclarationCancellationTokenParameterName(SemanticModel model, SyntaxNode node, out string? parameterName); + + protected static bool IsCancellationTokenParameter(SemanticModel model, SyntaxNode parameter) + { + return model.GetDeclaredSymbol(parameter) is IParameterSymbol parameterSymbol && + parameterSymbol.Type.Name.Equals(CancellationTokenName, StringComparison.Ordinal); + } public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(ForwardCancellationTokenToAsyncMethodsAnalyzer.RuleId); @@ -28,8 +36,6 @@ public abstract class ForwardCancellationTokenToAsyncMethodsFixer : CodeFixProvi public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; - protected const string CancellationTokenName = "CancellationToken"; - public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) { Document doc = context.Document; @@ -47,7 +53,7 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) return; } - if (!TryGetAncestorDeclarationCancellationTokenParameterName(node, out string? parameterName)) + if (!TryGetAncestorDeclarationCancellationTokenParameterName(model, node, out string? parameterName)) { return; } diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethodsTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethodsTests.cs index b357b5a222..083aeb0de6 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethodsTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethodsTests.cs @@ -1225,6 +1225,204 @@ async void LocalMethod(CancellationToken token) return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); } + [Fact] + public Task CS_Diagnostic_AliasTokenInDefault() + { + string originalCode = @" +using System.Threading; +using System.Threading.Tasks; +using TokenAlias = System.Threading.CancellationToken; +class C +{ + async void M(CancellationToken ct) + { + await [|MethodAsync()|]; + } + Task MethodAsync(TokenAlias c = default) => Task.CompletedTask; +} + "; + string fixedCode = @" +using System.Threading; +using System.Threading.Tasks; +using TokenAlias = System.Threading.CancellationToken; +class C +{ + async void M(CancellationToken ct) + { + await MethodAsync(ct); + } + Task MethodAsync(TokenAlias c = default) => Task.CompletedTask; +} + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task CS_Diagnostic_AliasTokenInOverload() + { + string originalCode = @" +using System.Threading; +using System.Threading.Tasks; +using TokenAlias = System.Threading.CancellationToken; +class C +{ + async void M(CancellationToken ct) + { + await [|MethodAsync()|]; + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(TokenAlias c) => Task.CompletedTask; +} + "; + string fixedCode = @" +using System.Threading; +using System.Threading.Tasks; +using TokenAlias = System.Threading.CancellationToken; +class C +{ + async void M(CancellationToken ct) + { + await MethodAsync(ct); + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(TokenAlias c) => Task.CompletedTask; +} + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task CS_Diagnostic_Default_AliasTokenInMethodParameter() + { + string originalCode = @" +using System.Threading; +using System.Threading.Tasks; +using TokenAlias = System.Threading.CancellationToken; +class C +{ + async void M(TokenAlias ct) + { + await [|MethodAsync()|]; + } + Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; +} + "; + string fixedCode = @" +using System.Threading; +using System.Threading.Tasks; +using TokenAlias = System.Threading.CancellationToken; +class C +{ + async void M(TokenAlias ct) + { + await MethodAsync(ct); + } + Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; +} + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task CS_Diagnostic_Overload_AliasTokenInMethodParameter() + { + string originalCode = @" +using System.Threading; +using System.Threading.Tasks; +using TokenAlias = System.Threading.CancellationToken; +class C +{ + async void M(TokenAlias ct) + { + await [|MethodAsync()|]; + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c) => Task.CompletedTask; +} + "; + string fixedCode = @" +using System.Threading; +using System.Threading.Tasks; +using TokenAlias = System.Threading.CancellationToken; +class C +{ + async void M(TokenAlias ct) + { + await MethodAsync(ct); + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c) => Task.CompletedTask; +} + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task CS_Diagnostic_Default_AliasTokenInDefaultAndMethodParameter() + { + string originalCode = @" +using System.Threading; +using System.Threading.Tasks; +using TokenAlias = System.Threading.CancellationToken; +class C +{ + async void M(TokenAlias ct) + { + await [|MethodAsync()|]; + } + Task MethodAsync(TokenAlias c = default) => Task.CompletedTask; +} + "; + string fixedCode = @" +using System.Threading; +using System.Threading.Tasks; +using TokenAlias = System.Threading.CancellationToken; +class C +{ + async void M(TokenAlias ct) + { + await MethodAsync(ct); + } + Task MethodAsync(TokenAlias c = default) => Task.CompletedTask; +} + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task CS_Diagnostic_Overload_AliasTokenInOverloadAndMethodParameter() + { + string originalCode = @" +using System.Threading; +using System.Threading.Tasks; +using TokenAlias = System.Threading.CancellationToken; +class C +{ + async void M(TokenAlias ct) + { + await [|MethodAsync()|]; + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c) => Task.CompletedTask; +} + "; + string fixedCode = @" +using System.Threading; +using System.Threading.Tasks; +using TokenAlias = System.Threading.CancellationToken; +class C +{ + async void M(TokenAlias ct) + { + await MethodAsync(ct); + } + Task MethodAsync() => Task.CompletedTask; + Task MethodAsync(CancellationToken c) => Task.CompletedTask; +} + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + #endregion #region No Diagnostic - VB @@ -2388,6 +2586,184 @@ End Class // VB_Diagnostic_NestedFunctionAwaitOutside // VB_Diagnostic_NestedFunctionAwait_WithConfigureAwait + [Fact] + public Task VB_Diagnostic_AliasTokenInOverload() + { + string originalCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Imports TokenAlias = System.Threading.CancellationToken +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await [|MethodAsync()|] + End Sub + Private Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal c As TokenAlias) As Task + Return Task.CompletedTask + End Function +End Class + "; + string fixedCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Imports TokenAlias = System.Threading.CancellationToken +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await MethodAsync(ct) + End Sub + Private Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal c As TokenAlias) As Task + Return Task.CompletedTask + End Function +End Class + "; + return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task VB_Diagnostic_Default_AliasTokenInMethodParameter() + { + string originalCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Imports TokenAlias = System.Threading.CancellationToken +Class C + Private Async Sub M(ByVal ct As TokenAlias) + Await [|MethodAsync()|] + End Sub + Private Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task + Return Task.CompletedTask + End Function +End Class + "; + string fixedCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Imports TokenAlias = System.Threading.CancellationToken +Class C + Private Async Sub M(ByVal ct As TokenAlias) + Await MethodAsync(ct) + End Sub + Private Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task + Return Task.CompletedTask + End Function +End Class + "; + return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task VB_Diagnostic_Overload_AliasTokenInMethodParameter() + { + string originalCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Imports TokenAlias = System.Threading.CancellationToken +Class C + Private Async Sub M(ByVal ct As TokenAlias) + Await [|MethodAsync()|] + End Sub + Private Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal c As CancellationToken) As Task + Return Task.CompletedTask + End Function +End Class + "; + string fixedCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Imports TokenAlias = System.Threading.CancellationToken +Class C + Private Async Sub M(ByVal ct As TokenAlias) + Await MethodAsync(ct) + End Sub + Private Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal c As CancellationToken) As Task + Return Task.CompletedTask + End Function +End Class + "; + return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task VB_Diagnostic_Default_AliasTokenInDefaultAndMethodParameter() + { + string originalCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Imports TokenAlias = System.Threading.CancellationToken +Class C + Private Async Sub M(ByVal ct As TokenAlias) + Await [|MethodAsync()|] + End Sub + Private Function MethodAsync(ByVal Optional c As TokenAlias = Nothing) As Task + Return Task.CompletedTask + End Function +End Class + "; + string fixedCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Imports TokenAlias = System.Threading.CancellationToken +Class C + Private Async Sub M(ByVal ct As TokenAlias) + Await MethodAsync(ct) + End Sub + Private Function MethodAsync(ByVal Optional c As TokenAlias = Nothing) As Task + Return Task.CompletedTask + End Function +End Class + "; + return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task VB_Diagnostic_Overload_AliasTokenInOverloadAndMethodParameter() + { + string originalCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Imports TokenAlias = System.Threading.CancellationToken +Class C + Private Async Sub M(ByVal ct As TokenAlias) + Await [|MethodAsync()|] + End Sub + Private Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal c As CancellationToken) As Task + Return Task.CompletedTask + End Function +End Class + "; + string fixedCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Imports TokenAlias = System.Threading.CancellationToken +Class C + Private Async Sub M(ByVal ct As TokenAlias) + Await MethodAsync(ct) + End Sub + Private Function MethodAsync() As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal c As CancellationToken) As Task + Return Task.CompletedTask + End Function +End Class + "; + return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); + } + #endregion } } \ No newline at end of file diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToAsyncMethods.Fixer.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToAsyncMethods.Fixer.vb index e6bd5ec818..a39be25443 100644 --- a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToAsyncMethods.Fixer.vb +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToAsyncMethods.Fixer.vb @@ -12,38 +12,35 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime Inherits ForwardCancellationTokenToAsyncMethodsFixer - Private Shared Function IsCancellationTokenParameter(parameter As ParameterSyntax) As Boolean - Dim type As SimpleNameSyntax = TryCast(parameter.AsClause.Type, SimpleNameSyntax) - Return type IsNot Nothing AndAlso type.Identifier.ValueText.Equals(CancellationTokenName, StringComparison.Ordinal) + Private Shared Function GetCancellationTokenName(model As SemanticModel, parameters As IEnumerable(Of ParameterSyntax)) As String + Return parameters.FirstOrDefault(Function(p) IsCancellationTokenParameter(model, p))?.Identifier.Identifier.ValueText End Function - Private Shared Function GetCancellationTokenName(parameterList As SeparatedSyntaxList(Of ParameterSyntax)) As String - Return parameterList.FirstOrDefault(Function(p) IsCancellationTokenParameter(p))?.Identifier.Identifier.ValueText - End Function - - Protected Overrides Function TryGetAncestorDeclarationCancellationTokenParameterName(node As SyntaxNode, ByRef parameterName As String) As Boolean + Protected Overrides Function TryGetAncestorDeclarationCancellationTokenParameterName(model As SemanticModel, node As SyntaxNode, ByRef parameterName As String) As Boolean parameterName = Nothing Dim currentNode As SyntaxNode = node.Parent + Dim parameters As IEnumerable(Of ParameterSyntax) = Nothing While currentNode IsNot Nothing - Dim singleLineLambdaNode As SingleLineLambdaExpressionSyntax = TryCast(currentNode, SingleLineLambdaExpressionSyntax) - Dim multiLineLambdaNode As MultiLineLambdaExpressionSyntax = TryCast(currentNode, MultiLineLambdaExpressionSyntax) - Dim methodNode As MethodStatementSyntax = TryCast(currentNode, MethodStatementSyntax) - Dim methodBlockNode As MethodBlockSyntax = TryCast(currentNode, MethodBlockSyntax) + Dim singleLineLambda As SingleLineLambdaExpressionSyntax = TryCast(currentNode, SingleLineLambdaExpressionSyntax) + Dim multiLineLambda As MultiLineLambdaExpressionSyntax = TryCast(currentNode, MultiLineLambdaExpressionSyntax) + Dim methodStatement As MethodStatementSyntax = TryCast(currentNode, MethodStatementSyntax) + Dim methodBlock As MethodBlockSyntax = TryCast(currentNode, MethodBlockSyntax) + + If singleLineLambda IsNot Nothing Then + parameters = singleLineLambda.SubOrFunctionHeader.ParameterList.Parameters + ElseIf multiLineLambda IsNot Nothing Then + parameters = multiLineLambda.SubOrFunctionHeader.ParameterList.Parameters + ElseIf methodStatement IsNot Nothing Then + parameters = methodStatement.ParameterList.Parameters + ElseIf methodBlock IsNot Nothing Then + parameters = methodBlock.SubOrFunctionStatement.ParameterList.Parameters + End If - If singleLineLambdaNode IsNot Nothing Then - parameterName = GetCancellationTokenName(singleLineLambdaNode.SubOrFunctionHeader.ParameterList.Parameters) - Exit While - ElseIf multiLineLambdaNode IsNot Nothing Then - parameterName = GetCancellationTokenName(multiLineLambdaNode.SubOrFunctionHeader.ParameterList.Parameters) - Exit While - ElseIf methodNode IsNot Nothing Then - parameterName = GetCancellationTokenName(methodNode.ParameterList.Parameters) - Exit While - ElseIf methodBlockNode IsNot Nothing Then - parameterName = GetCancellationTokenName(methodBlockNode.SubOrFunctionStatement.ParameterList.Parameters) + If parameters IsNot Nothing Then + parameterName = GetCancellationTokenName(model, parameters) Exit While End If From 5bfb7d8ea5c21a2dc68e7d68396eec21f2da3af6 Mon Sep 17 00:00:00 2001 From: carlossanlop Date: Thu, 21 May 2020 17:41:01 -0700 Subject: [PATCH 04/20] Address PR suggestions --- ...rdCancellationTokenToAsyncMethods.Fixer.cs | 1 - .../MicrosoftNetCoreAnalyzersResources.resx | 6 +- .../ForwardCancellationTokenToAsyncMethods.cs | 85 ++++++++++--------- .../MicrosoftNetCoreAnalyzersResources.cs.xlf | 12 +-- .../MicrosoftNetCoreAnalyzersResources.de.xlf | 12 +-- .../MicrosoftNetCoreAnalyzersResources.es.xlf | 12 +-- .../MicrosoftNetCoreAnalyzersResources.fr.xlf | 12 +-- .../MicrosoftNetCoreAnalyzersResources.it.xlf | 12 +-- .../MicrosoftNetCoreAnalyzersResources.ja.xlf | 12 +-- .../MicrosoftNetCoreAnalyzersResources.ko.xlf | 12 +-- .../MicrosoftNetCoreAnalyzersResources.pl.xlf | 12 +-- ...crosoftNetCoreAnalyzersResources.pt-BR.xlf | 12 +-- .../MicrosoftNetCoreAnalyzersResources.ru.xlf | 12 +-- .../MicrosoftNetCoreAnalyzersResources.tr.xlf | 12 +-- ...osoftNetCoreAnalyzersResources.zh-Hans.xlf | 12 +-- ...osoftNetCoreAnalyzersResources.zh-Hant.xlf | 12 +-- 16 files changed, 126 insertions(+), 122 deletions(-) diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToAsyncMethods.Fixer.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToAsyncMethods.Fixer.cs index abd196ce0c..3e57a4873a 100644 --- a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToAsyncMethods.Fixer.cs +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToAsyncMethods.Fixer.cs @@ -1,6 +1,5 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis; diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx index c09ab7925e..6ba9b66c91 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx @@ -1288,13 +1288,13 @@ Change the '{0}' method call to use the '{1}' overload - Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. + Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - Consider forwarding the '{0}' parameter to the '{1}' method. + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - Consider forwarding the 'CancellationToken' parameter to methods that take one. + Forward the 'CancellationToken' parameter to methods that take one. Change to call the two argument constructor, pass null for the message. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.cs index 78387937bb..04b84f8992 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Analyzer.Utilities; using Analyzer.Utilities.Extensions; @@ -84,16 +85,16 @@ private void AnalyzeCompilationStart(CompilationStartAnalysisContext context) context.RegisterOperationAction(context => { + IInvocationOperation invocation = (IInvocationOperation)context.Operation; if (!ShouldAnalyze( - (IInvocationOperation)context.Operation, + invocation, cancellationTokenType, - out string? cancellationTokenParameterName, - out string? methodName)) + out string? cancellationTokenParameterName)) { return; } - context.ReportDiagnostic(context.Operation.CreateDiagnostic(ForwardCancellationTokenToAsyncMethodsRule, cancellationTokenParameterName!, methodName!)); + context.ReportDiagnostic(context.Operation.CreateDiagnostic(ForwardCancellationTokenToAsyncMethodsRule, cancellationTokenParameterName, invocation.TargetMethod.Name)); }, OperationKind.Invocation); } @@ -101,17 +102,15 @@ private void AnalyzeCompilationStart(CompilationStartAnalysisContext context) private static bool ShouldAnalyze( IInvocationOperation invocation, INamedTypeSymbol cancellationTokenType, - out string? cancellationTokenParameterName, - out string? methodName) + [NotNullWhen(returnValue: true)] out string? cancellationTokenParameterName) { IMethodSymbol method = invocation.TargetMethod; cancellationTokenParameterName = null; - methodName = method.Name; // Check if the invocation has an optional implicit ct or an overload that takes one ct - if (!InvocationIgnoresOptionalCancellationToken(cancellationTokenType, invocation, method) && - !MethodHasCancellationTokenOverload(cancellationTokenType, method)) + if (!InvocationIgnoresOptionalCancellationToken(invocation, cancellationTokenType) && + !MethodHasCancellationTokenOverload(method, cancellationTokenType)) { return false; } @@ -123,16 +122,18 @@ private static bool ShouldAnalyze( } // Check if the ancestor method has a ct that we can pass to the invocation - if (!VerifyMethodOnlyHasOneCancellationTokenAsLastArgument(cancellationTokenType, methodDeclaration!, out cancellationTokenParameterName)) + if (!VerifyMethodOnlyHasOneCancellationTokenAsLastArgument(cancellationTokenType, methodDeclaration, out cancellationTokenParameterName)) { return false; } - return cancellationTokenParameterName != null && methodName != null; + return cancellationTokenParameterName != null; } // Looks for an ancestor that could be a method or function declaration. - private static bool TryGetAncestorDeclaration(IInvocationOperation invocation, out IMethodSymbol? declaration) + private static bool TryGetAncestorDeclaration( + IInvocationOperation invocation, + [NotNullWhen(returnValue: true)] out IMethodSymbol? declaration) { declaration = null; @@ -179,29 +180,32 @@ private static bool TryGetAncestorDeclaration(IInvocationOperation invocation, o private static bool VerifyMethodOnlyHasOneCancellationTokenAsLastArgument( INamedTypeSymbol cancellationTokenType, IMethodSymbol methodDeclaration, - out string? cancellationTokenParameterName) + [NotNullWhen(returnValue: true)] out string? cancellationTokenParameterName) { - cancellationTokenParameterName = null; - if (methodDeclaration.Parameters.Count(x => x.Type.Equals(cancellationTokenType)) == 1 && methodDeclaration.Parameters.Last() is IParameterSymbol lastParameter && lastParameter.Type.Equals(cancellationTokenType)) { cancellationTokenParameterName = lastParameter.Name; + return true; } - return cancellationTokenParameterName != null; + cancellationTokenParameterName = null; + return false; } // Check if the currently used overload is the one that takes the ct, but is utilizing the default value offered in the method signature. // We want to offer a diagnostic for this case, so the user explicitly passes the ancestor's ct. - private static bool InvocationIgnoresOptionalCancellationToken(INamedTypeSymbol cancellationTokenType, IInvocationOperation invocation, IMethodSymbol method) + private static bool InvocationIgnoresOptionalCancellationToken(IInvocationOperation invocation, INamedTypeSymbol cancellationTokenType) { - if (method.Parameters.Any() && method.Parameters.Last() is IParameterSymbol lastParameter && lastParameter.Type.Equals(cancellationTokenType)) + IMethodSymbol method = invocation.TargetMethod; + if (method.Parameters.Length != 0 && + method.Parameters[method.Parameters.Length - 1] is IParameterSymbol lastParameter && + lastParameter.Type.Equals(cancellationTokenType)) { // If the ct parameter has a default value, return true if a value is not being explicitly passed in the invocation return lastParameter.IsOptional && // Has a default value - invocation.Arguments.Last() is IArgumentOperation lastArgument && + invocation.Arguments[invocation.Arguments.Length - 1] is IArgumentOperation lastArgument && lastArgument.IsImplicit; // The default value is being used } @@ -209,36 +213,37 @@ private static bool InvocationIgnoresOptionalCancellationToken(INamedTypeSymbol } // Check if there's a method overload with the same parameters as this one, in the same order, plus a ct at the end. - private static bool MethodHasCancellationTokenOverload(ITypeSymbol cancellationTokenType, IMethodSymbol method) + private static bool MethodHasCancellationTokenOverload(IMethodSymbol method, ITypeSymbol cancellationTokenType) { - IMethodSymbol? overload = method.ContainingType.GetMembers(method.Name).OfType().FirstOrDefault(x => HasSameParametersPlusCancellationToken(cancellationTokenType, method, x)); - return overload != null; - } + IMethodSymbol? overload = method.ContainingType.GetMembers(method.Name) + .OfType() + .FirstOrDefault(methodToCompare => HasSameParametersPlusCancellationToken(cancellationTokenType, method, methodToCompare)); - // Checks if the parameters of the two passed methods only differ in a ct. - private static bool HasSameParametersPlusCancellationToken(ITypeSymbol cancellationTokenType, IMethodSymbol originalMethod, IMethodSymbol methodToCompare) - { - if (!originalMethod.Name.Equals(methodToCompare.Name, StringComparison.Ordinal) || - originalMethod.Equals(methodToCompare) || - methodToCompare.Parameters.Length == 0 || - methodToCompare.Parameters.Length != originalMethod.Parameters.Length + 1 || - !methodToCompare.Parameters[methodToCompare.Parameters.Length - 1].Type.Equals(cancellationTokenType)) - { - return false; - } + return overload != null; - for (int i = 0; i < originalMethod.Parameters.Length; i++) + // Checks if the parameters of the two passed methods only differ in a ct. + static bool HasSameParametersPlusCancellationToken(ITypeSymbol cancellationTokenType, IMethodSymbol originalMethod, IMethodSymbol methodToCompare) { - IParameterSymbol? originalParameter = originalMethod.Parameters[i]; - IParameterSymbol? comparedParameter = methodToCompare.Parameters[i]; - if (originalParameter == null || comparedParameter == null || !originalParameter.Type.Equals(comparedParameter.Type)) + if (originalMethod.Equals(methodToCompare) || + methodToCompare.Parameters.Length != originalMethod.Parameters.Length + 1 || + !methodToCompare.Parameters[methodToCompare.Parameters.Length - 1].Type.Equals(cancellationTokenType)) { return false; } - } - return true; + for (int i = 0; i < originalMethod.Parameters.Length; i++) + { + IParameterSymbol? originalParameter = originalMethod.Parameters[i]; + IParameterSymbol? comparedParameter = methodToCompare.Parameters[i]; + if (originalParameter == null || comparedParameter == null || !originalParameter.Type.Equals(comparedParameter.Type)) + { + return false; + } + } + + return true; + } } } } \ No newline at end of file diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf index 26ddd722d1..39ddf03446 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf @@ -878,18 +878,18 @@ - Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. - Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. + Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. + Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - Consider forwarding the '{0}' parameter to the '{1}' method. - Consider forwarding the '{0}' parameter to the '{1}' method. + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - Consider forwarding the 'CancellationToken' parameter to methods that take one. - Consider forwarding the 'CancellationToken' parameter to methods that take one. + Forward the 'CancellationToken' parameter to methods that take one. + Forward the 'CancellationToken' parameter to methods that take one. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf index 5fb6b7529d..6c5d6ddd2b 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf @@ -878,18 +878,18 @@ - Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. - Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. + Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. + Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - Consider forwarding the '{0}' parameter to the '{1}' method. - Consider forwarding the '{0}' parameter to the '{1}' method. + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - Consider forwarding the 'CancellationToken' parameter to methods that take one. - Consider forwarding the 'CancellationToken' parameter to methods that take one. + Forward the 'CancellationToken' parameter to methods that take one. + Forward the 'CancellationToken' parameter to methods that take one. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf index 88f954fa0a..7e3324e24c 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf @@ -878,18 +878,18 @@ - Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. - Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. + Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. + Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - Consider forwarding the '{0}' parameter to the '{1}' method. - Consider forwarding the '{0}' parameter to the '{1}' method. + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - Consider forwarding the 'CancellationToken' parameter to methods that take one. - Consider forwarding the 'CancellationToken' parameter to methods that take one. + Forward the 'CancellationToken' parameter to methods that take one. + Forward the 'CancellationToken' parameter to methods that take one. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf index f28e378cc3..9dfe652c30 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf @@ -878,18 +878,18 @@ - Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. - Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. + Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. + Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - Consider forwarding the '{0}' parameter to the '{1}' method. - Consider forwarding the '{0}' parameter to the '{1}' method. + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - Consider forwarding the 'CancellationToken' parameter to methods that take one. - Consider forwarding the 'CancellationToken' parameter to methods that take one. + Forward the 'CancellationToken' parameter to methods that take one. + Forward the 'CancellationToken' parameter to methods that take one. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf index 1849ed2bc4..c4cc5e7b01 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf @@ -878,18 +878,18 @@ - Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. - Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. + Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. + Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - Consider forwarding the '{0}' parameter to the '{1}' method. - Consider forwarding the '{0}' parameter to the '{1}' method. + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - Consider forwarding the 'CancellationToken' parameter to methods that take one. - Consider forwarding the 'CancellationToken' parameter to methods that take one. + Forward the 'CancellationToken' parameter to methods that take one. + Forward the 'CancellationToken' parameter to methods that take one. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf index 549acce376..55f2314a95 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf @@ -878,18 +878,18 @@ - Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. - Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. + Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. + Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - Consider forwarding the '{0}' parameter to the '{1}' method. - Consider forwarding the '{0}' parameter to the '{1}' method. + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - Consider forwarding the 'CancellationToken' parameter to methods that take one. - Consider forwarding the 'CancellationToken' parameter to methods that take one. + Forward the 'CancellationToken' parameter to methods that take one. + Forward the 'CancellationToken' parameter to methods that take one. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf index cc2970f39a..89e8f22649 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf @@ -878,18 +878,18 @@ - Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. - Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. + Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. + Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - Consider forwarding the '{0}' parameter to the '{1}' method. - Consider forwarding the '{0}' parameter to the '{1}' method. + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - Consider forwarding the 'CancellationToken' parameter to methods that take one. - Consider forwarding the 'CancellationToken' parameter to methods that take one. + Forward the 'CancellationToken' parameter to methods that take one. + Forward the 'CancellationToken' parameter to methods that take one. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf index 81f3235973..7fe7d69b09 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf @@ -879,18 +879,18 @@ - Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. - Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. + Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. + Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - Consider forwarding the '{0}' parameter to the '{1}' method. - Consider forwarding the '{0}' parameter to the '{1}' method. + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - Consider forwarding the 'CancellationToken' parameter to methods that take one. - Consider forwarding the 'CancellationToken' parameter to methods that take one. + Forward the 'CancellationToken' parameter to methods that take one. + Forward the 'CancellationToken' parameter to methods that take one. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf index 9525a7287b..01add35a46 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf @@ -878,18 +878,18 @@ - Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. - Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. + Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. + Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - Consider forwarding the '{0}' parameter to the '{1}' method. - Consider forwarding the '{0}' parameter to the '{1}' method. + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - Consider forwarding the 'CancellationToken' parameter to methods that take one. - Consider forwarding the 'CancellationToken' parameter to methods that take one. + Forward the 'CancellationToken' parameter to methods that take one. + Forward the 'CancellationToken' parameter to methods that take one. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf index fd71873775..8ae7a73ab9 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf @@ -878,18 +878,18 @@ - Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. - Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. + Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. + Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - Consider forwarding the '{0}' parameter to the '{1}' method. - Consider forwarding the '{0}' parameter to the '{1}' method. + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - Consider forwarding the 'CancellationToken' parameter to methods that take one. - Consider forwarding the 'CancellationToken' parameter to methods that take one. + Forward the 'CancellationToken' parameter to methods that take one. + Forward the 'CancellationToken' parameter to methods that take one. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf index c4c6b444aa..a7f8084010 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf @@ -878,18 +878,18 @@ - Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. - Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. + Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. + Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - Consider forwarding the '{0}' parameter to the '{1}' method. - Consider forwarding the '{0}' parameter to the '{1}' method. + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - Consider forwarding the 'CancellationToken' parameter to methods that take one. - Consider forwarding the 'CancellationToken' parameter to methods that take one. + Forward the 'CancellationToken' parameter to methods that take one. + Forward the 'CancellationToken' parameter to methods that take one. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf index 28eb20f8ee..6d67573091 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf @@ -878,18 +878,18 @@ - Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. - Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. + Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. + Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - Consider forwarding the '{0}' parameter to the '{1}' method. - Consider forwarding the '{0}' parameter to the '{1}' method. + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - Consider forwarding the 'CancellationToken' parameter to methods that take one. - Consider forwarding the 'CancellationToken' parameter to methods that take one. + Forward the 'CancellationToken' parameter to methods that take one. + Forward the 'CancellationToken' parameter to methods that take one. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf index f713b24c29..5fb2e9bd7f 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf @@ -878,18 +878,18 @@ - Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. - Consider forwarding the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated. + Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. + Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - Consider forwarding the '{0}' parameter to the '{1}' method. - Consider forwarding the '{0}' parameter to the '{1}' method. + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - Consider forwarding the 'CancellationToken' parameter to methods that take one. - Consider forwarding the 'CancellationToken' parameter to methods that take one. + Forward the 'CancellationToken' parameter to methods that take one. + Forward the 'CancellationToken' parameter to methods that take one. From efa1838b776d6f4f99bdc852b6d7bd8b65ce37bc Mon Sep 17 00:00:00 2001 From: carlossanlop Date: Thu, 21 May 2020 17:52:20 -0700 Subject: [PATCH 05/20] Fix breaking change introduced by most recent rebased changes. --- .../Runtime/ForwardCancellationTokenToAsyncMethods.Fixer.cs | 4 ++-- .../Runtime/ForwardCancellationTokenToAsyncMethods.cs | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Fixer.cs index 050b04be38..ab818ec8c8 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Fixer.cs @@ -100,12 +100,12 @@ private static Task FixInvocation(Document doc, SyntaxNode root, IInvo // The instance is implicit when calling a method from the same type, call the method directly else if (invocation.Instance.IsImplicit) { - newInvocation = invocation.GetInstance(); // GetInstance will return the method name + newInvocation = invocation.GetInstanceSyntax()!; // GetInstance will return the method name } // Calling a method from an object, we must include the instance variable name else { - newInvocation = generator.MemberAccessExpression(invocation.GetInstance(), invocation.TargetMethod.Name); + newInvocation = generator.MemberAccessExpression(invocation.GetInstanceSyntax(), invocation.TargetMethod.Name); } // Insert the new arguments to the new invocation SyntaxNode newInvocationWithArguments = generator.InvocationExpression(newInvocation, newArguments); diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.cs index 04b84f8992..4278f01c0a 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.cs @@ -1,6 +1,5 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Linq; From 4c5776d5ac597da7118eb1cf217c6487bc7b6767 Mon Sep 17 00:00:00 2001 From: carlossanlop Date: Wed, 27 May 2020 11:47:33 -0700 Subject: [PATCH 06/20] Address PR suggestions --- ...rdCancellationTokenToAsyncMethods.Fixer.cs | 23 +- ...rdCancellationTokenToAsyncMethods.Fixer.cs | 68 +++- .../ForwardCancellationTokenToAsyncMethods.cs | 89 ++--- ...ardCancellationTokenToAsyncMethodsTests.cs | 348 +++++++++++++++++- ...rdCancellationTokenToAsyncMethods.Fixer.vb | 18 +- 5 files changed, 445 insertions(+), 101 deletions(-) diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToAsyncMethods.Fixer.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToAsyncMethods.Fixer.cs index 3e57a4873a..90f61e458e 100644 --- a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToAsyncMethods.Fixer.cs +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToAsyncMethods.Fixer.cs @@ -1,10 +1,12 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Operations; using Microsoft.NetCore.Analyzers.Runtime; namespace Microsoft.NetCore.CSharp.Analyzers.Runtime @@ -12,10 +14,9 @@ namespace Microsoft.NetCore.CSharp.Analyzers.Runtime [ExportCodeFixProvider(LanguageNames.CSharp)] public sealed class CSharpForwardCancellationTokenToAsyncMethodsFixer : ForwardCancellationTokenToAsyncMethodsFixer { - private static string? GetCancellationTokenName(SemanticModel model, IEnumerable parameters) => - parameters.FirstOrDefault(p => IsCancellationTokenParameter(model, p))?.Identifier.ValueText; - - protected override bool TryGetAncestorDeclarationCancellationTokenParameterName(SemanticModel model, SyntaxNode node, out string? parameterName) + protected override bool TryGetAncestorDeclarationCancellationTokenParameterName( + SyntaxNode node, + [NotNullWhen(returnValue: true)] out string? parameterName) { parameterName = null; @@ -38,14 +39,26 @@ protected override bool TryGetAncestorDeclarationCancellationTokenParameterName( if (parameters != null) { - parameterName = GetCancellationTokenName(model, parameters); + parameterName = GetCancellationTokenName(parameters); break; } currentNode = currentNode.Parent; } + // Unexpected CS8752: Parameter 'parameterName' must have a non-null value when exiting with 'true' + // Active issue: https://github.com/dotnet/roslyn/issues/44526 +#pragma warning disable CS8762 return !string.IsNullOrEmpty(parameterName); +#pragma warning restore CS8762 } + + protected override bool IsArgumentNamed(IArgumentOperation argumentOperation) + { + return argumentOperation.Syntax is ArgumentSyntax argumentNode && argumentNode.NameColon != null; + } + + private static string? GetCancellationTokenName(IEnumerable parameters) => + parameters.Last()?.Identifier.ValueText; } } diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Fixer.cs index ab818ec8c8..fee4f01a76 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Fixer.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; using Analyzer.Utilities; @@ -18,17 +19,13 @@ namespace Microsoft.NetCore.Analyzers.Runtime /// public abstract class ForwardCancellationTokenToAsyncMethodsFixer : CodeFixProvider { - protected const string CancellationTokenName = "CancellationToken"; - // Looks for a ct parameter in the ancestor method or function declaration. If one is found, retrieve the name of the parameter. // Returns true if a ct parameter was found and parameterName is not null or empty. Returns false otherwise. - protected abstract bool TryGetAncestorDeclarationCancellationTokenParameterName(SemanticModel model, SyntaxNode node, out string? parameterName); + protected abstract bool TryGetAncestorDeclarationCancellationTokenParameterName( + SyntaxNode node, + [NotNullWhen(returnValue: true)] out string? parameterName); - protected static bool IsCancellationTokenParameter(SemanticModel model, SyntaxNode parameter) - { - return model.GetDeclaredSymbol(parameter) is IParameterSymbol parameterSymbol && - parameterSymbol.Type.Name.Equals(CancellationTokenName, StringComparison.Ordinal); - } + protected abstract bool IsArgumentNamed(IArgumentOperation argumentOperation); public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(ForwardCancellationTokenToAsyncMethodsAnalyzer.RuleId); @@ -48,19 +45,20 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) } SemanticModel model = await doc.GetSemanticModelAsync(ct).ConfigureAwait(false); + if (!(model.GetOperation(node, ct) is IInvocationOperation invocation)) { return; } - if (!TryGetAncestorDeclarationCancellationTokenParameterName(model, node, out string? parameterName)) + if (!TryGetAncestorDeclarationCancellationTokenParameterName(node, out string? parameterName)) { return; } string title = MicrosoftNetCoreAnalyzersResources.ForwardCancellationTokenToAsyncMethodsTitle; - Task createChangedDocument(CancellationToken _) => FixInvocation(doc, root, invocation, parameterName!); + Task createChangedDocument(CancellationToken _) => FixInvocation(doc, root, invocation, parameterName); context.RegisterCodeFix( new MyCodeAction( @@ -70,25 +68,55 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) context.Diagnostics); } - private static Task FixInvocation(Document doc, SyntaxNode root, IInvocationOperation invocation, string cancellationTokenParamenterName) + private Task FixInvocation( + Document doc, + SyntaxNode root, + IInvocationOperation invocation, + string cancellationTokenParameterName) { SyntaxGenerator generator = SyntaxGenerator.GetGenerator(doc); - // Create a new ct argument to pass to the invocation, using the ancestor method parameter name - SyntaxNode cancellationTokenIdentifier = generator.IdentifierName(cancellationTokenParamenterName); - SyntaxNode cancellationTokenNode = generator.Argument(cancellationTokenIdentifier); - // Pass the same arguments and add the ct List newArguments = new List(); - foreach (IArgumentOperation argument in invocation.Arguments) + bool shouldTokenUseName = false; + for (int i = 0; i < invocation.Arguments.Length; i++) { - if (argument.Parameter.Type.Name != CancellationTokenName || !argument.IsImplicit) + IArgumentOperation argument = invocation.Arguments[i]; + if (!argument.Parameter.Type.Name.Equals("CancellationToken", StringComparison.Ordinal)) { - newArguments.Add(argument.Syntax); + if (!argument.IsImplicit) + { + SyntaxNode newArg; + if (IsArgumentNamed(argument)) + { + newArg = generator.Argument(argument.Parameter.Name, argument.Parameter.RefKind, argument.Value.Syntax); + shouldTokenUseName = true; + } + else + { + newArg = argument.Syntax; + } + newArguments.Add(newArg); + } + else + { + shouldTokenUseName = true; + } } } - newArguments.Add(cancellationTokenNode); + // Create and append new ct argument to pass to the invocation, using the ancestor method parameter name + SyntaxNode cancellationTokenIdentifier = generator.IdentifierName(cancellationTokenParameterName); + SyntaxNode cancellationTokenNode; + if (shouldTokenUseName) + { + cancellationTokenNode = generator.Argument(cancellationTokenParameterName, RefKind.None, cancellationTokenIdentifier); + } + else + { + cancellationTokenNode = generator.Argument(cancellationTokenIdentifier); + } + newArguments.Add(cancellationTokenNode); SyntaxNode newInvocation; // The instance is null when calling a static method from another type @@ -100,7 +128,7 @@ private static Task FixInvocation(Document doc, SyntaxNode root, IInvo // The instance is implicit when calling a method from the same type, call the method directly else if (invocation.Instance.IsImplicit) { - newInvocation = invocation.GetInstanceSyntax()!; // GetInstance will return the method name + newInvocation = invocation.GetInstanceSyntax()!; } // Calling a method from an object, we must include the instance variable name else diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.cs index 4278f01c0a..f9ee7d4588 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.cs @@ -16,21 +16,16 @@ namespace Microsoft.NetCore.Analyzers.Runtime /// /// Conditions for positive cases: /// - The containing method signature receives a ct parameter. It can be a method, a nested method, an action or a func. - /// - The invocation is not receiving a ct argument, and... + /// - The invocation method is not receiving a ct argument, and... /// - The invocation method either: - /// - Has no overloads but its current signature receives an optional ct=default, being used right now, or... + /// - Has no overloads but its current signature receives an optional ct=default, currently implicit, or... /// - Has a method overload with the exact same arguments in the same order, plus one ct parameter at the end. /// /// Conditions for negative cases: /// - The containing method signature does not receive a ct parameter. - /// - The invocation is explicitly receiving a ct argument. - /// - The invocation method signature receives a ct but one is already being explicitly passed, or... + /// - The invocation method signature receives a ct and one is already being explicitly passed, or... /// - The invocation method does not have an overload with the exact same arguments that also receives a ct, or... - /// - The invocation method has overloads that receive more than one ct. - /// - /// Future improvements: - /// - Finding an overload with one ct parameter, but not in last position. - /// - Passing a named ct in a different position than default. + /// - The invocation method only has overloads that receive more than one ct. /// [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public sealed class ForwardCancellationTokenToAsyncMethodsAnalyzer : DiagnosticAnalyzer @@ -84,9 +79,16 @@ private void AnalyzeCompilationStart(CompilationStartAnalysisContext context) context.RegisterOperationAction(context => { + if (!(context.ContainingSymbol is IMethodSymbol containingSymbol)) + { + return; + } + IInvocationOperation invocation = (IInvocationOperation)context.Operation; + if (!ShouldAnalyze( invocation, + containingSymbol, cancellationTokenType, out string? cancellationTokenParameterName)) { @@ -100,13 +102,14 @@ private void AnalyzeCompilationStart(CompilationStartAnalysisContext context) private static bool ShouldAnalyze( IInvocationOperation invocation, + IMethodSymbol containingSymbol, INamedTypeSymbol cancellationTokenType, [NotNullWhen(returnValue: true)] out string? cancellationTokenParameterName) { - IMethodSymbol method = invocation.TargetMethod; - cancellationTokenParameterName = null; + IMethodSymbol method = invocation.TargetMethod; + // Check if the invocation has an optional implicit ct or an overload that takes one ct if (!InvocationIgnoresOptionalCancellationToken(invocation, cancellationTokenType) && !MethodHasCancellationTokenOverload(method, cancellationTokenType)) @@ -114,14 +117,8 @@ private static bool ShouldAnalyze( return false; } - // Find the ancestor method that contains this invocation - if (!TryGetAncestorDeclaration(invocation, out IMethodSymbol? methodDeclaration)) - { - return false; - } - // Check if the ancestor method has a ct that we can pass to the invocation - if (!VerifyMethodOnlyHasOneCancellationTokenAsLastArgument(cancellationTokenType, methodDeclaration, out cancellationTokenParameterName)) + if (!VerifyAncestorOnlyHasOneCancellationTokenAsLastArgument(cancellationTokenType, containingSymbol, out cancellationTokenParameterName)) { return false; } @@ -129,61 +126,16 @@ private static bool ShouldAnalyze( return cancellationTokenParameterName != null; } - // Looks for an ancestor that could be a method or function declaration. - private static bool TryGetAncestorDeclaration( - IInvocationOperation invocation, - [NotNullWhen(returnValue: true)] out IMethodSymbol? declaration) - { - declaration = null; - - IOperation currentOperation = invocation.Parent; - while (currentOperation != null) - { - if (currentOperation.Kind == OperationKind.AnonymousFunction && currentOperation is IAnonymousFunctionOperation anonymousFunction) - { - declaration = anonymousFunction.Symbol; - break; - } - else if (currentOperation.Kind == OperationKind.LocalFunction && currentOperation is ILocalFunctionOperation localFunction) - { - declaration = localFunction.Symbol; - break; - } - else if (currentOperation.Kind == OperationKind.MethodBody && currentOperation is IMethodBodyOperation methodBody) - { - if (methodBody.SemanticModel.GetDeclaredSymbol(methodBody.Syntax) is IMethodSymbol method) - { - declaration = method; - break; - } - } - else if (currentOperation.Kind == OperationKind.Block && currentOperation is IBlockOperation methodBaseBody) - { - if (methodBaseBody.SemanticModel.GetDeclaredSymbol(methodBaseBody.Syntax) is IMethodSymbol method) - { - declaration = method; - // There are many kinds of blocks, so only break if we found a method symbol for this block. - // Otherwise, blocks inside anonymous or local functions would not be detected - those would be the parent of the current operation. - break; - } - } - - currentOperation = currentOperation.Parent; - } - - return declaration != null; - } - // Check if the method only takes one ct and is the last parameter in the method signature. // We want to compare the current method signature to any others with the exact same arguments in the exact same order. - private static bool VerifyMethodOnlyHasOneCancellationTokenAsLastArgument( + private static bool VerifyAncestorOnlyHasOneCancellationTokenAsLastArgument( INamedTypeSymbol cancellationTokenType, IMethodSymbol methodDeclaration, [NotNullWhen(returnValue: true)] out string? cancellationTokenParameterName) { if (methodDeclaration.Parameters.Count(x => x.Type.Equals(cancellationTokenType)) == 1 && methodDeclaration.Parameters.Last() is IParameterSymbol lastParameter && - lastParameter.Type.Equals(cancellationTokenType)) + lastParameter.Type.Equals(cancellationTokenType)) // Covers the case when using an alias for ct { cancellationTokenParameterName = lastParameter.Name; return true; @@ -217,7 +169,8 @@ private static bool MethodHasCancellationTokenOverload(IMethodSymbol method, ITy IMethodSymbol? overload = method.ContainingType.GetMembers(method.Name) .OfType() - .FirstOrDefault(methodToCompare => HasSameParametersPlusCancellationToken(cancellationTokenType, method, methodToCompare)); + .FirstOrDefault(methodToCompare => + HasSameParametersPlusCancellationToken(cancellationTokenType, method, methodToCompare)); return overload != null; @@ -226,7 +179,7 @@ static bool HasSameParametersPlusCancellationToken(ITypeSymbol cancellationToken { if (originalMethod.Equals(methodToCompare) || methodToCompare.Parameters.Length != originalMethod.Parameters.Length + 1 || - !methodToCompare.Parameters[methodToCompare.Parameters.Length - 1].Type.Equals(cancellationTokenType)) + !methodToCompare.Parameters[methodToCompare.Parameters.Length - 1].Type.Equals(cancellationTokenType)) // Covers the case when using an alias for ct { return false; } @@ -235,7 +188,7 @@ static bool HasSameParametersPlusCancellationToken(ITypeSymbol cancellationToken { IParameterSymbol? originalParameter = originalMethod.Parameters[i]; IParameterSymbol? comparedParameter = methodToCompare.Parameters[i]; - if (originalParameter == null || comparedParameter == null || !originalParameter.Type.Equals(comparedParameter.Type)) + if (originalParameter == null || comparedParameter == null || !originalParameter.Type.Equals(comparedParameter.Type)) // Covers the case when using an alias for ct { return false; } diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethodsTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethodsTests.cs index 083aeb0de6..4621424c17 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethodsTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethodsTests.cs @@ -862,7 +862,7 @@ void M(CancellationToken ct) } [Fact] - public Task CS_NoDiagnostic_ActionDelegateNoAwait() + public Task CS_Diagnostic_ActionDelegateNoAwait() { string originalCode = @" using System; @@ -1423,6 +1423,166 @@ async void M(TokenAlias ct) return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); } + [Fact] + public Task CS_Diagnostic_Default_WithAllDefaultParametersImplicit() + { + string originalCode = @" +using System.Threading; +using System.Threading.Tasks; +class C +{ + Task M(CancellationToken ct) + { + return [|MethodAsync()|]; + } + Task MethodAsync(int x = 0, bool y = false, CancellationToken c = default) + { + return Task.CompletedTask; + } +} + "; + string fixedCode = @" +using System.Threading; +using System.Threading.Tasks; +class C +{ + Task M(CancellationToken ct) + { + return MethodAsync(c: ct); + } + Task MethodAsync(int x = 0, bool y = false, CancellationToken c = default) + { + return Task.CompletedTask; + } +} + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task CS_Diagnostic_Default_WithSomeDefaultParameters() + { + string originalCode = @" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + await [|MethodAsync(5)|]; + } + Task MethodAsync(int x, bool y = default, CancellationToken ct = default) => Task.CompletedTask; +} + "; + string fixedCode = @" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + await MethodAsync(5, ct: ct); + } + Task MethodAsync(int x, bool y = default, CancellationToken ct = default) => Task.CompletedTask; +} + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task CS_Diagnostic_Default_WithNamedParameters() + { + string originalCode = @" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + await [|MethodAsync(x: 5)|]; + } + Task MethodAsync(int x, bool y = default, CancellationToken ct = default) => Task.CompletedTask; +} + "; + string fixedCode = @" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + await MethodAsync(x: 5, ct: ct); + } + Task MethodAsync(int x, bool y = default, CancellationToken ct = default) => Task.CompletedTask; +} + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task CS_Diagnostic_Default_WithAncestorAliasAndNamedParameters() + { + string originalCode = @" +using System.Threading; +using System.Threading.Tasks; +using TokenAlias = System.Threading.CancellationToken; +class C +{ + async void M(TokenAlias ct) + { + await [|MethodAsync(x: 5)|]; + } + Task MethodAsync(int x, bool y = default, CancellationToken ct = default) => Task.CompletedTask; +} + "; + string fixedCode = @" +using System.Threading; +using System.Threading.Tasks; +using TokenAlias = System.Threading.CancellationToken; +class C +{ + async void M(TokenAlias ct) + { + await MethodAsync(x:5, ct: ct); + } + Task MethodAsync(int x, bool y = default, CancellationToken ct = default) => Task.CompletedTask; +} + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task CS_Diagnostic_Default_WithMethodArgumentAliasAndNamedParameters() + { + string originalCode = @" +using System.Threading; +using System.Threading.Tasks; +using TokenAlias = System.Threading.CancellationToken; +class C +{ + async void M(CancellationToken ct) + { + await [|MethodAsync(x: 5)|]; + } + Task MethodAsync(int x, bool y = default, TokenAlias ct = default) => Task.CompletedTask; +} + "; + string fixedCode = @" +using System.Threading; +using System.Threading.Tasks; +using TokenAlias = System.Threading.CancellationToken; +class C +{ + async void M(CancellationToken ct) + { + await MethodAsync(x: 5, ct: ct); + } + Task MethodAsync(int x, bool y = default, TokenAlias ct = default) => Task.CompletedTask; +} + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + #endregion #region No Diagnostic - VB @@ -1675,7 +1835,6 @@ End Class return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); } - [Fact] public Task VB_Diagnostic_Class_TokenDefault_WithConfigureAwait() { @@ -2764,6 +2923,191 @@ End Class return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); } + [Fact] + public Task VB_Diagnostic_Default_WithAllDefaultParametersImplicit() + { + string originalCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await [|MethodAsync()|] + End Sub + Private Function MethodAsync(ByVal Optional x As Integer = 0, ByVal Optional y As Boolean = False, ByVal Optional ct As CancellationToken = Nothing) As Task + Return Task.CompletedTask + End Function +End Class + "; + string fixedCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await MethodAsync(ct:=ct) + End Sub + Private Function MethodAsync(ByVal Optional x As Integer = 0, ByVal Optional y As Boolean = False, ByVal Optional ct As CancellationToken = Nothing) As Task + Return Task.CompletedTask + End Function +End Class + "; + return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task VB_Diagnostic_Default_WithSomeDefaultParameters() + { + string originalCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await [|MethodAsync(5)|] + End Sub + Private Function MethodAsync(ByVal x As Integer, ByVal Optional y As Boolean = false, ByVal Optional ct As CancellationToken = Nothing) As Task + Return Task.CompletedTask + End Function +End Class + "; + string fixedCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await MethodAsync(5, ct:=ct) + End Sub + Private Function MethodAsync(ByVal x As Integer, ByVal Optional y As Boolean = false, ByVal Optional ct As CancellationToken = Nothing) As Task + Return Task.CompletedTask + End Function +End Class + "; + return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task VB_Diagnostic_Default_WithNamedParameters() + { + string originalCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await [|MethodAsync(x:=5)|] + End Sub + Private Function MethodAsync(ByVal x As Integer, ByVal Optional y As Boolean = false, ByVal Optional ct As CancellationToken = Nothing) As Task + Return Task.CompletedTask + End Function +End Class + "; + string fixedCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await MethodAsync(x:=5, ct:=ct) + End Sub + Private Function MethodAsync(ByVal x As Integer, ByVal Optional y As Boolean = false, ByVal Optional ct As CancellationToken = Nothing) As Task + Return Task.CompletedTask + End Function +End Class + "; + return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task VB_Diagnostic_Default_WithNamedParametersUnordered() + { + string originalCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await [|MethodAsync(y:=true, x:=5)|] + End Sub + Private Function MethodAsync(ByVal x As Integer, ByVal Optional y As Boolean = false, ByVal Optional ct As CancellationToken = Nothing) As Task + Return Task.CompletedTask + End Function +End Class + "; + // Notice the parameters get reordered in their official position + string fixedCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await MethodAsync(x:=5, y:=true, ct:=ct) + End Sub + Private Function MethodAsync(ByVal x As Integer, ByVal Optional y As Boolean = false, ByVal Optional ct As CancellationToken = Nothing) As Task + Return Task.CompletedTask + End Function +End Class + "; + return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task VB_Diagnostic_Default_WithAncestorAliasAndNamedParameters() + { + string originalCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Imports TokenAlias = System.Threading.CancellationToken +Class C + Private Async Sub M(ByVal ct As TokenAlias) + Await [|MethodAsync(x:=5)|] + End Sub + Private Function MethodAsync(ByVal x As Integer, ByVal Optional y As Boolean = false, ByVal Optional ct As CancellationToken = Nothing) As Task + Return Task.CompletedTask + End Function +End Class + "; + string fixedCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Imports TokenAlias = System.Threading.CancellationToken +Class C + Private Async Sub M(ByVal ct As TokenAlias) + Await MethodAsync(x:=5, ct:=ct) + End Sub + Private Function MethodAsync(ByVal x As Integer, ByVal Optional y As Boolean = false, ByVal Optional ct As CancellationToken = Nothing) As Task + Return Task.CompletedTask + End Function +End Class + "; + return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task VB_Diagnostic_Default_WithMethodArgumentAliasAndNamedParameters() + { + string originalCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Imports TokenAlias = System.Threading.CancellationToken +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await [|MethodAsync(x:=5)|] + End Sub + Private Function MethodAsync(ByVal x As Integer, ByVal Optional y As Boolean = false, ByVal Optional ct As TokenAlias = Nothing) As Task + Return Task.CompletedTask + End Function +End Class + "; + string fixedCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Imports TokenAlias = System.Threading.CancellationToken +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await MethodAsync(x:=5, ct:=ct) + End Sub + Private Function MethodAsync(ByVal x As Integer, ByVal Optional y As Boolean = false, ByVal Optional ct As TokenAlias = Nothing) As Task + Return Task.CompletedTask + End Function +End Class + "; + return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); + } + #endregion } } \ No newline at end of file diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToAsyncMethods.Fixer.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToAsyncMethods.Fixer.vb index a39be25443..746ae003bd 100644 --- a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToAsyncMethods.Fixer.vb +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToAsyncMethods.Fixer.vb @@ -2,6 +2,7 @@ Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.CodeFixes +Imports Microsoft.CodeAnalysis.Operations Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Imports Microsoft.NetCore.Analyzers.Runtime @@ -12,11 +13,7 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime Inherits ForwardCancellationTokenToAsyncMethodsFixer - Private Shared Function GetCancellationTokenName(model As SemanticModel, parameters As IEnumerable(Of ParameterSyntax)) As String - Return parameters.FirstOrDefault(Function(p) IsCancellationTokenParameter(model, p))?.Identifier.Identifier.ValueText - End Function - - Protected Overrides Function TryGetAncestorDeclarationCancellationTokenParameterName(model As SemanticModel, node As SyntaxNode, ByRef parameterName As String) As Boolean + Protected Overrides Function TryGetAncestorDeclarationCancellationTokenParameterName(node As SyntaxNode, ByRef parameterName As String) As Boolean parameterName = Nothing @@ -40,7 +37,7 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime End If If parameters IsNot Nothing Then - parameterName = GetCancellationTokenName(model, parameters) + parameterName = GetCancellationTokenName(parameters) Exit While End If @@ -51,6 +48,15 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime Return Not String.IsNullOrEmpty(parameterName) End Function + Protected Overrides Function IsArgumentNamed(argumentOperation As IArgumentOperation) As Boolean + Dim argument As SimpleArgumentSyntax = TryCast(argumentOperation.Syntax, SimpleArgumentSyntax) + Return argument IsNot Nothing AndAlso argument.NameColonEquals IsNot Nothing + End Function + + Private Shared Function GetCancellationTokenName(parameters As IEnumerable(Of ParameterSyntax)) As String + Return parameters.Last()?.Identifier.Identifier.ValueText + End Function + End Class End Namespace From 981095fa957942f045bc5627e9418361b712f5fd Mon Sep 17 00:00:00 2001 From: carlossanlop Date: Wed, 27 May 2020 16:47:53 -0700 Subject: [PATCH 07/20] Address named argument problem in C#, add two cases with diagnostics but no fix. --- ...rdCancellationTokenToAsyncMethods.Fixer.cs | 23 +- .../ForwardCancellationTokenToAsyncMethods.cs | 11 +- ...ardCancellationTokenToAsyncMethodsTests.cs | 265 ++++++++++++++---- 3 files changed, 238 insertions(+), 61 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Fixer.cs index fee4f01a76..87904a9157 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Fixer.cs @@ -15,8 +15,6 @@ namespace Microsoft.NetCore.Analyzers.Runtime { - /// - /// public abstract class ForwardCancellationTokenToAsyncMethodsFixer : CodeFixProvider { // Looks for a ct parameter in the ancestor method or function declaration. If one is found, retrieve the name of the parameter. @@ -79,9 +77,15 @@ private Task FixInvocation( // Pass the same arguments and add the ct List newArguments = new List(); bool shouldTokenUseName = false; + string paramName = string.Empty; + + // In C#, invocation.Arguments contains the arguments in the order passed by the user + // In VB, invocation.Arguments contains the arguments in the official parameter order for (int i = 0; i < invocation.Arguments.Length; i++) { IArgumentOperation argument = invocation.Arguments[i]; + + // The type name is detected even if using an alias if (!argument.Parameter.Type.Name.Equals("CancellationToken", StringComparison.Ordinal)) { if (!argument.IsImplicit) @@ -103,6 +107,12 @@ private Task FixInvocation( shouldTokenUseName = true; } } + else + { + // Only reachable if the current method is the one that contains the ct + // Won't be reached if it's an overload that contains the paramName + paramName = argument.Parameter.Name; + } } // Create and append new ct argument to pass to the invocation, using the ancestor method parameter name @@ -110,7 +120,14 @@ private Task FixInvocation( SyntaxNode cancellationTokenNode; if (shouldTokenUseName) { - cancellationTokenNode = generator.Argument(cancellationTokenParameterName, RefKind.None, cancellationTokenIdentifier); + // If the paramName is unknown at this point, it's because an overload contains the ct parameter + // and since it cannot be obtained, no fix will be provided + if (string.IsNullOrEmpty(paramName)) + { + return Task.FromResult(doc); + } + + cancellationTokenNode = generator.Argument(paramName, RefKind.None, cancellationTokenIdentifier); } else { diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.cs index f9ee7d4588..be1ccb947a 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.cs @@ -152,12 +152,13 @@ private static bool InvocationIgnoresOptionalCancellationToken(IInvocationOperat IMethodSymbol method = invocation.TargetMethod; if (method.Parameters.Length != 0 && method.Parameters[method.Parameters.Length - 1] is IParameterSymbol lastParameter && - lastParameter.Type.Equals(cancellationTokenType)) + lastParameter.Type.Equals(cancellationTokenType) && + lastParameter.IsOptional) // Has a default value being used { - // If the ct parameter has a default value, return true if a value is not being explicitly passed in the invocation - return lastParameter.IsOptional && // Has a default value - invocation.Arguments[invocation.Arguments.Length - 1] is IArgumentOperation lastArgument && - lastArgument.IsImplicit; // The default value is being used + // Find out if the ct argument is using the default value + return invocation.Arguments.Any(x => + x.Parameter.Type.Equals(cancellationTokenType) && + x.ArgumentKind == ArgumentKind.DefaultValue); // The default value is being used } return false; diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethodsTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethodsTests.cs index 4621424c17..72daea9020 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethodsTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethodsTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Testing; using Xunit; using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< Microsoft.NetCore.Analyzers.Runtime.ForwardCancellationTokenToAsyncMethodsAnalyzer, @@ -229,9 +230,69 @@ async void M(CancellationToken ct) "); } + [Fact] + public Task CS_NoDiagnostic_NamedTokenUnordered() + { + return VerifyCS.VerifyAnalyzerAsync(@" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + await MethodAsync(s: ""Hello world"", c: CancellationToken.None, x: 5); + } + Task MethodAsync(int x, string s, CancellationToken c) => Task.CompletedTask; +} + "); + } + + [Fact] + public Task CS_NoDiagnostic_Overload_NamedTokenUnordered() + { + return VerifyCS.VerifyAnalyzerAsync(@" +using System.Threading; +using System.Threading.Tasks; +class C +{ + async void M(CancellationToken ct) + { + await MethodAsync(s: ""Hello world"", c: CancellationToken.None, x: 5); + } + Task MethodAsync(int x, string s) => Task.CompletedTask; + Task MethodAsync(int x, string s, CancellationToken c) => Task.CompletedTask; +} + "); + } + #endregion - #region Diagnostic = C# + #region Diagnostics with no fix = C# + + [Fact] + public Task CS_AnalyzerOnlyDiagnostic_OverloadWithNamedParametersUnordered() + { + // This is a special case that will get a diagnostic but will not get a fix + // because the fixer does not currently have a way to know the overload's ct parameter name + // If the ct argument got added at the end without a name, compilation would fail + return VerifyCS.VerifyAnalyzerAsync(@" +using System.Threading; +using System.Threading.Tasks; +class C +{ + Task M(CancellationToken ct) + { + return [|MethodAsync(z: ""Hello world"", x: 5, y: true)|]; + } + Task MethodAsync(int x, bool y = default, string z = """") => Task.CompletedTask; + Task MethodAsync(int x, bool y = default, string z = """", CancellationToken c = default) => Task.CompletedTask; +} + "); + } + + #endregion + + #region Diagnostics with Fix = C# [Fact] public Task CS_Diagnostic_Class_TokenDefault() @@ -1471,7 +1532,7 @@ async void M(CancellationToken ct) { await [|MethodAsync(5)|]; } - Task MethodAsync(int x, bool y = default, CancellationToken ct = default) => Task.CompletedTask; + Task MethodAsync(int x, bool y = default, CancellationToken c = default) => Task.CompletedTask; } "; string fixedCode = @" @@ -1481,9 +1542,9 @@ class C { async void M(CancellationToken ct) { - await MethodAsync(5, ct: ct); + await MethodAsync(5, c: ct); } - Task MethodAsync(int x, bool y = default, CancellationToken ct = default) => Task.CompletedTask; + Task MethodAsync(int x, bool y = default, CancellationToken c = default) => Task.CompletedTask; } "; return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); @@ -1501,7 +1562,7 @@ async void M(CancellationToken ct) { await [|MethodAsync(x: 5)|]; } - Task MethodAsync(int x, bool y = default, CancellationToken ct = default) => Task.CompletedTask; + Task MethodAsync(int x, bool y = default, CancellationToken c = default) => Task.CompletedTask; } "; string fixedCode = @" @@ -1511,9 +1572,9 @@ class C { async void M(CancellationToken ct) { - await MethodAsync(x: 5, ct: ct); + await MethodAsync(x: 5, c: ct); } - Task MethodAsync(int x, bool y = default, CancellationToken ct = default) => Task.CompletedTask; + Task MethodAsync(int x, bool y = default, CancellationToken c = default) => Task.CompletedTask; } "; return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); @@ -1532,7 +1593,7 @@ async void M(TokenAlias ct) { await [|MethodAsync(x: 5)|]; } - Task MethodAsync(int x, bool y = default, CancellationToken ct = default) => Task.CompletedTask; + Task MethodAsync(int x, bool y = default, CancellationToken c = default) => Task.CompletedTask; } "; string fixedCode = @" @@ -1543,9 +1604,9 @@ class C { async void M(TokenAlias ct) { - await MethodAsync(x:5, ct: ct); + await MethodAsync(x: 5, c: ct); } - Task MethodAsync(int x, bool y = default, CancellationToken ct = default) => Task.CompletedTask; + Task MethodAsync(int x, bool y = default, CancellationToken c = default) => Task.CompletedTask; } "; return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); @@ -1564,7 +1625,7 @@ async void M(CancellationToken ct) { await [|MethodAsync(x: 5)|]; } - Task MethodAsync(int x, bool y = default, TokenAlias ct = default) => Task.CompletedTask; + Task MethodAsync(int x, bool y = default, TokenAlias c = default) => Task.CompletedTask; } "; string fixedCode = @" @@ -1575,9 +1636,40 @@ class C { async void M(CancellationToken ct) { - await MethodAsync(x: 5, ct: ct); + await MethodAsync(x: 5, c: ct); + } + Task MethodAsync(int x, bool y = default, TokenAlias c = default) => Task.CompletedTask; +} + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task CS_Diagnostic_Default_WithNamedParametersUnordered() + { + string originalCode = @" +using System.Threading; +using System.Threading.Tasks; +class C +{ + Task M(CancellationToken ct) + { + return [|MethodAsync(z: ""Hello world"", x: 5, y: true)|]; } - Task MethodAsync(int x, bool y = default, TokenAlias ct = default) => Task.CompletedTask; + Task MethodAsync(int x, bool y = default, string z = """", CancellationToken c = default) => Task.CompletedTask; +} + "; + // Notice the parameters do NOT get reordered to their official position + string fixedCode = @" +using System.Threading; +using System.Threading.Tasks; +class C +{ + Task M(CancellationToken ct) + { + return MethodAsync(z: ""Hello world"", x: 5, y: true, c: ct); + } + Task MethodAsync(int x, bool y = default, string z = """", CancellationToken c = default) => Task.CompletedTask; } "; return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); @@ -1801,9 +1893,76 @@ End Class "); } + + [Fact] + public Task VB_NoDiagnostic_NamedTokenUnordered() + { + return VerifyVB.VerifyAnalyzerAsync(@" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await MethodAsync(s:=""Hello, world"", c:=CancellationToken.None, x:=5) + End Sub + Private Function MethodAsync(ByVal x As Integer, ByVal s As String, ByVal c As CancellationToken) As Task + Return Task.CompletedTask + End Function +End Class + "); + } + + [Fact] + public Task VB_NoDiagnostic_Overload_NamedTokenUnordered() + { + return VerifyVB.VerifyAnalyzerAsync(@" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await MethodAsync(s:=""Hello, world"", c:=CancellationToken.None, x:=5) + End Sub + Private Function MethodAsync(ByVal x As Integer, ByVal s As String) As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal x As Integer, ByVal s As String, ByVal c As CancellationToken) As Task + Return Task.CompletedTask + End Function +End Class + "); + } + #endregion - #region Diagnostic = VB + #region Diagnostics with no fix = VB + + [Fact] + public Task VB_AnalyzerOnlyDiagnostic_OverloadWithNamedParametersUnordered() + { + // This is a special case that will get a diagnostic but will not get a fix + // because the fixer does not currently have a way to know the overload's ct parameter name + // VB arguments get reordered in their official parameter order, so we could add the ct argument at the end + // and VB would compile successfully, but that would require separate VB handling in the fixer, so instead + // the C# and VB behavior will remain the same + return VerifyVB.VerifyAnalyzerAsync(@" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Function M(ByVal ct As CancellationToken) As Task + Return [|MethodAsync(z:=""Hello world"", x:=5, y:=true)|] + End Function + Private Function MethodAsync(ByVal x As Integer, ByVal Optional y As Boolean = false, ByVal Optional z As String = """") As Task + Return Task.CompletedTask + End Function + Private Function MethodAsync(ByVal x As Integer, ByVal Optional y As Boolean = false, ByVal Optional z As String = """", ByVal Optional c As CancellationToken = Nothing) As Task + Return Task.CompletedTask + End Function +End Class + "); + } + + #endregion + + #region Diagnostics with fix = VB [Fact] public Task VB_Diagnostic_Class_TokenDefault() @@ -2930,10 +3089,10 @@ public Task VB_Diagnostic_Default_WithAllDefaultParametersImplicit() Imports System.Threading Imports System.Threading.Tasks Class C - Private Async Sub M(ByVal ct As CancellationToken) - Await [|MethodAsync()|] - End Sub - Private Function MethodAsync(ByVal Optional x As Integer = 0, ByVal Optional y As Boolean = False, ByVal Optional ct As CancellationToken = Nothing) As Task + Private Function M(ByVal ct As CancellationToken) As Task + Return [|MethodAsync()|] + End Function + Private Function MethodAsync(ByVal Optional x As Integer = 0, ByVal Optional y As Boolean = False, ByVal Optional c As CancellationToken = Nothing) As Task Return Task.CompletedTask End Function End Class @@ -2942,10 +3101,10 @@ End Class Imports System.Threading Imports System.Threading.Tasks Class C - Private Async Sub M(ByVal ct As CancellationToken) - Await MethodAsync(ct:=ct) - End Sub - Private Function MethodAsync(ByVal Optional x As Integer = 0, ByVal Optional y As Boolean = False, ByVal Optional ct As CancellationToken = Nothing) As Task + Private Function M(ByVal ct As CancellationToken) As Task + Return MethodAsync(c:=ct) + End Function + Private Function MethodAsync(ByVal Optional x As Integer = 0, ByVal Optional y As Boolean = False, ByVal Optional c As CancellationToken = Nothing) As Task Return Task.CompletedTask End Function End Class @@ -2963,7 +3122,7 @@ Class C Private Async Sub M(ByVal ct As CancellationToken) Await [|MethodAsync(5)|] End Sub - Private Function MethodAsync(ByVal x As Integer, ByVal Optional y As Boolean = false, ByVal Optional ct As CancellationToken = Nothing) As Task + Private Function MethodAsync(ByVal x As Integer, ByVal Optional y As Boolean = false, ByVal Optional c As CancellationToken = Nothing) As Task Return Task.CompletedTask End Function End Class @@ -2973,9 +3132,9 @@ Imports System.Threading Imports System.Threading.Tasks Class C Private Async Sub M(ByVal ct As CancellationToken) - Await MethodAsync(5, ct:=ct) + Await MethodAsync(5, c:=ct) End Sub - Private Function MethodAsync(ByVal x As Integer, ByVal Optional y As Boolean = false, ByVal Optional ct As CancellationToken = Nothing) As Task + Private Function MethodAsync(ByVal x As Integer, ByVal Optional y As Boolean = false, ByVal Optional c As CancellationToken = Nothing) As Task Return Task.CompletedTask End Function End Class @@ -2993,7 +3152,7 @@ Class C Private Async Sub M(ByVal ct As CancellationToken) Await [|MethodAsync(x:=5)|] End Sub - Private Function MethodAsync(ByVal x As Integer, ByVal Optional y As Boolean = false, ByVal Optional ct As CancellationToken = Nothing) As Task + Private Function MethodAsync(ByVal x As Integer, ByVal Optional y As Boolean = false, ByVal Optional c As CancellationToken = Nothing) As Task Return Task.CompletedTask End Function End Class @@ -3003,9 +3162,9 @@ Imports System.Threading Imports System.Threading.Tasks Class C Private Async Sub M(ByVal ct As CancellationToken) - Await MethodAsync(x:=5, ct:=ct) + Await MethodAsync(x:=5, c:=ct) End Sub - Private Function MethodAsync(ByVal x As Integer, ByVal Optional y As Boolean = false, ByVal Optional ct As CancellationToken = Nothing) As Task + Private Function MethodAsync(ByVal x As Integer, ByVal Optional y As Boolean = false, ByVal Optional c As CancellationToken = Nothing) As Task Return Task.CompletedTask End Function End Class @@ -3014,29 +3173,30 @@ End Class } [Fact] - public Task VB_Diagnostic_Default_WithNamedParametersUnordered() + public Task VB_Diagnostic_Default_WithAncestorAliasAndNamedParameters() { string originalCode = @" Imports System.Threading Imports System.Threading.Tasks +Imports TokenAlias = System.Threading.CancellationToken Class C - Private Async Sub M(ByVal ct As CancellationToken) - Await [|MethodAsync(y:=true, x:=5)|] + Private Async Sub M(ByVal ct As TokenAlias) + Await [|MethodAsync(x:=5)|] End Sub - Private Function MethodAsync(ByVal x As Integer, ByVal Optional y As Boolean = false, ByVal Optional ct As CancellationToken = Nothing) As Task + Private Function MethodAsync(ByVal x As Integer, ByVal Optional y As Boolean = false, ByVal Optional c As CancellationToken = Nothing) As Task Return Task.CompletedTask End Function End Class "; - // Notice the parameters get reordered in their official position string fixedCode = @" Imports System.Threading Imports System.Threading.Tasks +Imports TokenAlias = System.Threading.CancellationToken Class C - Private Async Sub M(ByVal ct As CancellationToken) - Await MethodAsync(x:=5, y:=true, ct:=ct) + Private Async Sub M(ByVal ct As TokenAlias) + Await MethodAsync(x:=5, c:=ct) End Sub - Private Function MethodAsync(ByVal x As Integer, ByVal Optional y As Boolean = false, ByVal Optional ct As CancellationToken = Nothing) As Task + Private Function MethodAsync(ByVal x As Integer, ByVal Optional y As Boolean = false, ByVal Optional c As CancellationToken = Nothing) As Task Return Task.CompletedTask End Function End Class @@ -3045,17 +3205,17 @@ End Class } [Fact] - public Task VB_Diagnostic_Default_WithAncestorAliasAndNamedParameters() + public Task VB_Diagnostic_Default_WithMethodArgumentAliasAndNamedParameters() { string originalCode = @" Imports System.Threading Imports System.Threading.Tasks Imports TokenAlias = System.Threading.CancellationToken Class C - Private Async Sub M(ByVal ct As TokenAlias) + Private Async Sub M(ByVal ct As CancellationToken) Await [|MethodAsync(x:=5)|] End Sub - Private Function MethodAsync(ByVal x As Integer, ByVal Optional y As Boolean = false, ByVal Optional ct As CancellationToken = Nothing) As Task + Private Function MethodAsync(ByVal x As Integer, ByVal Optional y As Boolean = false, ByVal Optional c As TokenAlias = Nothing) As Task Return Task.CompletedTask End Function End Class @@ -3065,10 +3225,10 @@ Imports System.Threading Imports System.Threading.Tasks Imports TokenAlias = System.Threading.CancellationToken Class C - Private Async Sub M(ByVal ct As TokenAlias) - Await MethodAsync(x:=5, ct:=ct) + Private Async Sub M(ByVal ct As CancellationToken) + Await MethodAsync(x:=5, c:=ct) End Sub - Private Function MethodAsync(ByVal x As Integer, ByVal Optional y As Boolean = false, ByVal Optional ct As CancellationToken = Nothing) As Task + Private Function MethodAsync(ByVal x As Integer, ByVal Optional y As Boolean = false, ByVal Optional c As TokenAlias = Nothing) As Task Return Task.CompletedTask End Function End Class @@ -3077,30 +3237,29 @@ End Class } [Fact] - public Task VB_Diagnostic_Default_WithMethodArgumentAliasAndNamedParameters() + public Task VB_Diagnostic_Default_WithNamedParametersUnordered() { string originalCode = @" Imports System.Threading Imports System.Threading.Tasks -Imports TokenAlias = System.Threading.CancellationToken Class C - Private Async Sub M(ByVal ct As CancellationToken) - Await [|MethodAsync(x:=5)|] - End Sub - Private Function MethodAsync(ByVal x As Integer, ByVal Optional y As Boolean = false, ByVal Optional ct As TokenAlias = Nothing) As Task + Private Function M(ByVal ct As CancellationToken) As Task + Return [|MethodAsync(z:=""Hello world"", x:=5, y:=true)|] + End Function + Private Function MethodAsync(ByVal x As Integer, ByVal Optional y As Boolean = false, ByVal Optional z As String = """", ByVal Optional c As CancellationToken = Nothing) As Task Return Task.CompletedTask End Function End Class "; + // Notice the parameters get reordered to their official position string fixedCode = @" Imports System.Threading Imports System.Threading.Tasks -Imports TokenAlias = System.Threading.CancellationToken Class C - Private Async Sub M(ByVal ct As CancellationToken) - Await MethodAsync(x:=5, ct:=ct) - End Sub - Private Function MethodAsync(ByVal x As Integer, ByVal Optional y As Boolean = false, ByVal Optional ct As TokenAlias = Nothing) As Task + Private Function M(ByVal ct As CancellationToken) As Task + Return MethodAsync(x:=5, y:=true, z:=""Hello world"", c:=ct) + End Function + Private Function MethodAsync(ByVal x As Integer, ByVal Optional y As Boolean = false, ByVal Optional z As String = """", ByVal Optional c As CancellationToken = Nothing) As Task Return Task.CompletedTask End Function End Class From 9b867200cfe3cb907c09e97c23df038228026af4 Mon Sep 17 00:00:00 2001 From: carlossanlop Date: Wed, 27 May 2020 19:39:03 -0700 Subject: [PATCH 08/20] Offer diagnostic only on the invocation expression name. --- ...ancellationTokenToAsyncMethods.Analyzer.cs | 22 +++ ...ncellationTokenToAsyncMethods.Analyzer.cs} | 13 +- ...rdCancellationTokenToAsyncMethods.Fixer.cs | 6 +- ...ardCancellationTokenToAsyncMethodsTests.cs | 178 +++++++++--------- ...ancellationTokenToAsyncMethods.Analyzer.vb | 32 ++++ 5 files changed, 159 insertions(+), 92 deletions(-) create mode 100644 src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToAsyncMethods.Analyzer.cs rename src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/{ForwardCancellationTokenToAsyncMethods.cs => ForwardCancellationTokenToAsyncMethods.Analyzer.cs} (93%) create mode 100644 src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToAsyncMethods.Analyzer.vb diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToAsyncMethods.Analyzer.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToAsyncMethods.Analyzer.cs new file mode 100644 index 0000000000..374e13dc0a --- /dev/null +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToAsyncMethods.Analyzer.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.NetCore.Analyzers.Runtime; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Microsoft.NetCore.CSharp.Analyzers.Runtime +{ + [ExportCodeFixProvider(LanguageNames.CSharp)] + public sealed class CSharpForwardCancellationTokenToAsyncMethodsAnalyzer : ForwardCancellationTokenToAsyncMethodsAnalyzer + { + protected override SyntaxNode? GetMethodNameNode(SyntaxNode invocationNode) + { + if (invocationNode is InvocationExpressionSyntax invocationExpression) + { + return invocationExpression.Expression; + } + return null; + } + } +} diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Analyzer.cs similarity index 93% rename from src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.cs rename to src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Analyzer.cs index be1ccb947a..2281d66f03 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Analyzer.cs @@ -28,10 +28,12 @@ namespace Microsoft.NetCore.Analyzers.Runtime /// - The invocation method only has overloads that receive more than one ct. /// [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] - public sealed class ForwardCancellationTokenToAsyncMethodsAnalyzer : DiagnosticAnalyzer + public abstract class ForwardCancellationTokenToAsyncMethodsAnalyzer : DiagnosticAnalyzer { internal const string RuleId = "CA2016"; + protected abstract SyntaxNode? GetMethodNameNode(SyntaxNode invocationNode); + private static readonly LocalizableString s_localizableDescription = new LocalizableResourceString( nameof(MicrosoftNetCoreAnalyzersResources.ForwardCancellationTokenToAsyncMethodsDescription), MicrosoftNetCoreAnalyzersResources.ResourceManager, @@ -95,7 +97,12 @@ private void AnalyzeCompilationStart(CompilationStartAnalysisContext context) return; } - context.ReportDiagnostic(context.Operation.CreateDiagnostic(ForwardCancellationTokenToAsyncMethodsRule, cancellationTokenParameterName, invocation.TargetMethod.Name)); + // Only underline the method name, not the whole invocation + SyntaxNode? expressionNode = GetMethodNameNode(context.Operation.Syntax); + if (expressionNode != null) + { + context.ReportDiagnostic(expressionNode.CreateDiagnostic(ForwardCancellationTokenToAsyncMethodsRule, cancellationTokenParameterName, invocation.TargetMethod.Name)); + } }, OperationKind.Invocation); } @@ -123,7 +130,7 @@ private static bool ShouldAnalyze( return false; } - return cancellationTokenParameterName != null; + return true; } // Check if the method only takes one ct and is the last parameter in the method signature. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Fixer.cs index 87904a9157..6e901a1a91 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Fixer.cs @@ -44,7 +44,8 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) SemanticModel model = await doc.GetSemanticModelAsync(ct).ConfigureAwait(false); - if (!(model.GetOperation(node, ct) is IInvocationOperation invocation)) + // The analyzer created the diagnostic on the IdentifierNameSyntax, and the parent is the actual invocation + if (!(model.GetOperation(node.Parent, ct) is IInvocationOperation invocation)) { return; } @@ -121,7 +122,8 @@ private Task FixInvocation( if (shouldTokenUseName) { // If the paramName is unknown at this point, it's because an overload contains the ct parameter - // and since it cannot be obtained, no fix will be provided + // and since it cannot be obtained, no fix will be provided or else CA8323 shows up: + // CA8323: Named argument 'argName' is used out-of-position but is followed by an unnamed argument if (string.IsNullOrEmpty(paramName)) { return Task.FromResult(doc); diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethodsTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethodsTests.cs index 72daea9020..60cdaa42b9 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethodsTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethodsTests.cs @@ -1,13 +1,14 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Testing; using Xunit; using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< - Microsoft.NetCore.Analyzers.Runtime.ForwardCancellationTokenToAsyncMethodsAnalyzer, + Microsoft.NetCore.CSharp.Analyzers.Runtime.CSharpForwardCancellationTokenToAsyncMethodsAnalyzer, Microsoft.NetCore.CSharp.Analyzers.Runtime.CSharpForwardCancellationTokenToAsyncMethodsFixer>; using VerifyVB = Test.Utilities.VisualBasicCodeFixVerifier< - Microsoft.NetCore.Analyzers.Runtime.ForwardCancellationTokenToAsyncMethodsAnalyzer, + Microsoft.NetCore.VisualBasic.Analyzers.Runtime.BasicForwardCancellationTokenToAsyncMethodsAnalyzer, Microsoft.NetCore.VisualBasic.Analyzers.Runtime.BasicForwardCancellationTokenToAsyncMethodsFixer>; namespace Microsoft.NetCore.Analyzers.Runtime.UnitTests @@ -274,20 +275,22 @@ public Task CS_AnalyzerOnlyDiagnostic_OverloadWithNamedParametersUnordered() { // This is a special case that will get a diagnostic but will not get a fix // because the fixer does not currently have a way to know the overload's ct parameter name - // If the ct argument got added at the end without a name, compilation would fail - return VerifyCS.VerifyAnalyzerAsync(@" + // If the ct argument got added at the end without a name, compilation would fail with: + // CA8323: Named argument 'z' is used out-of-position but is followed by an unnamed argument + string originalCode = @" using System.Threading; using System.Threading.Tasks; class C { Task M(CancellationToken ct) { - return [|MethodAsync(z: ""Hello world"", x: 5, y: true)|]; + return [|MethodAsync|](z: ""Hello world"", x: 5, y: true); } Task MethodAsync(int x, bool y = default, string z = """") => Task.CompletedTask; Task MethodAsync(int x, bool y = default, string z = """", CancellationToken c = default) => Task.CompletedTask; } - "); + "; + return VerifyCS.VerifyAnalyzerAsync(originalCode); } #endregion @@ -304,7 +307,7 @@ class C { async void M(CancellationToken ct) { - await [|MethodAsync()|]; + await [|MethodAsync|](); } Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; } @@ -334,7 +337,7 @@ class C { async void M(CancellationToken ct) { - await [|MethodAsync()|].ConfigureAwait(false); + await [|MethodAsync|]().ConfigureAwait(false); } Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; } @@ -364,7 +367,7 @@ class C { void M(CancellationToken ct) { - [|MethodAsync()|]; + [|MethodAsync|](); } Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; } @@ -394,7 +397,7 @@ class C { void M(CancellationToken ct) { - Task t = [|MethodAsync()|]; + Task t = [|MethodAsync|](); } Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; } @@ -424,7 +427,7 @@ class C { async void M(CancellationToken ct) { - await [|MethodAsync()|]; + await [|MethodAsync|](); } static Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; } @@ -454,7 +457,7 @@ class C { async void M(CancellationToken ct) { - await [|MethodAsync()|].ConfigureAwait(false); + await [|MethodAsync|]().ConfigureAwait(false); } static Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; } @@ -485,7 +488,7 @@ class C async void M(CancellationToken ct) { O o = new O(); - await [|o.MethodAsync()|]; + await [|o.MethodAsync|](); } } class O @@ -523,7 +526,7 @@ class C async void M(CancellationToken ct) { O o = new O(); - await [|o.MethodAsync()|]; + await [|o.MethodAsync|](); } } class O @@ -562,7 +565,7 @@ class C { async void M(CancellationToken ct) { - await [|O.MethodAsync()|]; + await [|O.MethodAsync|](); } } class O @@ -598,7 +601,7 @@ class C { async void M(CancellationToken ct) { - await [|O.MethodAsync()|]; + await [|O.MethodAsync|](); } } class O @@ -636,7 +639,7 @@ struct S { async void M(CancellationToken ct) { - await [|MethodAsync()|]; + await [|MethodAsync|](); } Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; } @@ -666,7 +669,7 @@ struct S { async void M(CancellationToken ct) { - await [|MethodAsync()|].ConfigureAwait(false); + await [|MethodAsync|]().ConfigureAwait(false); } Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; } @@ -696,7 +699,7 @@ class C { async void M(CancellationToken ct) { - await [|MethodAsync()|]; + await [|MethodAsync|](); } Task MethodAsync() => Task.CompletedTask; Task MethodAsync(CancellationToken c) => Task.CompletedTask; @@ -728,7 +731,7 @@ class C { async void M(CancellationToken ct) { - await [|MethodAsync()|]; + await [|MethodAsync|](); } Task MethodAsync() => Task.CompletedTask; Task MethodAsync(CancellationToken c) => Task.CompletedTask; @@ -760,7 +763,7 @@ class C { async void M(CancellationToken ct) { - await [|MethodAsync()|]; + await [|MethodAsync|](); } Task MethodAsync() => Task.CompletedTask; Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; @@ -792,7 +795,7 @@ class C { async void M(CancellationToken ct) { - await [|MethodAsync()|].ConfigureAwait(false); + await [|MethodAsync|]().ConfigureAwait(false); } Task MethodAsync() => Task.CompletedTask; Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; @@ -824,7 +827,7 @@ class C { async void M(CancellationToken ct) { - await [|MethodAsync(5, ""Hello world"")|]; + await [|MethodAsync|](5, ""Hello world""); } Task MethodAsync() => Task.CompletedTask; Task MethodAsync(CancellationToken c) => Task.CompletedTask; @@ -860,7 +863,7 @@ class C { async void M(CancellationToken ct) { - await [|MethodAsync(5, ""Hello world"")|].ConfigureAwait(true); + await [|MethodAsync|](5, ""Hello world"").ConfigureAwait(true); } Task MethodAsync() => Task.CompletedTask; Task MethodAsync(CancellationToken c) => Task.CompletedTask; @@ -897,7 +900,7 @@ class C { void M(CancellationToken ct) { - Action a = async (CancellationToken token) => await [|MethodAsync()|]; + Action a = async (CancellationToken token) => await [|MethodAsync|](); a(ct); } Task MethodAsync() => Task.CompletedTask; @@ -933,7 +936,7 @@ class C { void M(CancellationToken ct) { - Action a = (CancellationToken c) => [|MethodAsync()|]; + Action a = (CancellationToken c) => [|MethodAsync|](); a(ct); } Task MethodAsync() => Task.CompletedTask; @@ -969,7 +972,7 @@ class C { void M(CancellationToken ct) { - Action a = async (CancellationToken token) => await [|MethodAsync()|].ConfigureAwait(false); + Action a = async (CancellationToken token) => await [|MethodAsync|]().ConfigureAwait(false); a(ct); } Task MethodAsync() => Task.CompletedTask; @@ -1007,7 +1010,7 @@ void M(CancellationToken ct) { Func> f = async (CancellationToken token) => { - await [|MethodAsync()|]; + await [|MethodAsync|](); return true; }; f(ct); @@ -1051,7 +1054,7 @@ void M(CancellationToken ct) { Func> f = async (CancellationToken token) => { - await [|MethodAsync()|].ConfigureAwait(true); + await [|MethodAsync|]().ConfigureAwait(true); return true; }; f(ct); @@ -1093,7 +1096,7 @@ class C { async void M(CancellationToken ct) { - Func f = (CancellationToken c) => [|MethodAsync()|]; + Func f = (CancellationToken c) => [|MethodAsync|](); await f(ct); } Task MethodAsync() => Task.CompletedTask; @@ -1131,7 +1134,7 @@ void M(CancellationToken ct) { async void LocalMethod(CancellationToken token) { - await [|MethodAsync()|]; + await [|MethodAsync|](); } LocalMethod(ct); } @@ -1173,7 +1176,7 @@ void M(CancellationToken ct) { void LocalMethod(CancellationToken token) { - [|MethodAsync()|]; + [|MethodAsync|](); } LocalMethod(ct); } @@ -1215,7 +1218,7 @@ async void M(CancellationToken ct) { Task LocalMethod(CancellationToken token) { - return [|MethodAsync()|]; + return [|MethodAsync|](); } await LocalMethod(ct); } @@ -1257,7 +1260,7 @@ void M(CancellationToken ct) { async void LocalMethod(CancellationToken token) { - await [|MethodAsync()|].ConfigureAwait(false); + await [|MethodAsync|]().ConfigureAwait(false); } LocalMethod(ct); } @@ -1297,7 +1300,7 @@ class C { async void M(CancellationToken ct) { - await [|MethodAsync()|]; + await [|MethodAsync|](); } Task MethodAsync(TokenAlias c = default) => Task.CompletedTask; } @@ -1329,7 +1332,7 @@ class C { async void M(CancellationToken ct) { - await [|MethodAsync()|]; + await [|MethodAsync|](); } Task MethodAsync() => Task.CompletedTask; Task MethodAsync(TokenAlias c) => Task.CompletedTask; @@ -1363,7 +1366,7 @@ class C { async void M(TokenAlias ct) { - await [|MethodAsync()|]; + await [|MethodAsync|](); } Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; } @@ -1395,7 +1398,7 @@ class C { async void M(TokenAlias ct) { - await [|MethodAsync()|]; + await [|MethodAsync|](); } Task MethodAsync() => Task.CompletedTask; Task MethodAsync(CancellationToken c) => Task.CompletedTask; @@ -1429,7 +1432,7 @@ class C { async void M(TokenAlias ct) { - await [|MethodAsync()|]; + await [|MethodAsync|](); } Task MethodAsync(TokenAlias c = default) => Task.CompletedTask; } @@ -1461,7 +1464,7 @@ class C { async void M(TokenAlias ct) { - await [|MethodAsync()|]; + await [|MethodAsync|](); } Task MethodAsync() => Task.CompletedTask; Task MethodAsync(CancellationToken c) => Task.CompletedTask; @@ -1494,7 +1497,7 @@ class C { Task M(CancellationToken ct) { - return [|MethodAsync()|]; + return [|MethodAsync|](); } Task MethodAsync(int x = 0, bool y = false, CancellationToken c = default) { @@ -1530,7 +1533,7 @@ class C { async void M(CancellationToken ct) { - await [|MethodAsync(5)|]; + await [|MethodAsync|](5); } Task MethodAsync(int x, bool y = default, CancellationToken c = default) => Task.CompletedTask; } @@ -1560,7 +1563,7 @@ class C { async void M(CancellationToken ct) { - await [|MethodAsync(x: 5)|]; + await [|MethodAsync|](x: 5); } Task MethodAsync(int x, bool y = default, CancellationToken c = default) => Task.CompletedTask; } @@ -1591,7 +1594,7 @@ class C { async void M(TokenAlias ct) { - await [|MethodAsync(x: 5)|]; + await [|MethodAsync|](x: 5); } Task MethodAsync(int x, bool y = default, CancellationToken c = default) => Task.CompletedTask; } @@ -1623,7 +1626,7 @@ class C { async void M(CancellationToken ct) { - await [|MethodAsync(x: 5)|]; + await [|MethodAsync|](x: 5); } Task MethodAsync(int x, bool y = default, TokenAlias c = default) => Task.CompletedTask; } @@ -1654,7 +1657,7 @@ class C { Task M(CancellationToken ct) { - return [|MethodAsync(z: ""Hello world"", x: 5, y: true)|]; + return [|MethodAsync|](z: ""Hello world"", x: 5, y: true); } Task MethodAsync(int x, bool y = default, string z = """", CancellationToken c = default) => Task.CompletedTask; } @@ -1941,14 +1944,14 @@ public Task VB_AnalyzerOnlyDiagnostic_OverloadWithNamedParametersUnordered() // This is a special case that will get a diagnostic but will not get a fix // because the fixer does not currently have a way to know the overload's ct parameter name // VB arguments get reordered in their official parameter order, so we could add the ct argument at the end - // and VB would compile successfully, but that would require separate VB handling in the fixer, so instead - // the C# and VB behavior will remain the same - return VerifyVB.VerifyAnalyzerAsync(@" + // and VB would compile successfully (CA8323 would not be thrown), but that would require separate VB + // handling in the fixer, so instead, the C# and VB behavior will remain the same + string originalCode = @" Imports System.Threading Imports System.Threading.Tasks Class C Private Function M(ByVal ct As CancellationToken) As Task - Return [|MethodAsync(z:=""Hello world"", x:=5, y:=true)|] + Return [|MethodAsync|](z:=""Hello world"", x:=5, y:=true) End Function Private Function MethodAsync(ByVal x As Integer, ByVal Optional y As Boolean = false, ByVal Optional z As String = """") As Task Return Task.CompletedTask @@ -1957,7 +1960,8 @@ Private Function MethodAsync(ByVal x As Integer, ByVal Optional y As Boolean = f Return Task.CompletedTask End Function End Class - "); + "; + return VerifyVB.VerifyAnalyzerAsync(originalCode); } #endregion @@ -1972,7 +1976,7 @@ Imports System.Threading Imports System.Threading.Tasks Class C Private Async Sub M(ByVal ct As CancellationToken) - Await [|MethodAsync()|] + Await [|MethodAsync|]() End Sub Private Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task Return Task.CompletedTask @@ -2002,7 +2006,7 @@ Imports System.Threading Imports System.Threading.Tasks Class C Private Async Sub M(ByVal ct As CancellationToken) - Await [|MethodAsync()|].ConfigureAwait(False) + Await [|MethodAsync|]().ConfigureAwait(False) End Sub Private Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task Return Task.CompletedTask @@ -2032,7 +2036,7 @@ Imports System.Threading Imports System.Threading.Tasks Class C Private Sub M(ByVal ct As CancellationToken) - [|MethodAsync()|] + [|MethodAsync|]() End Sub Private Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task Return Task.CompletedTask @@ -2063,7 +2067,7 @@ Imports System.Threading.Tasks Class C Private Sub M(ByVal ct As CancellationToken) - Dim t As Task = [|MethodAsync()|] + Dim t As Task = [|MethodAsync|]() End Sub Private Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task Return Task.CompletedTask @@ -2094,7 +2098,7 @@ Imports System.Threading Imports System.Threading.Tasks Class C Private Async Sub M(ByVal ct As CancellationToken) - Await [|MethodAsync()|] + Await [|MethodAsync|]() End Sub Private Shared Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task Return Task.CompletedTask @@ -2124,7 +2128,7 @@ Imports System.Threading Imports System.Threading.Tasks Class C Private Async Sub M(ByVal ct As CancellationToken) - Await [|MethodAsync()|].ConfigureAwait(False) + Await [|MethodAsync|]().ConfigureAwait(False) End Sub Private Shared Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task Return Task.CompletedTask @@ -2155,7 +2159,7 @@ Imports System.Threading.Tasks Class C Private Async Sub M(ByVal ct As CancellationToken) Dim o As O = New O() - Await [|o.MethodAsync()|] + Await [|o.MethodAsync|]() End Sub End Class Class O @@ -2191,7 +2195,7 @@ Imports System.Threading.Tasks Class C Private Async Sub M(ByVal ct As CancellationToken) Dim o As O = New O() - Await [|o.MethodAsync()|] + Await [|o.MethodAsync|]() End Sub End Class Class O @@ -2232,7 +2236,7 @@ Imports System.Threading Imports System.Threading.Tasks Class C Private Async Sub M(ByVal ct As CancellationToken) - Await [|O.MethodAsync()|] + Await [|O.MethodAsync|]() End Sub End Class Class O @@ -2267,7 +2271,7 @@ Imports System.Threading.Tasks Class C Private Async Sub M(ByVal ct As CancellationToken) Dim o As O = New O() - Await [|o.MethodAsync()|] + Await [|o.MethodAsync|]() End Sub End Class Class O @@ -2308,7 +2312,7 @@ Imports System.Threading Imports System.Threading.Tasks Structure S Private Async Sub M(ByVal ct As CancellationToken) - Await [|MethodAsync()|] + Await [|MethodAsync|]() End Sub Private Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task Return Task.CompletedTask @@ -2338,7 +2342,7 @@ Imports System.Threading Imports System.Threading.Tasks Structure S Private Async Sub M(ByVal ct As CancellationToken) - Await [|MethodAsync()|].ConfigureAwait(False) + Await [|MethodAsync|]().ConfigureAwait(False) End Sub Private Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task Return Task.CompletedTask @@ -2368,7 +2372,7 @@ Imports System.Threading Imports System.Threading.Tasks Class C Private Async Sub M(ByVal ct As CancellationToken) - Await [|MethodAsync()|] + Await [|MethodAsync|]() End Sub Private Function MethodAsync() As Task Return Task.CompletedTask @@ -2404,7 +2408,7 @@ Imports System.Threading Imports System.Threading.Tasks Class C Private Async Sub M(ByVal ct As CancellationToken) - Await [|MethodAsync()|] + Await [|MethodAsync|]() End Sub Private Function MethodAsync() As Task Return Task.CompletedTask @@ -2440,7 +2444,7 @@ Imports System.Threading Imports System.Threading.Tasks Class C Private Async Sub M(ByVal ct As CancellationToken) - Await [|MethodAsync()|] + Await [|MethodAsync|]() End Sub Private Function MethodAsync() As Task Return Task.CompletedTask @@ -2476,7 +2480,7 @@ Imports System.Threading Imports System.Threading.Tasks Class C Private Async Sub M(ByVal ct As CancellationToken) - Await [|MethodAsync()|].ConfigureAwait(False) + Await [|MethodAsync|]().ConfigureAwait(False) End Sub Private Function MethodAsync() As Task Return Task.CompletedTask @@ -2512,7 +2516,7 @@ Imports System.Threading Imports System.Threading.Tasks Class C Private Async Sub M(ByVal ct As CancellationToken) - Await [|MethodAsync(5, ""Hello, world"")|] + Await [|MethodAsync|](5, ""Hello, world"") End Sub Private Function MethodAsync() As Task Return Task.CompletedTask @@ -2560,7 +2564,7 @@ Imports System.Threading Imports System.Threading.Tasks Class C Private Async Sub M(ByVal ct As CancellationToken) - Await [|MethodAsync(5, ""Hello, world"")|].ConfigureAwait(True) + Await [|MethodAsync|](5, ""Hello, world"").ConfigureAwait(True) End Sub Private Function MethodAsync() As Task Return Task.CompletedTask @@ -2609,7 +2613,7 @@ Imports System.Threading Imports System.Threading.Tasks Class C Private Sub M(ByVal ct As CancellationToken) - Dim a As Action(Of CancellationToken) = Async Sub(ByVal token As CancellationToken) Await [|MethodAsync()|] + Dim a As Action(Of CancellationToken) = Async Sub(ByVal token As CancellationToken) Await [|MethodAsync|]() a(ct) End Sub Private Function MethodAsync() As Task @@ -2649,7 +2653,7 @@ Imports System.Threading Imports System.Threading.Tasks Class C Private Sub M(ByVal ct As CancellationToken) - Dim a As Action(Of CancellationToken) = Sub(ByVal c As CancellationToken) [|MethodAsync()|] + Dim a As Action(Of CancellationToken) = Sub(ByVal c As CancellationToken) [|MethodAsync|]() a(ct) End Sub Private Function MethodAsync() As Task @@ -2689,7 +2693,7 @@ Imports System.Threading Imports System.Threading.Tasks Class C Private Sub M(ByVal ct As CancellationToken) - Dim a As Action(Of CancellationToken) = Async Sub(ByVal token As CancellationToken) Await [|MethodAsync()|].ConfigureAwait(False) + Dim a As Action(Of CancellationToken) = Async Sub(ByVal token As CancellationToken) Await [|MethodAsync|]().ConfigureAwait(False) a(ct) End Sub Private Function MethodAsync() As Task @@ -2730,7 +2734,7 @@ Imports System.Threading.Tasks Class C Private Sub M(ByVal ct As CancellationToken) Dim f As Func(Of CancellationToken, Task(Of Boolean)) = Async Function(ByVal token As CancellationToken) - Await [|MethodAsync()|] + Await [|MethodAsync|]() Return True End Function f(ct) @@ -2776,7 +2780,7 @@ Imports System.Threading.Tasks Class C Private Sub M(ByVal ct As CancellationToken) Dim f As Func(Of CancellationToken, Boolean) = Function(ByVal token As CancellationToken) - [|MethodAsync()|] + [|MethodAsync|]() Return True End Function f(ct) @@ -2821,7 +2825,7 @@ Imports System.Threading Imports System.Threading.Tasks Class C Private Async Sub M(ByVal ct As CancellationToken) - Dim f As Func(Of CancellationToken, Task) = Function(ByVal c As CancellationToken) [|MethodAsync()|] + Dim f As Func(Of CancellationToken, Task) = Function(ByVal c As CancellationToken) [|MethodAsync|]() Await f(ct) End Sub Private Function MethodAsync() As Task @@ -2862,7 +2866,7 @@ Imports System.Threading.Tasks Class C Private Sub M(ByVal ct As CancellationToken) Dim f As Func(Of CancellationToken, Task(Of Boolean)) = Async Function(ByVal token As CancellationToken) - Await [|MethodAsync()|].ConfigureAwait(True) + Await [|MethodAsync|]().ConfigureAwait(True) Return True End Function f(ct) @@ -2913,7 +2917,7 @@ Imports System.Threading.Tasks Imports TokenAlias = System.Threading.CancellationToken Class C Private Async Sub M(ByVal ct As CancellationToken) - Await [|MethodAsync()|] + Await [|MethodAsync|]() End Sub Private Function MethodAsync() As Task Return Task.CompletedTask @@ -2951,7 +2955,7 @@ Imports System.Threading.Tasks Imports TokenAlias = System.Threading.CancellationToken Class C Private Async Sub M(ByVal ct As TokenAlias) - Await [|MethodAsync()|] + Await [|MethodAsync|]() End Sub Private Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task Return Task.CompletedTask @@ -2983,7 +2987,7 @@ Imports System.Threading.Tasks Imports TokenAlias = System.Threading.CancellationToken Class C Private Async Sub M(ByVal ct As TokenAlias) - Await [|MethodAsync()|] + Await [|MethodAsync|]() End Sub Private Function MethodAsync() As Task Return Task.CompletedTask @@ -3021,7 +3025,7 @@ Imports System.Threading.Tasks Imports TokenAlias = System.Threading.CancellationToken Class C Private Async Sub M(ByVal ct As TokenAlias) - Await [|MethodAsync()|] + Await [|MethodAsync|]() End Sub Private Function MethodAsync(ByVal Optional c As TokenAlias = Nothing) As Task Return Task.CompletedTask @@ -3053,7 +3057,7 @@ Imports System.Threading.Tasks Imports TokenAlias = System.Threading.CancellationToken Class C Private Async Sub M(ByVal ct As TokenAlias) - Await [|MethodAsync()|] + Await [|MethodAsync|]() End Sub Private Function MethodAsync() As Task Return Task.CompletedTask @@ -3090,7 +3094,7 @@ Imports System.Threading Imports System.Threading.Tasks Class C Private Function M(ByVal ct As CancellationToken) As Task - Return [|MethodAsync()|] + Return [|MethodAsync|]() End Function Private Function MethodAsync(ByVal Optional x As Integer = 0, ByVal Optional y As Boolean = False, ByVal Optional c As CancellationToken = Nothing) As Task Return Task.CompletedTask @@ -3120,7 +3124,7 @@ Imports System.Threading Imports System.Threading.Tasks Class C Private Async Sub M(ByVal ct As CancellationToken) - Await [|MethodAsync(5)|] + Await [|MethodAsync|](5) End Sub Private Function MethodAsync(ByVal x As Integer, ByVal Optional y As Boolean = false, ByVal Optional c As CancellationToken = Nothing) As Task Return Task.CompletedTask @@ -3150,7 +3154,7 @@ Imports System.Threading Imports System.Threading.Tasks Class C Private Async Sub M(ByVal ct As CancellationToken) - Await [|MethodAsync(x:=5)|] + Await [|MethodAsync|](x:=5) End Sub Private Function MethodAsync(ByVal x As Integer, ByVal Optional y As Boolean = false, ByVal Optional c As CancellationToken = Nothing) As Task Return Task.CompletedTask @@ -3181,7 +3185,7 @@ Imports System.Threading.Tasks Imports TokenAlias = System.Threading.CancellationToken Class C Private Async Sub M(ByVal ct As TokenAlias) - Await [|MethodAsync(x:=5)|] + Await [|MethodAsync|](x:=5) End Sub Private Function MethodAsync(ByVal x As Integer, ByVal Optional y As Boolean = false, ByVal Optional c As CancellationToken = Nothing) As Task Return Task.CompletedTask @@ -3213,7 +3217,7 @@ Imports System.Threading.Tasks Imports TokenAlias = System.Threading.CancellationToken Class C Private Async Sub M(ByVal ct As CancellationToken) - Await [|MethodAsync(x:=5)|] + Await [|MethodAsync|](x:=5) End Sub Private Function MethodAsync(ByVal x As Integer, ByVal Optional y As Boolean = false, ByVal Optional c As TokenAlias = Nothing) As Task Return Task.CompletedTask @@ -3244,7 +3248,7 @@ Imports System.Threading Imports System.Threading.Tasks Class C Private Function M(ByVal ct As CancellationToken) As Task - Return [|MethodAsync(z:=""Hello world"", x:=5, y:=true)|] + Return [|MethodAsync|](z:=""Hello world"", x:=5, y:=true) End Function Private Function MethodAsync(ByVal x As Integer, ByVal Optional y As Boolean = false, ByVal Optional z As String = """", ByVal Optional c As CancellationToken = Nothing) As Task Return Task.CompletedTask diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToAsyncMethods.Analyzer.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToAsyncMethods.Analyzer.vb new file mode 100644 index 0000000000..16cfbba34e --- /dev/null +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToAsyncMethods.Analyzer.vb @@ -0,0 +1,32 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.CodeFixes +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax +Imports Microsoft.NetCore.Analyzers.Runtime + +Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime + + + Public NotInheritable Class BasicForwardCancellationTokenToAsyncMethodsAnalyzer + + Inherits ForwardCancellationTokenToAsyncMethodsAnalyzer + + Protected Overrides Function GetMethodNameNode(invocationNode As SyntaxNode) As SyntaxNode + + Dim invocationExpression = TryCast(invocationNode, InvocationExpressionSyntax) + If invocationExpression IsNot Nothing Then + Return invocationExpression.Expression + End If + + Return Nothing + + End Function + + End Class + +End Namespace + + + + From 8b32db5518f690132050385e4fc0dd7f0020a93f Mon Sep 17 00:00:00 2001 From: carlossanlop Date: Mon, 1 Jun 2020 11:35:37 -0700 Subject: [PATCH 09/20] Bail out early in fixer before registering code fix --- ...rdCancellationTokenToAsyncMethods.Fixer.cs | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Fixer.cs index 6e901a1a91..425efbcdfa 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Fixer.cs @@ -57,7 +57,13 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) string title = MicrosoftNetCoreAnalyzersResources.ForwardCancellationTokenToAsyncMethodsTitle; - Task createChangedDocument(CancellationToken _) => FixInvocation(doc, root, invocation, parameterName); + if (!TryGenerateNewDocumentRoot(doc, root, invocation, parameterName, out SyntaxNode? newRoot)) + { + return; + } + + Task createChangedDocument(CancellationToken _) => + Task.FromResult(doc.WithSyntaxRoot(newRoot)); context.RegisterCodeFix( new MyCodeAction( @@ -67,12 +73,15 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) context.Diagnostics); } - private Task FixInvocation( + private bool TryGenerateNewDocumentRoot( Document doc, SyntaxNode root, IInvocationOperation invocation, - string cancellationTokenParameterName) + string cancellationTokenParameterName, + [NotNullWhen(returnValue: true)] out SyntaxNode? newRoot) { + newRoot = null; + SyntaxGenerator generator = SyntaxGenerator.GetGenerator(doc); // Pass the same arguments and add the ct @@ -126,7 +135,7 @@ private Task FixInvocation( // CA8323: Named argument 'argName' is used out-of-position but is followed by an unnamed argument if (string.IsNullOrEmpty(paramName)) { - return Task.FromResult(doc); + return false; } cancellationTokenNode = generator.Argument(paramName, RefKind.None, cancellationTokenIdentifier); @@ -157,8 +166,10 @@ private Task FixInvocation( // Insert the new arguments to the new invocation SyntaxNode newInvocationWithArguments = generator.InvocationExpression(newInvocation, newArguments); - SyntaxNode newRoot = generator.ReplaceNode(root, invocation.Syntax, newInvocationWithArguments); - return Task.FromResult(doc.WithSyntaxRoot(newRoot)); + newRoot = generator.ReplaceNode(root, invocation.Syntax, newInvocationWithArguments); + + return true; + } // Needed for Telemetry (https://github.com/dotnet/roslyn-analyzers/issues/192) From f776fbb8dad5cc4b7147d23c4afb7286e42e412d Mon Sep 17 00:00:00 2001 From: carlossanlop Date: Wed, 3 Jun 2020 18:06:20 -0700 Subject: [PATCH 10/20] address analyzer attributes in wrong locations --- .../CSharpForwardCancellationTokenToAsyncMethods.Analyzer.cs | 4 ++-- .../ForwardCancellationTokenToAsyncMethods.Analyzer.cs | 1 - .../BasicForwardCancellationTokenToAsyncMethods.Analyzer.vb | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToAsyncMethods.Analyzer.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToAsyncMethods.Analyzer.cs index 374e13dc0a..9b34fa8043 100644 --- a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToAsyncMethods.Analyzer.cs +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToAsyncMethods.Analyzer.cs @@ -1,13 +1,13 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.NetCore.Analyzers.Runtime; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; namespace Microsoft.NetCore.CSharp.Analyzers.Runtime { - [ExportCodeFixProvider(LanguageNames.CSharp)] + [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class CSharpForwardCancellationTokenToAsyncMethodsAnalyzer : ForwardCancellationTokenToAsyncMethodsAnalyzer { protected override SyntaxNode? GetMethodNameNode(SyntaxNode invocationNode) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Analyzer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Analyzer.cs index 2281d66f03..fe82ce07e7 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Analyzer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Analyzer.cs @@ -27,7 +27,6 @@ namespace Microsoft.NetCore.Analyzers.Runtime /// - The invocation method does not have an overload with the exact same arguments that also receives a ct, or... /// - The invocation method only has overloads that receive more than one ct. /// - [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public abstract class ForwardCancellationTokenToAsyncMethodsAnalyzer : DiagnosticAnalyzer { internal const string RuleId = "CA2016"; diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToAsyncMethods.Analyzer.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToAsyncMethods.Analyzer.vb index 16cfbba34e..da78468785 100644 --- a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToAsyncMethods.Analyzer.vb +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToAsyncMethods.Analyzer.vb @@ -1,13 +1,13 @@ ' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.CodeFixes +Imports Microsoft.CodeAnalysis.Diagnostics Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Imports Microsoft.NetCore.Analyzers.Runtime Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime - + Public NotInheritable Class BasicForwardCancellationTokenToAsyncMethodsAnalyzer Inherits ForwardCancellationTokenToAsyncMethodsAnalyzer From e809c9af0b007fc6508bedd0e7da4fe39d1e0f5f Mon Sep 17 00:00:00 2001 From: carlossanlop Date: Thu, 4 Jun 2020 21:15:49 -0700 Subject: [PATCH 11/20] Fix newest edge cases except lambda that does not take token --- ...ancellationTokenToInvocations.Analyzer.cs} | 9 +- ...rdCancellationTokenToInvocations.Fixer.cs} | 37 +- .../Core/AnalyzerReleases.Unshipped.md | 2 +- .../MicrosoftNetCoreAnalyzersResources.resx | 6 +- ...ancellationTokenToInvocations.Analyzer.cs} | 50 +- ...rdCancellationTokenToInvocations.Fixer.cs} | 24 +- .../MicrosoftNetCoreAnalyzersResources.cs.xlf | 6 +- .../MicrosoftNetCoreAnalyzersResources.de.xlf | 6 +- .../MicrosoftNetCoreAnalyzersResources.es.xlf | 6 +- .../MicrosoftNetCoreAnalyzersResources.fr.xlf | 6 +- .../MicrosoftNetCoreAnalyzersResources.it.xlf | 6 +- .../MicrosoftNetCoreAnalyzersResources.ja.xlf | 6 +- .../MicrosoftNetCoreAnalyzersResources.ko.xlf | 6 +- .../MicrosoftNetCoreAnalyzersResources.pl.xlf | 6 +- ...crosoftNetCoreAnalyzersResources.pt-BR.xlf | 6 +- .../MicrosoftNetCoreAnalyzersResources.ru.xlf | 6 +- .../MicrosoftNetCoreAnalyzersResources.tr.xlf | 6 +- ...osoftNetCoreAnalyzersResources.zh-Hans.xlf | 6 +- ...osoftNetCoreAnalyzersResources.zh-Hant.xlf | 6 +- ...ardCancellationTokenToInvocationsTests.cs} | 518 +++++++++++++++--- ...ancellationTokenToInvocations.Analyzer.vb} | 15 +- ...rdCancellationTokenToInvocations.Fixer.vb} | 37 +- 22 files changed, 640 insertions(+), 136 deletions(-) rename src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/{CSharpForwardCancellationTokenToAsyncMethods.Analyzer.cs => CSharpForwardCancellationTokenToInvocations.Analyzer.cs} (52%) rename src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/{CSharpForwardCancellationTokenToAsyncMethods.Fixer.cs => CSharpForwardCancellationTokenToInvocations.Fixer.cs} (64%) rename src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/{ForwardCancellationTokenToAsyncMethods.Analyzer.cs => ForwardCancellationTokenToInvocations.Analyzer.cs} (80%) rename src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/{ForwardCancellationTokenToAsyncMethods.Fixer.cs => ForwardCancellationTokenToInvocations.Fixer.cs} (86%) rename src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/{ForwardCancellationTokenToAsyncMethodsTests.cs => ForwardCancellationTokenToInvocationsTests.cs} (87%) rename src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/{BasicForwardCancellationTokenToAsyncMethods.Analyzer.vb => BasicForwardCancellationTokenToInvocations.Analyzer.vb} (67%) rename src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/{BasicForwardCancellationTokenToAsyncMethods.Fixer.vb => BasicForwardCancellationTokenToInvocations.Fixer.vb} (64%) diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToAsyncMethods.Analyzer.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocations.Analyzer.cs similarity index 52% rename from src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToAsyncMethods.Analyzer.cs rename to src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocations.Analyzer.cs index 9b34fa8043..fbc895adc2 100644 --- a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToAsyncMethods.Analyzer.cs +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocations.Analyzer.cs @@ -8,12 +8,19 @@ namespace Microsoft.NetCore.CSharp.Analyzers.Runtime { [DiagnosticAnalyzer(LanguageNames.CSharp)] - public sealed class CSharpForwardCancellationTokenToAsyncMethodsAnalyzer : ForwardCancellationTokenToAsyncMethodsAnalyzer + public sealed class CSharpForwardCancellationTokenToInvocationsAnalyzer : ForwardCancellationTokenToInvocationsAnalyzer { protected override SyntaxNode? GetMethodNameNode(SyntaxNode invocationNode) { if (invocationNode is InvocationExpressionSyntax invocationExpression) { + if (invocationExpression.Expression is MemberBindingExpressionSyntax memberBindingExpression) + { + // When using nullability features, specifically attempting to dereference possible null references, + // the dot becomes part of the member invocation expression, so we need to return just the name, + // so that the diagnostic gets properly returned in the method name only. + return memberBindingExpression.Name; + } return invocationExpression.Expression; } return null; diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToAsyncMethods.Fixer.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocations.Fixer.cs similarity index 64% rename from src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToAsyncMethods.Fixer.cs rename to src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocations.Fixer.cs index 90f61e458e..ea22350a7c 100644 --- a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToAsyncMethods.Fixer.cs +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocations.Fixer.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -12,8 +13,37 @@ namespace Microsoft.NetCore.CSharp.Analyzers.Runtime { [ExportCodeFixProvider(LanguageNames.CSharp)] - public sealed class CSharpForwardCancellationTokenToAsyncMethodsFixer : ForwardCancellationTokenToAsyncMethodsFixer + public sealed class CSharpForwardCancellationTokenToInvocationsFixer : ForwardCancellationTokenToInvocationsFixer { + protected override bool TryGetInvocation( + SemanticModel model, + SyntaxNode node, + CancellationToken ct, + [NotNullWhen(true)] out IInvocationOperation? invocation) + { + invocation = null; + + IOperation operation; + + // If the method was invoked using nullability for the case of attempting to dereference a possibly null reference, + // then the node.Parent.Parent is the actual invocation (and it will contain the dot as well) + if (node.Parent is MemberBindingExpressionSyntax) + { + operation = model.GetOperation(node.Parent.Parent, ct); + } + else + { + operation = model.GetOperation(node.Parent, ct); + } + + if (operation is IInvocationOperation invocationOperation) + { + invocation = invocationOperation; + } + + return invocation != null; + } + protected override bool TryGetAncestorDeclarationCancellationTokenParameterName( SyntaxNode node, [NotNullWhen(returnValue: true)] out string? parameterName) @@ -58,6 +88,11 @@ protected override bool IsArgumentNamed(IArgumentOperation argumentOperation) return argumentOperation.Syntax is ArgumentSyntax argumentNode && argumentNode.NameColon != null; } + protected override SyntaxNode GetConditionalOperationInvocationExpression(SyntaxNode invocationNode) + { + return ((InvocationExpressionSyntax)invocationNode).Expression; + } + private static string? GetCancellationTokenName(IEnumerable parameters) => parameters.Last()?.Identifier.ValueText; } diff --git a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md index 97d7944c92..5b60aa85ba 100644 --- a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md +++ b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md @@ -23,7 +23,7 @@ CA2012 | Reliability | Hidden | UseValueTasksCorrectlyAnalyzer, [Documentation]( CA2013 | Reliability | Warning | DoNotUseReferenceEqualsWithValueTypesAnalyzer, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca2013) CA2014 | Reliability | Warning | DoNotUseStackallocInLoopsAnalyzer, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca2014) CA2015 | Reliability | Warning | DoNotDefineFinalizersForTypesDerivedFromMemoryManager, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca2015) -CA2016 | Reliability | Warning | ForwardCancellationTokenToAsyncMethodsAnalyzer, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca2016) +CA2016 | Reliability | Warning | ForwardCancellationTokenToInvocationsAnalyzer, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca2016) CA2247 | Usage | Warning | DoNotCreateTaskCompletionSourceWithWrongArguments, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca2247) CA2248 | Usage | Info | DoNotCheckFlagFromDifferentEnum, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca2248) CA2249 | Usage | Info | PreferStringContainsOverIndexOfAnalyzer, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca2249) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx index 6ba9b66c91..e3f8c6195b 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx @@ -1287,13 +1287,13 @@ Change the '{0}' method call to use the '{1}' overload - + Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - + Forward the 'CancellationToken' parameter to methods that take one. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Analyzer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Analyzer.cs similarity index 80% rename from src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Analyzer.cs rename to src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Analyzer.cs index fe82ce07e7..bd7dea6781 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Analyzer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Analyzer.cs @@ -12,7 +12,7 @@ namespace Microsoft.NetCore.Analyzers.Runtime { /// - /// CA2016: Forward CancellationToken to async methods. + /// CA2016: Forward CancellationToken to invocations. /// /// Conditions for positive cases: /// - The containing method signature receives a ct parameter. It can be a method, a nested method, an action or a func. @@ -27,30 +27,30 @@ namespace Microsoft.NetCore.Analyzers.Runtime /// - The invocation method does not have an overload with the exact same arguments that also receives a ct, or... /// - The invocation method only has overloads that receive more than one ct. /// - public abstract class ForwardCancellationTokenToAsyncMethodsAnalyzer : DiagnosticAnalyzer + public abstract class ForwardCancellationTokenToInvocationsAnalyzer : DiagnosticAnalyzer { internal const string RuleId = "CA2016"; protected abstract SyntaxNode? GetMethodNameNode(SyntaxNode invocationNode); private static readonly LocalizableString s_localizableDescription = new LocalizableResourceString( - nameof(MicrosoftNetCoreAnalyzersResources.ForwardCancellationTokenToAsyncMethodsDescription), + nameof(MicrosoftNetCoreAnalyzersResources.ForwardCancellationTokenToInvocationsDescription), MicrosoftNetCoreAnalyzersResources.ResourceManager, typeof(MicrosoftNetCoreAnalyzersResources) ); private static readonly LocalizableString s_localizableMessage = new LocalizableResourceString( - nameof(MicrosoftNetCoreAnalyzersResources.ForwardCancellationTokenToAsyncMethodsMessage), + nameof(MicrosoftNetCoreAnalyzersResources.ForwardCancellationTokenToInvocationsMessage), MicrosoftNetCoreAnalyzersResources.ResourceManager, typeof(MicrosoftNetCoreAnalyzersResources) ); private static readonly LocalizableString s_localizableTitle = new LocalizableResourceString( - nameof(MicrosoftNetCoreAnalyzersResources.ForwardCancellationTokenToAsyncMethodsTitle), + nameof(MicrosoftNetCoreAnalyzersResources.ForwardCancellationTokenToInvocationsTitle), MicrosoftNetCoreAnalyzersResources.ResourceManager, typeof(MicrosoftNetCoreAnalyzersResources) ); - internal static DiagnosticDescriptor ForwardCancellationTokenToAsyncMethodsRule = DiagnosticDescriptorHelper.Create( + internal static DiagnosticDescriptor ForwardCancellationTokenToInvocationsRule = DiagnosticDescriptorHelper.Create( RuleId, s_localizableTitle, s_localizableMessage, @@ -62,7 +62,7 @@ public abstract class ForwardCancellationTokenToAsyncMethodsAnalyzer : Diagnosti ); public override ImmutableArray SupportedDiagnostics => - ImmutableArray.Create(ForwardCancellationTokenToAsyncMethodsRule); + ImmutableArray.Create(ForwardCancellationTokenToInvocationsRule); public override void Initialize(AnalysisContext context) { @@ -73,6 +73,7 @@ public override void Initialize(AnalysisContext context) private void AnalyzeCompilationStart(CompilationStartAnalysisContext context) { + if (!context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingCancellationToken, out INamedTypeSymbol? cancellationTokenType)) { return; @@ -100,12 +101,13 @@ private void AnalyzeCompilationStart(CompilationStartAnalysisContext context) SyntaxNode? expressionNode = GetMethodNameNode(context.Operation.Syntax); if (expressionNode != null) { - context.ReportDiagnostic(expressionNode.CreateDiagnostic(ForwardCancellationTokenToAsyncMethodsRule, cancellationTokenParameterName, invocation.TargetMethod.Name)); + context.ReportDiagnostic(expressionNode.CreateDiagnostic(ForwardCancellationTokenToInvocationsRule, cancellationTokenParameterName, invocation.TargetMethod.Name)); } }, OperationKind.Invocation); } + // Determines if an invocation should trigger a diagnostic for this rule or not. private static bool ShouldAnalyze( IInvocationOperation invocation, IMethodSymbol containingSymbol, @@ -116,8 +118,8 @@ private static bool ShouldAnalyze( IMethodSymbol method = invocation.TargetMethod; - // Check if the invocation has an optional implicit ct or an overload that takes one ct - if (!InvocationIgnoresOptionalCancellationToken(invocation, cancellationTokenType) && + // Check if the invocation's method has either an optional implicit ct or a params ct parameter, as well as an overload that takes one ct + if (!InvocationHasCancellationTokenArgument(method, invocation.Arguments, cancellationTokenType) && !MethodHasCancellationTokenOverload(method, cancellationTokenType)) { return false; @@ -151,18 +153,25 @@ private static bool VerifyAncestorOnlyHasOneCancellationTokenAsLastArgument( return false; } + // Checks if the invocation has an optional ct argument at the end or a params ct array at the end. + private static bool InvocationHasCancellationTokenArgument(IMethodSymbol method, ImmutableArray arguments, INamedTypeSymbol cancellationTokenType) + { + return + !method.Parameters.IsEmpty && + method.Parameters[method.Parameters.Length - 1] is IParameterSymbol lastParameter && + (InvocationIgnoresOptionalCancellationToken(lastParameter, arguments, cancellationTokenType) || + InvocationIsUsingParamsCancellationToken(lastParameter, cancellationTokenType)); + } + // Check if the currently used overload is the one that takes the ct, but is utilizing the default value offered in the method signature. // We want to offer a diagnostic for this case, so the user explicitly passes the ancestor's ct. - private static bool InvocationIgnoresOptionalCancellationToken(IInvocationOperation invocation, INamedTypeSymbol cancellationTokenType) + private static bool InvocationIgnoresOptionalCancellationToken(IParameterSymbol lastParameter, ImmutableArray arguments, INamedTypeSymbol cancellationTokenType) { - IMethodSymbol method = invocation.TargetMethod; - if (method.Parameters.Length != 0 && - method.Parameters[method.Parameters.Length - 1] is IParameterSymbol lastParameter && - lastParameter.Type.Equals(cancellationTokenType) && + if (lastParameter.Type.Equals(cancellationTokenType) && lastParameter.IsOptional) // Has a default value being used { // Find out if the ct argument is using the default value - return invocation.Arguments.Any(x => + return arguments.Any(x => x.Parameter.Type.Equals(cancellationTokenType) && x.ArgumentKind == ArgumentKind.DefaultValue); // The default value is being used } @@ -170,6 +179,15 @@ private static bool InvocationIgnoresOptionalCancellationToken(IInvocationOperat return false; } + // Checks if the method has a `params CancellationToken[]` argument in the last position. + private static bool InvocationIsUsingParamsCancellationToken(IParameterSymbol lastParameter, INamedTypeSymbol cancellationTokenType) + { + return lastParameter.IsParams && + lastParameter.Type.Kind == SymbolKind.ArrayType && + lastParameter.Type is IArrayTypeSymbol arrayTypeSymbol && + arrayTypeSymbol.ElementType.Equals(cancellationTokenType); + } + // Check if there's a method overload with the same parameters as this one, in the same order, plus a ct at the end. private static bool MethodHasCancellationTokenOverload(IMethodSymbol method, ITypeSymbol cancellationTokenType) { diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs similarity index 86% rename from src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Fixer.cs rename to src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs index 425efbcdfa..ff3e9b3abf 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethods.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs @@ -15,18 +15,29 @@ namespace Microsoft.NetCore.Analyzers.Runtime { - public abstract class ForwardCancellationTokenToAsyncMethodsFixer : CodeFixProvider + public abstract class ForwardCancellationTokenToInvocationsFixer : CodeFixProvider { + // Attempts to retrieve the invocation from the current operation. + protected abstract bool TryGetInvocation( + SemanticModel model, + SyntaxNode node, + CancellationToken ct, + [NotNullWhen(returnValue: true)] out IInvocationOperation? invocation); + // Looks for a ct parameter in the ancestor method or function declaration. If one is found, retrieve the name of the parameter. // Returns true if a ct parameter was found and parameterName is not null or empty. Returns false otherwise. protected abstract bool TryGetAncestorDeclarationCancellationTokenParameterName( SyntaxNode node, [NotNullWhen(returnValue: true)] out string? parameterName); + // Verifies if the specified argument was passed with an explicit name. protected abstract bool IsArgumentNamed(IArgumentOperation argumentOperation); + // Retrieves the invocation expression for a conditional operation, which consists of the dot and the method name. + protected abstract SyntaxNode GetConditionalOperationInvocationExpression(SyntaxNode invocationNode); + public override ImmutableArray FixableDiagnosticIds => - ImmutableArray.Create(ForwardCancellationTokenToAsyncMethodsAnalyzer.RuleId); + ImmutableArray.Create(ForwardCancellationTokenToInvocationsAnalyzer.RuleId); public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; @@ -45,7 +56,7 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) SemanticModel model = await doc.GetSemanticModelAsync(ct).ConfigureAwait(false); // The analyzer created the diagnostic on the IdentifierNameSyntax, and the parent is the actual invocation - if (!(model.GetOperation(node.Parent, ct) is IInvocationOperation invocation)) + if (!TryGetInvocation(model, node, ct, out IInvocationOperation? invocation)) { return; } @@ -55,7 +66,7 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) return; } - string title = MicrosoftNetCoreAnalyzersResources.ForwardCancellationTokenToAsyncMethodsTitle; + string title = MicrosoftNetCoreAnalyzersResources.ForwardCancellationTokenToInvocationsTitle; if (!TryGenerateNewDocumentRoot(doc, root, invocation, parameterName, out SyntaxNode? newRoot)) { @@ -153,6 +164,11 @@ private bool TryGenerateNewDocumentRoot( SyntaxNode staticType = generator.TypeExpressionForStaticMemberAccess(invocation.TargetMethod.ContainingType); newInvocation = generator.MemberAccessExpression(staticType, invocation.TargetMethod.Name); } + // The method is being invoked with nullability + else if (invocation.Instance is IConditionalAccessInstanceOperation) + { + newInvocation = GetConditionalOperationInvocationExpression(invocation.Syntax); + } // The instance is implicit when calling a method from the same type, call the method directly else if (invocation.Instance.IsImplicit) { diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf index 39ddf03446..d4f3b79dac 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf @@ -877,17 +877,17 @@ Finalizační metody by měly volat finalizační metodu základní třídy - + Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - + Forward the 'CancellationToken' parameter to methods that take one. Forward the 'CancellationToken' parameter to methods that take one. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf index 6c5d6ddd2b..d35d82a624 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf @@ -877,17 +877,17 @@ Finalizer sollten Basisklassen-Finalizer aufrufen - + Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - + Forward the 'CancellationToken' parameter to methods that take one. Forward the 'CancellationToken' parameter to methods that take one. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf index 7e3324e24c..0d6d37e41f 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf @@ -877,17 +877,17 @@ Los finalizadores deben llamar al finalizador de la clase base - + Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - + Forward the 'CancellationToken' parameter to methods that take one. Forward the 'CancellationToken' parameter to methods that take one. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf index 9dfe652c30..3320552418 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf @@ -877,17 +877,17 @@ Les finaliseurs doivent appeler un finaliseur de classe de base - + Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - + Forward the 'CancellationToken' parameter to methods that take one. Forward the 'CancellationToken' parameter to methods that take one. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf index c4cc5e7b01..841ad02a2d 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf @@ -877,17 +877,17 @@ I finalizzatori devono chiamare il finalizzatore della classe di base - + Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - + Forward the 'CancellationToken' parameter to methods that take one. Forward the 'CancellationToken' parameter to methods that take one. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf index 55f2314a95..9dcc396e7b 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf @@ -877,17 +877,17 @@ ファイナライザーは基底クラスのファイナライザーを呼び出さなければなりません - + Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - + Forward the 'CancellationToken' parameter to methods that take one. Forward the 'CancellationToken' parameter to methods that take one. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf index 89e8f22649..45298bac7c 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf @@ -877,17 +877,17 @@ 종료자가 기본 클래스 종료자를 호출해야 합니다. - + Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - + Forward the 'CancellationToken' parameter to methods that take one. Forward the 'CancellationToken' parameter to methods that take one. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf index 7fe7d69b09..0b670a5fb6 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf @@ -878,17 +878,17 @@ Finalizatory powinny wywoływać finalizator klasy podstawowej - + Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - + Forward the 'CancellationToken' parameter to methods that take one. Forward the 'CancellationToken' parameter to methods that take one. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf index 01add35a46..567fd12451 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf @@ -877,17 +877,17 @@ Os finalizadores devem chamar o finalizador da classe base - + Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - + Forward the 'CancellationToken' parameter to methods that take one. Forward the 'CancellationToken' parameter to methods that take one. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf index 8ae7a73ab9..086f5e42c5 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf @@ -877,17 +877,17 @@ Методы завершения должны вызывать метод завершения базового класса - + Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - + Forward the 'CancellationToken' parameter to methods that take one. Forward the 'CancellationToken' parameter to methods that take one. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf index a7f8084010..84ea0d151c 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf @@ -877,17 +877,17 @@ Sonlandırıcılar temel sınıf sonlandırıcısını çağırmalıdır - + Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - + Forward the 'CancellationToken' parameter to methods that take one. Forward the 'CancellationToken' parameter to methods that take one. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf index 6d67573091..6165d066c8 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf @@ -877,17 +877,17 @@ 终结器应调用基类的终结器 - + Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - + Forward the 'CancellationToken' parameter to methods that take one. Forward the 'CancellationToken' parameter to methods that take one. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf index 5fb2e9bd7f..3181dd4aa7 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf @@ -877,17 +877,17 @@ 完成項應呼叫基底類別完成項 - + Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - + Forward the 'CancellationToken' parameter to methods that take one. Forward the 'CancellationToken' parameter to methods that take one. diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethodsTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs similarity index 87% rename from src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethodsTests.cs rename to src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs index 60cdaa42b9..6666d37ea6 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToAsyncMethodsTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs @@ -1,24 +1,25 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Testing; using Xunit; using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< - Microsoft.NetCore.CSharp.Analyzers.Runtime.CSharpForwardCancellationTokenToAsyncMethodsAnalyzer, - Microsoft.NetCore.CSharp.Analyzers.Runtime.CSharpForwardCancellationTokenToAsyncMethodsFixer>; + Microsoft.NetCore.CSharp.Analyzers.Runtime.CSharpForwardCancellationTokenToInvocationsAnalyzer, + Microsoft.NetCore.CSharp.Analyzers.Runtime.CSharpForwardCancellationTokenToInvocationsFixer>; using VerifyVB = Test.Utilities.VisualBasicCodeFixVerifier< - Microsoft.NetCore.VisualBasic.Analyzers.Runtime.BasicForwardCancellationTokenToAsyncMethodsAnalyzer, - Microsoft.NetCore.VisualBasic.Analyzers.Runtime.BasicForwardCancellationTokenToAsyncMethodsFixer>; + Microsoft.NetCore.VisualBasic.Analyzers.Runtime.BasicForwardCancellationTokenToInvocationsAnalyzer, + Microsoft.NetCore.VisualBasic.Analyzers.Runtime.BasicForwardCancellationTokenToInvocationsFixer>; namespace Microsoft.NetCore.Analyzers.Runtime.UnitTests { - public class ForwardCancellationTokenToAsyncMethodsTests + public class ForwardCancellationTokenToInvocationsTests { #region No Diagnostic - C# [Fact] - public Task CS_NoDiagnostic_NoParentToken_NoToken() + public Task CS_NoDiagnostic_NoParentToken_AsyncNoToken() { return VerifyCS.VerifyAnalyzerAsync(@" using System.Threading; @@ -34,6 +35,21 @@ async void M() "); } + [Fact] + public Task CS_NoDiagnostic_NoParentToken_SyncNoToken() + { + return VerifyCS.VerifyAnalyzerAsync(@" +class C +{ + void M() + { + MyMethod(); + } + void MyMethod() {} +} + "); + } + [Fact] public Task CS_NoDiagnostic_NoParentToken_TokenDefault() { @@ -266,6 +282,37 @@ async void M(CancellationToken ct) "); } + [Fact] + public Task CS_NoDiagnostic_ExtensionMethodTakesToken() + { + // The extension method is in another class + string originalCode = @" +using System; +using System.Threading; +public static class Extensions +{ + public static void MyMethod(this MyClass mc, CancellationToken c) + { + } +} +class C +{ + public void M(CancellationToken ct) + { + MyClass mc = new MyClass(); + mc.MyMethod(); + } +} +public class MyClass +{ + public void MyMethod() + { + } +} + "; + return VerifyCS.VerifyAnalyzerAsync(originalCode); + } + #endregion #region Diagnostics with no fix = C# @@ -293,35 +340,62 @@ Task M(CancellationToken ct) return VerifyCS.VerifyAnalyzerAsync(originalCode); } + [Fact] + public Task CS_AnalyzerOnlyDiagnostic_CancellationTokenSource() + { + /* + CancellationTokenSource has 3 different overloads that take CancellationToken arguments. + When no ct is passed, because the overload that takes one instance is not setting a default value, then the analyzer considers it the `params`. + + public class CancellationTokenSource : IDisposable + { + public static CancellationTokenSource CreateLinkedTokenSource(CancellationToken token); + public static CancellationTokenSource CreateLinkedTokenSource(CancellationToken token1, CancellationToken token2); + public static CancellationTokenSource CreateLinkedTokenSource(params CancellationToken[] tokens); + } + + In C#, the invocation for a static method includes the type and the dot + */ + string originalCode = @" +using System.Threading; +class C +{ + void M(CancellationToken ct) + { + CancellationTokenSource cts = [|CancellationTokenSource.CreateLinkedTokenSource|](); + } +} + "; + return VerifyCS.VerifyAnalyzerAsync(originalCode); + } + #endregion - #region Diagnostics with Fix = C# + #region Diagnostics with fix = C# [Fact] public Task CS_Diagnostic_Class_TokenDefault() { string originalCode = @" using System.Threading; -using System.Threading.Tasks; class C { - async void M(CancellationToken ct) + void M(CancellationToken ct) { - await [|MethodAsync|](); + [|MyMethod|](); } - Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; + int MyMethod(CancellationToken c = default) => 1; } "; string fixedCode = @" using System.Threading; -using System.Threading.Tasks; class C { - async void M(CancellationToken ct) + void M(CancellationToken ct) { - await MethodAsync(ct); + MyMethod(ct); } - Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; + int MyMethod(CancellationToken c = default) => 1; } "; return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); @@ -482,34 +556,32 @@ public Task CS_Diagnostic_OtherClass_TokenDefault() { string originalCode = @" using System.Threading; -using System.Threading.Tasks; class C { - async void M(CancellationToken ct) + void M(CancellationToken ct) { O o = new O(); - await [|o.MethodAsync|](); + [|o.MyMethod|](); } } class O { - public Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; + public int MyMethod(CancellationToken c = default) => 1; } "; string fixedCode = @" using System.Threading; -using System.Threading.Tasks; class C { - async void M(CancellationToken ct) + void M(CancellationToken ct) { O o = new O(); - await o.MethodAsync(ct); + o.MyMethod(ct); } } class O { - public Task MethodAsync(CancellationToken c = default) => Task.CompletedTask; + public int MyMethod(CancellationToken c = default) => 1; } "; return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); @@ -558,6 +630,7 @@ class O [Fact] public Task CS_Diagnostic_OtherClassStaticMethod_TokenDefault() { + // The invocation for a static method includes the type and the dot string originalCode = @" using System.Threading; using System.Threading.Tasks; @@ -594,6 +667,7 @@ class O [Fact] public Task CS_Diagnostic_OtherClassStaticMethod_TokenDefault_WithConfigureAwait() { + // The invocation for a static method includes the type and the dot string originalCode = @" using System.Threading; using System.Threading.Tasks; @@ -1652,27 +1726,169 @@ public Task CS_Diagnostic_Default_WithNamedParametersUnordered() { string originalCode = @" using System.Threading; -using System.Threading.Tasks; class C { - Task M(CancellationToken ct) + int M(CancellationToken ct) { - return [|MethodAsync|](z: ""Hello world"", x: 5, y: true); + return [|MyMethod|](z: ""Hello world"", x: 5, y: true); } - Task MethodAsync(int x, bool y = default, string z = """", CancellationToken c = default) => Task.CompletedTask; + int MyMethod(int x, bool y = default, string z = """", CancellationToken c = default) => 1; } "; // Notice the parameters do NOT get reordered to their official position string fixedCode = @" using System.Threading; -using System.Threading.Tasks; class C { - Task M(CancellationToken ct) + int M(CancellationToken ct) { - return MethodAsync(z: ""Hello world"", x: 5, y: true, c: ct); + return MyMethod(z: ""Hello world"", x: 5, y: true, c: ct); + } + int MyMethod(int x, bool y = default, string z = """", CancellationToken c = default) => 1; +} + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task CS_Diagnostic_WithLock() + { + string originalCode = @" +using System.Threading; +class C +{ + private readonly object lockingObject = new object(); + int M (CancellationToken ct) + { + int x; + lock (lockingObject) + { + x = [|MyMethod|](5); + } + return x; + } + int MyMethod(int x, CancellationToken c = default) => 1; +} + "; + string fixedCode = @" +using System.Threading; +class C +{ + private readonly object lockingObject = new object(); + int M (CancellationToken ct) + { + int x; + lock (lockingObject) + { + x = MyMethod(5, ct); + } + return x; + } + int MyMethod(int x, CancellationToken c = default) => 1; +} + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task CS_Diagnostic_DereferencePossibleNullReference() + { + string originalCode = @" +#nullable enable +using System.Threading; +class C +{ + O? PossiblyNull() + { + return null; + } + void M(CancellationToken ct) + { + O? o = PossiblyNull(); + o?.[|MyMethod|](); + } +} +class O +{ + public int MyMethod(CancellationToken c = default) => 1; +} + "; + string fixedCode = @" +#nullable enable +using System.Threading; +class C +{ + O? PossiblyNull() + { + return null; + } + void M(CancellationToken ct) + { + O? o = PossiblyNull(); + o?.MyMethod(ct); + } +} +class O +{ + public int MyMethod(CancellationToken c = default) => 1; +} + "; + // Nullability is available in C# 8.0+ + return CSharp8VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task CS_Diagnostic_LambdaAndExtensionMethod() + { + // In C#, the invocation for a static method includes the type and the dot + string originalCode = @" +using System; +using System.Threading; +public static class Extensions +{ + public static void Extension(this bool b, Action action) + { + } + public static void MyMethod(this int i, CancellationToken c = default) + { + } +} +class C +{ + public void M(CancellationToken ct) + { + bool b = false; + b.Extension((j) => + { + Console.WriteLine(""Hello world""); + [|j.MyMethod|](); + }); + } +} + "; + string fixedCode = @" +using System; +using System.Threading; +public static class Extensions +{ + public static void Extension(this bool b, Action action) + { + } + public static void MyMethod(this int i, CancellationToken c = default) + { + } +} +class C +{ + public void M(CancellationToken ct) + { + bool b = false; + b.Extension((j) => + { + Console.WriteLine(""Hello world""); + j.MyMethod(ct); + }); } - Task MethodAsync(int x, bool y = default, string z = """", CancellationToken c = default) => Task.CompletedTask; } "; return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); @@ -1683,7 +1899,7 @@ Task M(CancellationToken ct) #region No Diagnostic - VB [Fact] - public Task VB_NoDiagnostic_NoParentToken_NoToken() + public Task VB_NoDiagnostic_NoParentToken_AsyncNoToken() { return VerifyVB.VerifyAnalyzerAsync(@" Imports System.Threading @@ -1699,6 +1915,20 @@ End Class "); } + [Fact] + public Task VB_NoDiagnostic_NoParentToken_SyncNoToken() + { + return VerifyVB.VerifyAnalyzerAsync(@" +Class C + Private Sub M() + MyMethod() + End Sub + Private Sub MyMethod() + End Sub +End Class + "); + } + [Fact] public Task VB_NoDiagnostic_NoParentToken_TokenDefault() { @@ -1934,6 +2164,33 @@ End Class "); } + [Fact] + public Task VB_NoDiagnostic_LambdaAndExtensionMethod() + { + // The extension method is in another class + string originalCode = @" +Imports System +Imports System.Threading +Imports System.Runtime.CompilerServices +Module Extensions + + Sub MyMethod(ByVal mc As [MyClass], ByVal c As CancellationToken) + End Sub +End Module +Class C + Public Sub M(ByVal ct As CancellationToken) + Dim mc As [MyClass] = New [MyClass]() + c.MyMethod() + End Sub +End Class +Public Class [MyClass] + Public Sub MyMethod() + End Sub +End Class + "; + return VerifyVB.VerifyAnalyzerAsync(originalCode); + } + #endregion #region Diagnostics with no fix = VB @@ -1964,6 +2221,34 @@ End Class return VerifyVB.VerifyAnalyzerAsync(originalCode); } + [Fact] + public Task VB_AnalyzerOnlyDiagnostic_CancellationTokenSource() + { + /* + CancellationTokenSource has 3 different overloads that take CancellationToken arguments. + When no ct is passed, because the overload that takes one instance is not setting a default value, then the analyzer considers it the `params`. + No fix provided. + + public class CancellationTokenSource : IDisposable + { + public static CancellationTokenSource CreateLinkedTokenSource(CancellationToken token); + public static CancellationTokenSource CreateLinkedTokenSource(CancellationToken token1, CancellationToken token2); + public static CancellationTokenSource CreateLinkedTokenSource(params CancellationToken[] tokens); + } + + Note: Unlinke C#, in VB the invocation for a static method does not include the type and the dot. + */ + string originalCode = @" +Imports System.Threading +Class C + Private Sub M(ByVal ct As CancellationToken) + Dim cts As CancellationTokenSource = CancellationTokenSource.[|CreateLinkedTokenSource|]() + End Sub +End Class + "; + return VerifyVB.VerifyAnalyzerAsync(originalCode); + } + #endregion #region Diagnostics with fix = VB @@ -1973,25 +2258,23 @@ public Task VB_Diagnostic_Class_TokenDefault() { string originalCode = @" Imports System.Threading -Imports System.Threading.Tasks Class C - Private Async Sub M(ByVal ct As CancellationToken) - Await [|MethodAsync|]() + Private Sub M(ByVal ct As CancellationToken) + [|MyMethod|]() End Sub - Private Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task - Return Task.CompletedTask + Private Function MyMethod(ByVal Optional c As CancellationToken = Nothing) As Integer + Return 1 End Function End Class "; string fixedCode = @" Imports System.Threading -Imports System.Threading.Tasks Class C - Private Async Sub M(ByVal ct As CancellationToken) - Await MethodAsync(ct) + Private Sub M(ByVal ct As CancellationToken) + MyMethod(ct) End Sub - Private Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task - Return Task.CompletedTask + Private Function MyMethod(ByVal Optional c As CancellationToken = Nothing) As Integer + Return 1 End Function End Class "; @@ -2155,31 +2438,29 @@ public Task VB_Diagnostic_OtherClass_TokenDefault() { string originalCode = @" Imports System.Threading -Imports System.Threading.Tasks Class C - Private Async Sub M(ByVal ct As CancellationToken) + Private Sub M(ByVal ct As CancellationToken) Dim o As O = New O() - Await [|o.MethodAsync|]() + o.[|MyMethod|]() End Sub End Class Class O - Public Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task - Return Task.CompletedTask + Public Function MyMethod(ByVal Optional c As CancellationToken = Nothing) As Integer + Return 1 End Function End Class "; string fixedCode = @" Imports System.Threading -Imports System.Threading.Tasks Class C - Private Async Sub M(ByVal ct As CancellationToken) + Private Sub M(ByVal ct As CancellationToken) Dim o As O = New O() - Await o.MethodAsync(ct) + o.MyMethod(ct) End Sub End Class Class O - Public Function MethodAsync(ByVal Optional c As CancellationToken = Nothing) As Task - Return Task.CompletedTask + Public Function MyMethod(ByVal Optional c As CancellationToken = Nothing) As Integer + Return 1 End Function End Class "; @@ -2195,7 +2476,7 @@ Imports System.Threading.Tasks Class C Private Async Sub M(ByVal ct As CancellationToken) Dim o As O = New O() - Await [|o.MethodAsync|]() + Await o.[|MethodAsync|]() End Sub End Class Class O @@ -2236,7 +2517,7 @@ Imports System.Threading Imports System.Threading.Tasks Class C Private Async Sub M(ByVal ct As CancellationToken) - Await [|O.MethodAsync|]() + Await O.[|MethodAsync|]() End Sub End Class Class O @@ -2271,7 +2552,7 @@ Imports System.Threading.Tasks Class C Private Async Sub M(ByVal ct As CancellationToken) Dim o As O = New O() - Await [|o.MethodAsync|]() + Await o.[|MethodAsync|]() End Sub End Class Class O @@ -3245,32 +3526,139 @@ public Task VB_Diagnostic_Default_WithNamedParametersUnordered() { string originalCode = @" Imports System.Threading -Imports System.Threading.Tasks Class C - Private Function M(ByVal ct As CancellationToken) As Task - Return [|MethodAsync|](z:=""Hello world"", x:=5, y:=true) + Private Function M(ByVal ct As CancellationToken) As Integer + Return [|MyMethod|](z:=""Hello world"", x:=5, y:=true) End Function - Private Function MethodAsync(ByVal x As Integer, ByVal Optional y As Boolean = false, ByVal Optional z As String = """", ByVal Optional c As CancellationToken = Nothing) As Task - Return Task.CompletedTask + Private Function MyMethod(ByVal x As Integer, ByVal Optional y As Boolean = false, ByVal Optional z As String = """", ByVal Optional c As CancellationToken = Nothing) As Integer + Return 1 End Function End Class "; // Notice the parameters get reordered to their official position string fixedCode = @" Imports System.Threading -Imports System.Threading.Tasks Class C - Private Function M(ByVal ct As CancellationToken) As Task - Return MethodAsync(x:=5, y:=true, z:=""Hello world"", c:=ct) + Private Function M(ByVal ct As CancellationToken) As Integer + Return MyMethod(x:=5, y:=true, z:=""Hello world"", c:=ct) End Function - Private Function MethodAsync(ByVal x As Integer, ByVal Optional y As Boolean = false, ByVal Optional z As String = """", ByVal Optional c As CancellationToken = Nothing) As Task - Return Task.CompletedTask + Private Function MyMethod(ByVal x As Integer, ByVal Optional y As Boolean = false, ByVal Optional z As String = """", ByVal Optional c As CancellationToken = Nothing) As Integer + Return 1 End Function End Class "; return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); } + [Fact] + public Task VB_Diagnostic_WithLock() + { + string originalCode = @" +Imports System.Threading +Class C + Private ReadOnly lockingObject As Object = New Object() + Private Function M(ByVal ct As CancellationToken) As Integer + Dim x As Integer + SyncLock lockingObject + x = [|MyMethod|](5) + End SyncLock + Return x + End Function + Private Function MyMethod(ByVal x As Integer, ByVal Optional c As CancellationToken = Nothing) As Integer + Return 1 + End Function +End Class + "; + string fixedCode = @" +Imports System.Threading +Class C + Private ReadOnly lockingObject As Object = New Object() + Private Function M(ByVal ct As CancellationToken) As Integer + Dim x As Integer + SyncLock lockingObject + x = MyMethod(5, ct) + End SyncLock + Return x + End Function + Private Function MyMethod(ByVal x As Integer, ByVal Optional c As CancellationToken = Nothing) As Integer + Return 1 + End Function +End Class + "; + return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); + } + + [Fact] + public Task VB_Diagnostic_DereferencePossibleNullReference() + { + string originalCode = @" +Imports System.Threading +Class C + Private Function PossiblyNull() As O? + Return Nothing + End Function + Private Sub M(ByVal ct As CancellationToken) + Dim o As O? = PossiblyNull() + o?.[|MyMethod|]() + End Sub +End Class +Structure O + Public Function MyMethod(ByVal Optional c As CancellationToken = Nothing) As Integer + Return 1 + End Function +End Structure + "; + string fixedCode = @" +Imports System.Threading +Class C + Private Function PossiblyNull() As O? + Return Nothing + End Function + Private Sub M(ByVal ct As CancellationToken) + Dim o As O? = PossiblyNull() + o?.MyMethod(ct) + End Sub +End Class +Structure O + Public Function MyMethod(ByVal Optional c As CancellationToken = Nothing) As Integer + Return 1 + End Function +End Structure + "; + // Nullability is available in C# 8.0+ + return VB16VerifyCodeFixAsync(originalCode, fixedCode); + } + + #endregion + + #region Helpers + + private static async Task VB16VerifyCodeFixAsync(string originalCode, string fixedCode) + { + var test = new VerifyVB.Test + { + TestCode = originalCode, + LanguageVersion = CodeAnalysis.VisualBasic.LanguageVersion.VisualBasic16, + FixedCode = fixedCode + }; + + test.ExpectedDiagnostics.AddRange(DiagnosticResult.EmptyDiagnosticResults); + await test.RunAsync(); + } + + private static async Task CSharp8VerifyCodeFixAsync(string originalCode, string fixedCode) + { + var test = new VerifyCS.Test + { + TestCode = originalCode, + LanguageVersion = CodeAnalysis.CSharp.LanguageVersion.CSharp8, + FixedCode = fixedCode + }; + + test.ExpectedDiagnostics.AddRange(DiagnosticResult.EmptyDiagnosticResults); + await test.RunAsync(); + } + #endregion } } \ No newline at end of file diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToAsyncMethods.Analyzer.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocations.Analyzer.vb similarity index 67% rename from src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToAsyncMethods.Analyzer.vb rename to src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocations.Analyzer.vb index da78468785..7bade163ee 100644 --- a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToAsyncMethods.Analyzer.vb +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocations.Analyzer.vb @@ -8,15 +8,26 @@ Imports Microsoft.NetCore.Analyzers.Runtime Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime - Public NotInheritable Class BasicForwardCancellationTokenToAsyncMethodsAnalyzer + Public NotInheritable Class BasicForwardCancellationTokenToInvocationsAnalyzer - Inherits ForwardCancellationTokenToAsyncMethodsAnalyzer + Inherits ForwardCancellationTokenToInvocationsAnalyzer Protected Overrides Function GetMethodNameNode(invocationNode As SyntaxNode) As SyntaxNode Dim invocationExpression = TryCast(invocationNode, InvocationExpressionSyntax) + If invocationExpression IsNot Nothing Then + + Dim memberBindingExpression = TryCast(invocationExpression.Expression, MemberAccessExpressionSyntax) + + If memberBindingExpression IsNot Nothing Then + + Return memberBindingExpression.Name + + End If + Return invocationExpression.Expression + End If Return Nothing diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToAsyncMethods.Fixer.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocations.Fixer.vb similarity index 64% rename from src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToAsyncMethods.Fixer.vb rename to src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocations.Fixer.vb index 746ae003bd..8a491455f5 100644 --- a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToAsyncMethods.Fixer.vb +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocations.Fixer.vb @@ -1,5 +1,7 @@ ' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +Imports System.Diagnostics.CodeAnalysis +Imports System.Threading Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.CodeFixes Imports Microsoft.CodeAnalysis.Operations @@ -9,11 +11,29 @@ Imports Microsoft.NetCore.Analyzers.Runtime Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime - Public NotInheritable Class BasicForwardCancellationTokenToAsyncMethodsFixer + Public NotInheritable Class BasicForwardCancellationTokenToInvocationsFixer - Inherits ForwardCancellationTokenToAsyncMethodsFixer + Inherits ForwardCancellationTokenToInvocationsFixer - Protected Overrides Function TryGetAncestorDeclarationCancellationTokenParameterName(node As SyntaxNode, ByRef parameterName As String) As Boolean + Protected Overrides Function TryGetInvocation(model As SemanticModel, node As SyntaxNode, ct As CancellationToken, ByRef invocation As IInvocationOperation) As Boolean + + Dim operation As IOperation + + Dim parentSyntax As MemberAccessExpressionSyntax = TryCast(node.Parent, MemberAccessExpressionSyntax) + + If parentSyntax IsNot Nothing Then + operation = model.GetOperation(node.Parent.Parent, ct) + Else + operation = model.GetOperation(node.Parent, ct) + End If + + invocation = TryCast(operation, IInvocationOperation) + + Return invocation IsNot Nothing + + End Function + + Protected Overrides Function TryGetAncestorDeclarationCancellationTokenParameterName(node As SyntaxNode, ByRef parameterName As String) As Boolean parameterName = Nothing @@ -53,8 +73,17 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime Return argument IsNot Nothing AndAlso argument.NameColonEquals IsNot Nothing End Function + Protected Overrides Function GetConditionalOperationInvocationExpression(invocationNode As SyntaxNode) As SyntaxNode + + Dim invocationExpression As InvocationExpressionSyntax = CType(invocationNode, InvocationExpressionSyntax) + Return invocationExpression.Expression + + End Function + Private Shared Function GetCancellationTokenName(parameters As IEnumerable(Of ParameterSyntax)) As String - Return parameters.Last()?.Identifier.Identifier.ValueText + Dim lastParameter As ParameterSyntax = parameters.Last() + + Return lastParameter?.Identifier.Identifier.ValueText End Function End Class From d08aadafc0559d19158d8c38f52f032fe01d790a Mon Sep 17 00:00:00 2001 From: carlossanlop Date: Fri, 5 Jun 2020 20:10:33 -0700 Subject: [PATCH 12/20] Address last false positives, add trivia support --- ...CancellationTokenToInvocations.Analyzer.cs | 30 ++- ...ardCancellationTokenToInvocations.Fixer.cs | 3 +- ...wardCancellationTokenToInvocationsTests.cs | 209 ++++++++++++++---- 3 files changed, 197 insertions(+), 45 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Analyzer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Analyzer.cs index bd7dea6781..dfc5e49a0b 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Analyzer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Analyzer.cs @@ -81,13 +81,13 @@ private void AnalyzeCompilationStart(CompilationStartAnalysisContext context) context.RegisterOperationAction(context => { + IInvocationOperation invocation = (IInvocationOperation)context.Operation; + if (!(context.ContainingSymbol is IMethodSymbol containingSymbol)) { return; } - IInvocationOperation invocation = (IInvocationOperation)context.Operation; - if (!ShouldAnalyze( invocation, containingSymbol, @@ -126,7 +126,7 @@ private static bool ShouldAnalyze( } // Check if the ancestor method has a ct that we can pass to the invocation - if (!VerifyAncestorOnlyHasOneCancellationTokenAsLastArgument(cancellationTokenType, containingSymbol, out cancellationTokenParameterName)) + if (!VerifyAncestorOnlyHasOneCancellationTokenAsLastArgument(cancellationTokenType, GetContainingSymbol(invocation, containingSymbol), out cancellationTokenParameterName)) { return false; } @@ -134,7 +134,29 @@ private static bool ShouldAnalyze( return true; } - // Check if the method only takes one ct and is the last parameter in the method signature. + // Try to find the most immediate containing symbol (anonymous or local function). If none is found, return the context containing symbol. + private static IMethodSymbol GetContainingSymbol(IInvocationOperation invocation, IMethodSymbol containingSymbol) + { + IOperation currentOperation = invocation.Parent; + + while (currentOperation != null) + { + if (currentOperation.Kind == OperationKind.AnonymousFunction) + { + return ((IAnonymousFunctionOperation)currentOperation).Symbol; + } + else if (currentOperation.Kind == OperationKind.LocalFunction) + { + return ((ILocalFunctionOperation)currentOperation).Symbol; + } + + currentOperation = currentOperation.Parent; + } + + return containingSymbol; + } + +// Check if the method only takes one ct and is the last parameter in the method signature. // We want to compare the current method signature to any others with the exact same arguments in the exact same order. private static bool VerifyAncestorOnlyHasOneCancellationTokenAsLastArgument( INamedTypeSymbol cancellationTokenType, diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs index ff3e9b3abf..b1451decd8 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs @@ -180,12 +180,11 @@ private bool TryGenerateNewDocumentRoot( newInvocation = generator.MemberAccessExpression(invocation.GetInstanceSyntax(), invocation.TargetMethod.Name); } // Insert the new arguments to the new invocation - SyntaxNode newInvocationWithArguments = generator.InvocationExpression(newInvocation, newArguments); + SyntaxNode newInvocationWithArguments = generator.InvocationExpression(newInvocation, newArguments).WithTriviaFrom(invocation.Syntax); newRoot = generator.ReplaceNode(root, invocation.Syntax, newInvocationWithArguments); return true; - } // Needed for Telemetry (https://github.com/dotnet/roslyn-analyzers/issues/192) diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs index 6666d37ea6..0fdd4c009e 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs @@ -313,6 +313,38 @@ public void MyMethod() return VerifyCS.VerifyAnalyzerAsync(originalCode); } + [Fact] + public Task CS_NoDiagnostic_LambdaAndExtensionMethod() + { + // Avoid triggering a diagnostic if the immediate ancestor is an anonymous function and the parameter type is not ct + string originalCode = @" +using System; +using System.Threading; +public static class Extensions +{ + public static void Extension(this bool b, Action action) + { + } + public static void MyMethod(this int i, CancellationToken c = default) + { + } +} +class C +{ + public void M(CancellationToken ct) + { + bool b = false; + b.Extension((j) => + { + Console.WriteLine(""Hello world""); + j.MyMethod(); + }); + } +} + "; + return VerifyCS.VerifyAnalyzerAsync(originalCode); + } + #endregion #region Diagnostics with no fix = C# @@ -1838,57 +1870,66 @@ class O } [Fact] - public Task CS_Diagnostic_LambdaAndExtensionMethod() + public Task CS_Diagnostic_WithTrivia() { - // In C#, the invocation for a static method includes the type and the dot string originalCode = @" -using System; using System.Threading; -public static class Extensions -{ - public static void Extension(this bool b, Action action) - { - } - public static void MyMethod(this int i, CancellationToken c = default) - { - } -} +using System.Threading.Tasks; class C { - public void M(CancellationToken ct) + async void M(CancellationToken ct) { - bool b = false; - b.Extension((j) => - { - Console.WriteLine(""Hello world""); - [|j.MyMethod|](); - }); + await /* Prefix1 */ [|MethodDefaultAsync|]() /* Suffix1 */; + await /* Prefix2 */ [|MethodOverloadAsync|]() /* Suffix2 */; + await /* Prefix3 */ [|MethodOverloadWithArgumentsAsync|](5 /*ArgumentComment0 */) /* Suffix3 */; + /* Prefix4 */ [|MethodDefault|]() /* Suffix4 */; + /* Prefix5 */ [|MethodOverload|]() /* Suffix5 */; + /* Prefix6 */ [|MethodDefaultWithArguments|](5 /* ArgumentComment1 */) /* Suffix6 */; + /* Prefix7 */ [|MethodOverloadWithArguments|](5 /* ArgumentComment2 */) /* Suffix7 */; + /* Prefix8 */ MethodOverloadWithArguments(x: /*ArgumentComment3 */ 5 /* ArgumentComment4 */, ct) /* Suffix8 */; + } + Task MethodDefaultAsync(CancellationToken c = default) => Task.CompletedTask; + Task MethodOverloadAsync() => Task.CompletedTask; + Task MethodOverloadAsync(CancellationToken c) => Task.CompletedTask; + Task MethodOverloadWithArgumentsAsync(int x) => Task.CompletedTask; + Task MethodOverloadWithArgumentsAsync(int x, CancellationToken c) => Task.CompletedTask; + void MethodDefault(CancellationToken c = default) {} + void MethodOverload() {} + void MethodOverload(CancellationToken c) {} + void MethodDefaultWithArguments(int x, CancellationToken c = default) {} + void MethodOverloadWithArguments(int x) {} + void MethodOverloadWithArguments(int x, CancellationToken c) {} } "; string fixedCode = @" -using System; using System.Threading; -public static class Extensions -{ - public static void Extension(this bool b, Action action) - { - } - public static void MyMethod(this int i, CancellationToken c = default) - { - } -} +using System.Threading.Tasks; class C { - public void M(CancellationToken ct) + async void M(CancellationToken ct) { - bool b = false; - b.Extension((j) => - { - Console.WriteLine(""Hello world""); - j.MyMethod(ct); - }); - } + await /* Prefix1 */ MethodDefaultAsync(ct) /* Suffix1 */; + await /* Prefix2 */ MethodOverloadAsync(ct) /* Suffix2 */; + await /* Prefix3 */ MethodOverloadWithArgumentsAsync(5 /*ArgumentComment0 */, ct) /* Suffix3 */; + /* Prefix4 */ MethodDefault(ct) /* Suffix4 */; + /* Prefix5 */ MethodOverload(ct) /* Suffix5 */; + /* Prefix6 */ MethodDefaultWithArguments(5 /* ArgumentComment1 */, ct) /* Suffix6 */; + /* Prefix7 */ MethodOverloadWithArguments(5 /* ArgumentComment2 */, ct) /* Suffix7 */; + /* Prefix8 */ MethodOverloadWithArguments(x: /*ArgumentComment3 */ 5 /* ArgumentComment4 */, ct) /* Suffix8 */; + + } + Task MethodDefaultAsync(CancellationToken c = default) => Task.CompletedTask; + Task MethodOverloadAsync() => Task.CompletedTask; + Task MethodOverloadAsync(CancellationToken c) => Task.CompletedTask; + Task MethodOverloadWithArgumentsAsync(int x) => Task.CompletedTask; + Task MethodOverloadWithArgumentsAsync(int x, CancellationToken c) => Task.CompletedTask; + void MethodDefault(CancellationToken c = default) {} + void MethodOverload() {} + void MethodOverload(CancellationToken c) {} + void MethodDefaultWithArguments(int x, CancellationToken c = default) {} + void MethodOverloadWithArguments(int x) {} + void MethodOverloadWithArguments(int x, CancellationToken c) {} } "; return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); @@ -2167,7 +2208,7 @@ End Class [Fact] public Task VB_NoDiagnostic_LambdaAndExtensionMethod() { - // The extension method is in another class + // Avoid triggering a diagnostic if the immediate ancestor is an anonymous function and the parameter type is not ct string originalCode = @" Imports System Imports System.Threading @@ -2180,7 +2221,7 @@ End Module Class C Public Sub M(ByVal ct As CancellationToken) Dim mc As [MyClass] = New [MyClass]() - c.MyMethod() + mc.MyMethod() End Sub End Class Public Class [MyClass] @@ -3629,6 +3670,96 @@ End Structure return VB16VerifyCodeFixAsync(originalCode, fixedCode); } + [Fact] + public Task VB_Diagnostic_WithTrivia() + { + string originalCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await [|MethodDefaultAsync|]() ' InvocationComment1 + Await [|MethodOverloadAsync|]() ' InvocationComment2 + Await [|MethodOverloadWithArgumentsAsync|](5) ' InvocationComment3 + [|MethodDefault|]() ' InvocationComment4 + [|MethodOverload|]() ' InvocationComment5 + [|MethodDefaultWithArguments|](5) ' InvocationComment6 + [|MethodOverloadWithArguments|](5) ' InvocationComment7 + End Sub + Private Function MethodDefaultAsync(ByVal Optional c As CancellationToken = Nothing) As Task + Return Task.CompletedTask + End Function + Private Function MethodOverloadAsync() As Task + Return Task.CompletedTask + End Function + Private Function MethodOverloadAsync(ByVal c As CancellationToken) As Task + Return Task.CompletedTask + End Function + Private Function MethodOverloadWithArgumentsAsync(ByVal x As Integer) As Task + Return Task.CompletedTask + End Function + Private Function MethodOverloadWithArgumentsAsync(ByVal x As Integer, ByVal c As CancellationToken) As Task + Return Task.CompletedTask + End Function + Private Sub MethodDefault(ByVal Optional c As CancellationToken = Nothing) + End Sub + Private Sub MethodOverload() + End Sub + Private Sub MethodOverload(ByVal c As CancellationToken) + End Sub + Private Sub MethodDefaultWithArguments(ByVal x As Integer, ByVal Optional c As CancellationToken = Nothing) + End Sub + Private Sub MethodOverloadWithArguments(ByVal x As Integer) + End Sub + Private Sub MethodOverloadWithArguments(ByVal x As Integer, ByVal c As CancellationToken) + End Sub +End Class + "; + string fixedCode = @" +Imports System.Threading +Imports System.Threading.Tasks +Class C + Private Async Sub M(ByVal ct As CancellationToken) + Await MethodDefaultAsync(ct) ' InvocationComment1 + Await MethodOverloadAsync(ct) ' InvocationComment2 + Await MethodOverloadWithArgumentsAsync(5, ct) ' InvocationComment3 + MethodDefault(ct) ' InvocationComment4 + MethodOverload(ct) ' InvocationComment5 + MethodDefaultWithArguments(5, ct) ' InvocationComment6 + MethodOverloadWithArguments(5, ct) ' InvocationComment7 + End Sub + Private Function MethodDefaultAsync(ByVal Optional c As CancellationToken = Nothing) As Task + Return Task.CompletedTask + End Function + Private Function MethodOverloadAsync() As Task + Return Task.CompletedTask + End Function + Private Function MethodOverloadAsync(ByVal c As CancellationToken) As Task + Return Task.CompletedTask + End Function + Private Function MethodOverloadWithArgumentsAsync(ByVal x As Integer) As Task + Return Task.CompletedTask + End Function + Private Function MethodOverloadWithArgumentsAsync(ByVal x As Integer, ByVal c As CancellationToken) As Task + Return Task.CompletedTask + End Function + Private Sub MethodDefault(ByVal Optional c As CancellationToken = Nothing) + End Sub + Private Sub MethodOverload() + End Sub + Private Sub MethodOverload(ByVal c As CancellationToken) + End Sub + Private Sub MethodDefaultWithArguments(ByVal x As Integer, ByVal Optional c As CancellationToken = Nothing) + End Sub + Private Sub MethodOverloadWithArguments(ByVal x As Integer) + End Sub + Private Sub MethodOverloadWithArguments(ByVal x As Integer, ByVal c As CancellationToken) + End Sub +End Class + "; + return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); + } + #endregion #region Helpers From 6cc2cc79352a7c1f6927c08de0484ab59b7b87eb Mon Sep 17 00:00:00 2001 From: carlossanlop Date: Fri, 5 Jun 2020 20:19:58 -0700 Subject: [PATCH 13/20] Fix comment formatting warning --- .../Runtime/ForwardCancellationTokenToInvocations.Analyzer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Analyzer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Analyzer.cs index dfc5e49a0b..08c12a7f73 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Analyzer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Analyzer.cs @@ -156,7 +156,7 @@ private static IMethodSymbol GetContainingSymbol(IInvocationOperation invocation return containingSymbol; } -// Check if the method only takes one ct and is the last parameter in the method signature. + // Check if the method only takes one ct and is the last parameter in the method signature. // We want to compare the current method signature to any others with the exact same arguments in the exact same order. private static bool VerifyAncestorOnlyHasOneCancellationTokenAsLastArgument( INamedTypeSymbol cancellationTokenType, From f4b4bddb42ff22239be14b93dd22f7c3b0087cbc Mon Sep 17 00:00:00 2001 From: carlossanlop Date: Fri, 5 Jun 2020 20:57:35 -0700 Subject: [PATCH 14/20] Additional case for ct params --- ...CancellationTokenToInvocations.Analyzer.cs | 27 ++++++--- ...wardCancellationTokenToInvocationsTests.cs | 60 ++++++++++++++++++- 2 files changed, 78 insertions(+), 9 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Analyzer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Analyzer.cs index 08c12a7f73..99a0c9f128 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Analyzer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Analyzer.cs @@ -88,7 +88,7 @@ private void AnalyzeCompilationStart(CompilationStartAnalysisContext context) return; } - if (!ShouldAnalyze( + if (!ShouldDiagnose( invocation, containingSymbol, cancellationTokenType, @@ -108,7 +108,7 @@ private void AnalyzeCompilationStart(CompilationStartAnalysisContext context) } // Determines if an invocation should trigger a diagnostic for this rule or not. - private static bool ShouldAnalyze( + private static bool ShouldDiagnose( IInvocationOperation invocation, IMethodSymbol containingSymbol, INamedTypeSymbol cancellationTokenType, @@ -182,7 +182,7 @@ private static bool InvocationHasCancellationTokenArgument(IMethodSymbol method, !method.Parameters.IsEmpty && method.Parameters[method.Parameters.Length - 1] is IParameterSymbol lastParameter && (InvocationIgnoresOptionalCancellationToken(lastParameter, arguments, cancellationTokenType) || - InvocationIsUsingParamsCancellationToken(lastParameter, cancellationTokenType)); + InvocationIsUsingParamsCancellationToken(lastParameter, arguments, cancellationTokenType)); } // Check if the currently used overload is the one that takes the ct, but is utilizing the default value offered in the method signature. @@ -201,13 +201,26 @@ private static bool InvocationIgnoresOptionalCancellationToken(IParameterSymbol return false; } - // Checks if the method has a `params CancellationToken[]` argument in the last position. - private static bool InvocationIsUsingParamsCancellationToken(IParameterSymbol lastParameter, INamedTypeSymbol cancellationTokenType) + // Checks if the method has a `params CancellationToken[]` argument in the last position and ensure no ct is being passed. + private static bool InvocationIsUsingParamsCancellationToken(IParameterSymbol lastParameter, ImmutableArray arguments, INamedTypeSymbol cancellationTokenType) { - return lastParameter.IsParams && + if (lastParameter.IsParams && lastParameter.Type.Kind == SymbolKind.ArrayType && lastParameter.Type is IArrayTypeSymbol arrayTypeSymbol && - arrayTypeSymbol.ElementType.Equals(cancellationTokenType); + arrayTypeSymbol.ElementType.Equals(cancellationTokenType)) + { + IArgumentOperation? paramsArgument = arguments.FirstOrDefault(a => a.ArgumentKind == ArgumentKind.ParamArray); + if (paramsArgument != null) + { + if (paramsArgument.Value is IArrayCreationOperation arrayOperation) + { + // Do not offer a diagnostic if the user already passed a ct to the params + return arrayOperation.Initializer.ElementValues.IsEmpty; + } + } + } + + return false; } // Check if there's a method overload with the same parameters as this one, in the same order, plus a ct at the end. diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs index 0fdd4c009e..1c9771dd6d 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs @@ -373,7 +373,7 @@ Task M(CancellationToken ct) } [Fact] - public Task CS_AnalyzerOnlyDiagnostic_CancellationTokenSource() + public Task CS_AnalyzerOnlyDiagnostic_CancellationTokenSource_ParamsEmpty() { /* CancellationTokenSource has 3 different overloads that take CancellationToken arguments. @@ -401,6 +401,35 @@ void M(CancellationToken ct) return VerifyCS.VerifyAnalyzerAsync(originalCode); } + [Fact] + public Task CS_AnalyzerOnlyDiagnostic_CancellationTokenSource_ParamsUsed() + { + /* + CancellationTokenSource has 3 different overloads that take CancellationToken arguments. + We should detect if a ct is passed and not offer a diagnostic, because it's considered one of the `params`. + + public class CancellationTokenSource : IDisposable + { + public static CancellationTokenSource CreateLinkedTokenSource(CancellationToken token); + public static CancellationTokenSource CreateLinkedTokenSource(CancellationToken token1, CancellationToken token2); + public static CancellationTokenSource CreateLinkedTokenSource(params CancellationToken[] tokens); + } + + In C#, the invocation for a static method includes the type and the dot + */ + string originalCode = @" +using System.Threading; +class C +{ + void M(CancellationToken ct) + { + CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(ct); + } +} + "; + return VerifyCS.VerifyAnalyzerAsync(originalCode); + } + #endregion #region Diagnostics with fix = C# @@ -2263,7 +2292,7 @@ End Class } [Fact] - public Task VB_AnalyzerOnlyDiagnostic_CancellationTokenSource() + public Task VB_AnalyzerOnlyDiagnostic_CancellationTokenSource_ParamsEmpty() { /* CancellationTokenSource has 3 different overloads that take CancellationToken arguments. @@ -2290,6 +2319,33 @@ End Class return VerifyVB.VerifyAnalyzerAsync(originalCode); } + [Fact] + public Task VB_AnalyzerOnlyDiagnostic_CancellationTokenSource_ParamsUsed() + { + /* + CancellationTokenSource has 3 different overloads that take CancellationToken arguments. + We should detect if a ct is passed and not offer a diagnostic, because it's considered one of the `params`. + + public class CancellationTokenSource : IDisposable + { + public static CancellationTokenSource CreateLinkedTokenSource(CancellationToken token); + public static CancellationTokenSource CreateLinkedTokenSource(CancellationToken token1, CancellationToken token2); + public static CancellationTokenSource CreateLinkedTokenSource(params CancellationToken[] tokens); + } + + Note: Unlinke C#, in VB the invocation for a static method does not include the type and the dot. + */ + string originalCode = @" +Imports System.Threading +Class C + Private Sub M(ByVal ct As CancellationToken) + Dim cts As CancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(ct) + End Sub +End Class + "; + return VerifyVB.VerifyAnalyzerAsync(originalCode); + } + #endregion #region Diagnostics with fix = VB From d5ddc40d4f1028171d4284202f876c1209cbc878 Mon Sep 17 00:00:00 2001 From: carlossanlop Date: Fri, 5 Jun 2020 22:43:36 -0700 Subject: [PATCH 15/20] Added more edge cases, ensured to do the most basic check at the beginning --- ...CancellationTokenToInvocations.Analyzer.cs | 12 +- ...wardCancellationTokenToInvocationsTests.cs | 302 ++++++++++++++---- 2 files changed, 249 insertions(+), 65 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Analyzer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Analyzer.cs index 99a0c9f128..792e44fd12 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Analyzer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Analyzer.cs @@ -118,8 +118,14 @@ private static bool ShouldDiagnose( IMethodSymbol method = invocation.TargetMethod; - // Check if the invocation's method has either an optional implicit ct or a params ct parameter, as well as an overload that takes one ct - if (!InvocationHasCancellationTokenArgument(method, invocation.Arguments, cancellationTokenType) && + // Verify that the current invocation is not passing a token already + if (invocation.Arguments.Any(a => a.Parameter.Type.Equals(cancellationTokenType) && !a.IsImplicit)) + { + return false; + } + + // Check if the invocation's method has either an optional implicit ct not being used or a params ct parameter not being used or an overload that takes a ct + if (!InvocationMethodTakesAToken(method, invocation.Arguments, cancellationTokenType) && !MethodHasCancellationTokenOverload(method, cancellationTokenType)) { return false; @@ -176,7 +182,7 @@ private static bool VerifyAncestorOnlyHasOneCancellationTokenAsLastArgument( } // Checks if the invocation has an optional ct argument at the end or a params ct array at the end. - private static bool InvocationHasCancellationTokenArgument(IMethodSymbol method, ImmutableArray arguments, INamedTypeSymbol cancellationTokenType) + private static bool InvocationMethodTakesAToken(IMethodSymbol method, ImmutableArray arguments, INamedTypeSymbol cancellationTokenType) { return !method.Parameters.IsEmpty && diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs index 1c9771dd6d..200e0272a3 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs @@ -103,7 +103,7 @@ async void M(CancellationToken ct) } [Fact] - public Task CS_NoDiagnostic_AlreadyPassingToken() + public Task CS_NoDiagnostic_Overload_AlreadyPassingToken() { return VerifyCS.VerifyAnalyzerAsync(@" using System.Threading; @@ -120,6 +120,22 @@ async void M(CancellationToken ct) "); } + [Fact] + public Task CS_NoDiagnostic_Default_AlreadyPassingToken() + { + return VerifyCS.VerifyAnalyzerAsync(@" +using System.Threading; +class C +{ + void M(CancellationToken ct) + { + Method(ct); + } + void Method(CancellationToken c = default) {} +} + "); + } + [Fact] public Task CS_NoDiagnostic_PassingTokenFromSource() { @@ -345,39 +361,44 @@ public void M(CancellationToken ct) return VerifyCS.VerifyAnalyzerAsync(originalCode); } - #endregion - - #region Diagnostics with no fix = C# - [Fact] - public Task CS_AnalyzerOnlyDiagnostic_OverloadWithNamedParametersUnordered() + public Task CS_NoDiagnostic_CancellationTokenSource_ParamsUsed_Order1() { - // This is a special case that will get a diagnostic but will not get a fix - // because the fixer does not currently have a way to know the overload's ct parameter name - // If the ct argument got added at the end without a name, compilation would fail with: - // CA8323: Named argument 'z' is used out-of-position but is followed by an unnamed argument - string originalCode = @" + /* + CancellationTokenSource has 3 different overloads that take CancellationToken arguments. + We should detect if a ct is passed and not offer a diagnostic, because it's considered one of the `params`. + + public class CancellationTokenSource : IDisposable + { + public static CancellationTokenSource CreateLinkedTokenSource(CancellationToken token); + public static CancellationTokenSource CreateLinkedTokenSource(CancellationToken token1, CancellationToken token2); + public static CancellationTokenSource CreateLinkedTokenSource(params CancellationToken[] tokens); + } + */ + return CS8VerifyAnalyzerAsync(@" using System.Threading; -using System.Threading.Tasks; class C { - Task M(CancellationToken ct) + void M(CancellationToken ct) { - return [|MethodAsync|](z: ""Hello world"", x: 5, y: true); + CTS.Method(ct); // Don't diagnose } - Task MethodAsync(int x, bool y = default, string z = """") => Task.CompletedTask; - Task MethodAsync(int x, bool y = default, string z = """", CancellationToken c = default) => Task.CompletedTask; } - "; - return VerifyCS.VerifyAnalyzerAsync(originalCode); +class CTS +{ + public static void Method(CancellationToken token){} + public static void Method(CancellationToken token1, CancellationToken token2){} + public static void Method(params CancellationToken[] tokens){} +} + "); } [Fact] - public Task CS_AnalyzerOnlyDiagnostic_CancellationTokenSource_ParamsEmpty() + public Task CS_NoDiagnostic_CancellationTokenSource_ParamsUsed_Order2() { /* CancellationTokenSource has 3 different overloads that take CancellationToken arguments. - When no ct is passed, because the overload that takes one instance is not setting a default value, then the analyzer considers it the `params`. + We should detect if a ct is passed and not offer a diagnostic, because it's considered one of the `params`. public class CancellationTokenSource : IDisposable { @@ -385,28 +406,142 @@ public class CancellationTokenSource : IDisposable public static CancellationTokenSource CreateLinkedTokenSource(CancellationToken token1, CancellationToken token2); public static CancellationTokenSource CreateLinkedTokenSource(params CancellationToken[] tokens); } - - In C#, the invocation for a static method includes the type and the dot */ - string originalCode = @" + return CS8VerifyAnalyzerAsync(@" using System.Threading; class C { void M(CancellationToken ct) { - CancellationTokenSource cts = [|CancellationTokenSource.CreateLinkedTokenSource|](); + CTS.Method(ct); // Don't diagnose + } +} +class CTS +{ + public static void Method(CancellationToken token){} + public static void Method(params CancellationToken[] tokens){} + public static void Method(CancellationToken token1, CancellationToken token2){} +} + "); + } + + [Fact] + public Task CS_NoDiagnostic_CancellationTokenSource_ParamsUsed_Order3() + { + return CS8VerifyAnalyzerAsync(@" +using System.Threading; +class C +{ + void M(CancellationToken ct) + { + CTS.Method(ct); // Don't diagnose + } +} +class CTS +{ + public static void Method(CancellationToken token1, CancellationToken token2){} + public static void Method(params CancellationToken[] tokens){} + public static void Method(CancellationToken token){} +} + "); + } + + [Fact] + public Task CS_NoDiagnostic_CancellationTokenSource_ParamsUsed_Order4() + { + return CS8VerifyAnalyzerAsync(@" +using System.Threading; +class C +{ + void M(CancellationToken ct) + { + CTS.Method(ct); // Don't diagnose + } +} +class CTS +{ + public static void Method(CancellationToken token1, CancellationToken token2){} + public static void Method(CancellationToken token){} + public static void Method(params CancellationToken[] tokens){} +} + "); + } + + [Fact] + public Task CS_NoDiagnostic_CancellationTokenSource_ParamsUsed_Order5() + { + return CS8VerifyAnalyzerAsync(@" +using System.Threading; +class C +{ + void M(CancellationToken ct) + { + CTS.Method(ct); // Don't diagnose + } +} +class CTS +{ + public static void Method(params CancellationToken[] tokens){} + public static void Method(CancellationToken token){} + public static void Method(CancellationToken token1, CancellationToken token2){} +} + "); + } + + [Fact] + public Task CS_NoDiagnostic_CancellationTokenSource_ParamsUsed_Order6() + { + return CS8VerifyAnalyzerAsync(@" +using System.Threading; +class C +{ + void M(CancellationToken ct) + { + CTS.Method(ct); // Don't diagnose + } +} +class CTS +{ + public static void Method(params CancellationToken[] tokens){} + public static void Method(CancellationToken token1, CancellationToken token2){} + public static void Method(CancellationToken token){} +} + "); + } + + #endregion + + #region Diagnostics with no fix = C# + + [Fact] + public Task CS_AnalyzerOnlyDiagnostic_OverloadWithNamedParametersUnordered() + { + // This is a special case that will get a diagnostic but will not get a fix + // because the fixer does not currently have a way to know the overload's ct parameter name + // If the ct argument got added at the end without a name, compilation would fail with: + // CA8323: Named argument 'z' is used out-of-position but is followed by an unnamed argument + string originalCode = @" +using System.Threading; +using System.Threading.Tasks; +class C +{ + Task M(CancellationToken ct) + { + return [|MethodAsync|](z: ""Hello world"", x: 5, y: true); } + Task MethodAsync(int x, bool y = default, string z = """") => Task.CompletedTask; + Task MethodAsync(int x, bool y = default, string z = """", CancellationToken c = default) => Task.CompletedTask; } "; return VerifyCS.VerifyAnalyzerAsync(originalCode); } [Fact] - public Task CS_AnalyzerOnlyDiagnostic_CancellationTokenSource_ParamsUsed() + public Task CS_AnalyzerOnlyDiagnostic_CancellationTokenSource_ParamsEmpty() { /* CancellationTokenSource has 3 different overloads that take CancellationToken arguments. - We should detect if a ct is passed and not offer a diagnostic, because it's considered one of the `params`. + When no ct is passed, because the overload that takes one instance is not setting a default value, then the analyzer considers it the `params`. public class CancellationTokenSource : IDisposable { @@ -423,11 +558,11 @@ class C { void M(CancellationToken ct) { - CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(ct); + CancellationTokenSource cts = [|CancellationTokenSource.CreateLinkedTokenSource|](); } } "; - return VerifyCS.VerifyAnalyzerAsync(originalCode); + return CS8VerifyAnalyzerAsync(originalCode); } #endregion @@ -1895,7 +2030,7 @@ class O } "; // Nullability is available in C# 8.0+ - return CSharp8VerifyCodeFixAsync(originalCode, fixedCode); + return CS8VerifyCodeFixAsync(originalCode, fixedCode); } [Fact] @@ -2054,7 +2189,7 @@ End Class } [Fact] - public Task VB_NoDiagnostic_AlreadyPassingToken() + public Task VB_NoDiagnostic_Overload_AlreadyPassingToken() { return VerifyVB.VerifyAnalyzerAsync(@" Imports System.Threading @@ -2073,6 +2208,21 @@ End Class "); } + [Fact] + public Task VB_NoDiagnostic_Default_AlreadyPassingToken() + { + return VerifyVB.VerifyAnalyzerAsync(@" +Imports System.Threading +Class C + Private Sub M(ByVal ct As CancellationToken) + Method(ct) + End Sub + Private Sub Method(ByVal Optional c As CancellationToken = Nothing) + End Sub +End Class + "); + } + [Fact] public Task VB_NoDiagnostic_PassingTokenFromSource() { @@ -2261,6 +2411,33 @@ End Class return VerifyVB.VerifyAnalyzerAsync(originalCode); } + [Fact] + public Task VB_NoDiagnostic_CancellationTokenSource_ParamsUsed() + { + /* + CancellationTokenSource has 3 different overloads that take CancellationToken arguments. + We should detect if a ct is passed and not offer a diagnostic, because it's considered one of the `params`. + + public class CancellationTokenSource : IDisposable + { + public static CancellationTokenSource CreateLinkedTokenSource(CancellationToken token); + public static CancellationTokenSource CreateLinkedTokenSource(CancellationToken token1, CancellationToken token2); + public static CancellationTokenSource CreateLinkedTokenSource(params CancellationToken[] tokens); + } + + Note: Unlinke C#, in VB the invocation for a static method does not include the type and the dot. + */ + string originalCode = @" +Imports System.Threading +Class C + Private Sub M(ByVal ct As CancellationToken) + Dim cts As CancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(ct) + End Sub +End Class + "; + return VB16VerifyAnalyzerAsync(originalCode); + } + #endregion #region Diagnostics with no fix = VB @@ -2316,34 +2493,7 @@ Private Sub M(ByVal ct As CancellationToken) End Sub End Class "; - return VerifyVB.VerifyAnalyzerAsync(originalCode); - } - - [Fact] - public Task VB_AnalyzerOnlyDiagnostic_CancellationTokenSource_ParamsUsed() - { - /* - CancellationTokenSource has 3 different overloads that take CancellationToken arguments. - We should detect if a ct is passed and not offer a diagnostic, because it's considered one of the `params`. - - public class CancellationTokenSource : IDisposable - { - public static CancellationTokenSource CreateLinkedTokenSource(CancellationToken token); - public static CancellationTokenSource CreateLinkedTokenSource(CancellationToken token1, CancellationToken token2); - public static CancellationTokenSource CreateLinkedTokenSource(params CancellationToken[] tokens); - } - - Note: Unlinke C#, in VB the invocation for a static method does not include the type and the dot. - */ - string originalCode = @" -Imports System.Threading -Class C - Private Sub M(ByVal ct As CancellationToken) - Dim cts As CancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(ct) - End Sub -End Class - "; - return VerifyVB.VerifyAnalyzerAsync(originalCode); + return VB16VerifyAnalyzerAsync(originalCode); } #endregion @@ -3820,12 +3970,40 @@ End Class #region Helpers + private static async Task CS8VerifyCodeFixAsync(string originalCode, string fixedCode) + { + var test = new VerifyCS.Test + { + TestCode = originalCode, + LanguageVersion = CodeAnalysis.CSharp.LanguageVersion.CSharp8, + ReferenceAssemblies = ReferenceAssemblies.NetCore.NetCoreApp50, + FixedCode = fixedCode, + }; + + test.ExpectedDiagnostics.AddRange(DiagnosticResult.EmptyDiagnosticResults); + await test.RunAsync(); + } + + private static async Task CS8VerifyAnalyzerAsync(string originalCode) + { + var test = new VerifyCS.Test + { + TestCode = originalCode, + LanguageVersion = CodeAnalysis.CSharp.LanguageVersion.CSharp8, + ReferenceAssemblies = ReferenceAssemblies.NetCore.NetCoreApp50, + }; + + test.ExpectedDiagnostics.AddRange(DiagnosticResult.EmptyDiagnosticResults); + await test.RunAsync(); + } + private static async Task VB16VerifyCodeFixAsync(string originalCode, string fixedCode) { var test = new VerifyVB.Test { TestCode = originalCode, LanguageVersion = CodeAnalysis.VisualBasic.LanguageVersion.VisualBasic16, + ReferenceAssemblies = ReferenceAssemblies.NetCore.NetCoreApp50, FixedCode = fixedCode }; @@ -3833,13 +4011,13 @@ private static async Task VB16VerifyCodeFixAsync(string originalCode, string fix await test.RunAsync(); } - private static async Task CSharp8VerifyCodeFixAsync(string originalCode, string fixedCode) + private static async Task VB16VerifyAnalyzerAsync(string originalCode) { - var test = new VerifyCS.Test + var test = new VerifyVB.Test { TestCode = originalCode, - LanguageVersion = CodeAnalysis.CSharp.LanguageVersion.CSharp8, - FixedCode = fixedCode + LanguageVersion = CodeAnalysis.VisualBasic.LanguageVersion.VisualBasic16, + ReferenceAssemblies = ReferenceAssemblies.NetCore.NetCoreApp50, }; test.ExpectedDiagnostics.AddRange(DiagnosticResult.EmptyDiagnosticResults); From b5a3fd6f3d802f335276b194cd1f7b236279b545 Mon Sep 17 00:00:00 2001 From: carlossanlop Date: Fri, 5 Jun 2020 23:22:36 -0700 Subject: [PATCH 16/20] Add method extension unit test --- ...wardCancellationTokenToInvocationsTests.cs | 86 ++++++++++++------- 1 file changed, 54 insertions(+), 32 deletions(-) diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs index 200e0272a3..27850458c3 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs @@ -298,37 +298,6 @@ async void M(CancellationToken ct) "); } - [Fact] - public Task CS_NoDiagnostic_ExtensionMethodTakesToken() - { - // The extension method is in another class - string originalCode = @" -using System; -using System.Threading; -public static class Extensions -{ - public static void MyMethod(this MyClass mc, CancellationToken c) - { - } -} -class C -{ - public void M(CancellationToken ct) - { - MyClass mc = new MyClass(); - mc.MyMethod(); - } -} -public class MyClass -{ - public void MyMethod() - { - } -} - "; - return VerifyCS.VerifyAnalyzerAsync(originalCode); - } - [Fact] public Task CS_NoDiagnostic_LambdaAndExtensionMethod() { @@ -509,6 +478,33 @@ public static void Method(CancellationToken token){} "); } + [Fact] + public Task CS_NoDiagnostic_ExtensionMethodTakesToken() + { + // The extension method is in another class, make sure the object mc is not substituted with the static class name + string originalCode = @" +using System; +using System.Threading; +class C +{ + public void M(CancellationToken ct) + { + MyClass mc = new MyClass(); + mc.MyMethod(); + } +} +public class MyClass +{ + public void MyMethod() { } +} +public static class Extensions +{ + public static void MyMethod(this MyClass mc, CancellationToken c) { } +} + "; + return CS8VerifyAnalyzerAsync(originalCode); + } + #endregion #region Diagnostics with no fix = C# @@ -2346,7 +2342,6 @@ End Class "); } - [Fact] public Task VB_NoDiagnostic_NamedTokenUnordered() { @@ -2438,6 +2433,33 @@ End Class return VB16VerifyAnalyzerAsync(originalCode); } + [Fact] + public Task VB_NoDiagnostic_ExtensionMethodTakesToken() + { + // The extension method is in another class, make sure the object mc is not substituted with the static class name + string originalCode = @" +Imports System +Imports System.Threading +Imports System.Runtime.CompilerServices +Class C + Public Sub M(ByVal ct As CancellationToken) + Dim mc As [MyClass] = New [MyClass]() + mc.MyMethod() + End Sub +End Class +Public Class [MyClass] + Public Sub MyMethod() + End Sub +End Class +Module Extensions + + Sub MyMethod(ByVal mc As [MyClass], ByVal c As CancellationToken) + End Sub +End Module + "; + return VB16VerifyAnalyzerAsync(originalCode); + } + #endregion #region Diagnostics with no fix = VB From 4061840309ec4c2afcffe48f9f9e77aa3c4431c4 Mon Sep 17 00:00:00 2001 From: carlossanlop Date: Wed, 10 Jun 2020 18:30:28 -0700 Subject: [PATCH 17/20] Address suggestions and more edge cases --- ...CancellationTokenToInvocations.Analyzer.cs | 12 +- ...ardCancellationTokenToInvocations.Fixer.cs | 57 +--- .../Core/AnalyzerReleases.Unshipped.md | 2 +- ...CancellationTokenToInvocations.Analyzer.cs | 156 ++++++--- ...ardCancellationTokenToInvocations.Fixer.cs | 91 ++---- ...wardCancellationTokenToInvocationsTests.cs | 299 ++++++++++++++---- ...CancellationTokenToInvocations.Analyzer.vb | 21 +- ...ardCancellationTokenToInvocations.Fixer.vb | 53 +--- 8 files changed, 440 insertions(+), 251 deletions(-) diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocations.Analyzer.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocations.Analyzer.cs index fbc895adc2..33e02912e2 100644 --- a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocations.Analyzer.cs +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocations.Analyzer.cs @@ -4,13 +4,16 @@ using Microsoft.NetCore.Analyzers.Runtime; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.Operations; +using System.Linq; namespace Microsoft.NetCore.CSharp.Analyzers.Runtime { [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class CSharpForwardCancellationTokenToInvocationsAnalyzer : ForwardCancellationTokenToInvocationsAnalyzer { - protected override SyntaxNode? GetMethodNameNode(SyntaxNode invocationNode) + protected override SyntaxNode? GetInvocationMethodNameNode(SyntaxNode invocationNode) { if (invocationNode is InvocationExpressionSyntax invocationExpression) { @@ -25,5 +28,12 @@ public sealed class CSharpForwardCancellationTokenToInvocationsAnalyzer : Forwar } return null; } + protected override bool ArgumentsImplicitOrNamed(INamedTypeSymbol cancellationTokenType, ImmutableArray arguments) + { + return arguments.Any(a => + (a.IsImplicit && !a.Parameter.Type.Equals(cancellationTokenType)) || + (a.Syntax is ArgumentSyntax argumentNode && argumentNode.NameColon != null)); + } + } } diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocations.Fixer.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocations.Fixer.cs index ea22350a7c..00d0593982 100644 --- a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocations.Fixer.cs +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocations.Fixer.cs @@ -44,45 +44,6 @@ protected override bool TryGetInvocation( return invocation != null; } - protected override bool TryGetAncestorDeclarationCancellationTokenParameterName( - SyntaxNode node, - [NotNullWhen(returnValue: true)] out string? parameterName) - { - parameterName = null; - - SyntaxNode currentNode = node.Parent; - IEnumerable? parameters = null; - while (currentNode != null) - { - if (currentNode is ParenthesizedLambdaExpressionSyntax lambdaNode) - { - parameters = lambdaNode.ParameterList.Parameters; - } - else if (currentNode is LocalFunctionStatementSyntax localNode) - { - parameters = localNode.ParameterList.Parameters; - } - else if (currentNode is MethodDeclarationSyntax methodNode) - { - parameters = methodNode.ParameterList.Parameters; - } - - if (parameters != null) - { - parameterName = GetCancellationTokenName(parameters); - break; - } - - currentNode = currentNode.Parent; - } - - // Unexpected CS8752: Parameter 'parameterName' must have a non-null value when exiting with 'true' - // Active issue: https://github.com/dotnet/roslyn/issues/44526 -#pragma warning disable CS8762 - return !string.IsNullOrEmpty(parameterName); -#pragma warning restore CS8762 - } - protected override bool IsArgumentNamed(IArgumentOperation argumentOperation) { return argumentOperation.Syntax is ArgumentSyntax argumentNode && argumentNode.NameColon != null; @@ -93,7 +54,21 @@ protected override SyntaxNode GetConditionalOperationInvocationExpression(Syntax return ((InvocationExpressionSyntax)invocationNode).Expression; } - private static string? GetCancellationTokenName(IEnumerable parameters) => - parameters.Last()?.Identifier.ValueText; + protected override bool TryGetExpressionAndArguments( + SyntaxNode invocationNode, + [NotNullWhen(returnValue: true)] out SyntaxNode? expression, + [NotNullWhen(returnValue: true)] out List? arguments) + { + if (invocationNode is InvocationExpressionSyntax invocationExpression) + { + expression = invocationExpression.Expression; + arguments = invocationExpression.ArgumentList.Arguments.Cast().ToList(); + return true; + } + + expression = null; + arguments = null; + return false; + } } } diff --git a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md index 5b60aa85ba..0976e81bd9 100644 --- a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md +++ b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md @@ -23,7 +23,7 @@ CA2012 | Reliability | Hidden | UseValueTasksCorrectlyAnalyzer, [Documentation]( CA2013 | Reliability | Warning | DoNotUseReferenceEqualsWithValueTypesAnalyzer, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca2013) CA2014 | Reliability | Warning | DoNotUseStackallocInLoopsAnalyzer, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca2014) CA2015 | Reliability | Warning | DoNotDefineFinalizersForTypesDerivedFromMemoryManager, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca2015) -CA2016 | Reliability | Warning | ForwardCancellationTokenToInvocationsAnalyzer, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca2016) +CA2016 | Reliability | Info | ForwardCancellationTokenToInvocationsAnalyzer, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca2016) CA2247 | Usage | Warning | DoNotCreateTaskCompletionSourceWithWrongArguments, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca2247) CA2248 | Usage | Info | DoNotCheckFlagFromDifferentEnum, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca2248) CA2249 | Usage | Info | PreferStringContainsOverIndexOfAnalyzer, [Documentation](https://docs.microsoft.com/visualstudio/code-quality/ca2249) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Analyzer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Analyzer.cs index 792e44fd12..4376bc44ab 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Analyzer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Analyzer.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -31,7 +32,11 @@ public abstract class ForwardCancellationTokenToInvocationsAnalyzer : Diagnostic { internal const string RuleId = "CA2016"; - protected abstract SyntaxNode? GetMethodNameNode(SyntaxNode invocationNode); + // Try to get the name of the method from the specified invocation + protected abstract SyntaxNode? GetInvocationMethodNameNode(SyntaxNode invocationNode); + + // Check if any of the other arguments is implicit or a named argument + protected abstract bool ArgumentsImplicitOrNamed(INamedTypeSymbol cancellationTokenType, ImmutableArray arguments); private static readonly LocalizableString s_localizableDescription = new LocalizableResourceString( nameof(MicrosoftNetCoreAnalyzersResources.ForwardCancellationTokenToInvocationsDescription), @@ -50,17 +55,21 @@ public abstract class ForwardCancellationTokenToInvocationsAnalyzer : Diagnostic MicrosoftNetCoreAnalyzersResources.ResourceManager, typeof(MicrosoftNetCoreAnalyzersResources) ); + internal static DiagnosticDescriptor ForwardCancellationTokenToInvocationsRule = DiagnosticDescriptorHelper.Create( RuleId, s_localizableTitle, s_localizableMessage, DiagnosticCategory.Reliability, - RuleLevel.BuildWarning, + RuleLevel.IdeSuggestion, s_localizableDescription, isPortedFxCopRule: false, isDataflowRule: false ); + internal const string ArgumentName = "ArgumentName"; + internal const string ParameterName = "ParameterName"; + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(ForwardCancellationTokenToInvocationsRule); @@ -73,7 +82,6 @@ public override void Initialize(AnalysisContext context) private void AnalyzeCompilationStart(CompilationStartAnalysisContext context) { - if (!context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingCancellationToken, out INamedTypeSymbol? cancellationTokenType)) { return; @@ -92,81 +100,128 @@ private void AnalyzeCompilationStart(CompilationStartAnalysisContext context) invocation, containingSymbol, cancellationTokenType, - out string? cancellationTokenParameterName)) + out string? cancellationTokenArgumentName, + out string? invocationTokenParameterName)) { return; } - // Only underline the method name, not the whole invocation - SyntaxNode? expressionNode = GetMethodNameNode(context.Operation.Syntax); - if (expressionNode != null) - { - context.ReportDiagnostic(expressionNode.CreateDiagnostic(ForwardCancellationTokenToInvocationsRule, cancellationTokenParameterName, invocation.TargetMethod.Name)); - } + // Underline only the method name, if possible + SyntaxNode? nodeToDiagnose = GetInvocationMethodNameNode(context.Operation.Syntax) ?? context.Operation.Syntax; + + ImmutableDictionary.Builder properties = ImmutableDictionary.CreateBuilder(StringComparer.Ordinal); + properties.Add(ArgumentName, cancellationTokenArgumentName); // The new argument to pass to the invocation + properties.Add(ParameterName, invocationTokenParameterName); // If the passed argument should be named, then this will be non-null + + context.ReportDiagnostic( + nodeToDiagnose.CreateDiagnostic( + rule: ForwardCancellationTokenToInvocationsRule, + properties: properties.ToImmutable(), + args: new object[] { cancellationTokenArgumentName, invocation.TargetMethod.Name })); }, OperationKind.Invocation); } // Determines if an invocation should trigger a diagnostic for this rule or not. - private static bool ShouldDiagnose( + private bool ShouldDiagnose( IInvocationOperation invocation, IMethodSymbol containingSymbol, INamedTypeSymbol cancellationTokenType, - [NotNullWhen(returnValue: true)] out string? cancellationTokenParameterName) + [NotNullWhen(returnValue: true)] out string? ancestorTokenParameterName, + out string? invocationTokenParameterName) { - cancellationTokenParameterName = null; + ancestorTokenParameterName = null; + invocationTokenParameterName = null; IMethodSymbol method = invocation.TargetMethod; - // Verify that the current invocation is not passing a token already + // Verify that the current invocation is not passing an explicitly token already if (invocation.Arguments.Any(a => a.Parameter.Type.Equals(cancellationTokenType) && !a.IsImplicit)) { return false; } - // Check if the invocation's method has either an optional implicit ct not being used or a params ct parameter not being used or an overload that takes a ct - if (!InvocationMethodTakesAToken(method, invocation.Arguments, cancellationTokenType) && - !MethodHasCancellationTokenOverload(method, cancellationTokenType)) + IMethodSymbol? overload = null; + // Check if the invocation's method has either an optional implicit ct at the end not being used, or a params ct parameter at the end not being used + if (InvocationMethodTakesAToken(method, invocation.Arguments, cancellationTokenType)) + { + if (ArgumentsImplicitOrNamed(cancellationTokenType, invocation.Arguments)) + { + invocationTokenParameterName = invocation.TargetMethod.Parameters.Last().Name; + } + } + // or an overload that takes a ct at the end + else if (MethodHasCancellationTokenOverload(method, cancellationTokenType, out overload)) + { + if (ArgumentsImplicitOrNamed(cancellationTokenType, invocation.Arguments)) + { + invocationTokenParameterName = overload.Parameters.Last().Name; + } + } + else { return false; } - // Check if the ancestor method has a ct that we can pass to the invocation - if (!VerifyAncestorOnlyHasOneCancellationTokenAsLastArgument(cancellationTokenType, GetContainingSymbol(invocation, containingSymbol), out cancellationTokenParameterName)) + // Check if there is an ancestor method that has a ct that we can pass to the invocation + if (!TryGetClosestAncestorThatTakesAToken(invocation, containingSymbol, cancellationTokenType, out IMethodSymbol? ancestor, out ancestorTokenParameterName)) { return false; } + // Finally, if the ct is in an overload method, but adding the ancestor's ct to the current + // invocation would cause the new signature to become a recursive call, avoid creating a diagnostic + if (overload != null && overload == ancestor) + { + ancestorTokenParameterName = null; + return false; + } + return true; } - // Try to find the most immediate containing symbol (anonymous or local function). If none is found, return the context containing symbol. - private static IMethodSymbol GetContainingSymbol(IInvocationOperation invocation, IMethodSymbol containingSymbol) + // Try to find the most immediate containing symbol (anonymous or local function). Returns true. + // If none is found, return the context containing symbol. Returns false. + private static bool TryGetClosestAncestorThatTakesAToken( + IInvocationOperation invocation, + IMethodSymbol containingSymbol, + INamedTypeSymbol cancellationTokenType, + [NotNullWhen(returnValue: true)] out IMethodSymbol? ancestor, + [NotNullWhen(returnValue: true)] out string? cancellationTokenParameterName) { IOperation currentOperation = invocation.Parent; - while (currentOperation != null) { + ancestor = null; + if (currentOperation.Kind == OperationKind.AnonymousFunction) { - return ((IAnonymousFunctionOperation)currentOperation).Symbol; + ancestor = ((IAnonymousFunctionOperation)currentOperation).Symbol; } else if (currentOperation.Kind == OperationKind.LocalFunction) { - return ((ILocalFunctionOperation)currentOperation).Symbol; + ancestor = ((ILocalFunctionOperation)currentOperation).Symbol; + } + + // When the current ancestor does not contain a ct, will continue with the next ancestor + if (ancestor != null && TryGetTokenParamName(ancestor, cancellationTokenType, out cancellationTokenParameterName)) + { + return true; } currentOperation = currentOperation.Parent; } - return containingSymbol; + // Last resort: fallback to the containing symbol + ancestor = containingSymbol; + return TryGetTokenParamName(ancestor, cancellationTokenType, out cancellationTokenParameterName); } // Check if the method only takes one ct and is the last parameter in the method signature. // We want to compare the current method signature to any others with the exact same arguments in the exact same order. - private static bool VerifyAncestorOnlyHasOneCancellationTokenAsLastArgument( - INamedTypeSymbol cancellationTokenType, + private static bool TryGetTokenParamName( IMethodSymbol methodDeclaration, + INamedTypeSymbol cancellationTokenType, [NotNullWhen(returnValue: true)] out string? cancellationTokenParameterName) { if (methodDeclaration.Parameters.Count(x => x.Type.Equals(cancellationTokenType)) == 1 && @@ -182,33 +237,40 @@ private static bool VerifyAncestorOnlyHasOneCancellationTokenAsLastArgument( } // Checks if the invocation has an optional ct argument at the end or a params ct array at the end. - private static bool InvocationMethodTakesAToken(IMethodSymbol method, ImmutableArray arguments, INamedTypeSymbol cancellationTokenType) + private static bool InvocationMethodTakesAToken( + IMethodSymbol method, + ImmutableArray arguments, + INamedTypeSymbol cancellationTokenType) { return !method.Parameters.IsEmpty && - method.Parameters[method.Parameters.Length - 1] is IParameterSymbol lastParameter && + method.Parameters[^1] is IParameterSymbol lastParameter && (InvocationIgnoresOptionalCancellationToken(lastParameter, arguments, cancellationTokenType) || InvocationIsUsingParamsCancellationToken(lastParameter, arguments, cancellationTokenType)); } // Check if the currently used overload is the one that takes the ct, but is utilizing the default value offered in the method signature. // We want to offer a diagnostic for this case, so the user explicitly passes the ancestor's ct. - private static bool InvocationIgnoresOptionalCancellationToken(IParameterSymbol lastParameter, ImmutableArray arguments, INamedTypeSymbol cancellationTokenType) + private static bool InvocationIgnoresOptionalCancellationToken( + IParameterSymbol lastParameter, + ImmutableArray arguments, + INamedTypeSymbol cancellationTokenType) { if (lastParameter.Type.Equals(cancellationTokenType) && lastParameter.IsOptional) // Has a default value being used { // Find out if the ct argument is using the default value - return arguments.Any(x => - x.Parameter.Type.Equals(cancellationTokenType) && - x.ArgumentKind == ArgumentKind.DefaultValue); // The default value is being used + // Need to check among all arguments in case the user is passing them named and unordered (despite the ct being defined as the last parameter) + return arguments.Any(a => a.Parameter.Type.Equals(cancellationTokenType) && a.ArgumentKind == ArgumentKind.DefaultValue); } - return false; } // Checks if the method has a `params CancellationToken[]` argument in the last position and ensure no ct is being passed. - private static bool InvocationIsUsingParamsCancellationToken(IParameterSymbol lastParameter, ImmutableArray arguments, INamedTypeSymbol cancellationTokenType) + private static bool InvocationIsUsingParamsCancellationToken( + IParameterSymbol lastParameter, + ImmutableArray arguments, + INamedTypeSymbol cancellationTokenType) { if (lastParameter.IsParams && lastParameter.Type.Kind == SymbolKind.ArrayType && @@ -230,31 +292,35 @@ lastParameter.Type is IArrayTypeSymbol arrayTypeSymbol && } // Check if there's a method overload with the same parameters as this one, in the same order, plus a ct at the end. - private static bool MethodHasCancellationTokenOverload(IMethodSymbol method, ITypeSymbol cancellationTokenType) + private static bool MethodHasCancellationTokenOverload( + IMethodSymbol method, + ITypeSymbol cancellationTokenType, + [NotNullWhen(returnValue: true)] out IMethodSymbol? overload) { - - IMethodSymbol? overload = method.ContainingType.GetMembers(method.Name) - .OfType() - .FirstOrDefault(methodToCompare => - HasSameParametersPlusCancellationToken(cancellationTokenType, method, methodToCompare)); + overload = method.ContainingType.GetMembers(method.Name) + .OfType() + .FirstOrDefault(methodToCompare => + HasSameParametersPlusCancellationToken(cancellationTokenType, method, methodToCompare)); return overload != null; // Checks if the parameters of the two passed methods only differ in a ct. static bool HasSameParametersPlusCancellationToken(ITypeSymbol cancellationTokenType, IMethodSymbol originalMethod, IMethodSymbol methodToCompare) { - if (originalMethod.Equals(methodToCompare) || - methodToCompare.Parameters.Length != originalMethod.Parameters.Length + 1 || - !methodToCompare.Parameters[methodToCompare.Parameters.Length - 1].Type.Equals(cancellationTokenType)) // Covers the case when using an alias for ct + // Avoid comparing to itself, or when there are no parameters, or when the last parameter is not a ct + if (originalMethod.Equals(methodToCompare.Name) || + methodToCompare.Parameters.Count(p => p.Type.Equals(cancellationTokenType)) != 1 || + !methodToCompare.Parameters[^1].Type.Equals(cancellationTokenType)) { return false; } + // Now compare the types of all parameters before the ct for (int i = 0; i < originalMethod.Parameters.Length; i++) { IParameterSymbol? originalParameter = originalMethod.Parameters[i]; IParameterSymbol? comparedParameter = methodToCompare.Parameters[i]; - if (originalParameter == null || comparedParameter == null || !originalParameter.Type.Equals(comparedParameter.Type)) // Covers the case when using an alias for ct + if (originalParameter == null || comparedParameter == null || !originalParameter.Type.Equals(comparedParameter.Type)) { return false; } diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs index b1451decd8..2c338611e4 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs @@ -24,11 +24,11 @@ protected abstract bool TryGetInvocation( CancellationToken ct, [NotNullWhen(returnValue: true)] out IInvocationOperation? invocation); - // Looks for a ct parameter in the ancestor method or function declaration. If one is found, retrieve the name of the parameter. - // Returns true if a ct parameter was found and parameterName is not null or empty. Returns false otherwise. - protected abstract bool TryGetAncestorDeclarationCancellationTokenParameterName( - SyntaxNode node, - [NotNullWhen(returnValue: true)] out string? parameterName); + // Retrieves the invocation expression node and the invocation argument list + protected abstract bool TryGetExpressionAndArguments( + SyntaxNode invocationNode, + [NotNullWhen(returnValue: true)] out SyntaxNode? expression, + [NotNullWhen(returnValue: true)] out List? arguments); // Verifies if the specified argument was passed with an explicit name. protected abstract bool IsArgumentNamed(IArgumentOperation argumentOperation); @@ -61,14 +61,23 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) return; } - if (!TryGetAncestorDeclarationCancellationTokenParameterName(node, out string? parameterName)) + ImmutableDictionary? properties = context.Diagnostics[0].Properties; + + // The name that identifies the object that is to be passed + if (!properties.TryGetValue(ForwardCancellationTokenToInvocationsAnalyzer.ArgumentName, out string argumentName) || string.IsNullOrEmpty(argumentName)) + { + return; + } + + // If the invocation requires the token to be passed with a name, use this + if (!properties.TryGetValue(ForwardCancellationTokenToInvocationsAnalyzer.ParameterName, out string parameterName)) { return; } string title = MicrosoftNetCoreAnalyzersResources.ForwardCancellationTokenToInvocationsTitle; - if (!TryGenerateNewDocumentRoot(doc, root, invocation, parameterName, out SyntaxNode? newRoot)) + if (!TryGenerateNewDocumentRoot(doc, root, invocation, argumentName, parameterName, out SyntaxNode? newRoot)) { return; } @@ -88,81 +97,37 @@ private bool TryGenerateNewDocumentRoot( Document doc, SyntaxNode root, IInvocationOperation invocation, - string cancellationTokenParameterName, + string invocationTokenArgumentName, + string ancestorTokenParameterName, [NotNullWhen(returnValue: true)] out SyntaxNode? newRoot) { newRoot = null; SyntaxGenerator generator = SyntaxGenerator.GetGenerator(doc); - // Pass the same arguments and add the ct - List newArguments = new List(); - bool shouldTokenUseName = false; - string paramName = string.Empty; - - // In C#, invocation.Arguments contains the arguments in the order passed by the user - // In VB, invocation.Arguments contains the arguments in the official parameter order - for (int i = 0; i < invocation.Arguments.Length; i++) + if (!TryGetExpressionAndArguments(invocation.Syntax, out SyntaxNode? expression, out List? newArguments)) { - IArgumentOperation argument = invocation.Arguments[i]; - - // The type name is detected even if using an alias - if (!argument.Parameter.Type.Name.Equals("CancellationToken", StringComparison.Ordinal)) - { - if (!argument.IsImplicit) - { - SyntaxNode newArg; - if (IsArgumentNamed(argument)) - { - newArg = generator.Argument(argument.Parameter.Name, argument.Parameter.RefKind, argument.Value.Syntax); - shouldTokenUseName = true; - } - else - { - newArg = argument.Syntax; - } - newArguments.Add(newArg); - } - else - { - shouldTokenUseName = true; - } - } - else - { - // Only reachable if the current method is the one that contains the ct - // Won't be reached if it's an overload that contains the paramName - paramName = argument.Parameter.Name; - } + return false; } - // Create and append new ct argument to pass to the invocation, using the ancestor method parameter name - SyntaxNode cancellationTokenIdentifier = generator.IdentifierName(cancellationTokenParameterName); - SyntaxNode cancellationTokenNode; - if (shouldTokenUseName) + SyntaxNode identifier = generator.IdentifierName(invocationTokenArgumentName); + SyntaxNode cancellationTokenArgument; + if (!string.IsNullOrEmpty(ancestorTokenParameterName)) { - // If the paramName is unknown at this point, it's because an overload contains the ct parameter - // and since it cannot be obtained, no fix will be provided or else CA8323 shows up: - // CA8323: Named argument 'argName' is used out-of-position but is followed by an unnamed argument - if (string.IsNullOrEmpty(paramName)) - { - return false; - } - - cancellationTokenNode = generator.Argument(paramName, RefKind.None, cancellationTokenIdentifier); + cancellationTokenArgument = generator.Argument(ancestorTokenParameterName, RefKind.None, identifier); } else { - cancellationTokenNode = generator.Argument(cancellationTokenIdentifier); + cancellationTokenArgument = generator.Argument(identifier); } - newArguments.Add(cancellationTokenNode); + + newArguments.Add(cancellationTokenArgument); SyntaxNode newInvocation; // The instance is null when calling a static method from another type if (invocation.Instance == null) { - SyntaxNode staticType = generator.TypeExpressionForStaticMemberAccess(invocation.TargetMethod.ContainingType); - newInvocation = generator.MemberAccessExpression(staticType, invocation.TargetMethod.Name); + newInvocation = expression; } // The method is being invoked with nullability else if (invocation.Instance is IConditionalAccessInstanceOperation) diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs index 27850458c3..72f076e94c 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs @@ -298,38 +298,6 @@ async void M(CancellationToken ct) "); } - [Fact] - public Task CS_NoDiagnostic_LambdaAndExtensionMethod() - { - // Avoid triggering a diagnostic if the immediate ancestor is an anonymous function and the parameter type is not ct - string originalCode = @" -using System; -using System.Threading; -public static class Extensions -{ - public static void Extension(this bool b, Action action) - { - } - public static void MyMethod(this int i, CancellationToken c = default) - { - } -} -class C -{ - public void M(CancellationToken ct) - { - bool b = false; - b.Extension((j) => - { - Console.WriteLine(""Hello world""); - j.MyMethod(); - }); - } -} - "; - return VerifyCS.VerifyAnalyzerAsync(originalCode); - } - [Fact] public Task CS_NoDiagnostic_CancellationTokenSource_ParamsUsed_Order1() { @@ -2029,6 +1997,63 @@ class O return CS8VerifyCodeFixAsync(originalCode, fixedCode); } + [Fact] + public Task CS_Diagnostic_LambdaAndExtensionMethod() + { + string originalCode = @" +using System; +using System.Threading; +public static class Extensions +{ + public static void Extension(this bool b, Action action) + { + } + public static void MyMethod(this int i, CancellationToken c = default) + { + } +} +class C +{ + public void M(CancellationToken ct) + { + bool b = false; + b.Extension((j) => + { + Console.WriteLine(""Hello world""); + [|j.MyMethod|](); + }); + } +} + "; + // Notice the ct is passed with a name, because "this int i" is considered implicit + string fixedCode = @" +using System; +using System.Threading; +public static class Extensions +{ + public static void Extension(this bool b, Action action) + { + } + public static void MyMethod(this int i, CancellationToken c = default) + { + } +} +class C +{ + public void M(CancellationToken ct) + { + bool b = false; + b.Extension((j) => + { + Console.WriteLine(""Hello world""); + j.MyMethod(c: ct); + }); + } +} + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + [Fact] public Task CS_Diagnostic_WithTrivia() { @@ -2095,6 +2120,71 @@ void MethodOverloadWithArguments(int x, CancellationToken c) {} return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); } + [Theory] + [InlineData("CancellationToken c", "", "", "")] + [InlineData("", "CancellationToken c", "", "")] + [InlineData("", "", "", "c")] + public Task CS_Diagnostic_MultiNesting(string topMethodParam, string localMethodParam, string extensionTypeParam, string extensionArg) + { + string originalCode = $@" +using System; +using System.Threading; +using System.Threading.Tasks; +public static class Extensions +{{ + public static void Extension(this bool b, Action{extensionTypeParam} action) {{}} +}} +class C +{{ + private readonly object lockingObject = new object(); + public void TopMethod({topMethodParam}) + {{ + void LocalMethod({localMethodParam}) + {{ + bool b = false; + b.Extension(({extensionArg}) => + {{ + lock (lockingObject) + {{ + [|TokenMethod|](); + }} + }}); + }} + }} + void TokenMethod(CancellationToken ct = default) {{}} +}} + "; + string fixedCode = $@" +using System; +using System.Threading; +using System.Threading.Tasks; +public static class Extensions +{{ + public static void Extension(this bool b, Action{extensionTypeParam} action) {{}} +}} +class C +{{ + private readonly object lockingObject = new object(); + public void TopMethod({topMethodParam}) + {{ + void LocalMethod({localMethodParam}) + {{ + bool b = false; + b.Extension(({extensionArg}) => + {{ + lock (lockingObject) + {{ + TokenMethod(c); + }} + }}); + }} + }} + void TokenMethod(CancellationToken ct = default) {{}} +}} + "; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + #endregion #region No Diagnostic - VB @@ -2379,33 +2469,6 @@ End Class "); } - [Fact] - public Task VB_NoDiagnostic_LambdaAndExtensionMethod() - { - // Avoid triggering a diagnostic if the immediate ancestor is an anonymous function and the parameter type is not ct - string originalCode = @" -Imports System -Imports System.Threading -Imports System.Runtime.CompilerServices -Module Extensions - - Sub MyMethod(ByVal mc As [MyClass], ByVal c As CancellationToken) - End Sub -End Module -Class C - Public Sub M(ByVal ct As CancellationToken) - Dim mc As [MyClass] = New [MyClass]() - mc.MyMethod() - End Sub -End Class -Public Class [MyClass] - Public Sub MyMethod() - End Sub -End Class - "; - return VerifyVB.VerifyAnalyzerAsync(originalCode); - } - [Fact] public Task VB_NoDiagnostic_CancellationTokenSource_ParamsUsed() { @@ -3804,12 +3867,12 @@ Return 1 End Function End Class "; - // Notice the parameters get reordered to their official position + // Notice the order is preserved and the missing implicit parameters are appended as they are found string fixedCode = @" Imports System.Threading Class C Private Function M(ByVal ct As CancellationToken) As Integer - Return MyMethod(x:=5, y:=true, z:=""Hello world"", c:=ct) + Return MyMethod(z:=""Hello world"", x:=5, y:=true, c:=ct) End Function Private Function MyMethod(ByVal x As Integer, ByVal Optional y As Boolean = false, ByVal Optional z As String = """", ByVal Optional c As CancellationToken = Nothing) As Integer Return 1 @@ -3898,6 +3961,63 @@ End Structure return VB16VerifyCodeFixAsync(originalCode, fixedCode); } + [Fact] + public Task VB_Diagnostic_LambdaAndExtensionMethod() + { + // The ct parameter is not in the closest ancestor method (anonymous) + string originalCode = @" +Imports System +Imports System.Threading +Imports System.Runtime.CompilerServices + +Module Extensions + + Sub Extension(ByVal b As Boolean, ByVal action As Action(Of Integer)) + End Sub + + + Sub MyMethod(ByVal i As Integer, ByVal Optional c As CancellationToken = Nothing) + End Sub +End Module + +Class C + Public Sub M(ByVal ct As CancellationToken) + Dim b As Boolean = False + b.Extension(Sub(j) + Console.WriteLine(""Hello, world"") + j.[|MyMethod|]() + End Sub) + End Sub +End Class + "; + string fixedCode = @" +Imports System +Imports System.Threading +Imports System.Runtime.CompilerServices + +Module Extensions + + Sub Extension(ByVal b As Boolean, ByVal action As Action(Of Integer)) + End Sub + + + Sub MyMethod(ByVal i As Integer, ByVal Optional c As CancellationToken = Nothing) + End Sub +End Module + +Class C + Public Sub M(ByVal ct As CancellationToken) + Dim b As Boolean = False + b.Extension(Sub(j) + Console.WriteLine(""Hello, world"") + j.MyMethod(ct) + End Sub) + End Sub +End Class + "; + return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); + } + [Fact] public Task VB_Diagnostic_WithTrivia() { @@ -3988,6 +4108,63 @@ End Class return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); } + [Theory] + [InlineData("c As CancellationToken", "", "")] + [InlineData("", "(Of CancellationToken)", "c")] + public Task VB_Diagnostic_MultiNesting(string topMethodParam, string extensionTypeParam, string extensionArg) + { + // Local methods do not exist in VB, it's the only difference with the CS mirror test + string originalCode = $@" +Imports System +Imports System.Threading +Imports System.Threading.Tasks +Imports System.Runtime.CompilerServices +Module Extensions + + Sub Extension(ByVal b As Boolean, ByVal action As Action{extensionTypeParam}) + End Sub +End Module +Class C + Private ReadOnly lockingObject As Object = New Object() + Public Sub TopMethod({topMethodParam}) + Dim b As Boolean = False + b.Extension(Sub({extensionArg}) + SyncLock lockingObject + TokenMethod(c) + End SyncLock + End Sub) + End Sub + Private Sub TokenMethod(ByVal Optional ct As CancellationToken = Nothing) + End Sub +End Class + "; + string fixedCode = $@" +Imports System +Imports System.Threading +Imports System.Threading.Tasks +Imports System.Runtime.CompilerServices +Module Extensions + + Sub Extension(ByVal b As Boolean, ByVal action As Action{extensionTypeParam}) + End Sub +End Module +Class C + Private ReadOnly lockingObject As Object = New Object() + Public Sub TopMethod({topMethodParam}) + Dim b As Boolean = False + b.Extension(Sub({extensionArg}) + SyncLock lockingObject + TokenMethod(c) + End SyncLock + End Sub) + End Sub + Private Sub TokenMethod(ByVal Optional ct As CancellationToken = Nothing) + End Sub +End Class + "; + return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); + } + #endregion #region Helpers diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocations.Analyzer.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocations.Analyzer.vb index 7bade163ee..8333f54626 100644 --- a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocations.Analyzer.vb +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocations.Analyzer.vb @@ -1,7 +1,9 @@ ' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +Imports System.Collections.Immutable Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.Diagnostics +Imports Microsoft.CodeAnalysis.Operations Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Imports Microsoft.NetCore.Analyzers.Runtime @@ -12,7 +14,8 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime Inherits ForwardCancellationTokenToInvocationsAnalyzer - Protected Overrides Function GetMethodNameNode(invocationNode As SyntaxNode) As SyntaxNode + + Protected Overrides Function GetInvocationMethodNameNode(invocationNode As SyntaxNode) As SyntaxNode Dim invocationExpression = TryCast(invocationNode, InvocationExpressionSyntax) @@ -34,6 +37,22 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime End Function + Protected Overrides Function ArgumentsImplicitOrNamed(cancellationTokenType As INamedTypeSymbol, arguments As ImmutableArray(Of IArgumentOperation)) As Boolean + + Return arguments.Any(Function(a) + + If a.IsImplicit AndAlso Not a.Parameter.Type.Equals(cancellationTokenType) Then + Return True + End If + + Dim argumentNode As ArgumentSyntax = TryCast(a.Syntax, ArgumentSyntax) + + Return argumentNode IsNot Nothing AndAlso argumentNode.IsNamed + + End Function) + + End Function + End Class End Namespace diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocations.Fixer.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocations.Fixer.vb index 8a491455f5..7fddba07e0 100644 --- a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocations.Fixer.vb +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocations.Fixer.vb @@ -33,41 +33,6 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime End Function - Protected Overrides Function TryGetAncestorDeclarationCancellationTokenParameterName(node As SyntaxNode, ByRef parameterName As String) As Boolean - - parameterName = Nothing - - Dim currentNode As SyntaxNode = node.Parent - Dim parameters As IEnumerable(Of ParameterSyntax) = Nothing - While currentNode IsNot Nothing - - Dim singleLineLambda As SingleLineLambdaExpressionSyntax = TryCast(currentNode, SingleLineLambdaExpressionSyntax) - Dim multiLineLambda As MultiLineLambdaExpressionSyntax = TryCast(currentNode, MultiLineLambdaExpressionSyntax) - Dim methodStatement As MethodStatementSyntax = TryCast(currentNode, MethodStatementSyntax) - Dim methodBlock As MethodBlockSyntax = TryCast(currentNode, MethodBlockSyntax) - - If singleLineLambda IsNot Nothing Then - parameters = singleLineLambda.SubOrFunctionHeader.ParameterList.Parameters - ElseIf multiLineLambda IsNot Nothing Then - parameters = multiLineLambda.SubOrFunctionHeader.ParameterList.Parameters - ElseIf methodStatement IsNot Nothing Then - parameters = methodStatement.ParameterList.Parameters - ElseIf methodBlock IsNot Nothing Then - parameters = methodBlock.SubOrFunctionStatement.ParameterList.Parameters - End If - - If parameters IsNot Nothing Then - parameterName = GetCancellationTokenName(parameters) - Exit While - End If - - currentNode = currentNode.Parent - - End While - - Return Not String.IsNullOrEmpty(parameterName) - End Function - Protected Overrides Function IsArgumentNamed(argumentOperation As IArgumentOperation) As Boolean Dim argument As SimpleArgumentSyntax = TryCast(argumentOperation.Syntax, SimpleArgumentSyntax) Return argument IsNot Nothing AndAlso argument.NameColonEquals IsNot Nothing @@ -80,10 +45,22 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime End Function - Private Shared Function GetCancellationTokenName(parameters As IEnumerable(Of ParameterSyntax)) As String - Dim lastParameter As ParameterSyntax = parameters.Last() + Protected Overrides Function TryGetExpressionAndArguments(invocationNode As SyntaxNode, ByRef expression As SyntaxNode, ByRef arguments As List(Of SyntaxNode)) As Boolean + + Dim invocationExpression As InvocationExpressionSyntax = TryCast(invocationNode, InvocationExpressionSyntax) + + If invocationExpression IsNot Nothing Then + + expression = invocationExpression.Expression + arguments = invocationExpression.ArgumentList.Arguments.Cast(Of SyntaxNode)().ToList() + Return True + + End If + + expression = Nothing + arguments = Nothing + Return False - Return lastParameter?.Identifier.Identifier.ValueText End Function End Class From 8b2f0dc44caf43ee6561083516c70a2de17c9383 Mon Sep 17 00:00:00 2001 From: carlossanlop Date: Wed, 17 Jun 2020 14:46:07 -0700 Subject: [PATCH 18/20] Address latest suggestions, fixed one VB case where extension method parameters were not detected correctly --- RoslynAnalyzers.sln | 17 ---- ...ardCancellationTokenToInvocations.Fixer.cs | 22 ++---- .../MicrosoftNetCoreAnalyzersResources.resx | 6 +- ...CancellationTokenToInvocations.Analyzer.cs | 79 ++++++++++--------- ...ardCancellationTokenToInvocations.Fixer.cs | 35 ++++---- .../MicrosoftNetCoreAnalyzersResources.cs.xlf | 12 +-- .../MicrosoftNetCoreAnalyzersResources.de.xlf | 12 +-- .../MicrosoftNetCoreAnalyzersResources.es.xlf | 12 +-- .../MicrosoftNetCoreAnalyzersResources.fr.xlf | 12 +-- .../MicrosoftNetCoreAnalyzersResources.it.xlf | 12 +-- .../MicrosoftNetCoreAnalyzersResources.ja.xlf | 12 +-- .../MicrosoftNetCoreAnalyzersResources.ko.xlf | 12 +-- .../MicrosoftNetCoreAnalyzersResources.pl.xlf | 12 +-- ...crosoftNetCoreAnalyzersResources.pt-BR.xlf | 12 +-- .../MicrosoftNetCoreAnalyzersResources.ru.xlf | 12 +-- .../MicrosoftNetCoreAnalyzersResources.tr.xlf | 12 +-- ...osoftNetCoreAnalyzersResources.zh-Hans.xlf | 12 +-- ...osoftNetCoreAnalyzersResources.zh-Hant.xlf | 12 +-- ...wardCancellationTokenToInvocationsTests.cs | 4 - 19 files changed, 147 insertions(+), 172 deletions(-) diff --git a/RoslynAnalyzers.sln b/RoslynAnalyzers.sln index 8a27038e52..74c774ddef 100644 --- a/RoslynAnalyzers.sln +++ b/RoslynAnalyzers.sln @@ -166,7 +166,6 @@ Global src\Utilities\Workspaces\Workspaces.Utilities.projitems*{1c62a2f6-7a44-4030-9d1a-7e7a966736e5}*SharedItemsImports = 5 src\Utilities\Compiler\Analyzer.Utilities.projitems*{2a4af3a4-307b-4252-888e-e6546244585a}*SharedItemsImports = 5 src\Utilities\Workspaces\Workspaces.Utilities.projitems*{2a4af3a4-307b-4252-888e-e6546244585a}*SharedItemsImports = 5 - src\Utilities\Compiler\Analyzer.Utilities.projitems*{2a520f3e-8c5d-43fe-aa03-fe5e3c5f23d1}*SharedItemsImports = 5 src\Utilities\Compiler\Analyzer.Utilities.projitems*{730c2d1c-0276-4132-85ea-675ce60068bb}*SharedItemsImports = 5 src\Utilities\Workspaces\Workspaces.Utilities.projitems*{730c2d1c-0276-4132-85ea-675ce60068bb}*SharedItemsImports = 5 src\Utilities\Workspaces\Workspaces.Utilities.projitems*{99f594b1-3916-471d-a761-a6731fc50e9a}*SharedItemsImports = 13 @@ -265,22 +264,6 @@ Global {0A7C9A18-888A-4028-B88F-989DA6A0140B}.Debug|Any CPU.Build.0 = Debug|Any CPU {0A7C9A18-888A-4028-B88F-989DA6A0140B}.Release|Any CPU.ActiveCfg = Release|Any CPU {0A7C9A18-888A-4028-B88F-989DA6A0140B}.Release|Any CPU.Build.0 = Release|Any CPU - {2A520F3E-8C5D-43FE-AA03-FE5E3C5F23D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2A520F3E-8C5D-43FE-AA03-FE5E3C5F23D1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2A520F3E-8C5D-43FE-AA03-FE5E3C5F23D1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2A520F3E-8C5D-43FE-AA03-FE5E3C5F23D1}.Release|Any CPU.Build.0 = Release|Any CPU - {D70D8CBB-1AD0-49DE-83A8-07990915A843}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D70D8CBB-1AD0-49DE-83A8-07990915A843}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D70D8CBB-1AD0-49DE-83A8-07990915A843}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D70D8CBB-1AD0-49DE-83A8-07990915A843}.Release|Any CPU.Build.0 = Release|Any CPU - {390DBE36-C8AF-4882-BB5D-87EFBF06B8A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {390DBE36-C8AF-4882-BB5D-87EFBF06B8A8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {390DBE36-C8AF-4882-BB5D-87EFBF06B8A8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {390DBE36-C8AF-4882-BB5D-87EFBF06B8A8}.Release|Any CPU.Build.0 = Release|Any CPU - {722141A2-91FE-411A-A64F-A2A2A7150122}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {722141A2-91FE-411A-A64F-A2A2A7150122}.Debug|Any CPU.Build.0 = Debug|Any CPU - {722141A2-91FE-411A-A64F-A2A2A7150122}.Release|Any CPU.ActiveCfg = Release|Any CPU - {722141A2-91FE-411A-A64F-A2A2A7150122}.Release|Any CPU.Build.0 = Release|Any CPU {774B5DF6-D14F-4839-95F3-D3632B754765}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {774B5DF6-D14F-4839-95F3-D3632B754765}.Debug|Any CPU.Build.0 = Debug|Any CPU {774B5DF6-D14F-4839-95F3-D3632B754765}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocations.Fixer.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocations.Fixer.cs index 00d0593982..f806326e6a 100644 --- a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocations.Fixer.cs +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocations.Fixer.cs @@ -6,6 +6,7 @@ using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Operations; using Microsoft.NetCore.Analyzers.Runtime; @@ -21,25 +22,14 @@ protected override bool TryGetInvocation( CancellationToken ct, [NotNullWhen(true)] out IInvocationOperation? invocation) { - invocation = null; - - IOperation operation; - // If the method was invoked using nullability for the case of attempting to dereference a possibly null reference, // then the node.Parent.Parent is the actual invocation (and it will contain the dot as well) - if (node.Parent is MemberBindingExpressionSyntax) - { - operation = model.GetOperation(node.Parent.Parent, ct); - } - else - { - operation = model.GetOperation(node.Parent, ct); - } - if (operation is IInvocationOperation invocationOperation) - { - invocation = invocationOperation; - } + var operation = node.Parent.IsKind(SyntaxKind.MemberBindingExpression) + ? model.GetOperation(node.Parent.Parent, ct) + : model.GetOperation(node.Parent, ct); + + invocation = operation as IInvocationOperation; return invocation != null; } diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx index e3f8c6195b..c29f577d43 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx @@ -1279,7 +1279,7 @@ Potential stack overflow. Move the stackalloc out of the loop. - Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync'. + Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' 'Stream' has a 'ReadAsync' overload that takes a 'Memory<Byte>' as the first argument, and a 'WriteAsync' overload that takes a 'ReadOnlyMemory<Byte>' as the first argument. Prefer calling the memory based overloads, which are more efficient. @@ -1291,10 +1291,10 @@ Forward the 'CancellationToken' parameter to methods that take one to ensure the operation cancellation notifications gets properly propagated, or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token - Forward the 'CancellationToken' parameter to methods that take one. + Forward the 'CancellationToken' parameter to methods that take one Change to call the two argument constructor, pass null for the message. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Analyzer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Analyzer.cs index 4376bc44ab..9ac330160b 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Analyzer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Analyzer.cs @@ -38,23 +38,11 @@ public abstract class ForwardCancellationTokenToInvocationsAnalyzer : Diagnostic // Check if any of the other arguments is implicit or a named argument protected abstract bool ArgumentsImplicitOrNamed(INamedTypeSymbol cancellationTokenType, ImmutableArray arguments); - private static readonly LocalizableString s_localizableDescription = new LocalizableResourceString( - nameof(MicrosoftNetCoreAnalyzersResources.ForwardCancellationTokenToInvocationsDescription), - MicrosoftNetCoreAnalyzersResources.ResourceManager, - typeof(MicrosoftNetCoreAnalyzersResources) - ); + private static readonly LocalizableString s_localizableDescription = new LocalizableResourceString(nameof(MicrosoftNetCoreAnalyzersResources.ForwardCancellationTokenToInvocationsDescription), MicrosoftNetCoreAnalyzersResources.ResourceManager, typeof(MicrosoftNetCoreAnalyzersResources)); - private static readonly LocalizableString s_localizableMessage = new LocalizableResourceString( - nameof(MicrosoftNetCoreAnalyzersResources.ForwardCancellationTokenToInvocationsMessage), - MicrosoftNetCoreAnalyzersResources.ResourceManager, - typeof(MicrosoftNetCoreAnalyzersResources) - ); + private static readonly LocalizableString s_localizableMessage = new LocalizableResourceString(nameof(MicrosoftNetCoreAnalyzersResources.ForwardCancellationTokenToInvocationsMessage), MicrosoftNetCoreAnalyzersResources.ResourceManager, typeof(MicrosoftNetCoreAnalyzersResources)); - private static readonly LocalizableString s_localizableTitle = new LocalizableResourceString( - nameof(MicrosoftNetCoreAnalyzersResources.ForwardCancellationTokenToInvocationsTitle), - MicrosoftNetCoreAnalyzersResources.ResourceManager, - typeof(MicrosoftNetCoreAnalyzersResources) - ); + private static readonly LocalizableString s_localizableTitle = new LocalizableResourceString(nameof(MicrosoftNetCoreAnalyzersResources.ForwardCancellationTokenToInvocationsTitle), MicrosoftNetCoreAnalyzersResources.ResourceManager, typeof(MicrosoftNetCoreAnalyzersResources)); internal static DiagnosticDescriptor ForwardCancellationTokenToInvocationsRule = DiagnosticDescriptorHelper.Create( RuleId, @@ -77,7 +65,7 @@ public override void Initialize(AnalysisContext context) { context.EnableConcurrentExecution(); context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); - context.RegisterCompilationStartAction(AnalyzeCompilationStart); + context.RegisterCompilationStartAction(context => AnalyzeCompilationStart(context)); } private void AnalyzeCompilationStart(CompilationStartAnalysisContext context) @@ -91,14 +79,14 @@ private void AnalyzeCompilationStart(CompilationStartAnalysisContext context) { IInvocationOperation invocation = (IInvocationOperation)context.Operation; - if (!(context.ContainingSymbol is IMethodSymbol containingSymbol)) + if (!(context.ContainingSymbol is IMethodSymbol containingMethod)) { return; } if (!ShouldDiagnose( invocation, - containingSymbol, + containingMethod, cancellationTokenType, out string? cancellationTokenArgumentName, out string? invocationTokenParameterName)) @@ -136,7 +124,8 @@ private bool ShouldDiagnose( IMethodSymbol method = invocation.TargetMethod; // Verify that the current invocation is not passing an explicitly token already - if (invocation.Arguments.Any(a => a.Parameter.Type.Equals(cancellationTokenType) && !a.IsImplicit)) + if (AnyArgument(invocation.Arguments, + a => a.Parameter.Type.Equals(cancellationTokenType) && !a.IsImplicit)) { return false; } @@ -147,7 +136,7 @@ private bool ShouldDiagnose( { if (ArgumentsImplicitOrNamed(cancellationTokenType, invocation.Arguments)) { - invocationTokenParameterName = invocation.TargetMethod.Parameters.Last().Name; + invocationTokenParameterName = method.Parameters[^1].Name; } } // or an overload that takes a ct at the end @@ -155,7 +144,7 @@ private bool ShouldDiagnose( { if (ArgumentsImplicitOrNamed(cancellationTokenType, invocation.Arguments)) { - invocationTokenParameterName = overload.Parameters.Last().Name; + invocationTokenParameterName = overload.Parameters[^1].Name; } } else @@ -225,7 +214,7 @@ private static bool TryGetTokenParamName( [NotNullWhen(returnValue: true)] out string? cancellationTokenParameterName) { if (methodDeclaration.Parameters.Count(x => x.Type.Equals(cancellationTokenType)) == 1 && - methodDeclaration.Parameters.Last() is IParameterSymbol lastParameter && + methodDeclaration.Parameters[^1] is IParameterSymbol lastParameter && lastParameter.Type.Equals(cancellationTokenType)) // Covers the case when using an alias for ct { cancellationTokenParameterName = lastParameter.Name; @@ -249,6 +238,21 @@ private static bool InvocationMethodTakesAToken( InvocationIsUsingParamsCancellationToken(lastParameter, arguments, cancellationTokenType)); } + // Checks if the arguments enumerable has any elements that satisfy the provided condition, + // starting the lookup with the last element since tokens tend to be added as the last argument. + private static bool AnyArgument(ImmutableArray arguments, Func predicate) + { + for (int i = arguments.Length - 1; i >= 0; i--) + { + if (predicate(arguments[i])) + { + return true; + } + } + + return false; + } + // Check if the currently used overload is the one that takes the ct, but is utilizing the default value offered in the method signature. // We want to offer a diagnostic for this case, so the user explicitly passes the ancestor's ct. private static bool InvocationIgnoresOptionalCancellationToken( @@ -261,7 +265,9 @@ private static bool InvocationIgnoresOptionalCancellationToken( { // Find out if the ct argument is using the default value // Need to check among all arguments in case the user is passing them named and unordered (despite the ct being defined as the last parameter) - return arguments.Any(a => a.Parameter.Type.Equals(cancellationTokenType) && a.ArgumentKind == ArgumentKind.DefaultValue); + return AnyArgument( + arguments, + a => a.Parameter.Equals(cancellationTokenType) && a.ArgumentKind == ArgumentKind.DefaultValue); } return false; } @@ -273,18 +279,14 @@ private static bool InvocationIsUsingParamsCancellationToken( INamedTypeSymbol cancellationTokenType) { if (lastParameter.IsParams && - lastParameter.Type.Kind == SymbolKind.ArrayType && lastParameter.Type is IArrayTypeSymbol arrayTypeSymbol && arrayTypeSymbol.ElementType.Equals(cancellationTokenType)) { IArgumentOperation? paramsArgument = arguments.FirstOrDefault(a => a.ArgumentKind == ArgumentKind.ParamArray); - if (paramsArgument != null) + if (paramsArgument?.Value is IArrayCreationOperation arrayOperation) { - if (paramsArgument.Value is IArrayCreationOperation arrayOperation) - { - // Do not offer a diagnostic if the user already passed a ct to the params - return arrayOperation.Initializer.ElementValues.IsEmpty; - } + // Do not offer a diagnostic if the user already passed a ct to the params + return arrayOperation.Initializer.ElementValues.IsEmpty; } } @@ -297,10 +299,10 @@ private static bool MethodHasCancellationTokenOverload( ITypeSymbol cancellationTokenType, [NotNullWhen(returnValue: true)] out IMethodSymbol? overload) { - overload = method.ContainingType.GetMembers(method.Name) - .OfType() - .FirstOrDefault(methodToCompare => - HasSameParametersPlusCancellationToken(cancellationTokenType, method, methodToCompare)); + ImmutableArray overloads = method.ContainingType.GetMembers(method.Name); + System.Collections.Generic.List ofType = overloads.OfType().ToList(); + overload = ofType.FirstOrDefault(methodToCompare => + HasSameParametersPlusCancellationToken(cancellationTokenType, method, methodToCompare)); return overload != null; @@ -315,11 +317,14 @@ static bool HasSameParametersPlusCancellationToken(ITypeSymbol cancellationToken return false; } + IMethodSymbol originalMethodWithAllParameters = originalMethod.ReducedFrom ?? originalMethod.ConstructedFrom ?? originalMethod; + IMethodSymbol methodToCompareWithAllParameters = methodToCompare.ReducedFrom ?? methodToCompare.ConstructedFrom ?? methodToCompare; + // Now compare the types of all parameters before the ct - for (int i = 0; i < originalMethod.Parameters.Length; i++) + for (int i = 0; i < originalMethodWithAllParameters.Parameters.Length; i++) { - IParameterSymbol? originalParameter = originalMethod.Parameters[i]; - IParameterSymbol? comparedParameter = methodToCompare.Parameters[i]; + IParameterSymbol? originalParameter = originalMethodWithAllParameters.Parameters[i]; + IParameterSymbol? comparedParameter = methodToCompareWithAllParameters.Parameters[i]; if (originalParameter == null || comparedParameter == null || !originalParameter.Type.Equals(comparedParameter.Type)) { return false; diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs index 2c338611e4..edf2298929 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs @@ -125,24 +125,25 @@ private bool TryGenerateNewDocumentRoot( SyntaxNode newInvocation; // The instance is null when calling a static method from another type - if (invocation.Instance == null) + switch (invocation.Instance) { - newInvocation = expression; - } - // The method is being invoked with nullability - else if (invocation.Instance is IConditionalAccessInstanceOperation) - { - newInvocation = GetConditionalOperationInvocationExpression(invocation.Syntax); - } - // The instance is implicit when calling a method from the same type, call the method directly - else if (invocation.Instance.IsImplicit) - { - newInvocation = invocation.GetInstanceSyntax()!; - } - // Calling a method from an object, we must include the instance variable name - else - { - newInvocation = generator.MemberAccessExpression(invocation.GetInstanceSyntax(), invocation.TargetMethod.Name); + case null: + newInvocation = expression; + break; + case IConditionalAccessInstanceOperation _: + newInvocation = GetConditionalOperationInvocationExpression(invocation.Syntax); + break; + default: + if (invocation.Instance.IsImplicit) + { + newInvocation = invocation.GetInstanceSyntax()!; + } + // Calling a method from an object, we must include the instance variable name + else + { + newInvocation = generator.MemberAccessExpression(invocation.GetInstanceSyntax(), invocation.TargetMethod.Name); + } + break; } // Insert the new arguments to the new invocation SyntaxNode newInvocationWithArguments = generator.InvocationExpression(newInvocation, newArguments).WithTriviaFrom(invocation.Syntax); diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf index d4f3b79dac..7e68fed2c5 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf @@ -883,13 +883,13 @@ - Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token - Forward the 'CancellationToken' parameter to methods that take one. - Forward the 'CancellationToken' parameter to methods that take one. + Forward the 'CancellationToken' parameter to methods that take one + Forward the 'CancellationToken' parameter to methods that take one @@ -1418,8 +1418,8 @@ - Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync'. - Pro ReadAsync a WriteAsync upřednostňovat přetížení založená na Memory + Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' + Pro ReadAsync a WriteAsync upřednostňovat přetížení založená na Memory diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf index d35d82a624..70fffb7dc6 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf @@ -883,13 +883,13 @@ - Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token - Forward the 'CancellationToken' parameter to methods that take one. - Forward the 'CancellationToken' parameter to methods that take one. + Forward the 'CancellationToken' parameter to methods that take one + Forward the 'CancellationToken' parameter to methods that take one @@ -1418,8 +1418,8 @@ - Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync'. - Arbeitsspeicherbasierte Überladungen für "ReadAsync" und "WriteAsync" bevorzugen + Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' + Arbeitsspeicherbasierte Überladungen für "ReadAsync" und "WriteAsync" bevorzugen diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf index 0d6d37e41f..c989335279 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf @@ -883,13 +883,13 @@ - Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token - Forward the 'CancellationToken' parameter to methods that take one. - Forward the 'CancellationToken' parameter to methods that take one. + Forward the 'CancellationToken' parameter to methods that take one + Forward the 'CancellationToken' parameter to methods that take one @@ -1418,8 +1418,8 @@ - Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync'. - Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync'. + Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' + Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf index 3320552418..a87ee765ca 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf @@ -883,13 +883,13 @@ - Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token - Forward the 'CancellationToken' parameter to methods that take one. - Forward the 'CancellationToken' parameter to methods that take one. + Forward the 'CancellationToken' parameter to methods that take one + Forward the 'CancellationToken' parameter to methods that take one @@ -1418,8 +1418,8 @@ - Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync'. - Préférez les surcharges de type ’Memory' pour ’ReadAsync’ et ’WriteAsync'. + Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' + Préférez les surcharges de type ’Memory' pour ’ReadAsync’ et ’WriteAsync'. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf index 841ad02a2d..fc54c95483 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf @@ -883,13 +883,13 @@ - Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token - Forward the 'CancellationToken' parameter to methods that take one. - Forward the 'CancellationToken' parameter to methods that take one. + Forward the 'CancellationToken' parameter to methods that take one + Forward the 'CancellationToken' parameter to methods that take one @@ -1418,8 +1418,8 @@ - Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync'. - Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync'. + Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' + Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf index 9dcc396e7b..b2a46e26cf 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf @@ -883,13 +883,13 @@ - Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token - Forward the 'CancellationToken' parameter to methods that take one. - Forward the 'CancellationToken' parameter to methods that take one. + Forward the 'CancellationToken' parameter to methods that take one + Forward the 'CancellationToken' parameter to methods that take one @@ -1418,8 +1418,8 @@ - Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync'. - Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync'. + Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' + Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf index 45298bac7c..203312be11 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf @@ -883,13 +883,13 @@ - Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token - Forward the 'CancellationToken' parameter to methods that take one. - Forward the 'CancellationToken' parameter to methods that take one. + Forward the 'CancellationToken' parameter to methods that take one + Forward the 'CancellationToken' parameter to methods that take one @@ -1418,8 +1418,8 @@ - Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync'. - Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync'. + Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' + Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf index 0b670a5fb6..d7ae914f86 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf @@ -884,13 +884,13 @@ - Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token - Forward the 'CancellationToken' parameter to methods that take one. - Forward the 'CancellationToken' parameter to methods that take one. + Forward the 'CancellationToken' parameter to methods that take one + Forward the 'CancellationToken' parameter to methods that take one @@ -1419,8 +1419,8 @@ - Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync'. - Preferuj „przeciążanie oparte na elemencie „Memory” dla elementów „ReadAsync” i „WriteAsync”. + Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' + Preferuj „przeciążanie oparte na elemencie „Memory” dla elementów „ReadAsync” i „WriteAsync”. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf index 567fd12451..fa99c2fe53 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf @@ -883,13 +883,13 @@ - Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token - Forward the 'CancellationToken' parameter to methods that take one. - Forward the 'CancellationToken' parameter to methods that take one. + Forward the 'CancellationToken' parameter to methods that take one + Forward the 'CancellationToken' parameter to methods that take one @@ -1418,8 +1418,8 @@ - Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync'. - Prefira as sobrecargas baseadas em 'Memory' para 'ReadAsync' e 'WriteAsync'. + Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' + Prefira as sobrecargas baseadas em 'Memory' para 'ReadAsync' e 'WriteAsync'. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf index 086f5e42c5..9c09a88389 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf @@ -883,13 +883,13 @@ - Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token - Forward the 'CancellationToken' parameter to methods that take one. - Forward the 'CancellationToken' parameter to methods that take one. + Forward the 'CancellationToken' parameter to methods that take one + Forward the 'CancellationToken' parameter to methods that take one @@ -1418,8 +1418,8 @@ - Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync'. - Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync'. + Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' + Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf index 84ea0d151c..dd728efca9 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf @@ -883,13 +883,13 @@ - Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token - Forward the 'CancellationToken' parameter to methods that take one. - Forward the 'CancellationToken' parameter to methods that take one. + Forward the 'CancellationToken' parameter to methods that take one + Forward the 'CancellationToken' parameter to methods that take one @@ -1418,8 +1418,8 @@ - Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync'. - Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync'. + Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' + Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf index 6165d066c8..378862aae9 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf @@ -883,13 +883,13 @@ - Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token - Forward the 'CancellationToken' parameter to methods that take one. - Forward the 'CancellationToken' parameter to methods that take one. + Forward the 'CancellationToken' parameter to methods that take one + Forward the 'CancellationToken' parameter to methods that take one @@ -1418,8 +1418,8 @@ - Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync'. - 对于 "ReadAsync" 和 "WriteAsync",首选基于内存的重载。 + Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' + 对于 "ReadAsync" 和 "WriteAsync",首选基于内存的重载。 diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf index 3181dd4aa7..e545537a8a 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf @@ -883,13 +883,13 @@ - Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. - Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token. + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token + Forward the '{0}' parameter to the '{1}' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token - Forward the 'CancellationToken' parameter to methods that take one. - Forward the 'CancellationToken' parameter to methods that take one. + Forward the 'CancellationToken' parameter to methods that take one + Forward the 'CancellationToken' parameter to methods that take one @@ -1418,8 +1418,8 @@ - Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync'. - Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync'. + Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' + Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs index 72f076e94c..9d835571a5 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs @@ -2019,7 +2019,6 @@ public void M(CancellationToken ct) bool b = false; b.Extension((j) => { - Console.WriteLine(""Hello world""); [|j.MyMethod|](); }); } @@ -2045,7 +2044,6 @@ public void M(CancellationToken ct) bool b = false; b.Extension((j) => { - Console.WriteLine(""Hello world""); j.MyMethod(c: ct); }); } @@ -3984,7 +3982,6 @@ Class C Public Sub M(ByVal ct As CancellationToken) Dim b As Boolean = False b.Extension(Sub(j) - Console.WriteLine(""Hello, world"") j.[|MyMethod|]() End Sub) End Sub @@ -4009,7 +4006,6 @@ Class C Public Sub M(ByVal ct As CancellationToken) Dim b As Boolean = False b.Extension(Sub(j) - Console.WriteLine(""Hello, world"") j.MyMethod(ct) End Sub) End Sub From 916722946ae76f49e29ecbd5c1bef26991ececf8 Mon Sep 17 00:00:00 2001 From: carlossanlop Date: Thu, 18 Jun 2020 17:59:43 -0700 Subject: [PATCH 19/20] Address latest suggestions --- ...CancellationTokenToInvocations.Analyzer.cs | 24 ++-- ...ardCancellationTokenToInvocations.Fixer.cs | 40 +++--- ...wardCancellationTokenToInvocationsTests.cs | 118 +----------------- 3 files changed, 32 insertions(+), 150 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Analyzer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Analyzer.cs index 9ac330160b..51f85db38f 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Analyzer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Analyzer.cs @@ -123,7 +123,7 @@ private bool ShouldDiagnose( IMethodSymbol method = invocation.TargetMethod; - // Verify that the current invocation is not passing an explicitly token already + // Verify that the current invocation is not passing an explicit token already if (AnyArgument(invocation.Arguments, a => a.Parameter.Type.Equals(cancellationTokenType) && !a.IsImplicit)) { @@ -267,7 +267,7 @@ private static bool InvocationIgnoresOptionalCancellationToken( // Need to check among all arguments in case the user is passing them named and unordered (despite the ct being defined as the last parameter) return AnyArgument( arguments, - a => a.Parameter.Equals(cancellationTokenType) && a.ArgumentKind == ArgumentKind.DefaultValue); + a => a.Parameter.Type.Equals(cancellationTokenType) && a.ArgumentKind == ArgumentKind.DefaultValue); } return false; } @@ -299,9 +299,10 @@ private static bool MethodHasCancellationTokenOverload( ITypeSymbol cancellationTokenType, [NotNullWhen(returnValue: true)] out IMethodSymbol? overload) { - ImmutableArray overloads = method.ContainingType.GetMembers(method.Name); - System.Collections.Generic.List ofType = overloads.OfType().ToList(); - overload = ofType.FirstOrDefault(methodToCompare => + overload = method.ContainingType + .GetMembers(method.Name) + .OfType() + .FirstOrDefault(methodToCompare => HasSameParametersPlusCancellationToken(cancellationTokenType, method, methodToCompare)); return overload != null; @@ -310,22 +311,23 @@ private static bool MethodHasCancellationTokenOverload( static bool HasSameParametersPlusCancellationToken(ITypeSymbol cancellationTokenType, IMethodSymbol originalMethod, IMethodSymbol methodToCompare) { // Avoid comparing to itself, or when there are no parameters, or when the last parameter is not a ct - if (originalMethod.Equals(methodToCompare.Name) || + if (originalMethod.Equals(methodToCompare) || methodToCompare.Parameters.Count(p => p.Type.Equals(cancellationTokenType)) != 1 || !methodToCompare.Parameters[^1].Type.Equals(cancellationTokenType)) { return false; } - IMethodSymbol originalMethodWithAllParameters = originalMethod.ReducedFrom ?? originalMethod.ConstructedFrom ?? originalMethod; - IMethodSymbol methodToCompareWithAllParameters = methodToCompare.ReducedFrom ?? methodToCompare.ConstructedFrom ?? methodToCompare; + IMethodSymbol originalMethodWithAllParameters = (originalMethod.ReducedFrom ?? originalMethod).OriginalDefinition; + IMethodSymbol methodToCompareWithAllParameters = (methodToCompare.ReducedFrom ?? methodToCompare).OriginalDefinition; // Now compare the types of all parameters before the ct + // The largest i is the number of parameters in the method that has fewer parameters for (int i = 0; i < originalMethodWithAllParameters.Parameters.Length; i++) { - IParameterSymbol? originalParameter = originalMethodWithAllParameters.Parameters[i]; - IParameterSymbol? comparedParameter = methodToCompareWithAllParameters.Parameters[i]; - if (originalParameter == null || comparedParameter == null || !originalParameter.Type.Equals(comparedParameter.Type)) + IParameterSymbol originalParameter = originalMethodWithAllParameters.Parameters[i]; + IParameterSymbol comparedParameter = methodToCompareWithAllParameters.Parameters[i]; + if (!originalParameter.Type.Equals(comparedParameter.Type)) { return false; } diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs index edf2298929..ef07cd268b 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs @@ -77,39 +77,37 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) string title = MicrosoftNetCoreAnalyzersResources.ForwardCancellationTokenToInvocationsTitle; - if (!TryGenerateNewDocumentRoot(doc, root, invocation, argumentName, parameterName, out SyntaxNode? newRoot)) + if (!TryGetExpressionAndArguments(invocation.Syntax, out SyntaxNode? expression, out List? newArguments)) { return; } - Task createChangedDocument(CancellationToken _) => - Task.FromResult(doc.WithSyntaxRoot(newRoot)); + Task CreateChangedDocument(CancellationToken _) + { + SyntaxNode newRoot = TryGenerateNewDocumentRoot(doc, root, invocation, argumentName, parameterName, expression, newArguments); + Document newDocument = doc.WithSyntaxRoot(newRoot); + return Task.FromResult(newDocument); + } context.RegisterCodeFix( new MyCodeAction( title: title, - createChangedDocument, + CreateChangedDocument, equivalenceKey: title), context.Diagnostics); } - private bool TryGenerateNewDocumentRoot( + private SyntaxNode TryGenerateNewDocumentRoot( Document doc, SyntaxNode root, IInvocationOperation invocation, string invocationTokenArgumentName, string ancestorTokenParameterName, - [NotNullWhen(returnValue: true)] out SyntaxNode? newRoot) + SyntaxNode expression, + List newArguments) { - newRoot = null; - SyntaxGenerator generator = SyntaxGenerator.GetGenerator(doc); - if (!TryGetExpressionAndArguments(invocation.Syntax, out SyntaxNode? expression, out List? newArguments)) - { - return false; - } - SyntaxNode identifier = generator.IdentifierName(invocationTokenArgumentName); SyntaxNode cancellationTokenArgument; if (!string.IsNullOrEmpty(ancestorTokenParameterName)) @@ -123,34 +121,32 @@ private bool TryGenerateNewDocumentRoot( newArguments.Add(cancellationTokenArgument); - SyntaxNode newInvocation; + SyntaxNode newInstance; // The instance is null when calling a static method from another type switch (invocation.Instance) { case null: - newInvocation = expression; + newInstance = expression; break; case IConditionalAccessInstanceOperation _: - newInvocation = GetConditionalOperationInvocationExpression(invocation.Syntax); + newInstance = GetConditionalOperationInvocationExpression(invocation.Syntax); break; default: if (invocation.Instance.IsImplicit) { - newInvocation = invocation.GetInstanceSyntax()!; + newInstance = invocation.GetInstanceSyntax()!; } // Calling a method from an object, we must include the instance variable name else { - newInvocation = generator.MemberAccessExpression(invocation.GetInstanceSyntax(), invocation.TargetMethod.Name); + newInstance = generator.MemberAccessExpression(invocation.GetInstanceSyntax(), invocation.TargetMethod.Name); } break; } // Insert the new arguments to the new invocation - SyntaxNode newInvocationWithArguments = generator.InvocationExpression(newInvocation, newArguments).WithTriviaFrom(invocation.Syntax); - - newRoot = generator.ReplaceNode(root, invocation.Syntax, newInvocationWithArguments); + SyntaxNode newInvocationWithArguments = generator.InvocationExpression(newInstance, newArguments).WithTriviaFrom(invocation.Syntax); - return true; + return generator.ReplaceNode(root, invocation.Syntax, newInvocationWithArguments); } // Needed for Telemetry (https://github.com/dotnet/roslyn-analyzers/issues/192) diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs index 9d835571a5..fe54245c80 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs @@ -299,7 +299,7 @@ async void M(CancellationToken ct) } [Fact] - public Task CS_NoDiagnostic_CancellationTokenSource_ParamsUsed_Order1() + public Task CS_NoDiagnostic_CancellationTokenSource_ParamsUsed_Order() { /* CancellationTokenSource has 3 different overloads that take CancellationToken arguments. @@ -330,122 +330,6 @@ public static void Method(params CancellationToken[] tokens){} "); } - [Fact] - public Task CS_NoDiagnostic_CancellationTokenSource_ParamsUsed_Order2() - { - /* - CancellationTokenSource has 3 different overloads that take CancellationToken arguments. - We should detect if a ct is passed and not offer a diagnostic, because it's considered one of the `params`. - - public class CancellationTokenSource : IDisposable - { - public static CancellationTokenSource CreateLinkedTokenSource(CancellationToken token); - public static CancellationTokenSource CreateLinkedTokenSource(CancellationToken token1, CancellationToken token2); - public static CancellationTokenSource CreateLinkedTokenSource(params CancellationToken[] tokens); - } - */ - return CS8VerifyAnalyzerAsync(@" -using System.Threading; -class C -{ - void M(CancellationToken ct) - { - CTS.Method(ct); // Don't diagnose - } -} -class CTS -{ - public static void Method(CancellationToken token){} - public static void Method(params CancellationToken[] tokens){} - public static void Method(CancellationToken token1, CancellationToken token2){} -} - "); - } - - [Fact] - public Task CS_NoDiagnostic_CancellationTokenSource_ParamsUsed_Order3() - { - return CS8VerifyAnalyzerAsync(@" -using System.Threading; -class C -{ - void M(CancellationToken ct) - { - CTS.Method(ct); // Don't diagnose - } -} -class CTS -{ - public static void Method(CancellationToken token1, CancellationToken token2){} - public static void Method(params CancellationToken[] tokens){} - public static void Method(CancellationToken token){} -} - "); - } - - [Fact] - public Task CS_NoDiagnostic_CancellationTokenSource_ParamsUsed_Order4() - { - return CS8VerifyAnalyzerAsync(@" -using System.Threading; -class C -{ - void M(CancellationToken ct) - { - CTS.Method(ct); // Don't diagnose - } -} -class CTS -{ - public static void Method(CancellationToken token1, CancellationToken token2){} - public static void Method(CancellationToken token){} - public static void Method(params CancellationToken[] tokens){} -} - "); - } - - [Fact] - public Task CS_NoDiagnostic_CancellationTokenSource_ParamsUsed_Order5() - { - return CS8VerifyAnalyzerAsync(@" -using System.Threading; -class C -{ - void M(CancellationToken ct) - { - CTS.Method(ct); // Don't diagnose - } -} -class CTS -{ - public static void Method(params CancellationToken[] tokens){} - public static void Method(CancellationToken token){} - public static void Method(CancellationToken token1, CancellationToken token2){} -} - "); - } - - [Fact] - public Task CS_NoDiagnostic_CancellationTokenSource_ParamsUsed_Order6() - { - return CS8VerifyAnalyzerAsync(@" -using System.Threading; -class C -{ - void M(CancellationToken ct) - { - CTS.Method(ct); // Don't diagnose - } -} -class CTS -{ - public static void Method(params CancellationToken[] tokens){} - public static void Method(CancellationToken token1, CancellationToken token2){} - public static void Method(CancellationToken token){} -} - "); - } - [Fact] public Task CS_NoDiagnostic_ExtensionMethodTakesToken() { From d5af47ede856d41b85586ececef4ac507f44a857 Mon Sep 17 00:00:00 2001 From: carlossanlop Date: Mon, 22 Jun 2020 11:58:42 -0700 Subject: [PATCH 20/20] Address last suggestions --- .../Runtime/ForwardCancellationTokenToInvocations.Fixer.cs | 4 ++-- .../BasicForwardCancellationTokenToInvocations.Analyzer.vb | 5 ----- .../BasicForwardCancellationTokenToInvocations.Fixer.vb | 4 ---- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs index ef07cd268b..880c04c866 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs @@ -82,7 +82,7 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) return; } - Task CreateChangedDocument(CancellationToken _) + Task CreateChangedDocumentAsync(CancellationToken _) { SyntaxNode newRoot = TryGenerateNewDocumentRoot(doc, root, invocation, argumentName, parameterName, expression, newArguments); Document newDocument = doc.WithSyntaxRoot(newRoot); @@ -92,7 +92,7 @@ Task CreateChangedDocument(CancellationToken _) context.RegisterCodeFix( new MyCodeAction( title: title, - CreateChangedDocument, + CreateChangedDocumentAsync, equivalenceKey: title), context.Diagnostics); } diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocations.Analyzer.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocations.Analyzer.vb index 8333f54626..02d51ff667 100644 --- a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocations.Analyzer.vb +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocations.Analyzer.vb @@ -14,7 +14,6 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime Inherits ForwardCancellationTokenToInvocationsAnalyzer - Protected Overrides Function GetInvocationMethodNameNode(invocationNode As SyntaxNode) As SyntaxNode Dim invocationExpression = TryCast(invocationNode, InvocationExpressionSyntax) @@ -56,7 +55,3 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime End Class End Namespace - - - - diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocations.Fixer.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocations.Fixer.vb index 7fddba07e0..0bb7fc893f 100644 --- a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocations.Fixer.vb +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocations.Fixer.vb @@ -66,7 +66,3 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime End Class End Namespace - - - -