Skip to content

Commit

Permalink
MSTest Asserts (#75)
Browse files Browse the repository at this point in the history
* first mstest analyzer

* added Assert.IsFalse

* cleanup

* progress

* added helper for mstest

* more analyzers

* wip

* fix conflicts

* add support for Assert.ThrowsException & Assert.ThrowsExceptionAsync

* save

* added support for more AssertEqual cases

* support AssertAreNotEqual

* completed mstest Assert scenarios

* cleanup rule messages

* add CollectionAssertAllItemsAreInstancesOfType

* CollectionAssert fixes

* cleanup usings

* cleanups

* fix parent class for mstest Assert

* add CollectionAssert AreEqual / AreNotEqual

* fix parent class for CollectionAssert

* fix parent class for StringAssert

* add CollectionAssert AreEquivalent / AreNotEquivalent

* add CollectionAssertAllItemsAreNotNull

* add CollectionAssert.AllItemsAreUnique

* add CollectionAssert.Contains

* add CollectionAssert.DoesNotContain

* fix tests

* add CollectionAssert.IsSubsetOf / CollectionAssert.IsNotSubsetOf

* add StringAssert methods

* fix tests
  • Loading branch information
Meir017 authored Jan 10, 2022
1 parent 1f7428e commit ea678d7
Show file tree
Hide file tree
Showing 54 changed files with 2,268 additions and 32 deletions.
5 changes: 4 additions & 1 deletion src/FluentAssertions.Analyzers.Tests/DiagnosticVerifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ static DiagnosticVerifier()
typeof(Compilation), // Microsoft.CodeAnalysis
typeof(AssertionScope), // FluentAssertions.Core
typeof(AssertionExtensions), // FluentAssertions
typeof(Microsoft.VisualStudio.TestTools.UnitTesting.Assert) // MsTest
}.Select(type => type.GetTypeInfo().Assembly.Location)
.Append(GetSystemAssemblyPathByName("System.Globalization.dll"))
.Append(GetSystemAssemblyPathByName("System.Text.RegularExpressions.dll"))
.Append(GetSystemAssemblyPathByName("System.Runtime.Extensions.dll"))
.Append(GetSystemAssemblyPathByName("System.Data.Common.dll"))
.Append(GetSystemAssemblyPathByName("System.Threading.Tasks.dll"))
Expand Down Expand Up @@ -641,7 +644,7 @@ private static string FormatDiagnostics(DiagnosticAnalyzer[] analyzers, params D

private static DiagnosticAnalyzer[] CreateAllAnalyzers()
{
var assembly = typeof(Constants).Assembly;
var assembly = typeof(Constants).Assembly;
var analyzersTypes = assembly.GetTypes()
.Where(type => !type.IsAbstract && typeof(DiagnosticAnalyzer).IsAssignableFrom(type));
var analyzers = analyzersTypes.Select(type => (DiagnosticAnalyzer)Activator.CreateInstance(type));
Expand Down
19 changes: 19 additions & 0 deletions src/FluentAssertions.Analyzers.Tests/GenerateCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,25 @@ public static string GenericIListExpressionBodyAssertion(string assertion) => Ge
.AppendLine("}")
.ToString();

public static string MsTestAssertion(string methodArguments, string assertion) => new StringBuilder()
.AppendLine("using System;")
.AppendLine("using FluentAssertions;")
.AppendLine("using FluentAssertions.Extensions;")
.AppendLine("using Microsoft.VisualStudio.TestTools.UnitTesting;")
.AppendLine("using System.Threading.Tasks;")
.AppendLine("namespace TestNamespace")
.AppendLine("{")
.AppendLine(" class TestClass")
.AppendLine(" {")
.AppendLine($" void TestMethod({methodArguments})")
.AppendLine(" {")
.AppendLine($" {assertion}")
.AppendLine(" }")
.AppendLine(" }")
.AppendMainMethod()
.AppendLine("}")
.ToString();

private static StringBuilder AppendMainMethod(this StringBuilder builder) => builder
.AppendLine(" class Program")
.AppendLine(" {")
Expand Down
545 changes: 545 additions & 0 deletions src/FluentAssertions.Analyzers.Tests/Tips/MsTestTests.cs

Large diffs are not rendered by default.

32 changes: 32 additions & 0 deletions src/FluentAssertions.Analyzers/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,38 @@ public static class Exceptions
public const string ExceptionShouldThrowWithMessage = nameof(ExceptionShouldThrowWithMessage);
public const string ExceptionShouldThrowWithInnerException = nameof(ExceptionShouldThrowWithInnerException);
}

public static class MsTest
{
public const string AssertIsTrue = nameof(AssertIsTrue);
public const string AssertIsFalse = nameof(AssertIsFalse);
public const string AssertIsNotNull = nameof(AssertIsNotNull);
public const string AssertIsNull = nameof(AssertIsNull);
public const string AssertIsInstanceOfType = nameof(AssertIsInstanceOfType);
public const string AssertIsNotInstanceOfType = nameof(AssertIsNotInstanceOfType);
public const string AssertAreEqual = nameof(AssertAreEqual);
public const string AssertAreNotEqual = nameof(AssertAreNotEqual);
public const string AssertAreSame = nameof(AssertAreSame);
public const string AssertAreNotSame = nameof(AssertAreNotSame);
public const string AssertThrowsException = nameof(AssertThrowsException);
public const string AssertThrowsExceptionAsync = nameof(AssertThrowsExceptionAsync);
public const string StringAssertContains = nameof(StringAssertContains);
public const string StringAssertStartsWith = nameof(StringAssertStartsWith);
public const string StringAssertEndsWith = nameof(StringAssertEndsWith);
public const string StringAssertMatches = nameof(StringAssertMatches);
public const string StringAssertDoesNotMatch = nameof(StringAssertDoesNotMatch);
public const string CollectionAssertAllItemsAreInstancesOfType = nameof(CollectionAssertAllItemsAreInstancesOfType);
public const string CollectionAssertAreEqual = nameof(CollectionAssertAreEqual);
public const string CollectionAssertAreNotEqual = nameof(CollectionAssertAreNotEqual);
public const string CollectionAssertAreEquivalent = nameof(CollectionAssertAreEquivalent);
public const string CollectionAssertAreNotEquivalent = nameof(CollectionAssertAreNotEquivalent);
public const string CollectionAssertAllItemsAreNotNull = nameof(CollectionAssertAllItemsAreNotNull);
public const string CollectionAssertAllItemsAreUnique = nameof(CollectionAssertAllItemsAreUnique);
public const string CollectionAssertContains = nameof(CollectionAssertContains);
public const string CollectionAssertDoesNotContain = nameof(CollectionAssertDoesNotContain);
public const string CollectionAssertIsSubsetOf = nameof(CollectionAssertIsSubsetOf);
public const string CollectionAssertIsNotSubsetOf = nameof(CollectionAssertIsNotSubsetOf);
}
}

public static class CodeSmell
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

<PropertyGroup>
<RootNamespace>FluentAssertions.Analyzers</RootNamespace>
<LangVersion>8.0</LangVersion>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public class ShouldHaveCount0SyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor
{
}

private static bool HaveCountArgumentsValidator(SeparatedSyntaxList<ArgumentSyntax> arguments)
private static bool HaveCountArgumentsValidator(SeparatedSyntaxList<ArgumentSyntax> arguments, SemanticModel semanticModel)
{
if (!arguments.Any()) return false;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;

namespace FluentAssertions.Analyzers
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ public SelectShouldEqualOtherCollectionSelectSyntaxVisitor()
{
}

private static bool MathodContainingArgumentInvokingLambda(SeparatedSyntaxList<ArgumentSyntax> arguments)
private static bool MathodContainingArgumentInvokingLambda(SeparatedSyntaxList<ArgumentSyntax> arguments, SemanticModel semanticModel)
{
if (!arguments.Any()) return false;

return arguments[0].Expression is InvocationExpressionSyntax invocation
&& MemberValidator.MethodContainingLambdaPredicate(invocation.ArgumentList.Arguments);
&& MemberValidator.MethodContainingLambdaPredicate(invocation.ArgumentList.Arguments, semanticModel);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;

namespace FluentAssertions.Analyzers
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public class ShouldHaveCountOtherCollectionCountSyntaxVisitor : FluentAssertions
{
}

private static bool HasArgumentInvokingCountMethod(SeparatedSyntaxList<ArgumentSyntax> arguments)
private static bool HasArgumentInvokingCountMethod(SeparatedSyntaxList<ArgumentSyntax> arguments, SemanticModel semanticModel)
{
if (!arguments.Any()) return false;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public class CountShouldNotBeOtherCollectionCountSyntaxVisitor : FluentAssertion
{
}

private static bool HasArgumentInvokingCountMethod(SeparatedSyntaxList<ArgumentSyntax> arguments)
private static bool HasArgumentInvokingCountMethod(SeparatedSyntaxList<ArgumentSyntax> arguments, SemanticModel semanticModel)
{
if (!arguments.Any()) return false;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public class ShouldHaveSameCountThisCollectionDistinctSyntaxVisitor : FluentAsse
{
}

private static bool ArgumentInvokesDistinctMethod(SeparatedSyntaxList<ArgumentSyntax> arguments)
private static bool ArgumentInvokesDistinctMethod(SeparatedSyntaxList<ArgumentSyntax> arguments, SemanticModel semanticModel)
{
if (!arguments.Any()) return false;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,15 @@ public override bool IsValid(ExpressionSyntax expression)
&& keyIdentifier.Identifier.Text == valueIdentifier.Identifier.Text;
}

protected static bool KeyIsProperty(SeparatedSyntaxList<ArgumentSyntax> arguments)
protected static bool KeyIsProperty(SeparatedSyntaxList<ArgumentSyntax> arguments, SemanticModel semanticModel)
{
if (!arguments.Any()) return false;

return arguments.First().Expression is MemberAccessExpressionSyntax valueAccess
&& valueAccess.Expression is IdentifierNameSyntax identifier
&& valueAccess.Name.Identifier.Text == "Key";
}
protected static bool ValueIsProperty(SeparatedSyntaxList<ArgumentSyntax> arguments)
protected static bool ValueIsProperty(SeparatedSyntaxList<ArgumentSyntax> arguments, SemanticModel semanticModel)
{
if (!arguments.Any()) return false;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using SF = Microsoft.CodeAnalysis.CSharp.SyntaxFactory;

namespace FluentAssertions.Analyzers
{
Expand Down
104 changes: 104 additions & 0 deletions src/FluentAssertions.Analyzers/Tips/MsTest/AssertAreEqual.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Threading;
using System.Threading.Tasks;
using TypeSelector = FluentAssertions.Analyzers.Utilities.SemanticModelTypeExtensions;

namespace FluentAssertions.Analyzers
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class AssertAreEqualAnalyzer : MsTestAssertAnalyzer
{
public const string DiagnosticId = Constants.Tips.MsTest.AssertAreEqual;
public const string Category = Constants.Tips.Category;

public const string Message = "Use .Should().BeApproximately() for complex numbers and .Should().Be() for other cases.";

protected override DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true);
protected override IEnumerable<FluentAssertionsCSharpSyntaxVisitor> Visitors
{
get
{
yield return new AssertFloatAreEqualWithDeltaSyntaxVisitor();
yield return new AssertDoubleAreEqualWithDeltaSyntaxVisitor();
yield return new AssertStringAreEqualSyntaxVisitor();
yield return new AssertObjectAreEqualSyntaxVisitor();
}
}

// public static void AreEqual(float expected, float actual, float delta)
public class AssertFloatAreEqualWithDeltaSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor
{
public AssertFloatAreEqualWithDeltaSyntaxVisitor() : base(
MemberValidator.ArgumentsMatch("AreEqual",
ArgumentValidator.IsType(TypeSelector.GetFloatType),
ArgumentValidator.IsType(TypeSelector.GetFloatType),
ArgumentValidator.IsType(TypeSelector.GetFloatType))
)
{
}
}

// public static void AreEqual(double expected, double actual, double delta)
public class AssertDoubleAreEqualWithDeltaSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor
{
public AssertDoubleAreEqualWithDeltaSyntaxVisitor() : base(
MemberValidator.ArgumentsMatch("AreEqual",
ArgumentValidator.IsType(TypeSelector.GetDoubleType),
ArgumentValidator.IsType(TypeSelector.GetDoubleType),
ArgumentValidator.IsType(TypeSelector.GetDoubleType))
)
{
}
}

// public static void AreEqual(string expected, string actual, bool ignoreCase, CultureInfo culture
public class AssertStringAreEqualSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor
{
public AssertStringAreEqualSyntaxVisitor() : base(
MemberValidator.ArgumentsMatch("AreEqual",
ArgumentValidator.IsType(TypeSelector.GetStringType),
ArgumentValidator.IsType(TypeSelector.GetStringType),
ArgumentValidator.IsType(TypeSelector.GetBooleanType)))
{
}
}

// public static void AreEqual<T>(T expected, T actual)
// public static void AreEqual(object expected, object actual)
public class AssertObjectAreEqualSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor
{
public AssertObjectAreEqualSyntaxVisitor() : base(new MemberValidator("AreEqual"))
{
}
}
}

[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(AssertAreEqualCodeFix)), Shared]
public class AssertAreEqualCodeFix : MsTestCodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(AssertAreEqualAnalyzer.DiagnosticId);

protected override async Task<ExpressionSyntax> GetNewExpressionAsync(ExpressionSyntax expression, Document document, FluentAssertionsDiagnosticProperties properties, CancellationToken cancellationToken)
{
switch (properties.VisitorName)
{
case nameof(AssertAreEqualAnalyzer.AssertFloatAreEqualWithDeltaSyntaxVisitor):
case nameof(AssertAreEqualAnalyzer.AssertDoubleAreEqualWithDeltaSyntaxVisitor):
return RenameMethodAndReorderActualExpectedAndReplaceWithSubjectShould(expression, "AreEqual", "BeApproximately", "Assert");
case nameof(AssertAreEqualAnalyzer.AssertObjectAreEqualSyntaxVisitor):
return RenameMethodAndReorderActualExpectedAndReplaceWithSubjectShould(expression, "AreEqual", "Be", "Assert");
case nameof(AssertAreEqualAnalyzer.AssertStringAreEqualSyntaxVisitor):
var semanticModel = await document.GetSemanticModelAsync(cancellationToken);
return GetNewExpressionForAreNotEqualOrAreEqualStrings(expression, semanticModel, "AreEqual", "Be", "BeEquivalentTo", "Assert");
default:
throw new System.InvalidOperationException($"Invalid visitor name - {properties.VisitorName}");
}
}
}
}
Loading

0 comments on commit ea678d7

Please sign in to comment.