diff --git a/src/FluentAssertions.Analyzers.Tests/TestAttributes.cs b/src/FluentAssertions.Analyzers.Tests/TestAttributes.cs index 0e983686..4b2e7e0b 100644 --- a/src/FluentAssertions.Analyzers.Tests/TestAttributes.cs +++ b/src/FluentAssertions.Analyzers.Tests/TestAttributes.cs @@ -16,6 +16,8 @@ public class NotImplementedAttribute : TestCategoryBaseAttribute [AttributeUsage(AttributeTargets.Method)] public class ImplementedAttribute : TestCategoryBaseAttribute { + public string Reason { get; set; } + public override IList TestCategories => new[] { "Completed" }; } diff --git a/src/FluentAssertions.Analyzers.Tests/Tips/CollectionTests.cs b/src/FluentAssertions.Analyzers.Tests/Tips/CollectionTests.cs index 6e131cea..1b360d69 100644 --- a/src/FluentAssertions.Analyzers.Tests/Tips/CollectionTests.cs +++ b/src/FluentAssertions.Analyzers.Tests/Tips/CollectionTests.cs @@ -8,6 +8,14 @@ public class CollectionTests { public TestContext TestContext { get; set; } + [AssertionDataTestMethod] + [AssertionDiagnostic("nestedList.Should().NotBeNull({0}).And.ContainSingle().Which.Should().NotBeEmpty();")] + [AssertionDiagnostic("nestedList.Should().NotBeNull().And.ContainSingle().Which.Should().NotBeEmpty({0});")] + [AssertionDiagnostic("nestedList.Should().NotBeNull().And.ContainSingle({0}).Which.Should().NotBeEmpty();")] + [NotImplemented] + public void NoDiagnostics(string assertion) => VerifyCSharpNoDiagnosticsCodeBlock(assertion); + + [AssertionDataTestMethod] [AssertionDiagnostic("actual.Any().Should().BeTrue({0});")] [AssertionDiagnostic("actual.AsEnumerable().Any().Should().BeTrue({0}).And.ToString();")] @@ -375,6 +383,8 @@ public class CollectionTests [AssertionDiagnostic("actual.Should().NotBeEmpty().And.NotBeNull({0});")] [AssertionDiagnostic("actual.AsEnumerable().Should().NotBeNull().And.NotBeEmpty({0}).And.ToString();")] [AssertionDiagnostic("actual.AsEnumerable().Should().NotBeEmpty().And.NotBeNull({0}).And.ToString();")] + [AssertionDiagnostic("actual.AsEnumerable().Should().NotBeNull().And.HaveCount(2).And.NotBeEmpty({0}).And.ToString();")] + [AssertionDiagnostic("actual.AsEnumerable().Should().NotBeEmpty().And.HaveCount(2).And.NotBeNull({0}).And.ToString();")] [Implemented] public void CollectionShouldNotBeNullOrEmpty_TestAnalyzer(string assertion) => VerifyCSharpDiagnosticCodeBlock(assertion); @@ -390,7 +400,7 @@ public class CollectionTests newAssertion: "actual.Should().NotBeNullOrEmpty({0});")] [AssertionCodeFix( oldAssertion: "actual.Should().NotBeEmpty({0}).And.NotBeNull();", - newAssertion: "actual.Should().NotBeNullOrEmpty({0});")] + newAssertion: "actual.Should().NotBeNullOrEmpty({0});")] [AssertionCodeFix( oldAssertion: "actual.AsEnumerable().Should().NotBeNull().And.HaveCount(2).And.NotBeEmpty({0}).And.ToString();", newAssertion: "actual.AsEnumerable().Should().NotBeNullOrEmpty({0}).And.HaveCount(2).And.ToString();")] @@ -400,8 +410,6 @@ public class CollectionTests [Implemented] public void CollectionShouldNotBeNullOrEmpty_TestCodeFix(string oldAssertion, string newAssertion) => VerifyCSharpFixCodeBlock(oldAssertion, newAssertion); - public void CollectionShouldNotBeNullOrEmptyMultipleReasons_TestCodeFix(string oldAssertion, string newAssertion) => VerifyCSharpFixCodeBlock(oldAssertion, newAssertion); - [AssertionDataTestMethod] [AssertionDiagnostic("actual.ElementAt(k).Should().Be(expectedItem{0});")] [AssertionDiagnostic("actual.ElementAt(6).Should().Be(expectedItem{0});")] @@ -604,7 +612,7 @@ public class CollectionTests newAssertion: "actual.AsEnumerable().Should().HaveElementAt(0, null{0}).And.ToString();")] [Ignore("What Should Happen?")] public void CollectionShouldHaveElementAt0Null_TestCodeFix(string oldAssertion, string newAssertion) => VerifyCSharpFixCodeBlock(oldAssertion, newAssertion); - + private void VerifyCSharpDiagnosticCodeBlock(string sourceAssertion) where TDiagnosticAnalyzer : Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer, new() { var source = GenerateCode.EnumerableCodeBlockAssertion(sourceAssertion); @@ -616,7 +624,7 @@ public class CollectionTests DiagnosticVerifier.VerifyCSharpDiagnosticUsingAllAnalyzers(source, new DiagnosticResult { Id = diagnosticId, - Message = string.Format(message, GenerateCode.ActualVariableName), + Message = message, Locations = new DiagnosticResultLocation[] { new DiagnosticResultLocation("Test0.cs", 11,13) @@ -636,7 +644,7 @@ public class CollectionTests DiagnosticVerifier.VerifyCSharpDiagnosticUsingAllAnalyzers(source, new DiagnosticResult { Id = diagnosticId, - Message = string.Format(message, GenerateCode.ActualVariableName), + Message = message, Locations = new DiagnosticResultLocation[] { new DiagnosticResultLocation("Test0.cs", 10,16) @@ -664,5 +672,11 @@ private void VerifyCSharpFixExpressionBody(oldSource, newSource); } + + private void VerifyCSharpNoDiagnosticsCodeBlock(string assertion) + { + var source = GenerateCode.EnumerableCodeBlockAssertion(assertion); + DiagnosticVerifier.VerifyCSharpDiagnosticUsingAllAnalyzers(source); + } } } diff --git a/src/FluentAssertions.Analyzers.Tests/Tips/SanityTests.cs b/src/FluentAssertions.Analyzers.Tests/Tips/SanityTests.cs index 8f1931d7..ee144f1b 100644 --- a/src/FluentAssertions.Analyzers.Tests/Tips/SanityTests.cs +++ b/src/FluentAssertions.Analyzers.Tests/Tips/SanityTests.cs @@ -6,7 +6,17 @@ namespace FluentAssertions.Analyzers.Tests.Tips public class SanityTests { [TestMethod] - [NotImplemented(Reason = "https://github.com/fluentassertions/fluentassertions.analyzers/issues/10")] + [Implemented(Reason = "https://github.com/fluentassertions/fluentassertions.analyzers/issues/11")] + public void CountWithPredicate() + { + const string assertion = "actual.Count(d => d.Message.Contains(\"a\")).Should().Be(2);"; + var source = GenerateCode.EnumerableCodeBlockAssertion(assertion); + + DiagnosticVerifier.VerifyCSharpDiagnosticUsingAllAnalyzers(source); + } + + [TestMethod] + [Implemented(Reason = "https://github.com/fluentassertions/fluentassertions.analyzers/issues/10")] public void AssertionCallMultipleMethodWithTheSameNameAndArguments() { const string assertion = "actual.Should().Contain(d => d.Message.Contains(\"a\")).And.Contain(d => d.Message.Contains(\"c\"));"; @@ -16,7 +26,7 @@ public void AssertionCallMultipleMethodWithTheSameNameAndArguments() } [TestMethod] - [NotImplemented(Reason = "https://github.com/fluentassertions/fluentassertions.analyzers/issues/13")] + [Implemented(Reason = "https://github.com/fluentassertions/fluentassertions.analyzers/issues/13")] public void PropertyOfIndexerShouldBe_ShouldNotThrowException() { const string assertion = "actual[0].Message.Should().Be(\"test\");"; @@ -26,7 +36,7 @@ public void PropertyOfIndexerShouldBe_ShouldNotThrowException() } [TestMethod] - [NotImplemented(Reason = "https://github.com/fluentassertions/fluentassertions.analyzers/issues/13")] + [Implemented(Reason = "https://github.com/fluentassertions/fluentassertions.analyzers/issues/13")] public void PropertyOfElementAtShouldBe_ShouldNotTriggerDiagnostic() { const string assertion = "actual.ElementAt(0).Message.Should().Be(\"test\");"; @@ -34,5 +44,16 @@ public void PropertyOfElementAtShouldBe_ShouldNotTriggerDiagnostic() DiagnosticVerifier.VerifyCSharpDiagnosticUsingAllAnalyzers(source); } + + [TestMethod] + [Implemented(Reason = "https://github.com/fluentassertions/fluentassertions.analyzers/issues/10")] + public void NestedAssertions_ShouldNotTrigger() + { + const string declaration = "var nestedList = new List>();"; + const string assertion = "nestedList.Should().NotBeNull().And.ContainSingle().Which.Should().NotBeEmpty();"; + var source = GenerateCode.EnumerableCodeBlockAssertion(declaration + assertion); + + DiagnosticVerifier.VerifyCSharpDiagnosticUsingAllAnalyzers(source); + } } } diff --git a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldBeEmpty.cs b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldBeEmpty.cs index 19dbc848..2aa578af 100644 --- a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldBeEmpty.cs +++ b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldBeEmpty.cs @@ -15,7 +15,7 @@ public class CollectionShouldBeEmptyAnalyzer : FluentAssertionsAnalyzer public const string DiagnosticId = Constants.Tips.Collections.CollectionsShouldBeEmpty; public const string Category = Constants.Tips.Category; - public const string Message = "Use {0} .Should() followed by .BeEmpty() instead."; + public const string Message = "Use .Should().BeEmpty() instead."; protected override DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true); protected override IEnumerable Visitors @@ -27,31 +27,24 @@ protected override IEnumerable Visitors } } - public class AnyShouldBeFalseSyntaxVisitor : FluentAssertionsWithoutLambdaArgumentCSharpSyntaxVisitor + public class AnyShouldBeFalseSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor { - protected override string MathodNotContainingLambda => "Any"; - - public AnyShouldBeFalseSyntaxVisitor() : base("Any", "Should", "BeFalse") + public AnyShouldBeFalseSyntaxVisitor() : base(MemberValidator.MathodNotContainingLambda("Any"), MemberValidator.Should, new MemberValidator("BeFalse")) { } } + public class ShouldHaveCount0SyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor { - private bool _haveCountMethodHas0Argument; - - public override bool IsValid => base.IsValid && _haveCountMethodHas0Argument; - - public ShouldHaveCount0SyntaxVisitor() : base("Should", "HaveCount") + public ShouldHaveCount0SyntaxVisitor() : base(MemberValidator.Should, new MemberValidator("HaveCount", HaveCountArgumentsValidator)) { } - public override void VisitArgumentList(ArgumentListSyntax node) + private static bool HaveCountArgumentsValidator(SeparatedSyntaxList arguments) { - if (!node.Arguments.Any()) return; - if (CurrentMethod != "HaveCount") return; + if (!arguments.Any()) return false; - _haveCountMethodHas0Argument = - node.Arguments[0].Expression is LiteralExpressionSyntax literal + return arguments[0].Expression is LiteralExpressionSyntax literal && literal.Token.Value is int argument && argument == 0; } @@ -62,7 +55,7 @@ node.Arguments[0].Expression is LiteralExpressionSyntax literal public class CollectionShouldBeEmptyCodeFix : FluentAssertionsCodeFixProvider { public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(CollectionShouldBeEmptyAnalyzer.DiagnosticId); - + protected override ExpressionSyntax GetNewExpression(ExpressionSyntax expression, FluentAssertionsDiagnosticProperties properties) { switch (properties.VisitorName) diff --git a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldBeInAscendingOrder.cs b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldBeInAscendingOrder.cs index 94b12ec0..c4d0435d 100644 --- a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldBeInAscendingOrder.cs +++ b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldBeInAscendingOrder.cs @@ -14,7 +14,7 @@ public class CollectionShouldBeInAscendingOrderAnalyzer : FluentAssertionsAnalyz public const string DiagnosticId = Constants.Tips.Collections.CollectionShouldBeInAscendingOrder; public const string Category = Constants.Tips.Category; - public const string Message = "Use {0} .Should() followed by .BeInAscendingOrder() instead."; + public const string Message = "Use .Should().BeInAscendingOrder() instead."; protected override DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true); protected override IEnumerable Visitors @@ -24,28 +24,11 @@ protected override IEnumerable Visitors yield return new OrderByShouldEqualSyntaxVisitor(); } } - private class OrderByShouldEqualSyntaxVisitor : FluentAssertionsWithLambdaArgumentCSharpSyntaxVisitor - { - private bool _argumentIsSelf; - protected override string MethodContainingLambda => "OrderBy"; - - public override bool IsValid => base.IsValid && _argumentIsSelf; - - public OrderByShouldEqualSyntaxVisitor() : base("OrderBy", "Should", "Equal") - { - } - public override void VisitArgumentList(ArgumentListSyntax node) + private class OrderByShouldEqualSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor + { + public OrderByShouldEqualSyntaxVisitor() : base(MemberValidator.MathodContainingLambda("OrderBy"), MemberValidator.Should, MemberValidator.ArgumentIsVariable("Equal")) { - if (!node.Arguments.Any()) return; - if (CurrentMethod != "Equal") - { - base.VisitArgumentList(node); - return; - } - - _argumentIsSelf = node.Arguments[0].Expression is IdentifierNameSyntax identifier - && identifier.Identifier.Text == VariableName; } } } @@ -54,7 +37,7 @@ public override void VisitArgumentList(ArgumentListSyntax node) public class CollectionShouldBeInAscendingOrderCodeFix : FluentAssertionsCodeFixProvider { public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(CollectionShouldBeInAscendingOrderAnalyzer.DiagnosticId); - + protected override ExpressionSyntax GetNewExpression(ExpressionSyntax expression, FluentAssertionsDiagnosticProperties properties) { var remove = NodeReplacement.RemoveAndExtractArguments("OrderBy"); diff --git a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldBeInDescendingOrder.cs b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldBeInDescendingOrder.cs index bb5ead98..cfb434b6 100644 --- a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldBeInDescendingOrder.cs +++ b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldBeInDescendingOrder.cs @@ -14,7 +14,7 @@ public class CollectionShouldBeInDescendingOrderAnalyzer : FluentAssertionsAnaly public const string DiagnosticId = Constants.Tips.Collections.CollectionShouldBeInDescendingOrder; public const string Category = Constants.Tips.Category; - public const string Message = "Use {0} .Should() followed by .BeInDescendingOrder() instead."; + public const string Message = "Use .Should().BeInDescendingOrder() instead."; protected override DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true); protected override IEnumerable Visitors @@ -24,28 +24,11 @@ protected override IEnumerable Visitors yield return new OrderByDescendingShouldEqualSyntaxVisitor(); } } - private class OrderByDescendingShouldEqualSyntaxVisitor : FluentAssertionsWithLambdaArgumentCSharpSyntaxVisitor - { - private bool _argumentIsSelf; - protected override string MethodContainingLambda => "OrderByDescending"; - - public override bool IsValid => base.IsValid && _argumentIsSelf; - public OrderByDescendingShouldEqualSyntaxVisitor() : base("OrderByDescending", "Should", "Equal") - { - } - - public override void VisitArgumentList(ArgumentListSyntax node) + private class OrderByDescendingShouldEqualSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor + { + public OrderByDescendingShouldEqualSyntaxVisitor() : base(MemberValidator.MathodContainingLambda("OrderByDescending"), MemberValidator.Should, MemberValidator.ArgumentIsVariable("Equal")) { - if (!node.Arguments.Any()) return; - if (CurrentMethod != "Equal") - { - base.VisitArgumentList(node); - return; - } - - _argumentIsSelf = node.Arguments[0].Expression is IdentifierNameSyntax identifier - && identifier.Identifier.Text == VariableName; } } } diff --git a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldContainItem.cs b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldContainItem.cs index 1431e711..db374877 100644 --- a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldContainItem.cs +++ b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldContainItem.cs @@ -14,7 +14,7 @@ public class CollectionShouldContainItemAnalyzer : FluentAssertionsAnalyzer public const string DiagnosticId = Constants.Tips.Collections.CollectionShouldContainItem; public const string Category = Constants.Tips.Category; - public const string Message = "Use {0} .Should() followed by Contain() instead."; + public const string Message = "Use .Should()Contain() instead."; protected override DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true); @@ -28,22 +28,8 @@ protected override IEnumerable Visitors private class ContainsShouldBeTrueSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor { - public string ExpectedItemString { get; private set; } - public override bool IsValid => base.IsValid && ExpectedItemString != null; - - public ContainsShouldBeTrueSyntaxVisitor() : base("Contains", "Should", "BeTrue") - { - } - - public override ImmutableDictionary ToDiagnosticProperties() - => base.ToDiagnosticProperties().Add(Constants.DiagnosticProperties.ExpectedItemString, ExpectedItemString); - - public override void VisitArgumentList(ArgumentListSyntax node) + public ContainsShouldBeTrueSyntaxVisitor() : base(new MemberValidator("Contains"), MemberValidator.Should, new MemberValidator("BeTrue")) { - if (!node.Arguments.Any()) return; - if (CurrentMethod != "Contains") return; - - ExpectedItemString = node.Arguments[0].ToFullString(); } } } diff --git a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldContainProperty.cs b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldContainProperty.cs index 4d371082..44f14e46 100644 --- a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldContainProperty.cs +++ b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldContainProperty.cs @@ -14,7 +14,7 @@ public class CollectionShouldContainPropertyAnalyzer : FluentAssertionsAnalyzer public const string DiagnosticId = Constants.Tips.Collections.CollectionShouldContainProperty; public const string Category = Constants.Tips.Category; - public const string Message = "Use {0} .Should() followed by .Contain() instead."; + public const string Message = "Use .Should().Contain() instead."; protected override DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true); protected override IEnumerable Visitors @@ -26,17 +26,16 @@ protected override IEnumerable Visitors } } - public class AnyShouldBeTrueSyntaxVisitor : FluentAssertionsWithLambdaArgumentCSharpSyntaxVisitor + public class AnyShouldBeTrueSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor { - protected override string MethodContainingLambda => "Any"; - public AnyShouldBeTrueSyntaxVisitor() : base("Any", "Should", "BeTrue") + public AnyShouldBeTrueSyntaxVisitor() : base(MemberValidator.MathodContainingLambda("Any"), MemberValidator.Should, new MemberValidator("BeTrue")) { } } - public class WhereShouldNotBeEmptySyntaxVisitor : FluentAssertionsWithLambdaArgumentCSharpSyntaxVisitor + + public class WhereShouldNotBeEmptySyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor { - protected override string MethodContainingLambda => "Where"; - public WhereShouldNotBeEmptySyntaxVisitor() : base("Where", "Should", "NotBeEmpty") + public WhereShouldNotBeEmptySyntaxVisitor() : base(MemberValidator.MathodContainingLambda("Where"), MemberValidator.Should, new MemberValidator("NotBeEmpty")) { } } diff --git a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldContainSingle.cs b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldContainSingle.cs index f32e90e2..518ac7e8 100644 --- a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldContainSingle.cs +++ b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldContainSingle.cs @@ -14,7 +14,7 @@ public class CollectionShouldContainSingleAnalyzer : FluentAssertionsAnalyzer public const string DiagnosticId = Constants.Tips.Collections.CollectionShouldContainSingle; public const string Category = Constants.Tips.Category; - public const string Message = "Use {0} .Should() followed by .ContainSingle() instead."; + public const string Message = "Use .Should().ContainSingle() instead."; protected override DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true); protected override IEnumerable Visitors @@ -26,47 +26,17 @@ protected override IEnumerable Visitors } } - public class WhereShouldHaveCount1SyntaxVisitor : FluentAssertionsWithLambdaArgumentCSharpSyntaxVisitor + public class WhereShouldHaveCount1SyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor { - private bool _haveCountMethodHas1Argument; - - protected override string MethodContainingLambda => "Where"; - public override bool IsValid => base.IsValid && _haveCountMethodHas1Argument; - public WhereShouldHaveCount1SyntaxVisitor() : base("Where", "Should", "HaveCount") + public WhereShouldHaveCount1SyntaxVisitor() : base(MemberValidator.MathodContainingLambda("Where"), MemberValidator.Should, MemberValidator.ArgumentIsLiteral("HaveCount", 1)) { } - - public override void VisitArgumentList(ArgumentListSyntax node) - { - if (!node.Arguments.Any()) return; - if (CurrentMethod != "HaveCount") - { - base.VisitArgumentList(node); - return; - } - - _haveCountMethodHas1Argument = - node.Arguments[0].Expression is LiteralExpressionSyntax literal - && literal.Token.Value is int argument - && argument == 1; - } } - public class ShouldHaveCount1SyntaxVisitor : FluentAssertionsWithArgumentCSharpSyntaxVisitor - { - protected override string MethodContainingArgument => "HaveCount"; - public ShouldHaveCount1SyntaxVisitor() : base("Should", "HaveCount") - { - } - protected override ExpressionSyntax ModifyArgument(ExpressionSyntax expression) + public class ShouldHaveCount1SyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor + { + public ShouldHaveCount1SyntaxVisitor() : base(MemberValidator.Should, MemberValidator.ArgumentIsLiteral("HaveCount", 1)) { - if (expression is LiteralExpressionSyntax literal - && literal.Token.Value is int argument - && argument == 1) - { - return expression; - } - return null; } } } @@ -75,7 +45,7 @@ protected override ExpressionSyntax ModifyArgument(ExpressionSyntax expression) public class CollectionShouldContainSingleCodeFix : FluentAssertionsCodeFixProvider { public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(CollectionShouldContainSingleAnalyzer.DiagnosticId); - + protected override ExpressionSyntax GetNewExpression(ExpressionSyntax expression, FluentAssertionsDiagnosticProperties properties) { var newStatement = GetNewExpression(expression, NodeReplacement.RenameAndRemoveFirstArgument("HaveCount", "ContainSingle")); diff --git a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldEqualOtherCollectionByComparer.cs b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldEqualOtherCollectionByComparer.cs index 43c63778..6c6651db 100644 --- a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldEqualOtherCollectionByComparer.cs +++ b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldEqualOtherCollectionByComparer.cs @@ -15,7 +15,7 @@ public class CollectionShouldEqualOtherCollectionByComparerAnalyzer : FluentAsse public const string DiagnosticId = Constants.Tips.Collections.CollectionShouldEqualOtherCollectionByComparer; public const string Category = Constants.Tips.Category; - public const string Message = "Use {0} .Should() followed by .Equal() instead."; + public const string Message = "Use .Should().Equal() instead."; protected override DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true); @@ -27,48 +27,19 @@ protected override IEnumerable Visitors } } - private class SelectShouldEqualOtherCollectionSelectSyntaxVisitor : FluentAssertionsWithArgumentsCSharpSyntaxVisitor + private class SelectShouldEqualOtherCollectionSelectSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor { - private ExpressionSyntax _lambdaArgument; - private string _otherVariable; - - protected override bool AreArgumentsValid() + public SelectShouldEqualOtherCollectionSelectSyntaxVisitor() + : base(MemberValidator.MathodContainingLambda("Select"), MemberValidator.Should, new MemberValidator("Equal", MathodContainingArgumentInvokingLambda)) { - if (Arguments.TryGetValue(("Select", 0), out var selectArgument) && selectArgument is SimpleLambdaExpressionSyntax select - && Arguments.TryGetValue(("Equal", 0), out var expectedArgument) && expectedArgument is InvocationExpressionSyntax expected) - { - var visitor = new SelectSyntaxVisitor(); - expected.Accept(visitor); - - if (visitor.IsValid) - { - _otherVariable = visitor.VariableName; - _lambdaArgument = SyntaxFactory.ParenthesizedLambdaExpression( - parameterList: SyntaxFactory.ParameterList().AddParameters(select.Parameter, visitor.Lambda.Parameter), - body: SyntaxFactory.BinaryExpression(SyntaxKind.EqualsExpression, - left: (ExpressionSyntax)select.Body, - right: (ExpressionSyntax)visitor.Lambda.Body) - ).NormalizeWhitespace(); - return true; - } - } - return false; } - public SelectShouldEqualOtherCollectionSelectSyntaxVisitor() : base("Select", "Should", "Equal") + private static bool MathodContainingArgumentInvokingLambda(SeparatedSyntaxList arguments) { - } + if (!arguments.Any()) return false; - public override ImmutableDictionary ToDiagnosticProperties() => base.ToDiagnosticProperties() - .Add(Constants.DiagnosticProperties.LambdaString, _lambdaArgument.ToFullString()) - .Add(Constants.DiagnosticProperties.ArgumentString, _otherVariable); - - private class SelectSyntaxVisitor : FluentAssertionsWithLambdaArgumentCSharpSyntaxVisitor - { - protected override string MethodContainingLambda => "Select"; - public SelectSyntaxVisitor() : base("Select") - { - } + return arguments[0].Expression is InvocationExpressionSyntax invocation + && MemberValidator.MethodContainingLambdaPredicate(invocation.ArgumentList.Arguments); } } } @@ -77,7 +48,7 @@ public SelectSyntaxVisitor() : base("Select") public class CollectionShouldEqualOtherCollectionByComparerCodeFix : FluentAssertionsCodeFixProvider { public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(CollectionShouldEqualOtherCollectionByComparerAnalyzer.DiagnosticId); - + protected override ExpressionSyntax GetNewExpression(ExpressionSyntax expression, FluentAssertionsDiagnosticProperties properties) { var removeMethodContainingFirstLambda = NodeReplacement.RemoveAndExtractArguments("Select"); diff --git a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldHaveCount.cs b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldHaveCount.cs index 07f7b889..fd865c3a 100644 --- a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldHaveCount.cs +++ b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldHaveCount.cs @@ -14,7 +14,7 @@ public class CollectionShouldHaveCountAnalyzer : FluentAssertionsAnalyzer public const string DiagnosticId = Constants.Tips.Collections.CollectionShouldHaveCount; public const string Category = Constants.Tips.Category; - public const string Message = "Use {0} .Should() followed by .HaveCount() instead."; + public const string Message = "Use .Should().HaveCount() instead."; protected override DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true); protected override IEnumerable Visitors @@ -27,23 +27,8 @@ protected override IEnumerable Visitors private class CountShouldBeSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor { - public string CountArgument { get; private set; } - - public override bool IsValid => base.IsValid && CountArgument != null; - - public CountShouldBeSyntaxVisitor() : base("Count", "Should", "Be") - { - } - - public override ImmutableDictionary ToDiagnosticProperties() - => base.ToDiagnosticProperties().Add(Constants.DiagnosticProperties.ArgumentString, CountArgument); - - public override void VisitArgumentList(ArgumentListSyntax node) + public CountShouldBeSyntaxVisitor() : base(MemberValidator.MathodNotContainingLambda("Count"), MemberValidator.Should, new MemberValidator("Be")) { - if (!node.Arguments.Any()) return; - if (CurrentMethod != "Be") return; - - CountArgument = node.Arguments[0].ToFullString(); } } } @@ -52,7 +37,7 @@ public override void VisitArgumentList(ArgumentListSyntax node) public class CollectionShouldHaveCountCodeFix : FluentAssertionsCodeFixProvider { public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(CollectionShouldHaveCountAnalyzer.DiagnosticId); - + protected override ExpressionSyntax GetNewExpression(ExpressionSyntax expression, FluentAssertionsDiagnosticProperties properties) { return GetNewExpression(expression, NodeReplacement.Remove("Count"), NodeReplacement.Rename("Be", "HaveCount")); diff --git a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldHaveCountGreaterOrEqualTo.cs b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldHaveCountGreaterOrEqualTo.cs index f2bfc76b..17d11370 100644 --- a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldHaveCountGreaterOrEqualTo.cs +++ b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldHaveCountGreaterOrEqualTo.cs @@ -14,7 +14,7 @@ public class CollectionShouldHaveCountGreaterOrEqualToAnalyzer : FluentAssertion public const string DiagnosticId = Constants.Tips.Collections.CollectionShouldHaveCountGreaterOrEqualTo; public const string Category = Constants.Tips.Category; - public const string Message = "Use {0} .Should() followed by ### instead."; + public const string Message = "Use .Should()### instead."; protected override DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true); protected override IEnumerable Visitors @@ -25,10 +25,9 @@ protected override IEnumerable Visitors } } - private class CountShouldBeGreaterOrEqualToSyntaxVisitor : FluentAssertionsWithArgumentCSharpSyntaxVisitor + private class CountShouldBeGreaterOrEqualToSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor { - protected override string MethodContainingArgument => "BeGreaterOrEqualTo"; - public CountShouldBeGreaterOrEqualToSyntaxVisitor() : base("Count", "Should", "BeGreaterOrEqualTo") + public CountShouldBeGreaterOrEqualToSyntaxVisitor() : base(new MemberValidator("Count"), MemberValidator.Should, new MemberValidator("BeGreaterOrEqualTo")) { } } diff --git a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldHaveCountGreaterThan.cs b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldHaveCountGreaterThan.cs index dcab4ed5..99d06205 100644 --- a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldHaveCountGreaterThan.cs +++ b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldHaveCountGreaterThan.cs @@ -14,7 +14,7 @@ public class CollectionShouldHaveCountGreaterThanAnalyzer : FluentAssertionsAnal public const string DiagnosticId = Constants.Tips.Collections.CollectionShouldHaveCountGreaterThan; public const string Category = Constants.Tips.Category; - public const string Message = "Use {0} .Should() followed by .HaveCountGreaterThan() instead."; + public const string Message = "Use .Should().HaveCountGreaterThan() instead."; protected override DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true); protected override IEnumerable Visitors @@ -25,11 +25,9 @@ protected override IEnumerable Visitors } } - private class CountShouldBeGreaterThanSyntaxVisitor : FluentAssertionsWithArgumentCSharpSyntaxVisitor + private class CountShouldBeGreaterThanSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor { - protected override string MethodContainingArgument => "BeGreaterThan"; - - public CountShouldBeGreaterThanSyntaxVisitor() : base("Count", "Should", "BeGreaterThan") + public CountShouldBeGreaterThanSyntaxVisitor() : base(new MemberValidator("Count"), MemberValidator.Should, new MemberValidator("BeGreaterThan")) { } } diff --git a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldHaveCountLessOrEqualTo.cs b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldHaveCountLessOrEqualTo.cs index be92e208..4f7c97d6 100644 --- a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldHaveCountLessOrEqualTo.cs +++ b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldHaveCountLessOrEqualTo.cs @@ -14,7 +14,7 @@ public class CollectionShouldHaveCountLessOrEqualToAnalyzer : FluentAssertionsAn public const string DiagnosticId = Constants.Tips.Collections.CollectionShouldHaveCountLessOrEqualTo; public const string Category = Constants.Tips.Category; - public const string Message = "Use {0} .Should() followed by .HaveCountLessOrEqualTo() instead."; + public const string Message = "Use .Should().HaveCountLessOrEqualTo() instead."; protected override DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true); @@ -25,10 +25,9 @@ protected override IEnumerable Visitors yield return new CountShouldBeLessOrEqualToSyntaxVisitor(); } } - private class CountShouldBeLessOrEqualToSyntaxVisitor : FluentAssertionsWithArgumentCSharpSyntaxVisitor + private class CountShouldBeLessOrEqualToSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor { - protected override string MethodContainingArgument => "BeLessOrEqualTo"; - public CountShouldBeLessOrEqualToSyntaxVisitor() : base("Count", "Should", "BeLessOrEqualTo") + public CountShouldBeLessOrEqualToSyntaxVisitor() : base(new MemberValidator("Count"), MemberValidator.Should, new MemberValidator("BeLessOrEqualTo")) { } } diff --git a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldHaveCountLessThan.cs b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldHaveCountLessThan.cs index ab495d6d..2897f46d 100644 --- a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldHaveCountLessThan.cs +++ b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldHaveCountLessThan.cs @@ -14,7 +14,7 @@ public class CollectionShouldHaveCountLessThanAnalyzer : FluentAssertionsAnalyze public const string DiagnosticId = Constants.Tips.Collections.CollectionShouldHaveCountLessThan; public const string Category = Constants.Tips.Category; - public const string Message = "Use {0} .Should() followed by .HaveCountLessThan() instead."; + public const string Message = "Use .Should().HaveCountLessThan() instead."; protected override DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true); protected override IEnumerable Visitors @@ -25,10 +25,9 @@ protected override IEnumerable Visitors } } - private class CountShouldBeLessThanSyntaxVisitor : FluentAssertionsWithArgumentCSharpSyntaxVisitor + private class CountShouldBeLessThanSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor { - protected override string MethodContainingArgument => "BeLessThan"; - public CountShouldBeLessThanSyntaxVisitor() : base("Count", "Should", "BeLessThan") + public CountShouldBeLessThanSyntaxVisitor() : base(new MemberValidator("Count"), MemberValidator.Should, new MemberValidator("BeLessThan")) { } } diff --git a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldHaveElementAt.cs b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldHaveElementAt.cs index a2c52e1c..6f0848ef 100644 --- a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldHaveElementAt.cs +++ b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldHaveElementAt.cs @@ -14,7 +14,7 @@ public class CollectionShouldHaveElementAtAnalyzer : FluentAssertionsAnalyzer public const string DiagnosticId = Constants.Tips.Collections.CollectionShouldHaveElementAt; public const string Category = Constants.Tips.Category; - public const string Message = "Use {0} .Should() followed by .HaveElementAt() instead."; + public const string Message = "Use .Should().HaveElementAt() instead."; protected override DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true); protected override IEnumerable Visitors @@ -27,50 +27,25 @@ protected override IEnumerable Visitors } } - public class ElementAtIndexShouldBeSyntaxVisitor : FluentAssertionsWithArgumentsCSharpSyntaxVisitor + public class ElementAtIndexShouldBeSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor { - protected override bool AreArgumentsValid() => - Arguments.TryGetValue(("ElementAt", 0), out ExpressionSyntax index) && (index is LiteralExpressionSyntax || index is IdentifierNameSyntax) - && Arguments.TryGetValue(("Be", 0), out ExpressionSyntax expectedItem) && (expectedItem is LiteralExpressionSyntax || expectedItem is IdentifierNameSyntax); - - public ElementAtIndexShouldBeSyntaxVisitor() : base("ElementAt", "Should", "Be") + public ElementAtIndexShouldBeSyntaxVisitor() : base(new MemberValidator("ElementAt"), MemberValidator.Should, new MemberValidator("Be")) { } - - public override ImmutableDictionary ToDiagnosticProperties() - => base.ToDiagnosticProperties() - .Add(Constants.DiagnosticProperties.ArgumentString, Arguments[("ElementAt", 0)].ToFullString()) - .Add(Constants.DiagnosticProperties.ExpectedItemString, Arguments[("Be", 0)].ToFullString()); } - public class IndexerShouldBeSyntaxVisitor : FluentAssertionsWithArgumentsCSharpSyntaxVisitor - { - protected override bool AreArgumentsValid() => - Arguments.TryGetValue(("[]", 0), out ExpressionSyntax index) && (index is LiteralExpressionSyntax || index is IdentifierNameSyntax) - && Arguments.TryGetValue(("Be", 0), out ExpressionSyntax expectedItem) && (expectedItem is LiteralExpressionSyntax || expectedItem is IdentifierNameSyntax); - public IndexerShouldBeSyntaxVisitor() : base("[]", "Should", "Be") + public class IndexerShouldBeSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor + { + public IndexerShouldBeSyntaxVisitor() : base(new MemberValidator("[]"), MemberValidator.Should, new MemberValidator("Be")) { } - public override ImmutableDictionary ToDiagnosticProperties() - => base.ToDiagnosticProperties() - .Add(Constants.DiagnosticProperties.ArgumentString, Arguments[("[]", 0)].ToFullString()) - .Add(Constants.DiagnosticProperties.ExpectedItemString, Arguments[("Be", 0)].ToFullString()); } - public class SkipFirstShouldBeSyntaxVisitor : FluentAssertionsWithArgumentsCSharpSyntaxVisitor + public class SkipFirstShouldBeSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor { - protected override bool AreArgumentsValid() => - Arguments.TryGetValue(("Skip", 0), out ExpressionSyntax index) && (index is LiteralExpressionSyntax || index is IdentifierNameSyntax) - && Arguments.TryGetValue(("Be", 0), out ExpressionSyntax expectedItem) && (expectedItem is LiteralExpressionSyntax || expectedItem is IdentifierNameSyntax); - - public SkipFirstShouldBeSyntaxVisitor() : base("Skip", "First", "Should", "Be") + public SkipFirstShouldBeSyntaxVisitor() : base(new MemberValidator("Skip"), MemberValidator.MathodNotContainingLambda("First"), MemberValidator.Should, new MemberValidator("Be")) { } - - public override ImmutableDictionary ToDiagnosticProperties() - => base.ToDiagnosticProperties() - .Add(Constants.DiagnosticProperties.ArgumentString, Arguments[("Skip", 0)].ToFullString()) - .Add(Constants.DiagnosticProperties.ExpectedItemString, Arguments[("Be", 0)].ToFullString()); } } @@ -78,7 +53,7 @@ public override ImmutableDictionary ToDiagnosticProperties() public class CollectionShouldHaveElementAtCodeFix : FluentAssertionsCodeFixProvider { public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(CollectionShouldHaveElementAtAnalyzer.DiagnosticId); - + protected override ExpressionSyntax GetNewExpression(ExpressionSyntax expression, FluentAssertionsDiagnosticProperties properties) { if (properties.VisitorName == nameof(CollectionShouldHaveElementAtAnalyzer.ElementAtIndexShouldBeSyntaxVisitor)) diff --git a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldHaveElementAt0Null.cs b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldHaveElementAt0Null.cs index 67ac3508..c0c7c57b 100644 --- a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldHaveElementAt0Null.cs +++ b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldHaveElementAt0Null.cs @@ -2,7 +2,6 @@ using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; -using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; @@ -14,32 +13,13 @@ public class CollectionShouldHaveElementAt0NullAnalyzer : FluentAssertionsAnalyz public const string DiagnosticId = Constants.Tips.Collections.CollectionShouldHaveElementAt0Null; public const string Category = Constants.Tips.Category; - public const string Message = "Use {0} .Should() followed by ### instead."; + public const string Message = "Use .Should()### instead."; protected override DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true); protected override Diagnostic AnalyzeExpression(ExpressionSyntax expression) { return null; - var visitor = new CollectionShouldHaveElementAt0NullSyntaxVisitor(); - expression.Accept(visitor); - - if (visitor.IsValid) - { - var properties = new Dictionary - { - [Constants.DiagnosticProperties.VariableName] = visitor.VariableName, - [Constants.DiagnosticProperties.Title] = Title - }.ToImmutableDictionary(); - throw new System.NotImplementedException(); - - return Diagnostic.Create( - descriptor: Rule, - location: expression.GetLocation(), - properties: properties, - messageArgs: visitor.VariableName); - } - return null; } } @@ -53,12 +33,4 @@ protected override ExpressionSyntax GetNewExpression(ExpressionSyntax expression throw new System.NotImplementedException(); } } - - public class CollectionShouldHaveElementAt0NullSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor - { - public CollectionShouldHaveElementAt0NullSyntaxVisitor() : base("###") - { - throw new System.NotImplementedException(); - } - } } diff --git a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldHaveSameCount.cs b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldHaveSameCount.cs index c9c3473c..fc3fac78 100644 --- a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldHaveSameCount.cs +++ b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldHaveSameCount.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; +using System.Linq; namespace FluentAssertions.Analyzers { @@ -14,7 +15,7 @@ public class CollectionShouldHaveSameCountAnalyzer : FluentAssertionsAnalyzer public const string DiagnosticId = Constants.Tips.Collections.CollectionShouldHaveSameCount; public const string Category = Constants.Tips.Category; - public const string Message = "Use {0} .Should() followed by .HaveSameCount() instead."; + public const string Message = "Use .Should().HaveSameCount() instead."; protected override DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true); protected override IEnumerable Visitors @@ -25,20 +26,21 @@ protected override IEnumerable Visitors } } - private class ShouldHaveCountOtherCollectionCountSyntaxVisitor : FluentAssertionsWithArgumentCSharpSyntaxVisitor + private class ShouldHaveCountOtherCollectionCountSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor { - protected override string MethodContainingArgument => "HaveCount"; - public ShouldHaveCountOtherCollectionCountSyntaxVisitor() : base("Should", "HaveCount") + public ShouldHaveCountOtherCollectionCountSyntaxVisitor() : base(MemberValidator.Should, new MemberValidator("HaveCount", HasArgumentInvokingCountMethod)) { } - protected override ExpressionSyntax ModifyArgument(ExpressionSyntax expression) + private static bool HasArgumentInvokingCountMethod(SeparatedSyntaxList arguments) { - var invocation = expression as InvocationExpressionSyntax; - var memberAccess = invocation?.Expression as MemberAccessExpressionSyntax; - var identifier = memberAccess?.Expression as IdentifierNameSyntax; + if (!arguments.Any()) return false; - return identifier; + return arguments.First().Expression is InvocationExpressionSyntax invocation + && invocation.Expression is MemberAccessExpressionSyntax memberAccess + && memberAccess.Name.Identifier.Text == nameof(Enumerable.Count) + && memberAccess.Expression is IdentifierNameSyntax; + } } } diff --git a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldIntersectWith.cs b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldIntersectWith.cs index 05aabee2..77682b43 100644 --- a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldIntersectWith.cs +++ b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldIntersectWith.cs @@ -14,7 +14,7 @@ public class CollectionShouldIntersectWithAnalyzer : FluentAssertionsAnalyzer public const string DiagnosticId = Constants.Tips.Collections.CollectionShouldIntersectWith; public const string Category = Constants.Tips.Category; - public const string Message = "Use {0} .Should() followed by .IntersectWith() instead."; + public const string Message = "Use .Should().IntersectWith() instead."; protected override DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true); protected override IEnumerable Visitors @@ -25,10 +25,9 @@ protected override IEnumerable Visitors } } - private class IntersectShouldNotBeEmptySyntaxVisitor : FluentAssertionsWithArgumentCSharpSyntaxVisitor + private class IntersectShouldNotBeEmptySyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor { - protected override string MethodContainingArgument => "Intersect"; - public IntersectShouldNotBeEmptySyntaxVisitor() : base("Intersect", "Should", "NotBeEmpty") + public IntersectShouldNotBeEmptySyntaxVisitor() : base(MemberValidator.HasArguments("Intersect"), MemberValidator.Should, new MemberValidator("NotBeEmpty")) { } } diff --git a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldNotBeEmpty.cs b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldNotBeEmpty.cs index 4aeb8d40..175ac408 100644 --- a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldNotBeEmpty.cs +++ b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldNotBeEmpty.cs @@ -15,7 +15,7 @@ public class CollectionShouldNotBeEmptyAnalyzer : FluentAssertionsAnalyzer public const string DiagnosticId = Constants.Tips.Collections.CollectionsShouldNotBeEmpty; public const string Category = Constants.Tips.Category; - public const string Message = "Use {0} .Should() followed by .NotBeEmpty() instead."; + public const string Message = "Use .Should().NotBeEmpty() instead."; protected override DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true); @@ -27,11 +27,9 @@ protected override IEnumerable Visitors } } - private class AnyShouldBeTrueSyntaxVisitor : FluentAssertionsWithoutLambdaArgumentCSharpSyntaxVisitor + private class AnyShouldBeTrueSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor { - protected override string MathodNotContainingLambda => "Any"; - - public AnyShouldBeTrueSyntaxVisitor() : base("Any", "Should", "BeTrue") + public AnyShouldBeTrueSyntaxVisitor() : base(MemberValidator.MathodNotContainingLambda("Any"), MemberValidator.Should, new MemberValidator("BeTrue")) { } } @@ -41,16 +39,10 @@ public AnyShouldBeTrueSyntaxVisitor() : base("Any", "Should", "BeTrue") public class CollectionShouldNotBeEmptyCodeFix : FluentAssertionsCodeFixProvider { public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(CollectionShouldNotBeEmptyAnalyzer.DiagnosticId); - + protected override ExpressionSyntax GetNewExpression(ExpressionSyntax expression, FluentAssertionsDiagnosticProperties properties) { - NodeReplacement[] replacements = - { - NodeReplacement.Remove("Any"), - NodeReplacement.Rename("BeTrue", "NotBeEmpty") - }; - - return GetNewExpression(expression, replacements); + return GetNewExpression(expression, NodeReplacement.Remove("Any"), NodeReplacement.Rename("BeTrue", "NotBeEmpty")); } } } diff --git a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldNotBeNullOrEmpty.cs b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldNotBeNullOrEmpty.cs index c0994fde..bcf5d698 100644 --- a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldNotBeNullOrEmpty.cs +++ b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldNotBeNullOrEmpty.cs @@ -14,7 +14,7 @@ public class CollectionShouldNotBeNullOrEmptyAnalyzer : FluentAssertionsAnalyzer public const string DiagnosticId = Constants.Tips.Collections.CollectionShouldNotBeNullOrEmpty; public const string Category = Constants.Tips.Category; - public const string Message = "Use {0} .Should() followed by .NotBeNullOrEmpty() instead."; + public const string Message = "Use .Should().NotBeNullOrEmpty() instead."; protected override DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true); @@ -29,13 +29,13 @@ protected override IEnumerable Visitors public class ShouldNotBeNullAndNotBeEmptySyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor { - public ShouldNotBeNullAndNotBeEmptySyntaxVisitor() : base("Should", "NotBeNull", "And", "NotBeEmpty") + public ShouldNotBeNullAndNotBeEmptySyntaxVisitor() : base(MemberValidator.Should, new MemberValidator("NotBeNull"), MemberValidator.And, new MemberValidator("NotBeEmpty")) { } } public class ShouldNotBeEmptyAndNotBeNullSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor { - public ShouldNotBeEmptyAndNotBeNullSyntaxVisitor() : base("Should", "NotBeEmpty", "And", "NotBeNull") + public ShouldNotBeEmptyAndNotBeNullSyntaxVisitor() : base(MemberValidator.Should, new MemberValidator("NotBeEmpty"), MemberValidator.And, new MemberValidator("NotBeNull")) { } } diff --git a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldNotContainItem.cs b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldNotContainItem.cs index 63fe7f61..2622384b 100644 --- a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldNotContainItem.cs +++ b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldNotContainItem.cs @@ -14,7 +14,7 @@ public class CollectionShouldNotContainItemAnalyzer : FluentAssertionsAnalyzer public const string DiagnosticId = Constants.Tips.Collections.CollectionShouldNotContainItem; public const string Category = Constants.Tips.Category; - public const string Message = "Use {0} .Should() followed by .NotContain() instead."; + public const string Message = "Use .Should().NotContain() instead."; protected override DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true); protected override IEnumerable Visitors @@ -27,22 +27,8 @@ protected override IEnumerable Visitors private class ContainsShouldBeFalseSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor { - public string UnexpectedItemString { get; private set; } - public override bool IsValid => base.IsValid && UnexpectedItemString != null; - - public ContainsShouldBeFalseSyntaxVisitor() : base("Contains", "Should", "BeFalse") - { - } - - public override ImmutableDictionary ToDiagnosticProperties() - => base.ToDiagnosticProperties().Add(Constants.DiagnosticProperties.UnexpectedItemString, UnexpectedItemString); - - public override void VisitArgumentList(ArgumentListSyntax node) + public ContainsShouldBeFalseSyntaxVisitor() : base(new MemberValidator("Contains"), MemberValidator.Should, new MemberValidator("BeFalse")) { - if (!node.Arguments.Any()) return; - if (CurrentMethod != "Contains") return; - - UnexpectedItemString = node.Arguments[0].ToFullString(); } } } diff --git a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldNotContainNulls.cs b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldNotContainNulls.cs index 00b0f1ef..f8b54456 100644 --- a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldNotContainNulls.cs +++ b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldNotContainNulls.cs @@ -14,7 +14,7 @@ public class CollectionShouldNotContainNullsAnalyzer : FluentAssertionsAnalyzer public const string DiagnosticId = Constants.Tips.Collections.CollectionShouldNotContainNulls; public const string Category = Constants.Tips.Category; - public const string Message = "Use {0} .Should() followed by ### instead."; + public const string Message = "Use .Should()### instead."; protected override DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true); protected override IEnumerable Visitors @@ -25,10 +25,9 @@ protected override IEnumerable Visitors } } - private class SelectShouldNotContainNullsSyntaxVisitor : FluentAssertionsWithLambdaArgumentCSharpSyntaxVisitor + private class SelectShouldNotContainNullsSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor { - protected override string MethodContainingLambda => "Select"; - public SelectShouldNotContainNullsSyntaxVisitor() : base("Select", "Should", "NotContainNulls") + public SelectShouldNotContainNullsSyntaxVisitor() : base(MemberValidator.MathodContainingLambda("Select"), MemberValidator.Should, new MemberValidator("NotContainNulls")) { } } diff --git a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldNotContainProperty.cs b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldNotContainProperty.cs index 2ed6da13..49524779 100644 --- a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldNotContainProperty.cs +++ b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldNotContainProperty.cs @@ -1,6 +1,5 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using System.Collections.Generic; @@ -15,7 +14,7 @@ public class CollectionShouldNotContainPropertyAnalyzer : FluentAssertionsAnalyz public const string DiagnosticId = Constants.Tips.Collections.CollectionShouldNotContainProperty; public const string Category = Constants.Tips.Category; - public const string Message = "Use {0} .Should() followed by .NotContain() instead."; + public const string Message = "Use .Should().NotContain() instead."; protected override DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true); protected override IEnumerable Visitors @@ -28,40 +27,25 @@ protected override IEnumerable Visitors } } - public class AnyShouldBeFalseSyntaxVisitor : FluentAssertionsWithLambdaArgumentCSharpSyntaxVisitor + public class AnyShouldBeFalseSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor { - protected override string MethodContainingLambda => "Any"; - public AnyShouldBeFalseSyntaxVisitor() : base("Any", "Should", "BeFalse") + public AnyShouldBeFalseSyntaxVisitor() : base(MemberValidator.MathodContainingLambda("Any"), MemberValidator.Should, new MemberValidator("BeFalse")) { } } - public class WhereShouldBeEmptySyntaxVisitor : FluentAssertionsWithLambdaArgumentCSharpSyntaxVisitor + public class WhereShouldBeEmptySyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor { - protected override string MethodContainingLambda => "Where"; - public WhereShouldBeEmptySyntaxVisitor() : base("Where", "Should", "BeEmpty") + public WhereShouldBeEmptySyntaxVisitor() : base(MemberValidator.MathodContainingLambda("Where"), MemberValidator.Should, new MemberValidator("BeEmpty")) { } } - public class ShouldOnlyContainNotSyntaxVisitor : FluentAssertionsWithLambdaArgumentCSharpSyntaxVisitor + public class ShouldOnlyContainNotSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor { - protected override string MethodContainingLambda => "OnlyContain"; - public override SimpleLambdaExpressionSyntax Lambda => ReverseLambda(base.Lambda); - - public ShouldOnlyContainNotSyntaxVisitor() : base("Should", "OnlyContain") + public ShouldOnlyContainNotSyntaxVisitor() : base(MemberValidator.Should, MemberValidator.MathodContainingLambda("OnlyContain")) { } - - private SimpleLambdaExpressionSyntax ReverseLambda(SimpleLambdaExpressionSyntax lambda) - { - if (lambda.Body is PrefixUnaryExpressionSyntax prefixUnary && prefixUnary.OperatorToken.IsKind(SyntaxKind.ExclamationToken)) - { - return lambda.WithBody(prefixUnary.Operand); - } - - return lambda; - } } } diff --git a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldNotHaveCount.cs b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldNotHaveCount.cs index 8624ca0a..f2e1a6a7 100644 --- a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldNotHaveCount.cs +++ b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldNotHaveCount.cs @@ -14,7 +14,7 @@ public class CollectionShouldNotHaveCountAnalyzer : FluentAssertionsAnalyzer public const string DiagnosticId = Constants.Tips.Collections.CollectionShouldNotHaveCount; public const string Category = Constants.Tips.Category; - public const string Message = "Use {0} .Should() followed by .NotHaveCount() instead."; + public const string Message = "Use .Should().NotHaveCount() instead."; protected override DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true); protected override IEnumerable Visitors @@ -25,19 +25,11 @@ protected override IEnumerable Visitors } } - private class CountShouldNotBeSyntaxVisitor : FluentAssertionsWithArgumentCSharpSyntaxVisitor + private class CountShouldNotBeSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor { - protected override string MethodContainingArgument => "NotBe"; - public CountShouldNotBeSyntaxVisitor() : base("Count", "Should", "NotBe") + public CountShouldNotBeSyntaxVisitor() : base(MemberValidator.HasNoArguments("Count"), MemberValidator.Should, MemberValidator.ArgumentIsIdentifierOrLiteral("NotBe")) { } - - protected override ExpressionSyntax ModifyArgument(ExpressionSyntax expression) - { - if (expression is IdentifierNameSyntax identifier) return identifier; - if (expression is LiteralExpressionSyntax literal) return literal; - return null; - } } } diff --git a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldNotHaveSameCount.cs b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldNotHaveSameCount.cs index a6cb741c..def52451 100644 --- a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldNotHaveSameCount.cs +++ b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldNotHaveSameCount.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; +using System.Linq; namespace FluentAssertions.Analyzers { @@ -14,7 +15,7 @@ public class CollectionShouldNotHaveSameCountAnalyzer : FluentAssertionsAnalyzer public const string DiagnosticId = Constants.Tips.Collections.CollectionShouldNotHaveSameCount; public const string Category = Constants.Tips.Category; - public const string Message = "Use {0} .Should() followed by .NotHaveSameCount() instead."; + public const string Message = "Use .Should().NotHaveSameCount() instead."; protected override DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true); protected override IEnumerable Visitors @@ -24,20 +25,22 @@ protected override IEnumerable Visitors yield return new CountShouldNotBeOtherCollectionCountSyntaxVisitor(); } } - private class CountShouldNotBeOtherCollectionCountSyntaxVisitor : FluentAssertionsWithArgumentCSharpSyntaxVisitor + + private class CountShouldNotBeOtherCollectionCountSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor { - protected override string MethodContainingArgument => "NotBe"; - public CountShouldNotBeOtherCollectionCountSyntaxVisitor() : base("Count", "Should", "NotBe") + public CountShouldNotBeOtherCollectionCountSyntaxVisitor() : base(MemberValidator.HasNoArguments("Count"), MemberValidator.Should, new MemberValidator("NotBe", HasArgumentInvokingCountMethod)) { } - protected override ExpressionSyntax ModifyArgument(ExpressionSyntax expression) + private static bool HasArgumentInvokingCountMethod(SeparatedSyntaxList arguments) { - var invocation = expression as InvocationExpressionSyntax; - var memberAccess = invocation?.Expression as MemberAccessExpressionSyntax; - var identifier = memberAccess?.Expression as IdentifierNameSyntax; + if (!arguments.Any()) return false; + + return arguments.First().Expression is InvocationExpressionSyntax invocation + && invocation.Expression is MemberAccessExpressionSyntax memberAccess + && memberAccess.Name.Identifier.Text == nameof(Enumerable.Count) + && memberAccess.Expression is IdentifierNameSyntax; - return identifier; } } } diff --git a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldNotIntersectWith.cs b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldNotIntersectWith.cs index 1367e68e..fb134a95 100644 --- a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldNotIntersectWith.cs +++ b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldNotIntersectWith.cs @@ -14,7 +14,7 @@ public class CollectionShouldNotIntersectWithAnalyzer : FluentAssertionsAnalyzer public const string DiagnosticId = Constants.Tips.Collections.CollectionShouldNotIntersectWith; public const string Category = Constants.Tips.Category; - public const string Message = "Use {0} .Should() followed by .NotIntersectWith() instead."; + public const string Message = "Use .Should().NotIntersectWith() instead."; protected override DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true); protected override IEnumerable Visitors @@ -24,10 +24,9 @@ protected override IEnumerable Visitors yield return new IntersectShouldBeEmptySyntaxVisitor(); } } - private class IntersectShouldBeEmptySyntaxVisitor : FluentAssertionsWithArgumentCSharpSyntaxVisitor + private class IntersectShouldBeEmptySyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor { - protected override string MethodContainingArgument => "Intersect"; - public IntersectShouldBeEmptySyntaxVisitor() : base("Intersect", "Should", "BeEmpty") + public IntersectShouldBeEmptySyntaxVisitor() : base(MemberValidator.HasArguments("Intersect"), MemberValidator.Should, new MemberValidator("BeEmpty")) { } } @@ -37,7 +36,7 @@ public IntersectShouldBeEmptySyntaxVisitor() : base("Intersect", "Should", "BeEm public class CollectionShouldNotIntersectWithCodeFix : FluentAssertionsCodeFixProvider { public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(CollectionShouldNotIntersectWithAnalyzer.DiagnosticId); - + protected override ExpressionSyntax GetNewExpression(ExpressionSyntax expression, FluentAssertionsDiagnosticProperties properties) { var remove = NodeReplacement.RemoveAndExtractArguments("Intersect"); diff --git a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldOnlyContainProperty.cs b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldOnlyContainProperty.cs index b71298a1..7600bf0c 100644 --- a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldOnlyContainProperty.cs +++ b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldOnlyContainProperty.cs @@ -14,7 +14,7 @@ public class CollectionShouldOnlyContainPropertyAnalyzer : FluentAssertionsAnaly public const string DiagnosticId = Constants.Tips.Collections.CollectionShouldOnlyContainProperty; public const string Category = Constants.Tips.Category; - public const string Message = "Use {0} .Should() followed by .OnlyContain() instead."; + public const string Message = "Use .Should().OnlyContain() instead."; protected override DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true); @@ -26,10 +26,9 @@ protected override IEnumerable Visitors } } - public class AllShouldBeTrueSyntaxVisitor : FluentAssertionsWithLambdaArgumentCSharpSyntaxVisitor + public class AllShouldBeTrueSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor { - protected override string MethodContainingLambda => "All"; - public AllShouldBeTrueSyntaxVisitor() : base("All", "Should", "BeTrue") + public AllShouldBeTrueSyntaxVisitor() : base(MemberValidator.MathodContainingLambda("All"), MemberValidator.Should, new MemberValidator("BeTrue")) { } } diff --git a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldOnlyHaveUniqueItems.cs b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldOnlyHaveUniqueItems.cs index b78c3f2b..7d1b819c 100644 --- a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldOnlyHaveUniqueItems.cs +++ b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldOnlyHaveUniqueItems.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; +using System.Linq; namespace FluentAssertions.Analyzers { @@ -14,7 +15,7 @@ public class CollectionShouldOnlyHaveUniqueItemsAnalyzer : FluentAssertionsAnaly public const string DiagnosticId = Constants.Tips.Collections.CollectionShouldOnlyHaveUniqueItems; public const string Category = Constants.Tips.Category; - public const string Message = "Use {0} .Should() followed by ### instead."; + public const string Message = "Use .Should().OnlyHaveUniqueItems() instead."; protected override DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true); @@ -26,26 +27,24 @@ protected override IEnumerable Visitors } } - private class ShouldHaveSameCountThisCollectionDistinctSyntaxVisitor : FluentAssertionsWithArgumentCSharpSyntaxVisitor + private class ShouldHaveSameCountThisCollectionDistinctSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor { - protected override string MethodContainingArgument => "HaveSameCount"; - public ShouldHaveSameCountThisCollectionDistinctSyntaxVisitor() : base("Should", "HaveSameCount") + public ShouldHaveSameCountThisCollectionDistinctSyntaxVisitor() : base(MemberValidator.Should, new MemberValidator("HaveSameCount", ArgumentInvokesDistinctMethod)) { } - protected override ExpressionSyntax ModifyArgument(ExpressionSyntax expression) + private static bool ArgumentInvokesDistinctMethod(SeparatedSyntaxList arguments) { - var visitor = new CollectionDistinctSyntaxVisitor(); - expression.Accept(visitor); + if (!arguments.Any()) return false; - return (visitor.IsValid && visitor.VariableName == VariableName) ? expression : null; - } - - private class CollectionDistinctSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor - { - public CollectionDistinctSyntaxVisitor() : base("Distinct") + if (arguments.First().Expression is InvocationExpressionSyntax invocation) { + var visitor = new FluentAssertionsCSharpSyntaxVisitor(new MemberValidator(nameof(Enumerable.Distinct))); + invocation.Accept(visitor); + + return visitor.IsValid && VariableNameExtractor.ExtractVariabeName(arguments.First()) == VariableNameExtractor.ExtractVariabeName(invocation); } + return false; } } } diff --git a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldOnlyHaveUniqueItemsByComparer.cs b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldOnlyHaveUniqueItemsByComparer.cs index f621ba7c..82179a9d 100644 --- a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldOnlyHaveUniqueItemsByComparer.cs +++ b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldOnlyHaveUniqueItemsByComparer.cs @@ -14,7 +14,7 @@ public class CollectionShouldOnlyHaveUniqueItemsByComparerAnalyzer : FluentAsser public const string DiagnosticId = Constants.Tips.Collections.CollectionShouldOnlyHaveUniqueItemsByComparer; public const string Category = Constants.Tips.Category; - public const string Message = "Use {0} .Should() followed by .OnlyHaveUniqueItems() instead."; + public const string Message = "Use .Should().OnlyHaveUniqueItems() instead."; protected override DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true); @@ -26,10 +26,9 @@ protected override IEnumerable Visitors } } - private class SelectShouldOnlyHaveUniqueItemsSyntaxVisitor : FluentAssertionsWithLambdaArgumentCSharpSyntaxVisitor + private class SelectShouldOnlyHaveUniqueItemsSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor { - protected override string MethodContainingLambda => "Select"; - public SelectShouldOnlyHaveUniqueItemsSyntaxVisitor() : base("Select", "Should", "OnlyHaveUniqueItems") + public SelectShouldOnlyHaveUniqueItemsSyntaxVisitor() : base(MemberValidator.MathodContainingLambda("Select"), MemberValidator.Should, new MemberValidator("OnlyHaveUniqueItems")) { } } diff --git a/src/FluentAssertions.Analyzers/Tips/Dictionaries/DictionaryShouldContainKey.cs b/src/FluentAssertions.Analyzers/Tips/Dictionaries/DictionaryShouldContainKey.cs index f6edde77..2b570334 100644 --- a/src/FluentAssertions.Analyzers/Tips/Dictionaries/DictionaryShouldContainKey.cs +++ b/src/FluentAssertions.Analyzers/Tips/Dictionaries/DictionaryShouldContainKey.cs @@ -14,7 +14,7 @@ public class DictionaryShouldContainKeyAnalyzer : FluentAssertionsAnalyzer public const string DiagnosticId = Constants.Tips.Dictionaries.DictionaryShouldContainKey; public const string Category = Constants.Tips.Category; - public const string Message = "Use {0} .Should() followed by .ContainKey() instead."; + public const string Message = "Use .Should().ContainKey() instead."; protected override DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true); protected override IEnumerable Visitors @@ -27,7 +27,7 @@ protected override IEnumerable Visitors private class ContainsKeyShouldBeTrue : FluentAssertionsCSharpSyntaxVisitor { - public ContainsKeyShouldBeTrue() : base("ContainsKey", "Should", "BeTrue") + public ContainsKeyShouldBeTrue() : base(new MemberValidator("ContainsKey"), MemberValidator.Should, new MemberValidator("BeTrue")) { } } diff --git a/src/FluentAssertions.Analyzers/Tips/Dictionaries/DictionaryShouldContainKeyAndValue.cs b/src/FluentAssertions.Analyzers/Tips/Dictionaries/DictionaryShouldContainKeyAndValue.cs index df56c647..33607e87 100644 --- a/src/FluentAssertions.Analyzers/Tips/Dictionaries/DictionaryShouldContainKeyAndValue.cs +++ b/src/FluentAssertions.Analyzers/Tips/Dictionaries/DictionaryShouldContainKeyAndValue.cs @@ -15,7 +15,7 @@ public class DictionaryShouldContainKeyAndValueAnalyzer : FluentAssertionsAnalyz public const string DiagnosticId = Constants.Tips.Dictionaries.DictionaryShouldContainKeyAndValue; public const string Category = Constants.Tips.Category; - public const string Message = "Use {0} .Should() followed by .Contain() instead."; + public const string Message = "Use .Should().Contain() instead."; protected override DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true); protected override IEnumerable Visitors @@ -26,32 +26,18 @@ protected override IEnumerable Visitors yield return new ShouldContainValueAndContainKeySyntaxVisitor(); } } - - public abstract class ContainKeyValueSyntaxVisitor : FluentAssertionsWithArgumentsCSharpSyntaxVisitor + + public class ShouldContainKeyAndContainValueSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor { - protected override bool AreArgumentsValid() - { - return Arguments.TryGetValue(("ContainKey", 0), out var key) - && (key is IdentifierNameSyntax || key is LiteralExpressionSyntax) - - && Arguments.TryGetValue(("ContainValue", 0), out var value) - && (value is IdentifierNameSyntax || value is LiteralExpressionSyntax); - } - protected ContainKeyValueSyntaxVisitor(params string[] requiredMethods) : base(requiredMethods) + public ShouldContainKeyAndContainValueSyntaxVisitor() : base(MemberValidator.Should, MemberValidator.ArgumentIsIdentifierOrLiteral("ContainKey"), MemberValidator.And, MemberValidator.ArgumentIsIdentifierOrLiteral("ContainValue")) { } } - public class ShouldContainKeyAndContainValueSyntaxVisitor : ContainKeyValueSyntaxVisitor - { - public ShouldContainKeyAndContainValueSyntaxVisitor() : base("Should", "ContainKey", "And", "ContainValue") - { - } - } - public class ShouldContainValueAndContainKeySyntaxVisitor : ContainKeyValueSyntaxVisitor + public class ShouldContainValueAndContainKeySyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor { - public ShouldContainValueAndContainKeySyntaxVisitor() : base("Should", "ContainValue", "And", "ContainKey") + public ShouldContainValueAndContainKeySyntaxVisitor() : base(MemberValidator.Should, MemberValidator.ArgumentIsIdentifierOrLiteral("ContainValue"), MemberValidator.And, MemberValidator.ArgumentIsIdentifierOrLiteral("ContainKey")) { } } diff --git a/src/FluentAssertions.Analyzers/Tips/Dictionaries/DictionaryShouldContainPair.cs b/src/FluentAssertions.Analyzers/Tips/Dictionaries/DictionaryShouldContainPair.cs index e70205b2..7268bc19 100644 --- a/src/FluentAssertions.Analyzers/Tips/Dictionaries/DictionaryShouldContainPair.cs +++ b/src/FluentAssertions.Analyzers/Tips/Dictionaries/DictionaryShouldContainPair.cs @@ -15,7 +15,7 @@ public class DictionaryShouldContainPairAnalyzer : FluentAssertionsAnalyzer public const string DiagnosticId = Constants.Tips.Dictionaries.DictionaryShouldContainPair; public const string Category = Constants.Tips.Category; - public const string Message = "Use {0} .Should() followed by .Contain() instead."; + public const string Message = "Use .Should().Contain() instead."; protected override DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true); protected override IEnumerable Visitors @@ -27,37 +27,50 @@ protected override IEnumerable Visitors } } - public abstract class ContainKeyValueSyntaxVisitor : FluentAssertionsWithArgumentsCSharpSyntaxVisitor + // TODO: how to extract the property accessing the Key & Value properties and compare them + public abstract class ContainKeyValueSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor { - protected override bool AreArgumentsValid() + private string KeyPropertyParentName; + private string ValuePropertyParentName; + + protected ContainKeyValueSyntaxVisitor(params MemberValidator[] members) : base(members) { - return Arguments.TryGetValue(("ContainKey", 0), out var key) - && key is MemberAccessExpressionSyntax keyAccess - && keyAccess.Expression is IdentifierNameSyntax keyContainer - && keyAccess.Name.Identifier.Text == "Key" + } - && Arguments.TryGetValue(("ContainValue", 0), out var value) - && value is MemberAccessExpressionSyntax valueAccess - && valueAccess.Expression is IdentifierNameSyntax valueContainer - && valueAccess.Name.Identifier.Text == "Value" + public override bool IsValid => base.IsValid && KeyPropertyParentName == ValuePropertyParentName; - && keyContainer.Identifier.Text == valueContainer.Identifier.Text; - } + protected static bool KeyIsProperty(SeparatedSyntaxList arguments) + { + if (!arguments.Any()) return false; - protected ContainKeyValueSyntaxVisitor(params string[] requiredMethods) : base(requiredMethods) + return arguments.First().Expression is MemberAccessExpressionSyntax valueAccess + && valueAccess.Expression is IdentifierNameSyntax identifier + && valueAccess.Name.Identifier.Text == "Key"; + } + protected static bool ValueIsProperty(SeparatedSyntaxList arguments) { + if (!arguments.Any()) return false; + + if (arguments.First().Expression is MemberAccessExpressionSyntax valueAccess + && valueAccess.Expression is IdentifierNameSyntax identifier + && valueAccess.Name.Identifier.Text == "Value") + { + return true; + } + return false; } } + public class ShouldContainKeyAndContainValueSyntaxVisitor : ContainKeyValueSyntaxVisitor { - - public ShouldContainKeyAndContainValueSyntaxVisitor() : base("Should", "ContainKey", "And", "ContainValue") + public ShouldContainKeyAndContainValueSyntaxVisitor() : base(MemberValidator.Should, new MemberValidator("ContainKey", KeyIsProperty), MemberValidator.And, new MemberValidator("ContainValue", ValueIsProperty)) { } } + public class ShouldContainValueAndContainKeySyntaxVisitor : ContainKeyValueSyntaxVisitor { - public ShouldContainValueAndContainKeySyntaxVisitor() : base("Should", "ContainValue", "And", "ContainKey") + public ShouldContainValueAndContainKeySyntaxVisitor() : base(MemberValidator.Should, new MemberValidator("ContainValue", ValueIsProperty), MemberValidator.And, new MemberValidator("ContainKey", KeyIsProperty)) { } } diff --git a/src/FluentAssertions.Analyzers/Tips/Dictionaries/DictionaryShouldContainValue.cs b/src/FluentAssertions.Analyzers/Tips/Dictionaries/DictionaryShouldContainValue.cs index 30765934..11a4545f 100644 --- a/src/FluentAssertions.Analyzers/Tips/Dictionaries/DictionaryShouldContainValue.cs +++ b/src/FluentAssertions.Analyzers/Tips/Dictionaries/DictionaryShouldContainValue.cs @@ -14,7 +14,7 @@ public class DictionaryShouldContainValueAnalyzer : FluentAssertionsAnalyzer public const string DiagnosticId = Constants.Tips.Dictionaries.DictionaryShouldContainValue; public const string Category = Constants.Tips.Category; - public const string Message = "Use {0} .Should() followed by .ContainValue() instead."; + public const string Message = "Use .Should().ContainValue() instead."; protected override DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true); protected override IEnumerable Visitors @@ -27,7 +27,7 @@ protected override IEnumerable Visitors private class ContainsValueShouldBeTrue : FluentAssertionsCSharpSyntaxVisitor { - public ContainsValueShouldBeTrue() : base("ContainsValue", "Should", "BeTrue") + public ContainsValueShouldBeTrue() : base(new MemberValidator("ContainsValue"), MemberValidator.Should, new MemberValidator("BeTrue")) { } } diff --git a/src/FluentAssertions.Analyzers/Tips/Dictionaries/DictionaryShouldNotContainKey.cs b/src/FluentAssertions.Analyzers/Tips/Dictionaries/DictionaryShouldNotContainKey.cs index e8835fd6..1631c25a 100644 --- a/src/FluentAssertions.Analyzers/Tips/Dictionaries/DictionaryShouldNotContainKey.cs +++ b/src/FluentAssertions.Analyzers/Tips/Dictionaries/DictionaryShouldNotContainKey.cs @@ -14,7 +14,7 @@ public class DictionaryShouldNotContainKeyAnalyzer : FluentAssertionsAnalyzer public const string DiagnosticId = Constants.Tips.Dictionaries.DictionaryShouldNotContainKey; public const string Category = Constants.Tips.Category; - public const string Message = "Use {0} .Should() followed by .NotContainKey() instead."; + public const string Message = "Use .Should().NotContainKey() instead."; protected override DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true); protected override IEnumerable Visitors @@ -27,7 +27,7 @@ protected override IEnumerable Visitors private class ContainsKeyShouldBeFalseSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor { - public ContainsKeyShouldBeFalseSyntaxVisitor() : base("ContainsKey", "Should", "BeFalse") + public ContainsKeyShouldBeFalseSyntaxVisitor() : base(new MemberValidator("ContainsKey"), MemberValidator.Should, new MemberValidator("BeFalse")) { } } diff --git a/src/FluentAssertions.Analyzers/Tips/Dictionaries/DictionaryShouldNotContainValue.cs b/src/FluentAssertions.Analyzers/Tips/Dictionaries/DictionaryShouldNotContainValue.cs index ca8de647..56303070 100644 --- a/src/FluentAssertions.Analyzers/Tips/Dictionaries/DictionaryShouldNotContainValue.cs +++ b/src/FluentAssertions.Analyzers/Tips/Dictionaries/DictionaryShouldNotContainValue.cs @@ -14,7 +14,7 @@ public class DictionaryShouldNotContainValueAnalyzer : FluentAssertionsAnalyzer public const string DiagnosticId = Constants.Tips.Dictionaries.DictionaryShouldNotContainValue; public const string Category = Constants.Tips.Category; - public const string Message = "Use {0} .Should() followed by .NotContainValue() instead."; + public const string Message = "Use .Should().NotContainValue() instead."; protected override DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true); protected override IEnumerable Visitors @@ -27,7 +27,7 @@ protected override IEnumerable Visitors private class ContainsValueShouldBeFalse : FluentAssertionsCSharpSyntaxVisitor { - public ContainsValueShouldBeFalse() : base("ContainsValue", "Should", "BeFalse") + public ContainsValueShouldBeFalse() : base(new MemberValidator("ContainsValue"), MemberValidator.Should, new MemberValidator("BeFalse")) { } } diff --git a/src/FluentAssertions.Analyzers/Utilities/FluentAssertionsAnalyzer.cs b/src/FluentAssertions.Analyzers/Utilities/FluentAssertionsAnalyzer.cs index 0d0d949b..5338820c 100644 --- a/src/FluentAssertions.Analyzers/Utilities/FluentAssertionsAnalyzer.cs +++ b/src/FluentAssertions.Analyzers/Utilities/FluentAssertionsAnalyzer.cs @@ -27,8 +27,8 @@ private void AnalyzeCodeBlock(CodeBlockAnalysisContext context) { var method = context.CodeBlock as MethodDeclarationSyntax; if (method == null) return; - - if(method.Body != null) + + if (method.Body != null) { foreach (var statement in method.Body.Statements.OfType()) { @@ -71,8 +71,7 @@ protected virtual Diagnostic CreateDiagnostic(TCSharpSyntaxVisitor visitor, Expr return Diagnostic.Create( descriptor: Rule, location: expression.GetLocation(), - properties: properties, - messageArgs: visitor.VariableName); + properties: properties); } } diff --git a/src/FluentAssertions.Analyzers/Utilities/FluentAssertionsCSharpSyntaxVisitor.cs b/src/FluentAssertions.Analyzers/Utilities/FluentAssertionsCSharpSyntaxVisitor.cs index bde1d91f..a44db069 100644 --- a/src/FluentAssertions.Analyzers/Utilities/FluentAssertionsCSharpSyntaxVisitor.cs +++ b/src/FluentAssertions.Analyzers/Utilities/FluentAssertionsCSharpSyntaxVisitor.cs @@ -1,112 +1,103 @@ -using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using System.Collections.Generic; +using System; using System.Collections.Immutable; +using System.Linq; namespace FluentAssertions.Analyzers { - public abstract class FluentAssertionsCSharpSyntaxVisitor : CSharpSyntaxVisitor + public class FluentAssertionsCSharpSyntaxVisitor : CSharpSyntaxVisitor { - protected string CurrentMethod => VisitedMethods.Count > 0 ? VisitedMethods.Peek() : null; + public string VariableName { get; private set; } + public ImmutableStack AllMembers { get; } + public ImmutableStack Members { get; private set; } - protected Stack AllVisitedMethods { get; } = new Stack(); - protected Stack VisitedMethods { get; } = new Stack(); + public virtual bool IsValid => Members.IsEmpty; - /// - /// The order in the syntax tree is reversed - /// - protected Stack RequiredMethods { get; } + public virtual ImmutableDictionary ToDiagnosticProperties() => ImmutableDictionary.Empty + .Add(Constants.DiagnosticProperties.VisitorName, GetType().Name) + .ToImmutableDictionary(); - public string VariableName { get; protected set; } - - public virtual bool IsValid => VariableName != null && RequiredMethods.Count == 0; - - protected FluentAssertionsCSharpSyntaxVisitor(params string[] requiredMethods) + public FluentAssertionsCSharpSyntaxVisitor(params MemberValidator[] members) { - RequiredMethods = new Stack(requiredMethods); + AllMembers = ImmutableStack.Create(members); + Members = AllMembers; } - public virtual ImmutableDictionary ToDiagnosticProperties() => new Dictionary - { - [Constants.DiagnosticProperties.VariableName] = VariableName, - [Constants.DiagnosticProperties.VisitorName] = GetType().Name - }.ToImmutableDictionary(); - - public override void VisitInvocationExpression(InvocationExpressionSyntax node) + public override void VisitMemberAccessExpression(MemberAccessExpressionSyntax node) { - Visit(node.Expression); - Visit(node.ArgumentList); - - if (VisitedMethods.Count > 0) VisitedMethods.Pop(); - } + var name = node.Name.Identifier.Text; - public override void VisitArgumentList(ArgumentListSyntax node) - { - foreach (var argument in node.Arguments) + if (Members.IsEmpty) { - Visit(argument); + // no op } - } - - public sealed override void VisitExpressionStatement(ExpressionStatementSyntax node) => Visit(node.Expression); - - public sealed override void VisitMemberAccessExpression(MemberAccessExpressionSyntax node) - { - var methodName = node.Name.Identifier.Text; - - VisitMethod(methodName); - - Visit(node.Expression); - - if (node.Parent is MemberAccessExpressionSyntax && VisitedMethods.Count > 0) + else if(name == "And" ) + { + if (Members.Peek().Name == "And") + { + Members = Members.Pop(); + } + } + else if (node.Parent is InvocationExpressionSyntax invocation) { - VisitedMethods.Pop(); + var member = Members.Peek(); + if (member.Name == name && member.AreArgumentsValid(invocation.ArgumentList.Arguments)) + { + Members = Members.Pop(); + } } + else + { + Members = AllMembers; + } + + Visit(node.Expression); } - public sealed override void VisitElementAccessExpression(ElementAccessExpressionSyntax node) + public override void VisitElementAccessExpression(ElementAccessExpressionSyntax node) { - const string methodName = "[]"; + const string name = "[]"; - VisitMethod(methodName); + var member = Members.Peek(); + if (Members.IsEmpty) + { + // no op + } + else if (member.Name == name && member.AreArgumentsValid(node.ArgumentList.Arguments)) + { + Members = Members.Pop(); + } + else + { + Members = AllMembers; + } Visit(node.Expression); - Visit(node.ArgumentList); - - VisitedMethods.Pop(); } - public sealed override void VisitIdentifierName(IdentifierNameSyntax node) + public override void VisitInvocationExpression(InvocationExpressionSyntax node) { - if (RequiredMethods.Count == 0) - { - VariableName = node.Identifier.Text; - } + Visit(node.Expression); } - private void VisitMethod(string methodName) + public override void VisitIdentifierName(IdentifierNameSyntax node) { - AllVisitedMethods.Push(methodName); - VisitedMethods.Push(methodName); - if (RequiredMethods.Count > 0 && methodName.Equals(RequiredMethods.Peek())) - { - RequiredMethods.Pop(); - } + VariableName = node.Identifier.Text; } /* private int _indent = 0; - public override void Visit(Microsoft.CodeAnalysis.SyntaxNode node) + public override void Visit(SyntaxNode node) { _indent++; var indent = new string(' ', _indent * 2); - if (this.GetType().Name == "ShouldContainKeyAndContainValueSyntaxVisitor") - System.Console.WriteLine($"{indent}{CurrentMethod ?? ""}: {node.GetType().Name}"); + Console.WriteLine($"{indent}{node.GetType().Name}"); base.Visit(node); --_indent; } */ - } } diff --git a/src/FluentAssertions.Analyzers/Utilities/FluentAssertionsWithArgumentCSharpSyntaxVisitor.cs b/src/FluentAssertions.Analyzers/Utilities/FluentAssertionsWithArgumentCSharpSyntaxVisitor.cs deleted file mode 100644 index 0ed7c52f..00000000 --- a/src/FluentAssertions.Analyzers/Utilities/FluentAssertionsWithArgumentCSharpSyntaxVisitor.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Collections.Immutable; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -namespace FluentAssertions.Analyzers -{ - public abstract class FluentAssertionsWithArgumentCSharpSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor - { - protected abstract string MethodContainingArgument { get; } - protected ExpressionSyntax Argument { get; set; } - - protected virtual ExpressionSyntax ModifyArgument(ExpressionSyntax expression) => expression; - - protected FluentAssertionsWithArgumentCSharpSyntaxVisitor(params string[] requiredMethods) : base(requiredMethods) - { - } - - public override ImmutableDictionary ToDiagnosticProperties() - => base.ToDiagnosticProperties().Add(Constants.DiagnosticProperties.ArgumentString, Argument.ToFullString()); - - public override bool IsValid => base.IsValid && Argument != null; - - public override void VisitArgumentList(ArgumentListSyntax node) - { - if (!node.Arguments.Any()) return; - if (CurrentMethod != MethodContainingArgument) return; - - Argument = ModifyArgument(node.Arguments[0].Expression); - } - } -} diff --git a/src/FluentAssertions.Analyzers/Utilities/FluentAssertionsWithArgumentsCSharpSyntaxVisitor.cs b/src/FluentAssertions.Analyzers/Utilities/FluentAssertionsWithArgumentsCSharpSyntaxVisitor.cs deleted file mode 100644 index ce5ae5d2..00000000 --- a/src/FluentAssertions.Analyzers/Utilities/FluentAssertionsWithArgumentsCSharpSyntaxVisitor.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using System.Collections.Generic; - -namespace FluentAssertions.Analyzers -{ - public abstract class FluentAssertionsWithArgumentsCSharpSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor - { - protected Dictionary<(string Method, int Index), ExpressionSyntax> Arguments { get; } = new Dictionary<(string Method, int Index), ExpressionSyntax>(); - - - private List<(string Method, SeparatedSyntaxList Arguments)> MethodArguments { get; } = new List<(string Method, SeparatedSyntaxList Arguments)>(); - protected string a; - - public override bool IsValid => base.IsValid && AreArgumentsValid(); - - protected FluentAssertionsWithArgumentsCSharpSyntaxVisitor(params string[] requiredMethods) : base(requiredMethods) - { - } - - protected abstract bool AreArgumentsValid(); - - public override void VisitArgumentList(ArgumentListSyntax node) - { - VisitArguments(node.Arguments); - } - public override void VisitBracketedArgumentList(BracketedArgumentListSyntax node) - { - VisitArguments(node.Arguments); - } - - private void VisitArguments(SeparatedSyntaxList arguments) - { - if (arguments.Any()) - { - MethodArguments.Add((CurrentMethod, arguments)); - for (int i = 0; i < arguments.Count; i++) - { - Arguments.Add((CurrentMethod, i), arguments[i].Expression); - } - } - } - } -} diff --git a/src/FluentAssertions.Analyzers/Utilities/FluentAssertionsWithLambdaArgumentCSharpSyntaxVisitor.cs b/src/FluentAssertions.Analyzers/Utilities/FluentAssertionsWithLambdaArgumentCSharpSyntaxVisitor.cs deleted file mode 100644 index 178efafc..00000000 --- a/src/FluentAssertions.Analyzers/Utilities/FluentAssertionsWithLambdaArgumentCSharpSyntaxVisitor.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Collections.Immutable; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -namespace FluentAssertions.Analyzers -{ - - public abstract class FluentAssertionsWithLambdaArgumentCSharpSyntaxVisitor : FluentAssertionsWithArgumentsCSharpSyntaxVisitor - { - public virtual SimpleLambdaExpressionSyntax Lambda => Arguments[(MethodContainingLambda, 0)] as SimpleLambdaExpressionSyntax; - - protected abstract string MethodContainingLambda { get; } - - public override bool IsValid => base.IsValid && Lambda != null; - protected override bool AreArgumentsValid() => Arguments.TryGetValue((MethodContainingLambda, 0), out var lambda) && lambda is SimpleLambdaExpressionSyntax; - - protected FluentAssertionsWithLambdaArgumentCSharpSyntaxVisitor(params string[] requiredMethods) : base(requiredMethods) - { - } - - public override ImmutableDictionary ToDiagnosticProperties() - => base.ToDiagnosticProperties().Add(Constants.DiagnosticProperties.LambdaString, Lambda.ToFullString()); - } -} \ No newline at end of file diff --git a/src/FluentAssertions.Analyzers/Utilities/FluentAssertionsWithoutLambdaArgumentCSharpSyntaxVisitor.cs b/src/FluentAssertions.Analyzers/Utilities/FluentAssertionsWithoutLambdaArgumentCSharpSyntaxVisitor.cs deleted file mode 100644 index fe990d2c..00000000 --- a/src/FluentAssertions.Analyzers/Utilities/FluentAssertionsWithoutLambdaArgumentCSharpSyntaxVisitor.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Microsoft.CodeAnalysis.CSharp.Syntax; - -namespace FluentAssertions.Analyzers -{ - public abstract class FluentAssertionsWithoutLambdaArgumentCSharpSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor - { - private bool _methodHasLambdaArgument; - - public override bool IsValid => base.IsValid && !_methodHasLambdaArgument; - protected abstract string MathodNotContainingLambda { get; } - - protected FluentAssertionsWithoutLambdaArgumentCSharpSyntaxVisitor(params string[] requiredMethods) : base(requiredMethods) - { - } - - public override void VisitArgumentList(ArgumentListSyntax node) - { - if (!node.Arguments.Any()) return; - if (CurrentMethod != MathodNotContainingLambda) return; - - _methodHasLambdaArgument = node.Arguments[0].Expression is SimpleLambdaExpressionSyntax; - } - } -} diff --git a/src/FluentAssertions.Analyzers/Utilities/MemberValidator.cs b/src/FluentAssertions.Analyzers/Utilities/MemberValidator.cs new file mode 100644 index 00000000..5c13b950 --- /dev/null +++ b/src/FluentAssertions.Analyzers/Utilities/MemberValidator.cs @@ -0,0 +1,82 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System; + +namespace FluentAssertions.Analyzers +{ + public class MemberValidator + { + public string Name { get; } + public Func, bool> AreArgumentsValid { get; } + + public MemberValidator(string name) + { + Name = name; + AreArgumentsValid = _ => true; + } + + public MemberValidator(string name, Func, bool> argumentsPredicate) + { + Name = name; + AreArgumentsValid = argumentsPredicate; + } + + public static MemberValidator And { get; } = new MemberValidator(nameof(And)); + public static MemberValidator Which { get; } = new MemberValidator(nameof(Which)); + public static MemberValidator Should { get; } = new MemberValidator(nameof(Should)); + + public static MemberValidator MathodNotContainingLambda(string name) => new MemberValidator(name, MethodNotContainingLambdaPredicate); + public static MemberValidator MathodContainingLambda(string name) => new MemberValidator(name, MethodContainingLambdaPredicate); + public static MemberValidator ArgumentIsVariable(string name) => new MemberValidator(name, ArgumentIsVariablePredicate); + public static MemberValidator ArgumentIsLiteral(string name, T value) => new MemberValidator(name, arguments => ArgumentIsLiteralPredicate(arguments, value)); + public static MemberValidator ArgumentIsIdentifierOrLiteral(string name) => new MemberValidator(name, ArgumentIsIdentifierOrLiteralPredicate); + public static MemberValidator HasArguments(string name) => new MemberValidator(name, arguments => arguments.Any()); + public static MemberValidator HasNoArguments(string name) => new MemberValidator(name, arguments => !arguments.Any()); + + public static bool MethodNotContainingLambdaPredicate(SeparatedSyntaxList arguments) + { + if (!arguments.Any()) return true; + + return !(arguments.First().Expression is LambdaExpressionSyntax); + } + public static bool MethodContainingLambdaPredicate(SeparatedSyntaxList arguments) + { + if (!arguments.Any()) return false; + + return arguments.First().Expression is LambdaExpressionSyntax; + } + public static bool ArgumentIsVariablePredicate(SeparatedSyntaxList arguments) + { + if (!arguments.Any()) return false; + + return ArgumentIsVariablePredicate(arguments.First()); + } + public static bool ArgumentIsVariablePredicate(ArgumentSyntax argument) + { + if (argument.Expression is IdentifierNameSyntax identifier) + { + var argumentName = identifier.Identifier.Text; + + var variableName = VariableNameExtractor.ExtractVariabeName(argument); + + return variableName == argumentName; + } + return false; + } + public static bool ArgumentIsLiteralPredicate(SeparatedSyntaxList arguments, T value) + { + return arguments.Any() + && arguments.First().Expression is LiteralExpressionSyntax literal + && literal.Token.Value is T argument + && argument.Equals(value); + } + public static bool ArgumentIsIdentifierOrLiteralPredicate(SeparatedSyntaxList arguments) + { + if (!arguments.Any()) return false; + + var argumentsExpression = arguments.First().Expression; + return argumentsExpression is IdentifierNameSyntax || argumentsExpression is LiteralExpressionSyntax; + } + + } +} diff --git a/src/FluentAssertions.Analyzers/Utilities/RetrieveIdentifierNameSyntaxVisitor.cs b/src/FluentAssertions.Analyzers/Utilities/RetrieveIdentifierNameSyntaxVisitor.cs deleted file mode 100644 index 7ef0d516..00000000 --- a/src/FluentAssertions.Analyzers/Utilities/RetrieveIdentifierNameSyntaxVisitor.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -namespace FluentAssertions.Analyzers -{ - public class RetrieveIdentifierNameSyntaxVisitor : CSharpSyntaxVisitor - { - public override string VisitInvocationExpression(InvocationExpressionSyntax node) => base.Visit(node.Expression); - public override string VisitExpressionStatement(ExpressionStatementSyntax node) => base.Visit(node.Expression); - public override string VisitMemberAccessExpression(MemberAccessExpressionSyntax node) => base.Visit(node.Expression); - public override string VisitIdentifierName(IdentifierNameSyntax node) => node.Identifier.Text; - } -} diff --git a/src/FluentAssertions.Analyzers/Utilities/VariableNameExtractor.cs b/src/FluentAssertions.Analyzers/Utilities/VariableNameExtractor.cs new file mode 100644 index 00000000..60c86cb7 --- /dev/null +++ b/src/FluentAssertions.Analyzers/Utilities/VariableNameExtractor.cs @@ -0,0 +1,41 @@ +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis; + +namespace FluentAssertions.Analyzers +{ + public class VariableNameExtractor : CSharpSyntaxWalker + { + public string VariableName { get; private set; } + + public override void VisitIdentifierName(IdentifierNameSyntax node) + { + VariableName = node.Identifier.Text; + } + + public override void Visit(SyntaxNode node) + { + // the first identifier encountered will be the one at the bottom of the syntax tree + if (VariableName == null) + { + base.Visit(node); + } + } + + public static string ExtractVariabeName(ArgumentSyntax argument) + { + if (argument.Parent is ArgumentListSyntax argumentList && argumentList.Parent is InvocationExpressionSyntax invocation) + { + return ExtractVariabeName(invocation); + } + return null; + } + public static string ExtractVariabeName(InvocationExpressionSyntax invocation) + { + var variableExtractor = new VariableNameExtractor(); + invocation.Accept(variableExtractor); + + return variableExtractor.VariableName; + } + } +}