Skip to content

Commit

Permalink
Improve analyzer OptimizeLinqMethodCall (RCS1077)
Browse files Browse the repository at this point in the history
  • Loading branch information
josefpihrt committed Feb 3, 2020
1 parent 20b9b5f commit 3ee2754
Show file tree
Hide file tree
Showing 5 changed files with 338 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,16 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
context.RegisterCodeFix(codeAction, diagnostic);
}

return;
}
case "Reverse":
{
CodeAction codeAction = CodeAction.Create(
"Call 'OrderByDescending",
ct => CallOrderByDescendingInsteadOfOrderByAndReverseAsync(document, invocationInfo, ct),
GetEquivalenceKey(diagnostic, "CallOrderByDescendingInsteadOfOrderByAndReverse"));

context.RegisterCodeFix(codeAction, diagnostic);
return;
}
case "Select":
Expand Down Expand Up @@ -175,12 +185,25 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
if (diagnostic.Properties.TryGetValue("PropertyName", out string propertyName))
{
CodeAction codeAction = CodeAction.Create(
$"Use '{propertyName}' property instead of calling 'Count'",
ct => UseCountOrLengthPropertyInsteadOfCountMethodAsync(document, invocation, diagnostic.Properties["PropertyName"], ct),
GetEquivalenceKey(diagnostic, "UseCountOrLengthPropertyInsteadOfCountMethod"));

context.RegisterCodeFix(codeAction, diagnostic);
if (diagnostic.Properties.TryGetValue("MethodName", out string methodName)
&& methodName == "Sum")
{
CodeAction codeAction = CodeAction.Create(
"Call 'Sum'",
ct => CallSumInsteadOfSelectManyAndCountAsync(document, invocationInfo, propertyName, ct),
GetEquivalenceKey(diagnostic, "CallSumInsteadOfSelectManyAndCount"));

context.RegisterCodeFix(codeAction, diagnostic);
}
else
{
CodeAction codeAction = CodeAction.Create(
$"Use '{propertyName}' property instead of calling 'Count'",
ct => UseCountOrLengthPropertyInsteadOfCountMethodAsync(document, invocation, diagnostic.Properties["PropertyName"], ct),
GetEquivalenceKey(diagnostic, "UseCountOrLengthPropertyInsteadOfCountMethod"));

context.RegisterCodeFix(codeAction, diagnostic);
}
}
else if (invocation.Parent is BinaryExpressionSyntax binaryExpression)
{
Expand Down Expand Up @@ -512,5 +535,56 @@ private static Task<Document> CallFirstOrDeafultInsteadOfConditionalExpressionAs

return document.ReplaceNodeAsync(conditionalExpression, newInvocationExpression, cancellationToken);
}

private static Task<Document> CallOrderByDescendingInsteadOfOrderByAndReverseAsync(
Document document,
in SimpleMemberInvocationExpressionInfo invocationInfo,
CancellationToken cancellationToken)
{
InvocationExpressionSyntax invocationExpression2 = SimpleMemberInvocationExpressionInfo(invocationInfo.Expression).InvocationExpression;

InvocationExpressionSyntax newInvocationExpression = ChangeInvokedMethodName(invocationExpression2, "OrderByDescending");

return document.ReplaceNodeAsync(invocationInfo.InvocationExpression, newInvocationExpression, cancellationToken);
}

private static Task<Document> CallSumInsteadOfSelectManyAndCountAsync(
Document document,
in SimpleMemberInvocationExpressionInfo invocationInfo,
string propertyName,
CancellationToken cancellationToken)
{
SimpleMemberInvocationExpressionInfo invocationInfo2 = SimpleMemberInvocationExpressionInfo(invocationInfo.Expression);

ArgumentSyntax argument = invocationInfo2.Arguments.Single();

var lambdaExpression = (LambdaExpressionSyntax)argument.Expression;

ExpressionSyntax expression;
if (lambdaExpression.Body is BlockSyntax block)
{
var returnStatement = (ReturnStatementSyntax)block.Statements.Single();

expression = returnStatement.Expression;
}
else
{
expression = (ExpressionSyntax)lambdaExpression.Body;
}

MemberAccessExpressionSyntax memberAccessExpression = SimpleMemberAccessExpression(
expression.WithoutTrivia(),
IdentifierName(propertyName)).WithTriviaFrom(expression);

LambdaExpressionSyntax newLambdaExpression = lambdaExpression.ReplaceNode(expression, memberAccessExpression);

ArgumentSyntax newArgument = argument.WithExpression(newLambdaExpression);

InvocationExpressionSyntax newInvocationExpression = invocationInfo2.InvocationExpression.ReplaceNode(argument, newArgument);

newInvocationExpression = ChangeInvokedMethodName(newInvocationExpression, "Sum");

return document.ReplaceNodeAsync(invocationInfo.InvocationExpression, newInvocationExpression, cancellationToken);
}
}
}
8 changes: 8 additions & 0 deletions src/Analyzers/Analyzers.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1781,6 +1781,14 @@ object x = items.Peek();]]></After>
<Before><![CDATA[enumerable.Any() ? enumerable.First() : default // [|Id|]]]></Before>
<After><![CDATA[enumerable.FirstOrDefault()]]></After>
</Sample>
<Sample>
<Before><![CDATA[enumerable.OrderBy(f => f).Reverse() // [|Id|]]]></Before>
<After><![CDATA[enumerable.OrderByDescending()]]></After>
</Sample>
<Sample>
<Before><![CDATA[enumerable.SelectMany(f => f.Items).Count() // [|Id|]]]></Before>
<After><![CDATA[enumerable.Sum(f => f.Items.Count)]]></After>
</Sample>
</Samples>
</Analyzer>
<Analyzer Identifier="UseEmptyStringLiteralInsteadOfStringEmpty">
Expand Down
10 changes: 9 additions & 1 deletion src/Analyzers/CSharp/Analysis/InvocationExpressionAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ private static void AnalyzeInvocationExpression(SyntaxNodeAnalysisContext contex
}
case "Count":
{
if (!context.IsAnalyzerSuppressed(DiagnosticDescriptors.OptimizeLinqMethodCall))
if (!context.IsAnalyzerSuppressed(DiagnosticDescriptors.OptimizeLinqMethodCall)
&& !OptimizeLinqMethodCallAnalysis.AnalyzeSelectManyAndCount(context, invocationInfo))
{
OptimizeLinqMethodCallAnalysis.AnalyzeCount(context, invocationInfo);
OptimizeLinqMethodCallAnalysis.AnalyzeWhere(context, invocationInfo);
Expand Down Expand Up @@ -130,6 +131,13 @@ private static void AnalyzeInvocationExpression(SyntaxNodeAnalysisContext contex
if (!context.IsAnalyzerSuppressed(DiagnosticDescriptors.OptimizeLinqMethodCall))
OptimizeLinqMethodCallAnalysis.AnalyzeSelectAndMinOrMax(context, invocationInfo);

break;
}
case "Reverse":
{
if (!context.IsAnalyzerSuppressed(DiagnosticDescriptors.OptimizeLinqMethodCall))
OptimizeLinqMethodCallAnalysis.AnalyzeOrderByAndReverse(context, invocationInfo);

break;
}
case "ToString":
Expand Down
Loading

0 comments on commit 3ee2754

Please sign in to comment.