diff --git a/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj b/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj index b1a32abb74..a0871f1848 100644 --- a/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj +++ b/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj @@ -404,6 +404,7 @@ + diff --git a/ICSharpCode.NRefactory.CSharp/Refactoring/CodeActions/ConvertToInitializer/ConvertInitializerToExplicitInitializationsAction.cs b/ICSharpCode.NRefactory.CSharp/Refactoring/CodeActions/ConvertToInitializer/ConvertInitializerToExplicitInitializationsAction.cs new file mode 100644 index 0000000000..1dbb82c9d0 --- /dev/null +++ b/ICSharpCode.NRefactory.CSharp/Refactoring/CodeActions/ConvertToInitializer/ConvertInitializerToExplicitInitializationsAction.cs @@ -0,0 +1,208 @@ +// +// ConvertInitializerToExplicitInitializationsAction.cs +// +// Author: +// Simon Lindgren +// +// Copyright (c) 2012 Simon Lindgren +// +// 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.Collections.Generic; +using System.Linq; + +namespace ICSharpCode.NRefactory.CSharp.Refactoring +{ + [ContextAction("Convert to explicit initializers", + Description = "Converts an object or collection initializer to explicit initializations.")] + public class ConvertInitializerToExplicitInitializationsAction : ICodeActionProvider + { + #region ICodeActionProvider implementation + + public IEnumerable GetActions(RefactoringContext context) + { + var codeAction = GetActionForVariableInitializer(context); + if (codeAction != null) { + yield return codeAction; + yield break; + } + codeAction = GetActionForAssignmentExpression(context); + if (codeAction != null) { + yield return codeAction; + yield break; + } + } + + public CodeAction GetActionForVariableInitializer(RefactoringContext context) + { + var variableInitializer = context.GetNode(); + if (variableInitializer == null) + return null; + var declaration = variableInitializer.Parent as VariableDeclarationStatement; + if (declaration == null) + return null; + if (variableInitializer.Initializer.IsNull) + return null; + var objectCreateExpression = variableInitializer.Initializer as ObjectCreateExpression; + if (objectCreateExpression == null) + return null; + var converter = new InitializerConversionVisitor(context); + Expression finalExpression; + var statements = converter.ConvertInitializer(objectCreateExpression, out finalExpression); + if (statements.Count > 0) { + return new CodeAction(context.TranslateString("Convert to explicit initializers"), script => { + foreach (var statement in statements) { + script.InsertBefore(declaration, statement); + } + script.Replace(variableInitializer.Initializer, finalExpression); + }); + } + return null; + } + + public CodeAction GetActionForAssignmentExpression(RefactoringContext context) + { + var assignmentExpression = context.GetNode(); + if (assignmentExpression == null) + return null; + var expressionStatement = assignmentExpression.Parent as ExpressionStatement; + if (expressionStatement == null) + return null; + var objectCreateExpression = assignmentExpression.Right as ObjectCreateExpression; + if (objectCreateExpression == null) + return null; + var converter = new InitializerConversionVisitor(context); + Expression finalExpression; + var statements = converter.ConvertInitializer(objectCreateExpression, out finalExpression); + if (statements.Count > 0) { + return new CodeAction(context.TranslateString("Convert to explicit initializers"), script => { + foreach (var statement in statements) { + script.InsertBefore(expressionStatement, statement); + } + script.Replace(assignmentExpression.Right, finalExpression); + }); + } + return null; + } + #endregion + + class InitializerConversionVisitor : DepthFirstAstVisitor + { + RefactoringContext context; + IList statements; + NamingHelper namingHelper; + + public InitializerConversionVisitor(RefactoringContext context) + { + this.context = context; + namingHelper = new NamingHelper(context); + } + + AstType GetDeclarationType(AstType type) + { + AstType declarationType; + if (context.UseExplicitTypes) { + declarationType = type.Clone(); + } else { + declarationType = new SimpleType("var"); + } + return declarationType; + } + + public IList ConvertInitializer(ObjectCreateExpression initializer, out Expression finalExpression) + { + statements = new List(); + + finalExpression = initializer.AcceptVisitor(this, null); + + return statements; + } + + public override Expression VisitObjectCreateExpression(ObjectCreateExpression objectCreateExpression, Expression data) + { + var creationType = objectCreateExpression.Type.Clone(); + + var parameters = objectCreateExpression.Arguments.Select(arg => arg.Clone()); + var newCreation = new ObjectCreateExpression(creationType, parameters); + if (objectCreateExpression.Initializer.IsNull) { + return newCreation; + } else { + AstType declarationType = GetDeclarationType(objectCreateExpression.Type); + var name = namingHelper.GenerateVariableName(objectCreateExpression.Type); + var variableInitializer = new VariableDeclarationStatement(declarationType, name, newCreation); + statements.Add(variableInitializer); + + var identifier = new IdentifierExpression(name); + base.VisitObjectCreateExpression(objectCreateExpression, identifier); + + return identifier; + } + } + + public override Expression VisitArrayInitializerExpression(ArrayInitializerExpression arrayInitializerExpression, Expression data) + { + if (!(arrayInitializerExpression.Parent is ArrayInitializerExpression)) { + return base.VisitArrayInitializerExpression(arrayInitializerExpression, data); + } + // this a tuple in a collection initializer + var arguments = arrayInitializerExpression.Elements.Select(element => element.AcceptVisitor(this, null).Clone()); + var method = new MemberReferenceExpression { + Target = data.Clone(), + MemberName = "Add" + }; + var statement = new ExpressionStatement(new InvocationExpression(method, arguments)); + statements.Add(statement); + + return null; + } + + public override Expression VisitNamedExpression(NamedExpression namedExpression, Expression data) + { + var member = new MemberReferenceExpression { + Target = data.Clone(), + MemberName = namedExpression.Name + }; + var expression = namedExpression.Expression.AcceptVisitor(this, member); + if (expression != null) { + var statement = new ExpressionStatement { + Expression = new AssignmentExpression { + Left = member, + Right = expression.Clone() + } + }; + statements.Add(statement); + } + + return null; + } + + protected override Expression VisitChildren(AstNode node, Expression data) + { + // Most expressions should just be used as-is, and + // a) need not be visited + // b) only return themselves + if (node is Expression && !(node is ObjectCreateExpression || node is ArrayInitializerExpression || node is NamedExpression)){ + return (Expression)node; + } + return base.VisitChildren(node, data); + } + } + } +} + diff --git a/ICSharpCode.NRefactory.Tests/CSharp/CodeActions/ConvertToInitializer/ConvertInitializerToExplicitIntializationsTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/CodeActions/ConvertToInitializer/ConvertInitializerToExplicitIntializationsTests.cs new file mode 100644 index 0000000000..b507069cb0 --- /dev/null +++ b/ICSharpCode.NRefactory.Tests/CSharp/CodeActions/ConvertToInitializer/ConvertInitializerToExplicitIntializationsTests.cs @@ -0,0 +1,141 @@ +// +// ConvertInitializerToExplicitIntializationsTests.cs +// +// Author: +// Simon Lindgren +// +// Copyright (c) 2012 Simon Lindgren +// +// 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 NUnit.Framework; +using ICSharpCode.NRefactory.CSharp.Refactoring; + +namespace ICSharpCode.NRefactory.CSharp.CodeActions +{ + [TestFixture] + public class ConvertInitializerToExplicitIntializationsTests : ContextActionTestBase + { + string baseText = @" +class TestClass +{ + public string Property { get; set; } + + public TestClass Nested { get; set; } + + void F () + { + "; + + [Test] + public void SingleLevelObjectIntializer () + { + Test(baseText + @" + var variable = new Test$Class () { + Property = ""Value"" + }; + } +}", baseText + @" + var testClass = new TestClass (); + testClass.Property = ""Value""; + var variable = testClass; + } +}"); + } + + [Test] + public void ObjectCreateExpressionWithoutObjectInitializer () + { + Test(baseText + @" + var variable $= new System.Collection.Generic.List () { + new TestClass () { + Property = ""One"", + Nested = new TestClass () + } + }; + } +}", baseText + @" + var list = new System.Collection.Generic.List (); + var testClass = new TestClass (); + testClass.Property = ""One""; + testClass.Nested = new TestClass (); + list.Add (testClass); + var variable = list; + } +}"); + } + + [Test] + public void SingleLevelObjectIntializerToExistingVariable () + { + Test(baseText + @" + var variable = new TestClass (); + variable = new Test$Class () { + Property = ""Value"" + }; + } +}", baseText + @" + var variable = new TestClass (); + var testClass = new TestClass (); + testClass.Property = ""Value""; + variable = testClass; + } +}"); + } + + [Test] + public void SingleLevelCollectionIntializer () + { + Test(baseText + @" + var variable $= new System.Collection.Generic.Dictionary () { + {""Key1"", new TestClass { Property = ""Value1"", Nested = { Property = ""Value1b"" }}} + }; + } +}", baseText + @" + var dictionary = new System.Collection.Generic.Dictionary (); + var testClass = new TestClass (); + testClass.Property = ""Value1""; + testClass.Nested.Property = ""Value1b""; + dictionary.Add (""Key1"", testClass); + var variable = dictionary; + } +}"); + } + + [Test] + public void CollectionOfIntializers () + { + Test(baseText + @" + var variable $= new System.Collection.Generic.Dictionary () { + {""Key1"", new TestClass { Property = ""Value1"", Nested = { Property = ""Value1b"" }}} + }; + } +}", baseText + @" + var dictionary = new System.Collection.Generic.Dictionary (); + var testClass = new TestClass (); + testClass.Property = ""Value1""; + testClass.Nested.Property = ""Value1b""; + dictionary.Add (""Key1"", testClass); + var variable = dictionary; + } +}"); + } + + } +} + diff --git a/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj b/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj index 4f0cfee082..5de6deb2fb 100644 --- a/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj +++ b/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj @@ -297,6 +297,7 @@ +