Skip to content

Commit cf2315d

Browse files
thomhurstclaude
andcommitted
feat: add System.Threading.Tasks using and preserve TestCase properties
- Add `using System.Threading.Tasks;` when async code is present in migration - Preserve NUnit TestCase named properties during migration: - Map Ignore/IgnoreReason to TUnit's Skip property - Add TODO comments for unsupported properties (TestName, Category, Description, etc.) - Update test expectations to include System.Threading.Tasks using 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 9827e31 commit cf2315d

File tree

5 files changed

+117
-6
lines changed

5 files changed

+117
-6
lines changed

TUnit.Analyzers.CodeFixers/NUnitExpectedResultRewriter.cs

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -441,19 +441,40 @@ private AttributeSyntax TransformTestCaseAttribute(AttributeSyntax attribute)
441441

442442
var newArgs = new List<AttributeArgumentSyntax>();
443443
ExpressionSyntax? expectedValue = null;
444+
var unsupportedProperties = new List<string>();
444445

445446
foreach (var arg in attribute.ArgumentList.Arguments)
446447
{
447-
if (arg.NameEquals?.Name.Identifier.Text == "ExpectedResult")
448+
var namedProperty = arg.NameEquals?.Name.Identifier.Text;
449+
450+
if (namedProperty == "ExpectedResult")
448451
{
449452
expectedValue = arg.Expression;
450453
}
451-
else if (arg.NameColon == null && arg.NameEquals == null)
454+
else if (namedProperty == null)
452455
{
453456
// Positional argument - keep it
454457
newArgs.Add(arg);
455458
}
456-
// Skip other named arguments for now
459+
else if (namedProperty == "Ignore" || namedProperty == "IgnoreReason")
460+
{
461+
// Map NUnit's Ignore/IgnoreReason to TUnit's Skip
462+
var skipArg = SyntaxFactory.AttributeArgument(
463+
SyntaxFactory.NameEquals(SyntaxFactory.IdentifierName("Skip")),
464+
null,
465+
arg.Expression);
466+
newArgs.Add(skipArg);
467+
}
468+
else if (namedProperty is "TestName" or "Category" or "Description" or "Author" or "Explicit" or "ExplicitReason")
469+
{
470+
// These properties don't have direct TUnit equivalents
471+
unsupportedProperties.Add($"{namedProperty} = {arg.Expression}");
472+
}
473+
// Other named arguments are preserved as-is (they might be TUnit-compatible)
474+
else
475+
{
476+
newArgs.Add(arg);
477+
}
457478
}
458479

459480
// Add expected value as last positional argument
@@ -462,9 +483,19 @@ private AttributeSyntax TransformTestCaseAttribute(AttributeSyntax attribute)
462483
newArgs.Add(SyntaxFactory.AttributeArgument(expectedValue));
463484
}
464485

465-
// The attribute will be renamed to "Arguments" by the existing attribute rewriter
466-
return attribute.WithArgumentList(
486+
var newAttribute = attribute.WithArgumentList(
467487
SyntaxFactory.AttributeArgumentList(SyntaxFactory.SeparatedList(newArgs)));
488+
489+
// Add TODO comment for unsupported properties
490+
if (unsupportedProperties.Count > 0)
491+
{
492+
var todoComment = SyntaxFactory.Comment($"/* TODO: TUnit migration - unsupported TestCase properties: {string.Join(", ", unsupportedProperties)} */");
493+
newAttribute = newAttribute.WithLeadingTrivia(
494+
newAttribute.GetLeadingTrivia().Add(todoComment).Add(SyntaxFactory.Space));
495+
}
496+
497+
// The attribute will be renamed to "Arguments" by the existing attribute rewriter
498+
return newAttribute;
468499
}
469500

470501
private class ReturnToAssignmentRewriter : CSharpSyntaxRewriter

TUnit.Analyzers.CodeFixers/NUnitMigrationCodeFixProvider.cs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,52 @@ protected override bool IsFrameworkAttribute(string attributeName)
6969
{
7070
return attributeName switch
7171
{
72-
"TestCase" => argumentList, // Arguments attribute uses the same format
72+
"TestCase" => ConvertTestCaseArguments(argumentList),
7373
"TestCaseSource" => ConvertTestCaseSourceArguments(argumentList),
7474
"Category" => ConvertCategoryArguments(argumentList),
7575
_ => argumentList
7676
};
7777
}
78+
79+
private AttributeArgumentListSyntax ConvertTestCaseArguments(AttributeArgumentListSyntax argumentList)
80+
{
81+
var newArgs = new List<AttributeArgumentSyntax>();
82+
83+
foreach (var arg in argumentList.Arguments)
84+
{
85+
var namedProperty = arg.NameEquals?.Name.Identifier.Text;
86+
87+
if (namedProperty == null)
88+
{
89+
// Positional argument - keep it
90+
newArgs.Add(arg);
91+
}
92+
else if (namedProperty == "Ignore" || namedProperty == "IgnoreReason")
93+
{
94+
// Map NUnit's Ignore/IgnoreReason to TUnit's Skip
95+
var skipArg = SyntaxFactory.AttributeArgument(
96+
SyntaxFactory.NameEquals(SyntaxFactory.IdentifierName("Skip")),
97+
null,
98+
arg.Expression);
99+
newArgs.Add(skipArg);
100+
}
101+
else if (namedProperty is "TestName" or "Category" or "Description" or "Author" or "Explicit" or "ExplicitReason" or "ExpectedResult")
102+
{
103+
// These properties don't have direct TUnit equivalents - preserve as comment
104+
// ExpectedResult is handled by NUnitExpectedResultRewriter, so if we get here it's a case without special handling
105+
var commentArg = SyntaxFactory.AttributeArgument(arg.Expression)
106+
.WithLeadingTrivia(SyntaxFactory.Comment($"/* TODO: {namedProperty} not supported */ "));
107+
newArgs.Add(commentArg);
108+
}
109+
else
110+
{
111+
// Other named arguments are preserved as-is
112+
newArgs.Add(arg);
113+
}
114+
}
115+
116+
return SyntaxFactory.AttributeArgumentList(SyntaxFactory.SeparatedList(newArgs));
117+
}
78118

79119
private AttributeArgumentListSyntax ConvertTestCaseSourceArguments(AttributeArgumentListSyntax argumentList)
80120
{

TUnit.Analyzers.Tests/MSTestMigrationAnalyzerTests.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ public void MyMethod()
114114
""",
115115
Verifier.Diagnostic(Rules.MSTestMigration).WithLocation(0),
116116
"""
117+
using System.Threading.Tasks;
117118
using TUnit.Core;
118119
using TUnit.Assertions;
119120
using static TUnit.Assertions.Assert;
@@ -285,6 +286,7 @@ public void MyMethod()
285286
""",
286287
Verifier.Diagnostic(Rules.MSTestMigration).WithLocation(0),
287288
"""
289+
using System.Threading.Tasks;
288290
using TUnit.Core;
289291
using TUnit.Assertions;
290292
using static TUnit.Assertions.Assert;
@@ -326,6 +328,7 @@ public void StringTests()
326328
""",
327329
Verifier.Diagnostic(Rules.MSTestMigration).WithLocation(0),
328330
"""
331+
using System.Threading.Tasks;
329332
using TUnit.Core;
330333
using TUnit.Assertions;
331334
using static TUnit.Assertions.Assert;
@@ -373,6 +376,7 @@ public void OuterTest()
373376
""",
374377
Verifier.Diagnostic(Rules.MSTestMigration).WithLocation(0),
375378
"""
379+
using System.Threading.Tasks;
376380
using TUnit.Core;
377381
using TUnit.Assertions;
378382
using static TUnit.Assertions.Assert;
@@ -419,6 +423,7 @@ public void GenericTest()
419423
""",
420424
Verifier.Diagnostic(Rules.MSTestMigration).WithLocation(0),
421425
"""
426+
using System.Threading.Tasks;
422427
using TUnit.Core;
423428
using TUnit.Assertions;
424429
using static TUnit.Assertions.Assert;
@@ -508,6 +513,7 @@ public static void ClassTeardown()
508513
Verifier.Diagnostic(Rules.MSTestMigration).WithLocation(0),
509514
"""
510515
using System;
516+
using System.Threading.Tasks;
511517
using TUnit.Core;
512518
using TUnit.Assertions;
513519
using static TUnit.Assertions.Assert;
@@ -608,6 +614,7 @@ public void TestMultipleAssertionTypes()
608614
""",
609615
Verifier.Diagnostic(Rules.MSTestMigration).WithLocation(0),
610616
"""
617+
using System.Threading.Tasks;
611618
using TUnit.Core;
612619
using TUnit.Assertions;
613620
using static TUnit.Assertions.Assert;
@@ -664,6 +671,7 @@ public void TestReferences()
664671
""",
665672
Verifier.Diagnostic(Rules.MSTestMigration).WithLocation(0),
666673
"""
674+
using System.Threading.Tasks;
667675
using TUnit.Core;
668676
using TUnit.Assertions;
669677
using static TUnit.Assertions.Assert;
@@ -708,6 +716,7 @@ public void TestWithMessages()
708716
""",
709717
Verifier.Diagnostic(Rules.MSTestMigration).WithLocation(0),
710718
"""
719+
using System.Threading.Tasks;
711720
using TUnit.Core;
712721
using TUnit.Assertions;
713722
using static TUnit.Assertions.Assert;

TUnit.Analyzers.Tests/NUnitMigrationAnalyzerTests.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ public void MyMethod()
113113
""",
114114
Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0),
115115
"""
116+
using System.Threading.Tasks;
116117
using TUnit.Core;
117118
using TUnit.Assertions;
118119
using static TUnit.Assertions.Assert;
@@ -290,6 +291,7 @@ public void OuterTest()
290291
""",
291292
Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0),
292293
"""
294+
using System.Threading.Tasks;
293295
using TUnit.Core;
294296
using TUnit.Assertions;
295297
using static TUnit.Assertions.Assert;
@@ -336,6 +338,7 @@ public void GenericTest()
336338
""",
337339
Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0),
338340
"""
341+
using System.Threading.Tasks;
339342
using TUnit.Core;
340343
using TUnit.Assertions;
341344
using static TUnit.Assertions.Assert;
@@ -378,6 +381,7 @@ public void ComplexConstraints()
378381
""",
379382
Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0),
380383
"""
384+
using System.Threading.Tasks;
381385
using TUnit.Core;
382386
using TUnit.Assertions;
383387
using static TUnit.Assertions.Assert;
@@ -468,6 +472,7 @@ public void ClassTeardown()
468472
Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0),
469473
"""
470474
using System;
475+
using System.Threading.Tasks;
471476
using TUnit.Core;
472477
using TUnit.Assertions;
473478
using static TUnit.Assertions.Assert;
@@ -558,6 +563,7 @@ public void TestMultipleAssertions()
558563
""",
559564
Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0),
560565
"""
566+
using System.Threading.Tasks;
561567
using TUnit.Core;
562568
using TUnit.Assertions;
563569
using static TUnit.Assertions.Assert;
@@ -596,6 +602,7 @@ public class MyClass
596602
""",
597603
Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0),
598604
"""
605+
using System.Threading.Tasks;
599606
using TUnit.Core;
600607
using TUnit.Assertions;
601608
using static TUnit.Assertions.Assert;
@@ -632,6 +639,7 @@ public class MyClass
632639
""",
633640
Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0),
634641
"""
642+
using System.Threading.Tasks;
635643
using TUnit.Core;
636644
using TUnit.Assertions;
637645
using static TUnit.Assertions.Assert;
@@ -672,6 +680,7 @@ public int Add(int a, int b)
672680
""",
673681
Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0),
674682
"""
683+
using System.Threading.Tasks;
675684
using TUnit.Core;
676685
using TUnit.Assertions;
677686
using static TUnit.Assertions.Assert;
@@ -708,6 +717,7 @@ public class MyClass
708717
""",
709718
Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0),
710719
"""
720+
using System.Threading.Tasks;
711721
using TUnit.Core;
712722
using TUnit.Assertions;
713723
using static TUnit.Assertions.Assert;
@@ -746,6 +756,7 @@ public void TestMethod()
746756
""",
747757
Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0),
748758
"""
759+
using System.Threading.Tasks;
749760
using TUnit.Core;
750761
using TUnit.Assertions;
751762
using static TUnit.Assertions.Assert;
@@ -782,6 +793,7 @@ public void TestMethod()
782793
""",
783794
Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0),
784795
"""
796+
using System.Threading.Tasks;
785797
using TUnit.Core;
786798
using TUnit.Assertions;
787799
using static TUnit.Assertions.Assert;
@@ -818,6 +830,7 @@ public void TestMethod()
818830
""",
819831
Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0),
820832
"""
833+
using System.Threading.Tasks;
821834
using TUnit.Core;
822835
using TUnit.Assertions;
823836
using static TUnit.Assertions.Assert;
@@ -854,6 +867,7 @@ public void TestMethod()
854867
""",
855868
Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0),
856869
"""
870+
using System.Threading.Tasks;
857871
using TUnit.Core;
858872
using TUnit.Assertions;
859873
using static TUnit.Assertions.Assert;
@@ -890,6 +904,7 @@ public void TestMethod()
890904
""",
891905
Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0),
892906
"""
907+
using System.Threading.Tasks;
893908
using TUnit.Core;
894909
using TUnit.Assertions;
895910
using static TUnit.Assertions.Assert;
@@ -926,6 +941,7 @@ public void TestMethod()
926941
""",
927942
Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0),
928943
"""
944+
using System.Threading.Tasks;
929945
using TUnit.Core;
930946
using TUnit.Assertions;
931947
using static TUnit.Assertions.Assert;
@@ -962,6 +978,7 @@ public void TestMethod()
962978
""",
963979
Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0),
964980
"""
981+
using System.Threading.Tasks;
965982
using TUnit.Core;
966983
using TUnit.Assertions;
967984
using static TUnit.Assertions.Assert;
@@ -999,6 +1016,7 @@ public void AdditionTest(int a, int b, int expected)
9991016
""",
10001017
Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0),
10011018
"""
1019+
using System.Threading.Tasks;
10021020
using TUnit.Core;
10031021
using TUnit.Assertions;
10041022
using static TUnit.Assertions.Assert;
@@ -1038,6 +1056,7 @@ public void AdditionTest(int a, int b, int expected)
10381056
""",
10391057
Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0),
10401058
"""
1059+
using System.Threading.Tasks;
10411060
using TUnit.Core;
10421061
using TUnit.Assertions;
10431062
using static TUnit.Assertions.Assert;

TUnit.Analyzers/Migrators/Base/MigrationHelpers.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,9 +184,21 @@ public static CompilationUnitSyntax AddTUnitUsings(CompilationUnitSyntax compila
184184
var assertionsStaticUsing = SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("TUnit.Assertions.Assert"))
185185
.WithStaticKeyword(SyntaxFactory.Token(SyntaxKind.StaticKeyword));
186186
var extensionsUsing = SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("TUnit.Assertions.Extensions"));
187+
// Add System.Threading.Tasks for async Task methods
188+
var tasksUsing = SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System.Threading.Tasks"));
187189

188190
var existingUsings = compilationUnit.Usings.ToList();
189191

192+
// Add System.Threading.Tasks only if the code has async methods or await expressions
193+
bool hasAsyncCode = compilationUnit.DescendantNodes()
194+
.Any(n => n is AwaitExpressionSyntax ||
195+
(n is MethodDeclarationSyntax m && m.Modifiers.Any(mod => mod.IsKind(SyntaxKind.AsyncKeyword))));
196+
197+
if (hasAsyncCode && !existingUsings.Any(u => u.Name?.ToString() == "System.Threading.Tasks"))
198+
{
199+
existingUsings.Add(tasksUsing);
200+
}
201+
190202
if (!existingUsings.Any(u => u.Name?.ToString() == "TUnit.Core"))
191203
{
192204
existingUsings.Add(tunitUsing);

0 commit comments

Comments
 (0)