Skip to content

Commit

Permalink
Detect testing libraries namespace in nested namespaces (#154)
Browse files Browse the repository at this point in the history
* extract namespace detection logic to common base class

* support namespace declaration and nested using directives

* add more tests
  • Loading branch information
Meir017 authored Mar 31, 2022
1 parent a43ef55 commit 8cd3cf2
Show file tree
Hide file tree
Showing 5 changed files with 222 additions and 30 deletions.
2 changes: 1 addition & 1 deletion src/FluentAssertions.Analyzers.Tests/GenerateCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ public static string GenericIListExpressionBodyAssertion(string assertion) => Ge
.AppendLine("}")
.ToString();

private static StringBuilder AppendMainMethod(this StringBuilder builder) => builder
public static StringBuilder AppendMainMethod(this StringBuilder builder) => builder
.AppendLine(" class Program")
.AppendLine(" {")
.AppendLine(" public static void Main()")
Expand Down
175 changes: 172 additions & 3 deletions src/FluentAssertions.Analyzers.Tests/Tips/MsTestTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

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

[AssertionDataTestMethod]
[AssertionDiagnostic("Assert.IsTrue(actual{0});")]
[AssertionDiagnostic("Assert.IsTrue(bool.Parse(\"true\"){0});")]
[Implemented]
public void AssertIsTrue_NestedUsingInNamespace1_TestAnalyzer(string assertion)
=> VerifyCSharpDiagnostic<AssertIsTrueAnalyzer>("bool actual", assertion, new StringBuilder()
.AppendLine("using System;")
.AppendLine("using FluentAssertions;")
.AppendLine("using FluentAssertions.Extensions;")
.AppendLine("using System.Threading.Tasks;")
.AppendLine("namespace Microsoft.VisualStudio.TestTools")
.AppendLine("{")
.AppendLine(" using UnitTesting;")
.AppendLine(" class TestClass")
.AppendLine(" {")
.AppendLine($" void TestMethod(bool actual)")
.AppendLine(" {")
.AppendLine($" {assertion}")
.AppendLine(" }")
.AppendLine(" }")
.AppendMainMethod()
.AppendLine("}")
.ToString());

[AssertionDataTestMethod]
[AssertionDiagnostic("Assert.IsTrue(actual{0});")]
[AssertionDiagnostic("Assert.IsTrue(bool.Parse(\"true\"){0});")]
[Implemented]
public void AssertIsTrue_NestedUsingInNamespace2_TestAnalyzer(string assertion)
=> VerifyCSharpDiagnostic<AssertIsTrueAnalyzer>("bool actual", assertion, new StringBuilder()
.AppendLine("using System;")
.AppendLine("using FluentAssertions;")
.AppendLine("using FluentAssertions.Extensions;")
.AppendLine("using System.Threading.Tasks;")
.AppendLine("namespace Microsoft.VisualStudio")
.AppendLine("{")
.AppendLine(" using TestTools.UnitTesting;")
.AppendLine(" class TestClass")
.AppendLine(" {")
.AppendLine($" void TestMethod(bool actual)")
.AppendLine(" {")
.AppendLine($" {assertion}")
.AppendLine(" }")
.AppendLine(" }")
.AppendMainMethod()
.AppendLine("}")
.ToString());

[AssertionDataTestMethod]
[AssertionDiagnostic("Assert.IsTrue(actual{0});")]
[AssertionDiagnostic("Assert.IsTrue(bool.Parse(\"true\"){0});")]
[Implemented]
public void AssertIsTrue_NestedUsingInNamespace3_TestAnalyzer(string assertion)
=> VerifyCSharpDiagnostic<AssertIsTrueAnalyzer>("bool actual", assertion, new StringBuilder()
.AppendLine("using System;")
.AppendLine("using FluentAssertions;")
.AppendLine("using FluentAssertions.Extensions;")
.AppendLine("using System.Threading.Tasks;")
.AppendLine("namespace Microsoft")
.AppendLine("{ namespace VisualStudio {")
.AppendLine(" using TestTools.UnitTesting;")
.AppendLine(" class TestClass")
.AppendLine(" {")
.AppendLine($" void TestMethod(bool actual)")
.AppendLine(" {")
.AppendLine($" {assertion}")
.AppendLine(" }")
.AppendLine(" }}")
.AppendMainMethod()
.AppendLine("}")
.ToString());

[AssertionDataTestMethod]
[AssertionDiagnostic("Assert.IsTrue(actual{0});")]
[AssertionDiagnostic("Assert.IsTrue(bool.Parse(\"true\"){0});")]
[Implemented]
public void AssertIsTrue_NestedUsingInNamespace4_TestAnalyzer(string assertion)
=> VerifyCSharpDiagnostic<AssertIsTrueAnalyzer>("bool actual", assertion, new StringBuilder()
.AppendLine("using System;")
.AppendLine("using FluentAssertions;")
.AppendLine("using FluentAssertions.Extensions;")
.AppendLine("using System.Threading.Tasks;")
.AppendLine("namespace Microsoft")
.AppendLine("{ namespace VisualStudio {")
.AppendLine(" using TestTools . UnitTesting;")
.AppendLine(" class TestClass")
.AppendLine(" {")
.AppendLine($" void TestMethod(bool actual)")
.AppendLine(" {")
.AppendLine($" {assertion}")
.AppendLine(" }")
.AppendLine(" }}")
.AppendMainMethod()
.AppendLine("}")
.ToString());

[AssertionDataTestMethod]
[AssertionDiagnostic("Assert.IsTrue(actual{0});")]
[AssertionDiagnostic("Assert.IsTrue(bool.Parse(\"true\"){0});")]
[Implemented]
public void AssertIsTrue_NestedUsingInNamespace5_TestAnalyzer(string assertion)
=> VerifyCSharpDiagnostic<AssertIsTrueAnalyzer>("bool actual", assertion, new StringBuilder()
.AppendLine("using System;")
.AppendLine("using FluentAssertions;")
.AppendLine("using FluentAssertions.Extensions;")
.AppendLine("using System.Threading.Tasks;")
.AppendLine("using Microsoft . VisualStudio . TestTools . UnitTesting;")
.AppendLine("namespace Testing")
.AppendLine("{")
.AppendLine(" class TestClass")
.AppendLine(" {")
.AppendLine($" void TestMethod(bool actual)")
.AppendLine(" {")
.AppendLine($" {assertion}")
.AppendLine(" }")
.AppendLine(" }")
.AppendMainMethod()
.AppendLine("}")
.ToString());

[AssertionDataTestMethod]
[AssertionDiagnostic("Assert.IsTrue(actual{0});")]
[AssertionDiagnostic("Assert.IsTrue(bool.Parse(\"true\"){0});")]
[Implemented]
public void AssertIsTrue_NestedUsingInNamespace6_TestAnalyzer(string assertion)
=> VerifyCSharpDiagnostic<AssertIsTrueAnalyzer>("bool actual", assertion, new StringBuilder()
.AppendLine("using System;")
.AppendLine("using FluentAssertions;")
.AppendLine("using FluentAssertions.Extensions;")
.AppendLine("using System.Threading.Tasks; using static Microsoft.VisualStudio.TestTools.UnitTesting.Assert;")
.AppendLine("using Microsoft . VisualStudio . TestTools . UnitTesting;")
.AppendLine("namespace Testing")
.AppendLine("{")
.AppendLine(" class TestClass")
.AppendLine(" {")
.AppendLine($" void TestMethod(bool actual)")
.AppendLine(" {")
.AppendLine($" {assertion}")
.AppendLine(" }")
.AppendLine(" }")
.AppendMainMethod()
.AppendLine("}")
.ToString());

[AssertionDataTestMethod]
[AssertionDiagnostic("Assert.IsTrue(actual{0});")]
[AssertionDiagnostic("Assert.IsTrue(bool.Parse(\"true\"){0});")]
[Implemented]
public void AssertIsTrue_NestedUsingInNamespace7_TestAnalyzer(string assertion)
=> VerifyCSharpDiagnostic<AssertIsTrueAnalyzer>("bool actual", assertion, new StringBuilder()
.AppendLine("using System;")
.AppendLine("using FluentAssertions;")
.AppendLine("using FluentAssertions.Extensions;")
.AppendLine("using System.Threading.Tasks; using MsAssert = Microsoft.VisualStudio.TestTools.UnitTesting.Assert;")
.AppendLine("using Microsoft . VisualStudio . TestTools . UnitTesting;")
.AppendLine("namespace Testing")
.AppendLine("{")
.AppendLine(" class TestClass")
.AppendLine(" {")
.AppendLine($" void TestMethod(bool actual)")
.AppendLine(" {")
.AppendLine($" {assertion}")
.AppendLine(" }")
.AppendLine(" }")
.AppendMainMethod()
.AppendLine("}")
.ToString());

[AssertionDataTestMethod]
[AssertionCodeFix(
oldAssertion: "Assert.IsTrue(actual{0});",
Expand Down Expand Up @@ -512,10 +681,8 @@ public void AssertThrowsExceptionAsync_TestCodeFix(string oldAssertion, string n
[Implemented]
public void StringAssertDoesNotMatch_TestCodeFix(string oldAssertion, string newAssertion) => VerifyCSharpFix<StringAssertDoesNotMatchCodeFix, StringAssertDoesNotMatchAnalyzer>("string actual, System.Text.RegularExpressions.Regex pattern", oldAssertion, newAssertion);

private void VerifyCSharpDiagnostic<TDiagnosticAnalyzer>(string methodArguments, string assertion) where TDiagnosticAnalyzer : Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer, new()
private void VerifyCSharpDiagnostic<TDiagnosticAnalyzer>(string methodArguments, string assertion, string source) where TDiagnosticAnalyzer : Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer, new()
{
var source = GenerateCode.MsTestAssertion(methodArguments, assertion);

var type = typeof(TDiagnosticAnalyzer);
var diagnosticId = (string)type.GetField("DiagnosticId").GetValue(null);
var message = (string)type.GetField("Message").GetValue(null);
Expand All @@ -531,6 +698,8 @@ public void AssertThrowsExceptionAsync_TestCodeFix(string oldAssertion, string n
Severity = DiagnosticSeverity.Info
});
}
private void VerifyCSharpDiagnostic<TDiagnosticAnalyzer>(string methodArguments, string assertion) where TDiagnosticAnalyzer : Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer, new()
=> VerifyCSharpDiagnostic<TDiagnosticAnalyzer>(methodArguments, assertion, GenerateCode.MsTestAssertion(methodArguments, assertion));

private void VerifyCSharpFix<TCodeFixProvider, TDiagnosticAnalyzer>(string methodArguments, string oldAssertion, string newAssertion)
where TCodeFixProvider : Microsoft.CodeAnalysis.CodeFixes.CodeFixProvider, new()
Expand Down
15 changes: 3 additions & 12 deletions src/FluentAssertions.Analyzers/Tips/MsTest/MsTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,13 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Linq;

namespace FluentAssertions.Analyzers
{
public abstract class MsTestAnalyzer : FluentAssertionsAnalyzer
public abstract class MsTestAnalyzer : TestingLibraryAnalyzerBase
{
private static readonly NameSyntax MsTestNamespace = SyntaxFactory.ParseName("Microsoft.VisualStudio.TestTools.UnitTesting");

protected override bool ShouldAnalyzeMethod(MethodDeclarationSyntax method)
{
var compilation = method.FirstAncestorOrSelf<CompilationUnitSyntax>();

if (compilation == null) return false;

return compilation.Usings.Any(usingDirective => usingDirective.Name.IsEquivalentTo(MsTestNamespace));
}
private static readonly string MsTestNamespace = "Microsoft.VisualStudio.TestTools.UnitTesting";
protected override string TestingLibraryNamespace => MsTestNamespace;
}

public abstract class MsTestAssertAnalyzer : MsTestAnalyzer
Expand Down
17 changes: 3 additions & 14 deletions src/FluentAssertions.Analyzers/Tips/Xunit/XunitBase.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,12 @@
using FluentAssertions.Analyzers.Utilities;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Linq;

namespace FluentAssertions.Analyzers.Xunit
{
public abstract class XunitAnalyzer : FluentAssertionsAnalyzer
public abstract class XunitAnalyzer : TestingLibraryAnalyzerBase
{
private static readonly NameSyntax XunitNamespace = SyntaxFactory.ParseName("Xunit");

protected override bool ShouldAnalyzeMethod(MethodDeclarationSyntax method)
{
var compilation = method.FirstAncestorOrSelf<CompilationUnitSyntax>();

if (compilation == null) return false;

return compilation.Usings.Any(usingDirective => usingDirective.Name.IsEquivalentTo(XunitNamespace));
}
private static readonly string XunitNamespace = "Xunit";
protected override string TestingLibraryNamespace => XunitNamespace;

protected override bool ShouldAnalyzeVariableType(INamedTypeSymbol type, SemanticModel semanticModel) => type.Name == "Assert";
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using SF = Microsoft.CodeAnalysis.CSharp.SyntaxFactory;

namespace FluentAssertions.Analyzers
{
Expand Down Expand Up @@ -109,4 +111,45 @@ private Diagnostic AnalyzeExpressionSafely(ExpressionSyntax expression, Semantic
public abstract class FluentAssertionsAnalyzer : FluentAssertionsAnalyzer<FluentAssertionsCSharpSyntaxVisitor>
{
}

public abstract class TestingLibraryAnalyzerBase : FluentAssertionsAnalyzer
{
protected abstract string TestingLibraryNamespace { get; }

protected override bool ShouldAnalyzeMethod(MethodDeclarationSyntax method)
{
var compilation = method.FirstAncestorOrSelf<CompilationUnitSyntax>();

if (compilation == null) return false;

foreach (var @using in compilation.Usings)
{
if (@using.Name.NormalizeWhitespace().ToString().Equals(TestingLibraryNamespace)) return true;
}

var parentNamespace = method.FirstAncestorOrSelf<NamespaceDeclarationSyntax>();
if (parentNamespace != null)
{
var namespaces = new List<NamespaceDeclarationSyntax>();
while(parentNamespace != null)
{
namespaces.Add(parentNamespace);
parentNamespace = parentNamespace.Parent as NamespaceDeclarationSyntax;
}
namespaces.Reverse();

for (int i = 0; i < namespaces.Count; i++)
{
var baseNamespace = string.Join(".", namespaces.Take(i+1).Select(ns => ns.Name));
foreach (var @using in namespaces[i].Usings)
{
var fullUsing = SF.ParseName($"{baseNamespace}.{@using.Name}").NormalizeWhitespace().ToString();
if (fullUsing.Equals(TestingLibraryNamespace)) return true;
}
}
}

return false;
}
}
}

0 comments on commit 8cd3cf2

Please sign in to comment.