Skip to content

Commit 96ebd1f

Browse files
authored
Merge pull request #6 from Meir017/feature/replacing-part-of-assertion
Feature/replacing part of assertion
2 parents e9e52b4 + d37574b commit 96ebd1f

34 files changed

+845
-116
lines changed

src/FluentAssertions.BestPractices.Tests/TestAttributes.cs

+14-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using Microsoft.VisualStudio.TestTools.UnitTesting;
22
using System;
33
using System.Collections.Generic;
4+
using System.Linq;
45

56
namespace FluentAssertions.BestPractices.Tests
67
{
@@ -21,21 +22,28 @@ public class AssertionDiagnosticAttribute : Attribute
2122
{
2223
public string Assertion { get; }
2324

24-
public AssertionDiagnosticAttribute(string assertion)
25+
public bool Ignore { get; }
26+
27+
public AssertionDiagnosticAttribute(string assertion, bool ignore = false)
2528
{
2629
Assertion = assertion;
30+
Ignore = ignore;
2731
}
2832
}
33+
2934
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
3035
public class AssertionCodeFixAttribute : Attribute
3136
{
3237
public string OldAssertion { get; }
3338
public string NewAssertion { get; }
3439

35-
public AssertionCodeFixAttribute(string oldAssertion, string newAssertion)
40+
public bool Ignore { get; }
41+
42+
public AssertionCodeFixAttribute(string oldAssertion, string newAssertion, bool ignore = false)
3643
{
3744
OldAssertion = oldAssertion;
3845
NewAssertion = newAssertion;
46+
Ignore = ignore;
3947
}
4048
}
4149

@@ -52,19 +60,19 @@ public override TestResult[] Execute(ITestMethod testMethod)
5260
var codeFixAttributes = testMethod.GetAttributes<AssertionCodeFixAttribute>(false);
5361

5462
var results = new List<TestResult>();
55-
for (int i = 0; i < diagnosticAttributes.Length; i++)
63+
foreach (var diagnosticAttribute in diagnosticAttributes.Where(attribute => !attribute.Ignore))
5664
{
57-
foreach (var assertion in GetTestCases(diagnosticAttributes[i]))
65+
foreach (var assertion in GetTestCases(diagnosticAttribute))
5866
{
5967
var result = testMethod.Invoke(new[] { assertion });
6068
result.DisplayName = assertion;
6169

6270
results.Add(result);
6371
}
6472
}
65-
for (int i = 0; i < codeFixAttributes.Length; i++)
73+
foreach (var codeFixAttribute in codeFixAttributes.Where(attribute => !attribute.Ignore))
6674
{
67-
foreach (var (oldAssertion, newAssertion) in GetTestCases(codeFixAttributes[i]))
75+
foreach (var (oldAssertion, newAssertion) in GetTestCases(codeFixAttribute))
6876
{
6977
var result = testMethod.Invoke(new[] { oldAssertion, newAssertion });
7078
result.DisplayName = $"{Environment.NewLine}old: \"{oldAssertion}\" {Environment.NewLine}new: \"{newAssertion}\"";

src/FluentAssertions.BestPractices.Tests/Tips/CollectionTests.cs

+195-16
Large diffs are not rendered by default.

src/FluentAssertions.BestPractices/Constants.cs

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ public static class DiagnosticProperties
1111
public const string UnexpectedItemString = nameof(UnexpectedItemString);
1212
public const string BecauseArgumentsString = nameof(BecauseArgumentsString);
1313
public const string ArgumentString = nameof(ArgumentString);
14+
15+
public const string VisitorName = nameof(VisitorName);
1416
}
1517

1618
public static class Tips

src/FluentAssertions.BestPractices/FluentAssertions.BestPractices.nuspec

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<metadata>
44
<id>FluentAssertions.BestPractices</id>
55
<title>Fluent Assertions Best Practice</title>
6-
<version>0.3.0</version>
6+
<version>0.4.0</version>
77
<owners>Meir Blachman</owners>
88
<authors>Meir Blachman</authors>
99
<summary>

src/FluentAssertions.BestPractices/Tips/Collections/CollectionShouldBeEmpty.cs

+31-4
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,15 @@ public class CollectionShouldBeEmptyAnalyzer : FluentAssertionsAnalyzer
2727
}
2828
}
2929

30-
private class AnyShouldBeFalseSyntaxVisitor : FluentAssertionsWithoutLambdaArgumentCSharpSyntaxVisitor
30+
public class AnyShouldBeFalseSyntaxVisitor : FluentAssertionsWithoutLambdaArgumentCSharpSyntaxVisitor
3131
{
3232
protected override string MathodNotContainingLambda => "Any";
3333

3434
public AnyShouldBeFalseSyntaxVisitor() : base("Any", "Should", "BeFalse")
3535
{
3636
}
3737
}
38-
private class ShouldHaveCount0SyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor
38+
public class ShouldHaveCount0SyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor
3939
{
4040
private bool _haveCountMethodHas0Argument;
4141

@@ -62,8 +62,35 @@ node.Arguments[0].Expression is LiteralExpressionSyntax literal
6262
public class CollectionShouldBeEmptyCodeFix : FluentAssertionsCodeFixProvider
6363
{
6464
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(CollectionShouldBeEmptyAnalyzer.DiagnosticId);
65+
66+
protected override StatementSyntax GetNewStatement(ExpressionStatementSyntax statement, FluentAssertionsDiagnosticProperties properties)
67+
{
68+
switch (properties.VisitorName)
69+
{
70+
case nameof(CollectionShouldBeEmptyAnalyzer.AnyShouldBeFalseSyntaxVisitor):
71+
return GetNewStatement(statement, NodeReplacement.Remove("Any"), NodeReplacement.Rename("BeFalse", "BeEmpty"));
72+
case nameof(CollectionShouldBeEmptyAnalyzer.ShouldHaveCount0SyntaxVisitor):
73+
return GetNewStatement(statement, new HaveCountNodeReplacement());
74+
default:
75+
throw new System.InvalidOperationException($"Invalid visitor name - {properties.VisitorName}");
76+
}
77+
}
6578

66-
protected override StatementSyntax GetNewStatement(FluentAssertionsDiagnosticProperties properties)
67-
=> SyntaxFactory.ParseStatement($"{properties.VariableName}.Should().BeEmpty({properties.BecauseArgumentsString});");
79+
private class HaveCountNodeReplacement : NodeReplacement
80+
{
81+
public override bool IsValidNode(MemberAccessExpressionSyntax node) => node.Name.Identifier.Text == "HaveCount";
82+
public override SyntaxNode ComputeOld(LinkedListNode<MemberAccessExpressionSyntax> listNode) => listNode.Value.Parent;
83+
public override SyntaxNode ComputeNew(LinkedListNode<MemberAccessExpressionSyntax> listNode)
84+
{
85+
var invocation = (InvocationExpressionSyntax)listNode.Value.Parent;
86+
87+
invocation = invocation.ReplaceNode(listNode.Value, listNode.Value.WithName(SyntaxFactory.IdentifierName("BeEmpty")));
88+
89+
// remove the 0 argument
90+
var arguments = invocation.ArgumentList.Arguments.RemoveAt(0);
91+
92+
return invocation.WithArgumentList(invocation.ArgumentList.WithArguments(arguments));
93+
}
94+
}
6895
}
6996
}

src/FluentAssertions.BestPractices/Tips/Collections/CollectionShouldBeInAscendingOrder.cs

+9-2
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,15 @@ public override void VisitArgumentList(ArgumentListSyntax node)
5555
public class CollectionShouldBeInAscendingOrderCodeFix : FluentAssertionsCodeFixProvider
5656
{
5757
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(CollectionShouldBeInAscendingOrderAnalyzer.DiagnosticId);
58+
59+
protected override StatementSyntax GetNewStatement(ExpressionStatementSyntax statement, FluentAssertionsDiagnosticProperties properties)
60+
{
61+
var remove = NodeReplacement.RemoveAndExtractArguments("OrderBy");
62+
var newStatement = GetNewStatement(statement, remove);
63+
64+
newStatement = GetNewStatement(newStatement, NodeReplacement.RenameAndRemoveFirstArgument("Equal", "BeInAscendingOrder"));
5865

59-
protected override StatementSyntax GetNewStatement(FluentAssertionsDiagnosticProperties properties)
60-
=> SyntaxFactory.ParseStatement($"{properties.VariableName}.Should().BeInAscendingOrder({properties.CombineWithBecauseArgumentsString(properties.LambdaString)});");
66+
return GetNewStatement(newStatement, NodeReplacement.PrependArguments("BeInAscendingOrder", remove.Arguments));
67+
}
6168
}
6269
}

src/FluentAssertions.BestPractices/Tips/Collections/CollectionShouldBeInDescendingOrder.cs

+9-2
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,15 @@ public override void VisitArgumentList(ArgumentListSyntax node)
5555
public class CollectionShouldBeInDescendingOrderCodeFix : FluentAssertionsCodeFixProvider
5656
{
5757
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(CollectionShouldBeInDescendingOrderAnalyzer.DiagnosticId);
58+
59+
protected override StatementSyntax GetNewStatement(ExpressionStatementSyntax statement, FluentAssertionsDiagnosticProperties properties)
60+
{
61+
var remove = NodeReplacement.RemoveAndExtractArguments("OrderByDescending");
62+
var newStatement = GetNewStatement(statement, remove);
63+
64+
newStatement = GetNewStatement(newStatement, NodeReplacement.RenameAndRemoveFirstArgument("Equal", "BeInDescendingOrder"));
5865

59-
protected override StatementSyntax GetNewStatement(FluentAssertionsDiagnosticProperties properties)
60-
=> SyntaxFactory.ParseStatement($"{properties.VariableName}.Should().BeInDescendingOrder({properties.CombineWithBecauseArgumentsString(properties.LambdaString)});");
66+
return GetNewStatement(newStatement, NodeReplacement.PrependArguments("BeInDescendingOrder", remove.Arguments));
67+
}
6168
}
6269
}

src/FluentAssertions.BestPractices/Tips/Collections/CollectionShouldContainItem.cs

+7-3
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,13 @@ public override void VisitArgumentList(ArgumentListSyntax node)
5353
public class CollectionShouldContainItemCodeFix : FluentAssertionsCodeFixProvider
5454
{
5555
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(CollectionShouldContainItemAnalyzer.DiagnosticId);
56+
57+
protected override StatementSyntax GetNewStatement(ExpressionStatementSyntax statement, FluentAssertionsDiagnosticProperties properties)
58+
{
59+
var remove = new NodeReplacement.RemoveAndExtractArgumentsNodeReplacement("Contains");
60+
var newStatement = GetNewStatement(statement, remove);
5661

57-
// actual.Should().Contain(expectedItem);
58-
protected override StatementSyntax GetNewStatement(FluentAssertionsDiagnosticProperties properties)
59-
=> SyntaxFactory.ParseStatement($"{properties.VariableName}.Should().Contain({properties.CombineWithBecauseArgumentsString(properties.ExpectedItemString)});");
62+
return GetNewStatement(newStatement, new NodeReplacement.RenameAndPrependArgumentsNodeReplacement("BeTrue", "Contain", remove.Arguments));
63+
}
6064
}
6165
}

src/FluentAssertions.BestPractices/Tips/Collections/CollectionShouldContainProperty.cs

+20-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using Microsoft.CodeAnalysis;
22
using Microsoft.CodeAnalysis.CodeFixes;
3-
using Microsoft.CodeAnalysis.CSharp;
43
using Microsoft.CodeAnalysis.CSharp.Syntax;
54
using Microsoft.CodeAnalysis.Diagnostics;
65
using System.Collections.Generic;
@@ -27,14 +26,14 @@ public class CollectionShouldContainPropertyAnalyzer : FluentAssertionsAnalyzer
2726
}
2827
}
2928

30-
private class AnyShouldBeTrueSyntaxVisitor : FluentAssertionsWithLambdaArgumentCSharpSyntaxVisitor
29+
public class AnyShouldBeTrueSyntaxVisitor : FluentAssertionsWithLambdaArgumentCSharpSyntaxVisitor
3130
{
3231
protected override string MethodContainingLambda => "Any";
3332
public AnyShouldBeTrueSyntaxVisitor() : base("Any", "Should", "BeTrue")
3433
{
3534
}
3635
}
37-
private class WhereShouldNotBeEmptySyntaxVisitor : FluentAssertionsWithLambdaArgumentCSharpSyntaxVisitor
36+
public class WhereShouldNotBeEmptySyntaxVisitor : FluentAssertionsWithLambdaArgumentCSharpSyntaxVisitor
3837
{
3938
protected override string MethodContainingLambda => "Where";
4039
public WhereShouldNotBeEmptySyntaxVisitor() : base("Where", "Should", "NotBeEmpty")
@@ -47,8 +46,24 @@ public WhereShouldNotBeEmptySyntaxVisitor() : base("Where", "Should", "NotBeEmpt
4746
public class CollectionShouldContainPropertyCodeFix : FluentAssertionsCodeFixProvider
4847
{
4948
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(CollectionShouldContainPropertyAnalyzer.DiagnosticId);
49+
50+
protected override StatementSyntax GetNewStatement(ExpressionStatementSyntax statement, FluentAssertionsDiagnosticProperties properties)
51+
{
52+
if (properties.VisitorName == nameof(CollectionShouldContainPropertyAnalyzer.AnyShouldBeTrueSyntaxVisitor))
53+
{
54+
var remove = new NodeReplacement.RemoveAndExtractArgumentsNodeReplacement("Any");
55+
var newStatement = GetNewStatement(statement, remove);
5056

51-
protected override StatementSyntax GetNewStatement(FluentAssertionsDiagnosticProperties properties)
52-
=> SyntaxFactory.ParseStatement($"{properties.VariableName}.Should().Contain({properties.CombineWithBecauseArgumentsString(properties.LambdaString)});");
57+
return GetNewStatement(newStatement, new NodeReplacement.RenameAndPrependArgumentsNodeReplacement("BeTrue", "Contain", remove.Arguments));
58+
}
59+
else if (properties.VisitorName == nameof(CollectionShouldContainPropertyAnalyzer.WhereShouldNotBeEmptySyntaxVisitor))
60+
{
61+
var remove = new NodeReplacement.RemoveAndExtractArgumentsNodeReplacement("Where");
62+
var newStatement = GetNewStatement(statement, remove);
63+
64+
return GetNewStatement(newStatement, new NodeReplacement.RenameAndPrependArgumentsNodeReplacement("NotBeEmpty", "Contain", remove.Arguments));
65+
}
66+
throw new System.InvalidOperationException($"Invalid visitor name - {properties.VisitorName}");
67+
}
5368
}
5469
}

src/FluentAssertions.BestPractices/Tips/Collections/CollectionShouldContainSingle.cs

+19-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using Microsoft.CodeAnalysis;
22
using Microsoft.CodeAnalysis.CodeFixes;
3-
using Microsoft.CodeAnalysis.CSharp;
43
using Microsoft.CodeAnalysis.CSharp.Syntax;
54
using Microsoft.CodeAnalysis.Diagnostics;
65
using System.Collections.Generic;
@@ -26,8 +25,8 @@ public class CollectionShouldContainSingleAnalyzer : FluentAssertionsAnalyzer
2625
yield return (new ShouldHaveCount1SyntaxVisitor(), new BecauseArgumentsSyntaxVisitor("HaveCount", 1));
2726
}
2827
}
29-
30-
private class WhereShouldHaveCount1SyntaxVisitor : FluentAssertionsWithLambdaArgumentCSharpSyntaxVisitor
28+
29+
public class WhereShouldHaveCount1SyntaxVisitor : FluentAssertionsWithLambdaArgumentCSharpSyntaxVisitor
3130
{
3231
private bool _haveCountMethodHas1Argument;
3332

@@ -52,7 +51,7 @@ node.Arguments[0].Expression is LiteralExpressionSyntax literal
5251
&& argument == 1;
5352
}
5453
}
55-
private class ShouldHaveCount1SyntaxVisitor : FluentAssertionsWithArgumentCSharpSyntaxVisitor
54+
public class ShouldHaveCount1SyntaxVisitor : FluentAssertionsWithArgumentCSharpSyntaxVisitor
5655
{
5756
protected override string MethodContainingArgument => "HaveCount";
5857
public ShouldHaveCount1SyntaxVisitor() : base("Should", "HaveCount")
@@ -76,8 +75,22 @@ protected override ExpressionSyntax ModifyArgument(ExpressionSyntax expression)
7675
public class CollectionShouldContainSingleCodeFix : FluentAssertionsCodeFixProvider
7776
{
7877
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(CollectionShouldContainSingleAnalyzer.DiagnosticId);
78+
79+
protected override StatementSyntax GetNewStatement(ExpressionStatementSyntax statement, FluentAssertionsDiagnosticProperties properties)
80+
{
81+
var newStatement = GetNewStatement(statement, NodeReplacement.RenameAndRemoveFirstArgument("HaveCount", "ContainSingle"));
82+
if (properties.VisitorName == nameof(CollectionShouldContainSingleAnalyzer.ShouldHaveCount1SyntaxVisitor))
83+
{
84+
return newStatement;
85+
}
86+
else if (properties.VisitorName == nameof(CollectionShouldContainSingleAnalyzer.WhereShouldHaveCount1SyntaxVisitor))
87+
{
88+
var remove = NodeReplacement.RemoveAndExtractArguments("Where");
89+
newStatement = GetNewStatement(newStatement, remove);
7990

80-
protected override StatementSyntax GetNewStatement(FluentAssertionsDiagnosticProperties properties)
81-
=> SyntaxFactory.ParseStatement($"{properties.VariableName}.Should().ContainSingle({properties.CombineWithBecauseArgumentsString(properties.LambdaString)});");
91+
return GetNewStatement(newStatement, NodeReplacement.PrependArguments("ContainSingle", remove.Arguments));
92+
}
93+
throw new System.InvalidOperationException($"Invalid visitor name - {properties.VisitorName}");
94+
}
8295
}
8396
}

src/FluentAssertions.BestPractices/Tips/Collections/CollectionShouldEqualOtherCollectionByComparer.cs

+28-3
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ public override ImmutableDictionary<string, string> ToDiagnosticProperties() =>
6363
.Add(Constants.DiagnosticProperties.LambdaString, _lambdaArgument.ToFullString())
6464
.Add(Constants.DiagnosticProperties.ArgumentString, _otherVariable);
6565

66-
6766
private class SelectSyntaxVisitor : FluentAssertionsWithLambdaArgumentCSharpSyntaxVisitor
6867
{
6968
protected override string MethodContainingLambda => "Select";
@@ -78,8 +77,34 @@ public SelectSyntaxVisitor() : base("Select")
7877
public class CollectionShouldEqualOtherCollectionByComparerCodeFix : FluentAssertionsCodeFixProvider
7978
{
8079
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(CollectionShouldEqualOtherCollectionByComparerAnalyzer.DiagnosticId);
80+
81+
protected override StatementSyntax GetNewStatement(ExpressionStatementSyntax statement, FluentAssertionsDiagnosticProperties properties)
82+
{
83+
var removeMethodContainingFirstLambda = NodeReplacement.RemoveAndExtractArguments("Select");
84+
var newStatement = GetNewStatement(statement, removeMethodContainingFirstLambda);
85+
86+
var removeArgument = NodeReplacement.RemoveFirstArgument("Equal");
87+
newStatement = GetNewStatement(newStatement, removeArgument);
88+
89+
var argumentInvocation = (InvocationExpressionSyntax)removeArgument.Argument.Expression;
90+
var identifier = ((MemberAccessExpressionSyntax)argumentInvocation.Expression).Expression;
91+
92+
var firstLambda = (SimpleLambdaExpressionSyntax)removeMethodContainingFirstLambda.Arguments[0].Expression;
93+
var secondLambda = (SimpleLambdaExpressionSyntax)argumentInvocation.ArgumentList.Arguments[0].Expression;
94+
95+
var newArguments = SyntaxFactory.SeparatedList<ArgumentSyntax>()
96+
.Add(removeArgument.Argument.WithExpression(identifier))
97+
.Add(removeArgument.Argument.WithExpression(CombineLambdas(firstLambda, secondLambda).NormalizeWhitespace()
98+
));
99+
100+
return GetNewStatement(newStatement, NodeReplacement.PrependArguments("Equal", newArguments));
101+
}
81102

82-
protected override StatementSyntax GetNewStatement(FluentAssertionsDiagnosticProperties properties)
83-
=> SyntaxFactory.ParseStatement($"{properties.VariableName}.Should().Equal({properties.ArgumentString}, {properties.CombineWithBecauseArgumentsString(properties.LambdaString)});");
103+
private ParenthesizedLambdaExpressionSyntax CombineLambdas(SimpleLambdaExpressionSyntax left, SimpleLambdaExpressionSyntax right) => SyntaxFactory.ParenthesizedLambdaExpression(
104+
parameterList: SyntaxFactory.ParameterList().AddParameters(left.Parameter, right.Parameter),
105+
body: SyntaxFactory.BinaryExpression(SyntaxKind.EqualsExpression,
106+
left: (ExpressionSyntax)left.Body,
107+
right: (ExpressionSyntax)right.Body)
108+
);
84109
}
85110
}

src/FluentAssertions.BestPractices/Tips/Collections/CollectionShouldHaveCount.cs

+5-3
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,10 @@ public override void VisitArgumentList(ArgumentListSyntax node)
5353
public class CollectionShouldHaveCountCodeFix : FluentAssertionsCodeFixProvider
5454
{
5555
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(CollectionShouldHaveCountAnalyzer.DiagnosticId);
56-
57-
protected override StatementSyntax GetNewStatement(FluentAssertionsDiagnosticProperties properties)
58-
=> SyntaxFactory.ParseStatement($"{properties.VariableName}.Should().HaveCount({properties.CombineWithBecauseArgumentsString(properties.ArgumentString)});");
56+
57+
protected override StatementSyntax GetNewStatement(ExpressionStatementSyntax statement, FluentAssertionsDiagnosticProperties properties)
58+
{
59+
return GetNewStatement(statement, new NodeReplacement.RemoveNodeReplacement("Count"), new NodeReplacement.RenameNodeReplacement("Be", "HaveCount"));
60+
}
5961
}
6062
}

0 commit comments

Comments
 (0)