From 0f015890399d7d7ad5a66c69f6982acb9c81080d Mon Sep 17 00:00:00 2001 From: Mansheng Yang Date: Fri, 1 Jun 2012 17:10:15 +0800 Subject: [PATCH] [CodeAction] Added UseStringFormatAction --- .../ICSharpCode.NRefactory.CSharp.csproj | 1 + .../CodeActions/UseStringFormatAction.cs | 144 ++++++++++++++++++ .../CodeActions/UseStringFormatTests.cs | 99 ++++++++++++ .../ICSharpCode.NRefactory.Tests.csproj | 1 + 4 files changed, 245 insertions(+) create mode 100644 ICSharpCode.NRefactory.CSharp/Refactoring/CodeActions/UseStringFormatAction.cs create mode 100644 ICSharpCode.NRefactory.Tests/CSharp/CodeActions/UseStringFormatTests.cs diff --git a/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj b/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj index 0dc77004fd..508f64064b 100644 --- a/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj +++ b/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj @@ -252,6 +252,7 @@ + diff --git a/ICSharpCode.NRefactory.CSharp/Refactoring/CodeActions/UseStringFormatAction.cs b/ICSharpCode.NRefactory.CSharp/Refactoring/CodeActions/UseStringFormatAction.cs new file mode 100644 index 0000000000..56f7404fe8 --- /dev/null +++ b/ICSharpCode.NRefactory.CSharp/Refactoring/CodeActions/UseStringFormatAction.cs @@ -0,0 +1,144 @@ +// +// UseStringFormatAction.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.Collections.Generic; +using System.Text; +using ICSharpCode.NRefactory.TypeSystem; +using ICSharpCode.NRefactory.PatternMatching; + +namespace ICSharpCode.NRefactory.CSharp.Refactoring +{ + [ContextAction ("Use string.Format()", + Description = "Convert concatenation of strings and objects to string.Format()")] + public class UseStringFormatAction : ICodeActionProvider + { + + public IEnumerable GetActions (RefactoringContext context) + { + // NOTE: @, multiple occurance + + var node = context.GetNode (); + while (node != null && !IsStringConcatenation(context, node as BinaryOperatorExpression)) + node = node.Parent; + + if (node == null) + yield break; + + var expr = (BinaryOperatorExpression)node; + var parent = expr.Parent as BinaryOperatorExpression; + while (parent != null && parent.Operator == BinaryOperatorType.Add) { + expr = parent; + parent = expr.Parent as BinaryOperatorExpression; + } + + yield return new CodeAction (context.TranslateString ("Use string.Format()"), + script => + { + var format = new StringBuilder (); + var stringType = new PrimitiveType ("string"); + var formatInvocation = new InvocationExpression ( + new MemberReferenceExpression (new TypeReferenceExpression (stringType), "Format")); + var formatLiteral = new PrimitiveExpression (""); + var counter = 0; + var verbatim = false; + var arguments = new List (); + + format.Append ('"'); + formatInvocation.Arguments.Add (formatLiteral); + foreach (var item in GetConcatItems (context, expr)) { + if (IsStringLiteral (item)) { + var stringLiteral = (PrimitiveExpression)item; + + if (stringLiteral.LiteralValue [0] == '@') { + verbatim = true; + format.Append (stringLiteral.LiteralValue, 2, stringLiteral.LiteralValue.Length - 3); + } else { + format.Append (stringLiteral.LiteralValue, 1, stringLiteral.LiteralValue.Length - 2); + } + } else { + var index = IndexOf (arguments, item); + if (index == -1) { + // new item + formatInvocation.Arguments.Add (item.Clone ()); + arguments.Add (item); + format.Append ("{" + counter++ + "}"); + } else { + // existing item + format.Append ("{" + index + "}"); + } + } + } + format.Append ('"'); + if (verbatim) + format.Insert (0, '@'); + formatLiteral.LiteralValue = format.ToString (); + script.Replace (expr, formatInvocation); + }); + } + + static int IndexOf (IList arguments, Expression item) + { + for (int i = 0; i < arguments.Count; i++) { + if (item.Match (arguments [i]).Success) + return i; + } + return -1; + } + + static IEnumerable GetConcatItems (RefactoringContext context, BinaryOperatorExpression expr) + { + var leftExpr = expr.Left as BinaryOperatorExpression; + if (IsStringConcatenation(context, leftExpr)) { + foreach (var item in GetConcatItems (context, leftExpr)) + yield return item; + } else { + yield return expr.Left; + } + + var rightExpr = expr.Right as BinaryOperatorExpression; + if (IsStringConcatenation(context, rightExpr)) { + foreach (var item in GetConcatItems (context, rightExpr)) + yield return item; + } else { + yield return expr.Right; + } + } + + static bool IsStringConcatenation (RefactoringContext context, BinaryOperatorExpression expr) + { + if (expr == null || expr.Operator != BinaryOperatorType.Add) + return false; + var typeDef = context.Resolve (expr).Type.GetDefinition(); + return typeDef != null && typeDef.KnownTypeCode == KnownTypeCode.String; + } + + static bool IsStringLiteral (AstNode node) + { + var expr = node as PrimitiveExpression; + return expr != null && expr.Value is string; + } + } +} diff --git a/ICSharpCode.NRefactory.Tests/CSharp/CodeActions/UseStringFormatTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/CodeActions/UseStringFormatTests.cs new file mode 100644 index 0000000000..56d4e41b76 --- /dev/null +++ b/ICSharpCode.NRefactory.Tests/CSharp/CodeActions/UseStringFormatTests.cs @@ -0,0 +1,99 @@ +// +// UseStringFormatTests.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 UseStringFormatTests : ContextActionTestBase + { + [Test] + public void Test () + { + Test (@" +class TestClass +{ + void TestMethod () + { + string str = 1 + $2 + ""test"" + 1 + ""test"" + 1.1; + } +}", @" +class TestClass +{ + void TestMethod () + { + string str = string.Format (""{0}test{1}test{2}"", 1 + 2, 1, 1.1); + } +}"); + } + + [Test] + public void TestVerbatim () + { + Test (@" +class TestClass +{ + void TestMethod () + { + string str = $@"" +test "" + 1; + } +}", @" +class TestClass +{ + void TestMethod () + { + string str = string.Format (@"" +test {0}"", 1); + } +}"); + } + + [Test] + public void TestRepeatedObject () + { + Test (@" +class TestClass +{ + void TestMethod () + { + int i = 0; + string str = $""test"" + i + ""test"" + i; + } +}", @" +class TestClass +{ + void TestMethod () + { + int i = 0; + string str = string.Format (""test{0}test{0}"", i); + } +}"); + } + } +} diff --git a/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj b/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj index af7e5dcb0c..110211ec76 100644 --- a/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj +++ b/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj @@ -92,6 +92,7 @@ +