diff --git a/src/Immediate.Handlers.Analyzers/AnalyzerReleases.Unshipped.md b/src/Immediate.Handlers.Analyzers/AnalyzerReleases.Unshipped.md index e69de29b..b892e17c 100644 --- a/src/Immediate.Handlers.Analyzers/AnalyzerReleases.Unshipped.md +++ b/src/Immediate.Handlers.Analyzers/AnalyzerReleases.Unshipped.md @@ -0,0 +1,16 @@ +### New Rules + +Rule ID | Category | Severity | Notes +--------|----------|----------|------- +IHR0014 | ImmediateHandler | Error | HandlerClassAnalyzer +IHR0015 | ImmediateHandler | Error | HandlerClassAnalyzer +IHR0016 | ImmediateHandler | Error | HandlerClassAnalyzer +IHR0017 | ImmediateHandler | Error | HandlerClassAnalyzer +IHR0018 | ImmediateHandler | Error | HandlerClassAnalyzer + +### Removed Rules + +Rule ID | Category | Severity | Notes +--------|----------|----------|------- +IHR0009 | ImmediateHandler | Error | HandlerClassAnalyzer + diff --git a/src/Immediate.Handlers.Analyzers/DiagnosticIds.cs b/src/Immediate.Handlers.Analyzers/DiagnosticIds.cs index 8c90af70..0ce9dc38 100644 --- a/src/Immediate.Handlers.Analyzers/DiagnosticIds.cs +++ b/src/Immediate.Handlers.Analyzers/DiagnosticIds.cs @@ -11,9 +11,13 @@ internal static class DiagnosticIds public const string IHR0006BehaviorsMustInheritFromBehavior = "IHR0006"; public const string IHR0007BehaviorsMustHaveTwoGenericParameters = "IHR0007"; public const string IHR0008BehaviorsMustUseUnboundGenerics = "IHR0008"; - public const string IHR0009HandlerMethodMustBeStatic = "IHR0009"; public const string IHR0010HandlerMethodMustBeUnique = "IHR0010"; public const string IHR0011HandlerMethodMustBePrivate = "IHR0011"; public const string IHR0012HandlerShouldUseCancellationToken = "IHR0012"; public const string IHR0013IHandlerMissingImplementation = "IHR0013"; + public const string IHR0014HandlerMethodMissingRequest = "IHR0014"; + public const string IHR0015HandlerMethodHasTooManyParameters = "IHR0015"; + public const string IHR0016ContainingClassMustBeSealed = "IHR0016"; + public const string IHR0017ContainingClassInstanceMembersMustBePrivate = "IHR0017"; + public const string IHR0018ContainingClassMustBeStatic = "IHR0018"; } diff --git a/src/Immediate.Handlers.Analyzers/HandlerClassAnalyzer.cs b/src/Immediate.Handlers.Analyzers/HandlerClassAnalyzer.cs index 6602000e..0139f3b9 100644 --- a/src/Immediate.Handlers.Analyzers/HandlerClassAnalyzer.cs +++ b/src/Immediate.Handlers.Analyzers/HandlerClassAnalyzer.cs @@ -21,8 +21,8 @@ public sealed class HandlerClassAnalyzer : DiagnosticAnalyzer public static readonly DiagnosticDescriptor HandlerMethodMustReturnTask = new( id: DiagnosticIds.IHR0002HandlerMethodMustReturnTask, - title: "Handler method must return a ValueTask", - messageFormat: "Method '{0}' must return a ValueTask", + title: "Handler method must return a ValueTask or ValueTask", + messageFormat: "Method '{0}' must return a ValueTask or ValueTask", category: "ImmediateHandler", defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true, @@ -40,17 +40,6 @@ public sealed class HandlerClassAnalyzer : DiagnosticAnalyzer description: "Handler class must not be nested in another type." ); - public static readonly DiagnosticDescriptor HandlerMethodMustBeStatic = - new( - id: DiagnosticIds.IHR0009HandlerMethodMustBeStatic, - title: "Handler method must be static", - messageFormat: "Method '{0}' must be static", - category: "ImmediateHandler", - defaultSeverity: DiagnosticSeverity.Error, - isEnabledByDefault: true, - description: "Handler method must be static." - ); - public static readonly DiagnosticDescriptor HandlerMethodMustBeUnique = new( id: DiagnosticIds.IHR0010HandlerMethodMustBeUnique, @@ -84,16 +73,75 @@ public sealed class HandlerClassAnalyzer : DiagnosticAnalyzer description: "Handlers should use CancellationToken to properly support cancellation." ); + public static readonly DiagnosticDescriptor HandlerMethodMissingRequest = + new( + id: DiagnosticIds.IHR0014HandlerMethodMissingRequest, + title: "Handler method is missing request parameter", + messageFormat: "Method '{0}' should receive a request parameter", + category: "ImmediateHandler", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: "Handlers must have a request parameter." + ); + + public static readonly DiagnosticDescriptor HandlerMethodHasTooManyParameters = + new( + id: DiagnosticIds.IHR0015HandlerMethodHasTooManyParameters, + title: "Handler method has too many parameters", + messageFormat: "Method '{0}' should have a request parameter and, optionally, a cancellation token parameter only", + category: "ImmediateHandler", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: "Handlers must have only one or two parameters." + ); + + public static readonly DiagnosticDescriptor ContainingClassMustBeSealed = + new( + id: DiagnosticIds.IHR0016ContainingClassMustBeSealed, + title: "Handler type must be `sealed`", + messageFormat: "Class '{0}' must be `sealed`", + category: "ImmediateHandler", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: "Containing classes must be sealed to prevent incorrect usage." + ); + + public static readonly DiagnosticDescriptor ContainingClassInstanceMembersMustBePrivate = + new( + id: DiagnosticIds.IHR0017ContainingClassInstanceMembersMustBePrivate, + title: "Instances members of handler types must be `private`", + messageFormat: "Member '{0}' must be `private`", + category: "ImmediateHandler", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: "All members of handler classes must be private to prevent incorrect usage." + ); + + public static readonly DiagnosticDescriptor ContainingClassMustBeStatic = + new( + id: DiagnosticIds.IHR0018ContainingClassMustBeStatic, + title: "Handler types must be `static`", + messageFormat: "Class '{0}' must be `static`", + category: "ImmediateHandler", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: "Containing classes must be static to prevent incorrect usage." + ); + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create( [ HandlerMethodMustExist, HandlerMethodMustReturnTask, HandlerMustNotBeNestedInAnotherClass, - HandlerMethodMustBeStatic, HandlerMethodMustBeUnique, HandlerMethodMustBePrivate, HandlerShouldUseCancellationToken, + HandlerMethodMissingRequest, + HandlerMethodHasTooManyParameters, + ContainingClassMustBeSealed, + ContainingClassInstanceMembersMustBePrivate, + ContainingClassMustBeStatic, ]); public override void Initialize(AnalysisContext context) @@ -112,10 +160,9 @@ private static void AnalyzeSymbol(SymbolAnalysisContext context) var token = context.CancellationToken; token.ThrowIfCancellationRequested(); - if (context.Symbol is not INamedTypeSymbol namedTypeSymbol) - return; + var containerSymbol = (INamedTypeSymbol)context.Symbol; - if (!namedTypeSymbol + if (!containerSymbol .GetAttributes() .Any(x => x.AttributeClass.IsHandlerAttribute()) ) @@ -124,17 +171,17 @@ private static void AnalyzeSymbol(SymbolAnalysisContext context) } token.ThrowIfCancellationRequested(); - if (namedTypeSymbol.ContainingType is not null) + if (containerSymbol.ContainingType is not null) { context.ReportDiagnostic( Diagnostic.Create( HandlerMustNotBeNestedInAnotherClass, - namedTypeSymbol.Locations[0], - namedTypeSymbol.Name) + containerSymbol.Locations[0], + containerSymbol.Name) ); } - if (namedTypeSymbol + if (containerSymbol .GetMembers() .OfType() .Where(x => x.Name is "Handle" or "HandleAsync") @@ -144,81 +191,198 @@ private static void AnalyzeSymbol(SymbolAnalysisContext context) context.ReportDiagnostic( Diagnostic.Create( HandlerMethodMustExist, - namedTypeSymbol.Locations[0], - namedTypeSymbol.Name) + containerSymbol.Locations[0], + containerSymbol.Name) ); return; } token.ThrowIfCancellationRequested(); - switch (methods.Where(m => m.IsStatic).ToList()) + + if (methods is [{ } method]) { - case { Count: var cnt and not 1 }: - { - token.ThrowIfCancellationRequested(); + if (method.IsStatic) + AnalyzeStaticHandler(context, containerSymbol, method); + else + AnalyzeInstanceMethod(context, containerSymbol, method); - var diagnostic = cnt == 0 - ? HandlerMethodMustBeStatic - : HandlerMethodMustBeUnique; + return; + } - foreach (var method in methods) - { - context.ReportDiagnostic( - Diagnostic.Create( - diagnostic, - method.Locations[0], - method.Name) - ); - } + foreach (var m in methods) + { + context.ReportDiagnostic( + Diagnostic.Create( + HandlerMethodMustBeUnique, + m.Locations[0], + m.Name) + ); + } + } - break; - } + private static void AnalyzeInstanceMethod(SymbolAnalysisContext context, INamedTypeSymbol containerSymbol, IMethodSymbol method) + { + AnalyzeAccessibility(context, method); + AnalyzeReturnType(context, method); + AnalyzeCancellationToken(context, method); + + if (method.Parameters.Length == 0 + || (method.Parameters.Length == 1 && method.Parameters[0].Type.IsCancellationToken())) + { + context.ReportDiagnostic( + Diagnostic.Create( + HandlerMethodMissingRequest, + method.Locations[0], + method.Name + ) + ); + } + else if (method.Parameters.Length > 2 + || (method.Parameters.Length > 1 && !method.Parameters[^1].Type.IsCancellationToken())) + { + context.ReportDiagnostic( + Diagnostic.Create( + HandlerMethodHasTooManyParameters, + method.Locations[0], + method.Name + ) + ); + } - case [var methodSymbol]: + if (!containerSymbol.IsSealed) + { + context.ReportDiagnostic( + Diagnostic.Create( + ContainingClassMustBeSealed, + containerSymbol.Locations[0], + containerSymbol.Name + ) + ); + } + + foreach (var member in containerSymbol.GetMembers()) + { + if (ReferenceEquals(member, method) + || member + // static members are fine + is { IsStatic: true } + // private members are fine + or { DeclaredAccessibility: Accessibility.Private or Accessibility.NotApplicable } + // nested types are fine + or INamedTypeSymbol + // constructors and property implementations are fine + or IMethodSymbol { MethodKind: MethodKind.Constructor or MethodKind.PropertyGet or MethodKind.PropertySet } + ) { - token.ThrowIfCancellationRequested(); - if (methodSymbol.ReturnType is INamedTypeSymbol { ConstructedFrom: { } from } - && !from.IsValueTask() && !from.IsValueTask1() + continue; + } + + context.ReportDiagnostic( + Diagnostic.Create( + ContainingClassInstanceMembersMustBePrivate, + member.Locations[0], + member.Name ) - { - context.ReportDiagnostic( - Diagnostic.Create( - HandlerMethodMustReturnTask, - methodSymbol.Locations[0], - methodSymbol.Name - ) - ); - } + ); + } + } - if (methodSymbol.DeclaredAccessibility is not Accessibility.Private) - { - context.ReportDiagnostic( - Diagnostic.Create( - HandlerMethodMustBePrivate, - methodSymbol.Locations[0], - methodSymbol.Name - ) - ); - } + private static void AnalyzeStaticHandler(SymbolAnalysisContext context, INamedTypeSymbol containerSymbol, IMethodSymbol method) + { + AnalyzeAccessibility(context, method); + AnalyzeReturnType(context, method); + AnalyzeCancellationToken(context, method); + + if (method.Parameters.Length == 0 + || (method.Parameters.Length == 1 && method.Parameters[0].Type.IsCancellationToken())) + { + context.ReportDiagnostic( + Diagnostic.Create( + HandlerMethodMissingRequest, + method.Locations[0], + method.Name + ) + ); + } - if (!methodSymbol.Parameters[^1].Type.IsCancellationToken()) + if (!containerSymbol.IsStatic) + { + context.ReportDiagnostic( + Diagnostic.Create( + ContainingClassMustBeStatic, + containerSymbol.Locations[0], + containerSymbol.Name + ) + ); + } + } + + private static void AnalyzeAccessibility(SymbolAnalysisContext context, IMethodSymbol method) + { + if (method.DeclaredAccessibility is not Accessibility.Private) + { + context.ReportDiagnostic( + Diagnostic.Create( + HandlerMethodMustBePrivate, + method.Locations[0], + method.Name + ) + ); + } + } + + private static void AnalyzeReturnType(SymbolAnalysisContext context, IMethodSymbol method) + { + if (method is not + { + ReturnType: INamedTypeSymbol { - context.ReportDiagnostic( - Diagnostic.Create( - HandlerShouldUseCancellationToken, - methodSymbol.Locations[0], - methodSymbol.Name - ) - ); + OriginalDefinition: + { + MetadataName: "ValueTask" or "ValueTask`1", + ContainingNamespace: + { + Name: "Tasks", + ContainingNamespace: + { + Name: "Threading", + ContainingNamespace: + { + Name: "System", + ContainingNamespace.IsGlobalNamespace: true + } + } + } + } } - - break; } + ) + { + context.ReportDiagnostic( + Diagnostic.Create( + HandlerMethodMustReturnTask, + method.Locations[0], + method.Name + ) + ); + } + } - default: - // should never happen - all count cases are covered above - break; + private static void AnalyzeCancellationToken(SymbolAnalysisContext context, IMethodSymbol method) + { + if (method.Parameters.Length == 0) + return; + + if (!method.Parameters[^1].Type.IsCancellationToken()) + { + context.ReportDiagnostic( + Diagnostic.Create( + HandlerShouldUseCancellationToken, + method.Locations[0], + method.Name + ) + ); } } } diff --git a/src/Immediate.Handlers.Analyzers/Immediate.Handlers.Analyzers.md b/src/Immediate.Handlers.Analyzers/Immediate.Handlers.Analyzers.md index 15e6f2ed..4bf71704 100644 --- a/src/Immediate.Handlers.Analyzers/Immediate.Handlers.Analyzers.md +++ b/src/Immediate.Handlers.Analyzers/Immediate.Handlers.Analyzers.md @@ -76,18 +76,6 @@ introduce inconsistencies in connecting multiple behaviors in a pipeline. |CodeFix|False| --- -## IHR0009: Handler method must be static - -Handler method must be static, in order to be correctly referenced by the handler system. - -|Item|Value| -|-|-| -|Category|ImmediateHandler| -|Enabled|True| -|Severity|Error| -|CodeFix|False| ---- - ## IHR0010: Handler method must be unique If both `Handle` and `HandleAsync` are provided, it will not be possible to identify which is the correct handler diff --git a/src/Immediate.Handlers.Generators/Properties/launchSettings.json b/src/Immediate.Handlers.Generators/Properties/launchSettings.json index b19d2a16..0e373a58 100644 --- a/src/Immediate.Handlers.Generators/Properties/launchSettings.json +++ b/src/Immediate.Handlers.Generators/Properties/launchSettings.json @@ -2,7 +2,11 @@ "profiles": { "Normal": { "commandName": "DebugRoslynComponent", - "targetProject": "..//..//Samples//Normal//Normal.csproj" + "targetProject": "..\\..\\samples\\Normal\\Normal.csproj" + }, + "Benchmark.Simple": { + "commandName": "DebugRoslynComponent", + "targetProject": "..\\..\\benchmarks\\Benchmark.Simple\\Benchmark.Simple.csproj" } } -} +} \ No newline at end of file diff --git a/src/Immediate.Handlers.Generators/TransformHandler.cs b/src/Immediate.Handlers.Generators/TransformHandler.cs index 906a4ba9..8b75d79d 100644 --- a/src/Immediate.Handlers.Generators/TransformHandler.cs +++ b/src/Immediate.Handlers.Generators/TransformHandler.cs @@ -31,8 +31,29 @@ is null // no parameters or { Parameters: [] } // not a valuetask return - // nb: both `ValueTask` and `ValueTask<>` will pass here - or { ReturnType: not INamedTypeSymbol { OriginalDefinition.Name: "ValueTask" } } + or + { + ReturnType: not INamedTypeSymbol + { + OriginalDefinition: + { + MetadataName: "ValueTask" or "ValueTask`1", + ContainingNamespace: + { + Name: "Tasks", + ContainingNamespace: + { + Name: "Threading", + ContainingNamespace: + { + Name: "System", + ContainingNamespace.IsGlobalNamespace: true + } + } + } + } + } + } // only private methods are considered or { DeclaredAccessibility: not Accessibility.Private }) { @@ -149,15 +170,12 @@ file static class Extensions .GetMembers() .OfType() .Where(m => m.Name is "Handle" or "HandleAsync") + .Take(2) .ToList(); if (candidates.Count == 1) return candidates[0]; - _ = candidates.RemoveAll(ims => !ims.IsStatic); - if (candidates.Count == 1) - return candidates[0]; - return null; } diff --git a/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandleMethodDoesNotReturnTask.cs b/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandleMethodDoesNotReturnTask.cs index 0bed83f2..3379429e 100644 --- a/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandleMethodDoesNotReturnTask.cs +++ b/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandleMethodDoesNotReturnTask.cs @@ -7,7 +7,7 @@ namespace Immediate.Handlers.Tests.AnalyzerTests.HandlerClassAnalyzerTests; public partial class Tests { [Test] - public async Task HandleMethodDoesNotReturnTask_AlertDiagnostic() => + public async Task HandleMethodDoesNotReturnTask_Static_AlertDiagnostic() => await AnalyzerTestHelpers.CreateAnalyzerTest( """ using System; @@ -24,26 +24,42 @@ public static partial class GetUsersQuery { public record Query; - private static IEnumerable? {|IHR0002:HandleAsync|}( + private static int {|IHR0002:HandleAsync|}( Query _, - UsersService usersService, CancellationToken token) { - return null; + return 0; } } + """, + DriverReferenceAssemblies.Normal + ).RunAsync(); - public class User { } - public class UsersService(ILogger logger) + [Test] + public async Task HandleMethodDoesNotReturnTask_Instance_AlertDiagnostic() => + await AnalyzerTestHelpers.CreateAnalyzerTest( + """ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + using Immediate.Handlers.Shared; + + [Handler] + public sealed partial class GetUsersQuery { - public ValueTask> GetUsers() + public record Query; + + private int {|IHR0002:Handle|}( + Query _, + CancellationToken token) { - _ = logger.ToString(); - return ValueTask.FromResult(Enumerable.Empty()); + return 0; } } - - public interface ILogger; """, DriverReferenceAssemblies.Normal ).RunAsync(); diff --git a/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandleMethodIsCorrectWithIntReturn.cs b/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandleMethodIsCorrectWithIntReturn.cs index e5cd01d7..ace4f33f 100644 --- a/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandleMethodIsCorrectWithIntReturn.cs +++ b/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandleMethodIsCorrectWithIntReturn.cs @@ -7,7 +7,7 @@ namespace Immediate.Handlers.Tests.AnalyzerTests.HandlerClassAnalyzerTests; public partial class Tests { [Test] - public async Task HandleMethodIsCorrectWithIntReturn_DoesNotAlert() => + public async Task HandleMethodIsCorrectWithIntReturn_Static_DoesNotAlert() => await AnalyzerTestHelpers.CreateAnalyzerTest( """ using System; @@ -20,7 +20,7 @@ await AnalyzerTestHelpers.CreateAnalyzerTest( using Immediate.Handlers.Shared; [Handler] - public partial class GetUsersQuery + public static partial class GetUsersQuery { public record Query; @@ -30,7 +30,29 @@ private static ValueTask HandleAsync( { return ValueTask.FromResult(0); } - + } + """, + DriverReferenceAssemblies.Normal + ).RunAsync(); + + [Test] + public async Task HandleMethodIsCorrectWithIntReturn_Instance_DoesNotAlert() => + await AnalyzerTestHelpers.CreateAnalyzerTest( + """ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + using Immediate.Handlers.Shared; + + [Handler] + public sealed partial class GetUsersQuery + { + public record Query; + private ValueTask Handle( Query _, CancellationToken token) diff --git a/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandleMethodIsCorrectWithVoidReturn.cs b/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandleMethodIsCorrectWithVoidReturn.cs index e2233af1..3406b3e0 100644 --- a/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandleMethodIsCorrectWithVoidReturn.cs +++ b/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandleMethodIsCorrectWithVoidReturn.cs @@ -7,7 +7,7 @@ namespace Immediate.Handlers.Tests.AnalyzerTests.HandlerClassAnalyzerTests; public partial class Tests { [Test] - public async Task HandleMethodIsCorrectWithVoidReturn_DoesNotAlert() => + public async Task HandleMethodIsCorrectWithVoidReturn_Static_DoesNotAlert() => await AnalyzerTestHelpers.CreateAnalyzerTest( """ using System; @@ -20,7 +20,7 @@ await AnalyzerTestHelpers.CreateAnalyzerTest( using Immediate.Handlers.Shared; [Handler] - public partial class GetUsersQuery + public static partial class GetUsersQuery { public record Query; @@ -29,7 +29,29 @@ private static async ValueTask HandleAsync( CancellationToken token) { } - + } + """, + DriverReferenceAssemblies.Normal + ).RunAsync(); + + [Test] + public async Task HandleMethodIsCorrectWithVoidReturn_Instance_DoesNotAlert() => + await AnalyzerTestHelpers.CreateAnalyzerTest( + """ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + using Immediate.Handlers.Shared; + + [Handler] + public sealed partial class GetUsersQuery + { + public record Query; + private async ValueTask Handle( Query _, CancellationToken token) diff --git a/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandleMethodIsNotPrivate.cs b/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandleMethodIsNotPrivate.cs index df471225..426fb683 100644 --- a/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandleMethodIsNotPrivate.cs +++ b/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandleMethodIsNotPrivate.cs @@ -7,7 +7,7 @@ namespace Immediate.Handlers.Tests.AnalyzerTests.HandlerClassAnalyzerTests; public partial class Tests { [Test] - public async Task HandleMethodIsNotPrivate_AlertDiagnostic() => + public async Task HandleMethodIsNotPrivate_Static_AlertDiagnostic() => await AnalyzerTestHelpers.CreateAnalyzerTest( """ using System; @@ -34,4 +34,33 @@ public record Query; """, DriverReferenceAssemblies.Normal ).RunAsync(); + + [Test] + public async Task HandleMethodIsNotPrivate_Instance_AlertDiagnostic() => + await AnalyzerTestHelpers.CreateAnalyzerTest( + """ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + using Immediate.Handlers.Shared; + + [Handler] + public sealed partial class GetUsersQuery + { + public record Query; + + public ValueTask {|IHR0011:HandleAsync|}( + Query _, + CancellationToken token) + { + return ValueTask.FromResult(0); + } + } + """, + DriverReferenceAssemblies.Normal + ).RunAsync(); } diff --git a/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandleMethodIsNotStatic.cs b/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandleMethodIsNotStatic.cs deleted file mode 100644 index 076d38c1..00000000 --- a/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandleMethodIsNotStatic.cs +++ /dev/null @@ -1,58 +0,0 @@ -using Immediate.Handlers.Analyzers; -using Immediate.Handlers.Tests.Helpers; - -namespace Immediate.Handlers.Tests.AnalyzerTests.HandlerClassAnalyzerTests; - -[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1724:Type names should not match namespaces", Justification = "Not being consumed by other code")] -public partial class Tests -{ - [Test] - public async Task HandleMethodIsNotStatic_AlertDiagnostic() => - await AnalyzerTestHelpers.CreateAnalyzerTest( - """ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Net.Http; - using System.Threading; - using System.Threading.Tasks; - using Immediate.Handlers.Shared; - - [Handler] - public partial class GetUsersQuery - { - public record Query; - - private IEnumerable? {|IHR0009:Handle|}( - Query _, - UsersService usersService, - CancellationToken token) - { - return null; - } - - private IEnumerable? {|IHR0009:HandleAsync|}( - Query _, - UsersService usersService, - CancellationToken token) - { - return null; - } - } - - public class User { } - public class UsersService(ILogger logger) - { - public ValueTask> GetUsers() - { - _ = logger.ToString(); - return ValueTask.FromResult(Enumerable.Empty()); - } - } - - public interface ILogger; - """, - DriverReferenceAssemblies.Normal - ).RunAsync(); -} diff --git a/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandleMethodIsNotUnique.cs b/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandleMethodIsNotUnique.cs index 4c02e6e9..2e8d30c4 100644 --- a/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandleMethodIsNotUnique.cs +++ b/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandleMethodIsNotUnique.cs @@ -7,7 +7,7 @@ namespace Immediate.Handlers.Tests.AnalyzerTests.HandlerClassAnalyzerTests; public partial class Tests { [Test] - public async Task HandleMethodIsNotUnique_AlertDiagnostic() => + public async Task HandleMethodIsNotUnique_Static_AlertDiagnostic() => await AnalyzerTestHelpers.CreateAnalyzerTest( """ using System; @@ -41,4 +41,40 @@ public record Query; """, DriverReferenceAssemblies.Normal ).RunAsync(); + + [Test] + public async Task HandleMethodIsNotUnique_Instance_AlertDiagnostic() => + await AnalyzerTestHelpers.CreateAnalyzerTest( + """ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + using Immediate.Handlers.Shared; + + [Handler] + public sealed partial class GetUsersQuery + { + public record Query; + + private ValueTask {|IHR0010:HandleAsync|}( + Query _, + CancellationToken token) + { + return ValueTask.FromResult(0); + } + + private ValueTask {|IHR0010:Handle|}( + Query _, + CancellationToken token) + { + return ValueTask.FromResult(0); + } + } + """, + DriverReferenceAssemblies.Normal + ).RunAsync(); } diff --git a/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandleMethodMissingRequest.cs b/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandleMethodMissingRequest.cs new file mode 100644 index 00000000..9c418623 --- /dev/null +++ b/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandleMethodMissingRequest.cs @@ -0,0 +1,120 @@ +using Immediate.Handlers.Analyzers; +using Immediate.Handlers.Tests.Helpers; + +namespace Immediate.Handlers.Tests.AnalyzerTests.HandlerClassAnalyzerTests; + +[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1724:Type names should not match namespaces", Justification = "Not being consumed by other code")] +public partial class Tests +{ + [Test] + public async Task HandleMethodWithZeroParameters_Static_AlertDiagnostic() => + await AnalyzerTestHelpers.CreateAnalyzerTest( + """ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + using Immediate.Handlers.Shared; + + [Handler] + public static partial class GetUsersQuery + { + public record Query; + + private static ValueTask {|IHR0014:HandleAsync|}() + { + return ValueTask.FromResult(0); + } + } + """, + DriverReferenceAssemblies.Normal + ).RunAsync(); + + [Test] + public async Task HandleMethodWithOnlyCancellationToken_Static_AlertDiagnostic() => + await AnalyzerTestHelpers.CreateAnalyzerTest( + """ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + using Immediate.Handlers.Shared; + + [Handler] + public static partial class GetUsersQuery + { + public record Query; + + private static ValueTask {|IHR0014:HandleAsync|}( + CancellationToken token + ) + { + return ValueTask.FromResult(0); + } + } + """, + DriverReferenceAssemblies.Normal + ).RunAsync(); + + [Test] + public async Task HandleMethodWithZeroParameters_Instance_AlertDiagnostic() => + await AnalyzerTestHelpers.CreateAnalyzerTest( + """ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + using Immediate.Handlers.Shared; + + [Handler] + public sealed partial class GetUsersQuery + { + public record Query; + + private ValueTask {|IHR0014:HandleAsync|}() + { + return ValueTask.FromResult(0); + } + } + """, + DriverReferenceAssemblies.Normal + ).RunAsync(); + + [Test] + public async Task HandleMethodWithOnlyCancellationToken_Instance_AlertDiagnostic() => + await AnalyzerTestHelpers.CreateAnalyzerTest( + """ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + using Immediate.Handlers.Shared; + + [Handler] + public sealed partial class GetUsersQuery + { + public record Query; + + private ValueTask {|IHR0014:HandleAsync|}( + CancellationToken token + ) + { + return ValueTask.FromResult(0); + } + } + """, + DriverReferenceAssemblies.Normal + ).RunAsync(); +} diff --git a/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandleMethodShouldUseCancellationToken.cs b/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandleMethodShouldUseCancellationToken.cs index 5fbda068..d99a06ce 100644 --- a/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandleMethodShouldUseCancellationToken.cs +++ b/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandleMethodShouldUseCancellationToken.cs @@ -7,7 +7,7 @@ namespace Immediate.Handlers.Tests.AnalyzerTests.HandlerClassAnalyzerTests; public partial class Tests { [Test] - public async Task HandleMethodWithoutCancellationToken_AlertDiagnostic() => + public async Task HandleMethodWithoutCancellationToken_Static_AlertDiagnostic() => await AnalyzerTestHelpers.CreateAnalyzerTest( """ using System; @@ -33,4 +33,32 @@ public record Query; """, DriverReferenceAssemblies.Normal ).RunAsync(); + + [Test] + public async Task HandleMethodWithoutCancellationToken_Instance_AlertDiagnostic() => + await AnalyzerTestHelpers.CreateAnalyzerTest( + """ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + using Immediate.Handlers.Shared; + + [Handler] + public sealed partial class GetUsersQuery + { + public record Query; + + private ValueTask {|IHR0012:HandleAsync|}( + Query _) + { + return ValueTask.FromResult(0); + } + } + """, + DriverReferenceAssemblies.Normal + ).RunAsync(); } diff --git a/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandleMethodTooManyParameters.cs b/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandleMethodTooManyParameters.cs new file mode 100644 index 00000000..7d35e88e --- /dev/null +++ b/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandleMethodTooManyParameters.cs @@ -0,0 +1,69 @@ +using Immediate.Handlers.Analyzers; +using Immediate.Handlers.Tests.Helpers; + +namespace Immediate.Handlers.Tests.AnalyzerTests.HandlerClassAnalyzerTests; + +[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1724:Type names should not match namespaces", Justification = "Not being consumed by other code")] +public partial class Tests +{ + [Test] + public async Task HandleMethodWithTooManyParameters_Instance_AlertDiagnostic() => + await AnalyzerTestHelpers.CreateAnalyzerTest( + """ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + using Immediate.Handlers.Shared; + + [Handler] + public sealed partial class GetUsersQuery + { + public record Query; + + private ValueTask {|IHR0012:{|IHR0015:HandleAsync|}|}( + Query query1, + Query query2 + ) + { + return ValueTask.FromResult(0); + } + } + """, + DriverReferenceAssemblies.Normal + ).RunAsync(); + + [Test] + public async Task HandleMethodWithCancellationTokenAndTooManyParameters_Instance_AlertDiagnostic() => + await AnalyzerTestHelpers.CreateAnalyzerTest( + """ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + using Immediate.Handlers.Shared; + + [Handler] + public sealed partial class GetUsersQuery + { + public record Query; + + private ValueTask {|IHR0015:HandleAsync|}( + Query query1, + Query query2, + CancellationToken token + ) + { + return ValueTask.FromResult(0); + } + } + """, + DriverReferenceAssemblies.Normal + ).RunAsync(); +} diff --git a/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandlerClassMembersPrivate.cs b/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandlerClassMembersPrivate.cs new file mode 100644 index 00000000..1deb5280 --- /dev/null +++ b/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandlerClassMembersPrivate.cs @@ -0,0 +1,40 @@ +using Immediate.Handlers.Analyzers; +using Immediate.Handlers.Tests.Helpers; + +namespace Immediate.Handlers.Tests.AnalyzerTests.HandlerClassAnalyzerTests; + +public sealed partial class Tests +{ + [Test] + public async Task HandlerClassMembers_DoesAlert() => + await AnalyzerTestHelpers.CreateAnalyzerTest( + """ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + using Immediate.Handlers.Shared; + + [Handler] + public sealed partial class GetUsersQuery + { + public record Query; + + private ValueTask HandleAsync( + Query _, + CancellationToken token) + { + return ValueTask.FromResult(0); + } + + public void {|IHR0017:Test1|}() { } + protected int {|IHR0017:Test2|} => 1; + internal int {|IHR0017:_test3|}; + } + """, + DriverReferenceAssemblies.Normal + ).RunAsync(); +} diff --git a/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandlerClassNested.cs b/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandlerClassNested.cs index 038c07ed..6281238a 100644 --- a/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandlerClassNested.cs +++ b/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandlerClassNested.cs @@ -18,7 +18,8 @@ await AnalyzerTestHelpers.CreateAnalyzerTest( using System.Threading.Tasks; using Immediate.Handlers.Shared; - public static partial class Wrapper { + public static partial class Wrapper + { [Handler] public static class {|IHR0005:GetUsersQuery|} { diff --git a/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandlerClassSealed.cs b/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandlerClassSealed.cs new file mode 100644 index 00000000..58e8721e --- /dev/null +++ b/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandlerClassSealed.cs @@ -0,0 +1,36 @@ +using Immediate.Handlers.Analyzers; +using Immediate.Handlers.Tests.Helpers; + +namespace Immediate.Handlers.Tests.AnalyzerTests.HandlerClassAnalyzerTests; + +public sealed partial class Tests +{ + [Test] + public async Task HandlerClassNotSealed_DoesAlert() => + await AnalyzerTestHelpers.CreateAnalyzerTest( + """ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + using Immediate.Handlers.Shared; + + [Handler] + public partial class {|IHR0016:GetUsersQuery|} + { + public record Query; + + private ValueTask HandleAsync( + Query _, + CancellationToken token) + { + return ValueTask.FromResult(0); + } + } + """, + DriverReferenceAssemblies.Normal + ).RunAsync(); +} diff --git a/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandlerClassStatic.cs b/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandlerClassStatic.cs new file mode 100644 index 00000000..0166acfc --- /dev/null +++ b/tests/Immediate.Handlers.Tests/AnalyzerTests/HandlerClassAnalyzerTests/Tests.HandlerClassStatic.cs @@ -0,0 +1,36 @@ +using Immediate.Handlers.Analyzers; +using Immediate.Handlers.Tests.Helpers; + +namespace Immediate.Handlers.Tests.AnalyzerTests.HandlerClassAnalyzerTests; + +public sealed partial class Tests +{ + [Test] + public async Task HandlerClassNotStatic_DoesAlert() => + await AnalyzerTestHelpers.CreateAnalyzerTest( + """ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + using Immediate.Handlers.Shared; + + [Handler] + public partial class {|IHR0018:GetUsersQuery|} + { + public record Query; + + private static ValueTask HandleAsync( + Query _, + CancellationToken token) + { + return ValueTask.FromResult(0); + } + } + """, + DriverReferenceAssemblies.Normal + ).RunAsync(); +}