Skip to content

Commit 7d1c8b2

Browse files
authored
feat: Add Using directive for FluentAssertion if missing (#285)
1 parent f7a5c04 commit 7d1c8b2

File tree

4 files changed

+190
-2
lines changed

4 files changed

+190
-2
lines changed

src/FluentAssertions.Analyzers.Tests/Tips/FluentAssertionsTests.cs

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using Microsoft.CodeAnalysis.CodeFixes;
12
using Microsoft.VisualStudio.TestTools.UnitTesting;
23

34
namespace FluentAssertions.Analyzers.Tests
@@ -55,5 +56,146 @@ public void BeTrue() { }
5556
.WithSources(source)
5657
);
5758
}
59+
60+
[TestMethod]
61+
[Implemented]
62+
public void ShouldAddFluentAssertionsUsing_WhenFluentAssertionIsNotInScope_ForXunit()
63+
=> ShouldAddFluentAssertionsUsing_WhenFluentAssertionIsNotInScope<XunitCodeFixProvider>("True", "using Xunit;", PackageReference.XunitAssert_2_5_1);
64+
65+
[TestMethod]
66+
[Implemented]
67+
public void ShouldNotAddFluentAssertionsUsing_WhenFluentAssertionIsInGlobalScope_ForXunit()
68+
=> ShouldNotAddFluentAssertionsUsing_WhenFluentAssertionIsInGlobalScope<XunitCodeFixProvider>("True", "using Xunit;", PackageReference.XunitAssert_2_5_1);
69+
70+
[TestMethod]
71+
[Implemented]
72+
public void ShouldNotAddFluentAssertionsUsing_WhenFluentAssertionIsInAnyScope_ForXunit()
73+
=> ShouldNotAddFluentAssertionsUsing_WhenFluentAssertionIsInAnyScope<XunitCodeFixProvider>("True", "using Xunit;", PackageReference.XunitAssert_2_5_1);
74+
75+
[TestMethod]
76+
[Implemented]
77+
public void ShouldAddFluentAssertionsUsing_WhenFluentAssertionIsNotInScope_ForMsTest()
78+
=> ShouldAddFluentAssertionsUsing_WhenFluentAssertionIsNotInScope<MsTestCodeFixProvider>("IsTrue", "using Microsoft.VisualStudio.TestTools.UnitTesting;", PackageReference.MSTestTestFramework_3_1_1);
79+
80+
[TestMethod]
81+
[Implemented]
82+
public void ShouldNotAddFluentAssertionsUsing_WhenFluentAssertionIsInGlobalScope_ForMsTest()
83+
=> ShouldNotAddFluentAssertionsUsing_WhenFluentAssertionIsInGlobalScope<MsTestCodeFixProvider>("IsTrue", "using Microsoft.VisualStudio.TestTools.UnitTesting;", PackageReference.MSTestTestFramework_3_1_1);
84+
85+
[TestMethod]
86+
[Implemented]
87+
public void ShouldNotAddFluentAssertionsUsing_WhenFluentAssertionIsInAnyScope_ForMsTest()
88+
=> ShouldNotAddFluentAssertionsUsing_WhenFluentAssertionIsInAnyScope<MsTestCodeFixProvider>("IsTrue", "using Microsoft.VisualStudio.TestTools.UnitTesting;", PackageReference.MSTestTestFramework_3_1_1);
89+
90+
private void ShouldAddFluentAssertionsUsing_WhenFluentAssertionIsNotInScope<TCodeFixProvider>(string assertTrue, string usingDirective, PackageReference testingLibraryReference) where TCodeFixProvider : CodeFixProvider, new()
91+
{
92+
string source = $@"
93+
{usingDirective}
94+
namespace TestProject
95+
{{
96+
public class TestClass
97+
{{
98+
public void TestMethod(bool subject)
99+
{{
100+
Assert.{assertTrue}(subject);
101+
}}
102+
}}
103+
}}";
104+
string newSource = @$"
105+
using FluentAssertions;
106+
{usingDirective}
107+
namespace TestProject
108+
{{
109+
public class TestClass
110+
{{
111+
public void TestMethod(bool subject)
112+
{{
113+
subject.Should().BeTrue();
114+
}}
115+
}}
116+
}}";
117+
DiagnosticVerifier.VerifyFix(new CodeFixVerifierArguments()
118+
.WithDiagnosticAnalyzer<AssertAnalyzer>()
119+
.WithCodeFixProvider<TCodeFixProvider>()
120+
.WithPackageReferences(PackageReference.FluentAssertions_6_12_0, testingLibraryReference)
121+
.WithSources(source)
122+
.WithFixedSources(newSource)
123+
);
124+
}
125+
126+
private void ShouldNotAddFluentAssertionsUsing_WhenFluentAssertionIsInGlobalScope<TCodeFixProvider>(string assertTrue, string usingDirective, PackageReference testingLibraryReference) where TCodeFixProvider : CodeFixProvider, new()
127+
{
128+
string source = $@"
129+
{usingDirective}
130+
namespace TestProject
131+
{{
132+
public class TestClass
133+
{{
134+
public void TestMethod(bool subject)
135+
{{
136+
Assert.{assertTrue}(subject);
137+
}}
138+
}}
139+
}}";
140+
const string globalUsings = "global using FluentAssertions;";
141+
string newSource = @$"
142+
{usingDirective}
143+
namespace TestProject
144+
{{
145+
public class TestClass
146+
{{
147+
public void TestMethod(bool subject)
148+
{{
149+
subject.Should().BeTrue();
150+
}}
151+
}}
152+
}}";
153+
154+
DiagnosticVerifier.VerifyFix(new CodeFixVerifierArguments()
155+
.WithDiagnosticAnalyzer<AssertAnalyzer>()
156+
.WithCodeFixProvider<TCodeFixProvider>()
157+
.WithPackageReferences(PackageReference.FluentAssertions_6_12_0, testingLibraryReference)
158+
.WithSources(source, globalUsings)
159+
.WithFixedSources(newSource)
160+
);
161+
}
162+
163+
private void ShouldNotAddFluentAssertionsUsing_WhenFluentAssertionIsInAnyScope<TCodeFixProvider>(string assertTrue, string usingDirective, PackageReference testingLibraryReference) where TCodeFixProvider : CodeFixProvider, new()
164+
{
165+
string source = $@"
166+
{usingDirective}
167+
namespace TestProject
168+
{{
169+
using FluentAssertions;
170+
public class TestClass
171+
{{
172+
public void TestMethod(bool subject)
173+
{{
174+
Assert.{assertTrue}(subject);
175+
}}
176+
}}
177+
}}";
178+
string newSource = @$"
179+
{usingDirective}
180+
namespace TestProject
181+
{{
182+
using FluentAssertions;
183+
public class TestClass
184+
{{
185+
public void TestMethod(bool subject)
186+
{{
187+
subject.Should().BeTrue();
188+
}}
189+
}}
190+
}}";
191+
192+
DiagnosticVerifier.VerifyFix(new CodeFixVerifierArguments()
193+
.WithDiagnosticAnalyzer<AssertAnalyzer>()
194+
.WithCodeFixProvider<TCodeFixProvider>()
195+
.WithPackageReferences(PackageReference.FluentAssertions_6_12_0, testingLibraryReference)
196+
.WithSources(source)
197+
.WithFixedSources(newSource)
198+
);
199+
}
58200
}
59201
}

src/FluentAssertions.Analyzers/Tips/MsTestCodeFixProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public class MsTestCodeFixProvider : TestingFrameworkCodeFixProvider
1212
{
1313
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(AssertAnalyzer.MSTestsRule.Id);
1414

15-
protected override CreateChangedDocument TryComputeFix(IInvocationOperation invocation, CodeFixContext context, TestingFrameworkCodeFixContext testContext, Diagnostic diagnostic)
15+
protected override CreateChangedDocument TryComputeFixCore(IInvocationOperation invocation, CodeFixContext context, TestingFrameworkCodeFixContext testContext, Diagnostic diagnostic)
1616
{
1717
var assertType = invocation.TargetMethod.ContainingType;
1818
return assertType.Name switch

src/FluentAssertions.Analyzers/Tips/TestingFrameworkCodeFixProvider.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
1+
using System;
2+
using System.Linq;
3+
using System.Threading;
4+
using System.Threading.Tasks;
15
using FluentAssertions.Analyzers.Utilities;
26
using Microsoft.CodeAnalysis;
7+
using Microsoft.CodeAnalysis.CodeFixes;
8+
using Microsoft.CodeAnalysis.CSharp;
9+
using Microsoft.CodeAnalysis.CSharp.Syntax;
10+
using Microsoft.CodeAnalysis.Editing;
11+
using Microsoft.CodeAnalysis.Formatting;
312
using Microsoft.CodeAnalysis.Operations;
13+
using Microsoft.CodeAnalysis.Options;
14+
using Microsoft.CodeAnalysis.Simplification;
415

516
namespace FluentAssertions.Analyzers;
617

@@ -71,6 +82,41 @@ protected static bool ArgumentsCount(IInvocationOperation invocation, int argume
7182
return invocation.TargetMethod.Parameters.Length == arguments;
7283
}
7384

85+
protected override Func<CancellationToken, Task<Document>> TryComputeFix(IInvocationOperation invocation, CodeFixContext context, TestingFrameworkCodeFixContext t, Diagnostic diagnostic)
86+
{
87+
var fix = TryComputeFixCore(invocation, context, t, diagnostic);
88+
if (fix is null)
89+
{
90+
return null;
91+
}
92+
93+
return async ctx =>
94+
{
95+
const string fluentAssertionNamespace = "FluentAssertions";
96+
var document = await fix(ctx);
97+
98+
var model = await document.GetSemanticModelAsync();
99+
var scopes = model.GetImportScopes(diagnostic.Location.SourceSpan.Start);
100+
101+
var hasFluentAssertionImport = scopes.Any(scope => scope.Imports.Any(import => import.NamespaceOrType.ToString().Equals(fluentAssertionNamespace)));
102+
if (hasFluentAssertionImport)
103+
{
104+
return document;
105+
}
106+
107+
var root = (CompilationUnitSyntax) await document.GetSyntaxRootAsync();
108+
root = root.AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(fluentAssertionNamespace)).WithAdditionalAnnotations(Simplifier.AddImportsAnnotation));
109+
110+
document = document.WithSyntaxRoot(root);
111+
document = await Formatter.OrganizeImportsAsync(document);
112+
113+
return document;
114+
};
115+
}
116+
117+
protected abstract Func<CancellationToken, Task<Document>> TryComputeFixCore(IInvocationOperation invocation, CodeFixContext context, TestingFrameworkCodeFixContext t, Diagnostic diagnostic);
118+
119+
74120
public sealed class TestingFrameworkCodeFixContext(Compilation compilation)
75121
{
76122
public INamedTypeSymbol Object { get; } = compilation.ObjectType;

src/FluentAssertions.Analyzers/Tips/XunitCodeFixProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public class XunitCodeFixProvider : TestingFrameworkCodeFixProvider
1313
{
1414
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(AssertAnalyzer.XunitRule.Id);
1515

16-
protected override CreateChangedDocument TryComputeFix(IInvocationOperation invocation, CodeFixContext context, TestingFrameworkCodeFixContext t, Diagnostic diagnostic)
16+
protected override CreateChangedDocument TryComputeFixCore(IInvocationOperation invocation, CodeFixContext context, TestingFrameworkCodeFixContext t, Diagnostic diagnostic)
1717
{
1818
switch (invocation.TargetMethod.Name)
1919
{

0 commit comments

Comments
 (0)