diff --git a/src/FluentAssertions.Analyzers.Tests/Tips/XunitTests.cs b/src/FluentAssertions.Analyzers.Tests/Tips/XunitTests.cs index f271201e..64599f69 100644 --- a/src/FluentAssertions.Analyzers.Tests/Tips/XunitTests.cs +++ b/src/FluentAssertions.Analyzers.Tests/Tips/XunitTests.cs @@ -396,6 +396,35 @@ public void AssertStringMatches_String_TestCodeFix(string oldAssertion, string n public void AssertStringMatches_Regex_TestCodeFix(string oldAssertion, string newAssertion) => VerifyCSharpFix("string actual, Regex expectedRegex", oldAssertion, newAssertion); + [DataTestMethod] + [DataRow("Assert.DoesNotMatch(expectedRegexPattern, actual);")] + [Implemented] + public void AssertStringDoesNotMatch_String_TestAnalyzer(string assertion) => + VerifyCSharpDiagnostic("string actual, string expectedRegexPattern", assertion); + + [DataTestMethod] + [DataRow( + /* oldAssertion: */ "Assert.DoesNotMatch(expectedRegexPattern, actual);", + /* newAssertion: */ "actual.Should().NotMatchRegex(expectedRegexPattern);")] + [Implemented] + public void AssertStringDoesNotMatch_String_TestCodeFix(string oldAssertion, string newAssertion) + => VerifyCSharpFix("string actual, string expectedRegexPattern", oldAssertion, newAssertion); + + [DataTestMethod] + [DataRow("Assert.DoesNotMatch(expectedRegex, actual);")] + [Implemented] + public void AssertStringDoesNotMatch_Regex_TestAnalyzer(string assertion) => + VerifyCSharpDiagnostic("string actual, Regex expectedRegex", assertion); + + [DataTestMethod] + [DataRow( + /* oldAssertion: */ "Assert.DoesNotMatch(expectedRegex, actual);", + /* newAssertion: */ "actual.Should().NotMatchRegex(expectedRegex);")] + [Implemented] + public void AssertStringDoesNotMatch_Regex_TestCodeFix(string oldAssertion, string newAssertion) + => VerifyCSharpFix("string actual, Regex expectedRegex", oldAssertion, newAssertion); + + private void VerifyCSharpDiagnostic(string methodArguments, string assertion) where TDiagnosticAnalyzer : Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer, new() { var source = GenerateCode.XunitAssertion(methodArguments, assertion); diff --git a/src/FluentAssertions.Analyzers/Constants.cs b/src/FluentAssertions.Analyzers/Constants.cs index cfbba751..b9fbda28 100644 --- a/src/FluentAssertions.Analyzers/Constants.cs +++ b/src/FluentAssertions.Analyzers/Constants.cs @@ -132,6 +132,7 @@ public static class Xunit public const string AssertContains = $"{DiagnosticProperties.IdPrefix}0710"; public const string AssertDoesNotContain = $"{DiagnosticProperties.IdPrefix}0711"; public const string AssertMatches = $"{DiagnosticProperties.IdPrefix}0712"; + public const string AssertDoesNotMatch = $"{DiagnosticProperties.IdPrefix}0713"; } } diff --git a/src/FluentAssertions.Analyzers/Tips/Xunit/AssertDoesNotMatch.cs b/src/FluentAssertions.Analyzers/Tips/Xunit/AssertDoesNotMatch.cs new file mode 100644 index 00000000..e048a7ab --- /dev/null +++ b/src/FluentAssertions.Analyzers/Tips/Xunit/AssertDoesNotMatch.cs @@ -0,0 +1,58 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using FluentAssertions.Analyzers.Utilities; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace FluentAssertions.Analyzers.Xunit; + +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public class AssertDoesNotMatchAnalyzer : XunitAnalyzer +{ + public const string DiagnosticId = Constants.Tips.Xunit.AssertDoesNotMatch; + public const string Category = Constants.Tips.Category; + + public const string Message = "Use .Should().NotMatchRegex()"; + + protected override DiagnosticDescriptor Rule => new(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true); + + protected override IEnumerable Visitors => new FluentAssertionsCSharpSyntaxVisitor[] + { + new AssertDoesNotMatchStringSyntaxVisitor() + }; + + //public static void DoesNotMatch(string expectedRegexPattern, string? actualString) + //public static void DoesNotMatch(Regex expectedRegex, string? actualString) + public class AssertDoesNotMatchStringSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor + { + public AssertDoesNotMatchStringSyntaxVisitor() : base( + MemberValidator.ArgumentsMatch("DoesNotMatch", + ArgumentValidator.IsAnyType(TypeSelector.GetStringType, TypeSelector.GetRegexType), + ArgumentValidator.IsType(TypeSelector.GetStringType)) + ) + { + } + } +} + +[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(AssertDoesNotMatchCodeFix)), Shared] +public class AssertDoesNotMatchCodeFix : XunitCodeFixProvider +{ + public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(AssertDoesNotMatchAnalyzer.DiagnosticId); + + protected override ExpressionSyntax GetNewExpression( + ExpressionSyntax expression, + FluentAssertionsDiagnosticProperties properties) + { + switch (properties.VisitorName) + { + case nameof(AssertDoesNotMatchAnalyzer.AssertDoesNotMatchStringSyntaxVisitor): + return RenameMethodAndReorderActualExpectedAndReplaceWithSubjectShould(expression, "DoesNotMatch", "NotMatchRegex"); + default: + throw new System.InvalidOperationException($"Invalid visitor name - {properties.VisitorName}"); + } + } +} \ No newline at end of file