From aa97561ae7c605d8427a48676a3b0a724f9e28ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9rald=20Barr=C3=A9?= Date: Sat, 21 Dec 2024 22:28:58 -0500 Subject: [PATCH] Find token from Xunit.TestContext.Current.CancellationToken --- ...verloadThatHasCancellationTokenAnalyzer.cs | 12 +++++- .../Helpers/ProjectBuilder.cs | 7 +++ ...adThatHasCancellationTokenAnalyzerTests.cs | 43 +++++++++++++++++++ 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/src/Meziantou.Analyzer/Rules/UseAnOverloadThatHasCancellationTokenAnalyzer.cs b/src/Meziantou.Analyzer/Rules/UseAnOverloadThatHasCancellationTokenAnalyzer.cs index df6407af..e5660dc0 100644 --- a/src/Meziantou.Analyzer/Rules/UseAnOverloadThatHasCancellationTokenAnalyzer.cs +++ b/src/Meziantou.Analyzer/Rules/UseAnOverloadThatHasCancellationTokenAnalyzer.cs @@ -92,6 +92,7 @@ private sealed class AnalyzerContext(Compilation compilation) private INamedTypeSymbol? TaskOfTSymbol { get; } = compilation.GetBestTypeByMetadataName("System.Threading.Tasks.Task`1"); private INamedTypeSymbol? ConfiguredCancelableAsyncEnumerableSymbol { get; } = compilation.GetBestTypeByMetadataName("System.Runtime.CompilerServices.ConfiguredCancelableAsyncEnumerable`1"); private INamedTypeSymbol? EnumeratorCancellationAttributeSymbol { get; } = compilation.GetBestTypeByMetadataName("System.Runtime.CompilerServices.EnumeratorCancellationAttribute"); + private INamedTypeSymbol? XunitTestContextSymbol { get; } = compilation.GetBestTypeByMetadataName("Xunit.TestContext"); private bool HasExplicitCancellationTokenArgument(IInvocationOperation operation) { @@ -179,7 +180,7 @@ public void AnalyzeInvocation(OperationAnalysisContext context) if (parentMethod is not null && parentMethod.IsOverrideOrInterfaceImplementation()) return; - context.ReportDiagnostic(UseAnOverloadThatHasCancellationTokenRule, CreateProperties(availableCancellationTokens, parameterInfo), operation, string.Join(", ", availableCancellationTokens)); + context.ReportDiagnostic(UseAnOverloadThatHasCancellationTokenRule, CreateProperties(availableCancellationTokens, parameterInfo), operation); } } @@ -309,7 +310,9 @@ public void AnalyzeLoop(OperationAnalysisContext context) private string[] FindCancellationTokens(IOperation operation, CancellationToken cancellationToken) { + var availableSymbols = new List(); + foreach (var symbol in operation.LookupAvailableSymbols(cancellationToken)) { var symbolType = symbol.GetSymbolType(); @@ -319,7 +322,7 @@ private string[] FindCancellationTokens(IOperation operation, CancellationToken availableSymbols.Add(new(symbol.Name, symbolType)); } - if (availableSymbols.Count == 0) + if (availableSymbols.Count == 0 && XunitTestContextSymbol is null) return []; var isInStaticContext = operation.IsInStaticContext(cancellationToken); @@ -348,6 +351,11 @@ private string[] FindCancellationTokens(IOperation operation, CancellationToken } } + if (XunitTestContextSymbol != null) + { + paths.Add("Xunit.TestContext.Current.CancellationToken"); + } + if (paths.Count == 0) return []; diff --git a/tests/Meziantou.Analyzer.Test/Helpers/ProjectBuilder.cs b/tests/Meziantou.Analyzer.Test/Helpers/ProjectBuilder.cs index cfdedc94..e78b4e22 100755 --- a/tests/Meziantou.Analyzer.Test/Helpers/ProjectBuilder.cs +++ b/tests/Meziantou.Analyzer.Test/Helpers/ProjectBuilder.cs @@ -73,9 +73,16 @@ async Task Download() using var stream = await SharedHttpClient.Instance.GetStreamAsync(new Uri($"https://www.nuget.org/api/v2/package/{packageName}/{version}")).ConfigureAwait(false); using var zip = new ZipArchive(stream, ZipArchiveMode.Read); + var hasEntry = false; foreach (var entry in zip.Entries.Where(file => paths.Any(path => file.FullName.StartsWith(path, StringComparison.Ordinal)))) { entry.ExtractToFile(Path.Combine(tempFolder, entry.Name), overwrite: true); + hasEntry = true; + } + + if (!hasEntry) + { + throw new InvalidOperationException("The NuGet package " + packageName + "@" + version + " does not contain any file matching the paths " + string.Join(", ", paths)); } try diff --git a/tests/Meziantou.Analyzer.Test/Rules/UseAnOverloadThatHasCancellationTokenAnalyzerTests.cs b/tests/Meziantou.Analyzer.Test/Rules/UseAnOverloadThatHasCancellationTokenAnalyzerTests.cs index 136f7efe..2ff8af19 100644 --- a/tests/Meziantou.Analyzer.Test/Rules/UseAnOverloadThatHasCancellationTokenAnalyzerTests.cs +++ b/tests/Meziantou.Analyzer.Test/Rules/UseAnOverloadThatHasCancellationTokenAnalyzerTests.cs @@ -1228,4 +1228,47 @@ class Sample .WithOutputKind(Microsoft.CodeAnalysis.OutputKind.ConsoleApplication) .ValidateAsync(); } + + [Fact] + public async Task Xunit2() + { + await CreateProjectBuilder() + .AddNuGetReference("xunit.abstractions", "2.0.3", "lib/netstandard2.0/") + .WithSourceCode(""" + using System.Threading; + using System.Threading.Tasks; + + {|MA0032:Sample.Repro()|}; + + class Sample + { + public static void Repro() => throw null; + public static void Repro(CancellationToken cancellationToken) => throw null; + } + """) + .WithOutputKind(Microsoft.CodeAnalysis.OutputKind.ConsoleApplication) + .ValidateAsync(); + } + + [Fact] + public async Task Xunit3() + { + await CreateProjectBuilder() + .AddNuGetReference("xunit.v3.extensibility.core", "1.0.0", "lib/netstandard2.0/") + .WithSourceCode(""" + using System.Threading; + using System.Threading.Tasks; + + {|MA0040:Sample.Repro()|}; + + class Sample + { + public static void Repro() => throw null; + public static void Repro(CancellationToken cancellationToken) => throw null; + } + """) + .WithOutputKind(Microsoft.CodeAnalysis.OutputKind.ConsoleApplication) + .ShouldReportDiagnosticWithMessage("Use an overload with a CancellationToken, available tokens: Xunit.TestContext.Current.CancellationToken") + .ValidateAsync(); + } }