Skip to content

Commit 09e2086

Browse files
authored
Merge pull request #7 from PandaTechAM/development
ignore test projects
2 parents 014c5ae + 5395d90 commit 09e2086

File tree

2 files changed

+60
-10
lines changed

2 files changed

+60
-10
lines changed

src/Analyzers/Analyzers.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
<PackageId>Pandatech.Analyzers</PackageId>
1919
<PackageIcon>pandatech.png</PackageIcon>
2020
<PackageReadmeFile>Readme.md</PackageReadmeFile>
21-
<Version>1.4.3</Version>
21+
<Version>1.5.0</Version>
2222
<Authors>Pandatech</Authors>
2323
<Description>Pandatech Roslyn analyzers enforcing company coding rules.</Description>
2424
<PackageLicenseExpression>MIT</PackageLicenseExpression>

src/Analyzers/Async/AsyncMethodConventionsAnalyzer.cs

Lines changed: 59 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -131,15 +131,20 @@ private static void AnalyzeAnonymousFunction(OperationAnalysisContext context)
131131
}
132132

133133
// For lambdas/anonymous functions we do NOT enforce "must have CT" (PT0002),
134-
// because the delegate signature is usually dictated by an external API
135-
// (Hangfire, ASP.NET, minimal APIs binder, etc.).
134+
// because the delegate signature is usually dictated by an external API.
136135
// We only normalize name and position if a CT parameter already exists.
137136
var ctInfo = GetCancellationTokenInfo(symbol);
138137
if (!ctInfo.HasCt)
139138
{
140139
return;
141140
}
142141

142+
// If the lambda is inside a test method, skip CT name/position completely.
143+
if (IsInsideTestMethod(anon))
144+
{
145+
return;
146+
}
147+
143148
const string displayName = "anonymous function";
144149
var location = anon.Syntax.GetLocation();
145150

@@ -173,6 +178,10 @@ private static void AnalyzeAsyncMember(IMethodSymbol method,
173178
Action<Diagnostic> report)
174179
{
175180
var isContract = method.IsContractImplementation();
181+
var isTest = IsTestMethod(method);
182+
183+
// For CT missing / position we skip both contracts and tests.
184+
var skipCtMissingAndPosition = isContract || isTest;
176185

177186
// PT0001 – name must end with Async (for named methods),
178187
// but we DO NOT enforce it on contract implementations (MediatR Handle, overrides, etc.).
@@ -186,10 +195,10 @@ private static void AnalyzeAsyncMember(IMethodSymbol method,
186195
var ctInfo = GetCancellationTokenInfo(method);
187196

188197
// PT0002 – missing CancellationToken
189-
// Only enforced on non-contract methods (e.g. interface itself, normal class methods).
198+
// Only enforced on non-contract, non-test methods.
190199
if (!ctInfo.HasCt)
191200
{
192-
if (!isContract)
201+
if (!skipCtMissingAndPosition)
193202
{
194203
report(Diagnostic.Create(CancellationTokenMissingRule, location, displayName));
195204
}
@@ -198,17 +207,15 @@ private static void AnalyzeAsyncMember(IMethodSymbol method,
198207
}
199208

200209
// PT0003 – name must be ct
201-
// Enforced only on non-contract methods (interfaces / non-contract class methods).
202-
// Implementations of contracts (overrides / interface impls) are skipped so we don't
203-
// fight the “implementation params must match interface” rule or external contracts.
210+
// Enforced only on non-contract methods (including tests, per "maybe maximum naming").
204211
if (!ctInfo.IsNamedCt && !isContract)
205212
{
206213
report(Diagnostic.Create(CancellationTokenNameRule, location, displayName, ctInfo.Name));
207214
}
208215

209216
// PT0004 – CT must be last
210-
// Only enforced on non-contract methods (interface / own class methods).
211-
if (!ctInfo.IsLast && !isContract)
217+
// Only enforced on non-contract, non-test methods.
218+
if (!ctInfo.IsLast && !skipCtMissingAndPosition)
212219
{
213220
report(Diagnostic.Create(CancellationTokenPositionRule, location, displayName, ctInfo.Name));
214221
}
@@ -224,6 +231,49 @@ private static bool IsTaskLike(INamedTypeSymbol type)
224231
return type.Name is "Task" or "ValueTask";
225232
}
226233

234+
private static bool IsTestMethod(IMethodSymbol method)
235+
{
236+
foreach (var attr in method.GetAttributes())
237+
{
238+
var attrClass = attr.AttributeClass;
239+
if (attrClass is null)
240+
{
241+
continue;
242+
}
243+
244+
var name = attrClass.Name;
245+
246+
if (name.EndsWith("Attribute", StringComparison.Ordinal))
247+
{
248+
name = name.Substring(0, name.Length - "Attribute".Length);
249+
}
250+
251+
if (name is "Fact"
252+
or "Theory"
253+
or "Test"
254+
or "TestCase"
255+
or "TestMethod"
256+
or "DataTestMethod")
257+
{
258+
return true;
259+
}
260+
}
261+
262+
return false;
263+
}
264+
265+
266+
private static bool IsInsideTestMethod(IAnonymousFunctionOperation anon)
267+
{
268+
if (anon.Symbol.ContainingSymbol is IMethodSymbol method)
269+
{
270+
return IsTestMethod(method);
271+
}
272+
273+
return false;
274+
}
275+
276+
227277
private static CtInfo GetCancellationTokenInfo(IMethodSymbol method)
228278
{
229279
IParameterSymbol? ctParam = null;

0 commit comments

Comments
 (0)