Skip to content

Commit c07c19a

Browse files
authored
Fix: XmlNodeList Should.HaveCount(1) (#111)
* add test for #82 * ignore CollectionShouldContainSingleAnalyzer if missing generic ienumerable interface * rename generate code methods * add tests CollectionShouldContainSingleAnalyzer * fix IsTypeOrConstructedFromTypeOrImplementsType
1 parent 8cdd432 commit c07c19a

16 files changed

+161
-47
lines changed

src/FluentAssertions.Analyzers.Tests/GenerateCode.cs

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ namespace FluentAssertions.Analyzers.Tests
55
{
66
public static class GenerateCode
77
{
8-
public static string EnumerableCodeBlockAssertion(string assertion) => EnumerableAssertion(
8+
public static string GenericIListCodeBlockAssertion(string assertion) => GenericIListAssertion(
99
" {" + Environment.NewLine +
1010
" " + assertion + Environment.NewLine +
1111
" }");
12-
public static string EnumerableExpressionBodyAssertion(string assertion) => EnumerableAssertion(
12+
public static string GenericIListExpressionBodyAssertion(string assertion) => GenericIListAssertion(
1313
" => " + assertion);
1414

15-
private static string EnumerableAssertion(string bodyExpression) => new StringBuilder()
15+
private static string GenericIListAssertion(string bodyExpression) => new StringBuilder()
1616
.AppendLine("using System.Collections.Generic;")
1717
.AppendLine("using System.Linq;")
1818
.AppendLine("using System;")
@@ -33,7 +33,43 @@ public static string EnumerableExpressionBodyAssertion(string assertion) => Enum
3333
.AppendLine("}")
3434
.ToString();
3535

36-
public static string DictionaryAssertion(string assertion) => new StringBuilder()
36+
public static string IEnumerableAssertion(string assertion) => new StringBuilder()
37+
.AppendLine("using System.Collections;")
38+
.AppendLine("using System.Linq;")
39+
.AppendLine("using System;")
40+
.AppendLine("using FluentAssertions;using FluentAssertions.Extensions;")
41+
.AppendLine("namespace TestNamespace")
42+
.AppendLine("{")
43+
.AppendLine(" public class TestClass")
44+
.AppendLine(" {")
45+
.AppendLine(" public void TestMethod(IEnumerable actual, IEnumerable expected)")
46+
.AppendLine(" {")
47+
.AppendLine($" {assertion}")
48+
.AppendLine(" }")
49+
.AppendLine(" }")
50+
.AppendMainMethod()
51+
.AppendLine("}")
52+
.ToString();
53+
54+
public static string GenericIEnumerableAssertion(string assertion) => new StringBuilder()
55+
.AppendLine("using System.Collections.Generic;")
56+
.AppendLine("using System.Linq;")
57+
.AppendLine("using System;")
58+
.AppendLine("using FluentAssertions;using FluentAssertions.Extensions;")
59+
.AppendLine("namespace TestNamespace")
60+
.AppendLine("{")
61+
.AppendLine(" public class TestClass")
62+
.AppendLine(" {")
63+
.AppendLine(" public void TestMethod<T>(IEnumerable<T> actual, IEnumerable<T> expected)")
64+
.AppendLine(" {")
65+
.AppendLine($" {assertion}")
66+
.AppendLine(" }")
67+
.AppendLine(" }")
68+
.AppendMainMethod()
69+
.AppendLine("}")
70+
.ToString();
71+
72+
public static string GenericIDictionaryAssertion(string assertion) => new StringBuilder()
3773
.AppendLine("using System.Collections.Generic;")
3874
.AppendLine("using System.Linq;")
3975
.AppendLine("using System;")
@@ -56,7 +92,7 @@ public static string EnumerableExpressionBodyAssertion(string assertion) => Enum
5692
.AppendLine("}")
5793
.ToString();
5894

59-
public static string NumericAssertion(string assertion) => new StringBuilder()
95+
public static string DoubleAssertion(string assertion) => new StringBuilder()
6096
.AppendLine("using System;")
6197
.AppendLine("using FluentAssertions;")
6298
.AppendLine("using FluentAssertions.Extensions;")
@@ -73,7 +109,7 @@ public static string EnumerableExpressionBodyAssertion(string assertion) => Enum
73109
.AppendLine("}")
74110
.ToString();
75111

76-
public static string ComparableAssertion(string assertion) => new StringBuilder()
112+
public static string ComparableInt32Assertion(string assertion) => new StringBuilder()
77113
.AppendLine("using System;")
78114
.AppendLine("using FluentAssertions;")
79115
.AppendLine("using FluentAssertions.Extensions;")

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

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,34 @@ public class CollectionTests
356356
[Implemented]
357357
public void CollectionShouldNotHaveSameCount_TestCodeFix(string oldAssertion, string newAssertion) => VerifyCSharpFixCodeBlock<CollectionShouldNotHaveSameCountCodeFix, CollectionShouldNotHaveSameCountAnalyzer>(oldAssertion, newAssertion);
358358

359+
[AssertionDataTestMethod]
360+
[AssertionDiagnostic("actual.Should().HaveCount(1{0});")]
361+
[Implemented]
362+
public void CollectionShouldContainSingle_TestAnalyzer_NonGenericIEnumerableShouldNotReport(string assertion)
363+
{
364+
var source = GenerateCode.IEnumerableAssertion(assertion);
365+
DiagnosticVerifier.VerifyCSharpDiagnosticUsingAllAnalyzers(source);
366+
}
367+
368+
[AssertionDataTestMethod]
369+
[AssertionDiagnostic("actual.Should().HaveCount(1{0});")]
370+
[Implemented]
371+
public void CollectionShouldContainSingle_TestAnalyzer_GenericIEnumerableShouldReport(string assertion)
372+
{
373+
var source = GenerateCode.GenericIEnumerableAssertion(assertion);
374+
375+
DiagnosticVerifier.VerifyCSharpDiagnosticUsingAllAnalyzers(source, new DiagnosticResult
376+
{
377+
Id = CollectionShouldContainSingleAnalyzer.DiagnosticId,
378+
Message = CollectionShouldContainSingleAnalyzer.Message,
379+
Locations = new DiagnosticResultLocation[]
380+
{
381+
new DiagnosticResultLocation("Test0.cs", 11,13)
382+
},
383+
Severity = DiagnosticSeverity.Info
384+
});
385+
}
386+
359387
[AssertionDataTestMethod]
360388
[AssertionDiagnostic("actual.Should().HaveCount(1{0});")]
361389
[AssertionDiagnostic("actual.Where(x => x.BooleanProperty).Should().HaveCount(1{0});")]
@@ -609,7 +637,7 @@ public class CollectionTests
609637

610638
private void VerifyCSharpDiagnosticCodeBlock<TDiagnosticAnalyzer>(string sourceAssertion) where TDiagnosticAnalyzer : Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer, new()
611639
{
612-
var source = GenerateCode.EnumerableCodeBlockAssertion(sourceAssertion);
640+
var source = GenerateCode.GenericIListCodeBlockAssertion(sourceAssertion);
613641

614642
var type = typeof(TDiagnosticAnalyzer);
615643
var diagnosticId = (string)type.GetField("DiagnosticId").GetValue(null);
@@ -629,7 +657,7 @@ public class CollectionTests
629657

630658
private void VerifyCSharpDiagnosticExpressionBody<TDiagnosticAnalyzer>(string sourceAssertion) where TDiagnosticAnalyzer : Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer, new()
631659
{
632-
var source = GenerateCode.EnumerableExpressionBodyAssertion(sourceAssertion);
660+
var source = GenerateCode.GenericIListExpressionBodyAssertion(sourceAssertion);
633661

634662
var type = typeof(TDiagnosticAnalyzer);
635663
var diagnosticId = (string)type.GetField("DiagnosticId").GetValue(null);
@@ -651,8 +679,8 @@ private void VerifyCSharpFixCodeBlock<TCodeFixProvider, TDiagnosticAnalyzer>(str
651679
where TCodeFixProvider : Microsoft.CodeAnalysis.CodeFixes.CodeFixProvider, new()
652680
where TDiagnosticAnalyzer : Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer, new()
653681
{
654-
var oldSource = GenerateCode.EnumerableCodeBlockAssertion(oldSourceAssertion);
655-
var newSource = GenerateCode.EnumerableCodeBlockAssertion(newSourceAssertion);
682+
var oldSource = GenerateCode.GenericIListCodeBlockAssertion(oldSourceAssertion);
683+
var newSource = GenerateCode.GenericIListCodeBlockAssertion(newSourceAssertion);
656684

657685
DiagnosticVerifier.VerifyCSharpFix<TCodeFixProvider, TDiagnosticAnalyzer>(oldSource, newSource);
658686
}
@@ -661,16 +689,10 @@ private void VerifyCSharpFixExpressionBody<TCodeFixProvider, TDiagnosticAnalyzer
661689
where TCodeFixProvider : Microsoft.CodeAnalysis.CodeFixes.CodeFixProvider, new()
662690
where TDiagnosticAnalyzer : Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer, new()
663691
{
664-
var oldSource = GenerateCode.EnumerableExpressionBodyAssertion(oldSourceAssertion);
665-
var newSource = GenerateCode.EnumerableExpressionBodyAssertion(newSourceAssertion);
692+
var oldSource = GenerateCode.GenericIListExpressionBodyAssertion(oldSourceAssertion);
693+
var newSource = GenerateCode.GenericIListExpressionBodyAssertion(newSourceAssertion);
666694

667695
DiagnosticVerifier.VerifyCSharpFix<TCodeFixProvider, TDiagnosticAnalyzer>(oldSource, newSource);
668696
}
669-
670-
private void VerifyCSharpNoDiagnosticsCodeBlock(string assertion)
671-
{
672-
var source = GenerateCode.EnumerableCodeBlockAssertion(assertion);
673-
DiagnosticVerifier.VerifyCSharpDiagnosticUsingAllAnalyzers(source);
674-
}
675697
}
676698
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ public class DictionaryTests
152152

153153
private void VerifyCSharpDiagnostic<TDiagnosticAnalyzer>(string sourceAssersion) where TDiagnosticAnalyzer : Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer, new()
154154
{
155-
var source = GenerateCode.DictionaryAssertion(sourceAssersion);
155+
var source = GenerateCode.GenericIDictionaryAssertion(sourceAssersion);
156156

157157
var type = typeof(TDiagnosticAnalyzer);
158158
var diagnosticId = (string)type.GetField("DiagnosticId").GetValue(null);
@@ -174,8 +174,8 @@ private void VerifyCSharpFix<TCodeFixProvider, TDiagnosticAnalyzer>(string oldSo
174174
where TCodeFixProvider : Microsoft.CodeAnalysis.CodeFixes.CodeFixProvider, new()
175175
where TDiagnosticAnalyzer : Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer, new()
176176
{
177-
var oldSource = GenerateCode.DictionaryAssertion(oldSourceAssertion);
178-
var newSource = GenerateCode.DictionaryAssertion(newSourceAssertion);
177+
var oldSource = GenerateCode.GenericIDictionaryAssertion(oldSourceAssertion);
178+
var newSource = GenerateCode.GenericIDictionaryAssertion(newSourceAssertion);
179179

180180
DiagnosticVerifier.VerifyCSharpFix<TCodeFixProvider, TDiagnosticAnalyzer>(oldSource, newSource);
181181
}

src/FluentAssertions.Analyzers.Tests/Tips/NumericTests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public class NumericTests
7878

7979
private void VerifyCSharpDiagnostic<TDiagnosticAnalyzer>(string sourceAssertion) where TDiagnosticAnalyzer : Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer, new()
8080
{
81-
var source = GenerateCode.NumericAssertion(sourceAssertion);
81+
var source = GenerateCode.DoubleAssertion(sourceAssertion);
8282

8383
var type = typeof(TDiagnosticAnalyzer);
8484
var diagnosticId = (string)type.GetField("DiagnosticId").GetValue(null);
@@ -100,8 +100,8 @@ private void VerifyCSharpFix<TCodeFixProvider, TDiagnosticAnalyzer>(string oldSo
100100
where TCodeFixProvider : Microsoft.CodeAnalysis.CodeFixes.CodeFixProvider, new()
101101
where TDiagnosticAnalyzer : Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer, new()
102102
{
103-
var oldSource = GenerateCode.NumericAssertion(oldSourceAssertion);
104-
var newSource = GenerateCode.NumericAssertion(newSourceAssertion);
103+
var oldSource = GenerateCode.DoubleAssertion(oldSourceAssertion);
104+
var newSource = GenerateCode.DoubleAssertion(newSourceAssertion);
105105

106106
DiagnosticVerifier.VerifyCSharpFix<TCodeFixProvider, TDiagnosticAnalyzer>(oldSource, newSource);
107107
}

src/FluentAssertions.Analyzers.Tests/Tips/SanityTests.cs

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ public class SanityTests
1010
public void CountWithPredicate()
1111
{
1212
const string assertion = "actual.Count(d => d.Message.Contains(\"a\")).Should().Be(2);";
13-
var source = GenerateCode.EnumerableCodeBlockAssertion(assertion);
13+
var source = GenerateCode.GenericIListCodeBlockAssertion(assertion);
1414

1515
DiagnosticVerifier.VerifyCSharpDiagnosticUsingAllAnalyzers(source);
1616
}
@@ -20,7 +20,7 @@ public void CountWithPredicate()
2020
public void AssertionCallMultipleMethodWithTheSameNameAndArguments()
2121
{
2222
const string assertion = "actual.Should().Contain(d => d.Message.Contains(\"a\")).And.Contain(d => d.Message.Contains(\"c\"));";
23-
var source = GenerateCode.EnumerableCodeBlockAssertion(assertion);
23+
var source = GenerateCode.GenericIListCodeBlockAssertion(assertion);
2424

2525
DiagnosticVerifier.VerifyCSharpDiagnosticUsingAllAnalyzers(source);
2626
}
@@ -29,7 +29,7 @@ public void AssertionCallMultipleMethodWithTheSameNameAndArguments()
2929
[Implemented(Reason = "https://github.com/fluentassertions/fluentassertions.analyzers/issues/44")]
3030
public void CollectionShouldHaveElementAt_ShouldIgnoreDictionaryTypes()
3131
{
32-
string source = GenerateCode.DictionaryAssertion("actual[\"key\"].Should().Be(expectedValue);");
32+
string source = GenerateCode.GenericIDictionaryAssertion("actual[\"key\"].Should().Be(expectedValue);");
3333
DiagnosticVerifier.VerifyCSharpDiagnosticUsingAllAnalyzers(source);
3434
}
3535

@@ -38,7 +38,7 @@ public void CollectionShouldHaveElementAt_ShouldIgnoreDictionaryTypes()
3838
public void PropertyOfIndexerShouldBe_ShouldNotThrowException()
3939
{
4040
const string assertion = "actual[0].Message.Should().Be(\"test\");";
41-
var source = GenerateCode.EnumerableCodeBlockAssertion(assertion);
41+
var source = GenerateCode.GenericIListCodeBlockAssertion(assertion);
4242

4343
DiagnosticVerifier.VerifyCSharpDiagnosticUsingAllAnalyzers(source);
4444
}
@@ -48,7 +48,7 @@ public void PropertyOfIndexerShouldBe_ShouldNotThrowException()
4848
public void PropertyOfElementAtShouldBe_ShouldNotTriggerDiagnostic()
4949
{
5050
const string assertion = "actual.ElementAt(0).Message.Should().Be(\"test\");";
51-
var source = GenerateCode.EnumerableCodeBlockAssertion(assertion);
51+
var source = GenerateCode.GenericIListCodeBlockAssertion(assertion);
5252

5353
DiagnosticVerifier.VerifyCSharpDiagnosticUsingAllAnalyzers(source);
5454
}
@@ -59,7 +59,7 @@ public void NestedAssertions_ShouldNotTrigger()
5959
{
6060
const string declaration = "var nestedList = new List<List<int>>();";
6161
const string assertion = "nestedList.Should().NotBeNull().And.ContainSingle().Which.Should().NotBeEmpty();";
62-
var source = GenerateCode.EnumerableCodeBlockAssertion(declaration + assertion);
62+
var source = GenerateCode.GenericIListCodeBlockAssertion(declaration + assertion);
6363

6464
DiagnosticVerifier.VerifyCSharpDiagnosticUsingAllAnalyzers(source);
6565
}
@@ -69,7 +69,7 @@ public void NestedAssertions_ShouldNotTrigger()
6969
public void DictionaryShouldContainPair_WhenPropertiesOfDifferentVariables_ShouldNotTrigger()
7070
{
7171
const string assertion = "actual.Should().ContainValue(pair.Value).And.ContainKey(otherPair.Key);";
72-
var source = GenerateCode.DictionaryAssertion(assertion);
72+
var source = GenerateCode.GenericIDictionaryAssertion(assertion);
7373

7474
DiagnosticVerifier.VerifyCSharpDiagnosticUsingAllAnalyzers(source);
7575
}
@@ -258,5 +258,30 @@ public static void Main()
258258

259259
DiagnosticVerifier.VerifyCSharpDiagnosticUsingAllAnalyzers(source);
260260
}
261+
262+
[TestMethod]
263+
[Implemented(Reason = "https://github.com/fluentassertions/fluentassertions.analyzers/issues/82")]
264+
public void XmlNodeListShouldHaveCount1_ShouldNotReport()
265+
{
266+
const string source = @"
267+
using System.Xml;
268+
using System.Collections.Generic;
269+
using FluentAssertions;
270+
using FluentAssertions.Extensions;
271+
272+
namespace TestNamespace
273+
{
274+
public class Program
275+
{
276+
public static void Main()
277+
{
278+
XmlNodeList childNodes = new XmlDocument().ChildNodes;
279+
childNodes.Should().HaveCount(1);
280+
}
281+
}
282+
}";
283+
284+
DiagnosticVerifier.VerifyCSharpDiagnosticUsingAllAnalyzers(source);
285+
}
261286
}
262287
}

src/FluentAssertions.Analyzers.Tests/Tips/ShouldEqualsTests.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ public void ShouldEquals_ShouldBe_ObjectType_TestCodeFix()
2626
[Implemented]
2727
public void ShouldEquals_ShouldBe_NumberType_TestCodeFix()
2828
{
29-
var oldSource = GenerateCode.NumericAssertion("actual.Should().Equals(expected);");
30-
var newSource = GenerateCode.NumericAssertion("actual.Should().Be(expected);");
29+
var oldSource = GenerateCode.DoubleAssertion("actual.Should().Equals(expected);");
30+
var newSource = GenerateCode.DoubleAssertion("actual.Should().Be(expected);");
3131

3232
DiagnosticVerifier.VerifyCSharpFix<ShouldEqualsCodeFix, ShouldEqualsAnalyzer>(oldSource, newSource);
3333
}
@@ -46,8 +46,8 @@ public void ShouldEquals_ShouldBe_StringType_TestCodeFix()
4646
[Implemented]
4747
public void ShouldEquals_ShouldEqual_EnumerableType_TestCodeFix()
4848
{
49-
var oldSource = GenerateCode.EnumerableCodeBlockAssertion("actual.Should().Equals(expected);");
50-
var newSource = GenerateCode.EnumerableCodeBlockAssertion("actual.Should().Equal(expected);");
49+
var oldSource = GenerateCode.GenericIListCodeBlockAssertion("actual.Should().Equals(expected);");
50+
var newSource = GenerateCode.GenericIListCodeBlockAssertion("actual.Should().Equal(expected);");
5151

5252
DiagnosticVerifier.VerifyCSharpFix<ShouldEqualsCodeFix, ShouldEqualsAnalyzer>(oldSource, newSource);
5353
}

src/FluentAssertions.Analyzers/Tips/Collections/CollectionAnalyzer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace FluentAssertions.Analyzers
55
{
66
public abstract class CollectionAnalyzer : FluentAssertionsAnalyzer
77
{
8-
protected override bool ShouldAnalyzeVariableType(ITypeSymbol type)
8+
protected override bool ShouldAnalyzeVariableType(INamedTypeSymbol type, SemanticModel semanticModel)
99
{
1010
return type.Name != "String"
1111
&& type.AllInterfaces.Any(@interface => @interface.Name == "IEnumerable")

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
using Microsoft.CodeAnalysis;
1+
using FluentAssertions.Analyzers.Utilities;
2+
using Microsoft.CodeAnalysis;
23
using Microsoft.CodeAnalysis.CodeFixes;
34
using Microsoft.CodeAnalysis.CSharp.Syntax;
45
using Microsoft.CodeAnalysis.Diagnostics;
56
using System.Collections.Generic;
67
using System.Collections.Immutable;
78
using System.Composition;
9+
using System.Linq;
810

911
namespace FluentAssertions.Analyzers
1012
{
@@ -26,6 +28,16 @@ protected override IEnumerable<FluentAssertionsCSharpSyntaxVisitor> Visitors
2628
}
2729
}
2830

31+
protected override bool ShouldAnalyzeVariableType(INamedTypeSymbol type, SemanticModel semanticModel)
32+
{
33+
if (!type.IsTypeOrConstructedFromTypeOrImplementsType(SpecialType.System_Collections_Generic_IEnumerable_T))
34+
{
35+
return false;
36+
}
37+
38+
return base.ShouldAnalyzeVariableType(type, semanticModel);
39+
}
40+
2941
public class WhereShouldHaveCount1SyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor
3042
{
3143
public WhereShouldHaveCount1SyntaxVisitor() : base(MemberValidator.MathodContainingLambda("Where"), MemberValidator.Should, MemberValidator.ArgumentIsLiteral("HaveCount", 1))

src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldHaveElementAt.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,14 @@ protected override IEnumerable<FluentAssertionsCSharpSyntaxVisitor> Visitors
2828
}
2929
}
3030

31-
protected override bool ShouldAnalyzeVariableType(ITypeSymbol type)
31+
protected override bool ShouldAnalyzeVariableType(INamedTypeSymbol type, SemanticModel semanticModel)
3232
{
3333
if (type.AllInterfaces.Any(@interface => @interface.Name == "IReadOnlyDictionary" || @interface.Name == "IDictionary"))
3434
{
3535
return false;
3636
}
3737

38-
return base.ShouldAnalyzeVariableType(type);
38+
return base.ShouldAnalyzeVariableType(type, semanticModel);
3939
}
4040

4141
public class ElementAtIndexShouldBeSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor

0 commit comments

Comments
 (0)