added extract parameter refactoring

This commit is contained in:
Ekaterina Tuzova
2012-11-27 19:29:21 +04:00
parent 1715bfa31a
commit b60b7f7c8e
13 changed files with 246 additions and 29 deletions
@@ -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)
@@ -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();
}
}
@@ -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<PsiElement> 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<PsiElement> introducer =
new PyInplaceVariableIntroducer(target, operation, occurrences);
introducer.performInplaceRefactoring(new LinkedHashSet<String>(operation.getSuggestedNames()));
final PsiElement statement = performRefactoring(operation);
if (statement instanceof PyAssignmentStatement) {
PyTargetExpression target = (PyTargetExpression) ((PyAssignmentStatement)statement).getTargets() [0];
final List<PsiElement> 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<PsiElement> introducer =
new PyInplaceVariableIntroducer(target, operation, occurrences);
introducer.performInplaceRefactoring(new LinkedHashSet<String>(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<PyAssignmentStatement>(project, expression.getContainingFile()) {
protected void run(final Result<PyAssignmentStatement> result) throws Throwable {
return new WriteCommandAction<PsiElement>(project, expression.getContainingFile()) {
protected void run(final Result<PsiElement> 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<PsiElement, TextRange> 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);
}
@@ -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<PsiElement> 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<PsiElement> introducer = new PyInplaceFieldIntroducer(target, operation, occurrences);
introducer.performInplaceRefactoring(new LinkedHashSet<String>(operation.getSuggestedNames()));
}
if (statement instanceof PyAssignmentStatement) {
final List<PsiElement> 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<PsiElement> introducer = new PyInplaceFieldIntroducer(target, operation, occurrences);
introducer.performInplaceRefactoring(new LinkedHashSet<String>(operation.getSuggestedNames()));
}
}
private static void putCaretOnFieldName(Editor editor, PsiElement occurrence) {
PyQualifiedExpression qExpr = PsiTreeUtil.getParentOfType(occurrence, PyQualifiedExpression.class, false);
@@ -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<String> newParams = new ArrayList<String>();
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<PsiElement> 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<PsiElement> introducer =
new PyInplaceParameterIntroducer((PyNamedParameter)statement, operation, occurrences);
introducer.performInplaceRefactoring(new LinkedHashSet<String>(operation.getSuggestedNames()));
}
}
private static class PyInplaceParameterIntroducer extends InplaceVariableIntroducer<PsiElement> {
private final PyNamedParameter myTarget;
public PyInplaceParameterIntroducer(PyNamedParameter target,
IntroduceOperation operation,
List<PsiElement> 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();
}
}
}
@@ -0,0 +1,3 @@
def funcq(param1, a="test", **args):
b = a
return b
@@ -0,0 +1,3 @@
def funcq(param1, **args):
b = "tes<caret>t"
return b
@@ -0,0 +1,3 @@
y = 1
def f(x, a=y.foo.bar):
return a.baz()
@@ -0,0 +1,4 @@
y = 1
def f(x):
return <selection>y.foo\
.bar</selection>.baz()
@@ -0,0 +1,3 @@
def f(x):
return <selection>x.foo\
.bar</selection>.baz()
@@ -0,0 +1,2 @@
def f(x, a="test"):
return a
@@ -0,0 +1,2 @@
def f(x):
return "<caret>test"
@@ -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());
}
}
}