diff --git a/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/AlternativeMonad/MatchToNullableCodeFix.cs b/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/AlternativeMonad/MatchToNullableCodeFix.cs index ef394fa1..d1764d33 100644 --- a/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/AlternativeMonad/MatchToNullableCodeFix.cs +++ b/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/AlternativeMonad/MatchToNullableCodeFix.cs @@ -1,11 +1,12 @@ using System.Collections.Immutable; using System.Composition; +using Funcky.Analyzers.CodeAnalysisExtensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; -using static Funcky.Analyzers.AlternativeMonadAnalyzer; +using static Funcky.Analyzers.AlternativeMonad.AlternativeMonadAnalyzer; using static Funcky.Analyzers.FunckyWellKnownMemberNames; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; diff --git a/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/AlternativeMonad/MatchToOrElseCodeFix.cs b/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/AlternativeMonad/MatchToOrElseCodeFix.cs index dc31abaa..99274ce8 100644 --- a/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/AlternativeMonad/MatchToOrElseCodeFix.cs +++ b/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/AlternativeMonad/MatchToOrElseCodeFix.cs @@ -1,11 +1,12 @@ using System.Collections.Immutable; using System.Composition; +using Funcky.Analyzers.CodeAnalysisExtensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; -using static Funcky.Analyzers.AlternativeMonadAnalyzer; +using static Funcky.Analyzers.AlternativeMonad.AlternativeMonadAnalyzer; using static Funcky.Analyzers.FunckyWellKnownMemberNames; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; diff --git a/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/DiagnosticExtensions.cs b/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/CodeAnalysisExtensions/DiagnosticExtensions.cs similarity index 93% rename from Funcky.Analyzers/Funcky.Analyzers.CodeFixes/DiagnosticExtensions.cs rename to Funcky.Analyzers/Funcky.Analyzers.CodeFixes/CodeAnalysisExtensions/DiagnosticExtensions.cs index a8f019c6..90e6d9f2 100644 --- a/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/DiagnosticExtensions.cs +++ b/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/CodeAnalysisExtensions/DiagnosticExtensions.cs @@ -1,7 +1,7 @@ using System.Diagnostics.CodeAnalysis; using Microsoft.CodeAnalysis; -namespace Funcky.Analyzers; +namespace Funcky.Analyzers.CodeAnalysisExtensions; internal static class DiagnosticExtensions { diff --git a/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/SyntaxGeneratorExtensions.cs b/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/CodeAnalysisExtensions/SyntaxGeneratorExtensions.cs similarity index 96% rename from Funcky.Analyzers/Funcky.Analyzers.CodeFixes/SyntaxGeneratorExtensions.cs rename to Funcky.Analyzers/Funcky.Analyzers.CodeFixes/CodeAnalysisExtensions/SyntaxGeneratorExtensions.cs index 096b16b0..193e4d92 100644 --- a/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/SyntaxGeneratorExtensions.cs +++ b/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/CodeAnalysisExtensions/SyntaxGeneratorExtensions.cs @@ -1,7 +1,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editing; -namespace Funcky.Analyzers; +namespace Funcky.Analyzers.CodeAnalysisExtensions; internal static class SyntaxGeneratorExtensions { diff --git a/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/CodeAnalysisExtensions/SyntaxNodeExtensions.ParameterReference.cs b/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/CodeAnalysisExtensions/SyntaxNodeExtensions.ParameterReference.cs new file mode 100644 index 00000000..b71f25bc --- /dev/null +++ b/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/CodeAnalysisExtensions/SyntaxNodeExtensions.ParameterReference.cs @@ -0,0 +1,25 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Operations; + +namespace Funcky.Analyzers.CodeAnalysisExtensions; + +internal static partial class SyntaxNodeExtensions +{ + public static TNode ReplaceParameterReferences(this TNode node, SemanticModel semanticModel, string parameterName, ExpressionSyntax replacement) + where TNode : SyntaxNode + => (TNode)new ReplaceParameterReferenceRewriter(semanticModel, parameterName, replacement).Visit(node); + + private sealed class ReplaceParameterReferenceRewriter( + SemanticModel semanticModel, + string parameterName, + ExpressionSyntax replacement) + : CSharpSyntaxRewriter(visitIntoStructuredTrivia: false) + { + public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax node) + => semanticModel.GetOperation(node) is IParameterReferenceOperation { Parameter.Name: var name } && name == parameterName + ? replacement.WithTriviaFrom(node) + : node; + } +} diff --git a/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/SyntaxNodeExtensions.cs b/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/CodeAnalysisExtensions/SyntaxNodeExtensions.cs similarity index 91% rename from Funcky.Analyzers/Funcky.Analyzers.CodeFixes/SyntaxNodeExtensions.cs rename to Funcky.Analyzers/Funcky.Analyzers.CodeFixes/CodeAnalysisExtensions/SyntaxNodeExtensions.cs index bdd35b0c..8228aae1 100644 --- a/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/SyntaxNodeExtensions.cs +++ b/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/CodeAnalysisExtensions/SyntaxNodeExtensions.cs @@ -2,7 +2,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; -namespace Funcky.Analyzers; +namespace Funcky.Analyzers.CodeAnalysisExtensions; internal static partial class SyntaxNodeExtensions { diff --git a/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/EnumerableRepeatNeverCodeFix.cs b/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/EnumerableRepeatNeverCodeFix.cs index 70dd5ba8..ff3409a6 100644 --- a/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/EnumerableRepeatNeverCodeFix.cs +++ b/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/EnumerableRepeatNeverCodeFix.cs @@ -1,5 +1,6 @@ using System.Collections.Immutable; using System.Composition; +using Funcky.Analyzers.CodeAnalysisExtensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; diff --git a/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/EnumerableRepeatOnceCodeFix.cs b/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/EnumerableRepeatOnceCodeFix.cs index 511c390e..300e8dbc 100644 --- a/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/EnumerableRepeatOnceCodeFix.cs +++ b/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/EnumerableRepeatOnceCodeFix.cs @@ -1,5 +1,6 @@ using System.Collections.Immutable; using System.Composition; +using Funcky.Analyzers.CodeAnalysisExtensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; diff --git a/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/Funcky.Analyzers.CodeFixes.targets b/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/Funcky.Analyzers.CodeFixes.targets index 2a13de99..2ce99827 100644 --- a/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/Funcky.Analyzers.CodeFixes.targets +++ b/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/Funcky.Analyzers.CodeFixes.targets @@ -16,6 +16,10 @@ cs + + + + diff --git a/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/FunctionalAssertFix.cs b/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/FunctionalAssertFix.cs index 4e4c148c..12d897d5 100644 --- a/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/FunctionalAssertFix.cs +++ b/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/FunctionalAssertFix.cs @@ -1,5 +1,6 @@ using System.Collections.Immutable; using System.Composition; +using Funcky.Analyzers.CodeAnalysisExtensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; diff --git a/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/OptionSomeWhereToFromBooleanRefactoring.cs b/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/OptionSomeWhereToFromBooleanRefactoring.cs index b742cc66..83de7458 100644 --- a/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/OptionSomeWhereToFromBooleanRefactoring.cs +++ b/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/OptionSomeWhereToFromBooleanRefactoring.cs @@ -1,5 +1,6 @@ using System.Composition; using System.Diagnostics.CodeAnalysis; +using Funcky.Analyzers.CodeAnalysisExtensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeRefactorings; @@ -52,7 +53,7 @@ private static bool IsWhereInvocation(SyntaxNode syntax, SemanticModel semanticM { whereInvocation = null; return semanticModel.GetOperation(syntax) is IInvocationOperation { TargetMethod.Name: WhereMethodName } operation - && SymbolEqualityComparer.Default.Equals(symbols.GenericOptionType, operation.TargetMethod.ContainingType.ConstructedFrom) + && SymbolEquals(symbols.GenericOptionType, operation.TargetMethod.ContainingType.ConstructedFrom) && (whereInvocation = operation) is var _; } @@ -60,7 +61,7 @@ private static bool IsOptionReturnInvocation(IOperation? candidate, Symbols symb { returnInvocationOperation = null; return candidate is IInvocationOperation { TargetMethod.Name: MonadReturnMethodName or OptionSomeMethodName } operation - && SymbolEqualityComparer.Default.Equals(symbols.OptionType, operation.TargetMethod.ContainingType) + && SymbolEquals(symbols.OptionType, operation.TargetMethod.ContainingType) && (returnInvocationOperation = operation) is var _; } diff --git a/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/ReplaceParameterReferenceRewriter.cs b/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/ReplaceParameterReferenceRewriter.cs deleted file mode 100644 index d7854f1c..00000000 --- a/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/ReplaceParameterReferenceRewriter.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Operations; - -namespace Funcky.Analyzers; - -internal sealed class ReplaceParameterReferenceRewriter( - SemanticModel semanticModel, - string parameterName, - ExpressionSyntax replacement) - : CSharpSyntaxRewriter(visitIntoStructuredTrivia: false) -{ - public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax node) - => semanticModel.GetOperation(node) is IParameterReferenceOperation { Parameter.Name: var name } && name == parameterName - ? replacement.WithTriviaFrom(node) - : node; -} - -internal static partial class SyntaxNodeExtensions -{ - public static TNode ReplaceParameterReferences(this TNode node, SemanticModel semanticModel, string parameterName, ExpressionSyntax replacement) - where TNode : SyntaxNode - => (TNode)new ReplaceParameterReferenceRewriter(semanticModel, parameterName, replacement).Visit(node); -} diff --git a/Funcky.Analyzers/Funcky.Analyzers.Test/AlternativeMonadAnalyzerTest.Stubs.cs b/Funcky.Analyzers/Funcky.Analyzers.Test/AlternativeMonad/AlternativeMonadAnalyzerTest.Stubs.cs similarity index 98% rename from Funcky.Analyzers/Funcky.Analyzers.Test/AlternativeMonadAnalyzerTest.Stubs.cs rename to Funcky.Analyzers/Funcky.Analyzers.Test/AlternativeMonad/AlternativeMonadAnalyzerTest.Stubs.cs index a50a0d0d..6388f93a 100644 --- a/Funcky.Analyzers/Funcky.Analyzers.Test/AlternativeMonadAnalyzerTest.Stubs.cs +++ b/Funcky.Analyzers/Funcky.Analyzers.Test/AlternativeMonad/AlternativeMonadAnalyzerTest.Stubs.cs @@ -1,4 +1,4 @@ -namespace Funcky.Analyzers.Test; +namespace Funcky.Analyzers.Test.AlternativeMonad; public sealed partial class AlternativeMonadAnalyzerTest { diff --git a/Funcky.Analyzers/Funcky.Analyzers.Test/AlternativeMonadAnalyzerTest.ToNullable.cs b/Funcky.Analyzers/Funcky.Analyzers.Test/AlternativeMonad/AlternativeMonadAnalyzerTest.ToNullable.cs similarity index 94% rename from Funcky.Analyzers/Funcky.Analyzers.Test/AlternativeMonadAnalyzerTest.ToNullable.cs rename to Funcky.Analyzers/Funcky.Analyzers.Test/AlternativeMonad/AlternativeMonadAnalyzerTest.ToNullable.cs index 3b0b6a40..48721dd2 100644 --- a/Funcky.Analyzers/Funcky.Analyzers.Test/AlternativeMonadAnalyzerTest.ToNullable.cs +++ b/Funcky.Analyzers/Funcky.Analyzers.Test/AlternativeMonad/AlternativeMonadAnalyzerTest.ToNullable.cs @@ -1,9 +1,9 @@ #pragma warning disable SA1118 // StyleCop support for collection expressions is missing using Xunit; -using static Funcky.Analyzers.AlternativeMonadAnalyzer; -using VerifyCS = Funcky.Analyzers.Test.CSharpCodeFixVerifier; +using static Funcky.Analyzers.AlternativeMonad.AlternativeMonadAnalyzer; +using VerifyCS = Funcky.Analyzers.Test.CSharpCodeFixVerifier; -namespace Funcky.Analyzers.Test; +namespace Funcky.Analyzers.Test.AlternativeMonad; public sealed partial class AlternativeMonadAnalyzerTest { diff --git a/Funcky.Analyzers/Funcky.Analyzers.Test/AlternativeMonadAnalyzerTest.cs b/Funcky.Analyzers/Funcky.Analyzers.Test/AlternativeMonad/AlternativeMonadAnalyzerTest.cs similarity index 98% rename from Funcky.Analyzers/Funcky.Analyzers.Test/AlternativeMonadAnalyzerTest.cs rename to Funcky.Analyzers/Funcky.Analyzers.Test/AlternativeMonad/AlternativeMonadAnalyzerTest.cs index 4cb25564..e2db08f0 100644 --- a/Funcky.Analyzers/Funcky.Analyzers.Test/AlternativeMonadAnalyzerTest.cs +++ b/Funcky.Analyzers/Funcky.Analyzers.Test/AlternativeMonad/AlternativeMonadAnalyzerTest.cs @@ -1,9 +1,9 @@ #pragma warning disable SA1118 // StyleCop support for collection expressions is missing using Xunit; -using static Funcky.Analyzers.AlternativeMonadAnalyzer; -using VerifyCS = Funcky.Analyzers.Test.CSharpCodeFixVerifier; +using static Funcky.Analyzers.AlternativeMonad.AlternativeMonadAnalyzer; +using VerifyCS = Funcky.Analyzers.Test.CSharpCodeFixVerifier; -namespace Funcky.Analyzers.Test; +namespace Funcky.Analyzers.Test.AlternativeMonad; public sealed partial class AlternativeMonadAnalyzerTest { diff --git a/Funcky.Analyzers/Funcky.Analyzers.Test/NonDefaultableTest.cs b/Funcky.Analyzers/Funcky.Analyzers.Test/NonDefaultableTest.cs index bdf4b381..aac6882d 100644 --- a/Funcky.Analyzers/Funcky.Analyzers.Test/NonDefaultableTest.cs +++ b/Funcky.Analyzers/Funcky.Analyzers.Test/NonDefaultableTest.cs @@ -1,6 +1,6 @@ using Microsoft.CodeAnalysis.Testing; using Xunit; -using VerifyCS = Funcky.Analyzers.Test.CSharpAnalyzerVerifier; +using VerifyCS = Funcky.Analyzers.Test.CSharpAnalyzerVerifier; namespace Funcky.Analyzers.Test; diff --git a/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadAnalyzer/AlternativeMonadAnalyzer.GetOrElse.cs b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.GetOrElse.cs similarity index 80% rename from Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadAnalyzer/AlternativeMonadAnalyzer.GetOrElse.cs rename to Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.GetOrElse.cs index d9a299c1..535a9471 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadAnalyzer/AlternativeMonadAnalyzer.GetOrElse.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.GetOrElse.cs @@ -1,9 +1,9 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Operations; using static Funcky.Analyzers.FunckyWellKnownMemberNames; -using static Funcky.Analyzers.IdentityFunctionMatching; +using static Funcky.Analyzers.Functions.IdentityFunctionMatching; -namespace Funcky.Analyzers; +namespace Funcky.Analyzers.AlternativeMonad; public partial class AlternativeMonadAnalyzer { @@ -18,8 +18,8 @@ public partial class AlternativeMonadAnalyzer /// Tests for a Match invocation of the shape Match(none: A, some: Identity). private static bool IsGetOrElseEquivalent(INamedTypeSymbol receiverType, IArgumentOperation errorStateArgument, IArgumentOperation successStateArgument) - => SymbolEqualityComparer.IncludeNullability.Equals(receiverType.TypeArguments.Last(), GetTypeOrDelegateReturnType(errorStateArgument.Value)) - && SymbolEqualityComparer.Default.Equals(receiverType.TypeArguments.Last(), GetTypeOrDelegateReturnType(successStateArgument.Value)) + => SymbolEqualsIncludeNullability(receiverType.TypeArguments.Last(), GetTypeOrDelegateReturnType(errorStateArgument.Value)) + && SymbolEquals(receiverType.TypeArguments.Last(), GetTypeOrDelegateReturnType(successStateArgument.Value)) && IsIdentityFunction(successStateArgument.Value); private static ITypeSymbol? GetTypeOrDelegateReturnType(IOperation operation) diff --git a/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadAnalyzer/AlternativeMonadAnalyzer.OrElse.cs b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.OrElse.cs similarity index 83% rename from Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadAnalyzer/AlternativeMonadAnalyzer.OrElse.cs rename to Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.OrElse.cs index cb3e6170..def5d62b 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadAnalyzer/AlternativeMonadAnalyzer.OrElse.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.OrElse.cs @@ -1,9 +1,9 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Operations; +using static Funcky.Analyzers.AlternativeMonad.MonadReturnMatching; using static Funcky.Analyzers.FunckyWellKnownMemberNames; -using static Funcky.Analyzers.MonadReturnMatching; -namespace Funcky.Analyzers; +namespace Funcky.Analyzers.AlternativeMonad; public partial class AlternativeMonadAnalyzer { @@ -18,6 +18,6 @@ public partial class AlternativeMonadAnalyzer /// Tests for a Match invocation of the shape Match(none: A, some: Option.Return). private static bool IsOrElseEquivalent(AlternativeMonadType alternativeMonadType, IInvocationOperation matchInvocation, INamedTypeSymbol receiverType, IArgumentOperation successStateArgument) - => SymbolEqualityComparer.IncludeNullability.Equals(receiverType, matchInvocation.Type) + => SymbolEqualsIncludeNullability(receiverType, matchInvocation.Type) && IsReturnFunction(alternativeMonadType, successStateArgument.Value); } diff --git a/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadAnalyzer/AlternativeMonadAnalyzer.SelectMany.cs b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.SelectMany.cs similarity index 82% rename from Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadAnalyzer/AlternativeMonadAnalyzer.SelectMany.cs rename to Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.SelectMany.cs index 1c4b5e0d..9673806c 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadAnalyzer/AlternativeMonadAnalyzer.SelectMany.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.SelectMany.cs @@ -1,9 +1,9 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Operations; -using static Funcky.Analyzers.AlternativeMonadErrorStateConstructorMatching; +using static Funcky.Analyzers.AlternativeMonad.AlternativeMonadErrorStateConstructorMatching; using static Funcky.Analyzers.FunckyWellKnownMemberNames; -namespace Funcky.Analyzers; +namespace Funcky.Analyzers.AlternativeMonad; public partial class AlternativeMonadAnalyzer { @@ -18,6 +18,6 @@ public partial class AlternativeMonadAnalyzer /// Tests for a Match invocation of the shape Match(none: Option<T>>.None, some: A). private static bool IsSelectManyEquivalent(AlternativeMonadType alternativeMonadType, IInvocationOperation matchInvocation, INamedTypeSymbol receiverType, IArgumentOperation errorStateArgument) - => SymbolEqualityComparer.IncludeNullability.Equals(receiverType, matchInvocation.Type) + => SymbolEqualsIncludeNullability(receiverType, matchInvocation.Type) && IsErrorStateConstructorReference(alternativeMonadType, errorStateArgument.Value); } diff --git a/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadAnalyzer/AlternativeMonadAnalyzer.ToNullable.cs b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.ToNullable.cs similarity index 80% rename from Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadAnalyzer/AlternativeMonadAnalyzer.ToNullable.cs rename to Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.ToNullable.cs index 6204297e..2d00171b 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadAnalyzer/AlternativeMonadAnalyzer.ToNullable.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.ToNullable.cs @@ -1,10 +1,11 @@ +using Funcky.Analyzers.CodeAnalysisExtensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Operations; -using static Funcky.Analyzers.ConstantFunctionMatching; using static Funcky.Analyzers.FunckyWellKnownMemberNames; -using static Funcky.Analyzers.IdentityFunctionMatching; +using static Funcky.Analyzers.Functions.ConstantFunctionMatching; +using static Funcky.Analyzers.Functions.IdentityFunctionMatching; -namespace Funcky.Analyzers; +namespace Funcky.Analyzers.AlternativeMonad; public partial class AlternativeMonadAnalyzer { @@ -29,13 +30,13 @@ private static bool IsToNullableEquivalent( bool IsToNullableReferenceType() => itemType.IsReferenceType - && SymbolEqualityComparer.Default.Equals(receiverType.TypeArguments.Single(), matchInvocation.Type) + && SymbolEquals(receiverType.TypeArguments.Single(), matchInvocation.Type) && IsNullOrNullFunction(noneArgument.Value) && IsIdentityFunction(someArgument.Value); bool IsToNullableValueType() => itemType.IsValueType - && SymbolEqualityComparer.Default.Equals(matchInvocation.SemanticModel?.NullableOfT(itemType), matchInvocation.Type) + && SymbolEquals(matchInvocation.SemanticModel?.NullableOfT(itemType), matchInvocation.Type) && IsNullOrNullFunction(noneArgument.Value) && IsIdentityFunctionWithNullConversion(someArgument.Value); diff --git a/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadAnalyzer/AlternativeMonadAnalyzer.cs b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.cs similarity index 51% rename from Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadAnalyzer/AlternativeMonadAnalyzer.cs rename to Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.cs index a3d5af13..1770456b 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadAnalyzer/AlternativeMonadAnalyzer.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.cs @@ -1,11 +1,11 @@ using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; +using Funcky.Analyzers.CodeAnalysisExtensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; using static Funcky.Analyzers.FunckyWellKnownMemberNames; -namespace Funcky.Analyzers; +namespace Funcky.Analyzers.AlternativeMonad; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed partial class AlternativeMonadAnalyzer : DiagnosticAnalyzer @@ -34,70 +34,68 @@ private static void AnalyzeInvocation(OperationAnalysisContext context, Alternat { var operation = (IInvocationOperation)context.Operation; - if (IsMatchInvocation(operation, alternativeMonadTypes, out var receiverType, out var alternativeMonad) - && AnalyzeMatchInvocation( - operation, - alternativeMonad, - receiverType, - errorStateArgument: operation.GetArgumentForParameterAtIndex(alternativeMonad.ErrorStateArgumentIndex), - successStateArgument: operation.GetArgumentForParameterAtIndex(alternativeMonad.SuccessStateArgumentIndex)) is { } diagnostic) + if (MatchMatchInvocation(operation, alternativeMonadTypes) is [var matchInvocation] + && AnalyzeMatchInvocation(operation, matchInvocation) is { } diagnostic) { context.ReportDiagnostic(diagnostic); } } - private static bool IsMatchInvocation( - IInvocationOperation invocation, - AlternativeMonadTypeCollection alternativeMonadTypes, - [NotNullWhen(true)] out INamedTypeSymbol? matchReceiverType, - [NotNullWhen(true)] out AlternativeMonadType? alternativeMonadType) - { - matchReceiverType = null; - alternativeMonadType = null; - return invocation is { TargetMethod: { ReceiverType: INamedTypeSymbol receiverType, Name: MatchMethodName }, Arguments: [_, _] } - && alternativeMonadTypes.Value.TryGetValue(receiverType.ConstructedFrom, out alternativeMonadType) - && (matchReceiverType = receiverType) is var _; - } + private static Option MatchMatchInvocation(IInvocationOperation invocation, AlternativeMonadTypeCollection alternativeMonadTypes) + => invocation is { TargetMethod: { ReceiverType: INamedTypeSymbol receiverType, Name: MatchMethodName } } + && alternativeMonadTypes.Value.TryGetValue(receiverType.ConstructedFrom, out var alternativeMonadType) + && invocation.GetArgumentsInParameterOrder() is { Length: 2 } arguments + ? [new MatchInvocation( + receiverType, + alternativeMonadType, + arguments[alternativeMonadType.ErrorStateArgumentIndex], + arguments[alternativeMonadType.SuccessStateArgumentIndex])] + : []; private static Diagnostic? AnalyzeMatchInvocation( - IInvocationOperation matchInvocation, - AlternativeMonadType alternativeMonadType, - INamedTypeSymbol receiverType, - IArgumentOperation errorStateArgument, - IArgumentOperation successStateArgument) + IInvocationOperation operation, + MatchInvocation match) { - if (alternativeMonadType.HasToNullable && IsToNullableEquivalent(matchInvocation, receiverType, errorStateArgument, successStateArgument)) + var alternativeMonadType = match.AlternativeMonadType; + + if (alternativeMonadType.HasToNullable && IsToNullableEquivalent(operation, match.Receiver, match.ErrorState, match.SuccessState)) { - return Diagnostic.Create(PreferToNullable, matchInvocation.Syntax.GetLocation()); + return Diagnostic.Create(PreferToNullable, operation.Syntax.GetLocation()); } - if (alternativeMonadType.HasGetOrElse && IsGetOrElseEquivalent(receiverType, errorStateArgument, successStateArgument)) + if (alternativeMonadType.HasGetOrElse && IsGetOrElseEquivalent(match.Receiver, match.ErrorState, match.SuccessState)) { - var errorStateArgumentIndex = matchInvocation.Arguments.IndexOf(errorStateArgument); + var errorStateArgumentIndex = operation.Arguments.IndexOf(match.ErrorState); return Diagnostic.Create( PreferGetOrElse, - matchInvocation.Syntax.GetLocation(), + operation.Syntax.GetLocation(), properties: ImmutableDictionary.Empty.Add(PreservedArgumentIndexProperty, errorStateArgumentIndex.ToString())); } - if (alternativeMonadType.HasOrElse && IsOrElseEquivalent(alternativeMonadType, matchInvocation, receiverType, successStateArgument)) + if (alternativeMonadType.HasOrElse && IsOrElseEquivalent(alternativeMonadType, operation, match.Receiver, match.SuccessState)) { - var errorStateArgumentIndex = matchInvocation.Arguments.IndexOf(errorStateArgument); + var errorStateArgumentIndex = operation.Arguments.IndexOf(match.ErrorState); return Diagnostic.Create( PreferOrElse, - matchInvocation.Syntax.GetLocation(), + operation.Syntax.GetLocation(), properties: ImmutableDictionary.Empty.Add(PreservedArgumentIndexProperty, errorStateArgumentIndex.ToString())); } - if (IsSelectManyEquivalent(alternativeMonadType, matchInvocation, receiverType, errorStateArgument)) + if (IsSelectManyEquivalent(alternativeMonadType, operation, match.Receiver, match.ErrorState)) { - var successStateArgumentIndex = matchInvocation.Arguments.IndexOf(successStateArgument); + var successStateArgumentIndex = operation.Arguments.IndexOf(match.SuccessState); return Diagnostic.Create( PreferSelectMany, - matchInvocation.Syntax.GetLocation(), + operation.Syntax.GetLocation(), properties: ImmutableDictionary.Empty.Add(PreservedArgumentIndexProperty, successStateArgumentIndex.ToString())); } return null; } + + private sealed record MatchInvocation( + INamedTypeSymbol Receiver, + AlternativeMonadType AlternativeMonadType, + IArgumentOperation ErrorState, + IArgumentOperation SuccessState); } diff --git a/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadErrorStateConstructorMatching.cs b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadErrorStateConstructorMatching.cs similarity index 86% rename from Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadErrorStateConstructorMatching.cs rename to Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadErrorStateConstructorMatching.cs index a602e9db..2545f634 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadErrorStateConstructorMatching.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadErrorStateConstructorMatching.cs @@ -1,7 +1,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Operations; -namespace Funcky.Analyzers; +namespace Funcky.Analyzers.AlternativeMonad; internal static class AlternativeMonadErrorStateConstructorMatching { @@ -24,6 +24,6 @@ private static bool IsErrorStateConstructorReference(AlternativeMonadType altern && IsTypeContainingConstructors(alternativeMonadType, containingType); private static bool IsTypeContainingConstructors(AlternativeMonadType alternativeMonadType, INamedTypeSymbol type) - => SymbolEqualityComparer.Default.Equals(type.ConstructedFrom, alternativeMonadType.Type) - || SymbolEqualityComparer.Default.Equals(type.ConstructedFrom, alternativeMonadType.ConstructorsType); + => SymbolEquals(type.ConstructedFrom, alternativeMonadType.Type) + || SymbolEquals(type.ConstructedFrom, alternativeMonadType.ConstructorsType); } diff --git a/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadAnalyzer/AlternativeMonadType.cs b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadType.cs similarity index 97% rename from Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadAnalyzer/AlternativeMonadType.cs rename to Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadType.cs index 16ac9fcd..e6bbc15c 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadAnalyzer/AlternativeMonadType.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadType.cs @@ -1,7 +1,7 @@ using Microsoft.CodeAnalysis; using static Funcky.Analyzers.FunckyWellKnownMemberNames; -namespace Funcky.Analyzers; +namespace Funcky.Analyzers.AlternativeMonad; internal sealed class AlternativeMonadType( INamedTypeSymbol type, diff --git a/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadAnalyzer/AlternativeMonadTypeCollection.cs b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadTypeCollection.cs similarity index 98% rename from Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadAnalyzer/AlternativeMonadTypeCollection.cs rename to Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadTypeCollection.cs index 160dd1f5..6ac1be70 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadAnalyzer/AlternativeMonadTypeCollection.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadTypeCollection.cs @@ -1,7 +1,7 @@ using Microsoft.CodeAnalysis; using static Funcky.Analyzers.FunckyWellKnownMemberNames; -namespace Funcky.Analyzers; +namespace Funcky.Analyzers.AlternativeMonad; internal sealed class AlternativeMonadTypeCollection { diff --git a/Funcky.Analyzers/Funcky.Analyzers/MonadReturnMatching.cs b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/MonadReturnMatching.cs similarity index 75% rename from Funcky.Analyzers/Funcky.Analyzers/MonadReturnMatching.cs rename to Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/MonadReturnMatching.cs index 87302cee..7076458d 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/MonadReturnMatching.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/MonadReturnMatching.cs @@ -1,9 +1,9 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Operations; -using static Funcky.Analyzers.AnonymousFunctionMatching; using static Funcky.Analyzers.FunckyWellKnownMemberNames; +using static Funcky.Analyzers.Functions.AnonymousFunctionMatching; -namespace Funcky.Analyzers; +namespace Funcky.Analyzers.AlternativeMonad; internal static class MonadReturnMatching { @@ -19,17 +19,17 @@ public static bool IsReturnFunction(AlternativeMonadType alternativeMonadType, I private static bool IsReturn(AlternativeMonadType alternativeMonadType, IMethodSymbol method) => method is { Name: var name, IsStatic: true, ContainingType: var methodType } && (name is MonadReturnMethodName || name == alternativeMonadType.ReturnAlias) - && (SymbolEqualityComparer.Default.Equals(methodType.ConstructedFrom, alternativeMonadType.Type) - || SymbolEqualityComparer.Default.Equals(methodType.ConstructedFrom, alternativeMonadType.ConstructorsType)); + && (SymbolEquals(methodType.ConstructedFrom, alternativeMonadType.Type) + || SymbolEquals(methodType.ConstructedFrom, alternativeMonadType.ConstructorsType)); private static bool IsReturnFunction(AlternativeMonadType alternativeMonadType, IAnonymousFunctionOperation anonymousFunction) - => MatchAnonymousUnaryFunctionWithSingleReturn(anonymousFunction, out var returnOperation) + => MatchAnonymousUnaryFunctionWithSingleReturn(anonymousFunction) is [var returnOperation] && returnOperation is { ReturnedValue: IInvocationOperation { Arguments: [{ Value: IParameterReferenceOperation { Parameter.ContainingSymbol: var parameterContainingSymbol } }] } returnedValue } && IsReturn(alternativeMonadType, returnedValue.TargetMethod) - && SymbolEqualityComparer.Default.Equals(parameterContainingSymbol, anonymousFunction.Symbol); + && SymbolEquals(parameterContainingSymbol, anonymousFunction.Symbol); private static bool IsImplicitReturn(AlternativeMonadType alternativeMonadType, IAnonymousFunctionOperation anonymousFunction) - => MatchAnonymousUnaryFunctionWithSingleReturn(anonymousFunction, out var returnOperation) + => MatchAnonymousUnaryFunctionWithSingleReturn(anonymousFunction) is [var returnOperation] && returnOperation is { ReturnedValue: IConversionOperation { IsImplicit: true, Operand: IParameterReferenceOperation, Type: var conversionType } } - && SymbolEqualityComparer.Default.Equals(conversionType?.OriginalDefinition, alternativeMonadType.Type); + && SymbolEquals(conversionType?.OriginalDefinition, alternativeMonadType.Type); } diff --git a/Funcky.Analyzers/Funcky.Analyzers/AnonymousFunctionMatching.cs b/Funcky.Analyzers/Funcky.Analyzers/AnonymousFunctionMatching.cs deleted file mode 100644 index 6c1531b8..00000000 --- a/Funcky.Analyzers/Funcky.Analyzers/AnonymousFunctionMatching.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using Microsoft.CodeAnalysis.Operations; - -namespace Funcky.Analyzers; - -internal static class AnonymousFunctionMatching -{ - /// Matches an anonymous function of the shape (x) => y. - public static bool MatchAnonymousUnaryFunctionWithSingleReturn( - IAnonymousFunctionOperation anonymousFunction, - [NotNullWhen(true)] out IReturnOperation? returnOperation) - => MatchAnonymousFunctionWithSingleReturn(anonymousFunction, out returnOperation) - && anonymousFunction.Symbol.Parameters is [_]; - - /// Matches an anonymous function of the shape (...) => y. - public static bool MatchAnonymousFunctionWithSingleReturn( - IAnonymousFunctionOperation anonymousFunction, - [NotNullWhen(true)] out IReturnOperation? functionReturnOperation) - { - functionReturnOperation = null; - return anonymousFunction.Body.Operations is [IReturnOperation returnOperation] - && (functionReturnOperation = returnOperation) is var _; - } -} diff --git a/Funcky.Analyzers/Funcky.Analyzers/ArgumentOperationExtensions.cs b/Funcky.Analyzers/Funcky.Analyzers/ArgumentOperationExtensions.cs deleted file mode 100644 index 819e203f..00000000 --- a/Funcky.Analyzers/Funcky.Analyzers/ArgumentOperationExtensions.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Microsoft.CodeAnalysis.Operations; - -namespace Funcky.Analyzers; - -internal static class ArgumentOperationExtensions -{ - public static IArgumentOperation GetArgumentForParameterAtIndex(this IInvocationOperation invocationOperation, int parameterIndex) - => GetArgumentForParameterAtIndex(invocationOperation.Arguments, parameterIndex); - - private static IArgumentOperation GetArgumentForParameterAtIndex(this IEnumerable arguments, int parameterIndex) - => arguments.Single(argument => argument.Parameter?.Ordinal == parameterIndex); -} diff --git a/Funcky.Analyzers/Funcky.Analyzers/CodeAnalysisExtensions/ArgumentOperationExtensions.cs b/Funcky.Analyzers/Funcky.Analyzers/CodeAnalysisExtensions/ArgumentOperationExtensions.cs new file mode 100644 index 00000000..7e58ebc9 --- /dev/null +++ b/Funcky.Analyzers/Funcky.Analyzers/CodeAnalysisExtensions/ArgumentOperationExtensions.cs @@ -0,0 +1,27 @@ +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.Operations; + +namespace Funcky.Analyzers.CodeAnalysisExtensions; + +internal static class ArgumentOperationExtensions +{ + /// Gets the arguments in the order that the parameters were declared. + /// Optimal for pattern matching using a list pattern. + public static ArgumentsInParameterOrder GetArgumentsInParameterOrder(this IInvocationOperation invocationOperation) + => new(invocationOperation); +} + +#pragma warning disable SA1201 +internal readonly struct ArgumentsInParameterOrder(IInvocationOperation invocationOperation) +#pragma warning restore SA1201 +{ + private readonly Lazy> _sortedArguments + = new(SortArguments(invocationOperation.Arguments)); + + public int Length => invocationOperation.Arguments.Length; + + public IArgumentOperation this[int index] => _sortedArguments.Value[index]; + + private static Func> SortArguments(ImmutableArray arguments) + => () => arguments.Sort((x, y) => Comparer.Default.Compare(x.Parameter?.Ordinal, y.Parameter?.Ordinal)); +} diff --git a/Funcky.Analyzers/Funcky.Analyzers/SemanticModelExtensions.cs b/Funcky.Analyzers/Funcky.Analyzers/CodeAnalysisExtensions/SemanticModelExtensions.cs similarity index 85% rename from Funcky.Analyzers/Funcky.Analyzers/SemanticModelExtensions.cs rename to Funcky.Analyzers/Funcky.Analyzers/CodeAnalysisExtensions/SemanticModelExtensions.cs index f7fdba74..96d4fac5 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/SemanticModelExtensions.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/CodeAnalysisExtensions/SemanticModelExtensions.cs @@ -1,6 +1,6 @@ using Microsoft.CodeAnalysis; -namespace Funcky.Analyzers; +namespace Funcky.Analyzers.CodeAnalysisExtensions; internal static class SemanticModelExtensions { diff --git a/Funcky.Analyzers/Funcky.Analyzers/CodeAnalysisExtensions/SymbolEqualityFunctions.cs b/Funcky.Analyzers/Funcky.Analyzers/CodeAnalysisExtensions/SymbolEqualityFunctions.cs new file mode 100644 index 00000000..b9253ba6 --- /dev/null +++ b/Funcky.Analyzers/Funcky.Analyzers/CodeAnalysisExtensions/SymbolEqualityFunctions.cs @@ -0,0 +1,12 @@ +using Microsoft.CodeAnalysis; + +namespace Funcky.Analyzers.CodeAnalysisExtensions; + +internal static class SymbolEqualityFunctions +{ + public static bool SymbolEquals(ISymbol? x, ISymbol? y) + => SymbolEqualityComparer.Default.Equals(x, y); + + public static bool SymbolEqualsIncludeNullability(ISymbol? x, ISymbol? y) + => SymbolEqualityComparer.IncludeNullability.Equals(x, y); +} diff --git a/Funcky.Analyzers/Funcky.Analyzers/CodeAnalysisExtensions/SymbolExtensions.cs b/Funcky.Analyzers/Funcky.Analyzers/CodeAnalysisExtensions/SymbolExtensions.cs new file mode 100644 index 00000000..49e9c7db --- /dev/null +++ b/Funcky.Analyzers/Funcky.Analyzers/CodeAnalysisExtensions/SymbolExtensions.cs @@ -0,0 +1,9 @@ +using Microsoft.CodeAnalysis; + +namespace Funcky.Analyzers.CodeAnalysisExtensions; + +internal static class SymbolExtensions +{ + public static bool HasAttribute(this ISymbol symbol, INamedTypeSymbol attributeClass) + => symbol.GetAttributes().Any(attribute => SymbolEquals(attribute.AttributeClass, attributeClass)); +} diff --git a/Funcky.Analyzers/Funcky.Analyzers/SyntaxNodeExtensions.ExpressionTree.cs b/Funcky.Analyzers/Funcky.Analyzers/CodeAnalysisExtensions/SyntaxNodeExtensions.ExpressionTree.cs similarity index 91% rename from Funcky.Analyzers/Funcky.Analyzers/SyntaxNodeExtensions.ExpressionTree.cs rename to Funcky.Analyzers/Funcky.Analyzers/CodeAnalysisExtensions/SyntaxNodeExtensions.ExpressionTree.cs index 10843c70..375a2815 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/SyntaxNodeExtensions.ExpressionTree.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/CodeAnalysisExtensions/SyntaxNodeExtensions.ExpressionTree.cs @@ -3,7 +3,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -namespace Funcky.Analyzers; +namespace Funcky.Analyzers.CodeAnalysisExtensions; internal static partial class SyntaxNodeExtensions { @@ -32,7 +32,7 @@ var node when node.IsAnyLambda() => LambdaIsExpressionTree(context), private static bool LambdaIsExpressionTree(IsExpressionTreeContext context) { var typeInfo = context.SemanticModel.GetTypeInfo(context.Syntax, context.CancellationToken); - return SymbolEqualityComparer.Default.Equals(context.ExpressionType, typeInfo.ConvertedType?.OriginalDefinition); + return SymbolEquals(context.ExpressionType, typeInfo.ConvertedType?.OriginalDefinition); } private static bool QueryExpressionIsExpressionTree(IsExpressionTreeContext context) @@ -53,7 +53,7 @@ private static bool TakesExpressionTree(SymbolInfo info, INamedTypeSymbol expres private static bool TakesExpressionTreeAsFirstArgument(ISymbol symbol, INamedTypeSymbol expressionType) => symbol is IMethodSymbol { Parameters: [var firstParameter, ..] } - && SymbolEqualityComparer.Default.Equals(expressionType, firstParameter.Type.OriginalDefinition); + && SymbolEquals(expressionType, firstParameter.Type.OriginalDefinition); private sealed record IsExpressionTreeContext( SyntaxNode Syntax, diff --git a/Funcky.Analyzers/Funcky.Analyzers/SyntaxNodeExtensions.cs b/Funcky.Analyzers/Funcky.Analyzers/CodeAnalysisExtensions/SyntaxNodeExtensions.cs similarity index 75% rename from Funcky.Analyzers/Funcky.Analyzers/SyntaxNodeExtensions.cs rename to Funcky.Analyzers/Funcky.Analyzers/CodeAnalysisExtensions/SyntaxNodeExtensions.cs index ddce46ae..e3934412 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/SyntaxNodeExtensions.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/CodeAnalysisExtensions/SyntaxNodeExtensions.cs @@ -3,17 +3,17 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; -namespace Funcky.Analyzers; +namespace Funcky.Analyzers.CodeAnalysisExtensions; internal static partial class SyntaxNodeExtensions { - internal static ImmutableArray GetAllSymbols(SymbolInfo info) + private static ImmutableArray GetAllSymbols(SymbolInfo info) => info.Symbol == null ? info.CandidateSymbols : ImmutableArray.Create(info.Symbol); // Copied from Roslyn's source code as this API is not public: // https://github.com/dotnet/roslyn/blob/232f7afa4966411958759c880de3a1765bdb28a0/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs#L925 - internal static bool IsAnyLambda([NotNullWhen(returnValue: true)] this SyntaxNode? node) + private static bool IsAnyLambda([NotNullWhen(returnValue: true)] this SyntaxNode? node) => node?.Kind() is SyntaxKind.ParenthesizedLambdaExpression or SyntaxKind.SimpleLambdaExpression; } diff --git a/Funcky.Analyzers/Funcky.Analyzers/EnumerableRepeatNeverAnalyzer.cs b/Funcky.Analyzers/Funcky.Analyzers/EnumerableRepeatNeverAnalyzer.cs index 442a05e8..fbfd5f44 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/EnumerableRepeatNeverAnalyzer.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/EnumerableRepeatNeverAnalyzer.cs @@ -1,5 +1,5 @@ using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; +using Funcky.Analyzers.CodeAnalysisExtensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; @@ -44,21 +44,17 @@ private static Action FindEnumerableRepeatNever(INamed { var operation = (IInvocationOperation)context.Operation; - if (MatchRepeatNever(enumerableType, operation, out var valueArgument)) + if (MatchRepeatNever(enumerableType, operation) is [var valueArgument]) { context.ReportDiagnostic(CreateDiagnostic(operation, valueArgument)); } }; - private static bool MatchRepeatNever( - INamedTypeSymbol enumerableType, - IInvocationOperation operation, - [NotNullWhen(true)] out IArgumentOperation? valueArgument) - { - valueArgument = null; - return MatchMethod(operation, enumerableType, nameof(Enumerable.Repeat)) - && MatchArguments(operation, out valueArgument, AnyArgument, out _, ConstantArgument(0)); - } + private static Option MatchRepeatNever(INamedTypeSymbol enumerableType, IInvocationOperation operation) + => MatchMethod(operation, enumerableType, nameof(Enumerable.Repeat)) + && operation.GetArgumentsInParameterOrder() is [var valueArgument, var countArgument] + && MatchConstantArgument(countArgument, 0) + ? [valueArgument] : []; private static Diagnostic CreateDiagnostic(IInvocationOperation operation, IArgumentOperation valueArgument) => Diagnostic.Create( diff --git a/Funcky.Analyzers/Funcky.Analyzers/EnumerableRepeatOnceAnalyzer.cs b/Funcky.Analyzers/Funcky.Analyzers/EnumerableRepeatOnceAnalyzer.cs index 49a86ab5..ecde62be 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/EnumerableRepeatOnceAnalyzer.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/EnumerableRepeatOnceAnalyzer.cs @@ -1,5 +1,5 @@ using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; +using Funcky.Analyzers.CodeAnalysisExtensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; @@ -44,21 +44,17 @@ private static Action FindEnumerableRepeatOnce(INamedT => context => { var operation = (IInvocationOperation)context.Operation; - if (MatchRepeatOnce(operation, enumerableType, out var valueArgument)) + if (MatchRepeatOnce(operation, enumerableType) is [var valueArgument]) { context.ReportDiagnostic(CreateDiagnostic(operation, valueArgument)); } }; - private static bool MatchRepeatOnce( - IInvocationOperation operation, - INamedTypeSymbol enumerableType, - [NotNullWhen(true)] out IArgumentOperation? valueArgument) - { - valueArgument = null; - return MatchMethod(operation, enumerableType, nameof(Enumerable.Repeat)) - && MatchArguments(operation, out valueArgument, AnyArgument, out _, ConstantArgument(1)); - } + private static Option MatchRepeatOnce(IInvocationOperation operation, INamedTypeSymbol enumerableType) + => MatchMethod(operation, enumerableType, nameof(Enumerable.Repeat)) + && operation.GetArgumentsInParameterOrder() is [var valueArgument, var countArgument] + && MatchConstantArgument(countArgument, 1) + ? [valueArgument] : []; private static Diagnostic CreateDiagnostic(IInvocationOperation operation, IArgumentOperation valueArgument) => Diagnostic.Create( diff --git a/Funcky.Analyzers/Funcky.Analyzers/Funcky.Analyzers.csproj.DotSettings b/Funcky.Analyzers/Funcky.Analyzers/Funcky.Analyzers.csproj.DotSettings deleted file mode 100644 index cd7be2cd..00000000 --- a/Funcky.Analyzers/Funcky.Analyzers/Funcky.Analyzers.csproj.DotSettings +++ /dev/null @@ -1,2 +0,0 @@ - - True diff --git a/Funcky.Analyzers/Funcky.Analyzers/Funcky.Analyzers.targets b/Funcky.Analyzers/Funcky.Analyzers/Funcky.Analyzers.targets index 3b66a77f..b1bea80b 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/Funcky.Analyzers.targets +++ b/Funcky.Analyzers/Funcky.Analyzers/Funcky.Analyzers.targets @@ -25,6 +25,9 @@ + + + True diff --git a/Funcky.Analyzers/Funcky.Analyzers/FunctionalAssert/FunctionalAssertAnalyzer.cs b/Funcky.Analyzers/Funcky.Analyzers/FunctionalAssert/FunctionalAssertAnalyzer.cs index a47bc046..73797dab 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/FunctionalAssert/FunctionalAssertAnalyzer.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/FunctionalAssert/FunctionalAssertAnalyzer.cs @@ -44,7 +44,7 @@ private static Action AnalyzeInvocation(AssertMethodHa => context => { var invocation = (IInvocationOperation)context.Operation; - if (MatchGenericAssertEqualInvocation(invocation, out var expectedArgument, out var actualArgument) + if (MatchGenericAssertEqualInvocation(invocation) is [var (expectedArgument, actualArgument)] && actualArgument.Value is IInvocationOperation innerInvocation && IsAssertMethodWithAccompanyingEqualOverload(innerInvocation, attributeType)) { diff --git a/Funcky.Analyzers/Funcky.Analyzers/FunctionalAssert/FunctionalAssertMatching.cs b/Funcky.Analyzers/Funcky.Analyzers/FunctionalAssert/FunctionalAssertMatching.cs index 1efd7fed..00fe89ec 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/FunctionalAssert/FunctionalAssertMatching.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/FunctionalAssert/FunctionalAssertMatching.cs @@ -1,3 +1,4 @@ +using Funcky.Analyzers.CodeAnalysisExtensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Operations; @@ -8,11 +9,7 @@ public sealed class FunctionalAssertMatching public static bool IsAssertMethodWithAccompanyingEqualOverload( IInvocationOperation invocation, AssertMethodHasOverloadWithExpectedValueAttributeType attributeType) - => invocation.TargetMethod.GetAttributes().Any(IsAttribute(attributeType.Value)) - && invocation.Arguments.Length == 1; - - private static Func IsAttribute(INamedTypeSymbol attributeType) - => data => SymbolEqualityComparer.Default.Equals(data.AttributeClass, attributeType); + => invocation.TargetMethod.HasAttribute(attributeType.Value) && invocation.Arguments.Length == 1; } public sealed record AssertMethodHasOverloadWithExpectedValueAttributeType(INamedTypeSymbol Value); diff --git a/Funcky.Analyzers/Funcky.Analyzers/FunctionalAssert/XunitAssertMatching.cs b/Funcky.Analyzers/Funcky.Analyzers/FunctionalAssert/XunitAssertMatching.cs index 4eb44a24..b38ac85a 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/FunctionalAssert/XunitAssertMatching.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/FunctionalAssert/XunitAssertMatching.cs @@ -1,29 +1,18 @@ -using System.Diagnostics.CodeAnalysis; -using Microsoft.CodeAnalysis; +using Funcky.Analyzers.CodeAnalysisExtensions; using Microsoft.CodeAnalysis.Operations; using static Funcky.Analyzers.FunckyWellKnownMemberNames; +using static Funcky.Analyzers.OperationMatching; namespace Funcky.Analyzers.FunctionalAssert; internal sealed class XunitAssertMatching { - public static bool MatchGenericAssertEqualInvocation( - IInvocationOperation invocation, - [NotNullWhen(true)] out IArgumentOperation? expectedArgument, - [NotNullWhen(true)] out IArgumentOperation? actualArgument) - { - const int expectedParameterIndex = 0; - const int actualParameterIndex = 1; - expectedArgument = null; - actualArgument = null; - return invocation.TargetMethod.Name == XunitAssert.EqualMethodName - && invocation.SemanticModel?.Compilation.GetXunitAssertType() is { } assertType - && SymbolEqualityComparer.Default.Equals(invocation.TargetMethod.ContainingType, assertType) - && invocation.TargetMethod.TypeParameters is [var typeParameter] - && invocation.TargetMethod.OriginalDefinition.Parameters is [var firstParameter, var secondParameter] - && SymbolEqualityComparer.Default.Equals(firstParameter.Type, typeParameter) - && SymbolEqualityComparer.Default.Equals(secondParameter.Type, typeParameter) - && (expectedArgument = invocation.GetArgumentForParameterAtIndex(expectedParameterIndex)) is var _ - && (actualArgument = invocation.GetArgumentForParameterAtIndex(actualParameterIndex)) is var _; - } + public static Option<(IArgumentOperation Expected, IArgumentOperation Actual)> MatchGenericAssertEqualInvocation( + IInvocationOperation invocation) + => MatchMethod(invocation, invocation.SemanticModel?.Compilation.GetXunitAssertType(), XunitAssert.EqualMethodName) + && invocation.TargetMethod.TypeParameters is [var typeParameter] + && invocation.GetArgumentsInParameterOrder() is [var expectedArgument, var actualArgument] + && SymbolEquals(expectedArgument.Parameter?.OriginalDefinition.Type, typeParameter) + && SymbolEquals(actualArgument.Parameter?.OriginalDefinition.Type, typeParameter) + ? [(expectedArgument, actualArgument)] : []; } diff --git a/Funcky.Analyzers/Funcky.Analyzers/Functions/AnonymousFunctionMatching.cs b/Funcky.Analyzers/Funcky.Analyzers/Functions/AnonymousFunctionMatching.cs new file mode 100644 index 00000000..c3e9ae9a --- /dev/null +++ b/Funcky.Analyzers/Funcky.Analyzers/Functions/AnonymousFunctionMatching.cs @@ -0,0 +1,18 @@ +using Microsoft.CodeAnalysis.Operations; + +namespace Funcky.Analyzers.Functions; + +internal static class AnonymousFunctionMatching +{ + /// Matches an anonymous function of the shape (x) => y. + public static Option MatchAnonymousUnaryFunctionWithSingleReturn( + IAnonymousFunctionOperation anonymousFunction) + => MatchAnonymousFunctionWithSingleReturn(anonymousFunction) is [var returnOperation] + && anonymousFunction.Symbol.Parameters is [_] + ? [returnOperation] : []; + + /// Matches an anonymous function of the shape (...) => y. + public static Option MatchAnonymousFunctionWithSingleReturn(IAnonymousFunctionOperation anonymousFunction) + => anonymousFunction.Body.Operations is [IReturnOperation returnOperation] + ? [returnOperation] : []; +} diff --git a/Funcky.Analyzers/Funcky.Analyzers/ConstantFunctionMatching.cs b/Funcky.Analyzers/Funcky.Analyzers/Functions/ConstantFunctionMatching.cs similarity index 86% rename from Funcky.Analyzers/Funcky.Analyzers/ConstantFunctionMatching.cs rename to Funcky.Analyzers/Funcky.Analyzers/Functions/ConstantFunctionMatching.cs index 5e7a8c0b..d209cc39 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/ConstantFunctionMatching.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/Functions/ConstantFunctionMatching.cs @@ -1,8 +1,8 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Operations; -using static Funcky.Analyzers.AnonymousFunctionMatching; +using static Funcky.Analyzers.Functions.AnonymousFunctionMatching; -namespace Funcky.Analyzers; +namespace Funcky.Analyzers.Functions; internal static class ConstantFunctionMatching { @@ -15,7 +15,7 @@ public static bool IsConstantFunction(IOperation operation, object? expectedValu }; private static bool IsConstantFunction(IAnonymousFunctionOperation anonymousFunction, object? expectedValue) - => MatchAnonymousFunctionWithSingleReturn(anonymousFunction, out var returnOperation) + => MatchAnonymousFunctionWithSingleReturn(anonymousFunction) is [var returnOperation] && returnOperation.ReturnedValue is { ConstantValue: { HasValue: true, Value: var returnedValue } } && returnedValue == expectedValue; } diff --git a/Funcky.Analyzers/Funcky.Analyzers/IdentityFunctionMatching.cs b/Funcky.Analyzers/Funcky.Analyzers/Functions/IdentityFunctionMatching.cs similarity index 79% rename from Funcky.Analyzers/Funcky.Analyzers/IdentityFunctionMatching.cs rename to Funcky.Analyzers/Funcky.Analyzers/Functions/IdentityFunctionMatching.cs index 5222c2b8..161b7797 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/IdentityFunctionMatching.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/Functions/IdentityFunctionMatching.cs @@ -1,9 +1,9 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Operations; -using static Funcky.Analyzers.AnonymousFunctionMatching; using static Funcky.Analyzers.FunckyWellKnownMemberNames; +using static Funcky.Analyzers.Functions.AnonymousFunctionMatching; -namespace Funcky.Analyzers; +namespace Funcky.Analyzers.Functions; internal static class IdentityFunctionMatching { @@ -18,18 +18,18 @@ public static bool IsIdentityFunction(IOperation operation) public static bool IsIdentityFunctionWithNullConversion(IOperation operation) => operation is IDelegateCreationOperation { Target: IAnonymousFunctionOperation anonymousFunction } - && MatchAnonymousUnaryFunctionWithSingleReturn(anonymousFunction, out var returnOperation) + && MatchAnonymousUnaryFunctionWithSingleReturn(anonymousFunction) is [var returnOperation] && returnOperation.ReturnedValue is IConversionOperation { Conversion.IsNullable: true, Operand: IParameterReferenceOperation { Parameter.ContainingSymbol: var parameterContainingSymbol } } - && SymbolEqualityComparer.Default.Equals(parameterContainingSymbol, anonymousFunction.Symbol); + && SymbolEquals(parameterContainingSymbol, anonymousFunction.Symbol); private static bool IsAnonymousIdentityFunction(IAnonymousFunctionOperation anonymousFunction) - => MatchAnonymousUnaryFunctionWithSingleReturn(anonymousFunction, out var returnOperation) + => MatchAnonymousUnaryFunctionWithSingleReturn(anonymousFunction) is [var returnOperation] && returnOperation.ReturnedValue is IParameterReferenceOperation { Parameter.ContainingSymbol: var parameterContainingSymbol } - && SymbolEqualityComparer.Default.Equals(parameterContainingSymbol, anonymousFunction.Symbol); + && SymbolEquals(parameterContainingSymbol, anonymousFunction.Symbol); private static bool IsFunckyIdentityFunction(IMethodReferenceOperation methodReference) => methodReference.Method.Name == IdentityMethodName - && SymbolEqualityComparer.Default.Equals( + && SymbolEquals( methodReference.Method.ContainingType, methodReference.SemanticModel?.Compilation.GetFunctionalType()); } diff --git a/Funcky.Analyzers/Funcky.Analyzers/JoinToStringEmptyAnalyzer.cs b/Funcky.Analyzers/Funcky.Analyzers/JoinToStringEmptyAnalyzer.cs index 52a9ee57..bab8986c 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/JoinToStringEmptyAnalyzer.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/JoinToStringEmptyAnalyzer.cs @@ -1,5 +1,5 @@ using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; +using Funcky.Analyzers.CodeAnalysisExtensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; @@ -47,23 +47,20 @@ private static Action FindJoinToStringEmpty(Symbols sy { var operation = (IInvocationOperation)context.Operation; - if (MatchJoinToStringEmpty(symbols, operation, out var valueArgument, out var stringArgument)) + if (MatchJoinToStringEmpty(symbols, operation) is [var (valueArgument, stringArgument)]) { context.ReportDiagnostic(CreateDiagnostic(operation, valueArgument, stringArgument)); } }; - private static bool MatchJoinToStringEmpty(Symbols symbols, IInvocationOperation operation, [NotNullWhen(true)] out IArgumentOperation? valueArgument, [NotNullWhen(true)] out IArgumentOperation? stringArgument) - { - valueArgument = null; - stringArgument = null; - return MatchMethod(operation, symbols.EnumerableType, JoinToString) - && MatchArguments(operation, out valueArgument, AnyArgument, out stringArgument, IsEmptyString(symbols.StringType)); - } + private static Option<(IArgumentOperation Value, IArgumentOperation String)> MatchJoinToStringEmpty(Symbols symbols, IInvocationOperation operation) + => MatchMethod(operation, symbols.EnumerableType, JoinToString) + && operation.GetArgumentsInParameterOrder() is [var valueArgument, var stringArgument] + && IsEmptyString(stringArgument, symbols.StringType) + ? [(valueArgument, stringArgument)] : []; - private static Func IsEmptyString(INamedTypeSymbol stringType) - => argument - => IsEmptyStringConstant(argument) || IsStringEmptyField(stringType, argument); + private static bool IsEmptyString(IArgumentOperation argument, INamedTypeSymbol stringType) + => IsEmptyStringConstant(argument) || IsStringEmptyField(stringType, argument); private static bool IsStringEmptyField(INamedTypeSymbol stringType, IArgumentOperation argument) => argument.Value is IFieldReferenceOperation fieldReferenceOperation && MatchField(fieldReferenceOperation, stringType, nameof(string.Empty)); diff --git a/Funcky.Analyzers/Funcky.Analyzers/NonDefaultableAnalyzer.cs b/Funcky.Analyzers/Funcky.Analyzers/NonDefaultable/NonDefaultableAnalyzer.cs similarity index 88% rename from Funcky.Analyzers/Funcky.Analyzers/NonDefaultableAnalyzer.cs rename to Funcky.Analyzers/Funcky.Analyzers/NonDefaultable/NonDefaultableAnalyzer.cs index c8075684..15f16eb3 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/NonDefaultableAnalyzer.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/NonDefaultable/NonDefaultableAnalyzer.cs @@ -1,9 +1,10 @@ using System.Collections.Immutable; +using Funcky.Analyzers.CodeAnalysisExtensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; -namespace Funcky.Analyzers; +namespace Funcky.Analyzers.NonDefaultable; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class NonDefaultableAnalyzer : DiagnosticAnalyzer @@ -30,7 +31,7 @@ public override void Initialize(AnalysisContext context) internal static bool IsParameterlessObjectCreationOfNonDefaultableStruct(IObjectCreationOperation operation, INamedTypeSymbol nonDefaultableAttribute) => operation is { Type: { } type, Arguments.Length: 0, Initializer: null } - && type.GetAttributes().Any(IsAttribute(nonDefaultableAttribute)); + && type.HasAttribute(nonDefaultableAttribute); private static void OnCompilationStart(CompilationStartAnalysisContext context) { @@ -45,7 +46,7 @@ private static Action AnalyzeDefaultValueOperation(INa => context => { var operation = (IDefaultValueOperation)context.Operation; - if (operation.Type is { } type && type.GetAttributes().Any(IsAttribute(nonDefaultableAttribute))) + if (operation.Type is { } type && type.HasAttribute(nonDefaultableAttribute)) { ReportDiagnostic(context); } @@ -66,7 +67,4 @@ private static void ReportDiagnostic(OperationAnalysisContext context) DoNotUseDefault, context.Operation.Syntax.GetLocation(), messageArgs: context.Operation.Type?.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat))); - - private static Func IsAttribute(INamedTypeSymbol attributeClass) - => attribute => SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, attributeClass); } diff --git a/Funcky.Analyzers/Funcky.Analyzers/NonDefaultableStyleCopSuppressor.cs b/Funcky.Analyzers/Funcky.Analyzers/NonDefaultable/NonDefaultableStyleCopSuppressor.cs similarity index 94% rename from Funcky.Analyzers/Funcky.Analyzers/NonDefaultableStyleCopSuppressor.cs rename to Funcky.Analyzers/Funcky.Analyzers/NonDefaultable/NonDefaultableStyleCopSuppressor.cs index 977911ac..f655b0fb 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/NonDefaultableStyleCopSuppressor.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/NonDefaultable/NonDefaultableStyleCopSuppressor.cs @@ -2,9 +2,9 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; -using static Funcky.Analyzers.NonDefaultableAnalyzer; +using static Funcky.Analyzers.NonDefaultable.NonDefaultableAnalyzer; -namespace Funcky.Analyzers; +namespace Funcky.Analyzers.NonDefaultable; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class NonDefaultableStyleCopSuppressor : DiagnosticSuppressor diff --git a/Funcky.Analyzers/Funcky.Analyzers/OperationMatching.cs b/Funcky.Analyzers/Funcky.Analyzers/OperationMatching.cs index 7d02bbd5..db600c89 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/OperationMatching.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/OperationMatching.cs @@ -1,4 +1,3 @@ -using System.Diagnostics.CodeAnalysis; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Operations; @@ -8,38 +7,19 @@ internal static class OperationMatching { public static bool MatchMethod( IInvocationOperation operation, - INamedTypeSymbol type, + INamedTypeSymbol? type, string methodName) - => SymbolEqualityComparer.Default.Equals(operation.TargetMethod.ContainingType, type) - && operation.TargetMethod.Name == methodName; - - public static bool MatchArguments( - IInvocationOperation operation, - [NotNullWhen(true)] out IArgumentOperation? firstArgument, - Func matchFirstArgument, - [NotNullWhen(true)] out IArgumentOperation? secondArgument, - Func matchSecondArgument) - { - firstArgument = null; - secondArgument = null; - return operation.Arguments is [_, _] - && (firstArgument = operation.GetArgumentForParameterAtIndex(0)) is var _ - && (secondArgument = operation.GetArgumentForParameterAtIndex(1)) is var _ - && matchFirstArgument(firstArgument) - && matchSecondArgument(secondArgument); - } + => SymbolEquals(operation.TargetMethod.ContainingType, type) && operation.TargetMethod.Name == methodName; public static bool MatchField( IFieldReferenceOperation operation, INamedTypeSymbol type, string fieldName) - => SymbolEqualityComparer.Default.Equals(operation.Type, type) - && operation.Field.Name == fieldName; + => SymbolEquals(operation.Type, type) && operation.Field.Name == fieldName; - public static bool AnyArgument(IArgumentOperation operation) => true; + public static bool MatchConstantArgument(IArgumentOperation argument, object? expectedValue) + => argument is { Value.ConstantValue: { HasValue: true, Value: var value } } && Equals(value, expectedValue); public static Func ConstantArgument(object? expectedValue) - => argument - => argument is { Value.ConstantValue: { HasValue: true, Value: var value } } - && Equals(value, expectedValue); + => argument => MatchConstantArgument(argument, expectedValue); } diff --git a/Funcky.Analyzers/Funcky.Analyzers/Option/Option.ListPattern.cs b/Funcky.Analyzers/Funcky.Analyzers/Option/Option.ListPattern.cs new file mode 100644 index 00000000..402e8ff6 --- /dev/null +++ b/Funcky.Analyzers/Funcky.Analyzers/Option/Option.ListPattern.cs @@ -0,0 +1,24 @@ +using System.ComponentModel; +using System.Diagnostics; + +namespace Funcky.Analyzers; + +internal readonly partial struct Option +{ + private const int NoneLength = 0; + private const int SomeLength = 1; + + [EditorBrowsable(EditorBrowsableState.Never)] + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + public int Count + => _hasItem + ? SomeLength + : NoneLength; + + [EditorBrowsable(EditorBrowsableState.Never)] + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + public TItem this[int index] + => _hasItem && index is 0 + ? _item + : throw new IndexOutOfRangeException("Index was out of range."); +} diff --git a/Funcky.Analyzers/Funcky.Analyzers/Option/Option.cs b/Funcky.Analyzers/Funcky.Analyzers/Option/Option.cs new file mode 100644 index 00000000..ca79efbc --- /dev/null +++ b/Funcky.Analyzers/Funcky.Analyzers/Option/Option.cs @@ -0,0 +1,68 @@ +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace Funcky.Analyzers; + +/// A very minimal option implementation +/// intended for use with pattern matching. +[CollectionBuilder(typeof(Option), nameof(Option.Create))] +internal readonly partial struct Option + where TItem : notnull +{ + private readonly bool _hasItem; + private readonly TItem _item; + + [EditorBrowsable(EditorBrowsableState.Never)] + internal Option(TItem item) + { + if (item is null) + { + throw new ArgumentNullException(nameof(item)); + } + + _item = item; + _hasItem = true; + } + + // Enumerator is required by collection expression. + [EditorBrowsable(EditorBrowsableState.Never)] + public Enumerator GetEnumerator() => new(this); + + [EditorBrowsable(EditorBrowsableState.Never)] + public struct Enumerator + { + private readonly TItem _item; + private bool _hasItem; + + [EditorBrowsable(EditorBrowsableState.Never)] + internal Enumerator(Option option) + { + _item = option._item; + _hasItem = option._hasItem; + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public readonly TItem Current => _item; + + [EditorBrowsable(EditorBrowsableState.Never)] + public bool MoveNext() + { + var hasItem = _hasItem; + _hasItem = false; + return hasItem; + } + } +} + +internal static class Option +{ + [EditorBrowsable(EditorBrowsableState.Never)] + public static Option Create(ReadOnlySpan values) + where TItem : notnull + => values.Length switch + { + 0 => default, + 1 => new Option(values[0]), + _ => throw new ArgumentException("An option can contain only zero or one elements"), + }; +} diff --git a/Funcky.Analyzers/Funcky.Analyzers/UseWithArgumentNamesAnalyzer.cs b/Funcky.Analyzers/Funcky.Analyzers/UseWithArgumentNamesAnalyzer.cs index 6228d496..e128d033 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/UseWithArgumentNamesAnalyzer.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/UseWithArgumentNamesAnalyzer.cs @@ -1,4 +1,5 @@ using System.Collections.Immutable; +using Funcky.Analyzers.CodeAnalysisExtensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; @@ -45,7 +46,7 @@ private static Action AnalyzeInvocation(INamedTypeSymb var invocation = (IInvocationOperation)context.Operation; var semanticModel = invocation.SemanticModel ?? throw new InvalidOperationException("Semantic model is never be null for operations passed to an analyzer (according to docs)"); - if (invocation.TargetMethod.GetAttributes().Any(attribute => SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, attributeSymbol)) + if (invocation.TargetMethod.HasAttribute(attributeSymbol) && !invocation.Syntax.IsInExpressionTree(semanticModel, expressionOfTType, context.CancellationToken)) { foreach (var argument in invocation.Arguments)