From 919d4aeb4fd9ded813a50917244aa014420186c0 Mon Sep 17 00:00:00 2001 From: Meir Blachman Date: Tue, 15 Aug 2023 21:51:54 +0300 Subject: [PATCH] add support for arrays in collection analyzers --- .../Tips/Collections/CollectionAnalyzer.cs | 9 ++++++++- .../CollectionShouldContainSingle.cs | 4 ++-- .../CollectionShouldHaveElementAt.cs | 4 ++-- .../Tips/Dictionaries/DictionaryAnalyzer.cs | 2 +- .../Tips/Exceptions/ExceptionAnalyzer.cs | 2 +- .../Tips/Numerics/NumericAnalyzer.cs | 2 +- .../Numerics/NumericShouldBeApproximately.cs | 2 +- .../Tips/Strings/StringAnalyzer.cs | 2 +- .../Tips/Xunit/XunitBase.cs | 2 +- .../Utilities/FluentAssertionsAnalyzer.cs | 18 ++++++++++++++---- .../Utilities/TypesExtensions.cs | 6 ++++++ 11 files changed, 38 insertions(+), 15 deletions(-) diff --git a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionAnalyzer.cs b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionAnalyzer.cs index 15b34872..e2867995 100644 --- a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionAnalyzer.cs +++ b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionAnalyzer.cs @@ -5,10 +5,17 @@ namespace FluentAssertions.Analyzers { public abstract class CollectionAnalyzer : FluentAssertionsAnalyzer { - protected override bool ShouldAnalyzeVariableType(INamedTypeSymbol type, SemanticModel semanticModel) + protected override bool ShouldAnalyzeVariableNamedType(INamedTypeSymbol type, SemanticModel semanticModel) { return type.SpecialType != SpecialType.System_String && type.IsTypeOrConstructedFromTypeOrImplementsType(SpecialType.System_Collections_Generic_IEnumerable_T); } + + override protected bool ShouldAnalyzeVariableType(ITypeSymbol type, SemanticModel semanticModel) + { + return type.SpecialType != SpecialType.System_String + && type.IsTypeOrConstructedFromTypeOrImplementsType(SpecialType.System_Collections_Generic_IEnumerable_T); + } + } } diff --git a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldContainSingle.cs b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldContainSingle.cs index c140aac0..0e259d37 100644 --- a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldContainSingle.cs +++ b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldContainSingle.cs @@ -27,14 +27,14 @@ protected override IEnumerable Visitors } } - protected override bool ShouldAnalyzeVariableType(INamedTypeSymbol type, SemanticModel semanticModel) + protected override bool ShouldAnalyzeVariableNamedType(INamedTypeSymbol type, SemanticModel semanticModel) { if (!type.IsTypeOrConstructedFromTypeOrImplementsType(SpecialType.System_Collections_Generic_IEnumerable_T)) { return false; } - return base.ShouldAnalyzeVariableType(type, semanticModel); + return base.ShouldAnalyzeVariableNamedType(type, semanticModel); } public class WhereShouldHaveCount1SyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor diff --git a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldHaveElementAt.cs b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldHaveElementAt.cs index fcc21cd8..624c1037 100644 --- a/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldHaveElementAt.cs +++ b/src/FluentAssertions.Analyzers/Tips/Collections/CollectionShouldHaveElementAt.cs @@ -28,7 +28,7 @@ protected override IEnumerable Visitors } } - protected override bool ShouldAnalyzeVariableType(INamedTypeSymbol type, SemanticModel semanticModel) + protected override bool ShouldAnalyzeVariableNamedType(INamedTypeSymbol type, SemanticModel semanticModel) { var iReadOnlyDictionaryType = semanticModel.GetIReadOnlyDictionaryType(); var iDictionaryType = semanticModel.GetGenericIDictionaryType(); @@ -38,7 +38,7 @@ protected override bool ShouldAnalyzeVariableType(INamedTypeSymbol type, Semanti return false; } - return base.ShouldAnalyzeVariableType(type, semanticModel); + return base.ShouldAnalyzeVariableNamedType(type, semanticModel); } public class ElementAtIndexShouldBeSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor diff --git a/src/FluentAssertions.Analyzers/Tips/Dictionaries/DictionaryAnalyzer.cs b/src/FluentAssertions.Analyzers/Tips/Dictionaries/DictionaryAnalyzer.cs index 82898ee0..55a8f780 100644 --- a/src/FluentAssertions.Analyzers/Tips/Dictionaries/DictionaryAnalyzer.cs +++ b/src/FluentAssertions.Analyzers/Tips/Dictionaries/DictionaryAnalyzer.cs @@ -5,7 +5,7 @@ namespace FluentAssertions.Analyzers { public abstract class DictionaryAnalyzer : FluentAssertionsAnalyzer { - protected override bool ShouldAnalyzeVariableType(INamedTypeSymbol type, SemanticModel semanticModel) + protected override bool ShouldAnalyzeVariableNamedType(INamedTypeSymbol type, SemanticModel semanticModel) { var iDictionaryType = semanticModel.GetGenericIDictionaryType(); return type.IsTypeOrConstructedFromTypeOrImplementsType(iDictionaryType); diff --git a/src/FluentAssertions.Analyzers/Tips/Exceptions/ExceptionAnalyzer.cs b/src/FluentAssertions.Analyzers/Tips/Exceptions/ExceptionAnalyzer.cs index e88832fa..5c4765b7 100644 --- a/src/FluentAssertions.Analyzers/Tips/Exceptions/ExceptionAnalyzer.cs +++ b/src/FluentAssertions.Analyzers/Tips/Exceptions/ExceptionAnalyzer.cs @@ -5,7 +5,7 @@ namespace FluentAssertions.Analyzers { public abstract class ExceptionAnalyzer : FluentAssertionsAnalyzer { - protected override bool ShouldAnalyzeVariableType(INamedTypeSymbol type, SemanticModel semanticModel) + protected override bool ShouldAnalyzeVariableNamedType(INamedTypeSymbol type, SemanticModel semanticModel) { var actionType = semanticModel.GetActionType(); return type.IsTypeOrConstructedFromTypeOrImplementsType(actionType); diff --git a/src/FluentAssertions.Analyzers/Tips/Numerics/NumericAnalyzer.cs b/src/FluentAssertions.Analyzers/Tips/Numerics/NumericAnalyzer.cs index 0f2fe5b6..adec919c 100644 --- a/src/FluentAssertions.Analyzers/Tips/Numerics/NumericAnalyzer.cs +++ b/src/FluentAssertions.Analyzers/Tips/Numerics/NumericAnalyzer.cs @@ -4,6 +4,6 @@ namespace FluentAssertions.Analyzers { public abstract class NumericAnalyzer : FluentAssertionsAnalyzer { - protected override bool ShouldAnalyzeVariableType(INamedTypeSymbol type, SemanticModel semanticModel) => true; + protected override bool ShouldAnalyzeVariableNamedType(INamedTypeSymbol type, SemanticModel semanticModel) => true; } } diff --git a/src/FluentAssertions.Analyzers/Tips/Numerics/NumericShouldBeApproximately.cs b/src/FluentAssertions.Analyzers/Tips/Numerics/NumericShouldBeApproximately.cs index 84ed4f29..3597ef1e 100644 --- a/src/FluentAssertions.Analyzers/Tips/Numerics/NumericShouldBeApproximately.cs +++ b/src/FluentAssertions.Analyzers/Tips/Numerics/NumericShouldBeApproximately.cs @@ -29,7 +29,7 @@ protected override IEnumerable Visitors } private static readonly string[] ValidaTypeNames = { "double", "decimal", "float" }; - protected override bool ShouldAnalyzeVariableType(INamedTypeSymbol type, SemanticModel semanticModel) + protected override bool ShouldAnalyzeVariableNamedType(INamedTypeSymbol type, SemanticModel semanticModel) => ValidaTypeNames.Contains(type.ToDisplayString(), StringComparer.OrdinalIgnoreCase); public class MathAbsShouldBeLessOrEqualToSyntaxVisitor : FluentAssertionsCSharpSyntaxVisitor diff --git a/src/FluentAssertions.Analyzers/Tips/Strings/StringAnalyzer.cs b/src/FluentAssertions.Analyzers/Tips/Strings/StringAnalyzer.cs index ceb23a2d..0233ba82 100644 --- a/src/FluentAssertions.Analyzers/Tips/Strings/StringAnalyzer.cs +++ b/src/FluentAssertions.Analyzers/Tips/Strings/StringAnalyzer.cs @@ -4,6 +4,6 @@ namespace FluentAssertions.Analyzers { public abstract class StringAnalyzer : FluentAssertionsAnalyzer { - protected override bool ShouldAnalyzeVariableType(INamedTypeSymbol type, SemanticModel semanticModel) => type.SpecialType == SpecialType.System_String; + protected override bool ShouldAnalyzeVariableNamedType(INamedTypeSymbol type, SemanticModel semanticModel) => type.SpecialType == SpecialType.System_String; } } diff --git a/src/FluentAssertions.Analyzers/Tips/Xunit/XunitBase.cs b/src/FluentAssertions.Analyzers/Tips/Xunit/XunitBase.cs index a5ba4d90..2b4f592f 100644 --- a/src/FluentAssertions.Analyzers/Tips/Xunit/XunitBase.cs +++ b/src/FluentAssertions.Analyzers/Tips/Xunit/XunitBase.cs @@ -9,7 +9,7 @@ public abstract class XunitAnalyzer : TestingLibraryAnalyzerBase protected override string TestingLibraryModule => "xunit.assert"; protected override string TestingLibraryAssertionType => "Assert"; - protected override bool ShouldAnalyzeVariableType(INamedTypeSymbol type, SemanticModel semanticModel) => type.Name == "Assert"; + protected override bool ShouldAnalyzeVariableNamedType(INamedTypeSymbol type, SemanticModel semanticModel) => type.Name == "Assert"; } public abstract class XunitCodeFixProvider : TestingLibraryCodeFixBase diff --git a/src/FluentAssertions.Analyzers/Utilities/FluentAssertionsAnalyzer.cs b/src/FluentAssertions.Analyzers/Utilities/FluentAssertionsAnalyzer.cs index 176402bb..d7425bfa 100644 --- a/src/FluentAssertions.Analyzers/Utilities/FluentAssertionsAnalyzer.cs +++ b/src/FluentAssertions.Analyzers/Utilities/FluentAssertionsAnalyzer.cs @@ -44,7 +44,18 @@ private void AnalyzeExpressionStatementSyntax(SyntaxNodeAnalysisContext context) } } - protected virtual bool ShouldAnalyzeVariableType(INamedTypeSymbol type, SemanticModel semanticModel) => true; + protected virtual bool ShouldAnalyzeVariableNamedType(INamedTypeSymbol type, SemanticModel semanticModel) => true; + protected virtual bool ShouldAnalyzeVariableType(ITypeSymbol type, SemanticModel semanticModel) => true; + + private bool ShouldAnalyzeVariableTypeCore(ITypeSymbol type, SemanticModel semanticModel) + { + if (type is INamedTypeSymbol namedType) + { + return ShouldAnalyzeVariableNamedType(namedType, semanticModel); + } + + return ShouldAnalyzeVariableType(type, semanticModel); + } protected virtual Diagnostic AnalyzeExpression(ExpressionSyntax expression, SemanticModel semanticModel) { @@ -53,8 +64,7 @@ protected virtual Diagnostic AnalyzeExpression(ExpressionSyntax expression, Sema if (variableNameExtractor.VariableIdentifierName == null) return null; var typeInfo = semanticModel.GetTypeInfo(variableNameExtractor.VariableIdentifierName); - if (!(typeInfo.Type is INamedTypeSymbol namedType)) return null; - if (!ShouldAnalyzeVariableType(namedType, semanticModel)) return null; + if (!ShouldAnalyzeVariableTypeCore(typeInfo.Type, semanticModel)) return null; foreach (var visitor in Visitors) { @@ -104,7 +114,7 @@ public abstract class TestingLibraryAnalyzerBase : FluentAssertionsAnalyzer protected abstract string TestingLibraryModule { get; } protected abstract string TestingLibraryAssertionType { get; } - protected override bool ShouldAnalyzeVariableType(INamedTypeSymbol type, SemanticModel semanticModel) + protected override bool ShouldAnalyzeVariableNamedType(INamedTypeSymbol type, SemanticModel semanticModel) => type.Name == TestingLibraryAssertionType && type.ContainingModule.Name == TestingLibraryModule + ".dll"; } } \ No newline at end of file diff --git a/src/FluentAssertions.Analyzers/Utilities/TypesExtensions.cs b/src/FluentAssertions.Analyzers/Utilities/TypesExtensions.cs index a0d69973..7dc54263 100644 --- a/src/FluentAssertions.Analyzers/Utilities/TypesExtensions.cs +++ b/src/FluentAssertions.Analyzers/Utilities/TypesExtensions.cs @@ -18,5 +18,11 @@ public static bool IsTypeOrConstructedFromTypeOrImplementsType(this INamedTypeSy return abstractType.Equals(other, SymbolEqualityComparer.Default) || abstractType.AllInterfaces.Any(@interface => @interface.OriginalDefinition.Equals(other, SymbolEqualityComparer.Default)); } + + public static bool IsTypeOrConstructedFromTypeOrImplementsType(this ITypeSymbol type, SpecialType specialType) + { + return type.SpecialType == specialType + || type.AllInterfaces.Any(@interface => @interface.OriginalDefinition.SpecialType == specialType); + } } }