From c0a24ba42ba92ee92594d711ab0d60d31e70b550 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tau=20G=C3=A4rtli?= Date: Wed, 5 Feb 2025 16:27:36 +0100 Subject: [PATCH 01/10] Add helper functions for symbol comparison --- .../Funcky.Analyzers.CodeFixes.targets | 4 ++++ .../OptionSomeWhereToFromBooleanRefactoring.cs | 4 ++-- .../AlternativeMonadAnalyzer.GetOrElse.cs | 4 ++-- .../AlternativeMonadAnalyzer.OrElse.cs | 2 +- .../AlternativeMonadAnalyzer.SelectMany.cs | 2 +- .../AlternativeMonadAnalyzer.ToNullable.cs | 4 ++-- .../AlternativeMonadErrorStateConstructorMatching.cs | 4 ++-- .../Funcky.Analyzers/Funcky.Analyzers.targets | 3 +++ .../FunctionalAssert/FunctionalAssertMatching.cs | 2 +- .../FunctionalAssert/XunitAssertMatching.cs | 7 +++---- .../Funcky.Analyzers/IdentityFunctionMatching.cs | 6 +++--- .../Funcky.Analyzers/MonadReturnMatching.cs | 8 ++++---- .../Funcky.Analyzers/NonDefaultableAnalyzer.cs | 2 +- .../Funcky.Analyzers/OperationMatching.cs | 6 ++---- .../Funcky.Analyzers/SymbolEqualityFunctions.cs | 12 ++++++++++++ .../SyntaxNodeExtensions.ExpressionTree.cs | 4 ++-- .../Funcky.Analyzers/UseWithArgumentNamesAnalyzer.cs | 2 +- 17 files changed, 46 insertions(+), 30 deletions(-) create mode 100644 Funcky.Analyzers/Funcky.Analyzers/SymbolEqualityFunctions.cs diff --git a/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/Funcky.Analyzers.CodeFixes.targets b/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/Funcky.Analyzers.CodeFixes.targets index 2a13de99..33ec1a3a 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/OptionSomeWhereToFromBooleanRefactoring.cs b/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/OptionSomeWhereToFromBooleanRefactoring.cs index b742cc66..8d161a6d 100644 --- a/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/OptionSomeWhereToFromBooleanRefactoring.cs +++ b/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/OptionSomeWhereToFromBooleanRefactoring.cs @@ -52,7 +52,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 +60,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/AlternativeMonadAnalyzer/AlternativeMonadAnalyzer.GetOrElse.cs b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadAnalyzer/AlternativeMonadAnalyzer.GetOrElse.cs index d9a299c1..c712e3b0 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadAnalyzer/AlternativeMonadAnalyzer.GetOrElse.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadAnalyzer/AlternativeMonadAnalyzer.GetOrElse.cs @@ -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/AlternativeMonadAnalyzer/AlternativeMonadAnalyzer.OrElse.cs index cb3e6170..94b958ae 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadAnalyzer/AlternativeMonadAnalyzer.OrElse.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadAnalyzer/AlternativeMonadAnalyzer.OrElse.cs @@ -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/AlternativeMonadAnalyzer/AlternativeMonadAnalyzer.SelectMany.cs index 1c4b5e0d..ab71a935 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadAnalyzer/AlternativeMonadAnalyzer.SelectMany.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadAnalyzer/AlternativeMonadAnalyzer.SelectMany.cs @@ -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/AlternativeMonadAnalyzer/AlternativeMonadAnalyzer.ToNullable.cs index 6204297e..bed10165 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadAnalyzer/AlternativeMonadAnalyzer.ToNullable.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadAnalyzer/AlternativeMonadAnalyzer.ToNullable.cs @@ -29,13 +29,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/AlternativeMonadErrorStateConstructorMatching.cs b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadErrorStateConstructorMatching.cs index a602e9db..a97109d1 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadErrorStateConstructorMatching.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadErrorStateConstructorMatching.cs @@ -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/Funcky.Analyzers.targets b/Funcky.Analyzers/Funcky.Analyzers/Funcky.Analyzers.targets index 3b66a77f..bd760d0f 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/FunctionalAssertMatching.cs b/Funcky.Analyzers/Funcky.Analyzers/FunctionalAssert/FunctionalAssertMatching.cs index 1efd7fed..188737ad 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/FunctionalAssert/FunctionalAssertMatching.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/FunctionalAssert/FunctionalAssertMatching.cs @@ -12,7 +12,7 @@ public static bool IsAssertMethodWithAccompanyingEqualOverload( && invocation.Arguments.Length == 1; private static Func IsAttribute(INamedTypeSymbol attributeType) - => data => SymbolEqualityComparer.Default.Equals(data.AttributeClass, attributeType); + => data => SymbolEquals(data.AttributeClass, attributeType); } 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..9fadd5e3 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/FunctionalAssert/XunitAssertMatching.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/FunctionalAssert/XunitAssertMatching.cs @@ -1,5 +1,4 @@ using System.Diagnostics.CodeAnalysis; -using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Operations; using static Funcky.Analyzers.FunckyWellKnownMemberNames; @@ -18,11 +17,11 @@ public static bool MatchGenericAssertEqualInvocation( actualArgument = null; return invocation.TargetMethod.Name == XunitAssert.EqualMethodName && invocation.SemanticModel?.Compilation.GetXunitAssertType() is { } assertType - && SymbolEqualityComparer.Default.Equals(invocation.TargetMethod.ContainingType, assertType) + && SymbolEquals(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) + && SymbolEquals(firstParameter.Type, typeParameter) + && SymbolEquals(secondParameter.Type, typeParameter) && (expectedArgument = invocation.GetArgumentForParameterAtIndex(expectedParameterIndex)) is var _ && (actualArgument = invocation.GetArgumentForParameterAtIndex(actualParameterIndex)) is var _; } diff --git a/Funcky.Analyzers/Funcky.Analyzers/IdentityFunctionMatching.cs b/Funcky.Analyzers/Funcky.Analyzers/IdentityFunctionMatching.cs index 5222c2b8..e3da194e 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/IdentityFunctionMatching.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/IdentityFunctionMatching.cs @@ -20,16 +20,16 @@ public static bool IsIdentityFunctionWithNullConversion(IOperation operation) => operation is IDelegateCreationOperation { Target: IAnonymousFunctionOperation anonymousFunction } && MatchAnonymousUnaryFunctionWithSingleReturn(anonymousFunction, out 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) && 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/MonadReturnMatching.cs b/Funcky.Analyzers/Funcky.Analyzers/MonadReturnMatching.cs index 87302cee..b4fd6669 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/MonadReturnMatching.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/MonadReturnMatching.cs @@ -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) && 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) && 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/NonDefaultableAnalyzer.cs b/Funcky.Analyzers/Funcky.Analyzers/NonDefaultableAnalyzer.cs index c8075684..003b1360 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/NonDefaultableAnalyzer.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/NonDefaultableAnalyzer.cs @@ -68,5 +68,5 @@ private static void ReportDiagnostic(OperationAnalysisContext context) messageArgs: context.Operation.Type?.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat))); private static Func IsAttribute(INamedTypeSymbol attributeClass) - => attribute => SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, attributeClass); + => attribute => SymbolEquals(attribute.AttributeClass, attributeClass); } diff --git a/Funcky.Analyzers/Funcky.Analyzers/OperationMatching.cs b/Funcky.Analyzers/Funcky.Analyzers/OperationMatching.cs index 7d02bbd5..23fab5f8 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/OperationMatching.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/OperationMatching.cs @@ -10,8 +10,7 @@ public static bool MatchMethod( IInvocationOperation operation, INamedTypeSymbol type, string methodName) - => SymbolEqualityComparer.Default.Equals(operation.TargetMethod.ContainingType, type) - && operation.TargetMethod.Name == methodName; + => SymbolEquals(operation.TargetMethod.ContainingType, type) && operation.TargetMethod.Name == methodName; public static bool MatchArguments( IInvocationOperation operation, @@ -33,8 +32,7 @@ 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; diff --git a/Funcky.Analyzers/Funcky.Analyzers/SymbolEqualityFunctions.cs b/Funcky.Analyzers/Funcky.Analyzers/SymbolEqualityFunctions.cs new file mode 100644 index 00000000..2070809e --- /dev/null +++ b/Funcky.Analyzers/Funcky.Analyzers/SymbolEqualityFunctions.cs @@ -0,0 +1,12 @@ +using Microsoft.CodeAnalysis; + +namespace Funcky.Analyzers; + +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/SyntaxNodeExtensions.ExpressionTree.cs b/Funcky.Analyzers/Funcky.Analyzers/SyntaxNodeExtensions.ExpressionTree.cs index 10843c70..160870c8 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/SyntaxNodeExtensions.ExpressionTree.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/SyntaxNodeExtensions.ExpressionTree.cs @@ -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/UseWithArgumentNamesAnalyzer.cs b/Funcky.Analyzers/Funcky.Analyzers/UseWithArgumentNamesAnalyzer.cs index 6228d496..a53e9107 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/UseWithArgumentNamesAnalyzer.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/UseWithArgumentNamesAnalyzer.cs @@ -45,7 +45,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.GetAttributes().Any(attribute => SymbolEquals(attribute.AttributeClass, attributeSymbol)) && !invocation.Syntax.IsInExpressionTree(semanticModel, expressionOfTType, context.CancellationToken)) { foreach (var argument in invocation.Arguments) From 5bd457f53816d69240f54a704ae7ac8ad39fe5af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tau=20G=C3=A4rtli?= Date: Wed, 5 Feb 2025 16:34:55 +0100 Subject: [PATCH 02/10] Simplify attribute checking --- .../FunctionalAssert/FunctionalAssertMatching.cs | 6 +----- .../Funcky.Analyzers/NonDefaultableAnalyzer.cs | 7 ++----- Funcky.Analyzers/Funcky.Analyzers/SymbolExtensions.cs | 9 +++++++++ .../Funcky.Analyzers/UseWithArgumentNamesAnalyzer.cs | 2 +- 4 files changed, 13 insertions(+), 11 deletions(-) create mode 100644 Funcky.Analyzers/Funcky.Analyzers/SymbolExtensions.cs diff --git a/Funcky.Analyzers/Funcky.Analyzers/FunctionalAssert/FunctionalAssertMatching.cs b/Funcky.Analyzers/Funcky.Analyzers/FunctionalAssert/FunctionalAssertMatching.cs index 188737ad..b97765d9 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/FunctionalAssert/FunctionalAssertMatching.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/FunctionalAssert/FunctionalAssertMatching.cs @@ -8,11 +8,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 => SymbolEquals(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/NonDefaultableAnalyzer.cs b/Funcky.Analyzers/Funcky.Analyzers/NonDefaultableAnalyzer.cs index 003b1360..7df145a9 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/NonDefaultableAnalyzer.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/NonDefaultableAnalyzer.cs @@ -30,7 +30,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 +45,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 +66,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 => SymbolEquals(attribute.AttributeClass, attributeClass); } diff --git a/Funcky.Analyzers/Funcky.Analyzers/SymbolExtensions.cs b/Funcky.Analyzers/Funcky.Analyzers/SymbolExtensions.cs new file mode 100644 index 00000000..37486f50 --- /dev/null +++ b/Funcky.Analyzers/Funcky.Analyzers/SymbolExtensions.cs @@ -0,0 +1,9 @@ +using Microsoft.CodeAnalysis; + +namespace Funcky.Analyzers; + +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/UseWithArgumentNamesAnalyzer.cs b/Funcky.Analyzers/Funcky.Analyzers/UseWithArgumentNamesAnalyzer.cs index a53e9107..526dc11e 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/UseWithArgumentNamesAnalyzer.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/UseWithArgumentNamesAnalyzer.cs @@ -45,7 +45,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 => SymbolEquals(attribute.AttributeClass, attributeSymbol)) + if (invocation.TargetMethod.HasAttribute(attributeSymbol) && !invocation.Syntax.IsInExpressionTree(semanticModel, expressionOfTType, context.CancellationToken)) { foreach (var argument in invocation.Arguments) From 9ab13c3f910f36c5780e6dd8fa88f6dce135a8a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tau=20G=C3=A4rtli?= Date: Wed, 5 Feb 2025 16:38:01 +0100 Subject: [PATCH 03/10] Group non defaultable analyzer --- Funcky.Analyzers/Funcky.Analyzers.Test/NonDefaultableTest.cs | 2 +- .../{ => NonDefaultable}/NonDefaultableAnalyzer.cs | 2 +- .../{ => NonDefaultable}/NonDefaultableStyleCopSuppressor.cs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) rename Funcky.Analyzers/Funcky.Analyzers/{ => NonDefaultable}/NonDefaultableAnalyzer.cs (98%) rename Funcky.Analyzers/Funcky.Analyzers/{ => NonDefaultable}/NonDefaultableStyleCopSuppressor.cs (94%) 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/NonDefaultableAnalyzer.cs b/Funcky.Analyzers/Funcky.Analyzers/NonDefaultable/NonDefaultableAnalyzer.cs similarity index 98% rename from Funcky.Analyzers/Funcky.Analyzers/NonDefaultableAnalyzer.cs rename to Funcky.Analyzers/Funcky.Analyzers/NonDefaultable/NonDefaultableAnalyzer.cs index 7df145a9..e8a882e8 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/NonDefaultableAnalyzer.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/NonDefaultable/NonDefaultableAnalyzer.cs @@ -3,7 +3,7 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; -namespace Funcky.Analyzers; +namespace Funcky.Analyzers.NonDefaultable; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class NonDefaultableAnalyzer : DiagnosticAnalyzer 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 From 55d4293a81655da315613ab1997d8ed2c0262085 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tau=20G=C3=A4rtli?= Date: Thu, 6 Feb 2025 09:28:35 +0100 Subject: [PATCH 04/10] Group classes related to alternative monad analyzer --- .../AlternativeMonad/MatchToNullableCodeFix.cs | 2 +- .../AlternativeMonad/MatchToOrElseCodeFix.cs | 2 +- .../AlternativeMonadAnalyzerTest.Stubs.cs | 2 +- .../AlternativeMonadAnalyzerTest.ToNullable.cs | 6 +++--- .../{ => AlternativeMonad}/AlternativeMonadAnalyzerTest.cs | 6 +++--- .../AlternativeMonadAnalyzer.GetOrElse.cs | 2 +- .../AlternativeMonadAnalyzer.OrElse.cs | 4 ++-- .../AlternativeMonadAnalyzer.SelectMany.cs | 4 ++-- .../AlternativeMonadAnalyzer.ToNullable.cs | 2 +- .../AlternativeMonadAnalyzer.cs | 2 +- .../AlternativeMonadErrorStateConstructorMatching.cs | 2 +- .../AlternativeMonadType.cs | 2 +- .../AlternativeMonadTypeCollection.cs | 2 +- .../{ => AlternativeMonad}/MonadReturnMatching.cs | 2 +- .../Funcky.Analyzers/Funcky.Analyzers.csproj.DotSettings | 2 -- 15 files changed, 20 insertions(+), 22 deletions(-) rename Funcky.Analyzers/Funcky.Analyzers.Test/{ => AlternativeMonad}/AlternativeMonadAnalyzerTest.Stubs.cs (98%) rename Funcky.Analyzers/Funcky.Analyzers.Test/{ => AlternativeMonad}/AlternativeMonadAnalyzerTest.ToNullable.cs (94%) rename Funcky.Analyzers/Funcky.Analyzers.Test/{ => AlternativeMonad}/AlternativeMonadAnalyzerTest.cs (98%) rename Funcky.Analyzers/Funcky.Analyzers/{AlternativeMonadAnalyzer => AlternativeMonad}/AlternativeMonadAnalyzer.GetOrElse.cs (97%) rename Funcky.Analyzers/Funcky.Analyzers/{AlternativeMonadAnalyzer => AlternativeMonad}/AlternativeMonadAnalyzer.OrElse.cs (90%) rename Funcky.Analyzers/Funcky.Analyzers/{AlternativeMonadAnalyzer => AlternativeMonad}/AlternativeMonadAnalyzer.SelectMany.cs (88%) rename Funcky.Analyzers/Funcky.Analyzers/{AlternativeMonadAnalyzer => AlternativeMonad}/AlternativeMonadAnalyzer.ToNullable.cs (97%) rename Funcky.Analyzers/Funcky.Analyzers/{AlternativeMonadAnalyzer => AlternativeMonad}/AlternativeMonadAnalyzer.cs (99%) rename Funcky.Analyzers/Funcky.Analyzers/{ => AlternativeMonad}/AlternativeMonadErrorStateConstructorMatching.cs (97%) rename Funcky.Analyzers/Funcky.Analyzers/{AlternativeMonadAnalyzer => AlternativeMonad}/AlternativeMonadType.cs (97%) rename Funcky.Analyzers/Funcky.Analyzers/{AlternativeMonadAnalyzer => AlternativeMonad}/AlternativeMonadTypeCollection.cs (98%) rename Funcky.Analyzers/Funcky.Analyzers/{ => AlternativeMonad}/MonadReturnMatching.cs (98%) delete mode 100644 Funcky.Analyzers/Funcky.Analyzers/Funcky.Analyzers.csproj.DotSettings diff --git a/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/AlternativeMonad/MatchToNullableCodeFix.cs b/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/AlternativeMonad/MatchToNullableCodeFix.cs index ef394fa1..a8c879b2 100644 --- a/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/AlternativeMonad/MatchToNullableCodeFix.cs +++ b/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/AlternativeMonad/MatchToNullableCodeFix.cs @@ -5,7 +5,7 @@ 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..6f6a0d32 100644 --- a/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/AlternativeMonad/MatchToOrElseCodeFix.cs +++ b/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/AlternativeMonad/MatchToOrElseCodeFix.cs @@ -5,7 +5,7 @@ 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.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/AlternativeMonadAnalyzer/AlternativeMonadAnalyzer.GetOrElse.cs b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.GetOrElse.cs similarity index 97% rename from Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadAnalyzer/AlternativeMonadAnalyzer.GetOrElse.cs rename to Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.GetOrElse.cs index c712e3b0..49490184 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadAnalyzer/AlternativeMonadAnalyzer.GetOrElse.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.GetOrElse.cs @@ -3,7 +3,7 @@ using static Funcky.Analyzers.FunckyWellKnownMemberNames; using static Funcky.Analyzers.IdentityFunctionMatching; -namespace Funcky.Analyzers; +namespace Funcky.Analyzers.AlternativeMonad; public partial class AlternativeMonadAnalyzer { diff --git a/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadAnalyzer/AlternativeMonadAnalyzer.OrElse.cs b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.OrElse.cs similarity index 90% rename from Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadAnalyzer/AlternativeMonadAnalyzer.OrElse.cs rename to Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.OrElse.cs index 94b958ae..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 { diff --git a/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadAnalyzer/AlternativeMonadAnalyzer.SelectMany.cs b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.SelectMany.cs similarity index 88% rename from Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadAnalyzer/AlternativeMonadAnalyzer.SelectMany.cs rename to Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.SelectMany.cs index ab71a935..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 { diff --git a/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadAnalyzer/AlternativeMonadAnalyzer.ToNullable.cs b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.ToNullable.cs similarity index 97% rename from Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadAnalyzer/AlternativeMonadAnalyzer.ToNullable.cs rename to Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.ToNullable.cs index bed10165..b8785722 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadAnalyzer/AlternativeMonadAnalyzer.ToNullable.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.ToNullable.cs @@ -4,7 +4,7 @@ using static Funcky.Analyzers.FunckyWellKnownMemberNames; using static Funcky.Analyzers.IdentityFunctionMatching; -namespace Funcky.Analyzers; +namespace Funcky.Analyzers.AlternativeMonad; public partial class AlternativeMonadAnalyzer { diff --git a/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadAnalyzer/AlternativeMonadAnalyzer.cs b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.cs similarity index 99% rename from Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadAnalyzer/AlternativeMonadAnalyzer.cs rename to Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.cs index a3d5af13..f371f7ea 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadAnalyzer/AlternativeMonadAnalyzer.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.cs @@ -5,7 +5,7 @@ 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 diff --git a/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadErrorStateConstructorMatching.cs b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadErrorStateConstructorMatching.cs similarity index 97% rename from Funcky.Analyzers/Funcky.Analyzers/AlternativeMonadErrorStateConstructorMatching.cs rename to Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadErrorStateConstructorMatching.cs index a97109d1..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 { 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 98% rename from Funcky.Analyzers/Funcky.Analyzers/MonadReturnMatching.cs rename to Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/MonadReturnMatching.cs index b4fd6669..2227b14a 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/MonadReturnMatching.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/MonadReturnMatching.cs @@ -3,7 +3,7 @@ using static Funcky.Analyzers.AnonymousFunctionMatching; using static Funcky.Analyzers.FunckyWellKnownMemberNames; -namespace Funcky.Analyzers; +namespace Funcky.Analyzers.AlternativeMonad; internal static class MonadReturnMatching { 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 From 17e1209bf42c0017d7c8caa5de8483ab15980173 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tau=20G=C3=A4rtli?= Date: Thu, 6 Feb 2025 09:37:00 +0100 Subject: [PATCH 05/10] Group function matchers --- .../AlternativeMonad/AlternativeMonadAnalyzer.GetOrElse.cs | 2 +- .../AlternativeMonad/AlternativeMonadAnalyzer.ToNullable.cs | 4 ++-- .../Funcky.Analyzers/AlternativeMonad/MonadReturnMatching.cs | 2 +- .../{ => Functions}/AnonymousFunctionMatching.cs | 2 +- .../{ => Functions}/ConstantFunctionMatching.cs | 4 ++-- .../{ => Functions}/IdentityFunctionMatching.cs | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) rename Funcky.Analyzers/Funcky.Analyzers/{ => Functions}/AnonymousFunctionMatching.cs (96%) rename Funcky.Analyzers/Funcky.Analyzers/{ => Functions}/ConstantFunctionMatching.cs (89%) rename Funcky.Analyzers/Funcky.Analyzers/{ => Functions}/IdentityFunctionMatching.cs (94%) diff --git a/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.GetOrElse.cs b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.GetOrElse.cs index 49490184..535a9471 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.GetOrElse.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.GetOrElse.cs @@ -1,7 +1,7 @@ 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.AlternativeMonad; diff --git a/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.ToNullable.cs b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.ToNullable.cs index b8785722..6edef3c8 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.ToNullable.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.ToNullable.cs @@ -1,8 +1,8 @@ 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.AlternativeMonad; diff --git a/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/MonadReturnMatching.cs b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/MonadReturnMatching.cs index 2227b14a..be421c09 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/MonadReturnMatching.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/MonadReturnMatching.cs @@ -1,7 +1,7 @@ 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.AlternativeMonad; diff --git a/Funcky.Analyzers/Funcky.Analyzers/AnonymousFunctionMatching.cs b/Funcky.Analyzers/Funcky.Analyzers/Functions/AnonymousFunctionMatching.cs similarity index 96% rename from Funcky.Analyzers/Funcky.Analyzers/AnonymousFunctionMatching.cs rename to Funcky.Analyzers/Funcky.Analyzers/Functions/AnonymousFunctionMatching.cs index 6c1531b8..584cdff1 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/AnonymousFunctionMatching.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/Functions/AnonymousFunctionMatching.cs @@ -1,7 +1,7 @@ using System.Diagnostics.CodeAnalysis; using Microsoft.CodeAnalysis.Operations; -namespace Funcky.Analyzers; +namespace Funcky.Analyzers.Functions; internal static class AnonymousFunctionMatching { diff --git a/Funcky.Analyzers/Funcky.Analyzers/ConstantFunctionMatching.cs b/Funcky.Analyzers/Funcky.Analyzers/Functions/ConstantFunctionMatching.cs similarity index 89% rename from Funcky.Analyzers/Funcky.Analyzers/ConstantFunctionMatching.cs rename to Funcky.Analyzers/Funcky.Analyzers/Functions/ConstantFunctionMatching.cs index 5e7a8c0b..405c58e3 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 { diff --git a/Funcky.Analyzers/Funcky.Analyzers/IdentityFunctionMatching.cs b/Funcky.Analyzers/Funcky.Analyzers/Functions/IdentityFunctionMatching.cs similarity index 94% rename from Funcky.Analyzers/Funcky.Analyzers/IdentityFunctionMatching.cs rename to Funcky.Analyzers/Funcky.Analyzers/Functions/IdentityFunctionMatching.cs index e3da194e..fd78787e 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 { From 9862dcf0acce044c63f32794fc58aef87dbb2e1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tau=20G=C3=A4rtli?= Date: Thu, 6 Feb 2025 09:48:17 +0100 Subject: [PATCH 06/10] Group code analysis extensions --- .../MatchToNullableCodeFix.cs | 1 + .../AlternativeMonad/MatchToOrElseCodeFix.cs | 1 + .../DiagnosticExtensions.cs | 2 +- .../SyntaxGeneratorExtensions.cs | 2 +- ...SyntaxNodeExtensions.ParameterReference.cs | 25 +++++++++++++++++++ .../SyntaxNodeExtensions.cs | 2 +- .../EnumerableRepeatNeverCodeFix.cs | 1 + .../EnumerableRepeatOnceCodeFix.cs | 1 + .../Funcky.Analyzers.CodeFixes.targets | 4 +-- .../FunctionalAssertFix.cs | 1 + ...OptionSomeWhereToFromBooleanRefactoring.cs | 1 + .../ReplaceParameterReferenceRewriter.cs | 25 ------------------- .../AlternativeMonadAnalyzer.ToNullable.cs | 1 + .../AlternativeMonadAnalyzer.cs | 1 + .../ArgumentOperationExtensions.cs | 2 +- .../SemanticModelExtensions.cs | 2 +- .../SymbolEqualityFunctions.cs | 2 +- .../SymbolExtensions.cs | 2 +- .../SyntaxNodeExtensions.ExpressionTree.cs | 2 +- .../SyntaxNodeExtensions.cs | 6 ++--- .../Funcky.Analyzers/Funcky.Analyzers.targets | 2 +- .../FunctionalAssertMatching.cs | 1 + .../FunctionalAssert/XunitAssertMatching.cs | 1 + .../NonDefaultable/NonDefaultableAnalyzer.cs | 1 + .../Funcky.Analyzers/OperationMatching.cs | 1 + .../UseWithArgumentNamesAnalyzer.cs | 1 + 26 files changed, 52 insertions(+), 39 deletions(-) rename Funcky.Analyzers/Funcky.Analyzers.CodeFixes/{ => CodeAnalysisExtensions}/DiagnosticExtensions.cs (93%) rename Funcky.Analyzers/Funcky.Analyzers.CodeFixes/{ => CodeAnalysisExtensions}/SyntaxGeneratorExtensions.cs (96%) create mode 100644 Funcky.Analyzers/Funcky.Analyzers.CodeFixes/CodeAnalysisExtensions/SyntaxNodeExtensions.ParameterReference.cs rename Funcky.Analyzers/Funcky.Analyzers.CodeFixes/{ => CodeAnalysisExtensions}/SyntaxNodeExtensions.cs (91%) delete mode 100644 Funcky.Analyzers/Funcky.Analyzers.CodeFixes/ReplaceParameterReferenceRewriter.cs rename Funcky.Analyzers/Funcky.Analyzers/{ => CodeAnalysisExtensions}/ArgumentOperationExtensions.cs (91%) rename Funcky.Analyzers/Funcky.Analyzers/{ => CodeAnalysisExtensions}/SemanticModelExtensions.cs (85%) rename Funcky.Analyzers/Funcky.Analyzers/{ => CodeAnalysisExtensions}/SymbolEqualityFunctions.cs (87%) rename Funcky.Analyzers/Funcky.Analyzers/{ => CodeAnalysisExtensions}/SymbolExtensions.cs (84%) rename Funcky.Analyzers/Funcky.Analyzers/{ => CodeAnalysisExtensions}/SyntaxNodeExtensions.ExpressionTree.cs (98%) rename Funcky.Analyzers/Funcky.Analyzers/{ => CodeAnalysisExtensions}/SyntaxNodeExtensions.cs (75%) diff --git a/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/AlternativeMonad/MatchToNullableCodeFix.cs b/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/AlternativeMonad/MatchToNullableCodeFix.cs index a8c879b2..d1764d33 100644 --- a/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/AlternativeMonad/MatchToNullableCodeFix.cs +++ b/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/AlternativeMonad/MatchToNullableCodeFix.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/AlternativeMonad/MatchToOrElseCodeFix.cs b/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/AlternativeMonad/MatchToOrElseCodeFix.cs index 6f6a0d32..99274ce8 100644 --- a/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/AlternativeMonad/MatchToOrElseCodeFix.cs +++ b/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/AlternativeMonad/MatchToOrElseCodeFix.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/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 33ec1a3a..2ce99827 100644 --- a/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/Funcky.Analyzers.CodeFixes.targets +++ b/Funcky.Analyzers/Funcky.Analyzers.CodeFixes/Funcky.Analyzers.CodeFixes.targets @@ -17,8 +17,8 @@ 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 8d161a6d..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; 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/AlternativeMonad/AlternativeMonadAnalyzer.ToNullable.cs b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.ToNullable.cs index 6edef3c8..2d00171b 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.ToNullable.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.ToNullable.cs @@ -1,3 +1,4 @@ +using Funcky.Analyzers.CodeAnalysisExtensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Operations; using static Funcky.Analyzers.FunckyWellKnownMemberNames; diff --git a/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.cs b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.cs index f371f7ea..7eac3cf2 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.cs @@ -1,5 +1,6 @@ using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; +using Funcky.Analyzers.CodeAnalysisExtensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; diff --git a/Funcky.Analyzers/Funcky.Analyzers/ArgumentOperationExtensions.cs b/Funcky.Analyzers/Funcky.Analyzers/CodeAnalysisExtensions/ArgumentOperationExtensions.cs similarity index 91% rename from Funcky.Analyzers/Funcky.Analyzers/ArgumentOperationExtensions.cs rename to Funcky.Analyzers/Funcky.Analyzers/CodeAnalysisExtensions/ArgumentOperationExtensions.cs index 819e203f..5a66c010 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/ArgumentOperationExtensions.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/CodeAnalysisExtensions/ArgumentOperationExtensions.cs @@ -1,6 +1,6 @@ using Microsoft.CodeAnalysis.Operations; -namespace Funcky.Analyzers; +namespace Funcky.Analyzers.CodeAnalysisExtensions; internal static class ArgumentOperationExtensions { 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/SymbolEqualityFunctions.cs b/Funcky.Analyzers/Funcky.Analyzers/CodeAnalysisExtensions/SymbolEqualityFunctions.cs similarity index 87% rename from Funcky.Analyzers/Funcky.Analyzers/SymbolEqualityFunctions.cs rename to Funcky.Analyzers/Funcky.Analyzers/CodeAnalysisExtensions/SymbolEqualityFunctions.cs index 2070809e..b9253ba6 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/SymbolEqualityFunctions.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/CodeAnalysisExtensions/SymbolEqualityFunctions.cs @@ -1,6 +1,6 @@ using Microsoft.CodeAnalysis; -namespace Funcky.Analyzers; +namespace Funcky.Analyzers.CodeAnalysisExtensions; internal static class SymbolEqualityFunctions { diff --git a/Funcky.Analyzers/Funcky.Analyzers/SymbolExtensions.cs b/Funcky.Analyzers/Funcky.Analyzers/CodeAnalysisExtensions/SymbolExtensions.cs similarity index 84% rename from Funcky.Analyzers/Funcky.Analyzers/SymbolExtensions.cs rename to Funcky.Analyzers/Funcky.Analyzers/CodeAnalysisExtensions/SymbolExtensions.cs index 37486f50..49e9c7db 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/SymbolExtensions.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/CodeAnalysisExtensions/SymbolExtensions.cs @@ -1,6 +1,6 @@ using Microsoft.CodeAnalysis; -namespace Funcky.Analyzers; +namespace Funcky.Analyzers.CodeAnalysisExtensions; internal static class SymbolExtensions { diff --git a/Funcky.Analyzers/Funcky.Analyzers/SyntaxNodeExtensions.ExpressionTree.cs b/Funcky.Analyzers/Funcky.Analyzers/CodeAnalysisExtensions/SyntaxNodeExtensions.ExpressionTree.cs similarity index 98% rename from Funcky.Analyzers/Funcky.Analyzers/SyntaxNodeExtensions.ExpressionTree.cs rename to Funcky.Analyzers/Funcky.Analyzers/CodeAnalysisExtensions/SyntaxNodeExtensions.ExpressionTree.cs index 160870c8..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 { 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/Funcky.Analyzers.targets b/Funcky.Analyzers/Funcky.Analyzers/Funcky.Analyzers.targets index bd760d0f..b1bea80b 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/Funcky.Analyzers.targets +++ b/Funcky.Analyzers/Funcky.Analyzers/Funcky.Analyzers.targets @@ -26,7 +26,7 @@ - + diff --git a/Funcky.Analyzers/Funcky.Analyzers/FunctionalAssert/FunctionalAssertMatching.cs b/Funcky.Analyzers/Funcky.Analyzers/FunctionalAssert/FunctionalAssertMatching.cs index b97765d9..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; diff --git a/Funcky.Analyzers/Funcky.Analyzers/FunctionalAssert/XunitAssertMatching.cs b/Funcky.Analyzers/Funcky.Analyzers/FunctionalAssert/XunitAssertMatching.cs index 9fadd5e3..14c13f1e 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/FunctionalAssert/XunitAssertMatching.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/FunctionalAssert/XunitAssertMatching.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using Funcky.Analyzers.CodeAnalysisExtensions; using Microsoft.CodeAnalysis.Operations; using static Funcky.Analyzers.FunckyWellKnownMemberNames; diff --git a/Funcky.Analyzers/Funcky.Analyzers/NonDefaultable/NonDefaultableAnalyzer.cs b/Funcky.Analyzers/Funcky.Analyzers/NonDefaultable/NonDefaultableAnalyzer.cs index e8a882e8..15f16eb3 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/NonDefaultable/NonDefaultableAnalyzer.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/NonDefaultable/NonDefaultableAnalyzer.cs @@ -1,4 +1,5 @@ using System.Collections.Immutable; +using Funcky.Analyzers.CodeAnalysisExtensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; diff --git a/Funcky.Analyzers/Funcky.Analyzers/OperationMatching.cs b/Funcky.Analyzers/Funcky.Analyzers/OperationMatching.cs index 23fab5f8..cb114b91 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/OperationMatching.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/OperationMatching.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using Funcky.Analyzers.CodeAnalysisExtensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Operations; diff --git a/Funcky.Analyzers/Funcky.Analyzers/UseWithArgumentNamesAnalyzer.cs b/Funcky.Analyzers/Funcky.Analyzers/UseWithArgumentNamesAnalyzer.cs index 526dc11e..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; From 8591f6b0c1f2d4313dc8ad5f771808da6510bce1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tau=20G=C3=A4rtli?= Date: Thu, 6 Feb 2025 10:49:57 +0100 Subject: [PATCH 07/10] Use list patterns for argument matching --- .../AlternativeMonadAnalyzer.cs | 21 +++++++++------- .../ArgumentOperationExtensions.cs | 23 ++++++++++++++--- .../EnumerableRepeatNeverAnalyzer.cs | 5 +++- .../EnumerableRepeatOnceAnalyzer.cs | 5 +++- .../FunctionalAssert/XunitAssertMatching.cs | 12 ++++----- .../JoinToStringEmptyAnalyzer.cs | 11 +++++--- .../Funcky.Analyzers/OperationMatching.cs | 25 +++---------------- 7 files changed, 54 insertions(+), 48 deletions(-) diff --git a/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.cs b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.cs index 7eac3cf2..562ff40a 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.cs @@ -35,13 +35,8 @@ 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 (IsMatchInvocation(operation, alternativeMonadTypes, out var receiverType, out var alternativeMonad, out var errorStateArgument, out var successStateArgument) + && AnalyzeMatchInvocation(operation, alternativeMonad, receiverType, errorStateArgument, successStateArgument) is { } diagnostic) { context.ReportDiagnostic(diagnostic); } @@ -51,12 +46,20 @@ private static bool IsMatchInvocation( IInvocationOperation invocation, AlternativeMonadTypeCollection alternativeMonadTypes, [NotNullWhen(true)] out INamedTypeSymbol? matchReceiverType, - [NotNullWhen(true)] out AlternativeMonadType? alternativeMonadType) + [NotNullWhen(true)] out AlternativeMonadType? alternativeMonadType, + [NotNullWhen(true)] out IArgumentOperation? errorStateArgument, + [NotNullWhen(true)] out IArgumentOperation? successStateArgument) { matchReceiverType = null; alternativeMonadType = null; - return invocation is { TargetMethod: { ReceiverType: INamedTypeSymbol receiverType, Name: MatchMethodName }, Arguments: [_, _] } + errorStateArgument = null; + successStateArgument = null; + var arguments = invocation.GetArgumentsInParameterOrder(); + return invocation is { TargetMethod: { ReceiverType: INamedTypeSymbol receiverType, Name: MatchMethodName } } && alternativeMonadTypes.Value.TryGetValue(receiverType.ConstructedFrom, out alternativeMonadType) + && arguments.Length == 2 + && (errorStateArgument = arguments[alternativeMonadType.ErrorStateArgumentIndex]) is var _ + && (successStateArgument = arguments[alternativeMonadType.SuccessStateArgumentIndex]) is var _ && (matchReceiverType = receiverType) is var _; } diff --git a/Funcky.Analyzers/Funcky.Analyzers/CodeAnalysisExtensions/ArgumentOperationExtensions.cs b/Funcky.Analyzers/Funcky.Analyzers/CodeAnalysisExtensions/ArgumentOperationExtensions.cs index 5a66c010..7e58ebc9 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/CodeAnalysisExtensions/ArgumentOperationExtensions.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/CodeAnalysisExtensions/ArgumentOperationExtensions.cs @@ -1,12 +1,27 @@ +using System.Collections.Immutable; using Microsoft.CodeAnalysis.Operations; namespace Funcky.Analyzers.CodeAnalysisExtensions; internal static class ArgumentOperationExtensions { - public static IArgumentOperation GetArgumentForParameterAtIndex(this IInvocationOperation invocationOperation, int parameterIndex) - => GetArgumentForParameterAtIndex(invocationOperation.Arguments, parameterIndex); + /// 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 IArgumentOperation GetArgumentForParameterAtIndex(this IEnumerable arguments, int parameterIndex) - => arguments.Single(argument => argument.Parameter?.Ordinal == parameterIndex); + 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/EnumerableRepeatNeverAnalyzer.cs b/Funcky.Analyzers/Funcky.Analyzers/EnumerableRepeatNeverAnalyzer.cs index 442a05e8..8682fd8e 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/EnumerableRepeatNeverAnalyzer.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/EnumerableRepeatNeverAnalyzer.cs @@ -1,5 +1,6 @@ using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; +using Funcky.Analyzers.CodeAnalysisExtensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; @@ -57,7 +58,9 @@ private static bool MatchRepeatNever( { valueArgument = null; return MatchMethod(operation, enumerableType, nameof(Enumerable.Repeat)) - && MatchArguments(operation, out valueArgument, AnyArgument, out _, ConstantArgument(0)); + && operation.GetArgumentsInParameterOrder() is [var valueArgument_, var countArgument] + && MatchConstantArgument(countArgument, 0) + && (valueArgument = valueArgument_) is var _; } private static Diagnostic CreateDiagnostic(IInvocationOperation operation, IArgumentOperation valueArgument) diff --git a/Funcky.Analyzers/Funcky.Analyzers/EnumerableRepeatOnceAnalyzer.cs b/Funcky.Analyzers/Funcky.Analyzers/EnumerableRepeatOnceAnalyzer.cs index 49a86ab5..f2da5179 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/EnumerableRepeatOnceAnalyzer.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/EnumerableRepeatOnceAnalyzer.cs @@ -1,5 +1,6 @@ using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; +using Funcky.Analyzers.CodeAnalysisExtensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; @@ -57,7 +58,9 @@ private static bool MatchRepeatOnce( { valueArgument = null; return MatchMethod(operation, enumerableType, nameof(Enumerable.Repeat)) - && MatchArguments(operation, out valueArgument, AnyArgument, out _, ConstantArgument(1)); + && operation.GetArgumentsInParameterOrder() is [var valueArgument_, var countArgument] + && MatchConstantArgument(countArgument, 1) + && (valueArgument = valueArgument_) is var _; } private static Diagnostic CreateDiagnostic(IInvocationOperation operation, IArgumentOperation valueArgument) diff --git a/Funcky.Analyzers/Funcky.Analyzers/FunctionalAssert/XunitAssertMatching.cs b/Funcky.Analyzers/Funcky.Analyzers/FunctionalAssert/XunitAssertMatching.cs index 14c13f1e..5253b4f3 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/FunctionalAssert/XunitAssertMatching.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/FunctionalAssert/XunitAssertMatching.cs @@ -12,18 +12,16 @@ public static bool MatchGenericAssertEqualInvocation( [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 && SymbolEquals(invocation.TargetMethod.ContainingType, assertType) && invocation.TargetMethod.TypeParameters is [var typeParameter] - && invocation.TargetMethod.OriginalDefinition.Parameters is [var firstParameter, var secondParameter] - && SymbolEquals(firstParameter.Type, typeParameter) - && SymbolEquals(secondParameter.Type, typeParameter) - && (expectedArgument = invocation.GetArgumentForParameterAtIndex(expectedParameterIndex)) is var _ - && (actualArgument = invocation.GetArgumentForParameterAtIndex(actualParameterIndex)) is var _; + && invocation.GetArgumentsInParameterOrder() is [var expectedArgument_, var actualArgument_] + && SymbolEquals(expectedArgument_.Parameter?.OriginalDefinition.Type, typeParameter) + && SymbolEquals(actualArgument_.Parameter?.OriginalDefinition.Type, typeParameter) + && (expectedArgument = expectedArgument_) is var _ + && (actualArgument = actualArgument_) is var _; } } diff --git a/Funcky.Analyzers/Funcky.Analyzers/JoinToStringEmptyAnalyzer.cs b/Funcky.Analyzers/Funcky.Analyzers/JoinToStringEmptyAnalyzer.cs index 52a9ee57..b57e4059 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/JoinToStringEmptyAnalyzer.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/JoinToStringEmptyAnalyzer.cs @@ -1,5 +1,6 @@ using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; +using Funcky.Analyzers.CodeAnalysisExtensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; @@ -58,12 +59,14 @@ private static bool MatchJoinToStringEmpty(Symbols symbols, IInvocationOperation valueArgument = null; stringArgument = null; return MatchMethod(operation, symbols.EnumerableType, JoinToString) - && MatchArguments(operation, out valueArgument, AnyArgument, out stringArgument, IsEmptyString(symbols.StringType)); + && operation.GetArgumentsInParameterOrder() is [var valueArgument_, var stringArgument_] + && IsEmptyString(stringArgument_, symbols.StringType) + && (stringArgument = stringArgument_) is var _ + && (valueArgument = valueArgument_) is var _; } - 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/OperationMatching.cs b/Funcky.Analyzers/Funcky.Analyzers/OperationMatching.cs index cb114b91..3e012524 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/OperationMatching.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/OperationMatching.cs @@ -1,5 +1,3 @@ -using System.Diagnostics.CodeAnalysis; -using Funcky.Analyzers.CodeAnalysisExtensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Operations; @@ -13,32 +11,15 @@ public static bool MatchMethod( string methodName) => SymbolEquals(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); - } - public static bool MatchField( IFieldReferenceOperation operation, INamedTypeSymbol type, string 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); } From 71c19ad390cd84d13cb40cd40e342702c9f4e02f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tau=20G=C3=A4rtli?= Date: Thu, 6 Feb 2025 12:00:33 +0100 Subject: [PATCH 08/10] Simplify matchers by using an Option --- .../AlternativeMonadAnalyzer.cs | 72 +++++++++---------- .../AlternativeMonad/MonadReturnMatching.cs | 4 +- .../EnumerableRepeatNeverAnalyzer.cs | 17 ++--- .../EnumerableRepeatOnceAnalyzer.cs | 17 ++--- .../FunctionalAssertAnalyzer.cs | 2 +- .../FunctionalAssert/XunitAssertMatching.cs | 28 +++----- .../Functions/AnonymousFunctionMatching.cs | 22 +++--- .../Functions/ConstantFunctionMatching.cs | 2 +- .../Functions/IdentityFunctionMatching.cs | 4 +- .../JoinToStringEmptyAnalyzer.cs | 18 ++--- .../Option/Option.ListPattern.cs | 24 +++++++ .../Funcky.Analyzers/Option/Option.cs | 50 +++++++++++++ 12 files changed, 147 insertions(+), 113 deletions(-) create mode 100644 Funcky.Analyzers/Funcky.Analyzers/Option/Option.ListPattern.cs create mode 100644 Funcky.Analyzers/Funcky.Analyzers/Option/Option.cs diff --git a/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.cs b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.cs index 562ff40a..1770456b 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/AlternativeMonadAnalyzer.cs @@ -1,5 +1,4 @@ using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; using Funcky.Analyzers.CodeAnalysisExtensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; @@ -35,73 +34,68 @@ private static void AnalyzeInvocation(OperationAnalysisContext context, Alternat { var operation = (IInvocationOperation)context.Operation; - if (IsMatchInvocation(operation, alternativeMonadTypes, out var receiverType, out var alternativeMonad, out var errorStateArgument, out var successStateArgument) - && AnalyzeMatchInvocation(operation, alternativeMonad, receiverType, errorStateArgument, successStateArgument) 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, - [NotNullWhen(true)] out IArgumentOperation? errorStateArgument, - [NotNullWhen(true)] out IArgumentOperation? successStateArgument) - { - matchReceiverType = null; - alternativeMonadType = null; - errorStateArgument = null; - successStateArgument = null; - var arguments = invocation.GetArgumentsInParameterOrder(); - return invocation is { TargetMethod: { ReceiverType: INamedTypeSymbol receiverType, Name: MatchMethodName } } - && alternativeMonadTypes.Value.TryGetValue(receiverType.ConstructedFrom, out alternativeMonadType) - && arguments.Length == 2 - && (errorStateArgument = arguments[alternativeMonadType.ErrorStateArgumentIndex]) is var _ - && (successStateArgument = arguments[alternativeMonadType.SuccessStateArgumentIndex]) is var _ - && (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/AlternativeMonad/MonadReturnMatching.cs b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/MonadReturnMatching.cs index be421c09..7076458d 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/MonadReturnMatching.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/AlternativeMonad/MonadReturnMatching.cs @@ -23,13 +23,13 @@ private static bool IsReturn(AlternativeMonadType alternativeMonadType, IMethodS || 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) && 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 } } && SymbolEquals(conversionType?.OriginalDefinition, alternativeMonadType.Type); } diff --git a/Funcky.Analyzers/Funcky.Analyzers/EnumerableRepeatNeverAnalyzer.cs b/Funcky.Analyzers/Funcky.Analyzers/EnumerableRepeatNeverAnalyzer.cs index 8682fd8e..fbfd5f44 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/EnumerableRepeatNeverAnalyzer.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/EnumerableRepeatNeverAnalyzer.cs @@ -1,5 +1,4 @@ using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; using Funcky.Analyzers.CodeAnalysisExtensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; @@ -45,23 +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)) - && operation.GetArgumentsInParameterOrder() is [var valueArgument_, var countArgument] + 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 = valueArgument_) is var _; - } + ? [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 f2da5179..ecde62be 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/EnumerableRepeatOnceAnalyzer.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/EnumerableRepeatOnceAnalyzer.cs @@ -1,5 +1,4 @@ using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; using Funcky.Analyzers.CodeAnalysisExtensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; @@ -45,23 +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)) - && operation.GetArgumentsInParameterOrder() is [var valueArgument_, var countArgument] + 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 = valueArgument_) is var _; - } + ? [valueArgument] : []; private static Diagnostic CreateDiagnostic(IInvocationOperation operation, IArgumentOperation valueArgument) => Diagnostic.Create( 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/XunitAssertMatching.cs b/Funcky.Analyzers/Funcky.Analyzers/FunctionalAssert/XunitAssertMatching.cs index 5253b4f3..2490dd7d 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/FunctionalAssert/XunitAssertMatching.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/FunctionalAssert/XunitAssertMatching.cs @@ -1,4 +1,3 @@ -using System.Diagnostics.CodeAnalysis; using Funcky.Analyzers.CodeAnalysisExtensions; using Microsoft.CodeAnalysis.Operations; using static Funcky.Analyzers.FunckyWellKnownMemberNames; @@ -7,21 +6,14 @@ namespace Funcky.Analyzers.FunctionalAssert; internal sealed class XunitAssertMatching { - public static bool MatchGenericAssertEqualInvocation( - IInvocationOperation invocation, - [NotNullWhen(true)] out IArgumentOperation? expectedArgument, - [NotNullWhen(true)] out IArgumentOperation? actualArgument) - { - expectedArgument = null; - actualArgument = null; - return invocation.TargetMethod.Name == XunitAssert.EqualMethodName - && invocation.SemanticModel?.Compilation.GetXunitAssertType() is { } assertType - && SymbolEquals(invocation.TargetMethod.ContainingType, assertType) - && 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 = expectedArgument_) is var _ - && (actualArgument = actualArgument_) is var _; - } + public static Option<(IArgumentOperation Expected, IArgumentOperation Actual)> MatchGenericAssertEqualInvocation( + IInvocationOperation invocation) + => invocation.TargetMethod.Name == XunitAssert.EqualMethodName + && invocation.SemanticModel?.Compilation.GetXunitAssertType() is { } assertType + && SymbolEquals(invocation.TargetMethod.ContainingType, assertType) + && 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 index 584cdff1..c3e9ae9a 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/Functions/AnonymousFunctionMatching.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/Functions/AnonymousFunctionMatching.cs @@ -1,4 +1,3 @@ -using System.Diagnostics.CodeAnalysis; using Microsoft.CodeAnalysis.Operations; namespace Funcky.Analyzers.Functions; @@ -6,19 +5,14 @@ namespace Funcky.Analyzers.Functions; 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 [_]; + 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 bool MatchAnonymousFunctionWithSingleReturn( - IAnonymousFunctionOperation anonymousFunction, - [NotNullWhen(true)] out IReturnOperation? functionReturnOperation) - { - functionReturnOperation = null; - return anonymousFunction.Body.Operations is [IReturnOperation returnOperation] - && (functionReturnOperation = returnOperation) is var _; - } + public static Option MatchAnonymousFunctionWithSingleReturn(IAnonymousFunctionOperation anonymousFunction) + => anonymousFunction.Body.Operations is [IReturnOperation returnOperation] + ? [returnOperation] : []; } diff --git a/Funcky.Analyzers/Funcky.Analyzers/Functions/ConstantFunctionMatching.cs b/Funcky.Analyzers/Funcky.Analyzers/Functions/ConstantFunctionMatching.cs index 405c58e3..d209cc39 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/Functions/ConstantFunctionMatching.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/Functions/ConstantFunctionMatching.cs @@ -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/Functions/IdentityFunctionMatching.cs b/Funcky.Analyzers/Funcky.Analyzers/Functions/IdentityFunctionMatching.cs index fd78787e..161b7797 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/Functions/IdentityFunctionMatching.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/Functions/IdentityFunctionMatching.cs @@ -18,12 +18,12 @@ 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 } } && 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 } && SymbolEquals(parameterContainingSymbol, anonymousFunction.Symbol); diff --git a/Funcky.Analyzers/Funcky.Analyzers/JoinToStringEmptyAnalyzer.cs b/Funcky.Analyzers/Funcky.Analyzers/JoinToStringEmptyAnalyzer.cs index b57e4059..bab8986c 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/JoinToStringEmptyAnalyzer.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/JoinToStringEmptyAnalyzer.cs @@ -1,5 +1,4 @@ using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; using Funcky.Analyzers.CodeAnalysisExtensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; @@ -48,22 +47,17 @@ 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) - && operation.GetArgumentsInParameterOrder() is [var valueArgument_, var stringArgument_] - && IsEmptyString(stringArgument_, symbols.StringType) - && (stringArgument = stringArgument_) is var _ - && (valueArgument = valueArgument_) is var _; - } + 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 bool IsEmptyString(IArgumentOperation argument, INamedTypeSymbol stringType) => IsEmptyStringConstant(argument) || IsStringEmptyField(stringType, argument); 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..9eb9cfbe --- /dev/null +++ b/Funcky.Analyzers/Funcky.Analyzers/Option/Option.cs @@ -0,0 +1,50 @@ +using System.Collections; +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 : IEnumerable + 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; + } + + IEnumerator IEnumerable.GetEnumerator() + { + if (_hasItem) + { + yield return _item; + } + } + + IEnumerator IEnumerable.GetEnumerator() => (this as IEnumerable).GetEnumerator(); +} + +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"), + }; +} From 38da798677eca170cb8844eda297f1cb7bc5763a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tau=20G=C3=A4rtli?= Date: Thu, 6 Feb 2025 12:22:56 +0100 Subject: [PATCH 09/10] Use MatchMethod --- .../Funcky.Analyzers/FunctionalAssert/XunitAssertMatching.cs | 5 ++--- Funcky.Analyzers/Funcky.Analyzers/OperationMatching.cs | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Funcky.Analyzers/Funcky.Analyzers/FunctionalAssert/XunitAssertMatching.cs b/Funcky.Analyzers/Funcky.Analyzers/FunctionalAssert/XunitAssertMatching.cs index 2490dd7d..b38ac85a 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/FunctionalAssert/XunitAssertMatching.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/FunctionalAssert/XunitAssertMatching.cs @@ -1,6 +1,7 @@ using Funcky.Analyzers.CodeAnalysisExtensions; using Microsoft.CodeAnalysis.Operations; using static Funcky.Analyzers.FunckyWellKnownMemberNames; +using static Funcky.Analyzers.OperationMatching; namespace Funcky.Analyzers.FunctionalAssert; @@ -8,9 +9,7 @@ internal sealed class XunitAssertMatching { public static Option<(IArgumentOperation Expected, IArgumentOperation Actual)> MatchGenericAssertEqualInvocation( IInvocationOperation invocation) - => invocation.TargetMethod.Name == XunitAssert.EqualMethodName - && invocation.SemanticModel?.Compilation.GetXunitAssertType() is { } assertType - && SymbolEquals(invocation.TargetMethod.ContainingType, assertType) + => 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) diff --git a/Funcky.Analyzers/Funcky.Analyzers/OperationMatching.cs b/Funcky.Analyzers/Funcky.Analyzers/OperationMatching.cs index 3e012524..db600c89 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/OperationMatching.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/OperationMatching.cs @@ -7,7 +7,7 @@ internal static class OperationMatching { public static bool MatchMethod( IInvocationOperation operation, - INamedTypeSymbol type, + INamedTypeSymbol? type, string methodName) => SymbolEquals(operation.TargetMethod.ContainingType, type) && operation.TargetMethod.Name == methodName; From 26702d54289a80a3fd8b50305d0453699eb6b08a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tau=20G=C3=A4rtli?= Date: Thu, 6 Feb 2025 12:41:53 +0100 Subject: [PATCH 10/10] Use pattern-based iteration --- .../Funcky.Analyzers/Option/Option.cs | 32 +++++++++++++++---- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/Funcky.Analyzers/Funcky.Analyzers/Option/Option.cs b/Funcky.Analyzers/Funcky.Analyzers/Option/Option.cs index 9eb9cfbe..ca79efbc 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/Option/Option.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/Option/Option.cs @@ -1,4 +1,3 @@ -using System.Collections; using System.ComponentModel; using System.Runtime.CompilerServices; @@ -7,7 +6,7 @@ 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 : IEnumerable +internal readonly partial struct Option where TItem : notnull { private readonly bool _hasItem; @@ -25,15 +24,34 @@ internal Option(TItem item) _hasItem = true; } - IEnumerator IEnumerable.GetEnumerator() + // Enumerator is required by collection expression. + [EditorBrowsable(EditorBrowsableState.Never)] + public Enumerator GetEnumerator() => new(this); + + [EditorBrowsable(EditorBrowsableState.Never)] + public struct Enumerator { - if (_hasItem) + private readonly TItem _item; + private bool _hasItem; + + [EditorBrowsable(EditorBrowsableState.Never)] + internal Enumerator(Option option) { - yield return _item; + _item = option._item; + _hasItem = option._hasItem; } - } - IEnumerator IEnumerable.GetEnumerator() => (this as IEnumerable).GetEnumerator(); + [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