Skip to content

Commit e9e52b4

Browse files
committed
cleanups
1 parent 3974955 commit e9e52b4

9 files changed

+105
-131
lines changed

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

+7-14
Original file line numberDiff line numberDiff line change
@@ -208,18 +208,7 @@ public class CollectionTests
208208
[Implemented]
209209
[Ignore("Will be available in Fluent Assertions 5.0")]
210210
public void CollectionShouldNotHaveCount_TestCodeFix(string oldAssertion, string newAssertion) => VerifyCSharpFix<CollectionShouldNotHaveCountCodeFix, CollectionShouldNotHaveCountAnalyzer>(oldAssertion, newAssertion);
211-
212-
[AssertionDataTestMethod]
213-
[AssertionDiagnostic("actual.Should().HaveCount(1{0});")]
214-
[Implemented]
215-
public void CollectionShouldContainSingle_TestAnalyzer(string assertion) => VerifyCSharpDiagnostic<CollectionShouldContainSingleAnalyzer>(assertion);
216-
217-
[AssertionDataTestMethod]
218-
[AssertionCodeFix(oldAssertion: "actual.Should().HaveCount(1{0});",
219-
newAssertion: "actual.Should().ContainSingle({0});")]
220-
[Implemented]
221-
public void CollectionShouldContainSingle_TestCodeFix(string oldAssertion, string newAssertion) => VerifyCSharpFix<CollectionShouldContainSingleCodeFix, CollectionShouldContainSingleAnalyzer>(oldAssertion, newAssertion);
222-
211+
223212
[AssertionDataTestMethod]
224213
[AssertionDiagnostic("actual.Should().HaveCount(expected.Count(){0});")]
225214
[Implemented]
@@ -246,16 +235,20 @@ public class CollectionTests
246235
public void CollectionShouldNotHaveSameCount_TestCodeFix(string oldAssertion, string newAssertion) => VerifyCSharpFix<CollectionShouldNotHaveSameCountCodeFix, CollectionShouldNotHaveSameCountAnalyzer>(oldAssertion, newAssertion);
247236

248237
[AssertionDataTestMethod]
238+
[AssertionDiagnostic("actual.Should().HaveCount(1{0});")]
249239
[AssertionDiagnostic("actual.Where(x => x.BooleanProperty).Should().HaveCount(1{0});")]
250240
[Implemented]
251-
public void CollectionShouldContainSingleProperty_TestAnalyzer(string assertion) => VerifyCSharpDiagnostic<CollectionShouldContainSinglePropertyAnalyzer>(assertion);
241+
public void CollectionShouldContainSingle_TestAnalyzer(string assertion) => VerifyCSharpDiagnostic<CollectionShouldContainSingleAnalyzer>(assertion);
252242

253243
[AssertionDataTestMethod]
254244
[AssertionCodeFix(
255245
oldAssertion: "actual.Where(x => x.BooleanProperty).Should().HaveCount(1{0});",
256246
newAssertion: "actual.Should().ContainSingle(x => x.BooleanProperty{0});")]
247+
[AssertionCodeFix(
248+
oldAssertion: "actual.Should().HaveCount(1{0});",
249+
newAssertion: "actual.Should().ContainSingle({0});")]
257250
[Implemented]
258-
public void CollectionShouldContainSingleProperty_TestCodeFix(string oldAssertion, string newAssertion) => VerifyCSharpFix<CollectionShouldContainSinglePropertyCodeFix, CollectionShouldContainSinglePropertyAnalyzer>(oldAssertion, newAssertion);
251+
public void CollectionShouldContainSingle_TestCodeFix(string oldAssertion, string newAssertion) => VerifyCSharpFix<CollectionShouldContainSingleCodeFix, CollectionShouldContainSingleAnalyzer>(oldAssertion, newAssertion);
259252

260253
[AssertionDataTestMethod]
261254
[AssertionDiagnostic("actual.Should().NotBeNull().And.NotBeEmpty({0});")]

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

-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
using Microsoft.CodeAnalysis;
22
using Microsoft.VisualStudio.TestTools.UnitTesting;
3-
using System;
4-
using System.Collections.Generic;
5-
using System.Linq;
6-
using System.Text;
7-
using System.Threading.Tasks;
83

94
namespace FluentAssertions.BestPractices.Tests
105
{

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

+36-12
Original file line numberDiff line numberDiff line change
@@ -15,37 +15,61 @@ public class CollectionShouldContainSingleAnalyzer : FluentAssertionsAnalyzer
1515
public const string DiagnosticId = Constants.Tips.Collections.CollectionShouldContainSingle;
1616
public const string Category = Constants.Tips.Category;
1717

18-
public const string Message = "Use {0} .Should() followed by ContainSingle() instead.";
18+
public const string Message = "Use {0} .Should() followed by .ContainSingle() instead.";
1919

2020
protected override DiagnosticDescriptor Rule => new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true);
21-
protected override IEnumerable<(FluentAssertionsCSharpSyntaxVisitor, BecauseArgumentsSyntaxVisitor)> Visitors {
21+
protected override IEnumerable<(FluentAssertionsCSharpSyntaxVisitor, BecauseArgumentsSyntaxVisitor)> Visitors
22+
{
2223
get
2324
{
24-
yield return (new ShouldHaveCount1SyntaxVisitor(), new BecauseArgumentsSyntaxVisitor("HaveCount", 1));
25+
yield return (new WhereShouldHaveCount1SyntaxVisitor(), new BecauseArgumentsSyntaxVisitor("HaveCount", 1));
26+
yield return (new ShouldHaveCount1SyntaxVisitor(), new BecauseArgumentsSyntaxVisitor("HaveCount", 1));
2527
}
2628
}
27-
28-
private class ShouldHaveCount1SyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor
29+
30+
private class WhereShouldHaveCount1SyntaxVisitor : FluentAssertionsWithLambdaArgumentCSharpSyntaxVisitor
2931
{
30-
private bool _haveCountMethodHas0Argument;
32+
private bool _haveCountMethodHas1Argument;
3133

32-
public override bool IsValid => base.IsValid && _haveCountMethodHas0Argument;
33-
34-
public ShouldHaveCount1SyntaxVisitor() : base("Should", "HaveCount")
34+
protected override string MethodContainingLambda => "Where";
35+
public override bool IsValid => base.IsValid && _haveCountMethodHas1Argument;
36+
public WhereShouldHaveCount1SyntaxVisitor() : base("Where", "Should", "HaveCount")
3537
{
3638
}
3739

3840
public override void VisitArgumentList(ArgumentListSyntax node)
3941
{
4042
if (!node.Arguments.Any()) return;
41-
if (CurrentMethod != "HaveCount") return;
43+
if (CurrentMethod != "HaveCount")
44+
{
45+
base.VisitArgumentList(node);
46+
return;
47+
}
4248

43-
_haveCountMethodHas0Argument =
49+
_haveCountMethodHas1Argument =
4450
node.Arguments[0].Expression is LiteralExpressionSyntax literal
4551
&& literal.Token.Value is int argument
4652
&& argument == 1;
4753
}
4854
}
55+
private class ShouldHaveCount1SyntaxVisitor : FluentAssertionsWithArgumentCSharpSyntaxVisitor
56+
{
57+
protected override string MethodContainingArgument => "HaveCount";
58+
public ShouldHaveCount1SyntaxVisitor() : base("Should", "HaveCount")
59+
{
60+
}
61+
62+
protected override ExpressionSyntax ModifyArgument(ExpressionSyntax expression)
63+
{
64+
if (expression is LiteralExpressionSyntax literal
65+
&& literal.Token.Value is int argument
66+
&& argument == 1)
67+
{
68+
return expression;
69+
}
70+
return null;
71+
}
72+
}
4973
}
5074

5175
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(CollectionShouldContainSingleCodeFix)), Shared]
@@ -54,6 +78,6 @@ public class CollectionShouldContainSingleCodeFix : FluentAssertionsCodeFixProvi
5478
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(CollectionShouldContainSingleAnalyzer.DiagnosticId);
5579

5680
protected override StatementSyntax GetNewStatement(FluentAssertionsDiagnosticProperties properties)
57-
=> SyntaxFactory.ParseStatement($"{properties.VariableName}.Should().ContainSingle({properties.BecauseArgumentsString});");
81+
=> SyntaxFactory.ParseStatement($"{properties.VariableName}.Should().ContainSingle({properties.CombineWithBecauseArgumentsString(properties.LambdaString)});");
5882
}
5983
}

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

-64
This file was deleted.

src/FluentAssertions.BestPractices/Utilities/FluentAssertionsAnalyzer.cs

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ public abstract class FluentAssertionsAnalyzer<TCSharpSyntaxVisitor> : Diagnosti
1818

1919
public sealed override void Initialize(AnalysisContext context)
2020
{
21+
context.EnableConcurrentExecution();
22+
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
2123
context.RegisterCodeBlockAction(AnalyzeCodeBlock);
2224
}
2325

src/FluentAssertions.BestPractices/Utilities/FluentAssertionsCSharpSyntaxVisitor.cs

+18-26
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ namespace FluentAssertions.BestPractices
99

1010
public abstract class FluentAssertionsCSharpSyntaxVisitor : CSharpSyntaxVisitor
1111
{
12-
protected bool EncounteredFirstMethod;
1312
protected string CurrentMethod => VisitedMethods.Count > 0 ? VisitedMethods.Peek() : null;
1413

1514
protected readonly Stack<string> VisitedMethods = new Stack<string>();
@@ -25,7 +24,7 @@ public abstract class FluentAssertionsCSharpSyntaxVisitor : CSharpSyntaxVisitor
2524

2625
protected FluentAssertionsCSharpSyntaxVisitor(params string[] requiredMethods)
2726
{
28-
RequiredMethods = new Stack<string>(requiredMethods);
27+
RequiredMethods = new Stack<string>(requiredMethods);
2928
}
3029

3130
public virtual ImmutableDictionary<string, string> ToDiagnosticProperties() => new Dictionary<string, string>
@@ -54,38 +53,22 @@ public override void VisitArgumentList(ArgumentListSyntax node)
5453
public sealed override void VisitMemberAccessExpression(MemberAccessExpressionSyntax node)
5554
{
5655
var methodName = node.Name.Identifier.Text;
57-
VisitedMethods.Push(methodName);
5856

59-
if (!EncounteredFirstMethod && !methodName.Equals(RequiredMethods.Peek()))
60-
{
61-
Visit(node.Expression);
62-
}
63-
else if (RequiredMethods.Count > 0 && methodName.Equals(RequiredMethods.Pop()))
64-
{
65-
EncounteredFirstMethod = true;
66-
Visit(node.Expression);
67-
}
57+
VisitMethod(methodName);
58+
59+
Visit(node.Expression);
60+
6861
}
6962
public sealed override void VisitElementAccessExpression(ElementAccessExpressionSyntax node)
7063
{
7164
const string methodName = "[]";
72-
VisitedMethods.Push(methodName);
7365

74-
if (!EncounteredFirstMethod && !methodName.Equals(RequiredMethods.Peek()))
75-
{
76-
Visit(node.Expression);
77-
Visit(node.ArgumentList);
66+
VisitMethod(methodName);
7867

79-
VisitedMethods.Pop();
80-
}
81-
else if (RequiredMethods.Count > 0 && methodName.Equals(RequiredMethods.Pop()))
82-
{
83-
EncounteredFirstMethod = true;
84-
Visit(node.Expression);
85-
Visit(node.ArgumentList);
68+
Visit(node.Expression);
69+
Visit(node.ArgumentList);
8670

87-
VisitedMethods.Pop();
88-
}
71+
VisitedMethods.Pop();
8972
}
9073

9174
public sealed override void VisitIdentifierName(IdentifierNameSyntax node)
@@ -95,5 +78,14 @@ public sealed override void VisitIdentifierName(IdentifierNameSyntax node)
9578
VariableName = node.Identifier.Text;
9679
}
9780
}
81+
82+
private void VisitMethod(string methodName)
83+
{
84+
VisitedMethods.Push(methodName);
85+
if (RequiredMethods.Count > 0 && methodName.Equals(RequiredMethods.Peek()))
86+
{
87+
RequiredMethods.Pop();
88+
}
89+
}
9890
}
9991
}

src/FluentAssertions.BestPractices/Utilities/FluentAssertionsCodeFixProvider.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,15 @@ protected async Task<Document> RewriteAssertion(Document document, ExpressionSta
3333
{
3434
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
3535

36-
var newStatement = GetNewStatement(new FluentAssertionsDiagnosticProperties(properties)).WithTriviaFrom(statement);
36+
var newStatement = GetNewStatement(statement, new FluentAssertionsDiagnosticProperties(properties)).WithTriviaFrom(statement);
3737

3838
root = root.ReplaceNode(statement, newStatement);
3939

4040
return document.WithSyntaxRoot(root);
4141
}
4242

43+
protected virtual StatementSyntax GetNewStatement(ExpressionStatementSyntax statement, FluentAssertionsDiagnosticProperties properties) => GetNewStatement(properties);
44+
4345
protected abstract StatementSyntax GetNewStatement(FluentAssertionsDiagnosticProperties properties);
4446
}
4547
}

src/FluentAssertions.BestPractices/Utilities/FluentAssertionsDiagnosticProperties.cs

+11-9
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,23 @@ public FluentAssertionsDiagnosticProperties(ImmutableDictionary<string, string>
1212
Properties = properties;
1313
}
1414

15-
public string Title => Properties[Constants.DiagnosticProperties.Title];
16-
public string VariableName => Properties[Constants.DiagnosticProperties.VariableName];
17-
public string BecauseArgumentsString => Properties[Constants.DiagnosticProperties.BecauseArgumentsString];
18-
public string LambdaString => Properties[Constants.DiagnosticProperties.LambdaString];
19-
public string ExpectedItemString => Properties[Constants.DiagnosticProperties.ExpectedItemString];
20-
public string UnexpectedItemString => Properties[Constants.DiagnosticProperties.UnexpectedItemString];
21-
public string ArgumentString => Properties[Constants.DiagnosticProperties.ArgumentString];
15+
public string Title => GetPropertyOrDefault(Constants.DiagnosticProperties.Title);
16+
public string VariableName => GetPropertyOrDefault(Constants.DiagnosticProperties.VariableName);
17+
public string BecauseArgumentsString => GetPropertyOrDefault(Constants.DiagnosticProperties.BecauseArgumentsString);
18+
public string LambdaString => GetPropertyOrDefault(Constants.DiagnosticProperties.LambdaString);
19+
public string ExpectedItemString => GetPropertyOrDefault(Constants.DiagnosticProperties.ExpectedItemString);
20+
public string UnexpectedItemString => GetPropertyOrDefault(Constants.DiagnosticProperties.UnexpectedItemString);
21+
public string ArgumentString => GetPropertyOrDefault(Constants.DiagnosticProperties.ArgumentString);
2222

2323
public string CombineWithBecauseArgumentsString(string validArgument)
2424
{
25-
if (!string.IsNullOrWhiteSpace(BecauseArgumentsString))
25+
if (!string.IsNullOrWhiteSpace(BecauseArgumentsString) && !string.IsNullOrWhiteSpace(validArgument))
2626
{
2727
return $"{validArgument}, {BecauseArgumentsString}";
2828
}
29-
return validArgument;
29+
return validArgument + BecauseArgumentsString;
3030
}
31+
32+
private string GetPropertyOrDefault(string key) => Properties.TryGetValue(key, out var value) ? value : string.Empty;
3133
}
3234
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<CodeSnippet Format="1.1.0" xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
2+
<Header>
3+
<Title>SyntaxVisitor class</Title>
4+
<Author>Meir Blachman</Author>
5+
<Shortcut>svc</Shortcut>
6+
<Description>Code snippet for "XXXSyntaxVisitor : FluentAssertionsAnalyzer"</Description>
7+
<SnippetTypes>
8+
<SnippetType>Expansion</SnippetType>
9+
</SnippetTypes>
10+
</Header>
11+
<Snippet>
12+
<Declarations>
13+
<Literal>
14+
<ID>className</ID>
15+
<ToolTip>The name of the syntax visitor class</ToolTip>
16+
<Default>Should</Default>
17+
</Literal>
18+
</Declarations>
19+
<Code Language="CSharp">
20+
<![CDATA[private class $className$SyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor
21+
{
22+
public $className$SyntaxVisitor() : base("Should")
23+
{
24+
}
25+
}]]>
26+
</Code>
27+
</Snippet>
28+
</CodeSnippet>

0 commit comments

Comments
 (0)