diff --git a/src/FluentAssertions.Analyzers.Tests/Tips/StringTests.cs b/src/FluentAssertions.Analyzers.Tests/Tips/StringTests.cs index 7ec0ee53..285b922b 100644 --- a/src/FluentAssertions.Analyzers.Tests/Tips/StringTests.cs +++ b/src/FluentAssertions.Analyzers.Tests/Tips/StringTests.cs @@ -11,7 +11,7 @@ public class StringTests [AssertionDiagnostic("actual.StartsWith(expected).Should().BeTrue({0});")] [AssertionDiagnostic("actual.ToString().StartsWith(expected).Should().BeTrue({0}).And.ToString();")] [Implemented] - public void StringShouldStartWith_TestAnalyzer(string assertion) => VerifyCSharpDiagnostic(assertion); + public void StringShouldStartWith_TestAnalyzer(string assertion) => VerifyCSharpDiagnostic(assertion, DiagnosticMetadata.StringShouldStartWith_StartsWithShouldBeTrue); [DataTestMethod] [AssertionCodeFix( @@ -21,13 +21,13 @@ public class StringTests oldAssertion: "actual.ToString().StartsWith(expected).Should().BeTrue({0}).And.ToString();", newAssertion: "actual.ToString().Should().StartWith(expected{0}).And.ToString();")] [Implemented] - public void StringShouldStartWith_TestCodeFix(string oldAssertion, string newAssertion) => VerifyCSharpFix(oldAssertion, newAssertion); + public void StringShouldStartWith_TestCodeFix(string oldAssertion, string newAssertion) => VerifyCSharpFix(oldAssertion, newAssertion); [DataTestMethod] [AssertionDiagnostic("actual.EndsWith(expected).Should().BeTrue({0});")] [AssertionDiagnostic("actual.ToString().EndsWith(expected).Should().BeTrue({0}).And.ToString();")] [Implemented] - public void StringShouldEndWith_TestAnalyzer(string assertion) => VerifyCSharpDiagnostic(assertion); + public void StringShouldEndWith_TestAnalyzer(string assertion) => VerifyCSharpDiagnostic(assertion, DiagnosticMetadata.StringShouldEndWith_EndsWithShouldBeTrue); [DataTestMethod] [AssertionCodeFix( @@ -37,18 +37,29 @@ public class StringTests oldAssertion: "actual.ToString().EndsWith(expected).Should().BeTrue({0}).And.ToString();", newAssertion: "actual.ToString().Should().EndWith(expected{0}).And.ToString();")] [Implemented] - public void StringShouldEndWith_TestCodeFix(string oldAssertion, string newAssertion) => VerifyCSharpFix(oldAssertion, newAssertion); + public void StringShouldEndWith_TestCodeFix(string oldAssertion, string newAssertion) => VerifyCSharpFix(oldAssertion, newAssertion); + + [DataTestMethod] + [AssertionDiagnostic("string.IsNullOrEmpty(actual).Should().BeFalse({0});")] + [AssertionDiagnostic("string.IsNullOrEmpty(actual.ToString()).Should().BeFalse({0}).And.ToString();")] + [Implemented] + public void StringShouldNotBeNullOrEmpty_StringIsNullOrEmptyShouldBeFalse_TestAnalyzer(string assertion) => VerifyCSharpDiagnostic(assertion, DiagnosticMetadata.StringShouldNotBeNullOrEmpty_StringIsNullOrEmptyShouldBeFalse); + + [DataTestMethod] + [AssertionDiagnostic("actual.Should().NotBeEmpty().And.NotBeNull({0});")] + [AssertionDiagnostic("actual.Should().NotBeEmpty({0}).And.NotBeNull();")] + [AssertionDiagnostic("actual.ToString().Should().NotBeEmpty({0}).And.NotBeNull().And.ToString();")] + [AssertionDiagnostic("actual.ToString().Should().NotBeEmpty().And.NotBeNull({0}).And.ToString();")] + [Implemented] + public void StringShouldNotBeNullOrEmpty_StringShouldNotBeEmptyAndNotBeNull_TestAnalyzer(string assertion) => VerifyCSharpDiagnostic(assertion, DiagnosticMetadata.StringShouldNotBeNullOrEmpty_StringShouldNotBeEmptyAndNotBeNull); [DataTestMethod] [AssertionDiagnostic("actual.Should().NotBeNull().And.NotBeEmpty({0});")] [AssertionDiagnostic("actual.Should().NotBeNull({0}).And.NotBeEmpty();")] - [AssertionDiagnostic("string.IsNullOrEmpty(actual).Should().BeFalse({0});")] [AssertionDiagnostic("actual.ToString().Should().NotBeNull().And.NotBeEmpty({0}).And.ToString();")] [AssertionDiagnostic("actual.ToString().Should().NotBeNull({0}).And.NotBeEmpty().And.ToString();")] - [AssertionDiagnostic("actual.ToString().Should().NotBeEmpty({0}).And.NotBeNull({0}).And.ToString();")] - [AssertionDiagnostic("string.IsNullOrEmpty(actual.ToString()).Should().BeFalse({0}).And.ToString();")] [Implemented] - public void StringShouldNotBeNullOrEmpty_TestAnalyzer(string assertion) => VerifyCSharpDiagnostic(assertion); + public void StringShouldNotBeNullOrEmpty_StringShouldNotBeNullAndNotBeEmpty_TestAnalyzer(string assertion) => VerifyCSharpDiagnostic(assertion, DiagnosticMetadata.StringShouldNotBeNullOrEmpty_StringShouldNotBeNullAndNotBeEmpty); [DataTestMethod] [AssertionCodeFix( @@ -82,13 +93,13 @@ public class StringTests oldAssertion: "string.IsNullOrEmpty(actual.ToString()).Should().BeFalse({0}).And.ToString();", newAssertion: "actual.ToString().Should().NotBeNullOrEmpty({0}).And.ToString();")] [Implemented] - public void StringShouldNotBeNullOrEmpty_TestCodeFix(string oldAssertion, string newAssertion) => VerifyCSharpFix(oldAssertion, newAssertion); + public void StringShouldNotBeNullOrEmpty_TestCodeFix(string oldAssertion, string newAssertion) => VerifyCSharpFix(oldAssertion, newAssertion); [DataTestMethod] [AssertionDiagnostic("string.IsNullOrEmpty(actual).Should().BeTrue({0});")] [AssertionDiagnostic("string.IsNullOrEmpty(actual.ToString()).Should().BeTrue({0}).And.ToString();")] [Implemented] - public void StringShouldBeNullOrEmpty_TestAnalyzer(string assertion) => VerifyCSharpDiagnostic(assertion); + public void StringShouldBeNullOrEmpty_TestAnalyzer(string assertion) => VerifyCSharpDiagnostic(assertion, DiagnosticMetadata.StringShouldBeNullOrEmpty_StringIsNullOrEmptyShouldBeTrue); [DataTestMethod] [AssertionCodeFix( @@ -98,7 +109,7 @@ public class StringTests oldAssertion: "string.IsNullOrEmpty(actual.ToString()).Should().BeTrue({0}).And.ToString();", newAssertion: "actual.ToString().Should().BeNullOrEmpty({0}).And.ToString();")] [Implemented] - public void StringShouldBeNullOrEmpty_TestCodeFix(string oldAssertion, string newAssertion) => VerifyCSharpFix(oldAssertion, newAssertion); + public void StringShouldBeNullOrEmpty_TestCodeFix(string oldAssertion, string newAssertion) => VerifyCSharpFix(oldAssertion, newAssertion); [DataTestMethod] [AssertionDiagnostic("string.IsNullOrWhiteSpace(actual).Should().BeTrue({0});")] @@ -106,7 +117,7 @@ public class StringTests [AssertionDiagnostic("string.IsNullOrWhiteSpace(actual.ToString()).Should().BeTrue({0});")] [AssertionDiagnostic("string.IsNullOrWhiteSpace(actual.ToString()).Should().BeTrue({0}).And.ToString();")] [Implemented] - public void StringShouldBeNullOrWhiteSpace_TestAnalyzer(string assertion) => VerifyCSharpDiagnostic(assertion); + public void StringShouldBeNullOrWhiteSpace_TestAnalyzer(string assertion) => VerifyCSharpDiagnostic(assertion, DiagnosticMetadata.StringShouldBeNullOrWhiteSpace_StringIsNullOrWhiteSpaceShouldBeTrue); [DataTestMethod] [AssertionCodeFix( @@ -122,7 +133,7 @@ public class StringTests oldAssertion: "string.IsNullOrWhiteSpace(actual.ToString()).Should().BeTrue({0}).And.ToString();", newAssertion: "actual.ToString().Should().BeNullOrWhiteSpace({0}).And.ToString();")] [Implemented] - public void StringShouldBeNullOrWhiteSpace_TestCodeFix(string oldAssertion, string newAssertion) => VerifyCSharpFix(oldAssertion, newAssertion); + public void StringShouldBeNullOrWhiteSpace_TestCodeFix(string oldAssertion, string newAssertion) => VerifyCSharpFix(oldAssertion, newAssertion); [DataTestMethod] [AssertionDiagnostic("string.IsNullOrWhiteSpace(actual).Should().BeFalse({0});")] @@ -130,7 +141,7 @@ public class StringTests [AssertionDiagnostic("string.IsNullOrWhiteSpace(actual.ToString()).Should().BeFalse({0});")] [AssertionDiagnostic("string.IsNullOrWhiteSpace(actual.ToString()).Should().BeFalse({0}).And.ToString();")] [Implemented] - public void StringShouldNotBeNullOrWhiteSpace_TestAnalyzer(string assertion) => VerifyCSharpDiagnostic(assertion); + public void StringShouldNotBeNullOrWhiteSpace_TestAnalyzer(string assertion) => VerifyCSharpDiagnostic(assertion, DiagnosticMetadata.StringShouldNotBeNullOrWhiteSpace_StringShouldNotBeNullOrWhiteSpace); [DataTestMethod] [AssertionCodeFix( @@ -146,13 +157,13 @@ public class StringTests oldAssertion: "string.IsNullOrWhiteSpace(actual.ToString()).Should().BeFalse({0}).And.ToString();", newAssertion: "actual.ToString().Should().NotBeNullOrWhiteSpace({0}).And.ToString();")] [Implemented] - public void StringShouldNotBeNullOrWhiteSpace_TestCodeFix(string oldAssertion, string newAssertion) => VerifyCSharpFix(oldAssertion, newAssertion); + public void StringShouldNotBeNullOrWhiteSpace_TestCodeFix(string oldAssertion, string newAssertion) => VerifyCSharpFix(oldAssertion, newAssertion); [DataTestMethod] [AssertionDiagnostic("actual.Length.Should().Be(k{0});")] [AssertionDiagnostic("actual.ToString().Length.Should().Be(k{0}).And.ToString();")] [Implemented] - public void StringShouldHaveLength_TestAnalyzer(string assertion) => VerifyCSharpDiagnostic(assertion); + public void StringShouldHaveLength_TestAnalyzer(string assertion) => VerifyCSharpDiagnostic(assertion, DiagnosticMetadata.StringShouldHaveLength_LengthShouldBe); [DataTestMethod] [AssertionCodeFix( @@ -162,20 +173,17 @@ public class StringTests oldAssertion: "actual.ToString().Length.Should().Be(k{0}).And.ToString();", newAssertion: "actual.ToString().Should().HaveLength(k{0}).And.ToString();")] [Implemented] - public void StringShouldHaveLength_TestCodeFix(string oldAssertion, string newAssertion) => VerifyCSharpFix(oldAssertion, newAssertion); + public void StringShouldHaveLength_TestCodeFix(string oldAssertion, string newAssertion) => VerifyCSharpFix(oldAssertion, newAssertion); - private void VerifyCSharpDiagnostic(string sourceAssertion) where TDiagnosticAnalyzer : Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer, new() + private void VerifyCSharpDiagnostic(string sourceAssertion, DiagnosticMetadata metadata) { var source = GenerateCode.StringAssertion(sourceAssertion); - var type = typeof(TDiagnosticAnalyzer); - var diagnosticId = (string)type.GetField("DiagnosticId").GetValue(null); - var message = (string)type.GetField("Message").GetValue(null); - DiagnosticVerifier.VerifyCSharpDiagnosticUsingAllAnalyzers(source, new DiagnosticResult { - Id = diagnosticId, - Message = message, + Id = FluentAssertionsOperationAnalyzer.DiagnosticId, + Message = metadata.Message, + VisitorName = metadata.Name, Locations = new DiagnosticResultLocation[] { new DiagnosticResultLocation("Test0.cs", 9, 13) diff --git a/src/FluentAssertions.Analyzers/Tips/DiagnosticMetadata.cs b/src/FluentAssertions.Analyzers/Tips/DiagnosticMetadata.cs index b37f5079..d139afdf 100644 --- a/src/FluentAssertions.Analyzers/Tips/DiagnosticMetadata.cs +++ b/src/FluentAssertions.Analyzers/Tips/DiagnosticMetadata.cs @@ -60,5 +60,15 @@ private DiagnosticMetadata(string message, string helpLink, [CallerMemberName] s public static DiagnosticMetadata NumericShouldBeInRange_BeLessOrEqualToAndBeGreaterOrEqualTo { get; } = new("Use .Should().BeInRange()", string.Empty); public static DiagnosticMetadata NumericShouldBeApproximately_MathAbsShouldBeLessOrEqualTo { get; } = new("Use .Should().BeApproximately()", string.Empty); + public static DiagnosticMetadata StringShouldStartWith_StartsWithShouldBeTrue { get; } = new("Use .Should().StartWith()", GetHelpLink("Strings-1")); + public static DiagnosticMetadata StringShouldEndWith_EndsWithShouldBeTrue { get; } = new("Use .Should().EndWith()", GetHelpLink("Strings-2")); + public static DiagnosticMetadata StringShouldNotBeNullOrEmpty_StringShouldNotBeNullAndNotBeEmpty { get; } = new("Use .Should().NotBeNullOrEmpty()", GetHelpLink("Strings-3")); + public static DiagnosticMetadata StringShouldNotBeNullOrEmpty_StringShouldNotBeEmptyAndNotBeNull { get; } = new("Use .Should().NotBeNullOrEmpty()", GetHelpLink("Strings-3")); + public static DiagnosticMetadata StringShouldBeNullOrEmpty_StringIsNullOrEmptyShouldBeTrue { get; } = new("Use .Should().BeNullOrEmpty()", GetHelpLink("Strings-4")); + public static DiagnosticMetadata StringShouldNotBeNullOrEmpty_StringIsNullOrEmptyShouldBeFalse { get; } = new("Use .Should().NotBeNullOrEmpty()", GetHelpLink("Strings-5")); + public static DiagnosticMetadata StringShouldBeNullOrWhiteSpace_StringIsNullOrWhiteSpaceShouldBeTrue { get; } = new("Use .Should().BeNullOrWhiteSpace()", GetHelpLink("Strings-6")); + public static DiagnosticMetadata StringShouldNotBeNullOrWhiteSpace_StringShouldNotBeNullOrWhiteSpace { get; } = new("Use .Should().NotBeNullOrWhiteSpace()", GetHelpLink("Strings-7")); + public static DiagnosticMetadata StringShouldHaveLength_LengthShouldBe { get; } = new("Use .Should().HaveLength()", GetHelpLink("Strings-8")); + private static string GetHelpLink(string id) => $"https://fluentassertions.com/tips/#{id}"; } \ No newline at end of file diff --git a/src/FluentAssertions.Analyzers/Tips/FluentAssertionsCodeFix.CollectionShouldNotBeNullOrEmpty.cs b/src/FluentAssertions.Analyzers/Tips/FluentAssertionsCodeFix.CollectionShouldNotBeNullOrEmpty.cs deleted file mode 100644 index 422168e7..00000000 --- a/src/FluentAssertions.Analyzers/Tips/FluentAssertionsCodeFix.CollectionShouldNotBeNullOrEmpty.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Microsoft.CodeAnalysis.CSharp.Syntax; - -namespace FluentAssertions.Analyzers; - -public partial class FluentAssertionsCodeFix -{ - private ExpressionSyntax GetCombinedAssertions(ExpressionSyntax expression, string removeMethod, string renameMethod) - => GetCombinedAssertions(expression, removeMethod, renameMethod, "NotBeNullOrEmpty"); - - private ExpressionSyntax GetCombinedAssertions(ExpressionSyntax expression, string removeMethod, string renameMethod, string newMethod) - { - var remove = NodeReplacement.RemoveAndExtractArguments(removeMethod); - var newExpression = GetNewExpression(expression, NodeReplacement.RemoveMethodBefore(removeMethod), remove); - - return GetNewExpression(newExpression, NodeReplacement.RenameAndPrependArguments(renameMethod, newMethod, remove.Arguments)); - } -} \ No newline at end of file diff --git a/src/FluentAssertions.Analyzers/Tips/FluentAssertionsCodeFix.cs b/src/FluentAssertions.Analyzers/Tips/FluentAssertionsCodeFix.cs index e3fc6b41..4a91a0ef 100644 --- a/src/FluentAssertions.Analyzers/Tips/FluentAssertionsCodeFix.cs +++ b/src/FluentAssertions.Analyzers/Tips/FluentAssertionsCodeFix.cs @@ -1,5 +1,6 @@ using System.Collections.Immutable; using System.Composition; +using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; @@ -159,14 +160,15 @@ protected override ExpressionSyntax GetNewExpression(ExpressionSyntax expression return GetNewExpression(newExpression, NodeReplacement.PrependArguments("OnlyHaveUniqueItems", remove.Arguments)); } case nameof(DiagnosticMetadata.CollectionShouldNotBeNullOrEmpty_ShouldNotBeNullAndNotBeEmpty): + case nameof(DiagnosticMetadata.StringShouldNotBeNullOrEmpty_StringShouldNotBeNullAndNotBeEmpty): { - return GetCombinedAssertions(expression, "NotBeEmpty", "NotBeNull"); + return GetCombinedAssertions(expression, "NotBeEmpty", "NotBeNull", "NotBeNullOrEmpty"); } case nameof(DiagnosticMetadata.CollectionShouldNotBeNullOrEmpty_ShouldNotBeEmptyAndNotBeNull): + case nameof(DiagnosticMetadata.StringShouldNotBeNullOrEmpty_StringShouldNotBeEmptyAndNotBeNull): { - return GetCombinedAssertions(expression, "NotBeNull", "NotBeEmpty"); + return GetCombinedAssertions(expression, "NotBeNull", "NotBeEmpty", "NotBeNullOrEmpty"); } - case nameof(DiagnosticMetadata.CollectionShouldHaveElementAt_ElementAtIndexShouldBe): { var remove = NodeReplacement.RemoveAndExtractArguments("ElementAt"); @@ -237,7 +239,94 @@ protected override ExpressionSyntax GetNewExpression(ExpressionSyntax expression return newExpression; } + case nameof(DiagnosticMetadata.StringShouldBeNullOrEmpty_StringIsNullOrEmptyShouldBeTrue): + { + var remove = NodeReplacement.RemoveAndExtractArguments("IsNullOrEmpty"); + var newExpression = GetNewExpression(expression, remove); + + var rename = NodeReplacement.Rename("BeTrue", "BeNullOrEmpty"); + newExpression = GetNewExpression(newExpression, rename); + + var stringKeyword = newExpression.DescendantNodes().OfType().Single(); + var subject = remove.Arguments.First().Expression; + + return newExpression.ReplaceNode(stringKeyword, subject.WithTriviaFrom(stringKeyword)); + } + case nameof(DiagnosticMetadata.StringShouldBeNullOrWhiteSpace_StringIsNullOrWhiteSpaceShouldBeTrue): + { + var remove = NodeReplacement.RemoveAndExtractArguments("IsNullOrWhiteSpace"); + var newExpression = GetNewExpression(expression, remove); + + var rename = NodeReplacement.Rename("BeTrue", "BeNullOrWhiteSpace"); + newExpression = GetNewExpression(newExpression, rename); + + var stringKeyword = newExpression.DescendantNodes().OfType().Single(); + var subject = remove.Arguments.First().Expression; + + return newExpression.ReplaceNode(stringKeyword, subject.WithTriviaFrom(stringKeyword)); + } + + case nameof(DiagnosticMetadata.StringShouldEndWith_EndsWithShouldBeTrue): + { + var remove = NodeReplacement.RemoveAndExtractArguments("EndsWith"); + var newExpression = GetNewExpression(expression, remove); + + return GetNewExpression(newExpression, NodeReplacement.RenameAndPrependArguments("BeTrue", "EndWith", remove.Arguments)); + } + case nameof(DiagnosticMetadata.StringShouldStartWith_StartsWithShouldBeTrue): + { + var remove = NodeReplacement.RemoveAndExtractArguments("StartsWith"); + var newExpression = GetNewExpression(expression, remove); + + return GetNewExpression(newExpression, NodeReplacement.RenameAndPrependArguments("BeTrue", "StartWith", remove.Arguments)); + } + case nameof(DiagnosticMetadata.StringShouldNotBeNullOrWhiteSpace_StringShouldNotBeNullOrWhiteSpace): + { + var remove = NodeReplacement.RemoveAndExtractArguments("IsNullOrWhiteSpace"); + var newExpression = GetNewExpression(expression, remove); + + var rename = NodeReplacement.Rename("BeFalse", "NotBeNullOrWhiteSpace"); + newExpression = GetNewExpression(newExpression, rename); + + var stringKeyword = newExpression.DescendantNodes().OfType().Single(); + var subject = remove.Arguments.First().Expression; + + return newExpression.ReplaceNode(stringKeyword, subject.WithTriviaFrom(stringKeyword)); + } + + + case nameof(DiagnosticMetadata.StringShouldNotBeNullOrEmpty_StringIsNullOrEmptyShouldBeFalse): + { + var remove = NodeReplacement.RemoveAndExtractArguments("IsNullOrEmpty"); + var newExpression = GetNewExpression(expression, remove); + + var rename = NodeReplacement.Rename("BeFalse", "NotBeNullOrEmpty"); + newExpression = GetNewExpression(newExpression, rename); + + var stringKeyword = newExpression.DescendantNodes().OfType().Single(); + var subject = remove.Arguments.First().Expression; + + return newExpression.ReplaceNode(stringKeyword, subject.WithTriviaFrom(stringKeyword)); + } + throw new System.InvalidOperationException($"Invalid visitor name - {properties.VisitorName}"); + + case nameof(DiagnosticMetadata.StringShouldHaveLength_LengthShouldBe): + { + var remove = NodeReplacement.Remove("Length"); + var newExpression = GetNewExpression(expression, remove); + + return GetNewExpression(newExpression, NodeReplacement.Rename("Be", "HaveLength")); + } + default: throw new System.InvalidOperationException($"Invalid visitor name - {properties.VisitorName}"); }; } + + private ExpressionSyntax GetCombinedAssertions(ExpressionSyntax expression, string removeMethod, string renameMethod, string newMethod) + { + var remove = NodeReplacement.RemoveAndExtractArguments(removeMethod); + var newExpression = GetNewExpression(expression, NodeReplacement.RemoveMethodBefore(removeMethod), remove); + + return GetNewExpression(newExpression, NodeReplacement.RenameAndPrependArguments(renameMethod, newMethod, remove.Arguments)); + } } \ No newline at end of file diff --git a/src/FluentAssertions.Analyzers/Tips/FluentAssertionsOperationAnalyzer.Utils.cs b/src/FluentAssertions.Analyzers/Tips/FluentAssertionsOperationAnalyzer.Utils.cs index 27a6a749..50e78028 100644 --- a/src/FluentAssertions.Analyzers/Tips/FluentAssertionsOperationAnalyzer.Utils.cs +++ b/src/FluentAssertions.Analyzers/Tips/FluentAssertionsOperationAnalyzer.Utils.cs @@ -25,6 +25,7 @@ public FluentAssertionsMetadata(Compilation compilation) NumericAssertionsOfT2 = compilation.GetTypeByMetadataName("FluentAssertions.Numeric.NumericAssertions`2"); BooleanAssertionsOfT1 = compilation.GetTypeByMetadataName("FluentAssertions.Primitives.BooleanAssertions`1"); GenericCollectionAssertionsOfT3 = compilation.GetTypeByMetadataName("FluentAssertions.Collections.GenericCollectionAssertions`3"); + StringAssertionsOfT1 = compilation.GetTypeByMetadataName("FluentAssertions.Primitives.StringAssertions`1"); IDictionaryOfT2 = compilation.GetTypeByMetadataName(typeof(IDictionary<,>).FullName); IReadonlyDictionaryOfT2 = compilation.GetTypeByMetadataName(typeof(IReadOnlyDictionary<,>).FullName); @@ -35,6 +36,7 @@ public FluentAssertionsMetadata(Compilation compilation) public INamedTypeSymbol ReferenceTypeAssertionsOfT2 { get; } public INamedTypeSymbol ObjectAssertionsOfT2 { get; } public INamedTypeSymbol GenericCollectionAssertionsOfT3 { get; } + public INamedTypeSymbol StringAssertionsOfT1 { get; } public INamedTypeSymbol IDictionaryOfT2 { get; } public INamedTypeSymbol IReadonlyDictionaryOfT2 { get; } public INamedTypeSymbol BooleanAssertionsOfT1 { get; } diff --git a/src/FluentAssertions.Analyzers/Tips/FluentAssertionsOperationAnalyzer.cs b/src/FluentAssertions.Analyzers/Tips/FluentAssertionsOperationAnalyzer.cs index f50d7d0a..b6378ac3 100644 --- a/src/FluentAssertions.Analyzers/Tips/FluentAssertionsOperationAnalyzer.cs +++ b/src/FluentAssertions.Analyzers/Tips/FluentAssertionsOperationAnalyzer.cs @@ -62,6 +62,7 @@ private static void AnalyzeInvocation(OperationAnalysisContext context, FluentAs if (chainedInvocation.IsContainedInType(metadata.ReferenceTypeAssertionsOfT2)) { context.ReportDiagnostic(CreateDiagnostic(chainedInvocation, DiagnosticMetadata.CollectionShouldNotBeNullOrEmpty_ShouldNotBeEmptyAndNotBeNull)); + return; } } else if (invocation.TryGetFirstDescendent(out var invocationBeforeShould)) @@ -78,6 +79,20 @@ private static void AnalyzeInvocation(OperationAnalysisContext context, FluentAs } return; } + case "NotBeEmpty" when assertion.IsContainedInType(metadata.StringAssertionsOfT1): + { + if (assertion.TryGetChainedInvocationAfterAndConstraint("NotBeNull", out var chainedInvocation)) + { + if (!assertion.HasEmptyBecauseAndReasonArgs() && !chainedInvocation.HasEmptyBecauseAndReasonArgs()) return; + + if (chainedInvocation.IsContainedInType(metadata.ReferenceTypeAssertionsOfT2)) + { + context.ReportDiagnostic(CreateDiagnostic(chainedInvocation, DiagnosticMetadata.StringShouldNotBeNullOrEmpty_StringShouldNotBeEmptyAndNotBeNull)); + return; + } + } + } + return; case "BeEmpty" when assertion.IsContainedInType(metadata.GenericCollectionAssertionsOfT3): { if (invocation.TryGetFirstDescendent(out var invocationBeforeShould)) @@ -102,6 +117,12 @@ private static void AnalyzeInvocation(OperationAnalysisContext context, FluentAs if (chainedInvocation.IsContainedInType(metadata.GenericCollectionAssertionsOfT3)) { context.ReportDiagnostic(CreateDiagnostic(chainedInvocation, DiagnosticMetadata.CollectionShouldNotBeNullOrEmpty_ShouldNotBeNullAndNotBeEmpty)); + return; + } + if (chainedInvocation.IsContainedInType(metadata.StringAssertionsOfT1)) + { + context.ReportDiagnostic(CreateDiagnostic(chainedInvocation, DiagnosticMetadata.StringShouldNotBeNullOrEmpty_StringShouldNotBeNullAndNotBeEmpty)); + return; } } return; @@ -135,22 +156,42 @@ private static void AnalyzeInvocation(OperationAnalysisContext context, FluentAs return; case "BeTrue" when assertion.IsContainedInType(metadata.BooleanAssertionsOfT1): { - if (!invocation.TryGetFirstDescendent(out var invocationBeforeShould)) return; - switch (invocationBeforeShould.TargetMethod.Name) + if (invocation.TryGetFirstDescendent(out var invocationBeforeShould)) { - case nameof(Enumerable.Any) when IsEnumerableMethodWithoutArguments(invocationBeforeShould, metadata): - context.ReportDiagnostic(CreateDiagnostic(assertion, DiagnosticMetadata.CollectionShouldNotBeEmpty_AnyShouldBeTrue)); - return; - case nameof(Enumerable.Any) when IsEnumerableMethodWithPredicate(invocationBeforeShould, metadata): - context.ReportDiagnostic(CreateDiagnostic(assertion, DiagnosticMetadata.CollectionShouldContainProperty_AnyWithLambdaShouldBeTrue)); - return; - case nameof(Enumerable.All) when IsEnumerableMethodWithPredicate(invocationBeforeShould, metadata): - context.ReportDiagnostic(CreateDiagnostic(assertion, DiagnosticMetadata.CollectionShouldOnlyContainProperty_AllShouldBeTrue)); - return; - case nameof(Enumerable.Contains) when invocationBeforeShould.IsContainedInType(metadata.Enumerable) && invocationBeforeShould.Arguments.Length is 2: - case nameof(ICollection.Contains) when invocationBeforeShould.ImplementsOrIsInterface(SpecialType.System_Collections_Generic_ICollection_T) && invocationBeforeShould.Arguments.Length is 1: - context.ReportDiagnostic(CreateDiagnostic(assertion, DiagnosticMetadata.CollectionShouldContainItem_ContainsShouldBeTrue)); - return; + switch (invocationBeforeShould.TargetMethod.Name) + { + case nameof(Enumerable.Any) when IsEnumerableMethodWithoutArguments(invocationBeforeShould, metadata): + context.ReportDiagnostic(CreateDiagnostic(assertion, DiagnosticMetadata.CollectionShouldNotBeEmpty_AnyShouldBeTrue)); + return; + case nameof(Enumerable.Any) when IsEnumerableMethodWithPredicate(invocationBeforeShould, metadata): + context.ReportDiagnostic(CreateDiagnostic(assertion, DiagnosticMetadata.CollectionShouldContainProperty_AnyWithLambdaShouldBeTrue)); + return; + case nameof(Enumerable.All) when IsEnumerableMethodWithPredicate(invocationBeforeShould, metadata): + context.ReportDiagnostic(CreateDiagnostic(assertion, DiagnosticMetadata.CollectionShouldOnlyContainProperty_AllShouldBeTrue)); + return; + case nameof(Enumerable.Contains) when invocationBeforeShould.IsContainedInType(metadata.Enumerable) && invocationBeforeShould.Arguments.Length is 2: + case nameof(ICollection.Contains) when invocationBeforeShould.ImplementsOrIsInterface(SpecialType.System_Collections_Generic_ICollection_T) && invocationBeforeShould.Arguments.Length is 1: + context.ReportDiagnostic(CreateDiagnostic(assertion, DiagnosticMetadata.CollectionShouldContainItem_ContainsShouldBeTrue)); + return; + case nameof(string.EndsWith) when invocationBeforeShould.IsContainedInType(SpecialType.System_String): + context.ReportDiagnostic(CreateDiagnostic(assertion, DiagnosticMetadata.StringShouldEndWith_EndsWithShouldBeTrue)); + return; + case nameof(string.StartsWith) when invocationBeforeShould.IsContainedInType(SpecialType.System_String): + context.ReportDiagnostic(CreateDiagnostic(assertion, DiagnosticMetadata.StringShouldStartWith_StartsWithShouldBeTrue)); + return; + } + } + if (subject is IInvocationOperation shouldArgumentInvocation) + { + switch (shouldArgumentInvocation.TargetMethod.Name) + { + case nameof(string.IsNullOrEmpty) when shouldArgumentInvocation.IsContainedInType(SpecialType.System_String): + context.ReportDiagnostic(CreateDiagnostic(assertion, DiagnosticMetadata.StringShouldBeNullOrEmpty_StringIsNullOrEmptyShouldBeTrue)); + return; + case nameof(string.IsNullOrWhiteSpace) when shouldArgumentInvocation.IsContainedInType(SpecialType.System_String): + context.ReportDiagnostic(CreateDiagnostic(assertion, DiagnosticMetadata.StringShouldBeNullOrWhiteSpace_StringIsNullOrWhiteSpaceShouldBeTrue)); + return; + } } } return; @@ -172,6 +213,18 @@ private static void AnalyzeInvocation(OperationAnalysisContext context, FluentAs return; } } + if (subject is IInvocationOperation shouldArgumentInvocation) + { + switch (shouldArgumentInvocation.TargetMethod.Name) + { + case nameof(string.IsNullOrEmpty) when shouldArgumentInvocation.IsContainedInType(SpecialType.System_String): + context.ReportDiagnostic(CreateDiagnostic(assertion, DiagnosticMetadata.StringShouldNotBeNullOrEmpty_StringIsNullOrEmptyShouldBeFalse)); + return; + case nameof(string.IsNullOrWhiteSpace) when shouldArgumentInvocation.IsContainedInType(SpecialType.System_String): + context.ReportDiagnostic(CreateDiagnostic(assertion, DiagnosticMetadata.StringShouldNotBeNullOrWhiteSpace_StringShouldNotBeNullOrWhiteSpace)); + return; + } + } } return; case "HaveCount" when assertion.IsContainedInType(metadata.GenericCollectionAssertionsOfT3) && assertion.Arguments[0].IsLiteralValue(1): @@ -240,6 +293,9 @@ private static void AnalyzeInvocation(OperationAnalysisContext context, FluentAs case nameof(Array.Length) when propertyBeforeShould.IsContainedInType(SpecialType.System_Array): context.ReportDiagnostic(CreateDiagnostic(assertion, DiagnosticMetadata.CollectionShouldHaveCount_LengthShouldBe)); return; + case nameof(string.Length) when propertyBeforeShould.IsContainedInType(SpecialType.System_String): + context.ReportDiagnostic(CreateDiagnostic(assertion, DiagnosticMetadata.StringShouldHaveLength_LengthShouldBe)); + return; } } if (subject is IPropertyReferenceOperation propertyReference && propertyReference.Property.Name == WellKnownMemberNames.Indexer diff --git a/src/FluentAssertions.Analyzers/Tips/Strings/StringAnalyzer.cs b/src/FluentAssertions.Analyzers/Tips/Strings/StringAnalyzer.cs deleted file mode 100644 index 9aa92701..00000000 --- a/src/FluentAssertions.Analyzers/Tips/Strings/StringAnalyzer.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Microsoft.CodeAnalysis; - -namespace FluentAssertions.Analyzers; - -public abstract class StringAnalyzer : FluentAssertionsAnalyzer -{ - protected override bool ShouldAnalyzeVariableNamedType(INamedTypeSymbol type, SemanticModel semanticModel) => type.SpecialType == SpecialType.System_String; - - protected override bool ShouldAnalyzeVariableType(ITypeSymbol type, SemanticModel semanticModel) - { - return false; - } -} diff --git a/src/FluentAssertions.Analyzers/Tips/Strings/StringShouldBeNullOrEmpty.cs b/src/FluentAssertions.Analyzers/Tips/Strings/StringShouldBeNullOrEmpty.cs deleted file mode 100644 index a2991152..00000000 --- a/src/FluentAssertions.Analyzers/Tips/Strings/StringShouldBeNullOrEmpty.cs +++ /dev/null @@ -1,55 +0,0 @@ -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.Linq; - -namespace FluentAssertions.Analyzers; - -[DiagnosticAnalyzer(LanguageNames.CSharp)] -public class StringShouldBeNullOrEmptyAnalyzer : StringAnalyzer -{ - public const string DiagnosticId = Constants.Tips.Strings.StringShouldBeNullOrEmpty; - public const string Category = Constants.Tips.Category; - - public const string Message = "Use .Should() followed by .BeNullOrEmpty() instead."; - - protected override DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true); - protected override IEnumerable Visitors - { - get - { - yield return new StringIsNullOrEmptyShouldBeTrueSyntaxVisitor(); - } - } - - public class StringIsNullOrEmptyShouldBeTrueSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor - { - public StringIsNullOrEmptyShouldBeTrueSyntaxVisitor() : base(new MemberValidator("IsNullOrEmpty"), MemberValidator.Should, new MemberValidator("BeTrue")) - { - } - } -} - -[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(StringShouldBeNullOrEmptyCodeFix)), Shared] -public class StringShouldBeNullOrEmptyCodeFix : FluentAssertionsCodeFixProvider -{ - public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(StringShouldBeNullOrEmptyAnalyzer.DiagnosticId); - - protected override ExpressionSyntax GetNewExpression(ExpressionSyntax expression, FluentAssertionsDiagnosticProperties properties) - { - var remove = NodeReplacement.RemoveAndExtractArguments("IsNullOrEmpty"); - var newExpression = GetNewExpression(expression, remove); - - var rename = NodeReplacement.Rename("BeTrue", "BeNullOrEmpty"); - newExpression = GetNewExpression(newExpression, rename); - - var stringKeyword = newExpression.DescendantNodes().OfType().Single(); - var subject = remove.Arguments.First().Expression; - - return newExpression.ReplaceNode(stringKeyword, subject.WithTriviaFrom(stringKeyword)); - } -} \ No newline at end of file diff --git a/src/FluentAssertions.Analyzers/Tips/Strings/StringShouldBeNullOrWhiteSpace.cs b/src/FluentAssertions.Analyzers/Tips/Strings/StringShouldBeNullOrWhiteSpace.cs deleted file mode 100644 index 530182eb..00000000 --- a/src/FluentAssertions.Analyzers/Tips/Strings/StringShouldBeNullOrWhiteSpace.cs +++ /dev/null @@ -1,55 +0,0 @@ -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.Linq; - -namespace FluentAssertions.Analyzers; - -[DiagnosticAnalyzer(LanguageNames.CSharp)] -public class StringShouldBeNullOrWhiteSpaceAnalyzer : StringAnalyzer -{ - public const string DiagnosticId = Constants.Tips.Strings.StringShouldBeNullOrWhiteSpace; - public const string Category = Constants.Tips.Category; - - public const string Message = "Use .Should() followed by .IsNullOrWhiteSpace() instead."; - - protected override DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true); - protected override IEnumerable Visitors - { - get - { - yield return new StringShouldBeNullOrWhiteSpaceSyntaxVisitor(); - } - } - - public class StringShouldBeNullOrWhiteSpaceSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor - { - public StringShouldBeNullOrWhiteSpaceSyntaxVisitor() : base(new MemberValidator("IsNullOrWhiteSpace"), MemberValidator.Should, new MemberValidator("BeTrue")) - { - } - } -} - -[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(StringShouldBeNullOrWhiteSpaceCodeFix)), Shared] -public class StringShouldBeNullOrWhiteSpaceCodeFix : FluentAssertionsCodeFixProvider -{ - public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(StringShouldBeNullOrWhiteSpaceAnalyzer.DiagnosticId); - - protected override ExpressionSyntax GetNewExpression(ExpressionSyntax expression, FluentAssertionsDiagnosticProperties properties) - { - var remove = NodeReplacement.RemoveAndExtractArguments("IsNullOrWhiteSpace"); - var newExpression = GetNewExpression(expression, remove); - - var rename = NodeReplacement.Rename("BeTrue", "BeNullOrWhiteSpace"); - newExpression = GetNewExpression(newExpression, rename); - - var stringKeyword = newExpression.DescendantNodes().OfType().Single(); - var subject = remove.Arguments.First().Expression; - - return newExpression.ReplaceNode(stringKeyword, subject.WithTriviaFrom(stringKeyword)); - } -} \ No newline at end of file diff --git a/src/FluentAssertions.Analyzers/Tips/Strings/StringShouldEndWith.cs b/src/FluentAssertions.Analyzers/Tips/Strings/StringShouldEndWith.cs deleted file mode 100644 index 0f1103bb..00000000 --- a/src/FluentAssertions.Analyzers/Tips/Strings/StringShouldEndWith.cs +++ /dev/null @@ -1,48 +0,0 @@ -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; - -namespace FluentAssertions.Analyzers; - -[DiagnosticAnalyzer(LanguageNames.CSharp)] -public class StringShouldEndWithAnalyzer : StringAnalyzer -{ - public const string DiagnosticId = Constants.Tips.Strings.StringShouldEndWith; - public const string Category = Constants.Tips.Category; - - public const string Message = "Use .Should().EndWith() instead."; - - protected override DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true); - protected override IEnumerable Visitors - { - get - { - yield return new EndWithShouldBeTrueSyntaxVisitor(); - } - } - - public class EndWithShouldBeTrueSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor - { - public EndWithShouldBeTrueSyntaxVisitor() : base(new MemberValidator("EndsWith"), MemberValidator.Should, new MemberValidator("BeTrue")) - { - } - } -} - -[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(StringShouldEndWithCodeFix)), Shared] -public class StringShouldEndWithCodeFix : FluentAssertionsCodeFixProvider -{ - public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(StringShouldEndWithAnalyzer.DiagnosticId); - - protected override ExpressionSyntax GetNewExpression(ExpressionSyntax expression, FluentAssertionsDiagnosticProperties properties) - { - var remove = NodeReplacement.RemoveAndExtractArguments("EndsWith"); - var newExpression = GetNewExpression(expression, remove); - - return GetNewExpression(newExpression, NodeReplacement.RenameAndPrependArguments("BeTrue", "EndWith", remove.Arguments)); - } -} \ No newline at end of file diff --git a/src/FluentAssertions.Analyzers/Tips/Strings/StringShouldHaveLength.cs b/src/FluentAssertions.Analyzers/Tips/Strings/StringShouldHaveLength.cs deleted file mode 100644 index 9c42def4..00000000 --- a/src/FluentAssertions.Analyzers/Tips/Strings/StringShouldHaveLength.cs +++ /dev/null @@ -1,49 +0,0 @@ -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; - -namespace FluentAssertions.Analyzers; - -[DiagnosticAnalyzer(LanguageNames.CSharp)] -public class StringShouldHaveLengthAnalyzer : StringAnalyzer -{ - public const string DiagnosticId = Constants.Tips.Strings.StringShouldHaveLength; - public const string Category = Constants.Tips.Category; - - public const string Message = "Use .Should().HaveLength() instead."; - - protected override DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true); - protected override IEnumerable Visitors - { - get - { - yield return new LengthShouldBeSyntaxVisitor(); - } - } - - public class LengthShouldBeSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor - { - public LengthShouldBeSyntaxVisitor() : base(new MemberValidator("Length"), MemberValidator.Should, new MemberValidator("Be")) - { - } - } -} - -[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(StringShouldHaveLengthCodeFix)), Shared] -public class StringShouldHaveLengthCodeFix : FluentAssertionsCodeFixProvider -{ - public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(StringShouldHaveLengthAnalyzer.DiagnosticId); - - protected override ExpressionSyntax GetNewExpression(ExpressionSyntax expression, FluentAssertionsDiagnosticProperties properties) - { - var remove = NodeReplacement.Remove("Length"); - var newExpression = GetNewExpression(expression, remove); - - return GetNewExpression(newExpression, NodeReplacement.Rename("Be", "HaveLength")); - - } -} \ No newline at end of file diff --git a/src/FluentAssertions.Analyzers/Tips/Strings/StringShouldNotBeNullOrEmpty.cs b/src/FluentAssertions.Analyzers/Tips/Strings/StringShouldNotBeNullOrEmpty.cs deleted file mode 100644 index 7daedee5..00000000 --- a/src/FluentAssertions.Analyzers/Tips/Strings/StringShouldNotBeNullOrEmpty.cs +++ /dev/null @@ -1,92 +0,0 @@ -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.Linq; - -namespace FluentAssertions.Analyzers; - -[DiagnosticAnalyzer(LanguageNames.CSharp)] -public class StringShouldNotBeNullOrEmptyAnalyzer : StringAnalyzer -{ - public const string DiagnosticId = Constants.Tips.Strings.StringShouldNotBeNullOrEmpty; - public const string Category = Constants.Tips.Category; - - public const string Message = "Use .Should() followed by .NotBeNullOrEmpty() instead."; - - protected override DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true); - protected override IEnumerable Visitors - { - get - { - yield return new StringShouldNotBeNullAndNotBeEmptySyntaxVisitor(); - yield return new StringShouldNotBeEmptyAndNotBeNullSyntaxVisitor(); - yield return new StringIsNullOrEmptyShouldBeFalseSyntaxVisitor(); - } - } - - - public class StringShouldNotBeNullAndNotBeEmptySyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor - { - public StringShouldNotBeNullAndNotBeEmptySyntaxVisitor() : base(MemberValidator.Should, new MemberValidator("NotBeNull"), MemberValidator.And, new MemberValidator("NotBeEmpty")) - { - } - } - - public class StringShouldNotBeEmptyAndNotBeNullSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor - { - public StringShouldNotBeEmptyAndNotBeNullSyntaxVisitor() : base(MemberValidator.Should, new MemberValidator("NotBeEmpty"), MemberValidator.And, new MemberValidator("NotBeNull")) - { - } - } - - public class StringIsNullOrEmptyShouldBeFalseSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor - { - public StringIsNullOrEmptyShouldBeFalseSyntaxVisitor() : base(new MemberValidator("IsNullOrEmpty"), MemberValidator.Should, new MemberValidator("BeFalse")) - { - } - } -} - -[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(StringShouldNotBeNullOrEmptyCodeFix)), Shared] -public class StringShouldNotBeNullOrEmptyCodeFix : FluentAssertionsCodeFixProvider -{ - public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(StringShouldNotBeNullOrEmptyAnalyzer.DiagnosticId); - - protected override ExpressionSyntax GetNewExpression(ExpressionSyntax expression, FluentAssertionsDiagnosticProperties properties) - { - if (properties.VisitorName == nameof(StringShouldNotBeNullOrEmptyAnalyzer.StringShouldNotBeNullAndNotBeEmptySyntaxVisitor)) - { - return GetCombinedAssertions(expression, "NotBeEmpty", "NotBeNull"); - } - else if (properties.VisitorName == nameof(StringShouldNotBeNullOrEmptyAnalyzer.StringShouldNotBeEmptyAndNotBeNullSyntaxVisitor)) - { - return GetCombinedAssertions(expression, "NotBeNull", "NotBeEmpty"); - } - else if (properties.VisitorName == nameof(StringShouldNotBeNullOrEmptyAnalyzer.StringIsNullOrEmptyShouldBeFalseSyntaxVisitor)) - { - var remove = NodeReplacement.RemoveAndExtractArguments("IsNullOrEmpty"); - var newExpression = GetNewExpression(expression, remove); - - var rename = NodeReplacement.Rename("BeFalse", "NotBeNullOrEmpty"); - newExpression = GetNewExpression(newExpression, rename); - - var stringKeyword = newExpression.DescendantNodes().OfType().Single(); - var subject = remove.Arguments.First().Expression; - - return newExpression.ReplaceNode(stringKeyword, subject.WithTriviaFrom(stringKeyword)); - } - throw new System.InvalidOperationException($"Invalid visitor name - {properties.VisitorName}"); - } - - private ExpressionSyntax GetCombinedAssertions(ExpressionSyntax expression, string removeMethod, string renameMethod) - { - var remove = NodeReplacement.RemoveAndExtractArguments(removeMethod); - var newExpression = GetNewExpression(expression, NodeReplacement.RemoveMethodBefore(removeMethod), remove); - - return GetNewExpression(newExpression, NodeReplacement.RenameAndPrependArguments(renameMethod, "NotBeNullOrEmpty", remove.Arguments)); - } -} \ No newline at end of file diff --git a/src/FluentAssertions.Analyzers/Tips/Strings/StringShouldNotBeNullOrWhiteSpace.cs b/src/FluentAssertions.Analyzers/Tips/Strings/StringShouldNotBeNullOrWhiteSpace.cs deleted file mode 100644 index bea66d0c..00000000 --- a/src/FluentAssertions.Analyzers/Tips/Strings/StringShouldNotBeNullOrWhiteSpace.cs +++ /dev/null @@ -1,55 +0,0 @@ -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.Linq; - -namespace FluentAssertions.Analyzers; - -[DiagnosticAnalyzer(LanguageNames.CSharp)] -public class StringShouldNotBeNullOrWhiteSpaceAnalyzer : StringAnalyzer -{ - public const string DiagnosticId = Constants.Tips.Strings.StringShouldNotBeNullOrWhiteSpace; - public const string Category = Constants.Tips.Category; - - public const string Message = "Use .Should() followed by .NotBeNullOrWhiteSpace() instead."; - - protected override DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true); - protected override IEnumerable Visitors - { - get - { - yield return new StringShouldNotBeNullOrWhiteSpaceSyntaxVisitor(); - } - } - - public class StringShouldNotBeNullOrWhiteSpaceSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor - { - public StringShouldNotBeNullOrWhiteSpaceSyntaxVisitor() : base(new MemberValidator("IsNullOrWhiteSpace"), MemberValidator.Should, new MemberValidator("BeFalse")) - { - } - } -} - -[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(StringShouldNotBeNullOrWhiteSpaceCodeFix)), Shared] -public class StringShouldNotBeNullOrWhiteSpaceCodeFix : FluentAssertionsCodeFixProvider -{ - public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(StringShouldNotBeNullOrWhiteSpaceAnalyzer.DiagnosticId); - - protected override ExpressionSyntax GetNewExpression(ExpressionSyntax expression, FluentAssertionsDiagnosticProperties properties) - { - var remove = NodeReplacement.RemoveAndExtractArguments("IsNullOrWhiteSpace"); - var newExpression = GetNewExpression(expression, remove); - - var rename = NodeReplacement.Rename("BeFalse", "NotBeNullOrWhiteSpace"); - newExpression = GetNewExpression(newExpression, rename); - - var stringKeyword = newExpression.DescendantNodes().OfType().Single(); - var subject = remove.Arguments.First().Expression; - - return newExpression.ReplaceNode(stringKeyword, subject.WithTriviaFrom(stringKeyword)); - } -} \ No newline at end of file diff --git a/src/FluentAssertions.Analyzers/Tips/Strings/StringShouldStartWith.cs b/src/FluentAssertions.Analyzers/Tips/Strings/StringShouldStartWith.cs deleted file mode 100644 index e919f85a..00000000 --- a/src/FluentAssertions.Analyzers/Tips/Strings/StringShouldStartWith.cs +++ /dev/null @@ -1,48 +0,0 @@ -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; - -namespace FluentAssertions.Analyzers; - -[DiagnosticAnalyzer(LanguageNames.CSharp)] -public class StringShouldStartWithAnalyzer : StringAnalyzer -{ - public const string DiagnosticId = Constants.Tips.Strings.StringShouldStartWith; - public const string Category = Constants.Tips.Category; - - public const string Message = "Use .Should().StartWith() instead."; - - protected override DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true); - protected override IEnumerable Visitors - { - get - { - yield return new StartWithShouldBeTrueSyntaxVisitor(); - } - } - - public class StartWithShouldBeTrueSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor - { - public StartWithShouldBeTrueSyntaxVisitor() : base(new MemberValidator("StartsWith"), MemberValidator.Should, new MemberValidator("BeTrue")) - { - } - } -} - -[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(StringShouldStartWithCodeFix)), Shared] -public class StringShouldStartWithCodeFix : FluentAssertionsCodeFixProvider -{ - public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(StringShouldStartWithAnalyzer.DiagnosticId); - - protected override ExpressionSyntax GetNewExpression(ExpressionSyntax expression, FluentAssertionsDiagnosticProperties properties) - { - var remove = NodeReplacement.RemoveAndExtractArguments("StartsWith"); - var newExpression = GetNewExpression(expression, remove); - - return GetNewExpression(newExpression, NodeReplacement.RenameAndPrependArguments("BeTrue", "StartWith", remove.Arguments)); - } -} \ No newline at end of file diff --git a/src/FluentAssertions.Analyzers/Utilities/HelpLinks.cs b/src/FluentAssertions.Analyzers/Utilities/HelpLinks.cs index bf734a3b..c6942f2d 100644 --- a/src/FluentAssertions.Analyzers/Utilities/HelpLinks.cs +++ b/src/FluentAssertions.Analyzers/Utilities/HelpLinks.cs @@ -37,16 +37,6 @@ static HelpLinks() [typeof(ExceptionShouldThrowWithMessageAnalyzer.ShouldThrowWhichMessageShouldEndWith)] = GetHelpLink("Exceptions-2"), [typeof(ExceptionShouldThrowWithMessageAnalyzer.ShouldThrowExactlyAndMessageShouldEndWith)] = GetHelpLink("Exceptions-1"), [typeof(ExceptionShouldThrowWithMessageAnalyzer.ShouldThrowAndMessageShouldStartWith)] = GetHelpLink("Exceptions-2"), - - [typeof(StringShouldStartWithAnalyzer.StartWithShouldBeTrueSyntaxVisitor)] = GetHelpLink("Strings-1"), - [typeof(StringShouldEndWithAnalyzer.EndWithShouldBeTrueSyntaxVisitor)] = GetHelpLink("Strings-2"), - [typeof(StringShouldNotBeNullOrEmptyAnalyzer.StringShouldNotBeNullAndNotBeEmptySyntaxVisitor)] = GetHelpLink("Strings-3"), - [typeof(StringShouldNotBeNullOrEmptyAnalyzer.StringShouldNotBeEmptyAndNotBeNullSyntaxVisitor)] = GetHelpLink("Strings-3"), - [typeof(StringShouldBeNullOrEmptyAnalyzer.StringIsNullOrEmptyShouldBeTrueSyntaxVisitor)] = GetHelpLink("Strings-4"), - [typeof(StringShouldNotBeNullOrEmptyAnalyzer.StringIsNullOrEmptyShouldBeFalseSyntaxVisitor)] = GetHelpLink("Strings-5"), - [typeof(StringShouldBeNullOrWhiteSpaceAnalyzer.StringShouldBeNullOrWhiteSpaceSyntaxVisitor)] = GetHelpLink("Strings-6"), - [typeof(StringShouldNotBeNullOrWhiteSpaceAnalyzer.StringShouldNotBeNullOrWhiteSpaceSyntaxVisitor)] = GetHelpLink("Strings-7"), - [typeof(StringShouldHaveLengthAnalyzer.LengthShouldBeSyntaxVisitor)] = GetHelpLink("Strings-8") }; } diff --git a/src/FluentAssertions.Analyzers/Utilities/OperartionExtensions.cs b/src/FluentAssertions.Analyzers/Utilities/OperartionExtensions.cs index 7eb5b36d..3f0d08d4 100644 --- a/src/FluentAssertions.Analyzers/Utilities/OperartionExtensions.cs +++ b/src/FluentAssertions.Analyzers/Utilities/OperartionExtensions.cs @@ -92,7 +92,7 @@ public static bool TryGetChainedInvocationAfterAndConstraint(this IInvocationOpe if (invocation.Parent is IPropertyReferenceOperation { Property.Name: "And" } andConstraint) { chainedInvocation = andConstraint.Parent as IInvocationOperation; - return chainedInvocation.TargetMethod.Name == chainedMethod; + return chainedInvocation?.TargetMethod?.Name == chainedMethod; } chainedInvocation = null;