From cb6ff80ec2c0450223804b352eedabee12f3d199 Mon Sep 17 00:00:00 2001 From: Rogatnev Sergey Date: Sat, 18 Jul 2026 13:41:19 +0700 Subject: [PATCH 1/6] reformat tests --- .../ArrayContainsFalsePositiveTests.cs | 12 +- .../ArrayContainsToHashSetTests.cs | 26 +-- .../RedundantToArrayLengthTests.cs | 4 +- Tests/ArrayTests/AddRangeTests.cs | 6 +- Tests/ArrayTests/ConstructorTests.cs | 10 +- Tests/ArrayTests/ObjectInitializerTests.cs | 4 +- .../RedundantArrayToArrayConversionTests.cs | 8 +- .../ConcurrentBagIsEmptyTests/IsEmptyTests.cs | 14 +- .../IsEmptyTests.cs | 14 +- .../IsEmptyTests.cs | 14 +- .../IsEmptyTests.cs | 14 +- .../EnumerableTests/AssignEnumerableTests.cs | 8 +- ...dundantEnumerableToArrayConversionTests.cs | 10 +- Tests/ListTests/ListCapacityTests.cs | 14 +- Tests/ListTests/ListLoopTests.cs | 10 +- Tests/ResourceReader.cs | 6 +- .../ArrayContains1.cs} | 0 .../ArrayContains2.cs} | 0 .../ArrayContains3.cs} | 0 .../ArrayContains4.cs} | 0 .../ArrayContains5.cs} | 0 .../ArrayContains6.cs} | 0 .../ArrayContains7.cs} | 0 .../ArrayContainsAfter1.cs} | 0 .../ArrayContainsAfter2.cs} | 0 .../ArrayContainsAfter3.cs} | 0 .../ArrayContainsAfter4.cs} | 0 .../ArrayContainsBefore1.cs} | 0 .../ArrayContainsBefore2.cs} | 0 .../ArrayContainsBefore3.cs} | 0 .../ArrayContainsBefore4.cs} | 0 .../ArrayContainsFalsePositive1.cs} | 0 .../ArrayContainsFalsePositive2.cs} | 0 .../ArrayContainsFalsePositive3.cs} | 0 .../ArrayContainsFalsePositive4.cs} | 0 .../ArrayContainsFalsePositive5.cs} | 0 .../ArrayContainsFalsePositive6.cs} | 0 .../ArrayCountAfter.cs} | 0 .../ArrayCountBefore.cs} | 0 .../AddRange1.cs} | 0 .../AddRange2.cs} | 0 .../AddRangeAfter.cs} | 0 .../AddRangeBefore.cs} | 0 .../ArrayToArray.cs} | 0 .../ArrayToArrayAfter.cs} | 0 .../ArrayToArrayBefore.cs} | 0 .../AssignEnumerable1.cs} | 0 .../AssignEnumerable2.cs} | 0 .../AssignEnumerableAfter1.cs} | 0 .../AssignEnumerableAfter2.cs} | 0 .../AssignEnumerableBefore1.cs} | 0 .../AssignEnumerableBefore2.cs} | 0 .../GetArrayToArray.cs} | 0 .../GetArrayToArrayAfter.cs} | 0 .../GetArrayToArrayBefore.cs} | 0 .../GetSetToArray.cs} | 0 .../GetStringSelectToArray.cs} | 0 .../GetStringToCharArraySelectAfter.cs} | 0 .../GetStringToCharArraySelectBefore.cs} | 0 .../SelectStringToArray.cs} | 0 .../ConcurrentBag1.cs} | 0 .../ConcurrentBag2.cs} | 0 .../ConcurrentBag3.cs} | 0 .../ConcurrentBagAfter1.cs} | 0 .../ConcurrentBagAfter2.cs} | 0 .../ConcurrentBagAfter3.cs} | 0 .../ConcurrentBagAfter4.cs} | 0 .../ConcurrentBagBefore1.cs} | 0 .../ConcurrentBagBefore2.cs} | 0 .../ConcurrentBagBefore3.cs} | 0 .../ConcurrentBagBefore4.cs} | 0 .../ConcurrentDictionary1.cs} | 0 .../ConcurrentDictionary2.cs} | 0 .../ConcurrentDictionary3.cs} | 0 .../ConcurrentDictionaryAfter1.cs} | 0 .../ConcurrentDictionaryAfter2.cs} | 0 .../ConcurrentDictionaryAfter3.cs} | 0 .../ConcurrentDictionaryAfter4.cs} | 0 .../ConcurrentDictionaryBefore1.cs} | 0 .../ConcurrentDictionaryBefore2.cs} | 0 .../ConcurrentDictionaryBefore3.cs} | 0 .../ConcurrentDictionaryBefore4.cs} | 0 .../ConcurrentQueue1.cs} | 0 .../ConcurrentQueue2.cs} | 0 .../ConcurrentQueue3.cs} | 0 .../ConcurrentQueueAfter1.cs} | 0 .../ConcurrentQueueAfter2.cs} | 0 .../ConcurrentQueueAfter3.cs} | 0 .../ConcurrentQueueAfter4.cs} | 0 .../ConcurrentQueueBefore1.cs} | 0 .../ConcurrentQueueBefore2.cs} | 0 .../ConcurrentQueueBefore3.cs} | 0 .../ConcurrentQueueBefore4.cs} | 0 .../ConcurrentStack1.cs} | 0 .../ConcurrentStack2.cs} | 0 .../ConcurrentStack3.cs} | 0 .../ConcurrentStackAfter1.cs} | 0 .../ConcurrentStackAfter2.cs} | 0 .../ConcurrentStackAfter3.cs} | 0 .../ConcurrentStackAfter4.cs} | 0 .../ConcurrentStackBefore1.cs} | 0 .../ConcurrentStackBefore2.cs} | 0 .../ConcurrentStackBefore3.cs} | 0 .../ConcurrentStackBefore4.cs} | 0 .../AsyncEnumerableMethodToArray.cs} | 0 .../AsyncEnumerableMethodToArray2.cs} | 0 .../EnumerableMethodToArray.cs} | 0 .../EnumerablePropertyToArray.cs} | 0 .../Constructor1.cs} | 0 .../Constructor2.cs} | 0 .../Constructor3.cs} | 0 .../Constructor4.cs} | 0 .../ConstructorAfter.cs} | 0 .../ConstructorBefore.cs} | 0 .../ListCountAfter.cs} | 0 .../ListCountBefore.cs} | 0 .../ListInitializer1.cs} | 0 .../ListInitializer2.cs} | 0 .../ListInitializer3.cs} | 0 .../ListInitializer4.cs} | 0 .../ListInitializerAfter1.cs} | 0 .../ListInitializerAfter2.cs} | 0 .../ListInitializerAfter3.cs} | 0 .../ListInitializerBefore1.cs} | 0 .../ListInitializerBefore2.cs} | 0 .../ListInitializerBefore3.cs} | 0 .../ListLoopArray1.cs} | 0 .../ListLoopArrayForAfter.cs} | 0 .../ListLoopArrayForBefore.cs} | 0 .../ListLoopArrayForeachAfter.cs} | 0 .../ListLoopArrayForeachBefore.cs} | 0 .../ListLoopCollectionForeachAfter.cs} | 0 .../ListLoopCollectionForeachBefore.cs} | 0 .../ListLoopImplicitNewAfter.cs} | 0 .../ListLoopImplicitNewBefore.cs} | 0 .../ObjectInitializer.cs} | 0 .../ObjectInitializerAfter.cs} | 0 .../ObjectInitializerBefore.cs} | 0 .../ForeachStringToArray.cs} | 0 .../ForeachStringToArrayAfter.cs} | 0 .../ForeachStringToArrayBefore.cs} | 0 .../ForeachStringToCharArray.cs} | 0 .../ForeachStringToCharArrayAfter.cs} | 0 .../ForeachStringToCharArrayBefore.cs} | 0 .../ForeachStringToListAfter.cs} | 0 .../ForeachStringToListBefore.cs} | 0 .../StringJoin1.cs} | 0 .../StringJoinAfter.cs} | 0 .../StringJoinBefore.cs} | 0 .../StringListConstructor1.cs} | 0 .../StringListConstructor2.cs} | 0 .../StringListConstructorAfter.cs} | 0 .../StringListConstructorBefore1.cs} | 0 .../StringListConstructorBefore2.cs} | 0 Tests/StringTests/ListFromStringTests.cs | 8 +- .../RedundantStringToArrayConversionTests.cs | 16 +- .../StringJoinToArrayConversionTests.cs | 4 +- Tests/Tests.csproj | 150 +----------------- 158 files changed, 108 insertions(+), 244 deletions(-) rename Tests/Resources/{ArrayContains1.txt => ArrayContainsToHashSetTests/ArrayContains1.cs} (100%) rename Tests/Resources/{ArrayContains2.txt => ArrayContainsToHashSetTests/ArrayContains2.cs} (100%) rename Tests/Resources/{ArrayContains3.txt => ArrayContainsToHashSetTests/ArrayContains3.cs} (100%) rename Tests/Resources/{ArrayContains4.txt => ArrayContainsToHashSetTests/ArrayContains4.cs} (100%) rename Tests/Resources/{ArrayContains5.txt => ArrayContainsToHashSetTests/ArrayContains5.cs} (100%) rename Tests/Resources/{ArrayContains6.txt => ArrayContainsToHashSetTests/ArrayContains6.cs} (100%) rename Tests/Resources/{ArrayContains7.txt => ArrayContainsToHashSetTests/ArrayContains7.cs} (100%) rename Tests/Resources/{ArrayContainsAfter1.txt => ArrayContainsToHashSetTests/ArrayContainsAfter1.cs} (100%) rename Tests/Resources/{ArrayContainsAfter2.txt => ArrayContainsToHashSetTests/ArrayContainsAfter2.cs} (100%) rename Tests/Resources/{ArrayContainsAfter3.txt => ArrayContainsToHashSetTests/ArrayContainsAfter3.cs} (100%) rename Tests/Resources/{ArrayContainsAfter4.txt => ArrayContainsToHashSetTests/ArrayContainsAfter4.cs} (100%) rename Tests/Resources/{ArrayContainsBefore1.txt => ArrayContainsToHashSetTests/ArrayContainsBefore1.cs} (100%) rename Tests/Resources/{ArrayContainsBefore2.txt => ArrayContainsToHashSetTests/ArrayContainsBefore2.cs} (100%) rename Tests/Resources/{ArrayContainsBefore3.txt => ArrayContainsToHashSetTests/ArrayContainsBefore3.cs} (100%) rename Tests/Resources/{ArrayContainsBefore4.txt => ArrayContainsToHashSetTests/ArrayContainsBefore4.cs} (100%) rename Tests/Resources/{ArrayContainsFalsePositive1.txt => ArrayContainsToHashSetTests/ArrayContainsFalsePositive1.cs} (100%) rename Tests/Resources/{ArrayContainsFalsePositive2.txt => ArrayContainsToHashSetTests/ArrayContainsFalsePositive2.cs} (100%) rename Tests/Resources/{ArrayContainsFalsePositive3.txt => ArrayContainsToHashSetTests/ArrayContainsFalsePositive3.cs} (100%) rename Tests/Resources/{ArrayContainsFalsePositive4.txt => ArrayContainsToHashSetTests/ArrayContainsFalsePositive4.cs} (100%) rename Tests/Resources/{ArrayContainsFalsePositive5.txt => ArrayContainsToHashSetTests/ArrayContainsFalsePositive5.cs} (100%) rename Tests/Resources/{ArrayContainsFalsePositive6.txt => ArrayContainsToHashSetTests/ArrayContainsFalsePositive6.cs} (100%) rename Tests/Resources/{ArrayCountAfter.txt => ArrayLengthTests/ArrayCountAfter.cs} (100%) rename Tests/Resources/{ArrayCountBefore.txt => ArrayLengthTests/ArrayCountBefore.cs} (100%) rename Tests/Resources/{AddRange1.txt => ArrayTests/AddRange1.cs} (100%) rename Tests/Resources/{AddRange2.txt => ArrayTests/AddRange2.cs} (100%) rename Tests/Resources/{AddRangeAfter.txt => ArrayTests/AddRangeAfter.cs} (100%) rename Tests/Resources/{AddRangeBefore.txt => ArrayTests/AddRangeBefore.cs} (100%) rename Tests/Resources/{ArrayToArray.txt => ArrayTests/ArrayToArray.cs} (100%) rename Tests/Resources/{ArrayToArrayAfter.txt => ArrayTests/ArrayToArrayAfter.cs} (100%) rename Tests/Resources/{ArrayToArrayBefore.txt => ArrayTests/ArrayToArrayBefore.cs} (100%) rename Tests/Resources/{AssignEnumerable1.txt => ArrayTests/AssignEnumerable1.cs} (100%) rename Tests/Resources/{AssignEnumerable2.txt => ArrayTests/AssignEnumerable2.cs} (100%) rename Tests/Resources/{AssignEnumerableAfter1.txt => ArrayTests/AssignEnumerableAfter1.cs} (100%) rename Tests/Resources/{AssignEnumerableAfter2.txt => ArrayTests/AssignEnumerableAfter2.cs} (100%) rename Tests/Resources/{AssignEnumerableBefore1.txt => ArrayTests/AssignEnumerableBefore1.cs} (100%) rename Tests/Resources/{AssignEnumerableBefore2.txt => ArrayTests/AssignEnumerableBefore2.cs} (100%) rename Tests/Resources/{GetArrayToArray.txt => ArrayTests/GetArrayToArray.cs} (100%) rename Tests/Resources/{GetArrayToArrayAfter.txt => ArrayTests/GetArrayToArrayAfter.cs} (100%) rename Tests/Resources/{GetArrayToArrayBefore.txt => ArrayTests/GetArrayToArrayBefore.cs} (100%) rename Tests/Resources/{GetSetToArray.txt => ArrayTests/GetSetToArray.cs} (100%) rename Tests/Resources/{GetStringSelectToArray.txt => ArrayTests/GetStringSelectToArray.cs} (100%) rename Tests/Resources/{GetStringToCharArraySelectAfter.txt => ArrayTests/GetStringToCharArraySelectAfter.cs} (100%) rename Tests/Resources/{GetStringToCharArraySelectBefore.txt => ArrayTests/GetStringToCharArraySelectBefore.cs} (100%) rename Tests/Resources/{SelectStringToArray.txt => ArrayTests/SelectStringToArray.cs} (100%) rename Tests/Resources/{ConcurrentBag1.txt => ConcurrentBagIsEmptyTests/ConcurrentBag1.cs} (100%) rename Tests/Resources/{ConcurrentBag2.txt => ConcurrentBagIsEmptyTests/ConcurrentBag2.cs} (100%) rename Tests/Resources/{ConcurrentBag3.txt => ConcurrentBagIsEmptyTests/ConcurrentBag3.cs} (100%) rename Tests/Resources/{ConcurrentBagAfter1.txt => ConcurrentBagIsEmptyTests/ConcurrentBagAfter1.cs} (100%) rename Tests/Resources/{ConcurrentBagAfter2.txt => ConcurrentBagIsEmptyTests/ConcurrentBagAfter2.cs} (100%) rename Tests/Resources/{ConcurrentBagAfter3.txt => ConcurrentBagIsEmptyTests/ConcurrentBagAfter3.cs} (100%) rename Tests/Resources/{ConcurrentBagAfter4.txt => ConcurrentBagIsEmptyTests/ConcurrentBagAfter4.cs} (100%) rename Tests/Resources/{ConcurrentBagBefore1.txt => ConcurrentBagIsEmptyTests/ConcurrentBagBefore1.cs} (100%) rename Tests/Resources/{ConcurrentBagBefore2.txt => ConcurrentBagIsEmptyTests/ConcurrentBagBefore2.cs} (100%) rename Tests/Resources/{ConcurrentBagBefore3.txt => ConcurrentBagIsEmptyTests/ConcurrentBagBefore3.cs} (100%) rename Tests/Resources/{ConcurrentBagBefore4.txt => ConcurrentBagIsEmptyTests/ConcurrentBagBefore4.cs} (100%) rename Tests/Resources/{ConcurrentDictionary1.txt => ConcurrentDictionaryIsEmptyTests/ConcurrentDictionary1.cs} (100%) rename Tests/Resources/{ConcurrentDictionary2.txt => ConcurrentDictionaryIsEmptyTests/ConcurrentDictionary2.cs} (100%) rename Tests/Resources/{ConcurrentDictionary3.txt => ConcurrentDictionaryIsEmptyTests/ConcurrentDictionary3.cs} (100%) rename Tests/Resources/{ConcurrentDictionaryAfter1.txt => ConcurrentDictionaryIsEmptyTests/ConcurrentDictionaryAfter1.cs} (100%) rename Tests/Resources/{ConcurrentDictionaryAfter2.txt => ConcurrentDictionaryIsEmptyTests/ConcurrentDictionaryAfter2.cs} (100%) rename Tests/Resources/{ConcurrentDictionaryAfter3.txt => ConcurrentDictionaryIsEmptyTests/ConcurrentDictionaryAfter3.cs} (100%) rename Tests/Resources/{ConcurrentDictionaryAfter4.txt => ConcurrentDictionaryIsEmptyTests/ConcurrentDictionaryAfter4.cs} (100%) rename Tests/Resources/{ConcurrentDictionaryBefore1.txt => ConcurrentDictionaryIsEmptyTests/ConcurrentDictionaryBefore1.cs} (100%) rename Tests/Resources/{ConcurrentDictionaryBefore2.txt => ConcurrentDictionaryIsEmptyTests/ConcurrentDictionaryBefore2.cs} (100%) rename Tests/Resources/{ConcurrentDictionaryBefore3.txt => ConcurrentDictionaryIsEmptyTests/ConcurrentDictionaryBefore3.cs} (100%) rename Tests/Resources/{ConcurrentDictionaryBefore4.txt => ConcurrentDictionaryIsEmptyTests/ConcurrentDictionaryBefore4.cs} (100%) rename Tests/Resources/{ConcurrentQueue1.txt => ConcurrentQueueIsEmptyTests/ConcurrentQueue1.cs} (100%) rename Tests/Resources/{ConcurrentQueue2.txt => ConcurrentQueueIsEmptyTests/ConcurrentQueue2.cs} (100%) rename Tests/Resources/{ConcurrentQueue3.txt => ConcurrentQueueIsEmptyTests/ConcurrentQueue3.cs} (100%) rename Tests/Resources/{ConcurrentQueueAfter1.txt => ConcurrentQueueIsEmptyTests/ConcurrentQueueAfter1.cs} (100%) rename Tests/Resources/{ConcurrentQueueAfter2.txt => ConcurrentQueueIsEmptyTests/ConcurrentQueueAfter2.cs} (100%) rename Tests/Resources/{ConcurrentQueueAfter3.txt => ConcurrentQueueIsEmptyTests/ConcurrentQueueAfter3.cs} (100%) rename Tests/Resources/{ConcurrentQueueAfter4.txt => ConcurrentQueueIsEmptyTests/ConcurrentQueueAfter4.cs} (100%) rename Tests/Resources/{ConcurrentQueueBefore1.txt => ConcurrentQueueIsEmptyTests/ConcurrentQueueBefore1.cs} (100%) rename Tests/Resources/{ConcurrentQueueBefore2.txt => ConcurrentQueueIsEmptyTests/ConcurrentQueueBefore2.cs} (100%) rename Tests/Resources/{ConcurrentQueueBefore3.txt => ConcurrentQueueIsEmptyTests/ConcurrentQueueBefore3.cs} (100%) rename Tests/Resources/{ConcurrentQueueBefore4.txt => ConcurrentQueueIsEmptyTests/ConcurrentQueueBefore4.cs} (100%) rename Tests/Resources/{ConcurrentStack1.txt => ConcurrentStackIsEmptyTests/ConcurrentStack1.cs} (100%) rename Tests/Resources/{ConcurrentStack2.txt => ConcurrentStackIsEmptyTests/ConcurrentStack2.cs} (100%) rename Tests/Resources/{ConcurrentStack3.txt => ConcurrentStackIsEmptyTests/ConcurrentStack3.cs} (100%) rename Tests/Resources/{ConcurrentStackAfter1.txt => ConcurrentStackIsEmptyTests/ConcurrentStackAfter1.cs} (100%) rename Tests/Resources/{ConcurrentStackAfter2.txt => ConcurrentStackIsEmptyTests/ConcurrentStackAfter2.cs} (100%) rename Tests/Resources/{ConcurrentStackAfter3.txt => ConcurrentStackIsEmptyTests/ConcurrentStackAfter3.cs} (100%) rename Tests/Resources/{ConcurrentStackAfter4.txt => ConcurrentStackIsEmptyTests/ConcurrentStackAfter4.cs} (100%) rename Tests/Resources/{ConcurrentStackBefore1.txt => ConcurrentStackIsEmptyTests/ConcurrentStackBefore1.cs} (100%) rename Tests/Resources/{ConcurrentStackBefore2.txt => ConcurrentStackIsEmptyTests/ConcurrentStackBefore2.cs} (100%) rename Tests/Resources/{ConcurrentStackBefore3.txt => ConcurrentStackIsEmptyTests/ConcurrentStackBefore3.cs} (100%) rename Tests/Resources/{ConcurrentStackBefore4.txt => ConcurrentStackIsEmptyTests/ConcurrentStackBefore4.cs} (100%) rename Tests/Resources/{AsyncEnumerableMethodToArray.txt => EnumerableTests/AsyncEnumerableMethodToArray.cs} (100%) rename Tests/Resources/{AsyncEnumerableMethodToArray2.txt => EnumerableTests/AsyncEnumerableMethodToArray2.cs} (100%) rename Tests/Resources/{EnumerableMethodToArray.txt => EnumerableTests/EnumerableMethodToArray.cs} (100%) rename Tests/Resources/{EnumerablePropertyToArray.txt => EnumerableTests/EnumerablePropertyToArray.cs} (100%) rename Tests/Resources/{Constructor1.txt => ListTests/Constructor1.cs} (100%) rename Tests/Resources/{Constructor2.txt => ListTests/Constructor2.cs} (100%) rename Tests/Resources/{Constructor3.txt => ListTests/Constructor3.cs} (100%) rename Tests/Resources/{Constructor4.txt => ListTests/Constructor4.cs} (100%) rename Tests/Resources/{ConstructorAfter.txt => ListTests/ConstructorAfter.cs} (100%) rename Tests/Resources/{ConstructorBefore.txt => ListTests/ConstructorBefore.cs} (100%) rename Tests/Resources/{ListCountAfter.txt => ListTests/ListCountAfter.cs} (100%) rename Tests/Resources/{ListCountBefore.txt => ListTests/ListCountBefore.cs} (100%) rename Tests/Resources/{ListInitializer1.txt => ListTests/ListInitializer1.cs} (100%) rename Tests/Resources/{ListInitializer2.txt => ListTests/ListInitializer2.cs} (100%) rename Tests/Resources/{ListInitializer3.txt => ListTests/ListInitializer3.cs} (100%) rename Tests/Resources/{ListInitializer4.txt => ListTests/ListInitializer4.cs} (100%) rename Tests/Resources/{ListInitializerAfter1.txt => ListTests/ListInitializerAfter1.cs} (100%) rename Tests/Resources/{ListInitializerAfter2.txt => ListTests/ListInitializerAfter2.cs} (100%) rename Tests/Resources/{ListInitializerAfter3.txt => ListTests/ListInitializerAfter3.cs} (100%) rename Tests/Resources/{ListInitializerBefore1.txt => ListTests/ListInitializerBefore1.cs} (100%) rename Tests/Resources/{ListInitializerBefore2.txt => ListTests/ListInitializerBefore2.cs} (100%) rename Tests/Resources/{ListInitializerBefore3.txt => ListTests/ListInitializerBefore3.cs} (100%) rename Tests/Resources/{ListLoopArray1.txt => ListTests/ListLoopArray1.cs} (100%) rename Tests/Resources/{ListLoopArrayForAfter.txt => ListTests/ListLoopArrayForAfter.cs} (100%) rename Tests/Resources/{ListLoopArrayForBefore.txt => ListTests/ListLoopArrayForBefore.cs} (100%) rename Tests/Resources/{ListLoopArrayForeachAfter.txt => ListTests/ListLoopArrayForeachAfter.cs} (100%) rename Tests/Resources/{ListLoopArrayForeachBefore.txt => ListTests/ListLoopArrayForeachBefore.cs} (100%) rename Tests/Resources/{ListLoopCollectionForeachAfter.txt => ListTests/ListLoopCollectionForeachAfter.cs} (100%) rename Tests/Resources/{ListLoopCollectionForeachBefore.txt => ListTests/ListLoopCollectionForeachBefore.cs} (100%) rename Tests/Resources/{ListLoopImplicitNewAfter.txt => ListTests/ListLoopImplicitNewAfter.cs} (100%) rename Tests/Resources/{ListLoopImplicitNewBefore.txt => ListTests/ListLoopImplicitNewBefore.cs} (100%) rename Tests/Resources/{ObjectInitializer.txt => ListTests/ObjectInitializer.cs} (100%) rename Tests/Resources/{ObjectInitializerAfter.txt => ListTests/ObjectInitializerAfter.cs} (100%) rename Tests/Resources/{ObjectInitializerBefore.txt => ListTests/ObjectInitializerBefore.cs} (100%) rename Tests/Resources/{ForeachStringToArray.txt => StringTests/ForeachStringToArray.cs} (100%) rename Tests/Resources/{ForeachStringToArrayAfter.txt => StringTests/ForeachStringToArrayAfter.cs} (100%) rename Tests/Resources/{ForeachStringToArrayBefore.txt => StringTests/ForeachStringToArrayBefore.cs} (100%) rename Tests/Resources/{ForeachStringToCharArray.txt => StringTests/ForeachStringToCharArray.cs} (100%) rename Tests/Resources/{ForeachStringToCharArrayAfter.txt => StringTests/ForeachStringToCharArrayAfter.cs} (100%) rename Tests/Resources/{ForeachStringToCharArrayBefore.txt => StringTests/ForeachStringToCharArrayBefore.cs} (100%) rename Tests/Resources/{ForeachStringToListAfter.txt => StringTests/ForeachStringToListAfter.cs} (100%) rename Tests/Resources/{ForeachStringToListBefore.txt => StringTests/ForeachStringToListBefore.cs} (100%) rename Tests/Resources/{StringJoin1.txt => StringTests/StringJoin1.cs} (100%) rename Tests/Resources/{StringJoinAfter.txt => StringTests/StringJoinAfter.cs} (100%) rename Tests/Resources/{StringJoinBefore.txt => StringTests/StringJoinBefore.cs} (100%) rename Tests/Resources/{StringListConstructor1.txt => StringTests/StringListConstructor1.cs} (100%) rename Tests/Resources/{StringListConstructor2.txt => StringTests/StringListConstructor2.cs} (100%) rename Tests/Resources/{StringListConstructorAfter.txt => StringTests/StringListConstructorAfter.cs} (100%) rename Tests/Resources/{StringListConstructorBefore1.txt => StringTests/StringListConstructorBefore1.cs} (100%) rename Tests/Resources/{StringListConstructorBefore2.txt => StringTests/StringListConstructorBefore2.cs} (100%) diff --git a/Tests/ArrayContainsToHashSetTests/ArrayContainsFalsePositiveTests.cs b/Tests/ArrayContainsToHashSetTests/ArrayContainsFalsePositiveTests.cs index 1643a4d..1d946a0 100644 --- a/Tests/ArrayContainsToHashSetTests/ArrayContainsFalsePositiveTests.cs +++ b/Tests/ArrayContainsToHashSetTests/ArrayContainsFalsePositiveTests.cs @@ -14,7 +14,7 @@ public class ArrayContainsFalsePositiveTests : CSharpCodeFixTest< [Test] public Task CustomContainsMethod_ShouldNotWarn() { - var code = ResourceReader.ReadFromFile("ArrayContainsFalsePositive1.txt"); + var code = ResourceReader.ReadFromFile("ArrayContainsFalsePositive1.cs"); return ArrayContainsVerifier.VerifyAnalyzerAsync(code, DiagnosticResult.EmptyDiagnosticResults); } @@ -22,7 +22,7 @@ public Task CustomContainsMethod_ShouldNotWarn() [Test] public Task ExtensionContainsMethod_ShouldNotWarn() { - var code = ResourceReader.ReadFromFile("ArrayContainsFalsePositive2.txt"); + var code = ResourceReader.ReadFromFile("ArrayContainsFalsePositive2.cs"); return ArrayContainsVerifier.VerifyAnalyzerAsync(code, DiagnosticResult.EmptyDiagnosticResults); } @@ -30,7 +30,7 @@ public Task ExtensionContainsMethod_ShouldNotWarn() [Test] public Task ArrayLengthUsage_ShouldNotWarn() { - var code = ResourceReader.ReadFromFile("ArrayContainsFalsePositive3.txt"); + var code = ResourceReader.ReadFromFile("ArrayContainsFalsePositive3.cs"); return ArrayContainsVerifier.VerifyAnalyzerAsync(code, DiagnosticResult.EmptyDiagnosticResults); } @@ -38,7 +38,7 @@ public Task ArrayLengthUsage_ShouldNotWarn() [Test] public Task ArrayPassedAsArgument_ShouldNotWarn() { - var code = ResourceReader.ReadFromFile("ArrayContainsFalsePositive4.txt"); + var code = ResourceReader.ReadFromFile("ArrayContainsFalsePositive4.cs"); return ArrayContainsVerifier.VerifyAnalyzerAsync(code, DiagnosticResult.EmptyDiagnosticResults); } @@ -46,7 +46,7 @@ public Task ArrayPassedAsArgument_ShouldNotWarn() [Test] public Task ArrayAliasing_ShouldNotWarn() { - var code = ResourceReader.ReadFromFile("ArrayContainsFalsePositive5.txt"); + var code = ResourceReader.ReadFromFile("ArrayContainsFalsePositive5.cs"); return ArrayContainsVerifier.VerifyAnalyzerAsync(code, DiagnosticResult.EmptyDiagnosticResults); } @@ -54,7 +54,7 @@ public Task ArrayAliasing_ShouldNotWarn() [Test] public Task StaticLinqContains_ShouldWarn() { - var code = ResourceReader.ReadFromFile("ArrayContainsFalsePositive6.txt"); + var code = ResourceReader.ReadFromFile("ArrayContainsFalsePositive6.cs"); return ArrayContainsVerifier.VerifyAnalyzerAsync(code, DiagnosticResult.CompilerWarning("CI0008").WithSpan(7, 15, 7, 22).WithArguments("numbers", "int")); diff --git a/Tests/ArrayContainsToHashSetTests/ArrayContainsToHashSetTests.cs b/Tests/ArrayContainsToHashSetTests/ArrayContainsToHashSetTests.cs index 4166b90..0eb3882 100644 --- a/Tests/ArrayContainsToHashSetTests/ArrayContainsToHashSetTests.cs +++ b/Tests/ArrayContainsToHashSetTests/ArrayContainsToHashSetTests.cs @@ -15,7 +15,7 @@ public class ArrayContainsToHashSetTests : CSharpCodeFixTest< [Test] public Task LocalVariableImplicitArray_ShouldWarn() { - var code = ResourceReader.ReadFromFile("ArrayContains1.txt"); + var code = ResourceReader.ReadFromFile("ArrayContains1.cs"); return ArrayContainsVerifier .VerifyAnalyzerAsync(code, DiagnosticResult.CompilerWarning("CI0008").WithSpan(7, 13, 7, 20)); @@ -24,7 +24,7 @@ public Task LocalVariableImplicitArray_ShouldWarn() [Test] public Task MinArrayLength_ShouldWarn() { - var code = ResourceReader.ReadFromFile("ArrayContains1.txt"); + var code = ResourceReader.ReadFromFile("ArrayContains1.cs"); var test = new CSharpCodeFixTest { @@ -45,7 +45,7 @@ public Task MinArrayLength_ShouldWarn() [Test] public Task MinArrayLength_ShouldNotWarn() { - var code = ResourceReader.ReadFromFile("ArrayContains1.txt"); + var code = ResourceReader.ReadFromFile("ArrayContains1.cs"); var test = new CSharpCodeFixTest { @@ -62,7 +62,7 @@ public Task MinArrayLength_ShouldNotWarn() [Test] public Task FieldExplicitArray_ShouldWarn() { - var code = ResourceReader.ReadFromFile("ArrayContains2.txt"); + var code = ResourceReader.ReadFromFile("ArrayContains2.cs"); return ArrayContainsVerifier .VerifyAnalyzerAsync(code, DiagnosticResult.CompilerWarning("CI0008").WithSpan(5, 28, 5, 34)); @@ -71,7 +71,7 @@ public Task FieldExplicitArray_ShouldWarn() [Test] public Task FieldCollectionInitializer_ShouldWarn() { - var code = ResourceReader.ReadFromFile("ArrayContains3.txt"); + var code = ResourceReader.ReadFromFile("ArrayContains3.cs"); return ArrayContainsVerifier .VerifyAnalyzerAsync(code, DiagnosticResult.CompilerWarning("CI0008").WithSpan(5, 28, 5, 34)); @@ -80,7 +80,7 @@ public Task FieldCollectionInitializer_ShouldWarn() [Test] public Task PropertyImplicitArray_ShouldWarn() { - var code = ResourceReader.ReadFromFile("ArrayContains4.txt"); + var code = ResourceReader.ReadFromFile("ArrayContains4.cs"); return ArrayContainsVerifier .VerifyAnalyzerAsync(code, DiagnosticResult.CompilerWarning("CI0008").WithSpan(5, 18, 5, 23)); @@ -89,7 +89,7 @@ public Task PropertyImplicitArray_ShouldWarn() [Test] public Task ArrayWithIndexAccess_ShouldNotWarn() { - var code = ResourceReader.ReadFromFile("ArrayContains5.txt"); + var code = ResourceReader.ReadFromFile("ArrayContains5.cs"); return ArrayContainsVerifier .VerifyAnalyzerAsync(code, DiagnosticResult.EmptyDiagnosticResults); @@ -98,7 +98,7 @@ public Task ArrayWithIndexAccess_ShouldNotWarn() [Test] public Task ArrayWithModification_ShouldNotWarn() { - var code = ResourceReader.ReadFromFile("ArrayContains6.txt"); + var code = ResourceReader.ReadFromFile("ArrayContains6.cs"); return ArrayContainsVerifier .VerifyAnalyzerAsync(code, DiagnosticResult.EmptyDiagnosticResults); @@ -107,17 +107,17 @@ public Task ArrayWithModification_ShouldNotWarn() [Test] public Task ArrayWithoutContains_ShouldNotWarn() { - var code = ResourceReader.ReadFromFile("ArrayContains7.txt"); + var code = ResourceReader.ReadFromFile("ArrayContains7.cs"); return ArrayContainsVerifier .VerifyAnalyzerAsync(code, DiagnosticResult.EmptyDiagnosticResults); } [Test] - [TestCase("ArrayContainsBefore1.txt", "ArrayContainsAfter1.txt")] - [TestCase("ArrayContainsBefore2.txt", "ArrayContainsAfter2.txt")] - [TestCase("ArrayContainsBefore3.txt", "ArrayContainsAfter3.txt", Ignore = "Different syntax tree in result")] - [TestCase("ArrayContainsBefore4.txt", "ArrayContainsAfter4.txt")] + [TestCase("ArrayContainsBefore1.cs", "ArrayContainsAfter1.cs")] + [TestCase("ArrayContainsBefore2.cs", "ArrayContainsAfter2.cs")] + [TestCase("ArrayContainsBefore3.cs", "ArrayContainsAfter3.cs", Ignore = "Different syntax tree in result")] + [TestCase("ArrayContainsBefore4.cs", "ArrayContainsAfter4.cs")] public Task CodeFixesTest(string before, string after) { var code = ResourceReader.ReadFromFile(before); diff --git a/Tests/ArrayLengthTests/RedundantToArrayLengthTests.cs b/Tests/ArrayLengthTests/RedundantToArrayLengthTests.cs index ca33648..e5c2642 100644 --- a/Tests/ArrayLengthTests/RedundantToArrayLengthTests.cs +++ b/Tests/ArrayLengthTests/RedundantToArrayLengthTests.cs @@ -11,8 +11,8 @@ public class RedundantToArrayLengthTests : CSharpCodeFixTest { [Test] - [TestCase("ArrayCountBefore.txt", "ArrayCountAfter.txt")] - [TestCase("ListCountBefore.txt", "ListCountAfter.txt")] + [TestCase("ArrayCountBefore.cs", "ArrayCountAfter.cs")] + [TestCase("ListCountBefore.cs", "ListCountAfter.cs")] public Task CodeFixesTest(string before, string after) { var code = ResourceReader.ReadFromFile(before); diff --git a/Tests/ArrayTests/AddRangeTests.cs b/Tests/ArrayTests/AddRangeTests.cs index af63c9f..576bde8 100644 --- a/Tests/ArrayTests/AddRangeTests.cs +++ b/Tests/ArrayTests/AddRangeTests.cs @@ -13,7 +13,7 @@ public class AddRangeTests : CSharpCodeFixTest { [Test] - [TestCase("Constructor1.txt")] - [TestCase("Constructor4.txt")] + [TestCase("Constructor1.cs")] + [TestCase("Constructor4.cs")] public Task ConstructorTest(string fileName) { var code = ResourceReader.ReadFromFile(fileName); @@ -24,7 +24,7 @@ public Task ConstructorTest(string fileName) [Test] public Task ManyArgumentsTest() { - var code = ResourceReader.ReadFromFile("Constructor2.txt"); + var code = ResourceReader.ReadFromFile("Constructor2.cs"); return ConstructorVerifier .VerifyAnalyzerAsync(code, @@ -35,14 +35,14 @@ public Task ManyArgumentsTest() [Test] public Task NoErrorTest() { - var code = ResourceReader.ReadFromFile("Constructor3.txt"); + var code = ResourceReader.ReadFromFile("Constructor3.cs"); return ConstructorVerifier .VerifyAnalyzerAsync(code); } [Test] - [TestCase("ConstructorBefore.txt", "ConstructorAfter.txt")] + [TestCase("ConstructorBefore.cs", "ConstructorAfter.cs")] public Task CodeFixesTest(string before, string after) { var code = ResourceReader.ReadFromFile(before); diff --git a/Tests/ArrayTests/ObjectInitializerTests.cs b/Tests/ArrayTests/ObjectInitializerTests.cs index 56d6bf2..a5e3c16 100644 --- a/Tests/ArrayTests/ObjectInitializerTests.cs +++ b/Tests/ArrayTests/ObjectInitializerTests.cs @@ -14,14 +14,14 @@ public class ObjectInitializerTests : CSharpCodeFixTest x.EndsWith("Resources." + resourceName) || x.Contains(".Resources.") && x.EndsWith("." + resourceName)) + ?? throw new InvalidOperationException($"Resource {resourceName} not found"); - using var stream = assembly.GetManifestResourceStream("Tests.Resources." + resourceName) ?? throw new InvalidOperationException(); + using var stream = assembly.GetManifestResourceStream(fullResourceName) ?? throw new InvalidOperationException(); using var reader = new StreamReader(stream); return reader.ReadToEnd(); } diff --git a/Tests/Resources/ArrayContains1.txt b/Tests/Resources/ArrayContainsToHashSetTests/ArrayContains1.cs similarity index 100% rename from Tests/Resources/ArrayContains1.txt rename to Tests/Resources/ArrayContainsToHashSetTests/ArrayContains1.cs diff --git a/Tests/Resources/ArrayContains2.txt b/Tests/Resources/ArrayContainsToHashSetTests/ArrayContains2.cs similarity index 100% rename from Tests/Resources/ArrayContains2.txt rename to Tests/Resources/ArrayContainsToHashSetTests/ArrayContains2.cs diff --git a/Tests/Resources/ArrayContains3.txt b/Tests/Resources/ArrayContainsToHashSetTests/ArrayContains3.cs similarity index 100% rename from Tests/Resources/ArrayContains3.txt rename to Tests/Resources/ArrayContainsToHashSetTests/ArrayContains3.cs diff --git a/Tests/Resources/ArrayContains4.txt b/Tests/Resources/ArrayContainsToHashSetTests/ArrayContains4.cs similarity index 100% rename from Tests/Resources/ArrayContains4.txt rename to Tests/Resources/ArrayContainsToHashSetTests/ArrayContains4.cs diff --git a/Tests/Resources/ArrayContains5.txt b/Tests/Resources/ArrayContainsToHashSetTests/ArrayContains5.cs similarity index 100% rename from Tests/Resources/ArrayContains5.txt rename to Tests/Resources/ArrayContainsToHashSetTests/ArrayContains5.cs diff --git a/Tests/Resources/ArrayContains6.txt b/Tests/Resources/ArrayContainsToHashSetTests/ArrayContains6.cs similarity index 100% rename from Tests/Resources/ArrayContains6.txt rename to Tests/Resources/ArrayContainsToHashSetTests/ArrayContains6.cs diff --git a/Tests/Resources/ArrayContains7.txt b/Tests/Resources/ArrayContainsToHashSetTests/ArrayContains7.cs similarity index 100% rename from Tests/Resources/ArrayContains7.txt rename to Tests/Resources/ArrayContainsToHashSetTests/ArrayContains7.cs diff --git a/Tests/Resources/ArrayContainsAfter1.txt b/Tests/Resources/ArrayContainsToHashSetTests/ArrayContainsAfter1.cs similarity index 100% rename from Tests/Resources/ArrayContainsAfter1.txt rename to Tests/Resources/ArrayContainsToHashSetTests/ArrayContainsAfter1.cs diff --git a/Tests/Resources/ArrayContainsAfter2.txt b/Tests/Resources/ArrayContainsToHashSetTests/ArrayContainsAfter2.cs similarity index 100% rename from Tests/Resources/ArrayContainsAfter2.txt rename to Tests/Resources/ArrayContainsToHashSetTests/ArrayContainsAfter2.cs diff --git a/Tests/Resources/ArrayContainsAfter3.txt b/Tests/Resources/ArrayContainsToHashSetTests/ArrayContainsAfter3.cs similarity index 100% rename from Tests/Resources/ArrayContainsAfter3.txt rename to Tests/Resources/ArrayContainsToHashSetTests/ArrayContainsAfter3.cs diff --git a/Tests/Resources/ArrayContainsAfter4.txt b/Tests/Resources/ArrayContainsToHashSetTests/ArrayContainsAfter4.cs similarity index 100% rename from Tests/Resources/ArrayContainsAfter4.txt rename to Tests/Resources/ArrayContainsToHashSetTests/ArrayContainsAfter4.cs diff --git a/Tests/Resources/ArrayContainsBefore1.txt b/Tests/Resources/ArrayContainsToHashSetTests/ArrayContainsBefore1.cs similarity index 100% rename from Tests/Resources/ArrayContainsBefore1.txt rename to Tests/Resources/ArrayContainsToHashSetTests/ArrayContainsBefore1.cs diff --git a/Tests/Resources/ArrayContainsBefore2.txt b/Tests/Resources/ArrayContainsToHashSetTests/ArrayContainsBefore2.cs similarity index 100% rename from Tests/Resources/ArrayContainsBefore2.txt rename to Tests/Resources/ArrayContainsToHashSetTests/ArrayContainsBefore2.cs diff --git a/Tests/Resources/ArrayContainsBefore3.txt b/Tests/Resources/ArrayContainsToHashSetTests/ArrayContainsBefore3.cs similarity index 100% rename from Tests/Resources/ArrayContainsBefore3.txt rename to Tests/Resources/ArrayContainsToHashSetTests/ArrayContainsBefore3.cs diff --git a/Tests/Resources/ArrayContainsBefore4.txt b/Tests/Resources/ArrayContainsToHashSetTests/ArrayContainsBefore4.cs similarity index 100% rename from Tests/Resources/ArrayContainsBefore4.txt rename to Tests/Resources/ArrayContainsToHashSetTests/ArrayContainsBefore4.cs diff --git a/Tests/Resources/ArrayContainsFalsePositive1.txt b/Tests/Resources/ArrayContainsToHashSetTests/ArrayContainsFalsePositive1.cs similarity index 100% rename from Tests/Resources/ArrayContainsFalsePositive1.txt rename to Tests/Resources/ArrayContainsToHashSetTests/ArrayContainsFalsePositive1.cs diff --git a/Tests/Resources/ArrayContainsFalsePositive2.txt b/Tests/Resources/ArrayContainsToHashSetTests/ArrayContainsFalsePositive2.cs similarity index 100% rename from Tests/Resources/ArrayContainsFalsePositive2.txt rename to Tests/Resources/ArrayContainsToHashSetTests/ArrayContainsFalsePositive2.cs diff --git a/Tests/Resources/ArrayContainsFalsePositive3.txt b/Tests/Resources/ArrayContainsToHashSetTests/ArrayContainsFalsePositive3.cs similarity index 100% rename from Tests/Resources/ArrayContainsFalsePositive3.txt rename to Tests/Resources/ArrayContainsToHashSetTests/ArrayContainsFalsePositive3.cs diff --git a/Tests/Resources/ArrayContainsFalsePositive4.txt b/Tests/Resources/ArrayContainsToHashSetTests/ArrayContainsFalsePositive4.cs similarity index 100% rename from Tests/Resources/ArrayContainsFalsePositive4.txt rename to Tests/Resources/ArrayContainsToHashSetTests/ArrayContainsFalsePositive4.cs diff --git a/Tests/Resources/ArrayContainsFalsePositive5.txt b/Tests/Resources/ArrayContainsToHashSetTests/ArrayContainsFalsePositive5.cs similarity index 100% rename from Tests/Resources/ArrayContainsFalsePositive5.txt rename to Tests/Resources/ArrayContainsToHashSetTests/ArrayContainsFalsePositive5.cs diff --git a/Tests/Resources/ArrayContainsFalsePositive6.txt b/Tests/Resources/ArrayContainsToHashSetTests/ArrayContainsFalsePositive6.cs similarity index 100% rename from Tests/Resources/ArrayContainsFalsePositive6.txt rename to Tests/Resources/ArrayContainsToHashSetTests/ArrayContainsFalsePositive6.cs diff --git a/Tests/Resources/ArrayCountAfter.txt b/Tests/Resources/ArrayLengthTests/ArrayCountAfter.cs similarity index 100% rename from Tests/Resources/ArrayCountAfter.txt rename to Tests/Resources/ArrayLengthTests/ArrayCountAfter.cs diff --git a/Tests/Resources/ArrayCountBefore.txt b/Tests/Resources/ArrayLengthTests/ArrayCountBefore.cs similarity index 100% rename from Tests/Resources/ArrayCountBefore.txt rename to Tests/Resources/ArrayLengthTests/ArrayCountBefore.cs diff --git a/Tests/Resources/AddRange1.txt b/Tests/Resources/ArrayTests/AddRange1.cs similarity index 100% rename from Tests/Resources/AddRange1.txt rename to Tests/Resources/ArrayTests/AddRange1.cs diff --git a/Tests/Resources/AddRange2.txt b/Tests/Resources/ArrayTests/AddRange2.cs similarity index 100% rename from Tests/Resources/AddRange2.txt rename to Tests/Resources/ArrayTests/AddRange2.cs diff --git a/Tests/Resources/AddRangeAfter.txt b/Tests/Resources/ArrayTests/AddRangeAfter.cs similarity index 100% rename from Tests/Resources/AddRangeAfter.txt rename to Tests/Resources/ArrayTests/AddRangeAfter.cs diff --git a/Tests/Resources/AddRangeBefore.txt b/Tests/Resources/ArrayTests/AddRangeBefore.cs similarity index 100% rename from Tests/Resources/AddRangeBefore.txt rename to Tests/Resources/ArrayTests/AddRangeBefore.cs diff --git a/Tests/Resources/ArrayToArray.txt b/Tests/Resources/ArrayTests/ArrayToArray.cs similarity index 100% rename from Tests/Resources/ArrayToArray.txt rename to Tests/Resources/ArrayTests/ArrayToArray.cs diff --git a/Tests/Resources/ArrayToArrayAfter.txt b/Tests/Resources/ArrayTests/ArrayToArrayAfter.cs similarity index 100% rename from Tests/Resources/ArrayToArrayAfter.txt rename to Tests/Resources/ArrayTests/ArrayToArrayAfter.cs diff --git a/Tests/Resources/ArrayToArrayBefore.txt b/Tests/Resources/ArrayTests/ArrayToArrayBefore.cs similarity index 100% rename from Tests/Resources/ArrayToArrayBefore.txt rename to Tests/Resources/ArrayTests/ArrayToArrayBefore.cs diff --git a/Tests/Resources/AssignEnumerable1.txt b/Tests/Resources/ArrayTests/AssignEnumerable1.cs similarity index 100% rename from Tests/Resources/AssignEnumerable1.txt rename to Tests/Resources/ArrayTests/AssignEnumerable1.cs diff --git a/Tests/Resources/AssignEnumerable2.txt b/Tests/Resources/ArrayTests/AssignEnumerable2.cs similarity index 100% rename from Tests/Resources/AssignEnumerable2.txt rename to Tests/Resources/ArrayTests/AssignEnumerable2.cs diff --git a/Tests/Resources/AssignEnumerableAfter1.txt b/Tests/Resources/ArrayTests/AssignEnumerableAfter1.cs similarity index 100% rename from Tests/Resources/AssignEnumerableAfter1.txt rename to Tests/Resources/ArrayTests/AssignEnumerableAfter1.cs diff --git a/Tests/Resources/AssignEnumerableAfter2.txt b/Tests/Resources/ArrayTests/AssignEnumerableAfter2.cs similarity index 100% rename from Tests/Resources/AssignEnumerableAfter2.txt rename to Tests/Resources/ArrayTests/AssignEnumerableAfter2.cs diff --git a/Tests/Resources/AssignEnumerableBefore1.txt b/Tests/Resources/ArrayTests/AssignEnumerableBefore1.cs similarity index 100% rename from Tests/Resources/AssignEnumerableBefore1.txt rename to Tests/Resources/ArrayTests/AssignEnumerableBefore1.cs diff --git a/Tests/Resources/AssignEnumerableBefore2.txt b/Tests/Resources/ArrayTests/AssignEnumerableBefore2.cs similarity index 100% rename from Tests/Resources/AssignEnumerableBefore2.txt rename to Tests/Resources/ArrayTests/AssignEnumerableBefore2.cs diff --git a/Tests/Resources/GetArrayToArray.txt b/Tests/Resources/ArrayTests/GetArrayToArray.cs similarity index 100% rename from Tests/Resources/GetArrayToArray.txt rename to Tests/Resources/ArrayTests/GetArrayToArray.cs diff --git a/Tests/Resources/GetArrayToArrayAfter.txt b/Tests/Resources/ArrayTests/GetArrayToArrayAfter.cs similarity index 100% rename from Tests/Resources/GetArrayToArrayAfter.txt rename to Tests/Resources/ArrayTests/GetArrayToArrayAfter.cs diff --git a/Tests/Resources/GetArrayToArrayBefore.txt b/Tests/Resources/ArrayTests/GetArrayToArrayBefore.cs similarity index 100% rename from Tests/Resources/GetArrayToArrayBefore.txt rename to Tests/Resources/ArrayTests/GetArrayToArrayBefore.cs diff --git a/Tests/Resources/GetSetToArray.txt b/Tests/Resources/ArrayTests/GetSetToArray.cs similarity index 100% rename from Tests/Resources/GetSetToArray.txt rename to Tests/Resources/ArrayTests/GetSetToArray.cs diff --git a/Tests/Resources/GetStringSelectToArray.txt b/Tests/Resources/ArrayTests/GetStringSelectToArray.cs similarity index 100% rename from Tests/Resources/GetStringSelectToArray.txt rename to Tests/Resources/ArrayTests/GetStringSelectToArray.cs diff --git a/Tests/Resources/GetStringToCharArraySelectAfter.txt b/Tests/Resources/ArrayTests/GetStringToCharArraySelectAfter.cs similarity index 100% rename from Tests/Resources/GetStringToCharArraySelectAfter.txt rename to Tests/Resources/ArrayTests/GetStringToCharArraySelectAfter.cs diff --git a/Tests/Resources/GetStringToCharArraySelectBefore.txt b/Tests/Resources/ArrayTests/GetStringToCharArraySelectBefore.cs similarity index 100% rename from Tests/Resources/GetStringToCharArraySelectBefore.txt rename to Tests/Resources/ArrayTests/GetStringToCharArraySelectBefore.cs diff --git a/Tests/Resources/SelectStringToArray.txt b/Tests/Resources/ArrayTests/SelectStringToArray.cs similarity index 100% rename from Tests/Resources/SelectStringToArray.txt rename to Tests/Resources/ArrayTests/SelectStringToArray.cs diff --git a/Tests/Resources/ConcurrentBag1.txt b/Tests/Resources/ConcurrentBagIsEmptyTests/ConcurrentBag1.cs similarity index 100% rename from Tests/Resources/ConcurrentBag1.txt rename to Tests/Resources/ConcurrentBagIsEmptyTests/ConcurrentBag1.cs diff --git a/Tests/Resources/ConcurrentBag2.txt b/Tests/Resources/ConcurrentBagIsEmptyTests/ConcurrentBag2.cs similarity index 100% rename from Tests/Resources/ConcurrentBag2.txt rename to Tests/Resources/ConcurrentBagIsEmptyTests/ConcurrentBag2.cs diff --git a/Tests/Resources/ConcurrentBag3.txt b/Tests/Resources/ConcurrentBagIsEmptyTests/ConcurrentBag3.cs similarity index 100% rename from Tests/Resources/ConcurrentBag3.txt rename to Tests/Resources/ConcurrentBagIsEmptyTests/ConcurrentBag3.cs diff --git a/Tests/Resources/ConcurrentBagAfter1.txt b/Tests/Resources/ConcurrentBagIsEmptyTests/ConcurrentBagAfter1.cs similarity index 100% rename from Tests/Resources/ConcurrentBagAfter1.txt rename to Tests/Resources/ConcurrentBagIsEmptyTests/ConcurrentBagAfter1.cs diff --git a/Tests/Resources/ConcurrentBagAfter2.txt b/Tests/Resources/ConcurrentBagIsEmptyTests/ConcurrentBagAfter2.cs similarity index 100% rename from Tests/Resources/ConcurrentBagAfter2.txt rename to Tests/Resources/ConcurrentBagIsEmptyTests/ConcurrentBagAfter2.cs diff --git a/Tests/Resources/ConcurrentBagAfter3.txt b/Tests/Resources/ConcurrentBagIsEmptyTests/ConcurrentBagAfter3.cs similarity index 100% rename from Tests/Resources/ConcurrentBagAfter3.txt rename to Tests/Resources/ConcurrentBagIsEmptyTests/ConcurrentBagAfter3.cs diff --git a/Tests/Resources/ConcurrentBagAfter4.txt b/Tests/Resources/ConcurrentBagIsEmptyTests/ConcurrentBagAfter4.cs similarity index 100% rename from Tests/Resources/ConcurrentBagAfter4.txt rename to Tests/Resources/ConcurrentBagIsEmptyTests/ConcurrentBagAfter4.cs diff --git a/Tests/Resources/ConcurrentBagBefore1.txt b/Tests/Resources/ConcurrentBagIsEmptyTests/ConcurrentBagBefore1.cs similarity index 100% rename from Tests/Resources/ConcurrentBagBefore1.txt rename to Tests/Resources/ConcurrentBagIsEmptyTests/ConcurrentBagBefore1.cs diff --git a/Tests/Resources/ConcurrentBagBefore2.txt b/Tests/Resources/ConcurrentBagIsEmptyTests/ConcurrentBagBefore2.cs similarity index 100% rename from Tests/Resources/ConcurrentBagBefore2.txt rename to Tests/Resources/ConcurrentBagIsEmptyTests/ConcurrentBagBefore2.cs diff --git a/Tests/Resources/ConcurrentBagBefore3.txt b/Tests/Resources/ConcurrentBagIsEmptyTests/ConcurrentBagBefore3.cs similarity index 100% rename from Tests/Resources/ConcurrentBagBefore3.txt rename to Tests/Resources/ConcurrentBagIsEmptyTests/ConcurrentBagBefore3.cs diff --git a/Tests/Resources/ConcurrentBagBefore4.txt b/Tests/Resources/ConcurrentBagIsEmptyTests/ConcurrentBagBefore4.cs similarity index 100% rename from Tests/Resources/ConcurrentBagBefore4.txt rename to Tests/Resources/ConcurrentBagIsEmptyTests/ConcurrentBagBefore4.cs diff --git a/Tests/Resources/ConcurrentDictionary1.txt b/Tests/Resources/ConcurrentDictionaryIsEmptyTests/ConcurrentDictionary1.cs similarity index 100% rename from Tests/Resources/ConcurrentDictionary1.txt rename to Tests/Resources/ConcurrentDictionaryIsEmptyTests/ConcurrentDictionary1.cs diff --git a/Tests/Resources/ConcurrentDictionary2.txt b/Tests/Resources/ConcurrentDictionaryIsEmptyTests/ConcurrentDictionary2.cs similarity index 100% rename from Tests/Resources/ConcurrentDictionary2.txt rename to Tests/Resources/ConcurrentDictionaryIsEmptyTests/ConcurrentDictionary2.cs diff --git a/Tests/Resources/ConcurrentDictionary3.txt b/Tests/Resources/ConcurrentDictionaryIsEmptyTests/ConcurrentDictionary3.cs similarity index 100% rename from Tests/Resources/ConcurrentDictionary3.txt rename to Tests/Resources/ConcurrentDictionaryIsEmptyTests/ConcurrentDictionary3.cs diff --git a/Tests/Resources/ConcurrentDictionaryAfter1.txt b/Tests/Resources/ConcurrentDictionaryIsEmptyTests/ConcurrentDictionaryAfter1.cs similarity index 100% rename from Tests/Resources/ConcurrentDictionaryAfter1.txt rename to Tests/Resources/ConcurrentDictionaryIsEmptyTests/ConcurrentDictionaryAfter1.cs diff --git a/Tests/Resources/ConcurrentDictionaryAfter2.txt b/Tests/Resources/ConcurrentDictionaryIsEmptyTests/ConcurrentDictionaryAfter2.cs similarity index 100% rename from Tests/Resources/ConcurrentDictionaryAfter2.txt rename to Tests/Resources/ConcurrentDictionaryIsEmptyTests/ConcurrentDictionaryAfter2.cs diff --git a/Tests/Resources/ConcurrentDictionaryAfter3.txt b/Tests/Resources/ConcurrentDictionaryIsEmptyTests/ConcurrentDictionaryAfter3.cs similarity index 100% rename from Tests/Resources/ConcurrentDictionaryAfter3.txt rename to Tests/Resources/ConcurrentDictionaryIsEmptyTests/ConcurrentDictionaryAfter3.cs diff --git a/Tests/Resources/ConcurrentDictionaryAfter4.txt b/Tests/Resources/ConcurrentDictionaryIsEmptyTests/ConcurrentDictionaryAfter4.cs similarity index 100% rename from Tests/Resources/ConcurrentDictionaryAfter4.txt rename to Tests/Resources/ConcurrentDictionaryIsEmptyTests/ConcurrentDictionaryAfter4.cs diff --git a/Tests/Resources/ConcurrentDictionaryBefore1.txt b/Tests/Resources/ConcurrentDictionaryIsEmptyTests/ConcurrentDictionaryBefore1.cs similarity index 100% rename from Tests/Resources/ConcurrentDictionaryBefore1.txt rename to Tests/Resources/ConcurrentDictionaryIsEmptyTests/ConcurrentDictionaryBefore1.cs diff --git a/Tests/Resources/ConcurrentDictionaryBefore2.txt b/Tests/Resources/ConcurrentDictionaryIsEmptyTests/ConcurrentDictionaryBefore2.cs similarity index 100% rename from Tests/Resources/ConcurrentDictionaryBefore2.txt rename to Tests/Resources/ConcurrentDictionaryIsEmptyTests/ConcurrentDictionaryBefore2.cs diff --git a/Tests/Resources/ConcurrentDictionaryBefore3.txt b/Tests/Resources/ConcurrentDictionaryIsEmptyTests/ConcurrentDictionaryBefore3.cs similarity index 100% rename from Tests/Resources/ConcurrentDictionaryBefore3.txt rename to Tests/Resources/ConcurrentDictionaryIsEmptyTests/ConcurrentDictionaryBefore3.cs diff --git a/Tests/Resources/ConcurrentDictionaryBefore4.txt b/Tests/Resources/ConcurrentDictionaryIsEmptyTests/ConcurrentDictionaryBefore4.cs similarity index 100% rename from Tests/Resources/ConcurrentDictionaryBefore4.txt rename to Tests/Resources/ConcurrentDictionaryIsEmptyTests/ConcurrentDictionaryBefore4.cs diff --git a/Tests/Resources/ConcurrentQueue1.txt b/Tests/Resources/ConcurrentQueueIsEmptyTests/ConcurrentQueue1.cs similarity index 100% rename from Tests/Resources/ConcurrentQueue1.txt rename to Tests/Resources/ConcurrentQueueIsEmptyTests/ConcurrentQueue1.cs diff --git a/Tests/Resources/ConcurrentQueue2.txt b/Tests/Resources/ConcurrentQueueIsEmptyTests/ConcurrentQueue2.cs similarity index 100% rename from Tests/Resources/ConcurrentQueue2.txt rename to Tests/Resources/ConcurrentQueueIsEmptyTests/ConcurrentQueue2.cs diff --git a/Tests/Resources/ConcurrentQueue3.txt b/Tests/Resources/ConcurrentQueueIsEmptyTests/ConcurrentQueue3.cs similarity index 100% rename from Tests/Resources/ConcurrentQueue3.txt rename to Tests/Resources/ConcurrentQueueIsEmptyTests/ConcurrentQueue3.cs diff --git a/Tests/Resources/ConcurrentQueueAfter1.txt b/Tests/Resources/ConcurrentQueueIsEmptyTests/ConcurrentQueueAfter1.cs similarity index 100% rename from Tests/Resources/ConcurrentQueueAfter1.txt rename to Tests/Resources/ConcurrentQueueIsEmptyTests/ConcurrentQueueAfter1.cs diff --git a/Tests/Resources/ConcurrentQueueAfter2.txt b/Tests/Resources/ConcurrentQueueIsEmptyTests/ConcurrentQueueAfter2.cs similarity index 100% rename from Tests/Resources/ConcurrentQueueAfter2.txt rename to Tests/Resources/ConcurrentQueueIsEmptyTests/ConcurrentQueueAfter2.cs diff --git a/Tests/Resources/ConcurrentQueueAfter3.txt b/Tests/Resources/ConcurrentQueueIsEmptyTests/ConcurrentQueueAfter3.cs similarity index 100% rename from Tests/Resources/ConcurrentQueueAfter3.txt rename to Tests/Resources/ConcurrentQueueIsEmptyTests/ConcurrentQueueAfter3.cs diff --git a/Tests/Resources/ConcurrentQueueAfter4.txt b/Tests/Resources/ConcurrentQueueIsEmptyTests/ConcurrentQueueAfter4.cs similarity index 100% rename from Tests/Resources/ConcurrentQueueAfter4.txt rename to Tests/Resources/ConcurrentQueueIsEmptyTests/ConcurrentQueueAfter4.cs diff --git a/Tests/Resources/ConcurrentQueueBefore1.txt b/Tests/Resources/ConcurrentQueueIsEmptyTests/ConcurrentQueueBefore1.cs similarity index 100% rename from Tests/Resources/ConcurrentQueueBefore1.txt rename to Tests/Resources/ConcurrentQueueIsEmptyTests/ConcurrentQueueBefore1.cs diff --git a/Tests/Resources/ConcurrentQueueBefore2.txt b/Tests/Resources/ConcurrentQueueIsEmptyTests/ConcurrentQueueBefore2.cs similarity index 100% rename from Tests/Resources/ConcurrentQueueBefore2.txt rename to Tests/Resources/ConcurrentQueueIsEmptyTests/ConcurrentQueueBefore2.cs diff --git a/Tests/Resources/ConcurrentQueueBefore3.txt b/Tests/Resources/ConcurrentQueueIsEmptyTests/ConcurrentQueueBefore3.cs similarity index 100% rename from Tests/Resources/ConcurrentQueueBefore3.txt rename to Tests/Resources/ConcurrentQueueIsEmptyTests/ConcurrentQueueBefore3.cs diff --git a/Tests/Resources/ConcurrentQueueBefore4.txt b/Tests/Resources/ConcurrentQueueIsEmptyTests/ConcurrentQueueBefore4.cs similarity index 100% rename from Tests/Resources/ConcurrentQueueBefore4.txt rename to Tests/Resources/ConcurrentQueueIsEmptyTests/ConcurrentQueueBefore4.cs diff --git a/Tests/Resources/ConcurrentStack1.txt b/Tests/Resources/ConcurrentStackIsEmptyTests/ConcurrentStack1.cs similarity index 100% rename from Tests/Resources/ConcurrentStack1.txt rename to Tests/Resources/ConcurrentStackIsEmptyTests/ConcurrentStack1.cs diff --git a/Tests/Resources/ConcurrentStack2.txt b/Tests/Resources/ConcurrentStackIsEmptyTests/ConcurrentStack2.cs similarity index 100% rename from Tests/Resources/ConcurrentStack2.txt rename to Tests/Resources/ConcurrentStackIsEmptyTests/ConcurrentStack2.cs diff --git a/Tests/Resources/ConcurrentStack3.txt b/Tests/Resources/ConcurrentStackIsEmptyTests/ConcurrentStack3.cs similarity index 100% rename from Tests/Resources/ConcurrentStack3.txt rename to Tests/Resources/ConcurrentStackIsEmptyTests/ConcurrentStack3.cs diff --git a/Tests/Resources/ConcurrentStackAfter1.txt b/Tests/Resources/ConcurrentStackIsEmptyTests/ConcurrentStackAfter1.cs similarity index 100% rename from Tests/Resources/ConcurrentStackAfter1.txt rename to Tests/Resources/ConcurrentStackIsEmptyTests/ConcurrentStackAfter1.cs diff --git a/Tests/Resources/ConcurrentStackAfter2.txt b/Tests/Resources/ConcurrentStackIsEmptyTests/ConcurrentStackAfter2.cs similarity index 100% rename from Tests/Resources/ConcurrentStackAfter2.txt rename to Tests/Resources/ConcurrentStackIsEmptyTests/ConcurrentStackAfter2.cs diff --git a/Tests/Resources/ConcurrentStackAfter3.txt b/Tests/Resources/ConcurrentStackIsEmptyTests/ConcurrentStackAfter3.cs similarity index 100% rename from Tests/Resources/ConcurrentStackAfter3.txt rename to Tests/Resources/ConcurrentStackIsEmptyTests/ConcurrentStackAfter3.cs diff --git a/Tests/Resources/ConcurrentStackAfter4.txt b/Tests/Resources/ConcurrentStackIsEmptyTests/ConcurrentStackAfter4.cs similarity index 100% rename from Tests/Resources/ConcurrentStackAfter4.txt rename to Tests/Resources/ConcurrentStackIsEmptyTests/ConcurrentStackAfter4.cs diff --git a/Tests/Resources/ConcurrentStackBefore1.txt b/Tests/Resources/ConcurrentStackIsEmptyTests/ConcurrentStackBefore1.cs similarity index 100% rename from Tests/Resources/ConcurrentStackBefore1.txt rename to Tests/Resources/ConcurrentStackIsEmptyTests/ConcurrentStackBefore1.cs diff --git a/Tests/Resources/ConcurrentStackBefore2.txt b/Tests/Resources/ConcurrentStackIsEmptyTests/ConcurrentStackBefore2.cs similarity index 100% rename from Tests/Resources/ConcurrentStackBefore2.txt rename to Tests/Resources/ConcurrentStackIsEmptyTests/ConcurrentStackBefore2.cs diff --git a/Tests/Resources/ConcurrentStackBefore3.txt b/Tests/Resources/ConcurrentStackIsEmptyTests/ConcurrentStackBefore3.cs similarity index 100% rename from Tests/Resources/ConcurrentStackBefore3.txt rename to Tests/Resources/ConcurrentStackIsEmptyTests/ConcurrentStackBefore3.cs diff --git a/Tests/Resources/ConcurrentStackBefore4.txt b/Tests/Resources/ConcurrentStackIsEmptyTests/ConcurrentStackBefore4.cs similarity index 100% rename from Tests/Resources/ConcurrentStackBefore4.txt rename to Tests/Resources/ConcurrentStackIsEmptyTests/ConcurrentStackBefore4.cs diff --git a/Tests/Resources/AsyncEnumerableMethodToArray.txt b/Tests/Resources/EnumerableTests/AsyncEnumerableMethodToArray.cs similarity index 100% rename from Tests/Resources/AsyncEnumerableMethodToArray.txt rename to Tests/Resources/EnumerableTests/AsyncEnumerableMethodToArray.cs diff --git a/Tests/Resources/AsyncEnumerableMethodToArray2.txt b/Tests/Resources/EnumerableTests/AsyncEnumerableMethodToArray2.cs similarity index 100% rename from Tests/Resources/AsyncEnumerableMethodToArray2.txt rename to Tests/Resources/EnumerableTests/AsyncEnumerableMethodToArray2.cs diff --git a/Tests/Resources/EnumerableMethodToArray.txt b/Tests/Resources/EnumerableTests/EnumerableMethodToArray.cs similarity index 100% rename from Tests/Resources/EnumerableMethodToArray.txt rename to Tests/Resources/EnumerableTests/EnumerableMethodToArray.cs diff --git a/Tests/Resources/EnumerablePropertyToArray.txt b/Tests/Resources/EnumerableTests/EnumerablePropertyToArray.cs similarity index 100% rename from Tests/Resources/EnumerablePropertyToArray.txt rename to Tests/Resources/EnumerableTests/EnumerablePropertyToArray.cs diff --git a/Tests/Resources/Constructor1.txt b/Tests/Resources/ListTests/Constructor1.cs similarity index 100% rename from Tests/Resources/Constructor1.txt rename to Tests/Resources/ListTests/Constructor1.cs diff --git a/Tests/Resources/Constructor2.txt b/Tests/Resources/ListTests/Constructor2.cs similarity index 100% rename from Tests/Resources/Constructor2.txt rename to Tests/Resources/ListTests/Constructor2.cs diff --git a/Tests/Resources/Constructor3.txt b/Tests/Resources/ListTests/Constructor3.cs similarity index 100% rename from Tests/Resources/Constructor3.txt rename to Tests/Resources/ListTests/Constructor3.cs diff --git a/Tests/Resources/Constructor4.txt b/Tests/Resources/ListTests/Constructor4.cs similarity index 100% rename from Tests/Resources/Constructor4.txt rename to Tests/Resources/ListTests/Constructor4.cs diff --git a/Tests/Resources/ConstructorAfter.txt b/Tests/Resources/ListTests/ConstructorAfter.cs similarity index 100% rename from Tests/Resources/ConstructorAfter.txt rename to Tests/Resources/ListTests/ConstructorAfter.cs diff --git a/Tests/Resources/ConstructorBefore.txt b/Tests/Resources/ListTests/ConstructorBefore.cs similarity index 100% rename from Tests/Resources/ConstructorBefore.txt rename to Tests/Resources/ListTests/ConstructorBefore.cs diff --git a/Tests/Resources/ListCountAfter.txt b/Tests/Resources/ListTests/ListCountAfter.cs similarity index 100% rename from Tests/Resources/ListCountAfter.txt rename to Tests/Resources/ListTests/ListCountAfter.cs diff --git a/Tests/Resources/ListCountBefore.txt b/Tests/Resources/ListTests/ListCountBefore.cs similarity index 100% rename from Tests/Resources/ListCountBefore.txt rename to Tests/Resources/ListTests/ListCountBefore.cs diff --git a/Tests/Resources/ListInitializer1.txt b/Tests/Resources/ListTests/ListInitializer1.cs similarity index 100% rename from Tests/Resources/ListInitializer1.txt rename to Tests/Resources/ListTests/ListInitializer1.cs diff --git a/Tests/Resources/ListInitializer2.txt b/Tests/Resources/ListTests/ListInitializer2.cs similarity index 100% rename from Tests/Resources/ListInitializer2.txt rename to Tests/Resources/ListTests/ListInitializer2.cs diff --git a/Tests/Resources/ListInitializer3.txt b/Tests/Resources/ListTests/ListInitializer3.cs similarity index 100% rename from Tests/Resources/ListInitializer3.txt rename to Tests/Resources/ListTests/ListInitializer3.cs diff --git a/Tests/Resources/ListInitializer4.txt b/Tests/Resources/ListTests/ListInitializer4.cs similarity index 100% rename from Tests/Resources/ListInitializer4.txt rename to Tests/Resources/ListTests/ListInitializer4.cs diff --git a/Tests/Resources/ListInitializerAfter1.txt b/Tests/Resources/ListTests/ListInitializerAfter1.cs similarity index 100% rename from Tests/Resources/ListInitializerAfter1.txt rename to Tests/Resources/ListTests/ListInitializerAfter1.cs diff --git a/Tests/Resources/ListInitializerAfter2.txt b/Tests/Resources/ListTests/ListInitializerAfter2.cs similarity index 100% rename from Tests/Resources/ListInitializerAfter2.txt rename to Tests/Resources/ListTests/ListInitializerAfter2.cs diff --git a/Tests/Resources/ListInitializerAfter3.txt b/Tests/Resources/ListTests/ListInitializerAfter3.cs similarity index 100% rename from Tests/Resources/ListInitializerAfter3.txt rename to Tests/Resources/ListTests/ListInitializerAfter3.cs diff --git a/Tests/Resources/ListInitializerBefore1.txt b/Tests/Resources/ListTests/ListInitializerBefore1.cs similarity index 100% rename from Tests/Resources/ListInitializerBefore1.txt rename to Tests/Resources/ListTests/ListInitializerBefore1.cs diff --git a/Tests/Resources/ListInitializerBefore2.txt b/Tests/Resources/ListTests/ListInitializerBefore2.cs similarity index 100% rename from Tests/Resources/ListInitializerBefore2.txt rename to Tests/Resources/ListTests/ListInitializerBefore2.cs diff --git a/Tests/Resources/ListInitializerBefore3.txt b/Tests/Resources/ListTests/ListInitializerBefore3.cs similarity index 100% rename from Tests/Resources/ListInitializerBefore3.txt rename to Tests/Resources/ListTests/ListInitializerBefore3.cs diff --git a/Tests/Resources/ListLoopArray1.txt b/Tests/Resources/ListTests/ListLoopArray1.cs similarity index 100% rename from Tests/Resources/ListLoopArray1.txt rename to Tests/Resources/ListTests/ListLoopArray1.cs diff --git a/Tests/Resources/ListLoopArrayForAfter.txt b/Tests/Resources/ListTests/ListLoopArrayForAfter.cs similarity index 100% rename from Tests/Resources/ListLoopArrayForAfter.txt rename to Tests/Resources/ListTests/ListLoopArrayForAfter.cs diff --git a/Tests/Resources/ListLoopArrayForBefore.txt b/Tests/Resources/ListTests/ListLoopArrayForBefore.cs similarity index 100% rename from Tests/Resources/ListLoopArrayForBefore.txt rename to Tests/Resources/ListTests/ListLoopArrayForBefore.cs diff --git a/Tests/Resources/ListLoopArrayForeachAfter.txt b/Tests/Resources/ListTests/ListLoopArrayForeachAfter.cs similarity index 100% rename from Tests/Resources/ListLoopArrayForeachAfter.txt rename to Tests/Resources/ListTests/ListLoopArrayForeachAfter.cs diff --git a/Tests/Resources/ListLoopArrayForeachBefore.txt b/Tests/Resources/ListTests/ListLoopArrayForeachBefore.cs similarity index 100% rename from Tests/Resources/ListLoopArrayForeachBefore.txt rename to Tests/Resources/ListTests/ListLoopArrayForeachBefore.cs diff --git a/Tests/Resources/ListLoopCollectionForeachAfter.txt b/Tests/Resources/ListTests/ListLoopCollectionForeachAfter.cs similarity index 100% rename from Tests/Resources/ListLoopCollectionForeachAfter.txt rename to Tests/Resources/ListTests/ListLoopCollectionForeachAfter.cs diff --git a/Tests/Resources/ListLoopCollectionForeachBefore.txt b/Tests/Resources/ListTests/ListLoopCollectionForeachBefore.cs similarity index 100% rename from Tests/Resources/ListLoopCollectionForeachBefore.txt rename to Tests/Resources/ListTests/ListLoopCollectionForeachBefore.cs diff --git a/Tests/Resources/ListLoopImplicitNewAfter.txt b/Tests/Resources/ListTests/ListLoopImplicitNewAfter.cs similarity index 100% rename from Tests/Resources/ListLoopImplicitNewAfter.txt rename to Tests/Resources/ListTests/ListLoopImplicitNewAfter.cs diff --git a/Tests/Resources/ListLoopImplicitNewBefore.txt b/Tests/Resources/ListTests/ListLoopImplicitNewBefore.cs similarity index 100% rename from Tests/Resources/ListLoopImplicitNewBefore.txt rename to Tests/Resources/ListTests/ListLoopImplicitNewBefore.cs diff --git a/Tests/Resources/ObjectInitializer.txt b/Tests/Resources/ListTests/ObjectInitializer.cs similarity index 100% rename from Tests/Resources/ObjectInitializer.txt rename to Tests/Resources/ListTests/ObjectInitializer.cs diff --git a/Tests/Resources/ObjectInitializerAfter.txt b/Tests/Resources/ListTests/ObjectInitializerAfter.cs similarity index 100% rename from Tests/Resources/ObjectInitializerAfter.txt rename to Tests/Resources/ListTests/ObjectInitializerAfter.cs diff --git a/Tests/Resources/ObjectInitializerBefore.txt b/Tests/Resources/ListTests/ObjectInitializerBefore.cs similarity index 100% rename from Tests/Resources/ObjectInitializerBefore.txt rename to Tests/Resources/ListTests/ObjectInitializerBefore.cs diff --git a/Tests/Resources/ForeachStringToArray.txt b/Tests/Resources/StringTests/ForeachStringToArray.cs similarity index 100% rename from Tests/Resources/ForeachStringToArray.txt rename to Tests/Resources/StringTests/ForeachStringToArray.cs diff --git a/Tests/Resources/ForeachStringToArrayAfter.txt b/Tests/Resources/StringTests/ForeachStringToArrayAfter.cs similarity index 100% rename from Tests/Resources/ForeachStringToArrayAfter.txt rename to Tests/Resources/StringTests/ForeachStringToArrayAfter.cs diff --git a/Tests/Resources/ForeachStringToArrayBefore.txt b/Tests/Resources/StringTests/ForeachStringToArrayBefore.cs similarity index 100% rename from Tests/Resources/ForeachStringToArrayBefore.txt rename to Tests/Resources/StringTests/ForeachStringToArrayBefore.cs diff --git a/Tests/Resources/ForeachStringToCharArray.txt b/Tests/Resources/StringTests/ForeachStringToCharArray.cs similarity index 100% rename from Tests/Resources/ForeachStringToCharArray.txt rename to Tests/Resources/StringTests/ForeachStringToCharArray.cs diff --git a/Tests/Resources/ForeachStringToCharArrayAfter.txt b/Tests/Resources/StringTests/ForeachStringToCharArrayAfter.cs similarity index 100% rename from Tests/Resources/ForeachStringToCharArrayAfter.txt rename to Tests/Resources/StringTests/ForeachStringToCharArrayAfter.cs diff --git a/Tests/Resources/ForeachStringToCharArrayBefore.txt b/Tests/Resources/StringTests/ForeachStringToCharArrayBefore.cs similarity index 100% rename from Tests/Resources/ForeachStringToCharArrayBefore.txt rename to Tests/Resources/StringTests/ForeachStringToCharArrayBefore.cs diff --git a/Tests/Resources/ForeachStringToListAfter.txt b/Tests/Resources/StringTests/ForeachStringToListAfter.cs similarity index 100% rename from Tests/Resources/ForeachStringToListAfter.txt rename to Tests/Resources/StringTests/ForeachStringToListAfter.cs diff --git a/Tests/Resources/ForeachStringToListBefore.txt b/Tests/Resources/StringTests/ForeachStringToListBefore.cs similarity index 100% rename from Tests/Resources/ForeachStringToListBefore.txt rename to Tests/Resources/StringTests/ForeachStringToListBefore.cs diff --git a/Tests/Resources/StringJoin1.txt b/Tests/Resources/StringTests/StringJoin1.cs similarity index 100% rename from Tests/Resources/StringJoin1.txt rename to Tests/Resources/StringTests/StringJoin1.cs diff --git a/Tests/Resources/StringJoinAfter.txt b/Tests/Resources/StringTests/StringJoinAfter.cs similarity index 100% rename from Tests/Resources/StringJoinAfter.txt rename to Tests/Resources/StringTests/StringJoinAfter.cs diff --git a/Tests/Resources/StringJoinBefore.txt b/Tests/Resources/StringTests/StringJoinBefore.cs similarity index 100% rename from Tests/Resources/StringJoinBefore.txt rename to Tests/Resources/StringTests/StringJoinBefore.cs diff --git a/Tests/Resources/StringListConstructor1.txt b/Tests/Resources/StringTests/StringListConstructor1.cs similarity index 100% rename from Tests/Resources/StringListConstructor1.txt rename to Tests/Resources/StringTests/StringListConstructor1.cs diff --git a/Tests/Resources/StringListConstructor2.txt b/Tests/Resources/StringTests/StringListConstructor2.cs similarity index 100% rename from Tests/Resources/StringListConstructor2.txt rename to Tests/Resources/StringTests/StringListConstructor2.cs diff --git a/Tests/Resources/StringListConstructorAfter.txt b/Tests/Resources/StringTests/StringListConstructorAfter.cs similarity index 100% rename from Tests/Resources/StringListConstructorAfter.txt rename to Tests/Resources/StringTests/StringListConstructorAfter.cs diff --git a/Tests/Resources/StringListConstructorBefore1.txt b/Tests/Resources/StringTests/StringListConstructorBefore1.cs similarity index 100% rename from Tests/Resources/StringListConstructorBefore1.txt rename to Tests/Resources/StringTests/StringListConstructorBefore1.cs diff --git a/Tests/Resources/StringListConstructorBefore2.txt b/Tests/Resources/StringTests/StringListConstructorBefore2.cs similarity index 100% rename from Tests/Resources/StringListConstructorBefore2.txt rename to Tests/Resources/StringTests/StringListConstructorBefore2.cs diff --git a/Tests/StringTests/ListFromStringTests.cs b/Tests/StringTests/ListFromStringTests.cs index 4c0ee8c..194efec 100644 --- a/Tests/StringTests/ListFromStringTests.cs +++ b/Tests/StringTests/ListFromStringTests.cs @@ -13,7 +13,7 @@ public class ListFromStringTests : CSharpCodeFixTestenable + + + + + @@ -18,149 +23,4 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From adba729c207ea4142867cf8a68221763933ef849 Mon Sep 17 00:00:00 2001 From: Rogatnev Sergey Date: Sat, 18 Jul 2026 15:17:06 +0700 Subject: [PATCH 2/6] dictionary lookup diagnostics --- .../CI0010/DictionaryLookupDiagnostic.cs | 217 ++++++++++++++++++ Collections.Analyzer/Resources.Designer.cs | 181 +++++---------- Collections.Analyzer/Resources.resx | 6 + Collections.Analyzer/Resources.ru.resx | 6 + Examples/DictionaryLookup/DictionaryLookup.cs | 85 +++++++ .../DictionaryLookupTests.cs | 68 ++++++ .../DictionaryLookupVerifier.cs | 19 ++ .../DictionaryLookup/DictionaryLookup1.cs | 23 ++ .../DictionaryLookup/DictionaryLookup2.cs | 20 ++ .../DictionaryLookup/DictionaryLookup3.cs | 24 ++ .../DictionaryLookup/DictionaryLookup4.cs | 30 +++ .../DictionaryLookup/DictionaryLookup5.cs | 22 ++ .../DictionaryLookup/DictionaryLookup6.cs | 21 ++ 13 files changed, 599 insertions(+), 123 deletions(-) create mode 100644 Collections.Analyzer/Diagnostics/CI0010/DictionaryLookupDiagnostic.cs create mode 100644 Examples/DictionaryLookup/DictionaryLookup.cs create mode 100644 Tests/DictionaryLookupTests/DictionaryLookupTests.cs create mode 100644 Tests/DictionaryLookupTests/DictionaryLookupVerifier.cs create mode 100644 Tests/Resources/DictionaryLookup/DictionaryLookup1.cs create mode 100644 Tests/Resources/DictionaryLookup/DictionaryLookup2.cs create mode 100644 Tests/Resources/DictionaryLookup/DictionaryLookup3.cs create mode 100644 Tests/Resources/DictionaryLookup/DictionaryLookup4.cs create mode 100644 Tests/Resources/DictionaryLookup/DictionaryLookup5.cs create mode 100644 Tests/Resources/DictionaryLookup/DictionaryLookup6.cs diff --git a/Collections.Analyzer/Diagnostics/CI0010/DictionaryLookupDiagnostic.cs b/Collections.Analyzer/Diagnostics/CI0010/DictionaryLookupDiagnostic.cs new file mode 100644 index 0000000..c93315f --- /dev/null +++ b/Collections.Analyzer/Diagnostics/CI0010/DictionaryLookupDiagnostic.cs @@ -0,0 +1,217 @@ +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Collections.Analyzer.Diagnostics.CI0010; + +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class DictionaryLookupDiagnostic : DiagnosticAnalyzer +{ + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + internal static readonly DiagnosticDescriptor Rule = new( + "CI0010", + Resources.CI0010_Title, + Resources.CI0010_MessageFormat, + Categories.Performance, + DiagnosticSeverity.Warning, + true + ); + + public override void Initialize(AnalysisContext context) + { + context.EnableConcurrentExecution(); + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + + context.RegisterSyntaxNodeAction(Analyze, SyntaxKind.ForEachStatement, SyntaxKind.ForStatement); + } + + private static void Analyze(SyntaxNodeAnalysisContext context) + { + var loopBody = context.Node switch + { + ForEachStatementSyntax foreachStatement => foreachStatement.Statement, + ForStatementSyntax forStatement => forStatement.Statement, + _ => null + }; + + if (loopBody == null) return; + + var loopVariables = GetLoopVariables(context.Node); + if (loopVariables.Length == 0) return; + + var invocations = loopBody.DescendantNodes() + .OfType(); + + foreach (var invocation in invocations) + { + if (invocation.Expression is not MemberAccessExpressionSyntax memberAccess) + continue; + + var methodName = memberAccess.Name.Identifier.Text; + if (methodName is not ( + nameof(Enumerable.First) or + nameof(Enumerable.FirstOrDefault) or + nameof(Enumerable.Single) or + nameof(Enumerable.SingleOrDefault) or + nameof(Enumerable.Any))) continue; + + if (invocation.ArgumentList.Arguments.Count != 1) continue; + var argument = invocation.ArgumentList.Arguments[0]; + + if (argument.Expression is not LambdaExpressionSyntax lambda) + continue; + + if (!IsSearchingByLoopVariable(lambda, context.SemanticModel, context.Node, loopVariables)) + continue; + + // Check if the collection is defined outside the loop + var collectionExpression = memberAccess.Expression; + if (!IsDefinedOutside(context, collectionExpression, context.Node)) + continue; + + var collectionName = collectionExpression.ToString(); + context.ReportDiagnostic(Diagnostic.Create(Rule, invocation.GetLocation(), collectionName)); + } + } + + + private static string[] GetLoopVariables(SyntaxNode loop) + { + return loop switch + { + ForEachStatementSyntax foreachStatement => new[] { foreachStatement.Identifier.Text }, + ForStatementSyntax { Declaration: not null } forStatement => forStatement.Declaration.Variables + .Select(v => v.Identifier.Text) + .ToArray(), + _ => System.Array.Empty() + }; + } + + private static bool IsSearchingByLoopVariable(LambdaExpressionSyntax lambda, SemanticModel semanticModel, SyntaxNode loop, string[] loopVariables) + { + // lambda is something like x => x.Id == key + var expression = lambda is SimpleLambdaExpressionSyntax simple ? simple.Body : (lambda as ParenthesizedLambdaExpressionSyntax)?.Body; + + if (expression is not BinaryExpressionSyntax binary) return false; + if (!binary.IsKind(SyntaxKind.EqualsExpression)) return false; + + // Check if one side is a property access on lambda parameter and other side is a loop variable + return (IsPropertyAccessOnParameter(lambda, binary.Left) && IsLoopVariableOrMember(binary.Right, semanticModel, loop, loopVariables)) || + (IsPropertyAccessOnParameter(lambda, binary.Right) && IsLoopVariableOrMember(binary.Left, semanticModel, loop, loopVariables)); + } + + private static bool IsPropertyAccessOnParameter(LambdaExpressionSyntax lambda, ExpressionSyntax expression) + { + var parameterName = lambda switch + { + SimpleLambdaExpressionSyntax simple => simple.Parameter.Identifier.Text, + ParenthesizedLambdaExpressionSyntax paren => paren.ParameterList.Parameters.FirstOrDefault()?.Identifier.Text, + _ => null + }; + + if (parameterName == null) return false; + + if (expression is MemberAccessExpressionSyntax memberAccess) + { + return memberAccess.Expression is IdentifierNameSyntax id && id.Identifier.Text == parameterName; + } + + if (expression is IdentifierNameSyntax identifier) + { + return identifier.Identifier.Text == parameterName; + } + + return false; + } + + private static bool IsLoopVariableOrMember(ExpressionSyntax expression, SemanticModel semanticModel, SyntaxNode loop, string[] loopVariables) + { + if (expression is IdentifierNameSyntax id) + { + var name = id.Identifier.Text; + if (loopVariables.Contains(name)) + return true; + + // Check if it's a local variable defined inside the loop + var symbol = semanticModel.GetSymbolInfo(id).Symbol; + if (symbol is ILocalSymbol localSymbol) + { + var loopSpan = loop.GetLocation().SourceSpan; + var isInsideLoop = localSymbol.Locations.Any(loc => loc.IsInSource && loopSpan.Contains(loc.SourceSpan)); + + if (isInsideLoop) + { + var declarator = localSymbol.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax() as VariableDeclaratorSyntax; + if (declarator?.Initializer != null) + { + return IsLoopVariableOrMember(declarator.Initializer.Value, semanticModel, loop, loopVariables); + } + } + } + } + + if (expression is BinaryExpressionSyntax binary) + { + return IsLoopVariableOrMember(binary.Left, semanticModel, loop, loopVariables) || IsLoopVariableOrMember(binary.Right, semanticModel, loop, loopVariables); + } + + if (expression is MemberAccessExpressionSyntax memberAccess) + { + // Support both key and keys[i] + if (IsLoopVariableOrMember(memberAccess.Expression, semanticModel, loop, loopVariables)) + return true; + } + + if (expression is ElementAccessExpressionSyntax elementAccess) + { + if (IsLoopVariableOrMember(elementAccess.Expression, semanticModel, loop, loopVariables)) + return true; + + // Check if indexer uses loop variable: keys[i] + foreach(var arg in elementAccess.ArgumentList.Arguments) + { + if (IsLoopVariableOrMember(arg.Expression, semanticModel, loop, loopVariables)) return true; + } + } + + if (expression is ParenthesizedExpressionSyntax parenthesized) + { + return IsLoopVariableOrMember(parenthesized.Expression, semanticModel, loop, loopVariables); + } + + return false; + } + + private static bool IsDefinedOutside(SyntaxNodeAnalysisContext context, ExpressionSyntax expression, SyntaxNode loop) + { + if (expression is MemberAccessExpressionSyntax or InvocationExpressionSyntax) + { + return true; + } + + var symbolInfo = context.SemanticModel.GetSymbolInfo(expression); + var symbol = symbolInfo.Symbol ?? symbolInfo.CandidateSymbols.FirstOrDefault(); + + if (symbol == null) + { + // If we can't resolve the symbol, assume it's defined outside + return true; + } + + var loopLocation = loop.GetLocation().SourceSpan; + foreach (var location in symbol.Locations) + { + if (location.IsInSource && !loopLocation.Contains(location.SourceSpan)) + { + return true; + } + } + + // If the symbol is a parameter of the containing method, it's defined outside the loop + return symbol.Kind == SymbolKind.Parameter; + } +} diff --git a/Collections.Analyzer/Resources.Designer.cs b/Collections.Analyzer/Resources.Designer.cs index a5279f0..82f793a 100644 --- a/Collections.Analyzer/Resources.Designer.cs +++ b/Collections.Analyzer/Resources.Designer.cs @@ -11,46 +11,32 @@ namespace Collections.Analyzer { using System; - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { - private static global::System.Resources.ResourceManager resourceMan; + private static System.Resources.ResourceManager resourceMan; - private static global::System.Globalization.CultureInfo resourceCulture; + private static System.Globalization.CultureInfo resourceCulture; - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal Resources() { } - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Resources.ResourceManager ResourceManager { get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Collections.Analyzer.Resources", typeof(Resources).Assembly); + if (object.Equals(null, resourceMan)) { + System.Resources.ResourceManager temp = new System.Resources.ResourceManager("Collections.Analyzer.Resources", typeof(Resources).Assembly); resourceMan = temp; } return resourceMan; } } - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Globalization.CultureInfo Culture { get { return resourceCulture; } @@ -59,193 +45,142 @@ internal Resources() { } } - /// - /// Looks up a localized string similar to Add ToCharArray call. - /// - internal static string AddToCharArray { + internal static string CI0001_Title { get { - return ResourceManager.GetString("AddToCharArray", resourceCulture); + return ResourceManager.GetString("CI0001_Title", resourceCulture); } } - /// - /// Looks up a localized string similar to Redundant string conversion. - /// - internal static string CI0001_Title { + internal static string RemoveRedundantCall { get { - return ResourceManager.GetString("CI0001_Title", resourceCulture); + return ResourceManager.GetString("RemoveRedundantCall", resourceCulture); } } - /// - /// Looks up a localized string similar to Redundant array conversion. - /// internal static string CI0002_Title { get { return ResourceManager.GetString("CI0002_Title", resourceCulture); } } - /// - /// Looks up a localized string similar to Redundant enumerable conversion. - /// internal static string CI0003_Title { get { return ResourceManager.GetString("CI0003_Title", resourceCulture); } } - /// - /// Looks up a localized string similar to Not optimal List<char> constructor usage. - /// internal static string CI0004_Title { get { return ResourceManager.GetString("CI0004_Title", resourceCulture); } } - /// - /// Looks up a localized string similar to Not optimal count of collection items. - /// - internal static string CI0005_Title { + internal static string AddToCharArray { get { - return ResourceManager.GetString("CI0005_Title", resourceCulture); + return ResourceManager.GetString("AddToCharArray", resourceCulture); } } - /// - /// Looks up a localized string similar to Collection initializer is used without capacity. - /// - internal static string CI0006_Title { + internal static string ReplaceWithToCharArray { get { - return ResourceManager.GetString("CI0006_Title", resourceCulture); + return ResourceManager.GetString("ReplaceWithToCharArray", resourceCulture); } } - /// - /// Looks up a localized string similar to Any() is blocking on concurrent collections. - /// - internal static string CI0007_Title { + internal static string ReplaceWithCountCall { get { - return ResourceManager.GetString("CI0007_Title", resourceCulture); + return ResourceManager.GetString("ReplaceWithCountCall", resourceCulture); } } - /// - /// Looks up a localized string similar to Replace array with HashSet. - /// - internal static string CI0008_CodeFixTitle { + internal static string CI0005_Title { get { - return ResourceManager.GetString("CI0008_CodeFixTitle", resourceCulture); + return ResourceManager.GetString("CI0005_Title", resourceCulture); } } - /// - /// Looks up a localized string similar to HashSet<T> provides O(1) lookup time for Contains() operation, while array has O(n) time complexity. - /// - internal static string CI0008_Description { + internal static string CI0006_Title { get { - return ResourceManager.GetString("CI0008_Description", resourceCulture); + return ResourceManager.GetString("CI0006_Title", resourceCulture); } } - /// - /// Looks up a localized string similar to Array '{0}' is used with Contains() method. Consider using HashSet&lt;{1}&gt; for better performance. - /// - internal static string CI0008_MessageFormat { + internal static string NotEnoughCapacity { get { - return ResourceManager.GetString("CI0008_MessageFormat", resourceCulture); + return ResourceManager.GetString("NotEnoughCapacity", resourceCulture); } } - /// - /// Looks up a localized string similar to Consider using HashSet instead of array for Contains operations. - /// - internal static string CI0008_Title { + internal static string SetActualCapacity { get { - return ResourceManager.GetString("CI0008_Title", resourceCulture); + return ResourceManager.GetString("SetActualCapacity", resourceCulture); } } - /// - /// Looks up a localized string similar to Set capacity to '{0}'. - /// - internal static string CI0009_MessageFormat { + internal static string CI0007_Title { get { - return ResourceManager.GetString("CI0009_MessageFormat", resourceCulture); + return ResourceManager.GetString("CI0007_Title", resourceCulture); } } - /// - /// Looks up a localized string similar to List capacity should be set based on the source collection. - /// - internal static string CI0009_Title { + internal static string ReplaceWithIsEmpty { get { - return ResourceManager.GetString("CI0009_Title", resourceCulture); + return ResourceManager.GetString("ReplaceWithIsEmpty", resourceCulture); } } - /// - /// Looks up a localized string similar to Initial collection capacity is less than amount of actual values. - /// - internal static string NotEnoughCapacity { + internal static string CI0008_Title { get { - return ResourceManager.GetString("NotEnoughCapacity", resourceCulture); + return ResourceManager.GetString("CI0008_Title", resourceCulture); } } - /// - /// Looks up a localized string similar to Remove method call {0}. - /// - internal static string RemoveRedundantCall { + internal static string CI0008_MessageFormat { get { - return ResourceManager.GetString("RemoveRedundantCall", resourceCulture); + return ResourceManager.GetString("CI0008_MessageFormat", resourceCulture); } } - /// - /// Looks up a localized string similar to Replace with Count call. - /// - internal static string ReplaceWithCountCall { + internal static string CI0008_Description { get { - return ResourceManager.GetString("ReplaceWithCountCall", resourceCulture); + return ResourceManager.GetString("CI0008_Description", resourceCulture); } } - /// - /// Looks up a localized string similar to Replace with IsEmpty. - /// - internal static string ReplaceWithIsEmpty { + internal static string CI0008_CodeFixTitle { get { - return ResourceManager.GetString("ReplaceWithIsEmpty", resourceCulture); + return ResourceManager.GetString("CI0008_CodeFixTitle", resourceCulture); } } - /// - /// Looks up a localized string similar to Replace with ToCharArray call. - /// - internal static string ReplaceWithToCharArray { + internal static string CI0009_Title { get { - return ResourceManager.GetString("ReplaceWithToCharArray", resourceCulture); + return ResourceManager.GetString("CI0009_Title", resourceCulture); } } - /// - /// Looks up a localized string similar to Set actual capacity. - /// - internal static string SetActualCapacity { + internal static string CI0009_MessageFormat { get { - return ResourceManager.GetString("SetActualCapacity", resourceCulture); + return ResourceManager.GetString("CI0009_MessageFormat", resourceCulture); } } - /// - /// Looks up a localized string similar to Set capacity based on source collection. - /// internal static string SetCapacity { get { return ResourceManager.GetString("SetCapacity", resourceCulture); } } + + internal static string CI0010_Title { + get { + return ResourceManager.GetString("CI0010_Title", resourceCulture); + } + } + + internal static string CI0010_MessageFormat { + get { + return ResourceManager.GetString("CI0010_MessageFormat", resourceCulture); + } + } } } diff --git a/Collections.Analyzer/Resources.resx b/Collections.Analyzer/Resources.resx index cae2846..66e8e16 100644 --- a/Collections.Analyzer/Resources.resx +++ b/Collections.Analyzer/Resources.resx @@ -86,4 +86,10 @@ Set capacity based on source collection + + Consider using Dictionary instead of linear search in a loop + + + Collection '{0}' is used for linear search in a loop. Consider converting it to a Dictionary for better performance + \ No newline at end of file diff --git a/Collections.Analyzer/Resources.ru.resx b/Collections.Analyzer/Resources.ru.resx index 88a1ad7..3454792 100644 --- a/Collections.Analyzer/Resources.ru.resx +++ b/Collections.Analyzer/Resources.ru.resx @@ -78,4 +78,10 @@ Указать размер списка на основе исходной коллекции + + Рассмотрите возможность использования Dictionary вместо линейного поиска в цикле + + + Коллекция '{0}' используется для линейного поиска в цикле. Рассмотрите возможность преобразования её в Dictionary для повышения производительности + \ No newline at end of file diff --git a/Examples/DictionaryLookup/DictionaryLookup.cs b/Examples/DictionaryLookup/DictionaryLookup.cs new file mode 100644 index 0000000..e5088e8 --- /dev/null +++ b/Examples/DictionaryLookup/DictionaryLookup.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +class TestClass +{ + class MyClass + { + public Guid Id { get; set; } + public string Name { get; set; } + } + void TestMethod1() + { + var list = new MyClass[10]; + var keys = new Guid[10]; + + foreach (var key in keys) + { + var item = list.FirstOrDefault(x => x.Id == key); + Console.Write(item); + } + } + + void TestMethod2(MyClass[] list, Guid[] keys) + { + foreach (var key in keys) + { + var item = list.SingleOrDefault(x => x.Id == key); + Console.Write(item); + } + } + + private static readonly Guid[] Keys = new Guid[10]; + private static readonly IEnumerable List = new MyClass[10]; + + public static void TestMethod3() + { + foreach (var key in Keys) + { + var item = List.First(x => x.Id == key); + Console.Write(item); + } + } + + public static void TestMethod4() + { + var list = GetData().Where(x => x != null).ToArray(); + + for (var i = 0; i < Keys.Length; i++) + { + var item = list.Single(x => x.Id == Keys[i]); + Console.Write(item); + } + } + + void TestMethod5() + { + var list = new MyClass[10]; + var keys = new Guid[10]; + + foreach (var key in keys) + { + var id = key; + var item = list.FirstOrDefault(x => x.Id == id); + Console.Write(item); + } + } + + void TestMethod6() + { + var list = new MyClass[10]; + var keys = new Tuple[10]; + + foreach (var key in keys) + { + var item = list.FirstOrDefault(x => x.Id == key.Item1); + Console.Write(item); + } + } + + private static IEnumerable GetData() + { + return Enumerable.Empty(); + } +} \ No newline at end of file diff --git a/Tests/DictionaryLookupTests/DictionaryLookupTests.cs b/Tests/DictionaryLookupTests/DictionaryLookupTests.cs new file mode 100644 index 0000000..6ac7cc9 --- /dev/null +++ b/Tests/DictionaryLookupTests/DictionaryLookupTests.cs @@ -0,0 +1,68 @@ +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Testing; +using NUnit.Framework; + +namespace Tests.DictionaryLookupTests; + +public class DictionaryLookupTests +{ + [Test] + public Task DictionaryLookup1Test() + { + var code = ResourceReader.ReadFromFile("DictionaryLookup1.cs"); + + return DictionaryLookupVerifier.VerifyAnalyzerAsync(code, DiagnosticResult.CompilerWarning("CI0010") + .WithSpan(19, 24, 19, 61) + .WithArguments("list")); + } + + [Test] + public Task DictionaryLookup2Test() + { + var code = ResourceReader.ReadFromFile("DictionaryLookup2.cs"); + + return DictionaryLookupVerifier.VerifyAnalyzerAsync(code, DiagnosticResult.CompilerWarning("CI0010") + .WithSpan(16, 24, 16, 62) + .WithArguments("list")); + } + + [Test] + public Task DictionaryLookup3Test() + { + var code = ResourceReader.ReadFromFile("DictionaryLookup3.cs"); + + return DictionaryLookupVerifier.VerifyAnalyzerAsync(code, DiagnosticResult.CompilerWarning("CI0010") + .WithSpan(20, 24, 20, 52) + .WithArguments("list")); + } + + [Test] + public Task DictionaryLookup4Test() + { + var code = ResourceReader.ReadFromFile("DictionaryLookup4.cs"); + + return DictionaryLookupVerifier.VerifyAnalyzerAsync(code, DiagnosticResult.CompilerWarning("CI0010") + .WithSpan(21, 24, 21, 57) + .WithArguments("list")); + } + + [Test] + public Task DictionaryLookup5Test() + { + var code = ResourceReader.ReadFromFile("DictionaryLookup5.cs"); + + return DictionaryLookupVerifier.VerifyAnalyzerAsync(code, DiagnosticResult.CompilerWarning("CI0010") + .WithSpan(16, 17, 16, 64) + .WithArguments("actual")); + } + + [Test] + public Task DictionaryLookup6Test() + { + var code = ResourceReader.ReadFromFile("DictionaryLookup6.cs"); + + return DictionaryLookupVerifier.VerifyAnalyzerAsync(code, DiagnosticResult.CompilerWarning("CI0010") + .WithSpan(15, 25, 15, 82) + .WithArguments("groupedRCStatistics")); + } +} diff --git a/Tests/DictionaryLookupTests/DictionaryLookupVerifier.cs b/Tests/DictionaryLookupTests/DictionaryLookupVerifier.cs new file mode 100644 index 0000000..5786cf5 --- /dev/null +++ b/Tests/DictionaryLookupTests/DictionaryLookupVerifier.cs @@ -0,0 +1,19 @@ +using System.Threading.Tasks; +using Collections.Analyzer.Diagnostics.CI0010; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Testing; + +namespace Tests.DictionaryLookupTests; + +public static class DictionaryLookupVerifier +{ + public static Task VerifyAnalyzerAsync(string source, params DiagnosticResult[] expected) + { + var test = new CSharpAnalyzerTest + { + TestCode = source + }; + test.ExpectedDiagnostics.AddRange(expected); + return test.RunAsync(); + } +} diff --git a/Tests/Resources/DictionaryLookup/DictionaryLookup1.cs b/Tests/Resources/DictionaryLookup/DictionaryLookup1.cs new file mode 100644 index 0000000..54aee9e --- /dev/null +++ b/Tests/Resources/DictionaryLookup/DictionaryLookup1.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +class TestClass +{ + class MyClass + { + public Guid Id { get; set; } + public string Name { get; set; } + } + void TestMethod() + { + var list = new MyClass[10]; + var keys = new Guid[10]; + + foreach (var key in keys) + { + var item = list.FirstOrDefault(x => x.Id == key); + Console.Write(item); + } + } +} \ No newline at end of file diff --git a/Tests/Resources/DictionaryLookup/DictionaryLookup2.cs b/Tests/Resources/DictionaryLookup/DictionaryLookup2.cs new file mode 100644 index 0000000..41fcfc3 --- /dev/null +++ b/Tests/Resources/DictionaryLookup/DictionaryLookup2.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +class TestClass +{ + class MyClass + { + public Guid Id { get; set; } + public string Name { get; set; } + } + void TestMethod(MyClass[] list, Guid[] keys) + { + foreach (var key in keys) + { + var item = list.SingleOrDefault(x => x.Id == key); + Console.Write(item); + } + } +} \ No newline at end of file diff --git a/Tests/Resources/DictionaryLookup/DictionaryLookup3.cs b/Tests/Resources/DictionaryLookup/DictionaryLookup3.cs new file mode 100644 index 0000000..67894e9 --- /dev/null +++ b/Tests/Resources/DictionaryLookup/DictionaryLookup3.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +class TestClass +{ + class MyClass + { + public Guid Id { get; set; } + public string Name { get; set; } + } + + private static IEnumerable list = new MyClass[10]; + private static IEnumerable keys = new Guid[10]; + + public static void TestMethod() + { + foreach (var key in keys) + { + var item = list.First(x => x.Id == key); + Console.Write(item); + } + } +} \ No newline at end of file diff --git a/Tests/Resources/DictionaryLookup/DictionaryLookup4.cs b/Tests/Resources/DictionaryLookup/DictionaryLookup4.cs new file mode 100644 index 0000000..c52940a --- /dev/null +++ b/Tests/Resources/DictionaryLookup/DictionaryLookup4.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +class TestClass +{ + public class MyClass + { + public Guid Id { get; set; } + public string Name { get; set; } + } + + private static Guid[] keys = new Guid[10]; + + public static void TestMethod() + { + var list = GetData().Where(x => x != null).ToArray(); + + for (int i = 0; i < keys.Length; i++) + { + var item = list.Single(x => x.Id == keys[i]); + Console.Write(item); + } + } + + public static IEnumerable GetData() + { + return Enumerable.Empty(); + } +} \ No newline at end of file diff --git a/Tests/Resources/DictionaryLookup/DictionaryLookup5.cs b/Tests/Resources/DictionaryLookup/DictionaryLookup5.cs new file mode 100644 index 0000000..2b6f95d --- /dev/null +++ b/Tests/Resources/DictionaryLookup/DictionaryLookup5.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +class TestClass +{ + class Requisite + { + public int DocType { get; set; } + } + + void TestMethod(List actual, List requisites) + { + foreach (var requisite in requisites) + { + if (actual.Any(a => a.DocType == requisite.DocType)) + { + Console.WriteLine("Found"); + } + } + } +} diff --git a/Tests/Resources/DictionaryLookup/DictionaryLookup6.cs b/Tests/Resources/DictionaryLookup/DictionaryLookup6.cs new file mode 100644 index 0000000..d76203b --- /dev/null +++ b/Tests/Resources/DictionaryLookup/DictionaryLookup6.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +public class TestClass +{ + public string[] ReadRCStatisticByYearAndFormType() + { + var groupedRCStatistics = new List>(); + + var result = new string[12]; + for (int i = 0; i < 12; i++) + { + var month = i + 1; + var tuple = groupedRCStatistics.FirstOrDefault(p => p.Item1 == month); + result[i] = tuple != null ? tuple.Item2 : "t"; + } + + return result; + } +} From 193561181276c77858f2dd385adeca7973131988 Mon Sep 17 00:00:00 2001 From: Rogatnev Sergey Date: Sat, 18 Jul 2026 18:13:48 +0700 Subject: [PATCH 3/6] support linq-methods --- .../CI0010/DictionaryLookupDiagnostic.cs | 109 ++++++++++++++---- Examples/DictionaryLookup/DictionaryLookup.cs | 8 ++ .../DictionaryLookupTests.cs | 10 ++ .../DictionaryLookup/DictionaryLookup7.cs | 19 +++ Tests/Tests.csproj | 1 + 5 files changed, 124 insertions(+), 23 deletions(-) create mode 100644 Tests/Resources/DictionaryLookup/DictionaryLookup7.cs diff --git a/Collections.Analyzer/Diagnostics/CI0010/DictionaryLookupDiagnostic.cs b/Collections.Analyzer/Diagnostics/CI0010/DictionaryLookupDiagnostic.cs index c93315f..81d1eaa 100644 --- a/Collections.Analyzer/Diagnostics/CI0010/DictionaryLookupDiagnostic.cs +++ b/Collections.Analyzer/Diagnostics/CI0010/DictionaryLookupDiagnostic.cs @@ -26,11 +26,17 @@ public override void Initialize(AnalysisContext context) context.EnableConcurrentExecution(); context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); - context.RegisterSyntaxNodeAction(Analyze, SyntaxKind.ForEachStatement, SyntaxKind.ForStatement); + context.RegisterSyntaxNodeAction(Analyze, SyntaxKind.ForEachStatement, SyntaxKind.ForStatement, SyntaxKind.InvocationExpression); } private static void Analyze(SyntaxNodeAnalysisContext context) { + if (context.Node is InvocationExpressionSyntax invocationNode) + { + AnalyzeInvocation(context, invocationNode); + return; + } + var loopBody = context.Node switch { ForEachStatementSyntax foreachStatement => foreachStatement.Statement, @@ -48,34 +54,91 @@ private static void Analyze(SyntaxNodeAnalysisContext context) foreach (var invocation in invocations) { - if (invocation.Expression is not MemberAccessExpressionSyntax memberAccess) - continue; + AnalyzeInvocationWithVariables(context, invocation, context.Node, loopVariables); + } + } + + private static void AnalyzeInvocation(SyntaxNodeAnalysisContext context, InvocationExpressionSyntax invocation) + { + if (invocation.Expression is not MemberAccessExpressionSyntax memberAccess) return; + + var methodName = memberAccess.Name.Identifier.Text; + if (methodName is not ( + nameof(Enumerable.Select) or + nameof(Enumerable.Where) or + nameof(Enumerable.Any) or + nameof(Enumerable.All) or + nameof(Enumerable.Count) or + nameof(Enumerable.First) or + nameof(Enumerable.FirstOrDefault) or + nameof(Enumerable.Single) or + nameof(Enumerable.SingleOrDefault))) + return; + + if (invocation.ArgumentList.Arguments.Count == 0) + return; + var argument = invocation.ArgumentList.Arguments[0]; + + if (argument.Expression is not LambdaExpressionSyntax lambda) + return; + + var loopVariables = GetLambdaParameters(lambda); + if (loopVariables.Length == 0) + return; + + var body = lambda is SimpleLambdaExpressionSyntax simple ? simple.Body : (lambda as ParenthesizedLambdaExpressionSyntax)?.Body; + if (body == null) + return; + + var innerInvocations = body.DescendantNodesAndSelf() + .OfType(); + + foreach (var innerInvocation in innerInvocations) + { + AnalyzeInvocationWithVariables(context, innerInvocation, invocation, loopVariables); + } + } + + private static void AnalyzeInvocationWithVariables(SyntaxNodeAnalysisContext context, InvocationExpressionSyntax invocation, SyntaxNode scopeNode, string[] loopVariables) + { + if (invocation.Expression is not MemberAccessExpressionSyntax memberAccess) return; - var methodName = memberAccess.Name.Identifier.Text; - if (methodName is not ( - nameof(Enumerable.First) or - nameof(Enumerable.FirstOrDefault) or - nameof(Enumerable.Single) or - nameof(Enumerable.SingleOrDefault) or - nameof(Enumerable.Any))) continue; + var methodName = memberAccess.Name.Identifier.Text; + if (methodName is not ( + nameof(Enumerable.First) or + nameof(Enumerable.FirstOrDefault) or + nameof(Enumerable.Single) or + nameof(Enumerable.SingleOrDefault) or + nameof(Enumerable.Any))) return; - if (invocation.ArgumentList.Arguments.Count != 1) continue; - var argument = invocation.ArgumentList.Arguments[0]; + if (invocation.ArgumentList.Arguments.Count != 1) return; + var argument = invocation.ArgumentList.Arguments[0]; - if (argument.Expression is not LambdaExpressionSyntax lambda) - continue; + if (argument.Expression is not LambdaExpressionSyntax lambda) return; - if (!IsSearchingByLoopVariable(lambda, context.SemanticModel, context.Node, loopVariables)) - continue; + if (!IsSearchingByLoopVariable(lambda, context.SemanticModel, scopeNode, loopVariables)) return; - // Check if the collection is defined outside the loop - var collectionExpression = memberAccess.Expression; - if (!IsDefinedOutside(context, collectionExpression, context.Node)) - continue; + // Check if the collection is defined outside the loop + var collectionExpression = memberAccess.Expression; + if (!IsDefinedOutside(context, collectionExpression, scopeNode)) return; - var collectionName = collectionExpression.ToString(); - context.ReportDiagnostic(Diagnostic.Create(Rule, invocation.GetLocation(), collectionName)); + var collectionName = collectionExpression.ToString(); + context.ReportDiagnostic(Diagnostic.Create(Rule, invocation.GetLocation(), collectionName)); + } + + private static string[] GetLambdaParameters(LambdaExpressionSyntax lambda) + { + if (lambda is SimpleLambdaExpressionSyntax simple) + { + return new[] { simple.Parameter.Identifier.Text }; } + + if (lambda is ParenthesizedLambdaExpressionSyntax parenthesized) + { + return parenthesized.ParameterList.Parameters.Select(p => p.Identifier.Text).ToArray(); + } + + return System.Array.Empty(); } @@ -99,7 +162,7 @@ private static bool IsSearchingByLoopVariable(LambdaExpressionSyntax lambda, Sem if (expression is not BinaryExpressionSyntax binary) return false; if (!binary.IsKind(SyntaxKind.EqualsExpression)) return false; - // Check if one side is a property access on lambda parameter and other side is a loop variable + // Check if one side is a property access on lambda parameter and another side is a loop variable return (IsPropertyAccessOnParameter(lambda, binary.Left) && IsLoopVariableOrMember(binary.Right, semanticModel, loop, loopVariables)) || (IsPropertyAccessOnParameter(lambda, binary.Right) && IsLoopVariableOrMember(binary.Left, semanticModel, loop, loopVariables)); } diff --git a/Examples/DictionaryLookup/DictionaryLookup.cs b/Examples/DictionaryLookup/DictionaryLookup.cs index e5088e8..49913e2 100644 --- a/Examples/DictionaryLookup/DictionaryLookup.cs +++ b/Examples/DictionaryLookup/DictionaryLookup.cs @@ -78,6 +78,14 @@ void TestMethod6() } } + void TestMethod7() + { + var list = new MyClass[10]; + var keys = new Tuple[10]; + + var result = keys.Select(x=>list.FirstOrDefault(o=>o.Id == x.Item1)).ToArray(); + } + private static IEnumerable GetData() { return Enumerable.Empty(); diff --git a/Tests/DictionaryLookupTests/DictionaryLookupTests.cs b/Tests/DictionaryLookupTests/DictionaryLookupTests.cs index 6ac7cc9..22628aa 100644 --- a/Tests/DictionaryLookupTests/DictionaryLookupTests.cs +++ b/Tests/DictionaryLookupTests/DictionaryLookupTests.cs @@ -65,4 +65,14 @@ public Task DictionaryLookup6Test() .WithSpan(15, 25, 15, 82) .WithArguments("groupedRCStatistics")); } + + [Test] + public Task DictionaryLookup7Test() + { + var code = ResourceReader.ReadFromFile("DictionaryLookup7.cs"); + + return DictionaryLookupVerifier.VerifyAnalyzerAsync(code, DiagnosticResult.CompilerWarning("CI0010") + .WithSpan(17, 39, 17, 80) + .WithArguments("list")); + } } diff --git a/Tests/Resources/DictionaryLookup/DictionaryLookup7.cs b/Tests/Resources/DictionaryLookup/DictionaryLookup7.cs new file mode 100644 index 0000000..2e41353 --- /dev/null +++ b/Tests/Resources/DictionaryLookup/DictionaryLookup7.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +public class MyClass +{ + public Guid Id { get; set; } +} + +public class TestClass +{ + void TestMethod7() + { + var list = new MyClass[10]; + var keys = new Tuple[10]; + + var result = keys.Select(x => list.FirstOrDefault(o => o.Id == x.Item1)).ToArray(); + } +} diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index 54bc157..cce076e 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -8,6 +8,7 @@ + From e35adf436f2fff06a14fbb3c4c59b536f2847910 Mon Sep 17 00:00:00 2001 From: Rogatnev Sergey Date: Sat, 18 Jul 2026 19:45:44 +0700 Subject: [PATCH 4/6] Ignore IQueryable --- .../CI0010/DictionaryLookupDiagnostic.cs | 9 ++++++ .../DictionaryLookupTests.cs | 8 +++++ .../DictionaryLookup/DictionaryLookup8.cs | 30 +++++++++++++++++++ Tests/Tests.csproj | 1 - 4 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 Tests/Resources/DictionaryLookup/DictionaryLookup8.cs diff --git a/Collections.Analyzer/Diagnostics/CI0010/DictionaryLookupDiagnostic.cs b/Collections.Analyzer/Diagnostics/CI0010/DictionaryLookupDiagnostic.cs index 81d1eaa..f2a2eac 100644 --- a/Collections.Analyzer/Diagnostics/CI0010/DictionaryLookupDiagnostic.cs +++ b/Collections.Analyzer/Diagnostics/CI0010/DictionaryLookupDiagnostic.cs @@ -122,10 +122,19 @@ private static void AnalyzeInvocationWithVariables(SyntaxNodeAnalysisContext con var collectionExpression = memberAccess.Expression; if (!IsDefinedOutside(context, collectionExpression, scopeNode)) return; + // Skip IQueryable + var typeInfo = context.SemanticModel.GetTypeInfo(collectionExpression); + if (typeInfo.Type != null && IsIQueryable(typeInfo.Type)) return; + var collectionName = collectionExpression.ToString(); context.ReportDiagnostic(Diagnostic.Create(Rule, invocation.GetLocation(), collectionName)); } + private static bool IsIQueryable(ITypeSymbol type) + { + return type.Name == nameof(IQueryable) || type.AllInterfaces.Any(i => i.Name == nameof(IQueryable)); + } + private static string[] GetLambdaParameters(LambdaExpressionSyntax lambda) { if (lambda is SimpleLambdaExpressionSyntax simple) diff --git a/Tests/DictionaryLookupTests/DictionaryLookupTests.cs b/Tests/DictionaryLookupTests/DictionaryLookupTests.cs index 22628aa..e4a95b4 100644 --- a/Tests/DictionaryLookupTests/DictionaryLookupTests.cs +++ b/Tests/DictionaryLookupTests/DictionaryLookupTests.cs @@ -75,4 +75,12 @@ public Task DictionaryLookup7Test() .WithSpan(17, 39, 17, 80) .WithArguments("list")); } + + [Test] + public Task DictionaryLookup8Test() + { + var code = ResourceReader.ReadFromFile("DictionaryLookup8.cs"); + + return DictionaryLookupVerifier.VerifyAnalyzerAsync(code); + } } diff --git a/Tests/Resources/DictionaryLookup/DictionaryLookup8.cs b/Tests/Resources/DictionaryLookup/DictionaryLookup8.cs new file mode 100644 index 0000000..c501423 --- /dev/null +++ b/Tests/Resources/DictionaryLookup/DictionaryLookup8.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +public interface ISafeDataContext +{ + IQueryable GetContracts(); +} + +public class News +{ + public Guid Id { get; set; } +} + +public class NewsUserRelations +{ + public Guid UserId { get; set; } + public Guid NewsId { get; set; } +} + +public class TestClass8 +{ + private static IQueryable GetNewsWithoutRelationsQuery(ISafeDataContext dataContext, Guid userId) + { + var allNews = dataContext.GetContracts(); + var userRelations = dataContext.GetContracts().Where(r => r.UserId == userId); + + return allNews.Where(n => !userRelations.Any(r => r.NewsId == n.Id)); + } +} diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index cce076e..54bc157 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -8,7 +8,6 @@ - From 89575f04ac72223d67bad915a785665ab5d21f49 Mon Sep 17 00:00:00 2001 From: Rogatnev Sergey Date: Sun, 19 Jul 2026 18:49:04 +0700 Subject: [PATCH 5/6] simple check --- .../CI0010/DictionaryLookupDiagnostic.cs | 172 +++++++----------- .../DictionaryLookupTests.cs | 8 + .../DictionaryLookup/DictionaryLookup8.cs | 1 + .../DictionaryLookup/DictionaryLookup9.cs | 19 ++ 4 files changed, 95 insertions(+), 105 deletions(-) create mode 100644 Tests/Resources/DictionaryLookup/DictionaryLookup9.cs diff --git a/Collections.Analyzer/Diagnostics/CI0010/DictionaryLookupDiagnostic.cs b/Collections.Analyzer/Diagnostics/CI0010/DictionaryLookupDiagnostic.cs index f2a2eac..75c71e7 100644 --- a/Collections.Analyzer/Diagnostics/CI0010/DictionaryLookupDiagnostic.cs +++ b/Collections.Analyzer/Diagnostics/CI0010/DictionaryLookupDiagnostic.cs @@ -1,3 +1,5 @@ +using System.Collections.Frozen; +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis; @@ -21,23 +23,41 @@ public sealed class DictionaryLookupDiagnostic : DiagnosticAnalyzer true ); + private static readonly FrozenSet TargetMethods = new HashSet() + { + nameof(Enumerable.First), + nameof(Enumerable.FirstOrDefault), + nameof(Enumerable.Single), + nameof(Enumerable.SingleOrDefault), + nameof(Enumerable.Any) + }.ToFrozenSet(); + + private static readonly FrozenSet LinqScopeMethods = new HashSet() + { + nameof(Enumerable.Select), + nameof(Enumerable.Where), + nameof(Enumerable.Any), + nameof(Enumerable.All), + nameof(Enumerable.Count), + nameof(Enumerable.First), + nameof(Enumerable.FirstOrDefault), + nameof(Enumerable.Single), + nameof(Enumerable.SingleOrDefault) + }.ToFrozenSet(); + public override void Initialize(AnalysisContext context) { context.EnableConcurrentExecution(); context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); - context.RegisterSyntaxNodeAction(Analyze, SyntaxKind.ForEachStatement, SyntaxKind.ForStatement, SyntaxKind.InvocationExpression); + context.RegisterSyntaxNodeAction(AnalyzeLoop, SyntaxKind.ForEachStatement, SyntaxKind.ForStatement); + context.RegisterSyntaxNodeAction(AnalyzeInvocation, SyntaxKind.InvocationExpression); } - private static void Analyze(SyntaxNodeAnalysisContext context) + private static void AnalyzeLoop(SyntaxNodeAnalysisContext context) { - if (context.Node is InvocationExpressionSyntax invocationNode) - { - AnalyzeInvocation(context, invocationNode); - return; - } - - var loopBody = context.Node switch + var loop = context.Node; + var loopBody = loop switch { ForEachStatementSyntax foreachStatement => foreachStatement.Statement, ForStatementSyntax forStatement => forStatement.Statement, @@ -46,88 +66,59 @@ private static void Analyze(SyntaxNodeAnalysisContext context) if (loopBody == null) return; - var loopVariables = GetLoopVariables(context.Node); + var loopVariables = GetLoopVariables(loop); if (loopVariables.Length == 0) return; - var invocations = loopBody.DescendantNodes() - .OfType(); - - foreach (var invocation in invocations) - { - AnalyzeInvocationWithVariables(context, invocation, context.Node, loopVariables); - } + AnalyzeInvocations(context, loopBody.DescendantNodes().OfType(), loop, loopVariables); } - private static void AnalyzeInvocation(SyntaxNodeAnalysisContext context, InvocationExpressionSyntax invocation) + private static void AnalyzeInvocation(SyntaxNodeAnalysisContext context) { + if (context.Node is not InvocationExpressionSyntax invocation) return; if (invocation.Expression is not MemberAccessExpressionSyntax memberAccess) return; var methodName = memberAccess.Name.Identifier.Text; - if (methodName is not ( - nameof(Enumerable.Select) or - nameof(Enumerable.Where) or - nameof(Enumerable.Any) or - nameof(Enumerable.All) or - nameof(Enumerable.Count) or - nameof(Enumerable.First) or - nameof(Enumerable.FirstOrDefault) or - nameof(Enumerable.Single) or - nameof(Enumerable.SingleOrDefault))) - return; - - if (invocation.ArgumentList.Arguments.Count == 0) - return; + if (!LinqScopeMethods.Contains(methodName)) return; + + if (invocation.ArgumentList.Arguments.Count == 0) return; var argument = invocation.ArgumentList.Arguments[0]; - if (argument.Expression is not LambdaExpressionSyntax lambda) - return; + if (argument.Expression is not LambdaExpressionSyntax lambda) return; var loopVariables = GetLambdaParameters(lambda); - if (loopVariables.Length == 0) - return; + if (loopVariables.Length == 0) return; var body = lambda is SimpleLambdaExpressionSyntax simple ? simple.Body : (lambda as ParenthesizedLambdaExpressionSyntax)?.Body; - if (body == null) - return; - - var innerInvocations = body.DescendantNodesAndSelf() - .OfType(); + if (body == null) return; - foreach (var innerInvocation in innerInvocations) - { - AnalyzeInvocationWithVariables(context, innerInvocation, invocation, loopVariables); - } + AnalyzeInvocations(context, body.DescendantNodesAndSelf().OfType(), invocation, loopVariables); } - private static void AnalyzeInvocationWithVariables(SyntaxNodeAnalysisContext context, InvocationExpressionSyntax invocation, SyntaxNode scopeNode, string[] loopVariables) + private static void AnalyzeInvocations(SyntaxNodeAnalysisContext context, IEnumerable invocations, SyntaxNode scopeNode, string[] loopVariables) { - if (invocation.Expression is not MemberAccessExpressionSyntax memberAccess) return; + foreach (var invocation in invocations) + { + if (invocation.Expression is not MemberAccessExpressionSyntax memberAccess) continue; - var methodName = memberAccess.Name.Identifier.Text; - if (methodName is not ( - nameof(Enumerable.First) or - nameof(Enumerable.FirstOrDefault) or - nameof(Enumerable.Single) or - nameof(Enumerable.SingleOrDefault) or - nameof(Enumerable.Any))) return; - - if (invocation.ArgumentList.Arguments.Count != 1) return; - var argument = invocation.ArgumentList.Arguments[0]; + if (!TargetMethods.Contains(memberAccess.Name.Identifier.Text)) continue; - if (argument.Expression is not LambdaExpressionSyntax lambda) return; + if (invocation.ArgumentList.Arguments.Count != 1) continue; + var argument = invocation.ArgumentList.Arguments[0]; - if (!IsSearchingByLoopVariable(lambda, context.SemanticModel, scopeNode, loopVariables)) return; + if (argument.Expression is not LambdaExpressionSyntax lambda) continue; - // Check if the collection is defined outside the loop - var collectionExpression = memberAccess.Expression; - if (!IsDefinedOutside(context, collectionExpression, scopeNode)) return; + if (!IsSearchingByLoopVariable(lambda, context.SemanticModel, scopeNode, loopVariables)) continue; - // Skip IQueryable - var typeInfo = context.SemanticModel.GetTypeInfo(collectionExpression); - if (typeInfo.Type != null && IsIQueryable(typeInfo.Type)) return; + var collectionExpression = memberAccess.Expression; + if (collectionExpression is not IdentifierNameSyntax) continue; - var collectionName = collectionExpression.ToString(); - context.ReportDiagnostic(Diagnostic.Create(Rule, invocation.GetLocation(), collectionName)); + if (!IsDefinedOutside(context, collectionExpression, scopeNode)) continue; + + var typeInfo = context.SemanticModel.GetTypeInfo(collectionExpression); + if (typeInfo.Type != null && IsIQueryable(typeInfo.Type)) continue; + + context.ReportDiagnostic(Diagnostic.Create(Rule, invocation.GetLocation(), collectionExpression.ToString())); + } } private static bool IsIQueryable(ITypeSymbol type) @@ -178,26 +169,16 @@ private static bool IsSearchingByLoopVariable(LambdaExpressionSyntax lambda, Sem private static bool IsPropertyAccessOnParameter(LambdaExpressionSyntax lambda, ExpressionSyntax expression) { - var parameterName = lambda switch - { - SimpleLambdaExpressionSyntax simple => simple.Parameter.Identifier.Text, - ParenthesizedLambdaExpressionSyntax paren => paren.ParameterList.Parameters.FirstOrDefault()?.Identifier.Text, - _ => null - }; - - if (parameterName == null) return false; + var parameters = GetLambdaParameters(lambda); + if (parameters.Length == 0) return false; + var primaryParameter = parameters[0]; if (expression is MemberAccessExpressionSyntax memberAccess) { - return memberAccess.Expression is IdentifierNameSyntax id && id.Identifier.Text == parameterName; + return memberAccess.Expression is IdentifierNameSyntax id && id.Identifier.Text == primaryParameter; } - if (expression is IdentifierNameSyntax identifier) - { - return identifier.Identifier.Text == parameterName; - } - - return false; + return expression is IdentifierNameSyntax identifier && identifier.Identifier.Text == primaryParameter; } private static bool IsLoopVariableOrMember(ExpressionSyntax expression, SemanticModel semanticModel, SyntaxNode loop, string[] loopVariables) @@ -260,30 +241,11 @@ private static bool IsLoopVariableOrMember(ExpressionSyntax expression, Semantic private static bool IsDefinedOutside(SyntaxNodeAnalysisContext context, ExpressionSyntax expression, SyntaxNode loop) { - if (expression is MemberAccessExpressionSyntax or InvocationExpressionSyntax) - { - return true; - } - - var symbolInfo = context.SemanticModel.GetSymbolInfo(expression); - var symbol = symbolInfo.Symbol ?? symbolInfo.CandidateSymbols.FirstOrDefault(); - - if (symbol == null) - { - // If we can't resolve the symbol, assume it's defined outside - return true; - } + var symbol = context.SemanticModel.GetSymbolInfo(expression).Symbol; + if (symbol == null) return true; var loopLocation = loop.GetLocation().SourceSpan; - foreach (var location in symbol.Locations) - { - if (location.IsInSource && !loopLocation.Contains(location.SourceSpan)) - { - return true; - } - } - - // If the symbol is a parameter of the containing method, it's defined outside the loop - return symbol.Kind == SymbolKind.Parameter; + return symbol.Locations.Any(location => location.IsInSource && !loopLocation.Contains(location.SourceSpan)) || + symbol.Kind == SymbolKind.Parameter; } } diff --git a/Tests/DictionaryLookupTests/DictionaryLookupTests.cs b/Tests/DictionaryLookupTests/DictionaryLookupTests.cs index e4a95b4..d6a1169 100644 --- a/Tests/DictionaryLookupTests/DictionaryLookupTests.cs +++ b/Tests/DictionaryLookupTests/DictionaryLookupTests.cs @@ -83,4 +83,12 @@ public Task DictionaryLookup8Test() return DictionaryLookupVerifier.VerifyAnalyzerAsync(code); } + + [Test] + public Task DictionaryLookup9Test() + { + var code = ResourceReader.ReadFromFile("DictionaryLookup9.cs"); + + return DictionaryLookupVerifier.VerifyAnalyzerAsync(code); + } } diff --git a/Tests/Resources/DictionaryLookup/DictionaryLookup8.cs b/Tests/Resources/DictionaryLookup/DictionaryLookup8.cs index c501423..7873c94 100644 --- a/Tests/Resources/DictionaryLookup/DictionaryLookup8.cs +++ b/Tests/Resources/DictionaryLookup/DictionaryLookup8.cs @@ -25,6 +25,7 @@ private static IQueryable GetNewsWithoutRelationsQuery(ISafeDataContext da var allNews = dataContext.GetContracts(); var userRelations = dataContext.GetContracts().Where(r => r.UserId == userId); + // This should NOT trigger CI0010 because of IQueryable return allNews.Where(n => !userRelations.Any(r => r.NewsId == n.Id)); } } diff --git a/Tests/Resources/DictionaryLookup/DictionaryLookup9.cs b/Tests/Resources/DictionaryLookup/DictionaryLookup9.cs new file mode 100644 index 0000000..b3588c0 --- /dev/null +++ b/Tests/Resources/DictionaryLookup/DictionaryLookup9.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Linq; + +public class TestClass9 +{ + public void TestMethod9(List source, OtherClass other) + { + foreach (var item in source) + { + // This should NOT trigger CI0010 because it's a property access + var found = other.Items.FirstOrDefault(it => it == item); + } + } +} + +public class OtherClass +{ + public List Items { get; set; } +} From 94cb98fb48229f2397ee5669823bfa7f168bf5dc Mon Sep 17 00:00:00 2001 From: Rogatnev Sergey Date: Sun, 19 Jul 2026 18:52:32 +0700 Subject: [PATCH 6/6] 0.2.15: Added CI0010 diagnostic --- CHANGELOG.md | 3 +++ .../AnalyzerReleases.Shipped.md | 8 ++++++++ .../Collections.Analyzer.csproj | 2 +- Documentation/CI0010.md | 20 +++++++++++++++++++ Documentation/Diagnostics.md | 3 ++- README.md | 2 +- 6 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 Documentation/CI0010.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e236a2..fd9f3d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 0.2.15 +Added [CI0010](https://github.com/Backs/Collections.Analyzer/blob/master/Documentation/CI0010.md): Diagnostic to suggest using `Dictionary` for lookups in collections inside loops or LINQ chains. This optimizes performance from O(N*M) to O(N+M). + ## 0.2.14 Added [CI0009](https://github.com/Backs/Collections.Analyzer/blob/master/Documentation/CI0009.md): Diagnostic to suggest initialize `List` capacity with the size of a source collection. diff --git a/Collections.Analyzer/AnalyzerReleases.Shipped.md b/Collections.Analyzer/AnalyzerReleases.Shipped.md index c689605..a575a25 100644 --- a/Collections.Analyzer/AnalyzerReleases.Shipped.md +++ b/Collections.Analyzer/AnalyzerReleases.Shipped.md @@ -1,6 +1,14 @@ ; Shipped analyzer releases ; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md +## Release 0.2.15 + +### New Rules + + Rule ID | Category | Severity | Notes +---------|-------------|----------|--------------------------------------- + CI0010 | Performance | Warning | DictionaryLookupDiagnostic + ## Release 0.2.14 ### New Rules diff --git a/Collections.Analyzer/Collections.Analyzer.csproj b/Collections.Analyzer/Collections.Analyzer.csproj index 8e409e2..e6f4e45 100644 --- a/Collections.Analyzer/Collections.Analyzer.csproj +++ b/Collections.Analyzer/Collections.Analyzer.csproj @@ -6,7 +6,7 @@ true true true - 0.2.14.2 + 0.2.15 Collections.Analyzer Rogatnev Sergey Collections.Analyzer is a set of roslyn-based diagnostics for C#-projects that detect potential problems with operating different collections. diff --git a/Documentation/CI0010.md b/Documentation/CI0010.md new file mode 100644 index 0000000..5a39aca --- /dev/null +++ b/Documentation/CI0010.md @@ -0,0 +1,20 @@ +# CI0010: Consider using Dictionary for lookups in collections + +This diagnostic identifies cases where a collection is searched linearly (O(N)) inside a loop or another LINQ operation, leading to O(N*M) complexity. Converting the collection to a `Dictionary` before the loop allows for O(1) lookup time, improving overall performance to O(N+M). + +## Examples + +### Violation +```csharp +public void TestMethod(IEnumerable keys, List items) +{ + foreach (var key in keys) + { + var item = items.FirstOrDefault(x => x.Id == key); + Console.WriteLine(item); + } +} +``` + +## Remarks +The diagnostic triggers for methods like `First`, `FirstOrDefault`, `Single`, `SingleOrDefault`, and `Any` when the search condition depends on a loop variable or a LINQ lambda parameter. It only suggests optimization for local variables and parameters to avoid unnecessary complexity with external object properties. diff --git a/Documentation/Diagnostics.md b/Documentation/Diagnostics.md index 0ebc2f9..77d5318 100644 --- a/Documentation/Diagnostics.md +++ b/Documentation/Diagnostics.md @@ -8,4 +8,5 @@ | [CI0006](CI0006.md) | Collection initializer is used without capacity | Warning | Set initial capacity with collection initializer | | [CI0007](CI0007.md) | Method `Any()` is used on concurrent collection | Warning | Use `IsEmpty` property to check empty collection | | [CI0008](CI0008.md) | Consider using HashSet for Contains operations | Warning | Array is used with `Contains()` method. `HashSet` provides O(1) lookup performance | -| [CI0009](CI0009.md) | List capacity should be set based on the source collection | Warning | Initialize `List` capacity with the size of a source collection | \ No newline at end of file +| [CI0009](CI0009.md) | List capacity should be set based on the source collection | Warning | Initialize `List` capacity with the size of a source collection | +| [CI0010](CI0010.md) | Consider using Dictionary for lookups in collections | Warning | Collection is used for lookups inside a loop or LINQ. Using `Dictionary` provides O(1) performance. | \ No newline at end of file diff --git a/README.md b/README.md index bb59d20..173b164 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Automatically fixes found problems. Every analyzer can be installed as a usual nuget-package. Just add a package reference to a project: ``` - + ``` The analyzer will work only in the project it was added to. If you want to analyse all projects in your solution, you