Skip to content

Commit 8cd3cf2

Browse files
authored
Detect testing libraries namespace in nested namespaces (#154)
* extract namespace detection logic to common base class * support namespace declaration and nested using directives * add more tests
1 parent a43ef55 commit 8cd3cf2

File tree

5 files changed

+222
-30
lines changed

5 files changed

+222
-30
lines changed

src/FluentAssertions.Analyzers.Tests/GenerateCode.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ public static string GenericIListExpressionBodyAssertion(string assertion) => Ge
213213
.AppendLine("}")
214214
.ToString();
215215

216-
private static StringBuilder AppendMainMethod(this StringBuilder builder) => builder
216+
public static StringBuilder AppendMainMethod(this StringBuilder builder) => builder
217217
.AppendLine(" class Program")
218218
.AppendLine(" {")
219219
.AppendLine(" public static void Main()")

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

Lines changed: 172 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using Microsoft.VisualStudio.TestTools.UnitTesting;
33
using System;
44
using System.Collections;
5+
using System.Text;
56
using System.Text.RegularExpressions;
67
using System.Threading.Tasks;
78

@@ -16,6 +17,174 @@ public class MsTestTests
1617
[Implemented]
1718
public void AssertIsTrue_TestAnalyzer(string assertion) => VerifyCSharpDiagnostic<AssertIsTrueAnalyzer>("bool actual", assertion);
1819

20+
[AssertionDataTestMethod]
21+
[AssertionDiagnostic("Assert.IsTrue(actual{0});")]
22+
[AssertionDiagnostic("Assert.IsTrue(bool.Parse(\"true\"){0});")]
23+
[Implemented]
24+
public void AssertIsTrue_NestedUsingInNamespace1_TestAnalyzer(string assertion)
25+
=> VerifyCSharpDiagnostic<AssertIsTrueAnalyzer>("bool actual", assertion, new StringBuilder()
26+
.AppendLine("using System;")
27+
.AppendLine("using FluentAssertions;")
28+
.AppendLine("using FluentAssertions.Extensions;")
29+
.AppendLine("using System.Threading.Tasks;")
30+
.AppendLine("namespace Microsoft.VisualStudio.TestTools")
31+
.AppendLine("{")
32+
.AppendLine(" using UnitTesting;")
33+
.AppendLine(" class TestClass")
34+
.AppendLine(" {")
35+
.AppendLine($" void TestMethod(bool actual)")
36+
.AppendLine(" {")
37+
.AppendLine($" {assertion}")
38+
.AppendLine(" }")
39+
.AppendLine(" }")
40+
.AppendMainMethod()
41+
.AppendLine("}")
42+
.ToString());
43+
44+
[AssertionDataTestMethod]
45+
[AssertionDiagnostic("Assert.IsTrue(actual{0});")]
46+
[AssertionDiagnostic("Assert.IsTrue(bool.Parse(\"true\"){0});")]
47+
[Implemented]
48+
public void AssertIsTrue_NestedUsingInNamespace2_TestAnalyzer(string assertion)
49+
=> VerifyCSharpDiagnostic<AssertIsTrueAnalyzer>("bool actual", assertion, new StringBuilder()
50+
.AppendLine("using System;")
51+
.AppendLine("using FluentAssertions;")
52+
.AppendLine("using FluentAssertions.Extensions;")
53+
.AppendLine("using System.Threading.Tasks;")
54+
.AppendLine("namespace Microsoft.VisualStudio")
55+
.AppendLine("{")
56+
.AppendLine(" using TestTools.UnitTesting;")
57+
.AppendLine(" class TestClass")
58+
.AppendLine(" {")
59+
.AppendLine($" void TestMethod(bool actual)")
60+
.AppendLine(" {")
61+
.AppendLine($" {assertion}")
62+
.AppendLine(" }")
63+
.AppendLine(" }")
64+
.AppendMainMethod()
65+
.AppendLine("}")
66+
.ToString());
67+
68+
[AssertionDataTestMethod]
69+
[AssertionDiagnostic("Assert.IsTrue(actual{0});")]
70+
[AssertionDiagnostic("Assert.IsTrue(bool.Parse(\"true\"){0});")]
71+
[Implemented]
72+
public void AssertIsTrue_NestedUsingInNamespace3_TestAnalyzer(string assertion)
73+
=> VerifyCSharpDiagnostic<AssertIsTrueAnalyzer>("bool actual", assertion, new StringBuilder()
74+
.AppendLine("using System;")
75+
.AppendLine("using FluentAssertions;")
76+
.AppendLine("using FluentAssertions.Extensions;")
77+
.AppendLine("using System.Threading.Tasks;")
78+
.AppendLine("namespace Microsoft")
79+
.AppendLine("{ namespace VisualStudio {")
80+
.AppendLine(" using TestTools.UnitTesting;")
81+
.AppendLine(" class TestClass")
82+
.AppendLine(" {")
83+
.AppendLine($" void TestMethod(bool actual)")
84+
.AppendLine(" {")
85+
.AppendLine($" {assertion}")
86+
.AppendLine(" }")
87+
.AppendLine(" }}")
88+
.AppendMainMethod()
89+
.AppendLine("}")
90+
.ToString());
91+
92+
[AssertionDataTestMethod]
93+
[AssertionDiagnostic("Assert.IsTrue(actual{0});")]
94+
[AssertionDiagnostic("Assert.IsTrue(bool.Parse(\"true\"){0});")]
95+
[Implemented]
96+
public void AssertIsTrue_NestedUsingInNamespace4_TestAnalyzer(string assertion)
97+
=> VerifyCSharpDiagnostic<AssertIsTrueAnalyzer>("bool actual", assertion, new StringBuilder()
98+
.AppendLine("using System;")
99+
.AppendLine("using FluentAssertions;")
100+
.AppendLine("using FluentAssertions.Extensions;")
101+
.AppendLine("using System.Threading.Tasks;")
102+
.AppendLine("namespace Microsoft")
103+
.AppendLine("{ namespace VisualStudio {")
104+
.AppendLine(" using TestTools . UnitTesting;")
105+
.AppendLine(" class TestClass")
106+
.AppendLine(" {")
107+
.AppendLine($" void TestMethod(bool actual)")
108+
.AppendLine(" {")
109+
.AppendLine($" {assertion}")
110+
.AppendLine(" }")
111+
.AppendLine(" }}")
112+
.AppendMainMethod()
113+
.AppendLine("}")
114+
.ToString());
115+
116+
[AssertionDataTestMethod]
117+
[AssertionDiagnostic("Assert.IsTrue(actual{0});")]
118+
[AssertionDiagnostic("Assert.IsTrue(bool.Parse(\"true\"){0});")]
119+
[Implemented]
120+
public void AssertIsTrue_NestedUsingInNamespace5_TestAnalyzer(string assertion)
121+
=> VerifyCSharpDiagnostic<AssertIsTrueAnalyzer>("bool actual", assertion, new StringBuilder()
122+
.AppendLine("using System;")
123+
.AppendLine("using FluentAssertions;")
124+
.AppendLine("using FluentAssertions.Extensions;")
125+
.AppendLine("using System.Threading.Tasks;")
126+
.AppendLine("using Microsoft . VisualStudio . TestTools . UnitTesting;")
127+
.AppendLine("namespace Testing")
128+
.AppendLine("{")
129+
.AppendLine(" class TestClass")
130+
.AppendLine(" {")
131+
.AppendLine($" void TestMethod(bool actual)")
132+
.AppendLine(" {")
133+
.AppendLine($" {assertion}")
134+
.AppendLine(" }")
135+
.AppendLine(" }")
136+
.AppendMainMethod()
137+
.AppendLine("}")
138+
.ToString());
139+
140+
[AssertionDataTestMethod]
141+
[AssertionDiagnostic("Assert.IsTrue(actual{0});")]
142+
[AssertionDiagnostic("Assert.IsTrue(bool.Parse(\"true\"){0});")]
143+
[Implemented]
144+
public void AssertIsTrue_NestedUsingInNamespace6_TestAnalyzer(string assertion)
145+
=> VerifyCSharpDiagnostic<AssertIsTrueAnalyzer>("bool actual", assertion, new StringBuilder()
146+
.AppendLine("using System;")
147+
.AppendLine("using FluentAssertions;")
148+
.AppendLine("using FluentAssertions.Extensions;")
149+
.AppendLine("using System.Threading.Tasks; using static Microsoft.VisualStudio.TestTools.UnitTesting.Assert;")
150+
.AppendLine("using Microsoft . VisualStudio . TestTools . UnitTesting;")
151+
.AppendLine("namespace Testing")
152+
.AppendLine("{")
153+
.AppendLine(" class TestClass")
154+
.AppendLine(" {")
155+
.AppendLine($" void TestMethod(bool actual)")
156+
.AppendLine(" {")
157+
.AppendLine($" {assertion}")
158+
.AppendLine(" }")
159+
.AppendLine(" }")
160+
.AppendMainMethod()
161+
.AppendLine("}")
162+
.ToString());
163+
164+
[AssertionDataTestMethod]
165+
[AssertionDiagnostic("Assert.IsTrue(actual{0});")]
166+
[AssertionDiagnostic("Assert.IsTrue(bool.Parse(\"true\"){0});")]
167+
[Implemented]
168+
public void AssertIsTrue_NestedUsingInNamespace7_TestAnalyzer(string assertion)
169+
=> VerifyCSharpDiagnostic<AssertIsTrueAnalyzer>("bool actual", assertion, new StringBuilder()
170+
.AppendLine("using System;")
171+
.AppendLine("using FluentAssertions;")
172+
.AppendLine("using FluentAssertions.Extensions;")
173+
.AppendLine("using System.Threading.Tasks; using MsAssert = Microsoft.VisualStudio.TestTools.UnitTesting.Assert;")
174+
.AppendLine("using Microsoft . VisualStudio . TestTools . UnitTesting;")
175+
.AppendLine("namespace Testing")
176+
.AppendLine("{")
177+
.AppendLine(" class TestClass")
178+
.AppendLine(" {")
179+
.AppendLine($" void TestMethod(bool actual)")
180+
.AppendLine(" {")
181+
.AppendLine($" {assertion}")
182+
.AppendLine(" }")
183+
.AppendLine(" }")
184+
.AppendMainMethod()
185+
.AppendLine("}")
186+
.ToString());
187+
19188
[AssertionDataTestMethod]
20189
[AssertionCodeFix(
21190
oldAssertion: "Assert.IsTrue(actual{0});",
@@ -512,10 +681,8 @@ public void AssertThrowsExceptionAsync_TestCodeFix(string oldAssertion, string n
512681
[Implemented]
513682
public void StringAssertDoesNotMatch_TestCodeFix(string oldAssertion, string newAssertion) => VerifyCSharpFix<StringAssertDoesNotMatchCodeFix, StringAssertDoesNotMatchAnalyzer>("string actual, System.Text.RegularExpressions.Regex pattern", oldAssertion, newAssertion);
514683

515-
private void VerifyCSharpDiagnostic<TDiagnosticAnalyzer>(string methodArguments, string assertion) where TDiagnosticAnalyzer : Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer, new()
684+
private void VerifyCSharpDiagnostic<TDiagnosticAnalyzer>(string methodArguments, string assertion, string source) where TDiagnosticAnalyzer : Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer, new()
516685
{
517-
var source = GenerateCode.MsTestAssertion(methodArguments, assertion);
518-
519686
var type = typeof(TDiagnosticAnalyzer);
520687
var diagnosticId = (string)type.GetField("DiagnosticId").GetValue(null);
521688
var message = (string)type.GetField("Message").GetValue(null);
@@ -531,6 +698,8 @@ public void AssertThrowsExceptionAsync_TestCodeFix(string oldAssertion, string n
531698
Severity = DiagnosticSeverity.Info
532699
});
533700
}
701+
private void VerifyCSharpDiagnostic<TDiagnosticAnalyzer>(string methodArguments, string assertion) where TDiagnosticAnalyzer : Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer, new()
702+
=> VerifyCSharpDiagnostic<TDiagnosticAnalyzer>(methodArguments, assertion, GenerateCode.MsTestAssertion(methodArguments, assertion));
534703

535704
private void VerifyCSharpFix<TCodeFixProvider, TDiagnosticAnalyzer>(string methodArguments, string oldAssertion, string newAssertion)
536705
where TCodeFixProvider : Microsoft.CodeAnalysis.CodeFixes.CodeFixProvider, new()

src/FluentAssertions.Analyzers/Tips/MsTest/MsTestBase.cs

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,13 @@
22
using Microsoft.CodeAnalysis;
33
using Microsoft.CodeAnalysis.CSharp;
44
using Microsoft.CodeAnalysis.CSharp.Syntax;
5-
using System.Linq;
65

76
namespace FluentAssertions.Analyzers
87
{
9-
public abstract class MsTestAnalyzer : FluentAssertionsAnalyzer
8+
public abstract class MsTestAnalyzer : TestingLibraryAnalyzerBase
109
{
11-
private static readonly NameSyntax MsTestNamespace = SyntaxFactory.ParseName("Microsoft.VisualStudio.TestTools.UnitTesting");
12-
13-
protected override bool ShouldAnalyzeMethod(MethodDeclarationSyntax method)
14-
{
15-
var compilation = method.FirstAncestorOrSelf<CompilationUnitSyntax>();
16-
17-
if (compilation == null) return false;
18-
19-
return compilation.Usings.Any(usingDirective => usingDirective.Name.IsEquivalentTo(MsTestNamespace));
20-
}
10+
private static readonly string MsTestNamespace = "Microsoft.VisualStudio.TestTools.UnitTesting";
11+
protected override string TestingLibraryNamespace => MsTestNamespace;
2112
}
2213

2314
public abstract class MsTestAssertAnalyzer : MsTestAnalyzer

src/FluentAssertions.Analyzers/Tips/Xunit/XunitBase.cs

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,12 @@
11
using FluentAssertions.Analyzers.Utilities;
22
using Microsoft.CodeAnalysis;
3-
using Microsoft.CodeAnalysis.CSharp;
4-
using Microsoft.CodeAnalysis.CSharp.Syntax;
5-
using System.Linq;
63

74
namespace FluentAssertions.Analyzers.Xunit
85
{
9-
public abstract class XunitAnalyzer : FluentAssertionsAnalyzer
6+
public abstract class XunitAnalyzer : TestingLibraryAnalyzerBase
107
{
11-
private static readonly NameSyntax XunitNamespace = SyntaxFactory.ParseName("Xunit");
12-
13-
protected override bool ShouldAnalyzeMethod(MethodDeclarationSyntax method)
14-
{
15-
var compilation = method.FirstAncestorOrSelf<CompilationUnitSyntax>();
16-
17-
if (compilation == null) return false;
18-
19-
return compilation.Usings.Any(usingDirective => usingDirective.Name.IsEquivalentTo(XunitNamespace));
20-
}
8+
private static readonly string XunitNamespace = "Xunit";
9+
protected override string TestingLibraryNamespace => XunitNamespace;
2110

2211
protected override bool ShouldAnalyzeVariableType(INamedTypeSymbol type, SemanticModel semanticModel) => type.Name == "Assert";
2312
}

src/FluentAssertions.Analyzers/Utilities/FluentAssertionsAnalyzer.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
using Microsoft.CodeAnalysis;
2+
using Microsoft.CodeAnalysis.CSharp;
23
using Microsoft.CodeAnalysis.CSharp.Syntax;
34
using Microsoft.CodeAnalysis.Diagnostics;
45
using System;
56
using System.Collections.Generic;
67
using System.Collections.Immutable;
78
using System.Linq;
9+
using SF = Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
810

911
namespace FluentAssertions.Analyzers
1012
{
@@ -109,4 +111,45 @@ private Diagnostic AnalyzeExpressionSafely(ExpressionSyntax expression, Semantic
109111
public abstract class FluentAssertionsAnalyzer : FluentAssertionsAnalyzer<FluentAssertionsCSharpSyntaxVisitor>
110112
{
111113
}
114+
115+
public abstract class TestingLibraryAnalyzerBase : FluentAssertionsAnalyzer
116+
{
117+
protected abstract string TestingLibraryNamespace { get; }
118+
119+
protected override bool ShouldAnalyzeMethod(MethodDeclarationSyntax method)
120+
{
121+
var compilation = method.FirstAncestorOrSelf<CompilationUnitSyntax>();
122+
123+
if (compilation == null) return false;
124+
125+
foreach (var @using in compilation.Usings)
126+
{
127+
if (@using.Name.NormalizeWhitespace().ToString().Equals(TestingLibraryNamespace)) return true;
128+
}
129+
130+
var parentNamespace = method.FirstAncestorOrSelf<NamespaceDeclarationSyntax>();
131+
if (parentNamespace != null)
132+
{
133+
var namespaces = new List<NamespaceDeclarationSyntax>();
134+
while(parentNamespace != null)
135+
{
136+
namespaces.Add(parentNamespace);
137+
parentNamespace = parentNamespace.Parent as NamespaceDeclarationSyntax;
138+
}
139+
namespaces.Reverse();
140+
141+
for (int i = 0; i < namespaces.Count; i++)
142+
{
143+
var baseNamespace = string.Join(".", namespaces.Take(i+1).Select(ns => ns.Name));
144+
foreach (var @using in namespaces[i].Usings)
145+
{
146+
var fullUsing = SF.ParseName($"{baseNamespace}.{@using.Name}").NormalizeWhitespace().ToString();
147+
if (fullUsing.Equals(TestingLibraryNamespace)) return true;
148+
}
149+
}
150+
}
151+
152+
return false;
153+
}
154+
}
112155
}

0 commit comments

Comments
 (0)