From 7f1858aeae1a32ed89c795d7faa64b8a728ebd95 Mon Sep 17 00:00:00 2001 From: Josef Pihrt Date: Sun, 9 Aug 2026 17:37:01 +0200 Subject: [PATCH 1/3] fix(analyzers): skip RCS1231 for sync Task-returning methods Fixes #1702 Co-authored-by: Cursor --- ChangeLog.md | 1 + .../Analysis/RefReadOnlyParameterAnalyzer.cs | 10 ++++++++++ .../RCS1231MakeParameterRefReadOnlyTests.cs | 17 +++++++++++++++++ 3 files changed, 28 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index 0291904a0d..fada11a885 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix analyzer [RCS1265](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1265) to not report catch clauses with a `when` filter ([PR](https://github.com/dotnet/roslynator/pull/1789)) - Fix analyzer [RCS0034](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS0034) for types with a primary constructor and multiple constraint clauses ([PR](https://github.com/dotnet/roslynator/pull/1791)) - [CLI] Fix GitLab output format to use relative paths, forward slashes, and 1-based line numbers ([PR](https://github.com/dotnet/roslynator/pull/1792)) +- Fix analyzer [RCS1231](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1231) to not report `CancellationToken` in sync methods returning `Task` ## [4.16.0] - 2026-08-08 diff --git a/src/Analyzers/CSharp/Analysis/RefReadOnlyParameterAnalyzer.cs b/src/Analyzers/CSharp/Analysis/RefReadOnlyParameterAnalyzer.cs index ea3c75993e..b355b12598 100644 --- a/src/Analyzers/CSharp/Analysis/RefReadOnlyParameterAnalyzer.cs +++ b/src/Analyzers/CSharp/Analysis/RefReadOnlyParameterAnalyzer.cs @@ -59,6 +59,11 @@ private static void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context) if (methodDeclaration.Modifiers.ContainsAny(SyntaxKind.AsyncKeyword, SyntaxKind.OverrideKeyword)) return; + IMethodSymbol methodSymbol = context.SemanticModel.GetDeclaredSymbol(methodDeclaration, context.CancellationToken); + + if (methodSymbol?.ReturnType.IsWellKnownTaskType() == true) + return; + Analyze(context, methodDeclaration, methodDeclaration.ParameterList, methodDeclaration.BodyOrExpressionBody()); } @@ -90,6 +95,11 @@ private static void AnalyzeLocalFunction(SyntaxNodeAnalysisContext context) if (localFunction.Modifiers.Contains(SyntaxKind.AsyncKeyword)) return; + IMethodSymbol methodSymbol = context.SemanticModel.GetDeclaredSymbol(localFunction, context.CancellationToken); + + if (methodSymbol?.ReturnType.IsWellKnownTaskType() == true) + return; + Analyze(context, localFunction, localFunction.ParameterList, localFunction.BodyOrExpressionBody()); } diff --git a/src/Tests/Analyzers.Tests/RCS1231MakeParameterRefReadOnlyTests.cs b/src/Tests/Analyzers.Tests/RCS1231MakeParameterRefReadOnlyTests.cs index 78fa8714af..648d126fe2 100644 --- a/src/Tests/Analyzers.Tests/RCS1231MakeParameterRefReadOnlyTests.cs +++ b/src/Tests/Analyzers.Tests/RCS1231MakeParameterRefReadOnlyTests.cs @@ -215,6 +215,23 @@ public void M(DateTime dt) public DateTime P { get; set; } } +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.MakeParameterRefReadOnly)] + public async Task TestNoDiagnostic_CancellationToken_SyncTaskReturningMethod() + { + await VerifyNoDiagnosticAsync(@" +using System.Threading; +using System.Threading.Tasks; + +class C +{ + public Task DoThatWay(CancellationToken cancellationToken) + { + return Task.FromCanceled(cancellationToken); + } +} "); } } From bb1a5603e5ab064b4939cfe18076f3396acb2b2a Mon Sep 17 00:00:00 2001 From: Josef Pihrt Date: Sun, 9 Aug 2026 17:37:12 +0200 Subject: [PATCH 2/3] chore: add PR link to changelog entry Co-authored-by: Cursor --- ChangeLog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index fada11a885..5bf4557f7c 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -13,7 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix analyzer [RCS1265](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1265) to not report catch clauses with a `when` filter ([PR](https://github.com/dotnet/roslynator/pull/1789)) - Fix analyzer [RCS0034](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS0034) for types with a primary constructor and multiple constraint clauses ([PR](https://github.com/dotnet/roslynator/pull/1791)) - [CLI] Fix GitLab output format to use relative paths, forward slashes, and 1-based line numbers ([PR](https://github.com/dotnet/roslynator/pull/1792)) -- Fix analyzer [RCS1231](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1231) to not report `CancellationToken` in sync methods returning `Task` +- Fix analyzer [RCS1231](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1231) to not report `CancellationToken` in sync methods returning `Task` ([PR](https://github.com/dotnet/roslynator/pull/1802)) ## [4.16.0] - 2026-08-08 From 5ff568291dc6a01e52e8500500cb56ebf4da9e05 Mon Sep 17 00:00:00 2001 From: Josef Pihrt Date: Fri, 14 Aug 2026 19:34:51 +0200 Subject: [PATCH 3/3] fix(analyzers): skip RCS1231 only for CancellationToken on sync Task methods Keep suggesting `in` for other readonly structs on Task-returning methods, and reuse the existing GetDeclaredSymbol lookup in Analyze. Co-authored-by: Cursor --- .../Analysis/RefReadOnlyParameterAnalyzer.cs | 16 +++++------- .../RCS1231MakeParameterRefReadOnlyTests.cs | 26 +++++++++++++++++++ 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/src/Analyzers/CSharp/Analysis/RefReadOnlyParameterAnalyzer.cs b/src/Analyzers/CSharp/Analysis/RefReadOnlyParameterAnalyzer.cs index b355b12598..74e6bf6cd7 100644 --- a/src/Analyzers/CSharp/Analysis/RefReadOnlyParameterAnalyzer.cs +++ b/src/Analyzers/CSharp/Analysis/RefReadOnlyParameterAnalyzer.cs @@ -59,11 +59,6 @@ private static void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context) if (methodDeclaration.Modifiers.ContainsAny(SyntaxKind.AsyncKeyword, SyntaxKind.OverrideKeyword)) return; - IMethodSymbol methodSymbol = context.SemanticModel.GetDeclaredSymbol(methodDeclaration, context.CancellationToken); - - if (methodSymbol?.ReturnType.IsWellKnownTaskType() == true) - return; - Analyze(context, methodDeclaration, methodDeclaration.ParameterList, methodDeclaration.BodyOrExpressionBody()); } @@ -95,11 +90,6 @@ private static void AnalyzeLocalFunction(SyntaxNodeAnalysisContext context) if (localFunction.Modifiers.Contains(SyntaxKind.AsyncKeyword)) return; - IMethodSymbol methodSymbol = context.SemanticModel.GetDeclaredSymbol(localFunction, context.CancellationToken); - - if (methodSymbol?.ReturnType.IsWellKnownTaskType() == true) - return; - Analyze(context, localFunction, localFunction.ParameterList, localFunction.BodyOrExpressionBody()); } @@ -152,6 +142,12 @@ private static void Analyze( continue; } + if (type.HasMetadataName(in MetadataNames.System_Threading_CancellationToken) + && methodSymbol.ReturnType.IsWellKnownTaskType()) + { + continue; + } + if (parameter.RefKind != RefKind.None) continue; diff --git a/src/Tests/Analyzers.Tests/RCS1231MakeParameterRefReadOnlyTests.cs b/src/Tests/Analyzers.Tests/RCS1231MakeParameterRefReadOnlyTests.cs index 648d126fe2..56a41deac0 100644 --- a/src/Tests/Analyzers.Tests/RCS1231MakeParameterRefReadOnlyTests.cs +++ b/src/Tests/Analyzers.Tests/RCS1231MakeParameterRefReadOnlyTests.cs @@ -232,6 +232,32 @@ public Task DoThatWay(CancellationToken cancellationToken) return Task.FromCanceled(cancellationToken); } } +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.MakeParameterRefReadOnly)] + public async Task Test_ReadOnlyStruct_SyncTaskReturningMethod() + { + await VerifyDiagnosticAndFixAsync(@" +using System.Threading.Tasks; + +readonly struct C +{ + public Task M(C [|c|]) + { + return Task.FromResult(c.GetHashCode()); + } +} +", @" +using System.Threading.Tasks; + +readonly struct C +{ + public Task M(in C c) + { + return Task.FromResult(c.GetHashCode()); + } +} "); } }