Skip to content

Commit a686729

Browse files
authored
XUnit ITestOutputHelper Migration Code Fixers (#2465)
1 parent d3d3536 commit a686729

File tree

2 files changed

+150
-2
lines changed

2 files changed

+150
-2
lines changed

TUnit.Analyzers.CodeFixers/XUnitMigrationCodeFixProvider.cs

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,16 +67,94 @@ private static async Task<Document> ConvertCodeAsync(Document document, Cancella
6767
updatedRoot = RemoveInterfacesAndBaseClasses(compilation, updatedRoot);
6868
UpdateSyntaxTrees(ref compilation, ref syntaxTree, updatedRoot);
6969

70-
updatedRoot = RemoveUsingDirectives(updatedRoot);
70+
updatedRoot = ConvertTheoryData(compilation, updatedRoot);
7171
UpdateSyntaxTrees(ref compilation, ref syntaxTree, updatedRoot);
7272

73-
updatedRoot = ConvertTheoryData(compilation, updatedRoot);
73+
updatedRoot = ConvertTestOutputHelpers(ref compilation, ref syntaxTree, updatedRoot);
74+
UpdateSyntaxTrees(ref compilation, ref syntaxTree, updatedRoot);
75+
76+
updatedRoot = RemoveUsingDirectives(updatedRoot);
7477
UpdateSyntaxTrees(ref compilation, ref syntaxTree, updatedRoot);
7578

7679
// Apply all changes in one step
7780
return document.WithSyntaxRoot(updatedRoot);
7881
}
7982

83+
private static SyntaxNode ConvertTestOutputHelpers(ref Compilation compilation, ref SyntaxTree syntaxTree, SyntaxNode root)
84+
{
85+
var currentRoot = root;
86+
87+
var compilationValue = compilation;
88+
89+
while (currentRoot.DescendantNodes()
90+
.OfType<InvocationExpressionSyntax>()
91+
.FirstOrDefault(x => IsTestOutputHelperInvocation(compilationValue, x))
92+
is {} invocationExpressionSyntax)
93+
{
94+
var memberAccessExpressionSyntax = (MemberAccessExpressionSyntax)invocationExpressionSyntax.Expression;
95+
96+
currentRoot = currentRoot.ReplaceNode(
97+
invocationExpressionSyntax,
98+
invocationExpressionSyntax.WithExpression(
99+
SyntaxFactory.MemberAccessExpression(
100+
SyntaxKind.SimpleMemberAccessExpression,
101+
SyntaxFactory.IdentifierName("Console"),
102+
SyntaxFactory.IdentifierName(memberAccessExpressionSyntax.Name.Identifier.Text)
103+
)
104+
)
105+
);
106+
107+
UpdateSyntaxTrees(ref compilation, ref syntaxTree, currentRoot);
108+
compilationValue = compilation;
109+
}
110+
111+
while (currentRoot.DescendantNodes()
112+
.OfType<ParameterSyntax>()
113+
.FirstOrDefault(x => x.Type?.TryGetInferredMemberName() == "ITestOutputHelper")
114+
is {} parameterSyntax)
115+
{
116+
currentRoot = currentRoot.RemoveNode(parameterSyntax, SyntaxRemoveOptions.KeepNoTrivia)!;
117+
}
118+
119+
while (currentRoot.DescendantNodes()
120+
.OfType<PropertyDeclarationSyntax>()
121+
.FirstOrDefault(x => x.Type.TryGetInferredMemberName() == "ITestOutputHelper")
122+
is {} propertyDeclarationSyntax)
123+
{
124+
currentRoot = currentRoot.RemoveNode(propertyDeclarationSyntax, SyntaxRemoveOptions.KeepNoTrivia)!;
125+
}
126+
127+
while (currentRoot.DescendantNodes()
128+
.OfType<FieldDeclarationSyntax>()
129+
.FirstOrDefault(x => x.Declaration.Type.TryGetInferredMemberName() == "ITestOutputHelper")
130+
is {} fieldDeclarationSyntax)
131+
{
132+
currentRoot = currentRoot.RemoveNode(fieldDeclarationSyntax, SyntaxRemoveOptions.KeepNoTrivia)!;
133+
}
134+
135+
return currentRoot;
136+
}
137+
138+
private static bool IsTestOutputHelperInvocation(Compilation compilation, InvocationExpressionSyntax invocationExpressionSyntax)
139+
{
140+
var semanticModel = compilation.GetSemanticModel(invocationExpressionSyntax.SyntaxTree);
141+
142+
var symbolInfo = semanticModel.GetSymbolInfo(invocationExpressionSyntax);
143+
144+
if (symbolInfo.Symbol is not IMethodSymbol methodSymbol)
145+
{
146+
return false;
147+
}
148+
149+
if (invocationExpressionSyntax.Expression is not MemberAccessExpressionSyntax)
150+
{
151+
return false;
152+
}
153+
154+
return methodSymbol.ContainingType?.ToDisplayString(DisplayFormats.FullyQualifiedGenericWithGlobalPrefix)
155+
is "global::Xunit.Abstractions.ITestOutputHelper" or "global::Xunit.ITestOutputHelper";
156+
}
157+
80158
private static SyntaxNode ConvertTheoryData(Compilation compilation, SyntaxNode root)
81159
{
82160
var currentRoot = root;
@@ -88,6 +166,11 @@ private static SyntaxNode ConvertTheoryData(Compilation compilation, SyntaxNode
88166
ImplicitObjectCreationExpressionSyntax implicitObjectCreationExpressionSyntax => SyntaxFactory.ParseTypeName(compilation.GetSemanticModel(implicitObjectCreationExpressionSyntax.SyntaxTree).GetTypeInfo(implicitObjectCreationExpressionSyntax).Type!.ToDisplayString()),
89167
_ => null
90168
};
169+
170+
while (type is QualifiedNameSyntax qualifiedNameSyntax)
171+
{
172+
type = qualifiedNameSyntax.Right;
173+
}
91174

92175
if (type is not GenericNameSyntax genericNameSyntax ||
93176
genericNameSyntax.Identifier.Text != "TheoryData")

TUnit.Analyzers.Tests/XUnitMigrationAnalyzerTests.cs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,4 +560,69 @@ public class MyClass
560560
"""
561561
);
562562
}
563+
564+
[Test]
565+
public async Task ITestOutputHelper_Is_Flagged()
566+
{
567+
await CodeFixer
568+
.VerifyAnalyzerAsync(
569+
"""
570+
{|#0:using System;
571+
using Xunit;
572+
573+
public class UnitTest1(ITestOutputHelper testOutputHelper)
574+
{
575+
private ITestOutputHelper _testOutputHelper = testOutputHelper;
576+
public ITestOutputHelper TestOutputHelper { get; } = testOutputHelper;
577+
578+
[Fact]
579+
public void Test1()
580+
{
581+
_testOutputHelper.WriteLine("Foo");
582+
TestOutputHelper.WriteLine("Bar");
583+
}
584+
}|}
585+
""",
586+
Verifier.Diagnostic(Rules.XunitMigration).WithLocation(0)
587+
);
588+
}
589+
590+
[Test]
591+
public async Task ITestOutputHelper_Can_Be_Converted()
592+
{
593+
await CodeFixer
594+
.VerifyCodeFixAsync(
595+
"""
596+
{|#0:using TUnit.Core;
597+
using Xunit;
598+
599+
public class UnitTest1(ITestOutputHelper testOutputHelper)
600+
{
601+
private ITestOutputHelper _testOutputHelper = testOutputHelper;
602+
public ITestOutputHelper TestOutputHelper { get; } = testOutputHelper;
603+
604+
[Fact]
605+
public void Test1()
606+
{
607+
_testOutputHelper.WriteLine("Foo");
608+
TestOutputHelper.WriteLine("Bar");
609+
}
610+
}|}
611+
""",
612+
Verifier.Diagnostic(Rules.XunitMigration).WithLocation(0),
613+
"""
614+
using TUnit.Core;
615+
616+
public class UnitTest1()
617+
{
618+
[Test]
619+
public void Test1()
620+
{
621+
Console.WriteLine("Foo");
622+
Console.WriteLine("Bar");
623+
}
624+
}
625+
"""
626+
);
627+
}
563628
}

0 commit comments

Comments
 (0)