提交 7c76020f 编写于 作者: C Cyrus Najmabadi

Add feature to allow simple reversing of a for-loop

上级 ef666b6e
......@@ -1141,6 +1141,15 @@ internal class CSharpFeaturesResources {
}
}
/// <summary>
/// Looks up a localized string similar to Reverse &apos;for&apos; statement.
/// </summary>
internal static string Reverse_for_statement {
get {
return ResourceManager.GetString("Reverse_for_statement", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to select clause.
/// </summary>
......
......@@ -669,4 +669,8 @@
<data name="Pass_in_captured_variables_as_arguments" xml:space="preserve">
<value>Pass in captured variables as arguments</value>
</data>
<data name="Reverse_for_statement" xml:space="preserve">
<value>Reverse 'for' statement</value>
<comment>{Locked="for"} "for" is a C# keyword and should not be localized.</comment>
</data>
</root>
\ No newline at end of file
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Formatting;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.ReverseForStatement
{
[ExportCodeRefactoringProvider(LanguageNames.CSharp), Shared]
internal class CSharpReverseForStatementCodeRefactoringProvider : CodeRefactoringProvider
{
public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
{
var forStatement = await context.TryGetRelevantNodeAsync<ForStatementSyntax>().ConfigureAwait(false);
if (forStatement == null)
return;
// We support the following cases
//
// for (var x = start; x < end ; x++)
// for (... ; ... ; ++x)
// for (... ; x <= end; ...)
// for (... ; ... ; x += 1)
//
// for (var x = end ; x >= start; x--)
// for (... ; ... ; --x)
// for (... ; ... ; x -= 1)
var declaration = forStatement.Declaration;
if (declaration == null)
return;
if (declaration.Variables.Count != 1)
return;
if (forStatement.Incrementors.Count != 1)
return;
var variable = declaration.Variables[0];
var after = forStatement.Incrementors[0];
if (!(forStatement.Condition is BinaryExpressionSyntax condition))
return;
if (after == null)
return;
var (document, _, cancellationToken) = context;
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
if (MatchesIncrementPattern(variable, condition, after, out _, out _, out _) ||
MatchesDecrementPattern(variable, condition, after, out _, out _))
{
context.RegisterRefactoring(new MyCodeAction(
c => ReverseForStatementAsync(document, forStatement, c)));
}
}
private bool MatchesIncrementPattern(
VariableDeclaratorSyntax variable, BinaryExpressionSyntax condition, ExpressionSyntax after,
out ExpressionSyntax start, out bool equals, out ExpressionSyntax end)
{
equals = default;
end = default;
return IsIncrementInitializer(variable, out start) &&
IsIncrementCondition(variable, condition, out equals, out end) &&
IsIncrementAfter(variable, after);
}
private bool MatchesDecrementPattern(
VariableDeclaratorSyntax variable, BinaryExpressionSyntax condition, ExpressionSyntax after,
out ExpressionSyntax end, out ExpressionSyntax start)
{
start = default;
return IsDecrementInitializer(variable, out end) &&
IsDecrementCondition(variable, condition, out start) &&
IsDecrementAfter(variable, after);
}
private bool IsIncrementInitializer(VariableDeclaratorSyntax variable, out ExpressionSyntax start)
{
start = variable.Initializer?.Value;
return start != null;
}
private bool IsIncrementCondition(
VariableDeclaratorSyntax variable, BinaryExpressionSyntax condition,
out bool equals, out ExpressionSyntax end)
{
// i < ... i <= ...
if (condition.Kind() == SyntaxKind.LessThanExpression ||
condition.Kind() == SyntaxKind.LessThanOrEqualExpression)
{
end = condition.Right;
equals = condition.Kind() == SyntaxKind.LessThanOrEqualExpression;
return IsVariableReference(variable, condition.Left);
}
// ... > i ... >= i
if (condition.Kind() == SyntaxKind.GreaterThanExpression ||
condition.Kind() == SyntaxKind.GreaterThanOrEqualExpression)
{
end = condition.Left;
equals = condition.Kind() == SyntaxKind.GreaterThanOrEqualExpression;
return IsVariableReference(variable, condition.Right);
}
end = default;
equals = default;
return false;
}
private bool IsIncrementAfter(
VariableDeclaratorSyntax variable, ExpressionSyntax after)
{
// i++
// ++i
// i += 1
if (after is PostfixUnaryExpressionSyntax postfixUnary &&
postfixUnary.Kind() == SyntaxKind.PostIncrementExpression &&
IsVariableReference(variable, postfixUnary.Operand))
{
return true;
}
if (after is PrefixUnaryExpressionSyntax prefixUnary &&
prefixUnary.Kind() == SyntaxKind.PreIncrementExpression &&
IsVariableReference(variable, prefixUnary.Operand))
{
return true;
}
if (after is AssignmentExpressionSyntax assignment &&
assignment.Kind() == SyntaxKind.AddAssignmentExpression &&
IsVariableReference(variable, assignment.Left) &&
IsLiteralOne(assignment.Right))
{
return true;
}
return false;
}
private static bool IsLiteralOne(ExpressionSyntax expression)
=> expression.WalkDownParentheses() is LiteralExpressionSyntax literal && literal.Token.Value is 1;
private bool IsDecrementInitializer(VariableDeclaratorSyntax variable, out ExpressionSyntax end)
{
end = variable.Initializer?.Value;
return end != null;
}
private bool IsDecrementCondition(
VariableDeclaratorSyntax variable, BinaryExpressionSyntax condition,
out ExpressionSyntax start)
{
// i >= ...
if (condition.Kind() == SyntaxKind.GreaterThanOrEqualExpression)
{
start = condition.Right;
return IsVariableReference(variable, condition.Left);
}
// ... <= i
if (condition.Kind() == SyntaxKind.LessThanOrEqualExpression)
{
start = condition.Left;
return IsVariableReference(variable, condition.Right);
}
start = default;
return false;
}
private bool IsDecrementAfter(
VariableDeclaratorSyntax variable, ExpressionSyntax after)
{
// i--
// --i
// i -= 1
if (after is PostfixUnaryExpressionSyntax postfixUnary &&
postfixUnary.Kind() == SyntaxKind.PostDecrementExpression &&
IsVariableReference(variable, postfixUnary.Operand))
{
return true;
}
if (after is PrefixUnaryExpressionSyntax prefixUnary &&
prefixUnary.Kind() == SyntaxKind.PreDecrementExpression &&
IsVariableReference(variable, prefixUnary.Operand))
{
return true;
}
if (after is AssignmentExpressionSyntax assignment &&
assignment.Kind() == SyntaxKind.SubtractAssignmentExpression &&
IsVariableReference(variable, assignment.Left) &&
IsLiteralOne(assignment.Right))
{
return true;
}
return false;
}
private bool IsVariableReference(VariableDeclaratorSyntax variable, ExpressionSyntax expr)
=> expr.WalkDownParentheses() is IdentifierNameSyntax identifier &&
identifier.Identifier.ValueText == variable.Identifier.ValueText;
private async Task<Document> ReverseForStatementAsync(
Document document, ForStatementSyntax forStatement, CancellationToken cancellationToken)
{
var variable = forStatement.Declaration.Variables[0];
var condition = (BinaryExpressionSyntax)forStatement.Condition;
var after = forStatement.Incrementors[0];
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var editor = new SyntaxEditor(root, document.Project.Solution.Workspace);
var generator = editor.Generator;
if (MatchesIncrementPattern(
variable, condition, after,
out var start, out var equals, out var end))
{
// for (var x = start ; x < end ; ...) =>
// for (var x = end - 1; x >= start; ...)
//
// for (var x = start; x <= end ; ...) =>
// for (var x = end ; x >= start; ...) =>
var newStart = equals
? end
: (ExpressionSyntax)generator.SubtractExpression(end, generator.LiteralExpression(1));
editor.ReplaceNode(variable.Initializer.Value, Reduce(newStart));
editor.ReplaceNode(condition, Reduce(Invert(variable, condition, start)));
}
else if (MatchesDecrementPattern(
variable, condition, after,
out end, out start))
{
// for (var x = end; x >= start; x--) =>
// for (var x = start; x <= end; x--)
editor.ReplaceNode(variable.Initializer.Value, Reduce(start));
editor.ReplaceNode(condition, Reduce(Invert(variable, condition, end)));
}
else
{
throw new InvalidOperationException();
}
editor.ReplaceNode(after, InvertAfter(after));
return document.WithSyntaxRoot(editor.GetChangedRoot());
}
private ExpressionSyntax Reduce(ExpressionSyntax expr)
{
expr = expr.WalkDownParentheses();
if (expr is BinaryExpressionSyntax outerBinary)
{
var reducedLeft = Reduce(outerBinary.Left);
var reducedRight = Reduce(outerBinary.Right);
// (... + 1) - 1 => ...
// (... - 1) + 1 => ...
{
if (reducedLeft is BinaryExpressionSyntax innerLeft &&
IsLiteralOne(innerLeft.Right) &&
IsLiteralOne(reducedRight))
{
if ((outerBinary.Kind() == SyntaxKind.SubtractExpression && innerLeft.Kind() == SyntaxKind.AddExpression) ||
(outerBinary.Kind() == SyntaxKind.AddExpression && innerLeft.Kind() == SyntaxKind.SubtractExpression))
{
return Reduce(innerLeft.Left);
}
}
}
// v <= x - 1 => v < x
// x - 1 >= v => x > v
{
if (outerBinary.Kind() == SyntaxKind.LessThanOrEqualExpression &&
reducedRight is BinaryExpressionSyntax innerRight &&
innerRight.Kind() == SyntaxKind.SubtractExpression &&
IsLiteralOne(innerRight.Right))
{
var newOperator = SyntaxFactory.Token(SyntaxKind.LessThanToken).WithTriviaFrom(outerBinary.OperatorToken);
return Reduce(outerBinary.WithRight(innerRight.Left)
.WithOperatorToken(newOperator));
}
if (outerBinary.Kind() == SyntaxKind.GreaterThanOrEqualExpression &&
reducedLeft is BinaryExpressionSyntax innerLeft &&
innerLeft.Kind() == SyntaxKind.SubtractExpression &&
IsLiteralOne(innerLeft.Right))
{
var newOperator = SyntaxFactory.Token(SyntaxKind.GreaterThanToken).WithTriviaFrom(outerBinary.OperatorToken);
return Reduce(outerBinary.WithRight(innerLeft.Left)
.WithOperatorToken(newOperator));
}
}
}
return expr.WithAdditionalAnnotations(Formatter.Annotation);
}
private BinaryExpressionSyntax Invert(
VariableDeclaratorSyntax variable, BinaryExpressionSyntax condition, ExpressionSyntax operand)
{
var (left, right) = IsVariableReference(variable, condition.Left)
? (condition.Left, operand)
: (operand, condition.Right);
var newOperatorKind = condition.Kind() == SyntaxKind.LessThanExpression || condition.Kind() == SyntaxKind.LessThanOrEqualExpression
? SyntaxKind.GreaterThanEqualsToken
: SyntaxKind.LessThanEqualsToken;
var newExpressionKind = newOperatorKind == SyntaxKind.GreaterThanEqualsToken
? SyntaxKind.GreaterThanOrEqualExpression
: SyntaxKind.LessThanOrEqualExpression;
var newOperator = SyntaxFactory.Token(newOperatorKind).WithTriviaFrom(condition.OperatorToken);
return SyntaxFactory.BinaryExpression(newExpressionKind, left, newOperator, right);
}
private ExpressionSyntax InvertAfter(ExpressionSyntax after)
{
var opToken = after switch
{
PostfixUnaryExpressionSyntax postfixUnary => postfixUnary.OperatorToken,
PrefixUnaryExpressionSyntax prefixUnary => prefixUnary.OperatorToken,
AssignmentExpressionSyntax assignment => assignment.OperatorToken,
_ => throw ExceptionUtilities.UnexpectedValue(after.Kind())
};
var newKind = opToken.Kind() switch
{
SyntaxKind.MinusMinusToken => SyntaxKind.PlusPlusToken,
SyntaxKind.PlusPlusToken => SyntaxKind.MinusMinusToken,
SyntaxKind.PlusEqualsToken => SyntaxKind.MinusEqualsToken,
SyntaxKind.MinusEqualsToken => SyntaxKind.PlusEqualsToken,
_ => throw ExceptionUtilities.UnexpectedValue(opToken.Kind())
};
var newOpToken = SyntaxFactory.Token(newKind).WithTriviaFrom(opToken);
return after.ReplaceToken(opToken, newOpToken);
}
private class MyCodeAction : CodeAction.DocumentChangeAction
{
public MyCodeAction(Func<CancellationToken, Task<Document>> createChangedDocument)
: base(CSharpFeaturesResources.Reverse_for_statement, createChangedDocument)
{
}
}
}
}
......@@ -137,6 +137,11 @@
<target state="translated">Odebrat nepoužívané proměnné</target>
<note />
</trans-unit>
<trans-unit id="Reverse_for_statement">
<source>Reverse 'for' statement</source>
<target state="new">Reverse 'for' statement</target>
<note>{Locked="for"} "for" is a C# keyword and should not be localized.</note>
</trans-unit>
<trans-unit id="Simplify_lambda_expression">
<source>Simplify lambda expression</source>
<target state="translated">Zjednodušit výraz lambda</target>
......
......@@ -137,6 +137,11 @@
<target state="translated">Nicht verwendete Variablen entfernen</target>
<note />
</trans-unit>
<trans-unit id="Reverse_for_statement">
<source>Reverse 'for' statement</source>
<target state="new">Reverse 'for' statement</target>
<note>{Locked="for"} "for" is a C# keyword and should not be localized.</note>
</trans-unit>
<trans-unit id="Simplify_lambda_expression">
<source>Simplify lambda expression</source>
<target state="translated">Lambdaausdruck vereinfachen</target>
......
......@@ -137,6 +137,11 @@
<target state="translated">Quitar variables no utilizadas</target>
<note />
</trans-unit>
<trans-unit id="Reverse_for_statement">
<source>Reverse 'for' statement</source>
<target state="new">Reverse 'for' statement</target>
<note>{Locked="for"} "for" is a C# keyword and should not be localized.</note>
</trans-unit>
<trans-unit id="Simplify_lambda_expression">
<source>Simplify lambda expression</source>
<target state="translated">Simplificar expresión lambda</target>
......
......@@ -137,6 +137,11 @@
<target state="translated">Supprimer les variables inutilisées</target>
<note />
</trans-unit>
<trans-unit id="Reverse_for_statement">
<source>Reverse 'for' statement</source>
<target state="new">Reverse 'for' statement</target>
<note>{Locked="for"} "for" is a C# keyword and should not be localized.</note>
</trans-unit>
<trans-unit id="Simplify_lambda_expression">
<source>Simplify lambda expression</source>
<target state="translated">Simplifier l'expression lambda</target>
......
......@@ -137,6 +137,11 @@
<target state="translated">Rimuovi le variabili non usate</target>
<note />
</trans-unit>
<trans-unit id="Reverse_for_statement">
<source>Reverse 'for' statement</source>
<target state="new">Reverse 'for' statement</target>
<note>{Locked="for"} "for" is a C# keyword and should not be localized.</note>
</trans-unit>
<trans-unit id="Simplify_lambda_expression">
<source>Simplify lambda expression</source>
<target state="translated">Semplifica l'espressione lambda</target>
......
......@@ -137,6 +137,11 @@
<target state="translated">未使用の変数を削除する</target>
<note />
</trans-unit>
<trans-unit id="Reverse_for_statement">
<source>Reverse 'for' statement</source>
<target state="new">Reverse 'for' statement</target>
<note>{Locked="for"} "for" is a C# keyword and should not be localized.</note>
</trans-unit>
<trans-unit id="Simplify_lambda_expression">
<source>Simplify lambda expression</source>
<target state="translated">ラムダ式の簡略化</target>
......
......@@ -137,6 +137,11 @@
<target state="translated">사용하지 않는 변수 제거</target>
<note />
</trans-unit>
<trans-unit id="Reverse_for_statement">
<source>Reverse 'for' statement</source>
<target state="new">Reverse 'for' statement</target>
<note>{Locked="for"} "for" is a C# keyword and should not be localized.</note>
</trans-unit>
<trans-unit id="Simplify_lambda_expression">
<source>Simplify lambda expression</source>
<target state="translated">람다 식 단순화</target>
......
......@@ -137,6 +137,11 @@
<target state="translated">Usuń nieużywane zmienne</target>
<note />
</trans-unit>
<trans-unit id="Reverse_for_statement">
<source>Reverse 'for' statement</source>
<target state="new">Reverse 'for' statement</target>
<note>{Locked="for"} "for" is a C# keyword and should not be localized.</note>
</trans-unit>
<trans-unit id="Simplify_lambda_expression">
<source>Simplify lambda expression</source>
<target state="translated">Uprość wyrażenie lambda</target>
......
......@@ -137,6 +137,11 @@
<target state="translated">Remover variáveis não utilizadas</target>
<note />
</trans-unit>
<trans-unit id="Reverse_for_statement">
<source>Reverse 'for' statement</source>
<target state="new">Reverse 'for' statement</target>
<note>{Locked="for"} "for" is a C# keyword and should not be localized.</note>
</trans-unit>
<trans-unit id="Simplify_lambda_expression">
<source>Simplify lambda expression</source>
<target state="translated">Simplificar expressão lambda</target>
......
......@@ -137,6 +137,11 @@
<target state="translated">Удалить неиспользуемые переменные</target>
<note />
</trans-unit>
<trans-unit id="Reverse_for_statement">
<source>Reverse 'for' statement</source>
<target state="new">Reverse 'for' statement</target>
<note>{Locked="for"} "for" is a C# keyword and should not be localized.</note>
</trans-unit>
<trans-unit id="Simplify_lambda_expression">
<source>Simplify lambda expression</source>
<target state="translated">Упростить лямбда-выражение</target>
......
......@@ -137,6 +137,11 @@
<target state="translated">Kullanılmayan değişkenleri kaldır</target>
<note />
</trans-unit>
<trans-unit id="Reverse_for_statement">
<source>Reverse 'for' statement</source>
<target state="new">Reverse 'for' statement</target>
<note>{Locked="for"} "for" is a C# keyword and should not be localized.</note>
</trans-unit>
<trans-unit id="Simplify_lambda_expression">
<source>Simplify lambda expression</source>
<target state="translated">Lambda ifadesini basitleştir</target>
......
......@@ -137,6 +137,11 @@
<target state="translated">删除未使用的变量</target>
<note />
</trans-unit>
<trans-unit id="Reverse_for_statement">
<source>Reverse 'for' statement</source>
<target state="new">Reverse 'for' statement</target>
<note>{Locked="for"} "for" is a C# keyword and should not be localized.</note>
</trans-unit>
<trans-unit id="Simplify_lambda_expression">
<source>Simplify lambda expression</source>
<target state="translated">简化 lambda 表达式</target>
......
......@@ -137,6 +137,11 @@
<target state="translated">移除未使用的變數</target>
<note />
</trans-unit>
<trans-unit id="Reverse_for_statement">
<source>Reverse 'for' statement</source>
<target state="new">Reverse 'for' statement</target>
<note>{Locked="for"} "for" is a C# keyword and should not be localized.</note>
</trans-unit>
<trans-unit id="Simplify_lambda_expression">
<source>Simplify lambda expression</source>
<target state="translated">簡化 Lambda 運算式</target>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册