diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Analyzer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Analyzer.cs index e20bb81a26..0bd93e4ca0 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Analyzer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Analyzer.cs @@ -68,14 +68,16 @@ public override void Initialize(AnalysisContext context) private void AnalyzeCompilationStart(CompilationStartAnalysisContext context) { - if (!context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingCancellationToken, out INamedTypeSymbol? cancellationTokenType)) + var typeProvider = WellKnownTypeProvider.GetOrCreate(context.Compilation); + if (!typeProvider.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingCancellationToken, out INamedTypeSymbol? cancellationTokenType) + || !typeProvider.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemObsoleteAttribute, out INamedTypeSymbol? obsoleteAttribute)) { return; } // We don't care if these symbols are not defined in our compilation. They are used to special case the Task <-> ValueTask logic - context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingTasksTask1, out INamedTypeSymbol? genericTask); - context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingTasksValueTask1, out INamedTypeSymbol? genericValueTask); + typeProvider.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingTasksTask1, out INamedTypeSymbol? genericTask); + typeProvider.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingTasksValueTask1, out INamedTypeSymbol? genericValueTask); context.RegisterOperationAction(context => { @@ -93,6 +95,7 @@ private void AnalyzeCompilationStart(CompilationStartAnalysisContext context) cancellationTokenType, genericTask, genericValueTask, + obsoleteAttribute, out int shouldFix, out string? cancellationTokenArgumentName, out string? invocationTokenParameterName)) @@ -125,6 +128,7 @@ private bool ShouldDiagnose( INamedTypeSymbol cancellationTokenType, INamedTypeSymbol? genericTask, INamedTypeSymbol? genericValueTask, + INamedTypeSymbol obsoleteAttribute, out int shouldFix, [NotNullWhen(returnValue: true)] out string? ancestorTokenParameterName, out string? invocationTokenParameterName) { shouldFix = 1; @@ -152,7 +156,7 @@ private bool ShouldDiagnose( } } // or an overload that takes a ct at the end - else if (MethodHasCancellationTokenOverload(compilation, method, cancellationTokenType, genericTask, genericValueTask, out overload)) + else if (MethodHasCancellationTokenOverload(compilation, method, cancellationTokenType, genericTask, genericValueTask, obsoleteAttribute, out overload)) { if (ArgumentsImplicitOrNamed(cancellationTokenType, invocation.Arguments)) { @@ -334,13 +338,14 @@ private static bool MethodHasCancellationTokenOverload( ITypeSymbol cancellationTokenType, INamedTypeSymbol? genericTask, INamedTypeSymbol? genericValueTask, + INamedTypeSymbol obsoleteAttribute, [NotNullWhen(returnValue: true)] out IMethodSymbol? overload) { overload = method.ContainingType - .GetMembers(method.Name) - .OfType() - .FirstOrDefault(methodToCompare => - HasSameParametersPlusCancellationToken(compilation, cancellationTokenType, genericTask, genericValueTask, method, methodToCompare)); + .GetMembers(method.Name) + .OfType() + .FirstOrDefault(methodToCompare => !methodToCompare.HasAnyAttribute(obsoleteAttribute) + && HasSameParametersPlusCancellationToken(compilation, cancellationTokenType, genericTask, genericValueTask, method, methodToCompare)); return overload != null; diff --git a/src/NetAnalyzers/RulesMissingDocumentation.md b/src/NetAnalyzers/RulesMissingDocumentation.md index 50fa5fb52f..42df726b08 100644 --- a/src/NetAnalyzers/RulesMissingDocumentation.md +++ b/src/NetAnalyzers/RulesMissingDocumentation.md @@ -13,6 +13,5 @@ CA1863 | | Use char overload | CA1866 | | Use char overload | CA1867 | | Use char overload | -CA1868 | | Unnecessary call to 'Contains(item)' | CA2021 | | Do not call Enumerable.Cast\ or Enumerable.OfType\ with incompatible types | CA2261 | | Do not use ConfigureAwaitOptions.SuppressThrowing with Task\ | diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs index de0b3e43fa..cd89401402 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs @@ -478,6 +478,28 @@ static void M1(string s, CancellationToken cancellationToken, __arglist) await VerifyCS.VerifyCodeFixAsync(source, source); } + [Fact] + [WorkItem(6819, "https://github.com/dotnet/roslyn-analyzers/issues/6819")] + public Task ObsoleteOverload() + { + return VerifyCS.VerifyAnalyzerAsync(@" +using System; +using System.Threading; + +class Test +{ + public void Main(CancellationToken token) + { + Run(); + } + + public void Run() {} + + [Obsolete] + public void Run(CancellationToken token) {} +}"); + } + #endregion #region Diagnostics with no fix = C#