Skip to content

Commit aa97561

Browse files
committed
Find token from Xunit.TestContext.Current.CancellationToken
1 parent 58436c3 commit aa97561

File tree

3 files changed

+60
-2
lines changed

3 files changed

+60
-2
lines changed

src/Meziantou.Analyzer/Rules/UseAnOverloadThatHasCancellationTokenAnalyzer.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ private sealed class AnalyzerContext(Compilation compilation)
9292
private INamedTypeSymbol? TaskOfTSymbol { get; } = compilation.GetBestTypeByMetadataName("System.Threading.Tasks.Task`1");
9393
private INamedTypeSymbol? ConfiguredCancelableAsyncEnumerableSymbol { get; } = compilation.GetBestTypeByMetadataName("System.Runtime.CompilerServices.ConfiguredCancelableAsyncEnumerable`1");
9494
private INamedTypeSymbol? EnumeratorCancellationAttributeSymbol { get; } = compilation.GetBestTypeByMetadataName("System.Runtime.CompilerServices.EnumeratorCancellationAttribute");
95+
private INamedTypeSymbol? XunitTestContextSymbol { get; } = compilation.GetBestTypeByMetadataName("Xunit.TestContext");
9596

9697
private bool HasExplicitCancellationTokenArgument(IInvocationOperation operation)
9798
{
@@ -179,7 +180,7 @@ public void AnalyzeInvocation(OperationAnalysisContext context)
179180
if (parentMethod is not null && parentMethod.IsOverrideOrInterfaceImplementation())
180181
return;
181182

182-
context.ReportDiagnostic(UseAnOverloadThatHasCancellationTokenRule, CreateProperties(availableCancellationTokens, parameterInfo), operation, string.Join(", ", availableCancellationTokens));
183+
context.ReportDiagnostic(UseAnOverloadThatHasCancellationTokenRule, CreateProperties(availableCancellationTokens, parameterInfo), operation);
183184
}
184185
}
185186

@@ -309,7 +310,9 @@ public void AnalyzeLoop(OperationAnalysisContext context)
309310

310311
private string[] FindCancellationTokens(IOperation operation, CancellationToken cancellationToken)
311312
{
313+
312314
var availableSymbols = new List<NameAndType>();
315+
313316
foreach (var symbol in operation.LookupAvailableSymbols(cancellationToken))
314317
{
315318
var symbolType = symbol.GetSymbolType();
@@ -319,7 +322,7 @@ private string[] FindCancellationTokens(IOperation operation, CancellationToken
319322
availableSymbols.Add(new(symbol.Name, symbolType));
320323
}
321324

322-
if (availableSymbols.Count == 0)
325+
if (availableSymbols.Count == 0 && XunitTestContextSymbol is null)
323326
return [];
324327

325328
var isInStaticContext = operation.IsInStaticContext(cancellationToken);
@@ -348,6 +351,11 @@ private string[] FindCancellationTokens(IOperation operation, CancellationToken
348351
}
349352
}
350353

354+
if (XunitTestContextSymbol != null)
355+
{
356+
paths.Add("Xunit.TestContext.Current.CancellationToken");
357+
}
358+
351359
if (paths.Count == 0)
352360
return [];
353361

tests/Meziantou.Analyzer.Test/Helpers/ProjectBuilder.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,16 @@ async Task<string[]> Download()
7373
using var stream = await SharedHttpClient.Instance.GetStreamAsync(new Uri($"https://www.nuget.org/api/v2/package/{packageName}/{version}")).ConfigureAwait(false);
7474
using var zip = new ZipArchive(stream, ZipArchiveMode.Read);
7575

76+
var hasEntry = false;
7677
foreach (var entry in zip.Entries.Where(file => paths.Any(path => file.FullName.StartsWith(path, StringComparison.Ordinal))))
7778
{
7879
entry.ExtractToFile(Path.Combine(tempFolder, entry.Name), overwrite: true);
80+
hasEntry = true;
81+
}
82+
83+
if (!hasEntry)
84+
{
85+
throw new InvalidOperationException("The NuGet package " + packageName + "@" + version + " does not contain any file matching the paths " + string.Join(", ", paths));
7986
}
8087

8188
try

tests/Meziantou.Analyzer.Test/Rules/UseAnOverloadThatHasCancellationTokenAnalyzerTests.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1228,4 +1228,47 @@ class Sample
12281228
.WithOutputKind(Microsoft.CodeAnalysis.OutputKind.ConsoleApplication)
12291229
.ValidateAsync();
12301230
}
1231+
1232+
[Fact]
1233+
public async Task Xunit2()
1234+
{
1235+
await CreateProjectBuilder()
1236+
.AddNuGetReference("xunit.abstractions", "2.0.3", "lib/netstandard2.0/")
1237+
.WithSourceCode("""
1238+
using System.Threading;
1239+
using System.Threading.Tasks;
1240+
1241+
{|MA0032:Sample.Repro()|};
1242+
1243+
class Sample
1244+
{
1245+
public static void Repro() => throw null;
1246+
public static void Repro(CancellationToken cancellationToken) => throw null;
1247+
}
1248+
""")
1249+
.WithOutputKind(Microsoft.CodeAnalysis.OutputKind.ConsoleApplication)
1250+
.ValidateAsync();
1251+
}
1252+
1253+
[Fact]
1254+
public async Task Xunit3()
1255+
{
1256+
await CreateProjectBuilder()
1257+
.AddNuGetReference("xunit.v3.extensibility.core", "1.0.0", "lib/netstandard2.0/")
1258+
.WithSourceCode("""
1259+
using System.Threading;
1260+
using System.Threading.Tasks;
1261+
1262+
{|MA0040:Sample.Repro()|};
1263+
1264+
class Sample
1265+
{
1266+
public static void Repro() => throw null;
1267+
public static void Repro(CancellationToken cancellationToken) => throw null;
1268+
}
1269+
""")
1270+
.WithOutputKind(Microsoft.CodeAnalysis.OutputKind.ConsoleApplication)
1271+
.ShouldReportDiagnosticWithMessage("Use an overload with a CancellationToken, available tokens: Xunit.TestContext.Current.CancellationToken")
1272+
.ValidateAsync();
1273+
}
12311274
}

0 commit comments

Comments
 (0)