From 582326739a7509be5d30ed2ca4b7394b74ecbec7 Mon Sep 17 00:00:00 2001 From: Mansheng Yang Date: Wed, 23 May 2012 16:08:40 +0800 Subject: [PATCH] [CodeAction]Added ConvertSwitchToIfAction --- .../ICSharpCode.NRefactory.CSharp.csproj | 1 + .../CodeActions/ConvertSwitchToIfAction.cs | 147 +++++++++++ .../CodeActions/ConvertSwitchToIfTests.cs | 244 ++++++++++++++++++ .../ICSharpCode.NRefactory.Tests.csproj | 1 + 4 files changed, 393 insertions(+) create mode 100644 ICSharpCode.NRefactory.CSharp/Refactoring/CodeActions/ConvertSwitchToIfAction.cs create mode 100644 ICSharpCode.NRefactory.Tests/CSharp/CodeActions/ConvertSwitchToIfTests.cs diff --git a/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj b/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj index 360d26833b..dfff4667af 100644 --- a/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj +++ b/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj @@ -242,6 +242,7 @@ + diff --git a/ICSharpCode.NRefactory.CSharp/Refactoring/CodeActions/ConvertSwitchToIfAction.cs b/ICSharpCode.NRefactory.CSharp/Refactoring/CodeActions/ConvertSwitchToIfAction.cs new file mode 100644 index 0000000000..0be9b312cc --- /dev/null +++ b/ICSharpCode.NRefactory.CSharp/Refactoring/CodeActions/ConvertSwitchToIfAction.cs @@ -0,0 +1,147 @@ +// +// ConvertSwitchToIfAction.cs +// +// Author: +// Mansheng Yang +// +// Copyright (c) 2012 Mansheng Yang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System.Linq; + +namespace ICSharpCode.NRefactory.CSharp.Refactoring +{ + [ContextAction ("Convert 'switch' to 'if'", Description = "Convert 'switch' statement to 'if' statement")] + public class ConvertSwitchToIfAction : SpecializedCodeAction + { + static readonly InsertParenthesesVisitor insertParenthesesVisitor = new InsertParenthesesVisitor (); + + protected override CodeAction GetAction (RefactoringContext context, SwitchStatement node) + { + if (!node.Contains (context.Location)) + return null; + + // empty switch + if (node.SwitchSections.Count == 0) + return null; + + // switch with default only + if (node.SwitchSections.First ().CaseLabels.Any (label => label.Expression.IsNull)) + return null; + + // check non-trailing breaks + foreach (var switchSection in node.SwitchSections) { + var lastStatement = switchSection.Statements.LastOrDefault (); + var finder = new NonTrailingBreakFinder (lastStatement as BreakStatement); + if (switchSection.AcceptVisitor (finder)) + return null; + } + + return new CodeAction (context.TranslateString ("Convert 'switch' to 'if'"), + script => + { + IfElseStatement ifStatement = null; + IfElseStatement currentStatement = null; + foreach (var switchSection in node.SwitchSections) { + var condition = CollectCondition (node.Expression, switchSection.CaseLabels); + var bodyStatement = new BlockStatement (); + var lastStatement = switchSection.Statements.LastOrDefault (); + foreach (var statement in switchSection.Statements) { + // skip trailing break + if (statement == lastStatement && statement is BreakStatement) + continue; + bodyStatement.Add (statement.Clone ()); + } + + // default -> else + if (condition == null) { + currentStatement.FalseStatement = bodyStatement; + break; + } + var elseIfStatement = new IfElseStatement (condition, bodyStatement); + if (ifStatement == null) + ifStatement = elseIfStatement; + else + currentStatement.FalseStatement = elseIfStatement; + currentStatement = elseIfStatement; + } + script.Replace (node, ifStatement); + script.FormatText (ifStatement); + }); + } + + static Expression CollectCondition(Expression switchExpr, AstNodeCollection caseLabels) + { + // default + if (caseLabels.Count == 0 || caseLabels.Any (label => label.Expression.IsNull)) + return null; + + var conditionList = caseLabels.Select ( + label => new BinaryOperatorExpression (switchExpr.Clone (), BinaryOperatorType.Equality, label.Expression.Clone ())) + .ToArray (); + + // insert necessary parentheses + foreach (var expr in conditionList) + expr.AcceptVisitor (insertParenthesesVisitor); + + if (conditionList.Length == 1) + return conditionList [0]; + + // combine case labels into an conditional or expression + BinaryOperatorExpression condition = null; + BinaryOperatorExpression currentCondition = null; + for (int i = 0; i < conditionList.Length - 1; i++) { + var newCondition = new BinaryOperatorExpression + { + Operator = BinaryOperatorType.ConditionalOr, + Left = conditionList[i] + }; + if (currentCondition == null) + condition = newCondition; + else + currentCondition.Right = newCondition; + currentCondition = newCondition; + } + currentCondition.Right = conditionList [conditionList.Length - 1]; + + return condition; + } + + class NonTrailingBreakFinder : DepthFirstAstVisitor + { + BreakStatement trailingBreakStatement; + + public NonTrailingBreakFinder (BreakStatement trailingBreak) + { + trailingBreakStatement = trailingBreak; + } + + protected override bool VisitChildren (AstNode node) + { + return node.Children.Any (child => child.AcceptVisitor (this)); + } + + public override bool VisitBreakStatement (BreakStatement breakStatement) + { + return breakStatement != trailingBreakStatement; + } + } + } +} diff --git a/ICSharpCode.NRefactory.Tests/CSharp/CodeActions/ConvertSwitchToIfTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/CodeActions/ConvertSwitchToIfTests.cs new file mode 100644 index 0000000000..4a1a91e389 --- /dev/null +++ b/ICSharpCode.NRefactory.Tests/CSharp/CodeActions/ConvertSwitchToIfTests.cs @@ -0,0 +1,244 @@ +// +// ConvertSwitchToIfTests.cs +// +// Author: +// Mansheng Yang +// +// Copyright (c) 2012 Mansheng Yang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using ICSharpCode.NRefactory.CSharp.Refactoring; +using NUnit.Framework; + +namespace ICSharpCode.NRefactory.CSharp.CodeActions +{ + [TestFixture] + public class ConvertSwitchToIfTests : ContextActionTestBase + { + [Test] + public void TestReturn () + { + Test (@" +class TestClass +{ + int TestMethod (int a) + { + $switch (a) { + case 0: + return 0; + case 1: + case 2: + return 1; + case 3: + case 4: + case 5: + return 1; + default: + return 2; + } + } +}", @" +class TestClass +{ + int TestMethod (int a) + { + if (a == 0) { + return 0; + } else + if (a == 1 || a == 2) { + return 1; + } else + if (a == 3 || a == 4 || a == 5) { + return 1; + } else { + return 2; + } + } +}"); + } + + [Test] + public void TestWithoutDefault () + { + Test (@" +class TestClass +{ + int TestMethod (int a) + { + $switch (a) { + case 0: + return 0; + case 1: + case 2: + return 1; + case 3: + case 4: + case 5: + return 1; + } + } +}", @" +class TestClass +{ + int TestMethod (int a) + { + if (a == 0) { + return 0; + } else + if (a == 1 || a == 2) { + return 1; + } else + if (a == 3 || a == 4 || a == 5) { + return 1; + } + } +}"); + } + + [Test] + public void TestBreak () + { + Test (@" +class TestClass +{ + void TestMethod (int a) + { + $switch (a) { + case 0: + int b = 1; + break; + case 1: + case 2: + break; + case 3: + case 4: + case 5: + break; + default: + break; + } + } +}", @" +class TestClass +{ + void TestMethod (int a) + { + if (a == 0) { + int b = 1; + } else + if (a == 1 || a == 2) { + } else + if (a == 3 || a == 4 || a == 5) { + } else { + } + } +}"); + } + + [Test] + public void TestOperatorPriority () + { + Test (@" +class TestClass +{ + int TestMethod (int a) + { + $switch (a) { + case 0: + return 0; + case 1 == 1 ? 1 : 2: + return 1; + default: + return 2; + } + } +}", @" +class TestClass +{ + int TestMethod (int a) + { + if (a == 0) { + return 0; + } else + if (a == (1 == 1 ? 1 : 2)) { + return 1; + } else { + return 2; + } + } +}"); + } + + [Test] + public void TestEmptySwitch () + { + TestWrongContext (@" +class TestClass +{ + void TestMethod (int a) + { + $switch (a) + { + } + } +}"); + } + + [Test] + public void TestSwitchWithDefaultOnly () + { + TestWrongContext (@" +class TestClass +{ + void TestMethod (int a) + { + $switch (a) + { + case 0: + default: + break; + } + } +}"); + } + + [Test] + public void TestNonTrailingBreak () + { + TestWrongContext (@" +class TestClass +{ + void TestMethod (int a, int b) + { + $switch (a) + { + case 0: + if (b == 0) break; + b = 1; + break; + default: + break; + } + } +}"); + } + + } +} diff --git a/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj b/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj index a3092bb75e..b58f55a293 100644 --- a/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj +++ b/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj @@ -82,6 +82,7 @@ +