From b60b7f7c8ea6d85af6d24d94d34350de1c4288cd Mon Sep 17 00:00:00 2001 From: Ekaterina Tuzova Date: Tue, 27 Nov 2012 19:29:21 +0400 Subject: [PATCH] added extract parameter refactoring --- .../com/jetbrains/python/PyBundle.properties | 3 + .../refactoring/PyRefactoringProvider.java | 7 + .../introduce/IntroduceHandler.java | 42 +++--- .../field/PyIntroduceFieldHandler.java | 20 +-- .../PyIntroduceParameterHandler.java | 136 ++++++++++++++++++ .../introduceParameter/kwParameter.after.py | 3 + .../introduceParameter/kwParameter.py | 3 + .../introduceParameter/parameter.after.py | 3 + .../introduceParameter/parameter.py | 4 + .../introduceParameter/referencedParameter.py | 3 + .../introduceParameter/simple.after.py | 2 + .../refactoring/introduceParameter/simple.py | 2 + .../refactoring/PyIntroduceParameterTest.java | 47 ++++++ 13 files changed, 246 insertions(+), 29 deletions(-) create mode 100644 python/src/com/jetbrains/python/refactoring/introduce/parameter/PyIntroduceParameterHandler.java create mode 100644 python/testData/refactoring/introduceParameter/kwParameter.after.py create mode 100644 python/testData/refactoring/introduceParameter/kwParameter.py create mode 100644 python/testData/refactoring/introduceParameter/parameter.after.py create mode 100644 python/testData/refactoring/introduceParameter/parameter.py create mode 100644 python/testData/refactoring/introduceParameter/referencedParameter.py create mode 100644 python/testData/refactoring/introduceParameter/simple.after.py create mode 100644 python/testData/refactoring/introduceParameter/simple.py create mode 100644 python/testSrc/com/jetbrains/python/refactoring/PyIntroduceParameterTest.java diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties index 06673f0d5c81..e6110d86f9a7 100644 --- a/python/src/com/jetbrains/python/PyBundle.properties +++ b/python/src/com/jetbrains/python/PyBundle.properties @@ -452,6 +452,9 @@ refactoring.introduce.variable.scope.error=Name clashes with existing variable o refactoring.introduce.constant.dialog.title=Extract Constant refactoring.introduce.constant.scope.error=Name is already declared in scope +# introduce parameter +refactoring.introduce.parameter.dialog.title=Extract Parameter + # pull up refactoring.pull.up.dialog.title=Pull members up to refactoring.pull.up.error.cannot.perform.refactoring.using.selected.elements=Cannot perform pull member up using selected element(s) diff --git a/python/src/com/jetbrains/python/refactoring/PyRefactoringProvider.java b/python/src/com/jetbrains/python/refactoring/PyRefactoringProvider.java index a19a56f595d9..caee4384ea5b 100644 --- a/python/src/com/jetbrains/python/refactoring/PyRefactoringProvider.java +++ b/python/src/com/jetbrains/python/refactoring/PyRefactoringProvider.java @@ -13,6 +13,7 @@ import com.jetbrains.python.refactoring.classes.extractSuperclass.PyExtractSuper import com.jetbrains.python.refactoring.classes.pullUp.PyPullUpHandler; import com.jetbrains.python.refactoring.classes.pushDown.PyPushDownHandler; import com.jetbrains.python.refactoring.extractmethod.PyExtractMethodHandler; +import com.jetbrains.python.refactoring.introduce.parameter.PyIntroduceParameterHandler; import com.jetbrains.python.refactoring.introduce.constant.PyIntroduceConstantHandler; import com.jetbrains.python.refactoring.introduce.field.PyIntroduceFieldHandler; import com.jetbrains.python.refactoring.introduce.variable.PyIntroduceVariableHandler; @@ -74,4 +75,10 @@ public class PyRefactoringProvider extends RefactoringSupportProvider { public ChangeSignatureHandler getChangeSignatureHandler() { return new PyChangeSignatureHandler(); } + + @Nullable + @Override + public RefactoringActionHandler getIntroduceParameterHandler() { + return new PyIntroduceParameterHandler(); + } } diff --git a/python/src/com/jetbrains/python/refactoring/introduce/IntroduceHandler.java b/python/src/com/jetbrains/python/refactoring/introduce/IntroduceHandler.java index d20dacd695d1..a330bb006be8 100644 --- a/python/src/com/jetbrains/python/refactoring/introduce/IntroduceHandler.java +++ b/python/src/com/jetbrains/python/refactoring/introduce/IntroduceHandler.java @@ -391,15 +391,17 @@ abstract public class IntroduceHandler implements RefactoringActionHandler { } protected void performInplaceIntroduce(IntroduceOperation operation) { - final PyAssignmentStatement statement = performRefactoring(operation); - PyTargetExpression target = (PyTargetExpression) statement.getTargets() [0]; - final List occurrences = operation.getOccurrences(); - final PsiElement occurrence = findOccurrenceUnderCaret(occurrences, operation.getEditor()); - PsiElement elementForCaret = occurrence != null ? occurrence : target; - operation.getEditor().getCaretModel().moveToOffset(elementForCaret.getTextRange().getStartOffset()); - final InplaceVariableIntroducer introducer = - new PyInplaceVariableIntroducer(target, operation, occurrences); - introducer.performInplaceRefactoring(new LinkedHashSet(operation.getSuggestedNames())); + final PsiElement statement = performRefactoring(operation); + if (statement instanceof PyAssignmentStatement) { + PyTargetExpression target = (PyTargetExpression) ((PyAssignmentStatement)statement).getTargets() [0]; + final List occurrences = operation.getOccurrences(); + final PsiElement occurrence = findOccurrenceUnderCaret(occurrences, operation.getEditor()); + PsiElement elementForCaret = occurrence != null ? occurrence : target; + operation.getEditor().getCaretModel().moveToOffset(elementForCaret.getTextRange().getStartOffset()); + final InplaceVariableIntroducer introducer = + new PyInplaceVariableIntroducer(target, operation, occurrences); + introducer.performInplaceRefactoring(new LinkedHashSet(operation.getSuggestedNames())); + } } protected void performIntroduceWithDialog(IntroduceOperation operation) { @@ -415,14 +417,14 @@ abstract public class IntroduceHandler implements RefactoringActionHandler { operation.setInitPlace(dialog.getInitPlace()); } - PyAssignmentStatement declaration = performRefactoring(operation); + PsiElement declaration = performRefactoring(operation); final Editor editor = operation.getEditor(); editor.getCaretModel().moveToOffset(declaration.getTextRange().getEndOffset()); editor.getSelectionModel().removeSelection(); } - protected PyAssignmentStatement performRefactoring(IntroduceOperation operation) { - PyAssignmentStatement declaration = createDeclaration(operation); + protected PsiElement performRefactoring(IntroduceOperation operation) { + PsiElement declaration = createDeclaration(operation); declaration = performReplace(declaration, operation); declaration = CodeInsightUtilBase.forcePsiPostprocessAndRestoreElement(declaration); @@ -506,12 +508,12 @@ abstract public class IntroduceHandler implements RefactoringActionHandler { return PyRefactoringUtil.getOccurrences(expression, context); } - private PyAssignmentStatement performReplace(@NotNull final PyAssignmentStatement declaration, - final IntroduceOperation operation) { + private PsiElement performReplace(@NotNull final PsiElement declaration, + final IntroduceOperation operation) { final PyExpression expression = operation.getInitializer(); final Project project = operation.getProject(); - return new WriteCommandAction(project, expression.getContainingFile()) { - protected void run(final Result result) throws Throwable { + return new WriteCommandAction(project, expression.getContainingFile()) { + protected void run(final Result result) throws Throwable { result.setResult(addDeclaration(operation, declaration)); PyExpression newExpression = createExpression(project, operation.getName(), declaration); @@ -537,18 +539,18 @@ abstract public class IntroduceHandler implements RefactoringActionHandler { } @Nullable - public PyAssignmentStatement addDeclaration(IntroduceOperation operation, PyAssignmentStatement declaration) { + public PsiElement addDeclaration(IntroduceOperation operation, PsiElement declaration) { final PsiElement expression = operation.getInitializer(); final Pair data = expression.getUserData(PyPsiUtils.SELECTION_BREAKS_AST_NODE); if (data == null) { - return (PyAssignmentStatement)addDeclaration(expression, declaration, operation); + return addDeclaration(expression, declaration, operation); } else { - return (PyAssignmentStatement)addDeclaration(data.first, declaration, operation); + return addDeclaration(data.first, declaration, operation); } } - protected PyExpression createExpression(Project project, String name, PyAssignmentStatement declaration) { + protected PyExpression createExpression(Project project, String name, PsiElement declaration) { return PyElementGenerator.getInstance(project).createExpressionFromText(name); } diff --git a/python/src/com/jetbrains/python/refactoring/introduce/field/PyIntroduceFieldHandler.java b/python/src/com/jetbrains/python/refactoring/introduce/field/PyIntroduceFieldHandler.java index bd2f9074fc7e..b15679205eb4 100644 --- a/python/src/com/jetbrains/python/refactoring/introduce/field/PyIntroduceFieldHandler.java +++ b/python/src/com/jetbrains/python/refactoring/introduce/field/PyIntroduceFieldHandler.java @@ -196,7 +196,7 @@ public class PyIntroduceFieldHandler extends IntroduceHandler { } @Override - protected PyExpression createExpression(Project project, String name, PyAssignmentStatement declaration) { + protected PyExpression createExpression(Project project, String name, PsiElement declaration) { final String text = declaration.getText(); final String self_name = text.substring(0, text.indexOf('.')); return PyElementGenerator.getInstance(project).createExpressionFromText(self_name + "." + name); @@ -281,15 +281,17 @@ public class PyIntroduceFieldHandler extends IntroduceHandler { @Override protected void performInplaceIntroduce(IntroduceOperation operation) { - final PyAssignmentStatement statement = performRefactoring(operation); + final PsiElement statement = performRefactoring(operation); // put caret on identifier after "self." - final List occurrences = operation.getOccurrences(); - final PsiElement occurrence = findOccurrenceUnderCaret(occurrences, operation.getEditor()); - PyTargetExpression target = (PyTargetExpression) statement.getTargets() [0]; - putCaretOnFieldName(operation.getEditor(), occurrence != null ? occurrence : target); - final InplaceVariableIntroducer introducer = new PyInplaceFieldIntroducer(target, operation, occurrences); - introducer.performInplaceRefactoring(new LinkedHashSet(operation.getSuggestedNames())); - } + if (statement instanceof PyAssignmentStatement) { + final List occurrences = operation.getOccurrences(); + final PsiElement occurrence = findOccurrenceUnderCaret(occurrences, operation.getEditor()); + PyTargetExpression target = (PyTargetExpression) ((PyAssignmentStatement)statement).getTargets() [0]; + putCaretOnFieldName(operation.getEditor(), occurrence != null ? occurrence : target); + final InplaceVariableIntroducer introducer = new PyInplaceFieldIntroducer(target, operation, occurrences); + introducer.performInplaceRefactoring(new LinkedHashSet(operation.getSuggestedNames())); + } + } private static void putCaretOnFieldName(Editor editor, PsiElement occurrence) { PyQualifiedExpression qExpr = PsiTreeUtil.getParentOfType(occurrence, PyQualifiedExpression.class, false); diff --git a/python/src/com/jetbrains/python/refactoring/introduce/parameter/PyIntroduceParameterHandler.java b/python/src/com/jetbrains/python/refactoring/introduce/parameter/PyIntroduceParameterHandler.java new file mode 100644 index 000000000000..fbf8edaf50a9 --- /dev/null +++ b/python/src/com/jetbrains/python/refactoring/introduce/parameter/PyIntroduceParameterHandler.java @@ -0,0 +1,136 @@ +package com.jetbrains.python.refactoring.introduce.parameter; + +import com.intellij.codeInsight.CodeInsightUtilBase; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiReference; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.refactoring.RefactoringBundle; +import com.intellij.refactoring.introduce.inplace.InplaceVariableIntroducer; +import com.intellij.refactoring.util.CommonRefactoringUtil; +import com.jetbrains.python.PyBundle; +import com.jetbrains.python.psi.*; +import com.jetbrains.python.refactoring.introduce.IntroduceHandler; +import com.jetbrains.python.refactoring.introduce.IntroduceOperation; +import com.jetbrains.python.refactoring.introduce.variable.VariableValidator; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; + +/** + * User: ktisha + */ +public class PyIntroduceParameterHandler extends IntroduceHandler { + public PyIntroduceParameterHandler() { + super(new VariableValidator(), PyBundle.message("refactoring.introduce.parameter.dialog.title")); + } + + @Override + protected String getHelpId() { + return "python.reference.introduceParameter"; + } + + @Nullable + @Override + protected PsiElement addDeclaration(@NotNull PsiElement expression, + @NotNull PsiElement declaration, + @NotNull IntroduceOperation operation) { + return doIntroduceParameter(expression, (PyAssignmentStatement)declaration); + } + + + public PsiElement doIntroduceParameter(PsiElement expression, PyAssignmentStatement declaration) { + PyFunction function = PsiTreeUtil.getParentOfType(expression, PyFunction.class); + if (function != null && declaration != null) { + PyParameterList parameterList = function.getParameterList(); + List newParams = new ArrayList(); + if (parameterList.hasKeywordContainer()) { + for (PyParameter parameter : parameterList.getParameters()) { + if (parameter instanceof PyNamedParameter && ((PyNamedParameter)parameter).isKeywordContainer()) { + newParams.add(declaration.getText()); + } + newParams.add(parameter.getText()); + } + final PyParameterList replacementList = + PyElementGenerator.getInstance(function.getProject()).createFromText(LanguageLevel.forElement(parameterList), + PyFunction.class, "def f(" + + StringUtil.join(newParams, ",") + + "): pass").getParameterList(); + parameterList = (PyParameterList)parameterList.replace(replacementList); + CodeInsightUtilBase.forcePsiPostprocessAndRestoreElement(parameterList); + } + else { + parameterList.addParameter(PyElementGenerator.getInstance(function.getProject()).createParameter(declaration.getText())); + } + CodeInsightUtilBase.forcePsiPostprocessAndRestoreElement(function); + return parameterList.findParameterByName(declaration.getTargets()[0].getText()); + } + return null; + } + + @Override + protected boolean checkIntroduceContext(PsiFile file, Editor editor, PsiElement element) { + if (element != null) { + final PyFunction function = PsiTreeUtil.getParentOfType(element, PyFunction.class); + if (function == null) { + CommonRefactoringUtil.showErrorHint(file.getProject(), editor, + "Introduce Parameter refactoring cannot be performed outside any function", + RefactoringBundle.message("introduce.parameter.title"), null); + return false; + } + if (isResolvedToParameter(element)) { + CommonRefactoringUtil.showErrorHint(file.getProject(), editor, + PyBundle.message("refactoring.introduce.selection.error"), + RefactoringBundle.message("introduce.parameter.title"), null); + return false; + } + } + return super.checkIntroduceContext(file, editor, element); + } + + private boolean isResolvedToParameter(PsiElement element) { + while (element instanceof PyReferenceExpression) { + final PsiReference reference = element.getReference(); + if (reference != null && reference.resolve() instanceof PyNamedParameter) + return true; + element = ((PyReferenceExpression)element).getQualifier(); + } + return false; + } + + @Override + protected void performInplaceIntroduce(IntroduceOperation operation) { + final PsiElement statement = performRefactoring(operation); + if (statement instanceof PyNamedParameter) { + final List occurrences = operation.getOccurrences(); + final PsiElement occurrence = findOccurrenceUnderCaret(occurrences, operation.getEditor()); + PsiElement elementForCaret = occurrence != null ? occurrence : statement; + operation.getEditor().getCaretModel().moveToOffset(elementForCaret.getTextRange().getStartOffset()); + final InplaceVariableIntroducer introducer = + new PyInplaceParameterIntroducer((PyNamedParameter)statement, operation, occurrences); + introducer.performInplaceRefactoring(new LinkedHashSet(operation.getSuggestedNames())); + } + } + + private static class PyInplaceParameterIntroducer extends InplaceVariableIntroducer { + private final PyNamedParameter myTarget; + + public PyInplaceParameterIntroducer(PyNamedParameter target, + IntroduceOperation operation, + List occurrences) { + super(target, operation.getEditor(), operation.getProject(), "Introduce Parameter", + occurrences.toArray(new PsiElement[occurrences.size()]), null); + myTarget = target; + } + + @Override + protected PsiElement checkLocalScope() { + return myTarget.getContainingFile(); + } + } +} diff --git a/python/testData/refactoring/introduceParameter/kwParameter.after.py b/python/testData/refactoring/introduceParameter/kwParameter.after.py new file mode 100644 index 000000000000..46c11921ed21 --- /dev/null +++ b/python/testData/refactoring/introduceParameter/kwParameter.after.py @@ -0,0 +1,3 @@ +def funcq(param1, a="test", **args): + b = a + return b \ No newline at end of file diff --git a/python/testData/refactoring/introduceParameter/kwParameter.py b/python/testData/refactoring/introduceParameter/kwParameter.py new file mode 100644 index 000000000000..0629b39d2804 --- /dev/null +++ b/python/testData/refactoring/introduceParameter/kwParameter.py @@ -0,0 +1,3 @@ +def funcq(param1, **args): + b = "test" + return b \ No newline at end of file diff --git a/python/testData/refactoring/introduceParameter/parameter.after.py b/python/testData/refactoring/introduceParameter/parameter.after.py new file mode 100644 index 000000000000..6594d65eff2d --- /dev/null +++ b/python/testData/refactoring/introduceParameter/parameter.after.py @@ -0,0 +1,3 @@ +y = 1 +def f(x, a=y.foo.bar): + return a.baz() \ No newline at end of file diff --git a/python/testData/refactoring/introduceParameter/parameter.py b/python/testData/refactoring/introduceParameter/parameter.py new file mode 100644 index 000000000000..e2310bb3e210 --- /dev/null +++ b/python/testData/refactoring/introduceParameter/parameter.py @@ -0,0 +1,4 @@ +y = 1 +def f(x): + return y.foo\ + .bar.baz() \ No newline at end of file diff --git a/python/testData/refactoring/introduceParameter/referencedParameter.py b/python/testData/refactoring/introduceParameter/referencedParameter.py new file mode 100644 index 000000000000..611cd134f551 --- /dev/null +++ b/python/testData/refactoring/introduceParameter/referencedParameter.py @@ -0,0 +1,3 @@ +def f(x): + return x.foo\ + .bar.baz() \ No newline at end of file diff --git a/python/testData/refactoring/introduceParameter/simple.after.py b/python/testData/refactoring/introduceParameter/simple.after.py new file mode 100644 index 000000000000..317c0b10c3c9 --- /dev/null +++ b/python/testData/refactoring/introduceParameter/simple.after.py @@ -0,0 +1,2 @@ +def f(x, a="test"): + return a \ No newline at end of file diff --git a/python/testData/refactoring/introduceParameter/simple.py b/python/testData/refactoring/introduceParameter/simple.py new file mode 100644 index 000000000000..b3387a4326d6 --- /dev/null +++ b/python/testData/refactoring/introduceParameter/simple.py @@ -0,0 +1,2 @@ +def f(x): + return "test" \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/refactoring/PyIntroduceParameterTest.java b/python/testSrc/com/jetbrains/python/refactoring/PyIntroduceParameterTest.java new file mode 100644 index 000000000000..b4ff5bb1e631 --- /dev/null +++ b/python/testSrc/com/jetbrains/python/refactoring/PyIntroduceParameterTest.java @@ -0,0 +1,47 @@ +package com.jetbrains.python.refactoring; + +import com.intellij.refactoring.util.CommonRefactoringUtil; +import com.intellij.testFramework.TestDataPath; +import com.jetbrains.python.PyBundle; +import com.jetbrains.python.refactoring.introduce.IntroduceHandler; +import com.jetbrains.python.refactoring.introduce.parameter.PyIntroduceParameterHandler; + +/** + * User: ktisha + */ +@TestDataPath("$CONTENT_ROOT/../testData/refactoring/introduceParameter/") +public class PyIntroduceParameterTest extends PyIntroduceTestCase { + public void testSimple() { + doTest(); + } + + public void testReferencedParameter() { + doTestCannotPerform(PyBundle.message("refactoring.introduce.selection.error")); + } + + public void testParameter() { + doTest(); + } + + public void testKwParameter() { + doTest(); + } + + @Override + protected String getTestDataPath() { + return super.getTestDataPath() + "/refactoring/introduceParameter"; + } + + protected IntroduceHandler createHandler() { + return new PyIntroduceParameterHandler(); + } + + private void doTestCannotPerform(String expected) { + try { + doTest(); + } + catch (CommonRefactoringUtil.RefactoringErrorHintException e) { + assertEquals(expected, e.getMessage()); + } + } +}