Skip to content

Commit

Permalink
Fixer now supports explicit param name prefix when adding 1 parameter…
Browse files Browse the repository at this point in the history
… to the invocation is not in the right position
  • Loading branch information
DavidBoike committed Apr 28, 2021
1 parent 1abc15d commit 7aed0fb
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,44 @@ public class TestMessage : ICommand {}
return VerifyFix(test, fixedTest);
}

[Test]
public Task MultipleOptionalParameters()
{
var test = @"
using NServiceBus;
using System.Threading;
using System.Threading.Tasks;
public class Foo : IHandleMessages<TestMessage>
{
public async Task Handle(TestMessage message, IMessageHandlerContext context)
{
await TestMethod(1);
}
Task TestMethod(int a, int b = 0, int c = 1, int d = 2, CancellationToken token = default(CancellationToken)) { return Task.CompletedTask; }
}
public class TestMessage : ICommand {}
";

var fixedTest = @"
using NServiceBus;
using System.Threading;
using System.Threading.Tasks;
public class Foo : IHandleMessages<TestMessage>
{
public async Task Handle(TestMessage message, IMessageHandlerContext context)
{
await TestMethod(1, token: context.CancellationToken);
}
Task TestMethod(int a, int b = 0, int c = 1, int d = 2, CancellationToken token = default(CancellationToken)) { return Task.CompletedTask; }
}
public class TestMessage : ICommand {}
";

return VerifyFix(test, fixedTest);
}

protected override DiagnosticAnalyzer GetAnalyzer() => new ForwardCancellationTokenAnalyzer();

protected override CodeFixProvider GetCodeFixProvider() => new ForwardCancellationTokenFixer();
Expand Down
17 changes: 12 additions & 5 deletions src/NServiceBus.Core.Analyzer/ForwardCancellationTokenAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,9 +141,9 @@ static void AnalyzeSyntax(SyntaxNodeAnalysisContext context, KnownTypes knownTyp
return;
}

if (InvocationMethodTakesAToken(methodSymbol, knownTypes))
if (InvocationMethodTakesAToken(methodSymbol, knownTypes, invocationArgs, out var explicitParamName))
{
ReportDiagnostic(context, invocation, contextParamName, methodSymbol);
ReportDiagnostic(context, invocation, contextParamName, methodSymbol, explicitParamName);
return;
}

Expand Down Expand Up @@ -242,8 +242,10 @@ static bool ClassInheritsATargetType(SyntaxNodeAnalysisContext context, ClassDec
});
}

static bool InvocationMethodTakesAToken(IMethodSymbol method, KnownTypes knownTypes)
static bool InvocationMethodTakesAToken(IMethodSymbol method, KnownTypes knownTypes, ArgumentSyntax[] arguments, out string explicitParamName)
{
explicitParamName = null;

// If is an NServiceBus-namespace extension method that extends IMessageHandlerContext, then skip
if (method.IsExtensionMethod && method.ContainingNamespace?.Name == "NServiceBus")
{
Expand All @@ -265,18 +267,23 @@ static bool InvocationMethodTakesAToken(IMethodSymbol method, KnownTypes knownTy
// If parameter has a default value being used
if (lastParameter.Type.Equals(knownTypes.CancellationToken) && lastParameter.IsOptional)
{
if (method.Parameters.Length != arguments.Length + 1)
{
explicitParamName = lastParameter.Name;
}
return true;
}

return false;
}

static void ReportDiagnostic(SyntaxNodeAnalysisContext context, InvocationExpressionSyntax invocation, string contextParamName, IMethodSymbol methodSymbol)
static void ReportDiagnostic(SyntaxNodeAnalysisContext context, InvocationExpressionSyntax invocation, string contextParamName, IMethodSymbol methodSymbol, string explicitParamName = null)
{
var properties = new Dictionary<string, string>
{
{ "ContextParamName", contextParamName },
{ "MethodName", methodSymbol.Name }
{ "MethodName", methodSymbol.Name },
{ "ExplicitParameterName", explicitParamName }
}.ToImmutableDictionary();

var diagnostic = Diagnostic.Create(ForwardCancellationTokenDiagnostic, invocation.GetLocation(), properties, contextParamName, methodSymbol.Name);
Expand Down
20 changes: 17 additions & 3 deletions src/NServiceBus.Core.Analyzer/ForwardCancellationTokenFixer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
var diagnostic = context.Diagnostics.First();
var contextVarName = diagnostic.Properties["ContextParamName"];
var methodName = diagnostic.Properties["MethodName"];
var explicitParameterName = diagnostic.Properties["ExplicitParameterName"];
var sourceLocation = diagnostic.Location;
var diagnosticSpan = sourceLocation.SourceSpan;

Expand All @@ -38,13 +39,26 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
// Register a code action that will invoke the fix.
context.RegisterCodeFix(
CodeAction.Create(title, ct =>
AddCancellationToken(context.Document, invocationSyntax, contextVarName, ct), equivalenceKey: title), diagnostic);
AddCancellationToken(context.Document, invocationSyntax, contextVarName, explicitParameterName, ct), equivalenceKey: title), diagnostic);

}

static async Task<Document> AddCancellationToken(Document document, InvocationExpressionSyntax invocationSyntax, string contextVarName, CancellationToken cancellationToken)
static async Task<Document> AddCancellationToken(Document document, InvocationExpressionSyntax invocationSyntax, string contextVarName, string explicitParameterName, CancellationToken cancellationToken)
{
var newArg = SyntaxFactory.Argument(SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, SyntaxFactory.IdentifierName(contextVarName), SyntaxFactory.IdentifierName("CancellationToken")));
// The context.CancellationToken part. This is always required.
var simpleMemberAccess = SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, SyntaxFactory.IdentifierName(contextVarName), SyntaxFactory.IdentifierName("CancellationToken"));

ArgumentSyntax newArg;
if (explicitParameterName != null)
{
// Add the prefix `token: context.CancellationToken` if adding 1 argument would not be in the correct location
var nameColon = SyntaxFactory.NameColon(SyntaxFactory.IdentifierName(explicitParameterName));
newArg = SyntaxFactory.Argument(nameColon, default, simpleMemberAccess);
}
else
{
newArg = SyntaxFactory.Argument(simpleMemberAccess);
}
var newArgList = invocationSyntax.ArgumentList.AddArguments(newArg);

var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
Expand Down

0 comments on commit 7aed0fb

Please sign in to comment.