Skip to content

Commit 7919d2a

Browse files
Warn users if not using CancellationToken (#93)
1 parent 97a8dd9 commit 7919d2a

File tree

6 files changed

+91
-7
lines changed

6 files changed

+91
-7
lines changed

.gitattributes

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
* text=auto eol=lf
1+
* text=auto

src/Immediate.Handlers.Analyzers/AnalyzerReleases.Shipped.md

+15-6
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,21 @@
44

55
Rule ID | Category | Severity | Notes
66
--------|----------|----------|--------------------
7-
IHR0001 | ImmediateHandler | Error | HandlerClassAnalyzer
8-
IHR0002 | ImmediateHandler | Error | HandlerClassAnalyzer
9-
IHR0005 | ImmediateHandler | Error | HandlerClassAnalyzer
10-
IHR0006 | ImmediateHandler | Error | BehaviorsAnalyzer
11-
IHR0007 | ImmediateHandler | Error | BehaviorsAnalyzer
12-
IHR0008 | ImmediateHandler | Error | BehaviorsAnalyzer
7+
IHR0001 | ImmediateHandler | Error | HandlerClassAnalyzer
8+
IHR0002 | ImmediateHandler | Error | HandlerClassAnalyzer
9+
IHR0005 | ImmediateHandler | Error | HandlerClassAnalyzer
10+
IHR0006 | ImmediateHandler | Error | BehaviorsAnalyzer
11+
IHR0007 | ImmediateHandler | Error | BehaviorsAnalyzer
12+
IHR0008 | ImmediateHandler | Error | BehaviorsAnalyzer
1313
IHR0009 | ImmediateHandler | Error | HandlerClassAnalyzer
1414
IHR0010 | ImmediateHandler | Error | HandlerClassAnalyzer
1515
IHR0011 | ImmediateHandler | Error | HandlerClassAnalyzer
16+
17+
18+
## Release 1.5
19+
20+
### New Rules
21+
22+
Rule ID | Category | Severity | Notes
23+
--------|----------|----------|--------------------
24+
IHR0012 | ImmediateHandler | Warning | HandlerClassAnalyzer

src/Immediate.Handlers.Analyzers/DiagnosticIds.cs

+1
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ internal static class DiagnosticIds
1414
public const string IHR0009HandlerMethodMustBeStatic = "IHR0009";
1515
public const string IHR0010HandlerMethodMustBeUnique = "IHR0010";
1616
public const string IHR0011HandlerMethodMustBePrivate = "IHR0011";
17+
public const string IHR0012HandlerShouldUseCancellationToken = "IHR0012";
1718
}

src/Immediate.Handlers.Analyzers/HandlerClassAnalyzer.cs

+23
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,17 @@ public sealed class HandlerClassAnalyzer : DiagnosticAnalyzer
7373
description: "Static handler method must be static."
7474
);
7575

76+
public static readonly DiagnosticDescriptor HandlerShouldUseCancellationToken =
77+
new(
78+
id: DiagnosticIds.IHR0012HandlerShouldUseCancellationToken,
79+
title: "Handler method should use CancellationToken",
80+
messageFormat: "Method '{0}' should receive a `CancellationToken`",
81+
category: "ImmediateHandler",
82+
defaultSeverity: DiagnosticSeverity.Warning,
83+
isEnabledByDefault: true,
84+
description: "Handlers should use CancellationToken to properly support cancellation."
85+
);
86+
7687
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } =
7788
ImmutableArray.Create(
7889
[
@@ -82,6 +93,7 @@ public sealed class HandlerClassAnalyzer : DiagnosticAnalyzer
8293
HandlerMethodMustBeStatic,
8394
HandlerMethodMustBeUnique,
8495
HandlerMethodMustBePrivate,
96+
HandlerShouldUseCancellationToken,
8597
]);
8698

8799
public override void Initialize(AnalysisContext context)
@@ -192,6 +204,17 @@ private static void AnalyzeSymbol(SymbolAnalysisContext context)
192204
);
193205
}
194206

207+
if (!methodSymbol.Parameters[^1].Type.IsCancellationToken())
208+
{
209+
context.ReportDiagnostic(
210+
Diagnostic.Create(
211+
HandlerShouldUseCancellationToken,
212+
methodSymbol.Locations[0],
213+
methodSymbol.Name
214+
)
215+
);
216+
}
217+
195218
break;
196219
}
197220

src/Immediate.Handlers.Analyzers/ITypeSymbolExtensions.cs

+15
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,19 @@ typeSymbol is
4545
public static bool ImplementsBehavior(this INamedTypeSymbol typeSymbol) =>
4646
typeSymbol.IsBehavior2()
4747
|| (typeSymbol.BaseType is not null && ImplementsBehavior(typeSymbol.BaseType.OriginalDefinition));
48+
49+
public static bool IsCancellationToken(this ITypeSymbol typeSymbol) =>
50+
typeSymbol is INamedTypeSymbol
51+
{
52+
Name: "CancellationToken",
53+
ContainingNamespace:
54+
{
55+
Name: "Threading",
56+
ContainingNamespace:
57+
{
58+
Name: "System",
59+
ContainingNamespace.IsGlobalNamespace: true
60+
}
61+
}
62+
};
4863
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using Immediate.Handlers.Analyzers;
2+
using Immediate.Handlers.Tests.Helpers;
3+
4+
namespace Immediate.Handlers.Tests.AnalyzerTests.HandlerClassAnalyzerTests;
5+
6+
[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1724:Type names should not match namespaces", Justification = "Not being consumed by other code")]
7+
public partial class Tests
8+
{
9+
[Fact]
10+
public async Task HandleMethodWithoutCancellationToken_AlertDiagnostic() =>
11+
await AnalyzerTestHelpers.CreateAnalyzerTest<HandlerClassAnalyzer>(
12+
"""
13+
using System;
14+
using System.Collections.Generic;
15+
using System.IO;
16+
using System.Linq;
17+
using System.Net.Http;
18+
using System.Threading;
19+
using System.Threading.Tasks;
20+
using Immediate.Handlers.Shared;
21+
22+
[Handler]
23+
public class GetUsersQuery
24+
{
25+
public record Query;
26+
27+
private static ValueTask<int> {|IHR0012:HandleAsync|}(
28+
Query _)
29+
{
30+
return ValueTask.FromResult(0);
31+
}
32+
}
33+
""",
34+
DriverReferenceAssemblies.Normal
35+
).RunAsync();
36+
}

0 commit comments

Comments
 (0)