Skip to content

Commit 2478fc4

Browse files
committed
Qualify handler order attribute analysis
1 parent 30fe19e commit 2478fc4

2 files changed

Lines changed: 121 additions & 4 deletions

File tree

DesignPatterns.Analyzers/UnregisteredHandlerAnalyzer.cs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -143,13 +143,21 @@ private static bool IsHandlerOrderAttribute(INamedTypeSymbol? attributeClass)
143143
return false;
144144
}
145145

146-
var metadataName = attributeClass.MetadataName;
147-
if (metadataName == "HandlerOrderAttribute")
146+
return GetFullMetadataName(attributeClass) == HandlerAnalysisConstants.HandlerOrderMetadataName ||
147+
GetFullMetadataName(attributeClass.OriginalDefinition) == HandlerAnalysisConstants.HandlerOrderGenericMetadataName;
148+
}
149+
150+
private static string GetFullMetadataName(INamedTypeSymbol typeSymbol)
151+
{
152+
var metadataName = typeSymbol.MetadataName;
153+
for (var containingType = typeSymbol.ContainingType; containingType is not null; containingType = containingType.ContainingType)
148154
{
149-
return true;
155+
metadataName = containingType.MetadataName + "+" + metadataName;
150156
}
151157

152-
return attributeClass.OriginalDefinition.MetadataName == "HandlerOrderAttribute`1";
158+
return typeSymbol.ContainingNamespace.IsGlobalNamespace
159+
? metadataName
160+
: typeSymbol.ContainingNamespace.ToDisplayString() + "." + metadataName;
153161
}
154162

155163
private static INamedTypeSymbol? TryGetContextFromAttribute(AttributeData attribute)

tests/DesignPatterns.Analyzers.Tests/UnregisteredHandlerAnalyzerTests.cs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,4 +112,113 @@ public ValueTask InvokeAsync(
112112

113113
Assert.DoesNotContain(diagnostics, diagnostic => diagnostic.Id == "DP024");
114114
}
115+
116+
[Fact]
117+
public async Task DoesNotTreatForeignHandlerOrderAsRegisteredContext()
118+
{
119+
const string source = """
120+
using System;
121+
using System.Threading;
122+
using System.Threading.Tasks;
123+
using DesignPatterns.Behavioral;
124+
125+
namespace Other
126+
{
127+
[AttributeUsage(AttributeTargets.Class)]
128+
public sealed class HandlerOrderAttribute : Attribute
129+
{
130+
public HandlerOrderAttribute(int order, Type contextType) { }
131+
}
132+
}
133+
134+
namespace TestAssembly
135+
{
136+
public sealed class RequestContext
137+
{
138+
}
139+
140+
[Other.HandlerOrder(10, typeof(RequestContext))]
141+
public sealed class LoggingHandler : IHandler<RequestContext>
142+
{
143+
public ValueTask InvokeAsync(
144+
RequestContext context,
145+
HandlerDelegate<RequestContext> next,
146+
CancellationToken cancellationToken = default) =>
147+
default;
148+
}
149+
150+
public sealed class AuditHandler : IHandler<RequestContext>
151+
{
152+
public ValueTask InvokeAsync(
153+
RequestContext context,
154+
HandlerDelegate<RequestContext> next,
155+
CancellationToken cancellationToken = default) =>
156+
default;
157+
}
158+
}
159+
""";
160+
161+
var diagnostics = await AnalyzerTestContext.RunAnalyzersAsync(
162+
source,
163+
new UnregisteredHandlerAnalyzer());
164+
165+
Assert.DoesNotContain(diagnostics, diagnostic => diagnostic.Id == "DP024");
166+
}
167+
168+
[Fact]
169+
public async Task ReportsWhenOnlyForeignHandlerOrderIsPresentOnImplementation()
170+
{
171+
const string source = """
172+
using System;
173+
using System.Threading;
174+
using System.Threading.Tasks;
175+
using DesignPatterns.Behavioral;
176+
177+
namespace Other
178+
{
179+
[AttributeUsage(AttributeTargets.Class)]
180+
public sealed class HandlerOrderAttribute<TContext> : Attribute
181+
{
182+
public HandlerOrderAttribute(int order) { }
183+
}
184+
}
185+
186+
namespace TestAssembly
187+
{
188+
public sealed class RequestContext
189+
{
190+
}
191+
192+
[DesignPatterns.Behavioral.HandlerOrder<RequestContext>(10)]
193+
public sealed class LoggingHandler : IHandler<RequestContext>
194+
{
195+
public ValueTask InvokeAsync(
196+
RequestContext context,
197+
HandlerDelegate<RequestContext> next,
198+
CancellationToken cancellationToken = default) =>
199+
default;
200+
}
201+
202+
[Other.HandlerOrder<RequestContext>(20)]
203+
public sealed class AuditHandler : IHandler<RequestContext>
204+
{
205+
public ValueTask InvokeAsync(
206+
RequestContext context,
207+
HandlerDelegate<RequestContext> next,
208+
CancellationToken cancellationToken = default) =>
209+
default;
210+
}
211+
}
212+
""";
213+
214+
var diagnostics = await AnalyzerTestContext.RunAnalyzersAsync(
215+
source,
216+
new UnregisteredHandlerAnalyzer());
217+
218+
Assert.Contains(
219+
diagnostics,
220+
diagnostic =>
221+
diagnostic.Id == "DP024" &&
222+
diagnostic.GetMessage().Contains("AuditHandler", StringComparison.Ordinal));
223+
}
115224
}

0 commit comments

Comments
 (0)