Skip to content

Commit

Permalink
Handle switching from params to non-params overload in xUnit1051 (#192)
Browse files Browse the repository at this point in the history
  • Loading branch information
SapiensAnatis authored Dec 1, 2024
1 parent d5ad1bf commit 6897a29
Show file tree
Hide file tree
Showing 2 changed files with 171 additions and 0 deletions.
48 changes: 48 additions & 0 deletions src/xunit.analyzers.fixes/X1000/UseCancellationTokenFixer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Operations;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;

namespace Xunit.Analyzers.Fixes;
Expand Down Expand Up @@ -51,6 +52,9 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
if (root.FindNode(diagnostic.Location.SourceSpan) is not InvocationExpressionSyntax invocation)
return;

if (semanticModel.GetOperation(invocation, context.CancellationToken) is not IInvocationOperation invocationOperation)
return;

var arguments = invocation.ArgumentList.Arguments;

for (var argumentIndex = 0; argumentIndex < arguments.Count; argumentIndex++)
Expand Down Expand Up @@ -78,6 +82,16 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)

var args = new List<ArgumentSyntax>(arguments);

if (
invocationOperation.Arguments.FirstOrDefault(arg =>
arg.ArgumentKind == ArgumentKind.ParamArray
) is
{ } paramsArgument
)
{
TransformParamsArgument(args, paramsArgument, editor.Generator);
}

if (parameterIndex < args.Count)
{
args[parameterIndex] = args[parameterIndex].WithExpression(testContextCancellationTokenExpression);
Expand Down Expand Up @@ -106,4 +120,38 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
context.Diagnostics
);
}

private static void TransformParamsArgument(
List<ArgumentSyntax> arguments,
IArgumentOperation paramsOperation,
SyntaxGenerator generator
)
{

if (paramsOperation is not
{
Value: IArrayCreationOperation
{
Type: IArrayTypeSymbol arrayTypeSymbol,
Initializer: { } initializer
}
})
{
return;
}

// We know that the params arguments occupy the end of the list because the language
// does not allow regular arguments after params.
int paramsCount = initializer.ElementValues.Length;
int paramsStart = arguments.Count - paramsCount;

arguments.RemoveRange(paramsStart, paramsCount);

ExpressionSyntax arrayCreation = (ExpressionSyntax)generator.ArrayCreationExpression(
generator.TypeExpression(arrayTypeSymbol.ElementType),
initializer.ElementValues.Select(x => x.Syntax)
);

arguments.Add(Argument(arrayCreation));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,127 @@ public void TestMethod()

await Verify.VerifyCodeFixV3(before, after, UseCancellationTokenFixer.Key_UseCancellationTokenArgument);
}

[Fact]
public async Task UseCancellationTokenArgument_ParamsArgument()
{
var before = /* lang=c#-test */ """
using System.Threading;
using System.Threading.Tasks;
using MyContext = Xunit.TestContext;
public class TestClass {
[Xunit.Fact]
public void TestMethod()
{
[|Function(1, 2, 3)|];
}
void Function(params int[] integers) { }
void Function(int[] integers, CancellationToken token = default(CancellationToken)) { }
}
""";
var after = /* lang=c#-test */ """
using System.Threading;
using System.Threading.Tasks;
using MyContext = Xunit.TestContext;
public class TestClass {
[Xunit.Fact]
public void TestMethod()
{
Function(new int[] { 1, 2, 3 }, MyContext.Current.CancellationToken);
}
void Function(params int[] integers) { }
void Function(int[] integers, CancellationToken token = default(CancellationToken)) { }
}
""";

await Verify.VerifyCodeFixV3(before, after, UseCancellationTokenFixer.Key_UseCancellationTokenArgument);
}

[Fact]
public async Task UseCancellationTokenArgument_ParamsArgumentAfterRegularArguments()
{
var before = /* lang=c#-test */ """
using System.Threading;
using System.Threading.Tasks;
using MyContext = Xunit.TestContext;
public class TestClass {
[Xunit.Fact]
public void TestMethod()
{
[|Function("hello", System.Guid.NewGuid(), System.Guid.NewGuid())|];
}
void Function(string str, params System.Guid[] guids) { }
void Function(string str, System.Guid[] guids, CancellationToken token = default(CancellationToken)) { }
}
""";
var after = /* lang=c#-test */ """
using System.Threading;
using System.Threading.Tasks;
using MyContext = Xunit.TestContext;
public class TestClass {
[Xunit.Fact]
public void TestMethod()
{
Function("hello", new System.Guid[] { System.Guid.NewGuid(), System.Guid.NewGuid() }, MyContext.Current.CancellationToken);
}
void Function(string str, params System.Guid[] guids) { }
void Function(string str, System.Guid[] guids, CancellationToken token = default(CancellationToken)) { }
}
""";

await Verify.VerifyCodeFixV3(before, after, UseCancellationTokenFixer.Key_UseCancellationTokenArgument);
}

[Fact]
public async Task UseCancellationTokenArgument_ParamsArgumentWithNoValues()
{
var before = /* lang=c#-test */ """
using System.Threading;
using System.Threading.Tasks;
using MyContext = Xunit.TestContext;
public class TestClass {
[Xunit.Fact]
public void TestMethod()
{
[|Function()|];
}
void Function(params int[] integers) { }
void Function(int[] integers, CancellationToken token = default(CancellationToken)) { }
}
""";
var after = /* lang=c#-test */ """
using System.Threading;
using System.Threading.Tasks;
using MyContext = Xunit.TestContext;
public class TestClass {
[Xunit.Fact]
public void TestMethod()
{
Function(new int[] { }, MyContext.Current.CancellationToken);
}
void Function(params int[] integers) { }
void Function(int[] integers, CancellationToken token = default(CancellationToken)) { }
}
""";

await Verify.VerifyCodeFixV3(before, after, UseCancellationTokenFixer.Key_UseCancellationTokenArgument);
}
}

0 comments on commit 6897a29

Please sign in to comment.