Browse Source

[CodeAction]Added ConvertIfToSwitchAction

newNRvisualizers
Mansheng Yang 14 years ago
parent
commit
d061eaa538
  1. 8
      ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj
  2. 14
      ICSharpCode.NRefactory.CSharp/Refactoring/BaseRefactoringContext.cs
  3. 190
      ICSharpCode.NRefactory.CSharp/Refactoring/CodeActions/ConvertIfToSwitchAction.cs
  4. 438
      ICSharpCode.NRefactory.Tests/CSharp/CodeActions/ConvertIfToSwtichTests.cs
  5. 9
      ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj

8
ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj

@ -241,6 +241,7 @@ @@ -241,6 +241,7 @@
<Compile Include="Parser\mcs\typespec.cs" />
<Compile Include="Parser\mcs\visit.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Refactoring\CodeActions\ConvertIfToSwitchAction.cs" />
<Compile Include="Refactoring\CodeIssues\ExplicitConversionInForEachIssue.cs" />
<Compile Include="Refactoring\DocumentScript.cs" />
<Compile Include="Refactoring\PatternHelper.cs" />
@ -381,12 +382,7 @@ @@ -381,12 +382,7 @@
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.Targets" />
<ItemGroup>
<Folder Include="Completion\" />
<Folder Include="Refactoring\CodeIssues\" />
<Folder Include="Refactoring\CodeIssues\InconsistentNamingIssue\" />
<Folder Include="Refactoring\CodeActions\ExtractMethod\" />
</ItemGroup>
<ItemGroup />
<ProjectExtensions>
<MonoDevelop>
<Properties>

14
ICSharpCode.NRefactory.CSharp/Refactoring/BaseRefactoringContext.cs

@ -135,6 +135,20 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring @@ -135,6 +135,20 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring
{
return new DefiniteAssignmentAnalysis (root, resolver, CancellationToken);
}
/// <summary>
/// Creates a new reachability analysis object with a given statement.
/// </summary>
/// <param name="statement">
/// The statement to start the analysis.
/// </param>
/// <returns>
/// The reachability analysis object.
/// </returns>
public ReachabilityAnalysis CreateReachabilityAnalysis (Statement statement)
{
return ReachabilityAnalysis.Create (statement, resolver, CancellationToken);
}
#endregion
/// <summary>

190
ICSharpCode.NRefactory.CSharp/Refactoring/CodeActions/ConvertIfToSwitchAction.cs

@ -0,0 +1,190 @@ @@ -0,0 +1,190 @@
//
// ConvertIfToSwitchAction.cs
//
// Author:
// Mansheng Yang <lightyang0@gmail.com>
//
// Copyright (c) 2012 Mansheng Yang <lightyang0@gmail.com>
//
// 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;
using System.Linq;
using System.Collections.Generic;
using ICSharpCode.NRefactory.TypeSystem;
using ICSharpCode.NRefactory.PatternMatching;
namespace ICSharpCode.NRefactory.CSharp.Refactoring
{
[ContextAction ("Convert 'if' to 'switch'", Description = "Convert 'if' statement to 'switch' statement")]
public class ConvertIfToSwitchAction : SpecializedCodeAction<IfElseStatement>
{
protected override CodeAction GetAction (RefactoringContext context, IfElseStatement node)
{
if (!node.Contains (context.Location))
return null;
var switchExpr = GetSwitchExpression (context, node.Condition);
if (switchExpr == null)
return null;
var switchSections = new List<SwitchSection> ();
if (!CollectSwitchSections (switchSections, context, node, switchExpr))
return null;
return new CodeAction (context.TranslateString ("Convert 'if' to 'switch'"),
script =>
{
var switchStatement = new SwitchStatement { Expression = switchExpr.Clone () };
switchStatement.SwitchSections.AddRange (switchSections);
script.Replace (node, switchStatement);
});
}
static Expression GetSwitchExpression (RefactoringContext context, Expression expr)
{
var binaryOp = expr as BinaryOperatorExpression;
if (binaryOp == null)
return null;
if (binaryOp.Operator == BinaryOperatorType.ConditionalOr)
return GetSwitchExpression (context, binaryOp.Left);
if (binaryOp.Operator == BinaryOperatorType.Equality) {
Expression switchExpr = null;
if (IsConstantExpression (context, binaryOp.Right))
switchExpr = binaryOp.Left;
if (IsConstantExpression (context, binaryOp.Left))
switchExpr = binaryOp.Right;
if (switchExpr != null && IsValidSwitchType (context.Resolve (switchExpr).Type))
return switchExpr;
}
return null;
}
static bool IsConstantExpression (RefactoringContext context, Expression expr)
{
if (expr is PrimitiveExpression || expr is NullReferenceExpression)
return true;
return context.Resolve (expr).IsCompileTimeConstant;
}
static readonly KnownTypeCode [] validTypes =
{
KnownTypeCode.String, KnownTypeCode.Boolean, KnownTypeCode.Char,
KnownTypeCode.Byte, KnownTypeCode.SByte,
KnownTypeCode.Int16, KnownTypeCode.Int32, KnownTypeCode.Int64,
KnownTypeCode.UInt16, KnownTypeCode.UInt32, KnownTypeCode.UInt64
};
static bool IsValidSwitchType (IType type)
{
if (type.Kind == TypeKind.Enum)
return true;
var typeDefinition = type.GetDefinition ();
if (typeDefinition == null)
return false;
if (typeDefinition.KnownTypeCode == KnownTypeCode.NullableOfT) {
var nullableType = (ParameterizedType)type;
typeDefinition = nullableType.TypeArguments [0].GetDefinition ();
if (typeDefinition == null)
return false;
}
return Array.IndexOf (validTypes, typeDefinition.KnownTypeCode) != -1;
}
static bool CollectSwitchSections (ICollection<SwitchSection> result, RefactoringContext context,
IfElseStatement ifStatement, Expression switchExpr)
{
// if
var section = new SwitchSection ();
if (!CollectCaseLabels (section.CaseLabels, context, ifStatement.Condition, switchExpr))
return false;
CollectSwitchSectionStatements (section.Statements, context, ifStatement.TrueStatement);
result.Add (section);
// else if
var falseStatement = ifStatement.FalseStatement as IfElseStatement;
if (falseStatement != null)
return CollectSwitchSections (result, context, falseStatement, switchExpr);
// else (default label)
var defaultSection = new SwitchSection ();
defaultSection.CaseLabels.Add (new CaseLabel ());
CollectSwitchSectionStatements (defaultSection.Statements, context, ifStatement.FalseStatement);
result.Add (defaultSection);
return true;
}
static bool CollectCaseLabels (AstNodeCollection<CaseLabel> result, RefactoringContext context,
Expression condition, Expression switchExpr)
{
if (condition is ParenthesizedExpression)
return CollectCaseLabels (result, context, ((ParenthesizedExpression)condition).Expression, switchExpr);
var binaryOp = condition as BinaryOperatorExpression;
if (binaryOp == null)
return false;
if (binaryOp.Operator == BinaryOperatorType.ConditionalOr)
return CollectCaseLabels (result, context, binaryOp.Left, switchExpr) &&
CollectCaseLabels (result, context, binaryOp.Right, switchExpr);
if (binaryOp.Operator == BinaryOperatorType.Equality) {
if (switchExpr.Match (binaryOp.Left).Success) {
if (IsConstantExpression (context, binaryOp.Right)) {
result.Add (new CaseLabel (binaryOp.Right.Clone ()));
return true;
}
} else if (switchExpr.Match (binaryOp.Right).Success) {
if (IsConstantExpression (context, binaryOp.Left)) {
result.Add (new CaseLabel (binaryOp.Left.Clone ()));
return true;
}
}
}
return false;
}
static void CollectSwitchSectionStatements (AstNodeCollection<Statement> result, RefactoringContext context,
Statement statement)
{
BlockStatement blockStatement;
if (statement is BlockStatement)
blockStatement = (BlockStatement)statement.Clone ();
else
blockStatement = new BlockStatement { statement.Clone () };
var breackStatement = new BreakStatement ();
blockStatement.Add (breackStatement);
// check if break is needed
var reachabilityAnalysis = context.CreateReachabilityAnalysis (blockStatement);
if (!reachabilityAnalysis.IsReachable (breackStatement))
blockStatement.Statements.Remove (breackStatement);
var statements = blockStatement.Statements.ToArray ();
blockStatement.Statements.Clear ();
result.AddRange (statements);
}
}
}

438
ICSharpCode.NRefactory.Tests/CSharp/CodeActions/ConvertIfToSwtichTests.cs

@ -0,0 +1,438 @@ @@ -0,0 +1,438 @@
//
// ConvertIfToSwtichTests.cs
//
// Author:
// Mansheng Yang <lightyang0@gmail.com>
//
// Copyright (c) 2012 Mansheng Yang <lightyang0@gmail.com>
//
// 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 ConvertIfToSwtichTests : ContextActionTestBase
{
[Test]
public void TestBreak ()
{
Test<ConvertIfToSwitchAction> (@"
class TestClass
{
void TestMethod (int a)
{
int b;
if$ (a == 0) {
b = 0;
} else if (a == 1) {
b = 1;
} else if (a == 2 || a == 3) {
b = 2;
} else {
b = 3;
}
}
}", @"
class TestClass
{
void TestMethod (int a)
{
int b;
switch (a) {
case 0:
b = 0;
break;
case 1:
b = 1;
break;
case 2:
case 3:
b = 2;
break;
default:
b = 3;
break;
}
}
}");
}
[Test]
public void TestReturn ()
{
Test<ConvertIfToSwitchAction> (@"
class TestClass
{
int TestMethod (int a)
{
if$ (a == 0) {
int b = 1;
return b + 1;
} else if (a == 2 || a == 3) {
return 2;
} else {
return -1;
}
}
}", @"
class TestClass
{
int TestMethod (int a)
{
switch (a) {
case 0:
int b = 1;
return b + 1;
case 2:
case 3:
return 2;
default:
return -1;
}
}
}");
}
[Test]
public void TestConstantExpression ()
{
Test<ConvertIfToSwitchAction> (@"
class TestClass
{
int TestMethod (int? a)
{
if$ (a == (1 == 1 ? 11 : 12)) {
return 1;
} else if (a == (2 * 3) + 1 || a == 6 / 2) {
return 2;
} else if (a == null || a == (int)(10L + 2) || a == default(int) || a == sizeof(int)) {
return 3;
} else {
return -1;
}
}
}", @"
class TestClass
{
int TestMethod (int? a)
{
switch (a) {
case (1 == 1 ? 11 : 12):
return 1;
case (2 * 3) + 1:
case 6 / 2:
return 2;
case null:
case (int)(10L + 2):
case default(int):
case sizeof(int):
return 3;
default:
return -1;
}
}
}");
Test<ConvertIfToSwitchAction> (@"
class TestClass
{
const int b = 0;
int TestMethod (int a)
{
const int c = 1;
if$ (a == b) {
return 1;
} else if (a == b + c) {
return 0;
} else {
return -1;
}
}
}", @"
class TestClass
{
const int b = 0;
int TestMethod (int a)
{
const int c = 1;
switch (a) {
case b:
return 1;
case b + c:
return 0;
default:
return -1;
}
}
}");
}
[Test]
public void TestNestedOr ()
{
Test<ConvertIfToSwitchAction> (@"
class TestClass
{
int TestMethod (int a)
{
if$ (a == 0) {
return 1;
} else if ((a == 2 || a == 4) || (a == 3 || a == 5)) {
return 2;
} else {
return -1;
}
}
}", @"
class TestClass
{
int TestMethod (int a)
{
switch (a) {
case 0:
return 1;
case 2:
case 4:
case 3:
case 5:
return 2;
default:
return -1;
}
}
}");
}
[Test]
public void TestComplexSwitchExpression ()
{
Test<ConvertIfToSwitchAction> (@"
class TestClass
{
int TestMethod (int a, int b)
{
if$ (a + b == 0) {
return 1;
} else if (1 == a + b) {
return 0;
} else {
return -1;
}
}
}", @"
class TestClass
{
int TestMethod (int a, int b)
{
switch (a + b) {
case 0:
return 1;
case 1:
return 0;
default:
return -1;
}
}
}");
}
[Test]
public void TestNonConstantExpression ()
{
TestWrongContext<ConvertIfToSwitchAction> (@"
class TestClass
{
void TestMethod (int a, int c)
{
int b;
if$ (a == 0) {
b = 0;
} else if (a == c) {
b = 1;
} else if (a == 2 || a == 3) {
b = 2;
} else {
b = 3;
}
}
}");
TestWrongContext<ConvertIfToSwitchAction> (@"
class TestClass
{
void TestMethod (int a, int c)
{
int b;
if$ (a == c) {
b = 0;
} else if (a == 1) {
b = 1;
} else if (a == 2 || a == 3) {
b = 2;
} else {
b = 3;
}
}
}");
TestWrongContext<ConvertIfToSwitchAction> (@"
class TestClass
{
void TestMethod (int a, int c)
{
int b;
if$ (a == 0) {
b = 0;
} else if (a == 1) {
b = 1;
} else if (a == 2 || a == c) {
b = 2;
} else {
b = 3;
}
}
}");
}
[Test]
public void TestNonEqualityComparison ()
{
TestWrongContext<ConvertIfToSwitchAction> (@"
class TestClass
{
void TestMethod (int a)
{
int b;
if$ (a == 0) {
b = 0;
} else if (a > 4) {
b = 1;
} else if (a == 2 || a == 3) {
b = 2;
} else {
b = 3;
}
}
}");
}
[Test]
public void TestValidType ()
{
// enum
Test<ConvertIfToSwitchAction> (@"
enum TestEnum
{
First,
Second,
}
class TestClass
{
int TestMethod (TestEnum a)
{
if$ (a == TestEnum.First) {
return 1;
} else {
return -1;
}
}
}", @"
enum TestEnum
{
First,
Second,
}
class TestClass
{
int TestMethod (TestEnum a)
{
switch (a) {
case TestEnum.First:
return 1;
default:
return -1;
}
}
}");
// string, bool, char, integral, nullable
TestValidType ("string", "\"test\"");
TestValidType ("bool", "true");
TestValidType ("char", "'a'");
TestValidType ("byte", "0");
TestValidType ("sbyte", "0");
TestValidType ("short", "0");
TestValidType ("long", "0");
TestValidType ("ushort", "0");
TestValidType ("uint", "0");
TestValidType ("ulong", "0");
TestValidType ("bool?", "null");
}
void TestValidType (string type, string caseValue)
{
Test<ConvertIfToSwitchAction> (@"
class TestClass
{
int TestMethod (" + type + @" a)
{
if$ (a == " + caseValue + @") {
return 1;
} else {
return -1;
}
}
}", @"
class TestClass
{
int TestMethod (" + type + @" a)
{
switch (a) {
case " + caseValue + @":
return 1;
default:
return -1;
}
}
}");
}
[Test]
public void TestInvalidType ()
{
TestWrongContext<ConvertIfToSwitchAction> (@"
class TestClass
{
void TestMethod (double a)
{
int b;
if$ (a == 0) {
b = 0;
} else {
b = 3;
}
}
}");
}
}
}

9
ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj

@ -81,6 +81,7 @@ @@ -81,6 +81,7 @@
</Compile>
<Compile Include="CSharp\Analysis\DefiniteAssignmentTests.cs" />
<Compile Include="CSharp\AstStructureTests.cs" />
<Compile Include="CSharp\CodeActions\ConvertIfToSwtichTests.cs" />
<Compile Include="CSharp\CodeIssues\ExplicitConversionInForEachIssueTests.cs" />
<Compile Include="CSharp\CSharpAmbienceTests.cs" />
<Compile Include="CSharp\CodeDomConvertVisitorTests.cs" />
@ -291,13 +292,7 @@ @@ -291,13 +292,7 @@
<Private>False</Private>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Folder Include="CSharp\" />
<Folder Include="CSharp\Parser\" />
<Folder Include="CSharp\CodeIssues\" />
<Folder Include="CSharp\CodeActions\" />
<Folder Include="CSharp\Parser\Bugs\" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ProjectExtensions>
<MonoDevelop>

Loading…
Cancel
Save