feat: migration code fixer improvements for xUnit and NUnit - #4367
Conversation
Adds support for converting xUnit's Record.Exception() and
Record.ExceptionAsync() patterns to try-catch blocks during migration.
The conversion transforms:
```csharp
var ex = Record.Exception(() => SomeMethod());
```
To:
```csharp
Exception? ex = null;
try
{
SomeMethod();
}
catch (Exception e)
{
ex = e;
}
```
Fixes#4332
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>Add tests verifying that Assert.Throws<T>() with property access works correctly: - Assert_Throws_With_Property_Access_Can_Be_Converted: tests capturing exception and accessing properties like .ParamName - Assert_Throws_With_Message_Contains_Can_Be_Converted: tests using Assert.Contains with ex.Message Both tests pass, confirming issue #4334 is working correctly. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Instead of removing the NUnit [Platform] attribute, convert it to TUnit's equivalent attributes: - [Platform(Include = "Win")] -> [RunOn(OS.Windows)] - [Platform(Exclude = "Linux")] -> [ExcludeOn(OS.Linux)] - [Platform(Include = "Win,Linux")] -> [RunOn(OS.Windows | OS.Linux)] Supports mapping NUnit platform strings to TUnit OS enum: - Win, Win32, Windows -> OS.Windows - Linux, Unix -> OS.Linux - MacOsX, MacOS, OSX, Mac -> OS.MacOs Unknown platforms are removed (cannot convert). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
thomhurst
commented
Jan 13, 2026
SummaryThis PR adds NUnit Platform attribute conversion to RunOn/ExcludeOn and XUnit Record.Exception conversion to try-catch blocks for the migration code fixers. Critical IssuesNone found ✅ Suggestions1. NUnit Platform Attribute - Positional Argument BugIn if(argName=="Include"||argName==null){// Named argument Include= or positional argument (which is Include)includeValue=value;}Issue: If there are multiple positional arguments (e.g., Suggestion: Consider only processing the first positional argument or documenting this behavior. 2. Record.Exception - Missing Block Body HandlingIn if(bodyisBlockSyntaxblock){// If it's a single statement block, extract the statementif(block.Statements.Count==1){returnblock.Statements[0];}// Otherwise return the block as-is (will need to be a compound statement)returnblock;}Issue: When returning the block directly for multi-statement lambdas, it's placed inside the try block as a nested block: try{{// Extra braces herestatement1;statement2;}}Suggestion: When if(actionBodyisBlockSyntaxblockBody&&blockBody.Statements.Count>1){// Use block's statements directlytryBodyStatement=SyntaxFactory.Block(blockBody.Statements)...}3. Record.ExceptionAsync - Missing Async/Await VerificationIn // If async, we may need to await the actionif(isAsync&&tryBodyStatementisExpressionStatementSyntaxexprStmt){varawaitExpr=SyntaxFactory.AwaitExpression(...);Issue: This blindly adds Suggestion: Consider using semantic analysis to check if the expression is actually awaitable, or document that this migration assumes correct XUnit usage. 4. Platform Attribute - Case SensitivityIn returnnunitPlatform.ToLowerInvariant()switch{"win" or "win32" or ...=>"Windows",Minor: NUnit's platform matching is case-insensitive, so this is correct. However, consider adding a comment explaining this matches NUnit's behavior, as it's not immediately obvious why we're lower-casing. 5. Test Coverage - Missing Edge CasesThe tests cover basic scenarios well, but consider adding tests for:
Verdict✅ APPROVE - No critical issues The implementation is solid and follows TUnit conventions well. The suggestions above are optional improvements for edge cases and code robustness, but not blocking issues. |
Uh oh!
There was an error while loading. Please reload this page.
Summary
This PR addresses several migration code fixer issues:
Record.Exception()pattern to try-catch blocksAssert.Throws<T>()exception property access (already working)[Platform]attribute not handled #4339: Convert NUnit[Platform]attribute to TUnit[RunOn]/[ExcludeOn]Changes
xUnit Record.Exception() Conversion (#4332)
Converts the xUnit pattern:
To:
xUnit Exception Property Access (#4334)
Added tests verifying that
Assert.Throws<T>()with property access works correctly:.ParamNameAssert.Containswithex.MessageNUnit [Platform] Conversion (#4339)
Instead of removing the NUnit
[Platform]attribute, converts to TUnit equivalents:[Platform(Include = "Win")]→[RunOn(OS.Windows)][Platform(Exclude = "Linux")]→[ExcludeOn(OS.Linux)][Platform(Include = "Win,Linux")]→[RunOn(OS.Windows | OS.Linux)]Supported platform mappings:
Test plan
🤖 Generated with Claude Code