From fc7af1020aa10c2e667789b7a90270d4c5927ce8 Mon Sep 17 00:00:00 2001 From: Meir Blachman Date: Wed, 4 Oct 2023 09:24:49 +0300 Subject: [PATCH] add support for xunit SetAsserts Contains --- .../DiagnosticVerifier.cs | 1 + .../GenerateCode.cs | 1 + .../Tips/XunitTests.cs | 34 +++++++++++++++++-- .../Tips/Xunit/AssertContains.cs | 19 ++++++++++- .../Utilities/ArgumentValidator.cs | 6 ++++ 5 files changed, 57 insertions(+), 4 deletions(-) diff --git a/src/FluentAssertions.Analyzers.Tests/DiagnosticVerifier.cs b/src/FluentAssertions.Analyzers.Tests/DiagnosticVerifier.cs index eebf4ca3..0dcd523a 100644 --- a/src/FluentAssertions.Analyzers.Tests/DiagnosticVerifier.cs +++ b/src/FluentAssertions.Analyzers.Tests/DiagnosticVerifier.cs @@ -39,6 +39,7 @@ static DiagnosticVerifier() typeof(AssertionScope), // FluentAssertions.Core typeof(AssertionExtensions), // FluentAssertions typeof(HttpRequestMessage), // System.Net.Http + typeof(ImmutableArray), // System.Collections.Immutable typeof(Microsoft.VisualStudio.TestTools.UnitTesting.Assert), // MsTest typeof(XunitAssert), // Xunit }.Select(type => type.GetTypeInfo().Assembly.Location) diff --git a/src/FluentAssertions.Analyzers.Tests/GenerateCode.cs b/src/FluentAssertions.Analyzers.Tests/GenerateCode.cs index 98e1f831..9329d578 100644 --- a/src/FluentAssertions.Analyzers.Tests/GenerateCode.cs +++ b/src/FluentAssertions.Analyzers.Tests/GenerateCode.cs @@ -225,6 +225,7 @@ public static string GenericIListExpressionBodyAssertion(string assertion) => Ge public static string XunitAssertion(string methodArguments, string assertion) => new StringBuilder() .AppendLine("using System;") .AppendLine("using System.Collections.Generic;") + .AppendLine("using System.Collections.Immutable;") .AppendLine("using System.Text.RegularExpressions;") .AppendLine("using FluentAssertions;") .AppendLine("using FluentAssertions.Extensions;") diff --git a/src/FluentAssertions.Analyzers.Tests/Tips/XunitTests.cs b/src/FluentAssertions.Analyzers.Tests/Tips/XunitTests.cs index dbc5e541..410e5482 100644 --- a/src/FluentAssertions.Analyzers.Tests/Tips/XunitTests.cs +++ b/src/FluentAssertions.Analyzers.Tests/Tips/XunitTests.cs @@ -2,8 +2,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using FluentAssertions.Analyzers.Xunit; -using XunitAssert = Xunit.Assert; - namespace FluentAssertions.Analyzers.Tests.Tips { [TestClass] @@ -368,6 +366,36 @@ public void AssertStringContains_TestAnalyzer(string assertion) => public void AssertStringContains_TestCodeFix(string oldAssertion, string newAssertion) => VerifyCSharpFix("string actual, string expected", oldAssertion, newAssertion); + [DataTestMethod] + [DataRow("Assert.Contains(expected, actual);", "ISet actual, string expected")] + [DataRow("Assert.Contains(expected, actual);", "IReadOnlySet actual, string expected")] + [DataRow("Assert.Contains(expected, actual);", "HashSet actual, string expected")] + [DataRow("Assert.Contains(expected, actual);", "ImmutableHashSet actual, string expected")] + [Implemented] + public void AssertSetContains_TestAnalyzer(string assertion, string arguments) => + VerifyCSharpDiagnostic(arguments, assertion); + + [DataTestMethod] + [DataRow( + /* oldAssertion: */ "Assert.Contains(expected, actual);", + /* newAssertion: */ "actual.Should().Contain(expected);", + /* arguments: */ "ISet actual, string expected")] + [DataRow( + /* oldAssertion: */ "Assert.Contains(expected, actual);", + /* newAssertion: */ "actual.Should().Contain(expected);", + /* arguments: */ "IReadOnlySet actual, string expected")] + [DataRow( + /* oldAssertion: */ "Assert.Contains(expected, actual);", + /* newAssertion: */ "actual.Should().Contain(expected);", + /* arguments: */ "HashSet actual, string expected")] + [DataRow( + /* oldAssertion: */ "Assert.Contains(expected, actual);", + /* newAssertion: */ "actual.Should().Contain(expected);", + /* arguments: */ "ImmutableHashSet actual, string expected")] + [Implemented] + public void AssertSetContains_TestCodeFix(string oldAssertion, string newAssertion, string arguments) + => VerifyCSharpFix(arguments, oldAssertion, newAssertion); + [DataTestMethod] [DataRow("Assert.DoesNotContain(expected, actual);")] [Implemented] @@ -494,7 +522,7 @@ public void AssertStartsWith_TestCodeFix(string oldAssertion, string newAssertio Message = message, Locations = new DiagnosticResultLocation[] { - new("Test0.cs", 14, 13) + new("Test0.cs", 15, 13) }, Severity = DiagnosticSeverity.Info }); diff --git a/src/FluentAssertions.Analyzers/Tips/Xunit/AssertContains.cs b/src/FluentAssertions.Analyzers/Tips/Xunit/AssertContains.cs index a9bc9476..093d005b 100644 --- a/src/FluentAssertions.Analyzers/Tips/Xunit/AssertContains.cs +++ b/src/FluentAssertions.Analyzers/Tips/Xunit/AssertContains.cs @@ -21,7 +21,8 @@ public class AssertContainsAnalyzer : XunitAnalyzer protected override IEnumerable Visitors => new FluentAssertionsCSharpSyntaxVisitor[] { - new AssertContainsStringSyntaxVisitor() + new AssertContainsStringSyntaxVisitor(), + new AssertContainsSetSyntaxVisitor() }; //public static void Contains(string expectedSubstring, string? actualString) @@ -35,6 +36,21 @@ public AssertContainsStringSyntaxVisitor() : base( { } } + + //public static void Contains(T expected, ISet actual) + //public static void Contains(T expected, IReadOnlySet actual) + //public static void Contains(T expected, HashSet actual) + //public static void Contains(T expected, ImmutableHashSet actual) + public class AssertContainsSetSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor + { + public AssertContainsSetSyntaxVisitor() : base( + MemberValidator.ArgumentsMatch("Contains", + ArgumentValidator.Exists(), + ArgumentValidator.IsTypeOrConstructedFromTypeOrImplementsType(SpecialType.System_Collections_IEnumerable)) + ) + { + } + } } [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(AssertContainsCodeFix)), Shared] @@ -49,6 +65,7 @@ protected override ExpressionSyntax GetNewExpression( switch (properties.VisitorName) { case nameof(AssertContainsAnalyzer.AssertContainsStringSyntaxVisitor): + case nameof(AssertContainsAnalyzer.AssertContainsSetSyntaxVisitor): return RenameMethodAndReorderActualExpectedAndReplaceWithSubjectShould(expression, "Contains", "Contain"); default: throw new System.InvalidOperationException($"Invalid visitor name - {properties.VisitorName}"); diff --git a/src/FluentAssertions.Analyzers/Utilities/ArgumentValidator.cs b/src/FluentAssertions.Analyzers/Utilities/ArgumentValidator.cs index 7e922e65..7ebbd247 100644 --- a/src/FluentAssertions.Analyzers/Utilities/ArgumentValidator.cs +++ b/src/FluentAssertions.Analyzers/Utilities/ArgumentValidator.cs @@ -1,3 +1,4 @@ +using FluentAssertions.Analyzers.Utilities; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -11,6 +12,11 @@ public static ArgumentPredicate IsIdentifier() => (argument, semanticModel) => argument.Expression.IsKind(SyntaxKind.IdentifierName); public static ArgumentPredicate IsType(Func typeSelector) => (argument, semanticModel) => semanticModel.GetTypeInfo(argument.Expression).Type?.Equals(typeSelector(semanticModel), SymbolEqualityComparer.Default) ?? false; + public static ArgumentPredicate IsTypeOrConstructedFromTypeOrImplementsType(SpecialType specialType) + => (argument, semanticModel) => semanticModel.GetTypeInfo(argument.Expression).Type?.IsTypeOrConstructedFromTypeOrImplementsType(specialType) ?? false; + public static ArgumentPredicate Exists() { + return (argument, semanticModel) => true; + } public static ArgumentPredicate IsAnyType(params Func[] typeSelectors) => (argument, semanticModel) => Array.Exists(typeSelectors, typeSelector => IsType(typeSelector)(argument, semanticModel)); public static ArgumentPredicate IsNull()