diff --git a/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj b/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj index cee761fe32..8db055f87e 100644 --- a/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj +++ b/ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj @@ -352,6 +352,7 @@ + diff --git a/ICSharpCode.NRefactory.CSharp/Refactoring/CodeActions/CreateMethodDeclarationAction.cs b/ICSharpCode.NRefactory.CSharp/Refactoring/CodeActions/CreateMethodDeclarationAction.cs index fbe2bce8c5..4a20266edf 100644 --- a/ICSharpCode.NRefactory.CSharp/Refactoring/CodeActions/CreateMethodDeclarationAction.cs +++ b/ICSharpCode.NRefactory.CSharp/Refactoring/CodeActions/CreateMethodDeclarationAction.cs @@ -228,7 +228,7 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring return sb.Length == 0 ? "str" : sb.ToString(); } - static string CreateBaseName(AstNode node, IType type) + public static string CreateBaseName(AstNode node, IType type) { string name = null; if (node is DirectionExpression) { @@ -243,16 +243,58 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring if (pe.Value is string) { name = CreateBaseNameFromString(pe.Value.ToString()); } else { - name = char.ToLower(type.Name [0]).ToString(); + return char.ToLower(type.Name [0]).ToString(); } } else { - name = type.Kind == TypeKind.Unknown ? "par" : type.Name; + if (type.Kind == TypeKind.Unknown) { + return "par"; + } + name = GuessNameFromType(type); } name = char.ToLower(name [0]) + name.Substring(1); return name; } + static string GuessNameFromType(IType returnType) + { + switch (returnType.ReflectionName) { + case "System.Byte": + case "System.SByte": + return "b"; + + case "System.Int16": + case "System.UInt16": + case "System.Int32": + case "System.UInt32": + case "System.Int64": + case "System.UInt64": + return "i"; + + case "System.Boolean": + return "b"; + + case "System.DateTime": + return "date"; + + case "System.Char": + return "ch"; + case "System.String": + return "str"; + + case "System.Exception": + return "e"; + case "System.Object": + return "obj"; + case "System.Func": + return "func"; + case "System.Action": + return "action"; + } + return returnType.Name; + } + + string GetMethodName(InvocationExpression invocation) { if (invocation.Target is IdentifierExpression) { diff --git a/ICSharpCode.NRefactory.CSharp/Refactoring/CodeActions/DeclareLocalVariableAction.cs b/ICSharpCode.NRefactory.CSharp/Refactoring/CodeActions/DeclareLocalVariableAction.cs new file mode 100644 index 0000000000..9a42982c3a --- /dev/null +++ b/ICSharpCode.NRefactory.CSharp/Refactoring/CodeActions/DeclareLocalVariableAction.cs @@ -0,0 +1,154 @@ +// +// DeclareLocalVariableAction.cs +// +// Author: +// Mike Krüger +// +// Copyright (c) 2012 Xamarin Inc. (http://xamarin.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.Threading; +using System.Collections.Generic; +using System.Linq; +using ICSharpCode.NRefactory.Semantics; +using ICSharpCode.NRefactory.CSharp.Resolver; +using ICSharpCode.NRefactory.TypeSystem; +using ICSharpCode.NRefactory.PatternMatching; + +namespace ICSharpCode.NRefactory.CSharp.Refactoring +{ + [ContextAction("Declare local variable", Description = "Declare a local variable out of a selected expression.")] + public class DeclareLocalVariableAction : ICodeActionProvider + { + public IEnumerable GetActions(RefactoringContext context) + { + if (!context.IsSomethingSelected) { + yield break; + } + var selected = new List(context.GetSelectedNodes()); + if (selected.Count != 1 || !(selected [0] is Expression)) { + yield break; + } + var expr = selected [0] as Expression; + var visitor = new SearchNodeVisitior(expr); + + var node = context.GetNode (); + if (node != null) { + node.AcceptVisitor(visitor); + } + + yield return new CodeAction(context.TranslateString("Declare local variable"), script => { + var resolveResult = context.Resolve(expr); + var guessedType = resolveResult.Type; + if (resolveResult is MethodGroupResolveResult) { + guessedType = GetDelegateType(context, ((MethodGroupResolveResult)resolveResult).Methods.First(), expr); + } + var name = CreateMethodDeclarationAction.CreateBaseName(expr, guessedType); + var varDecl = new VariableDeclarationStatement(context.CreateShortType(guessedType), name, expr.Clone()); + if (expr.Parent is ExpressionStatement) { + script.Replace(expr.Parent, varDecl); + } else { + var containing = expr.Parent; + while (!(containing.Parent is BlockStatement)) { + containing = containing.Parent; + } + + script.InsertBefore(containing, varDecl); + script.Replace(expr, new IdentifierExpression(name)); + } + }); + + if (visitor.Matches.Count > 1) { + yield return new CodeAction(string.Format(context.TranslateString("Declare local variable (replace '{0}' occurrences)"), visitor.Matches), script => { + var resolveResult = context.Resolve(expr); + var guessedType = resolveResult.Type; + if (resolveResult is MethodGroupResolveResult) { + guessedType = GetDelegateType(context, ((MethodGroupResolveResult)resolveResult).Methods.First(), expr); + } + var name = CreateMethodDeclarationAction.CreateBaseName(expr, guessedType); + var varDecl = new VariableDeclarationStatement(context.CreateShortType(guessedType), name, expr.Clone()); + var first = visitor.Matches [0]; + if (first.Parent is ExpressionStatement) { + script.Replace(first.Parent, varDecl); + } else { + var containing = first.Parent; + while (!(containing.Parent is BlockStatement)) { + containing = containing.Parent; + } + + script.InsertBefore(containing, varDecl); + script.Replace(first, new IdentifierExpression(name)); + } + for (int i = 1; i < visitor.Matches.Count; i++) { + script.Replace(visitor.Matches[i], new IdentifierExpression(name)); + } + }); + } + } + + // Gets Action/Func delegate types for a given method. + IType GetDelegateType(RefactoringContext context, IMethod method, Expression expr) + { + var parameters = new List(); + var invoke = expr.Parent as InvocationExpression; + if (invoke == null) { + return null; + } + foreach (var arg in invoke.Arguments) { + parameters.Add(context.Resolve(arg).Type); + } + + ITypeDefinition genericType; + if (method.ReturnType.FullName == "System.Void") { + genericType = context.Compilation.GetAllTypeDefinitions().FirstOrDefault(t => t.FullName == "System.Action" && t.TypeParameterCount == parameters.Count); + } else { + parameters.Add(method.ReturnType); + genericType = context.Compilation.GetAllTypeDefinitions().FirstOrDefault(t => t.FullName == "System.Func" && t.TypeParameterCount == parameters.Count); + } + if (genericType == null) { + return null; + } + return new ParameterizedType(genericType, parameters); + } + + + class SearchNodeVisitior : DepthFirstAstVisitor + { + readonly AstNode searchForNode; + public readonly List Matches = new List (); + + public SearchNodeVisitior (AstNode searchForNode) + { + this.searchForNode = searchForNode; + Matches.Add (searchForNode); + } + + protected override void VisitChildren(AstNode node) + { + + if (node.StartLocation > searchForNode.StartLocation && node.IsMatch (searchForNode)) + Matches.Add (node); + base.VisitChildren (node); + } + } + + + } +} diff --git a/ICSharpCode.NRefactory.CSharp/Refactoring/CodeActions/InlineLocalVariableAction.cs b/ICSharpCode.NRefactory.CSharp/Refactoring/CodeActions/InlineLocalVariableAction.cs index f7e3b6f6b2..d24adf3783 100644 --- a/ICSharpCode.NRefactory.CSharp/Refactoring/CodeActions/InlineLocalVariableAction.cs +++ b/ICSharpCode.NRefactory.CSharp/Refactoring/CodeActions/InlineLocalVariableAction.cs @@ -38,6 +38,9 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring static FindReferences refFinder = new FindReferences(); public IEnumerable GetActions(RefactoringContext context) { + if (context.IsSomethingSelected) { + yield break; + } var node = context.GetNode(); if (node == null || node.Variables.Count != 1) { yield break; diff --git a/ICSharpCode.NRefactory.CSharp/Refactoring/CodeActions/IntroduceFormatItemAction.cs b/ICSharpCode.NRefactory.CSharp/Refactoring/CodeActions/IntroduceFormatItemAction.cs index d4ee99a62f..039c433d2f 100644 --- a/ICSharpCode.NRefactory.CSharp/Refactoring/CodeActions/IntroduceFormatItemAction.cs +++ b/ICSharpCode.NRefactory.CSharp/Refactoring/CodeActions/IntroduceFormatItemAction.cs @@ -93,12 +93,13 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring return new PrimitiveExpression (context.SelectedText); } - static PrimitiveExpression CreateFormatString (RefactoringContext context, PrimitiveExpression pExpr, int argumentNumber) + static PrimitiveExpression CreateFormatString(RefactoringContext context, PrimitiveExpression pExpr, int argumentNumber) { - var start = context.GetOffset (pExpr.StartLocation); - var end = context.GetOffset (pExpr.EndLocation); - - return new PrimitiveExpression ("", context.GetText (start, context.SelectionStart - start) + "{" + argumentNumber + "}" + context.GetText (context.SelectionEnd, end - context.SelectionEnd)); + var start = context.GetOffset(pExpr.StartLocation); + var end = context.GetOffset(pExpr.EndLocation); + var sStart = context.GetOffset(context.SelectionStart); + var sEnd = context.GetOffset(context.SelectionEnd); + return new PrimitiveExpression("", context.GetText(start, sStart - start) + "{" + argumentNumber + "}" + context.GetText(sEnd, end - sEnd)); } } } diff --git a/ICSharpCode.NRefactory.CSharp/Refactoring/CodeActions/SplitDeclarationAndAssignmentAction.cs b/ICSharpCode.NRefactory.CSharp/Refactoring/CodeActions/SplitDeclarationAndAssignmentAction.cs index 7c635a0b4d..5ee9cab932 100644 --- a/ICSharpCode.NRefactory.CSharp/Refactoring/CodeActions/SplitDeclarationAndAssignmentAction.cs +++ b/ICSharpCode.NRefactory.CSharp/Refactoring/CodeActions/SplitDeclarationAndAssignmentAction.cs @@ -36,6 +36,9 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring { public IEnumerable GetActions(RefactoringContext context) { + if (context.IsSomethingSelected) { + yield break; + } AstType type; var varDecl = GetVariableDeclarationStatement(context, out type); if (varDecl == null) { diff --git a/ICSharpCode.NRefactory.CSharp/Refactoring/RefactoringContext.cs b/ICSharpCode.NRefactory.CSharp/Refactoring/RefactoringContext.cs index e668e45298..fb1a0262e5 100644 --- a/ICSharpCode.NRefactory.CSharp/Refactoring/RefactoringContext.cs +++ b/ICSharpCode.NRefactory.CSharp/Refactoring/RefactoringContext.cs @@ -33,11 +33,14 @@ using ICSharpCode.NRefactory.Semantics; using ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.NRefactory.TypeSystem.Implementation; using ICSharpCode.NRefactory.Editor; +using System.Collections.Generic; namespace ICSharpCode.NRefactory.CSharp.Refactoring { public abstract class RefactoringContext : BaseRefactoringContext { + + public RefactoringContext(CSharpAstResolver resolver, CancellationToken cancellationToken) : base (resolver, cancellationToken) { } @@ -51,17 +54,27 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring return builder.ConvertType(fullType); } - public AstType CreateShortType (string ns, string name, int typeParameterCount = 0) + public AstType CreateShortType(string ns, string name, int typeParameterCount = 0) { foreach (var asm in Compilation.Assemblies) { - var def = asm.GetTypeDefinition (ns, name, typeParameterCount); - if (def != null) - return CreateShortType (def); + var def = asm.GetTypeDefinition(ns, name, typeParameterCount); + if (def != null) { + return CreateShortType(def); + } } - return new MemberType (new SimpleType (ns), name); + return new MemberType(new SimpleType(ns), name); } - + + public virtual IEnumerable GetSelectedNodes() + { + if (!IsSomethingSelected) { + return Enumerable.Empty (); + } + + return RootNode.GetNodesBetween(SelectionStart, SelectionEnd); + } + public AstNode GetNode () { return RootNode.GetNodeAt (Location); @@ -83,21 +96,25 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring } public virtual bool IsSomethingSelected { - get { return this.SelectionLength > 0; } + get { + return SelectionStart != TextLocation.Empty; + } } public virtual string SelectedText { get { return string.Empty; } } - public virtual int SelectionStart { - get { return 0; } - } - public virtual int SelectionEnd { - get { return 0; } + public virtual TextLocation SelectionStart { + get { + return TextLocation.Empty; + } } - public virtual int SelectionLength { - get { return 0; } + + public virtual TextLocation SelectionEnd { + get { + return TextLocation.Empty; + } } public abstract int GetOffset (TextLocation location); diff --git a/ICSharpCode.NRefactory.Tests/CSharp/CodeActions/ContextActionTestBase.cs b/ICSharpCode.NRefactory.Tests/CSharp/CodeActions/ContextActionTestBase.cs index 721c9f0da7..ee8bfe7210 100644 --- a/ICSharpCode.NRefactory.Tests/CSharp/CodeActions/ContextActionTestBase.cs +++ b/ICSharpCode.NRefactory.Tests/CSharp/CodeActions/ContextActionTestBase.cs @@ -53,9 +53,9 @@ namespace ICSharpCode.NRefactory.CSharp.CodeActions return sb.ToString (); } - public void Test (string input, string output) where T : ICodeActionProvider, new () + public void Test (string input, string output, int action = 0) where T : ICodeActionProvider, new () { - string result = RunContextAction (new T (), HomogenizeEol (input)); + string result = RunContextAction (new T (), HomogenizeEol (input), action); bool passed = result == output; if (!passed) { Console.WriteLine ("-----------Expected:"); @@ -67,7 +67,7 @@ namespace ICSharpCode.NRefactory.CSharp.CodeActions } - protected static string RunContextAction (ICodeActionProvider action, string input) + protected static string RunContextAction (ICodeActionProvider action, string input, int actionIndex = 0) { var context = TestRefactoringContext.Create (input); bool isValid = action.GetActions (context).Any (); @@ -76,7 +76,7 @@ namespace ICSharpCode.NRefactory.CSharp.CodeActions Console.WriteLine ("invalid node is:" + context.GetNode ()); Assert.IsTrue (isValid, action.GetType () + " is invalid."); using (var script = context.StartScript ()) { - action.GetActions (context).First ().Run (script); + action.GetActions (context).Skip (actionIndex).First ().Run (script); } return context.doc.Text; diff --git a/ICSharpCode.NRefactory.Tests/CSharp/CodeActions/DeclareLocalVariableTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/CodeActions/DeclareLocalVariableTests.cs new file mode 100644 index 0000000000..b7446c2b3f --- /dev/null +++ b/ICSharpCode.NRefactory.Tests/CSharp/CodeActions/DeclareLocalVariableTests.cs @@ -0,0 +1,175 @@ +// +// DeclareLocalVariableTests.cs +// +// Author: +// Mike Krüger +// +// Copyright (c) 2012 Xamarin Inc. (http://xamarin.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 NUnit.Framework; +using ICSharpCode.NRefactory.CSharp.Refactoring; + +namespace ICSharpCode.NRefactory.CSharp.CodeActions +{ + [TestFixture] + public class DeclareLocalVariableTests : ContextActionTestBase + { + [Test()] + public void TestSimpleInline () + { + Test (@"class TestClass +{ + int Foo() {} + void Test () + { + <-Foo()->; + } +}", @"class TestClass +{ + int Foo() {} + void Test () + { + int i = Foo (); + } +}"); + } + + [Test()] + public void TestReplaceAll () + { + Test (@"class TestClass +{ + void Test () + { + Console.WriteLine (<-5 + 3->); + Console.WriteLine (5 + 3); + Console.WriteLine (5 + 3); + } +}", @"class TestClass +{ + void Test () + { + int i = 5 + 3; + Console.WriteLine (i); + Console.WriteLine (i); + Console.WriteLine (i); + } +}", 1); + } + + [Test()] + public void DeclareLocalExpressionTest () + { + Test (@"class TestClass +{ + void Test () + { + Console.WriteLine (1 +<- 9 ->+ 5); + } +} +", @"class TestClass +{ + void Test () + { + int i = 9; + Console.WriteLine (1 + i + 5); + } +} +"); + } + + /// + /// Bug 693855 - Extracting variable from ELSE IF puts it in the wrong place + /// + [Test()] + public void TestBug693855 () + { + Test (@"class TestClass +{ + void Test () + { + string str = ""test""; + if (str == ""something"") { + //do A + } else if (<-str == ""other""->) { + //do B + } else { + //do C + } + } +}", +@"class TestClass +{ + void Test () + { + string str = ""test""; + bool b = str == ""other""; + if (str == ""something"") { + //do A + } else if (b) { + //do B + } else { + //do C + } + } +}"); + } + + + /// + /// Bug 693875 - Extract Local on just method name leaves brackets in wrong place + /// + [Test()] + public void TestBug693875 () + { + Test (@"class TestClass +{ + void DoStuff() + { + if (<-GetInt->() == 0) { + } + } + + int GetInt() + { + return 1; + } +}", +@"class TestClass +{ + void DoStuff() + { + System.Func getInt = GetInt; + if (getInt() == 0) { + } + } + + int GetInt() + { + return 1; + } +}"); + } + + + } +} diff --git a/ICSharpCode.NRefactory.Tests/CSharp/CodeActions/TestRefactoringContext.cs b/ICSharpCode.NRefactory.Tests/CSharp/CodeActions/TestRefactoringContext.cs index c6de917d36..2633e762b1 100644 --- a/ICSharpCode.NRefactory.Tests/CSharp/CodeActions/TestRefactoringContext.cs +++ b/ICSharpCode.NRefactory.Tests/CSharp/CodeActions/TestRefactoringContext.cs @@ -112,17 +112,15 @@ namespace ICSharpCode.NRefactory.CSharp.CodeActions #region Text stuff public override string EolMarker { get { return Environment.NewLine; } } - public override bool IsSomethingSelected { get { return SelectionStart > 0; } } + public override bool IsSomethingSelected { get { return selectionStart > 0; } } - public override string SelectedText { get { return IsSomethingSelected ? doc.GetText (SelectionStart, SelectionLength) : ""; } } + public override string SelectedText { get { return IsSomethingSelected ? doc.GetText (selectionStart, selectionEnd - selectionStart) : ""; } } int selectionStart; - public override int SelectionStart { get { return selectionStart; } } + public override TextLocation SelectionStart { get { return doc.GetLocation (selectionStart); } } int selectionEnd; - public override int SelectionEnd { get { return selectionEnd; } } - - public override int SelectionLength { get { return IsSomethingSelected ? SelectionEnd - SelectionStart : 0; } } + public override TextLocation SelectionEnd { get { return doc.GetLocation (selectionEnd); } } public override int GetOffset (TextLocation location) { @@ -190,7 +188,6 @@ namespace ICSharpCode.NRefactory.CSharp.CodeActions TextLocation location = TextLocation.Empty; if (idx >= 0) location = doc.GetLocation (idx); - Console.WriteLine ("idx:" + location); return new TestRefactoringContext(doc, location, resolver) { selectionStart = selectionStart, selectionEnd = selectionEnd diff --git a/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj b/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj index be18f3bd3f..945d5cc6a6 100644 --- a/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj +++ b/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj @@ -255,6 +255,7 @@ +