Skip to content

Commit 9335c71

Browse files
Implement analysis and fix for OrderBy(x => x) to Order() (#1522)
1 parent e5cfbdb commit 9335c71

File tree

5 files changed

+112
-16
lines changed

5 files changed

+112
-16
lines changed

ChangeLog.md

+14-10
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- Analyzer [RCS1077](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1077) now suggests to use `Order` instead of `OrderBy` ([PR](https://github.com/dotnet/roslynator/pull/1522))
13+
1014
### Fixed
1115

1216
- Fix analyzer [RCS0053](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS0053) ([PR](https://github.com/dotnet/roslynator/pull/1518))
@@ -112,12 +116,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
112116
- These packages are recommended to be used in an environment where Roslynator IDE extension cannot be used, e.g. VS Code + C# Dev Kit (see related [issue](https://github.com/dotnet/vscode-csharp/issues/6790))
113117
- Add analyzer "Remove redundant catch block" [RCS1265](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1265) ([PR](https://github.com/dotnet/roslynator/pull/1364) by @jakubreznak)
114118
- [CLI] Spellcheck file names ([PR](https://github.com/dotnet/roslynator/pull/1368))
115-
- `roslynator spellcheck --scope file-name`
119+
- `roslynator spellcheck --scope file-name`
116120

117121
### Changed
118122

119123
- Update analyzer [RCS1197](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1197) ([PR](https://github.com/dotnet/roslynator/pull/1370))
120-
- Do not report interpolated string and string concatenation
124+
- Do not report interpolated string and string concatenation
121125

122126
### Fixed
123127

@@ -181,7 +185,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
181185
- Add analyzer "Unnecessary raw string literal" ([RCS1262](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1262)) ([PR](https://github.com/dotnet/roslynator/pull/1293))
182186
- Add analyzer "Invalid reference in a documentation comment" ([RCS1263](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1263)) ([PR](https://github.com/dotnet/roslynator/pull/1295))
183187
- Add analyzer "Add/remove blank line between switch sections" ([RCS0061](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS0061)) ([PR](https://github.com/dotnet/roslynator/pull/1302))
184-
- Option (required): `roslynator_blank_line_between_switch_sections = include|omit|omit_after_block`
188+
- Option (required): `roslynator_blank_line_between_switch_sections = include|omit|omit_after_block`
185189
- Make analyzer [RCS0014](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS0014) obsolete
186190

187191
### Changed
@@ -271,7 +275,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
271275
- Update logo ([PR](https://github.com/dotnet/roslynator/pull/1208), [PR](https://github.com/dotnet/roslynator/pull/1210)).
272276
- Migrate to .NET Foundation ([PR](https://github.com/dotnet/roslynator/pull/1206), [PR](https://github.com/dotnet/roslynator/pull/1207), [PR](https://github.com/dotnet/roslynator/pull/1219)).
273277
- Bump Roslyn to 4.7.0 ([PR](https://github.com/dotnet/roslynator/pull/1218)).
274-
- Applies to CLI and testing library.
278+
- Applies to CLI and testing library.
275279
- Bump Microsoft.Build.Locator to 1.6.1 ([PR](https://github.com/dotnet/roslynator/pull/1194))
276280
- Improve testing framework ([PR](https://github.com/dotnet/roslynator/pull/1214))
277281
- Add methods to `DiagnosticVerifier`, `RefactoringVerifier` and `CompilerDiagnosticFixVerifier`.
@@ -611,7 +615,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
611615
### 3.0.0 (2020-06-16)
612616

613617
* Update references to Roslyn API to 3.5.0
614-
* Release .NET Core Global Tool [Roslynator.DotNet.Cli](https://www.nuget.org/packages/roslynator.dotnet.cli)
618+
* Release .NET Core Global Tool [Roslynator.DotNet.Cli](https://www.nuget.org/packages/roslynator.dotnet.cli)
615619
* Introduce concept of "[Analyzer Options](https://github.com/JosefPihrt/Roslynator/blob/main/docs/AnalyzerOptions)"
616620
* Reassign ID for some analyzers.
617621
* See "[How to: Migrate Analyzers to Version 3.0](https://github.com/JosefPihrt/Roslynator/blob/main/docs/HowToMigrateAnalyzersToVersion3)"
@@ -2170,13 +2174,13 @@ Code fixes has been added for the following compiler diagnostics:
21702174
* Bug fixed in **"Uncomment"** refactoring
21712175

21722176
### 0.9.11 (2016-04-30)
2173-
2177+
21742178
* Bug fixes and minor improvements
2175-
2179+
21762180
### 0.9.1 (2016-04-27)
2177-
2181+
21782182
* Bug fixes
2179-
2183+
21802184
### 0.9.0 (2016-04-26)
2181-
2185+
21822186
* Initial release

src/Analyzers.CodeFixes/CSharp/CodeFixes/OptimizeLinqMethodCallCodeFixProvider.cs

+22
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,16 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
267267
ct => UseElementAccessInsteadOfEnumerableMethodRefactoring.UseElementAccessInsteadOfLastAsync(document, invocation, ct),
268268
GetEquivalenceKey(diagnostic, "UseElementAccessInsteadOfLast"));
269269

270+
context.RegisterCodeFix(codeAction, diagnostic);
271+
return;
272+
}
273+
case "OrderBy":
274+
{
275+
CodeAction codeAction = CodeAction.Create(
276+
"Call 'Order' instead of 'OrderBy'",
277+
ct => CallOrderInsteadOfOrderByIdentityAsync(document, invocationInfo, ct),
278+
GetEquivalenceKey(diagnostic, "CallOrderInsteadOfOrderByIdentity"));
279+
270280
context.RegisterCodeFix(codeAction, diagnostic);
271281
return;
272282
}
@@ -565,6 +575,18 @@ private static Task<Document> CallOrderByDescendingInsteadOfOrderByAndReverseAsy
565575
return document.ReplaceNodeAsync(invocationInfo.InvocationExpression, newInvocationExpression, cancellationToken);
566576
}
567577

578+
private static Task<Document> CallOrderInsteadOfOrderByIdentityAsync(
579+
Document document,
580+
in SimpleMemberInvocationExpressionInfo invocationInfo,
581+
CancellationToken cancellationToken)
582+
{
583+
InvocationExpressionSyntax newInvocationExpression = ChangeInvokedMethodName(invocationInfo.InvocationExpression, "Order");
584+
585+
newInvocationExpression = newInvocationExpression.WithArgumentList(newInvocationExpression.ArgumentList.WithArguments(SeparatedList<ArgumentSyntax>()));
586+
587+
return document.ReplaceNodeAsync(invocationInfo.InvocationExpression, newInvocationExpression, cancellationToken);
588+
}
589+
568590
private static Task<Document> CallOrderByAndWhereInReverseOrderAsync(
569591
Document document,
570592
in SimpleMemberInvocationExpressionInfo invocationInfo,

src/Analyzers/CSharp/Analysis/InvocationExpressionAnalyzer.cs

+3
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,9 @@ private static void AnalyzeInvocationExpression(SyntaxNodeAnalysisContext contex
331331
if (DiagnosticRules.CallThenByInsteadOfOrderBy.IsEffective(context))
332332
CallThenByInsteadOfOrderByAnalysis.Analyze(context, invocationInfo);
333333

334+
if (DiagnosticRules.OptimizeLinqMethodCall.IsEffective(context))
335+
OptimizeLinqMethodCallAnalysis.AnalyzeOrderByIdentity(context, invocationInfo);
336+
334337
break;
335338
}
336339
}

src/Analyzers/CSharp/Analysis/OptimizeLinqMethodCallAnalysis.cs

+32
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,38 @@ public static void AnalyzeOrderByAndReverse(SyntaxNodeAnalysisContext context, i
690690
Report(context, invocationExpression, span, checkDirectives: true);
691691
}
692692

693+
// x.OrderBy(f => f) >>> x.Order()
694+
public static void AnalyzeOrderByIdentity(SyntaxNodeAnalysisContext context, in SimpleMemberInvocationExpressionInfo invocationInfo)
695+
{
696+
InvocationExpressionSyntax invocationExpression = invocationInfo.InvocationExpression;
697+
698+
IMethodSymbol orderMethod = context.SemanticModel
699+
.GetSymbolInfo(invocationExpression)
700+
.Symbol
701+
.ContainingType
702+
.FindMember<IMethodSymbol>(method => method.Name == "Order" && method.Parameters.Length is 1);
703+
704+
if (orderMethod is null)
705+
return;
706+
707+
ArgumentSyntax argument = invocationInfo.Arguments.SingleOrDefault(shouldThrow: false);
708+
709+
if (argument is null)
710+
return;
711+
712+
if (!string.Equals(invocationInfo.NameText, "OrderBy", StringComparison.Ordinal))
713+
return;
714+
715+
if (argument.Expression is not SimpleLambdaExpressionSyntax lambdaExpression)
716+
return;
717+
718+
if (lambdaExpression.Body is not IdentifierNameSyntax identifier || identifier.Identifier.Text != lambdaExpression.Parameter.Identifier.Text)
719+
return;
720+
721+
TextSpan span = TextSpan.FromBounds(invocationInfo.Name.SpanStart, invocationExpression.Span.End);
722+
Report(context, invocationExpression, span, checkDirectives: true);
723+
}
724+
693725
// x.SelectMany(f => f).Count() >>> x.Sum(f = f.Count)
694726
public static bool AnalyzeSelectManyAndCount(SyntaxNodeAnalysisContext context, in SimpleMemberInvocationExpressionInfo invocationInfo)
695727
{

src/Tests/Analyzers.Tests/RCS1077OptimizeLinqMethodCallTests.cs

+41-6
Original file line numberDiff line numberDiff line change
@@ -940,7 +940,7 @@ void M()
940940
{
941941
IEnumerable<object> x = null;
942942
943-
x = x.[|OrderBy(f => f).Reverse()|];
943+
x = x.[|OrderBy(f => { return f; }).Reverse()|];
944944
}
945945
}
946946
", @"
@@ -953,7 +953,42 @@ void M()
953953
{
954954
IEnumerable<object> x = null;
955955
956-
x = x.OrderByDescending(f => f);
956+
x = x.OrderByDescending(f => { return f; });
957+
}
958+
}
959+
");
960+
}
961+
962+
[Theory, Trait(Traits.Analyzer, DiagnosticIdentifiers.OptimizeLinqMethodCall)]
963+
[InlineData("OrderBy(f => f)")]
964+
[InlineData("OrderBy(_ => _)")]
965+
[InlineData("OrderBy(@int => @int)")]
966+
public async Task Test_CallOrderInsteadOfOrderByIdentity(string test)
967+
{
968+
await VerifyDiagnosticAndFixAsync($@"
969+
using System.Collections.Generic;
970+
using System.Linq;
971+
972+
class C
973+
{{
974+
void M()
975+
{{
976+
IEnumerable<object> x = null;
977+
978+
x = x.[|{test}|];
979+
}}
980+
}}
981+
", @"
982+
using System.Collections.Generic;
983+
using System.Linq;
984+
985+
class C
986+
{
987+
void M()
988+
{
989+
IEnumerable<object> x = null;
990+
991+
x = x.Order();
957992
}
958993
}
959994
");
@@ -972,7 +1007,7 @@ void M()
9721007
{
9731008
IEnumerable<object> x = null;
9741009
975-
x = x.[|OrderBy(f => f).Where(_ => true)|];
1010+
x = x.[|OrderBy(f => { return f; }).Where(_ => true)|];
9761011
}
9771012
}
9781013
", @"
@@ -985,7 +1020,7 @@ void M()
9851020
{
9861021
IEnumerable<object> x = null;
9871022
988-
x = x.Where(_ => true).OrderBy(f => f);
1023+
x = x.Where(_ => true).OrderBy(f => { return f; });
9891024
}
9901025
}
9911026
");
@@ -1036,7 +1071,7 @@ void M()
10361071
{
10371072
IEnumerable<object> x = null;
10381073
1039-
x = x.[|OrderByDescending(f => f).Where(_ => true)|];
1074+
x = x.[|OrderByDescending(f => { return f; }).Where(_ => true)|];
10401075
}
10411076
}
10421077
", @"
@@ -1049,7 +1084,7 @@ void M()
10491084
{
10501085
IEnumerable<object> x = null;
10511086
1052-
x = x.Where(_ => true).OrderByDescending(f => f);
1087+
x = x.Where(_ => true).OrderByDescending(f => { return f; });
10531088
}
10541089
}
10551090
");

0 commit comments

Comments
 (0)