diff --git a/TUnit.Analyzers.CodeFixers/TwoPhase/XUnitTwoPhaseAnalyzer.cs b/TUnit.Analyzers.CodeFixers/TwoPhase/XUnitTwoPhaseAnalyzer.cs index c33de0bcd1..bbb0b7e63a 100644 --- a/TUnit.Analyzers.CodeFixers/TwoPhase/XUnitTwoPhaseAnalyzer.cs +++ b/TUnit.Analyzers.CodeFixers/TwoPhase/XUnitTwoPhaseAnalyzer.cs @@ -865,38 +865,49 @@ protected override bool ShouldRemoveAttribute(AttributeSyntax node) return conversion; } + private static readonly Dictionary _xUnitFactAndTheoryToTUnitArguments = new() + { + { "Skip", "Skip" }, + { "DisplayName", "DisplayName" }, + { "Timeout", "Timeout" }, + }; + private AttributeConversion ConvertTestAttribute(AttributeSyntax node) { - // Check for Skip argument: [Fact(Skip = "reason")] or [Theory(Skip = "reason")] - var skipArg = node.ArgumentList?.Arguments - .FirstOrDefault(a => a.NameEquals?.Name.Identifier.ValueText == "Skip"); + var result = new AttributeConversion + { + NewAttributeName = "Test", + NewArgumentList = "", // Remove any arguments + OriginalText = node.ToString(), + AdditionalAttributes = node.ArgumentList is null ? null : [] + }; - if (skipArg != null) + if (node.ArgumentList is null) { - // Extract the skip reason and create additional Skip attribute - var skipReason = skipArg.Expression.ToString(); - return new AttributeConversion - { - NewAttributeName = "Test", - NewArgumentList = "", // Remove the Skip argument - OriginalText = node.ToString(), - AdditionalAttributes = new List - { - new AdditionalAttribute - { - Name = "Skip", - Arguments = $"({skipReason})" - } - } - }; + return result; } - return new AttributeConversion + foreach (var argument in node.ArgumentList.Arguments) { - NewAttributeName = "Test", - NewArgumentList = "", // Remove any arguments - OriginalText = node.ToString() - }; + if (argument.NameEquals is null) + { + continue; + } + + if (!_xUnitFactAndTheoryToTUnitArguments.TryGetValue(argument.NameEquals.Name.Identifier.ValueText, out var argumentName)) + { + continue; + } + + var argumentValue = argument.Expression.ToString(); + result.AdditionalAttributes!.Add(new AdditionalAttribute + { + Name = argumentName, + Arguments = $"({argumentValue})" + }); + } + + return result; } private AttributeConversion? ConvertCollection(AttributeSyntax node) diff --git a/TUnit.Analyzers.Tests/XUnitMigrationAnalyzerTests.cs b/TUnit.Analyzers.Tests/XUnitMigrationAnalyzerTests.cs index cbc2619747..5b86515664 100644 --- a/TUnit.Analyzers.Tests/XUnitMigrationAnalyzerTests.cs +++ b/TUnit.Analyzers.Tests/XUnitMigrationAnalyzerTests.cs @@ -110,6 +110,78 @@ public void MyTest() ); } + [Test] + [Arguments("Fact")] + [Arguments("Theory")] + [Arguments("Xunit.Fact")] + [Arguments("Xunit.Theory")] + public async Task DisplayNamed_Test_Attributes_Can_Be_Fixed(string attribute) + { + await CodeFixer + .VerifyCodeFixAsync( + $$""" + {|#0:using Xunit; + + public class MyClass + { + [{{attribute}}(DisplayName = "Description")] + public void MyTest() + { + } + }|} + """, + Verifier.Diagnostic(Rules.XunitMigration).WithLocation(0), + $$""" + + public class MyClass + { + [Test] + [DisplayName("Description")] + public void MyTest() + { + } + } + """, + ConfigureXUnitTest + ); + } + + [Test] + [Arguments("Fact")] + [Arguments("Theory")] + [Arguments("Xunit.Fact")] + [Arguments("Xunit.Theory")] + public async Task Timeout_Test_Attributes_Can_Be_Fixed(string attribute) + { + await CodeFixer + .VerifyCodeFixAsync( + $$""" + {|#0:using Xunit; + + public class MyClass + { + [{{attribute}}(Timeout = 3600)] + public void MyTest() + { + } + }|} + """, + Verifier.Diagnostic(Rules.XunitMigration).WithLocation(0), + $$""" + + public class MyClass + { + [Test] + [Timeout(3600)] + public void MyTest() + { + } + } + """, + ConfigureXUnitTest + ); + } + [Test] public async Task Collection_Attributes_Can_Be_Fixed() {