Skip to content

Commit 1c423c3

Browse files
authored
Improve XUnit Migration Analyzer (#2482)
* WIP * WIP * WIP * WIP * Working * WIP * Fixes * Fixes * Fixes
1 parent 37f8d35 commit 1c423c3

File tree

5 files changed

+179
-77
lines changed

5 files changed

+179
-77
lines changed

TUnit.Analyzers.CodeFixers/XUnitMigrationCodeFixProvider.cs

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -59,22 +59,22 @@ private static async Task<Document> ConvertCodeAsync(Document document, Cancella
5959

6060
// Always use the latest updatedRoot as input for the next transformation
6161
var updatedRoot = UpdateInitializeDispose(compilation, root);
62-
UpdateSyntaxTrees(ref compilation, ref syntaxTree, updatedRoot);
62+
UpdateSyntaxTrees(ref compilation, ref syntaxTree, ref updatedRoot);
6363

6464
updatedRoot = UpdateClassAttributes(compilation, updatedRoot);
65-
UpdateSyntaxTrees(ref compilation, ref syntaxTree, updatedRoot);
65+
UpdateSyntaxTrees(ref compilation, ref syntaxTree, ref updatedRoot);
6666

6767
updatedRoot = RemoveInterfacesAndBaseClasses(compilation, updatedRoot);
68-
UpdateSyntaxTrees(ref compilation, ref syntaxTree, updatedRoot);
68+
UpdateSyntaxTrees(ref compilation, ref syntaxTree, ref updatedRoot);
6969

7070
updatedRoot = ConvertTheoryData(compilation, updatedRoot);
71-
UpdateSyntaxTrees(ref compilation, ref syntaxTree, updatedRoot);
71+
UpdateSyntaxTrees(ref compilation, ref syntaxTree, ref updatedRoot);
7272

7373
updatedRoot = ConvertTestOutputHelpers(ref compilation, ref syntaxTree, updatedRoot);
74-
UpdateSyntaxTrees(ref compilation, ref syntaxTree, updatedRoot);
74+
UpdateSyntaxTrees(ref compilation, ref syntaxTree, ref updatedRoot);
7575

7676
updatedRoot = RemoveUsingDirectives(updatedRoot);
77-
UpdateSyntaxTrees(ref compilation, ref syntaxTree, updatedRoot);
77+
UpdateSyntaxTrees(ref compilation, ref syntaxTree, ref updatedRoot);
7878

7979
// Apply all changes in one step
8080
return document.WithSyntaxRoot(updatedRoot);
@@ -104,7 +104,7 @@ private static SyntaxNode ConvertTestOutputHelpers(ref Compilation compilation,
104104
)
105105
);
106106

107-
UpdateSyntaxTrees(ref compilation, ref syntaxTree, currentRoot);
107+
UpdateSyntaxTrees(ref compilation, ref syntaxTree, ref currentRoot);
108108
compilationValue = compilation;
109109
}
110110

@@ -494,10 +494,15 @@ public override SyntaxNode VisitAttributeList(AttributeListSyntax node)
494494
SyntaxFactory.IdentifierName("Obsolete")))],
495495
_ => [attr]
496496
};
497-
497+
498498
newAttributes.AddRange(converted);
499499
}
500500

501+
if (node.Attributes.SequenceEqual(newAttributes))
502+
{
503+
return node;
504+
}
505+
501506
// Preserve original trivia instead of forcing elastic trivia
502507
return SyntaxFactory.AttributeList(SyntaxFactory.SeparatedList(newAttributes))
503508
.WithLeadingTrivia(node.GetLeadingTrivia())
@@ -718,9 +723,24 @@ public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node)
718723
}
719724
}
720725

721-
private static void UpdateSyntaxTrees(ref Compilation compilation, ref SyntaxTree syntaxTree, SyntaxNode updatedRoot)
726+
private static void UpdateSyntaxTrees(ref Compilation compilation, ref SyntaxTree syntaxTree, ref SyntaxNode updatedRoot)
722727
{
723-
compilation = compilation.ReplaceSyntaxTree(syntaxTree, updatedRoot.SyntaxTree);
724-
syntaxTree = updatedRoot.SyntaxTree;
728+
var parseOptions = syntaxTree.Options;
729+
var newSyntaxTree = updatedRoot.SyntaxTree;
730+
731+
// If the parse options differ, re-parse the updatedRoot with the correct options
732+
if (!Equals(newSyntaxTree.Options, parseOptions))
733+
{
734+
newSyntaxTree = CSharpSyntaxTree.ParseText(
735+
updatedRoot.ToFullString(),
736+
(CSharpParseOptions)parseOptions,
737+
syntaxTree.FilePath
738+
);
739+
}
740+
741+
compilation = compilation.ReplaceSyntaxTree(syntaxTree, newSyntaxTree);
742+
syntaxTree = newSyntaxTree;
743+
744+
updatedRoot = newSyntaxTree.GetRoot();
725745
}
726746
}

TUnit.Analyzers.Tests/Verifiers/CSharpAnalyzerVerifier`1.cs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Microsoft.CodeAnalysis.Diagnostics;
44
using Microsoft.CodeAnalysis.Testing;
55
using System.Diagnostics.CodeAnalysis;
6+
using Microsoft.CodeAnalysis.Text;
67
using Polly.CircuitBreaker;
78
using TUnit.Core;
89
using TUnit.TestProject.Library;
@@ -25,13 +26,18 @@ public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor)
2526
=> CSharpAnalyzerVerifier<TAnalyzer, DefaultVerifier>.Diagnostic(descriptor);
2627

2728
/// <inheritdoc cref="AnalyzerVerifier{TAnalyzer, TTest, TVerifier}.VerifyAnalyzerAsync(string, DiagnosticResult[])"/>
28-
public static async Task VerifyAnalyzerAsync([StringSyntax("c#")] string source, params DiagnosticResult[] expected)
29+
public static Task VerifyAnalyzerAsync([StringSyntax("c#")] string source, params DiagnosticResult[] expected)
30+
{
31+
return VerifyAnalyzerAsync(source, _ => { }, expected);
32+
}
33+
34+
/// <inheritdoc cref="AnalyzerVerifier{TAnalyzer, TTest, TVerifier}.VerifyAnalyzerAsync(string, DiagnosticResult[])"/>
35+
public static async Task VerifyAnalyzerAsync([StringSyntax("c#")] string source, Action<Test> configureTest, params DiagnosticResult[] expected)
2936
{
3037
var test = new Test
3138
{
3239
TestCode = source,
33-
ReferenceAssemblies = ReferenceAssemblies.Net.Net90
34-
.AddPackages([new PackageIdentity("xunit.v3.extensibility.core", "2.0.0")]),
40+
ReferenceAssemblies = ReferenceAssemblies.Net.Net90,
3541
TestState =
3642
{
3743
AdditionalReferences =
@@ -44,6 +50,9 @@ public static async Task VerifyAnalyzerAsync([StringSyntax("c#")] string source,
4450
};
4551

4652
test.ExpectedDiagnostics.AddRange(expected);
53+
54+
configureTest(test);
55+
4756
await test.RunAsync(CancellationToken.None);
4857
}
4958
}

TUnit.Analyzers.Tests/Verifiers/CSharpCodeFixVerifier`2.cs

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,16 @@ public static DiagnosticResult Diagnostic(string diagnosticId)
2525
public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor)
2626
=> CSharpCodeFixVerifier<TAnalyzer, TCodeFix, DefaultVerifier>.Diagnostic(descriptor);
2727

28+
/// <inheritdoc cref="AnalyzerVerifier{TAnalyzer, TTest, TVerifier}.VerifyAnalyzerAsync(string, DiagnosticResult[])"/>
29+
public static Task VerifyAnalyzerAsync([StringSyntax("c#")] string source, params DiagnosticResult[] expected)
30+
{
31+
return VerifyAnalyzerAsync(source, _ => { }, expected);
32+
}
33+
2834
/// <inheritdoc cref="CodeFixVerifier{TAnalyzer, TCodeFix, TTest, TVerifier}.VerifyAnalyzerAsync(string, DiagnosticResult[])"/>
2935
public static async Task VerifyAnalyzerAsync(
3036
[StringSyntax("c#")] string source,
37+
Action<Test> configureTest,
3138
params DiagnosticResult[] expected
3239
)
3340
{
@@ -47,6 +54,9 @@ params DiagnosticResult[] expected
4754
};
4855

4956
test.ExpectedDiagnostics.AddRange(expected);
57+
58+
configureTest(test);
59+
5060
await test.RunAsync(CancellationToken.None);
5161
}
5262

@@ -58,19 +68,32 @@ public static async Task VerifyCodeFixAsync([StringSyntax("c#")] string source,
5868
public static async Task VerifyCodeFixAsync([StringSyntax("c#")] string source, DiagnosticResult expected, [StringSyntax("c#")] string fixedSource)
5969
=> await VerifyCodeFixAsync(source, [expected], fixedSource);
6070

71+
public static async Task VerifyCodeFixAsync([StringSyntax("c#")] string source, DiagnosticResult expected, [StringSyntax("c#")] string fixedSource, Action<Test> configureTest)
72+
=> await VerifyCodeFixAsync(source, [expected], fixedSource, configureTest);
73+
6174
/// <inheritdoc cref="CodeFixVerifier{TAnalyzer, TCodeFix, TTest, TVerifier}.VerifyCodeFixAsync(string, DiagnosticResult[], string)"/>
62-
public static async Task VerifyCodeFixAsync(
75+
public static Task VerifyCodeFixAsync(
6376
[StringSyntax("c#")] string source,
6477
IEnumerable<DiagnosticResult> expected,
6578
[StringSyntax("c#")] string fixedSource
6679
)
80+
{
81+
return VerifyCodeFixAsync(source, expected, fixedSource, _ => { });
82+
}
83+
84+
/// <inheritdoc cref="CodeFixVerifier{TAnalyzer, TCodeFix, TTest, TVerifier}.VerifyCodeFixAsync(string, DiagnosticResult[], string)"/>
85+
public static async Task VerifyCodeFixAsync(
86+
[StringSyntax("c#")] string source,
87+
IEnumerable<DiagnosticResult> expected,
88+
[StringSyntax("c#")] string fixedSource,
89+
Action<Test> configureTest
90+
)
6791
{
6892
var test = new Test
6993
{
7094
TestCode = source.NormalizeLineEndings(),
7195
FixedCode = fixedSource.NormalizeLineEndings(),
72-
ReferenceAssemblies = ReferenceAssemblies.Net.Net90
73-
.AddPackages([new PackageIdentity("xunit.v3.extensibility.core", "2.0.0")]),
96+
ReferenceAssemblies = ReferenceAssemblies.Net.Net90,
7497
TestState =
7598
{
7699
AdditionalReferences =
@@ -83,6 +106,9 @@ public static async Task VerifyCodeFixAsync(
83106
};
84107

85108
test.ExpectedDiagnostics.AddRange(expected);
109+
110+
configureTest(test);
111+
86112
await test.RunAsync(CancellationToken.None);
87113
}
88114
}

0 commit comments

Comments
 (0)