diff --git a/ChangeLog.md b/ChangeLog.md index 6df918b2c2..1c957d8282 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix enum contained flags check for partial matches in [RCS1258](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1258) ([PR](https://github.com/dotnet/roslynator/pull/1740) by @ovska) - Fix analyzer [RCS1146](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1146) ([PR](https://github.com/dotnet/roslynator/pull/1747)) - Fix analyzer [RCS1194](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1194) ([PR](https://github.com/dotnet/roslynator/pull/1733)) +- Fix analyzer [RCS1077](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1077) ([PR](https://github.com/dotnet/roslynator/pull/1742) by @MattFromRVA) - [CLI] Fix `fix` command ignoring `--include` / `--exclude` file filter ([PR](https://github.com/dotnet/roslynator/pull/1758) by @hashiiiii) ## [4.15.0] - 2025-12-14 diff --git a/src/Analyzers.CodeFixes/CSharp/CodeFixes/OptimizeLinqMethodCallCodeFixProvider.cs b/src/Analyzers.CodeFixes/CSharp/CodeFixes/OptimizeLinqMethodCallCodeFixProvider.cs index 42226365b3..c6154602fd 100644 --- a/src/Analyzers.CodeFixes/CSharp/CodeFixes/OptimizeLinqMethodCallCodeFixProvider.cs +++ b/src/Analyzers.CodeFixes/CSharp/CodeFixes/OptimizeLinqMethodCallCodeFixProvider.cs @@ -587,11 +587,26 @@ private static Task CallConvertAllInsteadOfSelectAsync( in SimpleMemberInvocationExpressionInfo invocationInfo, CancellationToken cancellationToken) { - InvocationExpressionSyntax invocationExpression2 = SimpleMemberInvocationExpressionInfo(invocationInfo.Expression).InvocationExpression; + InvocationExpressionSyntax toListInvocation = invocationInfo.InvocationExpression; - InvocationExpressionSyntax newInvocationExpression = ChangeInvokedMethodName(invocationExpression2, "ConvertAll"); + InvocationExpressionSyntax selectInvocation = SimpleMemberInvocationExpressionInfo(invocationInfo.Expression).InvocationExpression; - return document.ReplaceNodeAsync(invocationInfo.InvocationExpression, newInvocationExpression, cancellationToken); + InvocationExpressionSyntax newInvocationExpression = ChangeInvokedMethodName(selectInvocation, "ConvertAll"); + + IEnumerable removedTrivia = toListInvocation.DescendantTrivia( + TextSpan.FromBounds(selectInvocation.Span.End, toListInvocation.Span.End)); + + if (removedTrivia.Any(f => !f.IsWhitespaceOrEndOfLineTrivia())) + { + newInvocationExpression = newInvocationExpression.WithTrailingTrivia( + removedTrivia.Where(f => !f.IsWhitespaceOrEndOfLineTrivia()).Concat(toListInvocation.GetTrailingTrivia())); + } + else + { + newInvocationExpression = newInvocationExpression.WithTrailingTrivia(toListInvocation.GetTrailingTrivia()); + } + + return document.ReplaceNodeAsync(toListInvocation, newInvocationExpression, cancellationToken); } private static Task CallSumInsteadOfSelectManyAndCountAsync( diff --git a/src/Tests/Analyzers.Tests/RCS1077OptimizeLinqMethodCallTests.cs b/src/Tests/Analyzers.Tests/RCS1077OptimizeLinqMethodCallTests.cs index e78825022b..1707f86ca8 100644 --- a/src/Tests/Analyzers.Tests/RCS1077OptimizeLinqMethodCallTests.cs +++ b/src/Tests/Analyzers.Tests/RCS1077OptimizeLinqMethodCallTests.cs @@ -1551,6 +1551,111 @@ void M() var x = q.OrderBy(f => f); } } +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.OptimizeLinqMethodCall)] + public async Task Test_CallConvertAllInsteadOfSelectAndToList_List_SemicolonNotMoved() + { + await VerifyDiagnosticAndFixAsync(@" +using System; +using System.Collections.Generic; +using System.Linq; + +class C +{ + void M(Action> action, List items) + { + action(() => + { + List x = items + .[|Select(f => f.ToString()) + .ToList()|]; + + return x; + }); + } +} +", @" +using System; +using System.Collections.Generic; +using System.Linq; + +class C +{ + void M(Action> action, List items) + { + action(() => + { + List x = items + .ConvertAll(f => f.ToString()); + + return x; + }); + } +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.OptimizeLinqMethodCall)] + public async Task Test_CallConvertAllInsteadOfSelectAndToList_PreservesCommentInRemovedSegment() + { + await VerifyDiagnosticAndFixAsync(@" +using System.Collections.Generic; +using System.Linq; + +class C +{ + void M(List items) + { + var x = items + .[|Select(f => f.ToString()) + /* comment in removed segment */.ToList()|]; + } +} +", @" +using System.Collections.Generic; +using System.Linq; + +class C +{ + void M(List items) + { + var x = items + .ConvertAll(f => f.ToString())/* comment in removed segment */; + } +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.OptimizeLinqMethodCall)] + public async Task Test_CallConvertAllInsteadOfSelectAndToList_PreservesTrailingCommentAfterStatement() + { + await VerifyDiagnosticAndFixAsync(@" +using System.Collections.Generic; +using System.Linq; + +class C +{ + void M(List items) + { + var x = items + .[|Select(f => f.ToString()) + .ToList()|]; // trailing comment + } +} +", @" +using System.Collections.Generic; +using System.Linq; + +class C +{ + void M(List items) + { + var x = items + .ConvertAll(f => f.ToString()); // trailing comment + } +} "); } }