From 470d4d0ac5c857ff30ce79df6da453f1c184fd8b Mon Sep 17 00:00:00 2001 From: "Ilya.Kazakevich" Date: Wed, 26 Feb 2014 00:30:40 +0400 Subject: [PATCH] PY-12213 When moving, members should be sorted according to their dependencies --- .../MemberDependenciesStorage.java | 6 +- .../src/com/jetbrains/python/psi/PyUtil.java | 31 +++++++ .../classes/DependencyVisitor.java | 87 +++++++++++++++++++ .../classes/PyClassRefactoringUtil.java | 65 ++++++++++++-- .../classes/PyDependenciesComparator.java | 70 +++++++++++++++ .../PyExtractSuperclassPresenterImpl.java | 4 +- .../membersManager/ClassFieldsManager.java | 7 +- .../membersManager/InstanceFieldsManager.java | 2 +- .../membersManager/MembersManager.java | 19 +++- .../vp/MembersBasedPresenterImpl.java | 10 ++- .../MembersBasedPresenterNoPreviewImpl.java | 6 +- .../MembersBasedPresenterWithPreviewImpl.java | 6 +- .../classes/pullUp/PyPullUpPresenter.java | 4 + .../classes/pullUp/PyPullUpPresenterImpl.java | 6 +- .../classes/pullUp/PyPullUpViewSwingImpl.java | 2 + .../pushDown/PyPushDownPresenterImpl.java | 3 +- .../testData/refactoring/dependenciesTest.py | 9 ++ .../pullup/dependenciesOrder.after.py | 30 +++++++ .../refactoring/pullup/dependenciesOrder.py | 28 ++++++ .../classes/PyDependenciesComparatorTest.java | 40 +++++++++ .../classes/pullUp/PyPullUpTest.java | 11 ++- 21 files changed, 419 insertions(+), 27 deletions(-) create mode 100644 python/src/com/jetbrains/python/refactoring/classes/DependencyVisitor.java create mode 100644 python/src/com/jetbrains/python/refactoring/classes/PyDependenciesComparator.java create mode 100644 python/testData/refactoring/dependenciesTest.py create mode 100644 python/testData/refactoring/pullup/dependenciesOrder.after.py create mode 100644 python/testData/refactoring/pullup/dependenciesOrder.py create mode 100644 python/testSrc/com/jetbrains/python/refactoring/classes/PyDependenciesComparatorTest.java diff --git a/platform/lang-impl/src/com/intellij/refactoring/classMembers/MemberDependenciesStorage.java b/platform/lang-impl/src/com/intellij/refactoring/classMembers/MemberDependenciesStorage.java index d01dc0f51e9e..fb3a597fda06 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/classMembers/MemberDependenciesStorage.java +++ b/platform/lang-impl/src/com/intellij/refactoring/classMembers/MemberDependenciesStorage.java @@ -28,7 +28,7 @@ import java.util.Set; public class MemberDependenciesStorage { protected final C myClass; - private final C mySuperClass; + private C mySuperClass; private final Map> myDependencyGraph; public MemberDependenciesStorage(C aClass, C superClass) { @@ -37,6 +37,10 @@ public class MemberDependenciesStorage>(); } + public void setSuperClass(C superClass) { + mySuperClass = superClass; + } + @Nullable protected Set getMemberDependencies(T member) { Set result = myDependencyGraph.get(member); diff --git a/python/src/com/jetbrains/python/psi/PyUtil.java b/python/src/com/jetbrains/python/psi/PyUtil.java index 8af009bf41ed..83036eec8626 100644 --- a/python/src/com/jetbrains/python/psi/PyUtil.java +++ b/python/src/com/jetbrains/python/psi/PyUtil.java @@ -63,6 +63,7 @@ import com.jetbrains.python.codeInsight.stdlib.PyNamedTupleType; import com.jetbrains.python.psi.impl.PyBuiltinCache; import com.jetbrains.python.psi.impl.PyPsiUtils; import com.jetbrains.python.psi.types.*; +import com.jetbrains.python.refactoring.classes.PyDependenciesComparator; import com.jetbrains.python.refactoring.classes.extractSuperclass.PyExtractSuperclassHelper; import com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo; import org.jetbrains.annotations.NonNls; @@ -1285,6 +1286,36 @@ public class PyUtil { return instanceOf(element, toSkip) ? null : element; } + /** + * Adds element to statement list to the correct place according to its dependencies. + * @param element to insert + * @param statementList where element should be inserted + * @return inserted element + */ + public static T addElementToStatementList(@NotNull final T element, + @NotNull final PyStatementList statementList) { + PsiElement before = null; + PsiElement after = null; + for (final PyStatement statement : statementList.getStatements()) { + if (PyDependenciesComparator.depends(element, statement)) { + after = statement; + }else if (PyDependenciesComparator.depends(statement, element)) { + before = statement; + } + } + final PsiElement result; + if (after != null) { + + result = statementList.addAfter(element, after); + }else if (before != null) { + result = statementList.addBefore(element, before); + } else { + result = addElementToStatementList(element, statementList, true); + } + @SuppressWarnings("unchecked") // Inserted element can't have different type + final T resultCasted = (T)result; + return resultCasted; + } public static PsiElement addElementToStatementList(@NotNull PsiElement element, @NotNull PyStatementList statementList, diff --git a/python/src/com/jetbrains/python/refactoring/classes/DependencyVisitor.java b/python/src/com/jetbrains/python/refactoring/classes/DependencyVisitor.java new file mode 100644 index 000000000000..b22449d9abb0 --- /dev/null +++ b/python/src/com/jetbrains/python/refactoring/classes/DependencyVisitor.java @@ -0,0 +1,87 @@ +package com.jetbrains.python.refactoring.classes; + +import com.intellij.openapi.util.Condition; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiPolyVariantReference; +import com.intellij.psi.PsiReference; +import com.intellij.psi.util.PsiTreeUtil; +import com.jetbrains.python.psi.*; +import org.jetbrains.annotations.NotNull; + +/** + * Searches for element in another element's usages. + * Parametrize it with needle and make stack to accept it. + * + * @author Ilya.Kazakevich + */ +class DependencyVisitor extends PyRecursiveElementVisitor { + + @NotNull + private final PyElement myElementToFind; + private boolean myDependencyFound; + + /** + * @param elementToFind what to find + */ + DependencyVisitor(@NotNull final PyElement elementToFind) { + myElementToFind = elementToFind; + } + + @Override + public void visitPyCallExpression(@NotNull final PyCallExpression node) { + final PyExpression callee = node.getCallee(); + if (callee != null) { + final PsiReference calleeReference = callee.getReference(); + if ((calleeReference != null) && calleeReference.isReferenceTo(myElementToFind)) { + myDependencyFound = true; + return; + } + final String calleeName = callee.getName(); + + if ((calleeName != null) && calleeName.equals(myElementToFind.getName())) { // Check by name also + myDependencyFound = true; + } + } + } + + @Override + public void visitPyReferenceExpression(final PyReferenceExpression node) { + + final PsiPolyVariantReference reference = node.getReference(); + if (reference.isReferenceTo(myElementToFind)) { + myDependencyFound = true; + return; + } + // TODO: This step is member-type specific. Move to MemberManagers? + if (myElementToFind instanceof PyAssignmentStatement) { + final PyExpression[] targets = ((PyAssignmentStatement)myElementToFind).getTargets(); + + if (targets.length != 1) { + return; + } + final PyExpression expression = targets[0]; + + if (reference.isReferenceTo(expression)) { + myDependencyFound = true; + return; + } + if (node.getText().equals(expression.getText())) { // Check by name also + myDependencyFound = true; + } + return; + } + final PsiElement declaration = reference.resolve(); + myDependencyFound = PsiTreeUtil.findFirstParent(declaration, new PsiElementCondition()) != null; + } + + public boolean isDependencyFound() { + return myDependencyFound; + } + + private class PsiElementCondition implements Condition { + @Override + public boolean value(final PsiElement psiElement) { + return psiElement.equals(myElementToFind); + } + } +} diff --git a/python/src/com/jetbrains/python/refactoring/classes/PyClassRefactoringUtil.java b/python/src/com/jetbrains/python/refactoring/classes/PyClassRefactoringUtil.java index 50ebb54a6919..79f0382a8e23 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/PyClassRefactoringUtil.java +++ b/python/src/com/jetbrains/python/refactoring/classes/PyClassRefactoringUtil.java @@ -60,19 +60,68 @@ public final class PyClassRefactoringUtil { /** * Copies class field declarations to some other place * - * @param assignmentStatement list of class fields + * @param assignmentStatements list of class fields + * @param dequalifyIfDeclaredInClass If not null method will check if field declared in this class. + * If declared -- qualifier will be removed. + * For example: MyClass.Foo will become Foo it this param is MyClass. * @return new (copied) fields */ @NotNull - public static List copyFieldDeclarationToStatement(@NotNull final Collection assignmentStatement, - @NotNull final PyStatementList superClassStatement) { - final List declations = new ArrayList(assignmentStatement.size()); - for (final PyAssignmentStatement expression : assignmentStatement) { - final PyAssignmentStatement newDeclaration = (PyAssignmentStatement)expression.copy(); - declations.add((PyAssignmentStatement)PyUtil.addElementToStatementList(newDeclaration, superClassStatement, true)); + public static List copyFieldDeclarationToStatement(@NotNull final Collection assignmentStatements, + @NotNull final PyStatementList superClassStatement, + @Nullable final PyClass dequalifyIfDeclaredInClass) { + final List declarations = new ArrayList(assignmentStatements.size()); + Collections.sort(declarations, PyDependenciesComparator.INSTANCE); + + + for (final PyAssignmentStatement pyAssignmentStatement : assignmentStatements) { + final PyElement value = pyAssignmentStatement.getAssignedValue(); + final PyAssignmentStatement newDeclaration = (PyAssignmentStatement)pyAssignmentStatement.copy(); + + if (value instanceof PyReferenceExpression && dequalifyIfDeclaredInClass != null) { + final String newValue = getNewValueToAssign((PyReferenceExpression)value, dequalifyIfDeclaredInClass); + + setNewAssigneeValue(newDeclaration, newValue); + + } + + declarations.add(PyUtil.addElementToStatementList(newDeclaration, superClassStatement)); PyPsiUtils.removeRedundantPass(superClassStatement); } - return declations; + return declarations; + } + + /** + * Sets new value to assignment statement. + * @param assignmentStatement statement to change + * @param newValue new value + */ + private static void setNewAssigneeValue(@NotNull final PyAssignmentStatement assignmentStatement, @NotNull final String newValue) { + final PyExpression oldValue = assignmentStatement.getAssignedValue(); + final PyExpression newExpression = + PyElementGenerator.getInstance(assignmentStatement.getProject()).createExpressionFromText(LanguageLevel.forElement(assignmentStatement), newValue); + if (oldValue != null) { + oldValue.replace(newExpression); + } else { + assignmentStatement.add(newExpression); + } + } + + /** + * Checks if current value declared in provided class and removes class qualifier if true + * @param currentValue current value + * @param dequalifyIfDeclaredInClass class to check + * @return value as string + */ + @NotNull + private static String getNewValueToAssign(@NotNull final PyReferenceExpression currentValue, @NotNull final PyClass dequalifyIfDeclaredInClass) { + final PyExpression qualifier = currentValue.getQualifier(); + if ((qualifier instanceof PyReferenceExpression) && + ((PyReferenceExpression)qualifier).getReference().isReferenceTo(dequalifyIfDeclaredInClass)) { + final String name = currentValue.getName(); + return ((name != null) ? name : currentValue.getText()); + } + return currentValue.getText(); } @NotNull diff --git a/python/src/com/jetbrains/python/refactoring/classes/PyDependenciesComparator.java b/python/src/com/jetbrains/python/refactoring/classes/PyDependenciesComparator.java new file mode 100644 index 000000000000..700a300e0906 --- /dev/null +++ b/python/src/com/jetbrains/python/refactoring/classes/PyDependenciesComparator.java @@ -0,0 +1,70 @@ +package com.jetbrains.python.refactoring.classes; + +import com.jetbrains.python.psi.*; +import org.jetbrains.annotations.NotNull; + +import java.io.Serializable; +import java.util.Comparator; + +/** + * Compares elements by their dependencies. + * If A depends on B, then A < B + */ +public class PyDependenciesComparator implements Comparator, Serializable { + + /** + * Singleton comparator instance + */ + public static final PyDependenciesComparator INSTANCE = new PyDependenciesComparator(); + + private PyDependenciesComparator() { + } + + @Override + public int compare(@NotNull final PyElement o1, @NotNull final PyElement o2) { + if (depends(o1, o2)) { + return 1; + } + if (depends(o2, o1)) { + return -1; + } + return getBlockType(o1).compareTo(getBlockType(o2)); + } + + @NotNull + private static BlockType getBlockType(@NotNull final PyElement statement) { + for (BlockType type : BlockType.values()) { + if (type.myClass.isAssignableFrom(statement.getClass())) { + return type; + } + } + + return BlockType.OTHER; + } + + /** + * @return true if first param depends on second. + */ + public static boolean depends(@NotNull final PyElement o1, @NotNull final PyElement o2) { + final DependencyVisitor visitor = new DependencyVisitor(o2); + o1.accept(visitor); + return visitor.isDependencyFound(); + } + + /** + * Types of class members in order, they should appear + */ + private enum BlockType { + DOC(PyExpressionStatement.class), + DECLARATION(PyAssignmentStatement.class), + METHOD(PyFunction.class), + OTHER(PyElement.class); + + @NotNull + private final Class myClass; + + BlockType(@NotNull final Class aClass) { + myClass = aClass; + } + } +} diff --git a/python/src/com/jetbrains/python/refactoring/classes/extractSuperclass/PyExtractSuperclassPresenterImpl.java b/python/src/com/jetbrains/python/refactoring/classes/extractSuperclass/PyExtractSuperclassPresenterImpl.java index f39b335f8d77..a61d7a6fed07 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/extractSuperclass/PyExtractSuperclassPresenterImpl.java +++ b/python/src/com/jetbrains/python/refactoring/classes/extractSuperclass/PyExtractSuperclassPresenterImpl.java @@ -8,6 +8,7 @@ import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.refactoring.RefactoringBundle; +import com.intellij.refactoring.classMembers.MemberInfoModel; import com.jetbrains.python.PyBundle; import com.jetbrains.python.PythonLanguage; import com.jetbrains.python.psi.PyClass; @@ -27,7 +28,8 @@ import java.util.Collections; /** * @author Ilya.Kazakevich */ -class PyExtractSuperclassPresenterImpl extends MembersBasedPresenterNoPreviewImpl +class PyExtractSuperclassPresenterImpl extends MembersBasedPresenterNoPreviewImpl>> implements PyExtractSuperclassPresenter { private final NamesValidator myNamesValidator = LanguageNamesValidation.INSTANCE.forLanguage(PythonLanguage.getInstance()); diff --git a/python/src/com/jetbrains/python/refactoring/classes/membersManager/ClassFieldsManager.java b/python/src/com/jetbrains/python/refactoring/classes/membersManager/ClassFieldsManager.java index 42444df279c3..3bf4f00553e1 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/membersManager/ClassFieldsManager.java +++ b/python/src/com/jetbrains/python/refactoring/classes/membersManager/ClassFieldsManager.java @@ -1,6 +1,9 @@ package com.jetbrains.python.refactoring.classes.membersManager; -import com.jetbrains.python.psi.*; +import com.jetbrains.python.psi.PyAssignmentStatement; +import com.jetbrains.python.psi.PyClass; +import com.jetbrains.python.psi.PyElement; +import com.jetbrains.python.psi.PyTargetExpression; import com.jetbrains.python.refactoring.classes.PyClassRefactoringUtil; import org.jetbrains.annotations.NotNull; @@ -31,7 +34,7 @@ class ClassFieldsManager extends FieldsManager { //TODO: Copy/paste with InstanceFieldsManager. Move to parent? final List result = new ArrayList(); for (final PyClass destClass : to) { - result.addAll(PyClassRefactoringUtil.copyFieldDeclarationToStatement(statements, destClass.getStatementList())); + result.addAll(PyClassRefactoringUtil.copyFieldDeclarationToStatement(statements, destClass.getStatementList(), destClass)); } deleteElements(statements); PyClassRefactoringUtil.insertPassIfNeeded(from); diff --git a/python/src/com/jetbrains/python/refactoring/classes/membersManager/InstanceFieldsManager.java b/python/src/com/jetbrains/python/refactoring/classes/membersManager/InstanceFieldsManager.java index 7ae68cd6b646..a1e19c455a0f 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/membersManager/InstanceFieldsManager.java +++ b/python/src/com/jetbrains/python/refactoring/classes/membersManager/InstanceFieldsManager.java @@ -67,7 +67,7 @@ class InstanceFieldsManager extends FieldsManager { toInitMethod = createInitMethod(to); } final PyStatementList statementList = toInitMethod.getStatementList(); - return PyClassRefactoringUtil.copyFieldDeclarationToStatement(members, statementList); + return PyClassRefactoringUtil.copyFieldDeclarationToStatement(members, statementList, null); } /** diff --git a/python/src/com/jetbrains/python/refactoring/classes/membersManager/MembersManager.java b/python/src/com/jetbrains/python/refactoring/classes/membersManager/MembersManager.java index ff4d20d68c75..437b8085d765 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/membersManager/MembersManager.java +++ b/python/src/com/jetbrains/python/refactoring/classes/membersManager/MembersManager.java @@ -18,9 +18,7 @@ package com.jetbrains.python.refactoring.classes.membersManager; import com.google.common.base.Function; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; -import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Collections2; -import com.google.common.collect.Multimap; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiNamedElement; import com.intellij.util.ArrayUtil; @@ -29,6 +27,7 @@ import com.jetbrains.NotNullPredicate; import com.jetbrains.python.psi.PyClass; import com.jetbrains.python.psi.PyElement; import com.jetbrains.python.refactoring.classes.PyClassRefactoringUtil; +import com.jetbrains.python.refactoring.classes.PyDependenciesComparator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -103,6 +102,20 @@ public abstract class MembersManager implements Function> memberInfosSorted = new ArrayList>(memberInfos); + Collections.sort(memberInfosSorted, new Comparator>() { + @Override + public int compare(PyMemberInfo o1, PyMemberInfo o2) { + return PyDependenciesComparator.INSTANCE.compare(o1.getMember(), o2.getMember()); + } + }); + + for (PyMemberInfo info : memberInfosSorted) { + TypeSafeMovingStrategy.moveCheckingTypesAtRunTime(from, info.getMembersManager(), Collections.singleton(info), to); + } + + + /*//Move at once, sort final Multimap, PyMemberInfo> managerToMember = ArrayListMultimap.create(); //Collect map (manager)->(list_of_memebers) for (final PyMemberInfo memberInfo : memberInfos) { @@ -112,7 +125,7 @@ public abstract class MembersManager implements Function membersManager : managerToMember.keySet()) { final Collection> members = managerToMember.get(membersManager); TypeSafeMovingStrategy.moveCheckingTypesAtRunTime(from, membersManager, members, to); - } + }*/ PyClassRefactoringUtil.insertPassIfNeeded(from); } diff --git a/python/src/com/jetbrains/python/refactoring/classes/membersManager/vp/MembersBasedPresenterImpl.java b/python/src/com/jetbrains/python/refactoring/classes/membersManager/vp/MembersBasedPresenterImpl.java index e0d99d4e176b..57f437fcb655 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/membersManager/vp/MembersBasedPresenterImpl.java +++ b/python/src/com/jetbrains/python/refactoring/classes/membersManager/vp/MembersBasedPresenterImpl.java @@ -19,9 +19,11 @@ import java.util.Collection; * or {@link com.jetbrains.python.refactoring.classes.membersManager.vp.MembersBasedPresenterWithPreviewImpl} instead * * @param view for that presenter + * @param Type of model {@link #myModel} * @author Ilya.Kazakevich */ -abstract class MembersBasedPresenterImpl> implements MembersBasedPresenter { +abstract class MembersBasedPresenterImpl, + M extends MemberInfoModel>> implements MembersBasedPresenter { @NotNull protected final T myView; @NotNull @@ -32,18 +34,18 @@ abstract class MembersBasedPresenterImpl> implemen * Member model */ @NotNull - protected final MemberInfoModel> myModel; + protected final M myModel; /** * @param view View for presenter * @param classUnderRefactoring class to be refactored * @param infoStorage info storage - * @param model Member model (to be used for dependencies checking) + * @param model Member model (to be used for dependencies checking) */ MembersBasedPresenterImpl(@NotNull final T view, @NotNull final PyClass classUnderRefactoring, @NotNull final PyMemberInfoStorage infoStorage, - @NotNull final MemberInfoModel> model) { + @NotNull final M model) { myView = view; myClassUnderRefactoring = classUnderRefactoring; myStorage = infoStorage; diff --git a/python/src/com/jetbrains/python/refactoring/classes/membersManager/vp/MembersBasedPresenterNoPreviewImpl.java b/python/src/com/jetbrains/python/refactoring/classes/membersManager/vp/MembersBasedPresenterNoPreviewImpl.java index 5ec793a8a405..dfbe3da04d35 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/membersManager/vp/MembersBasedPresenterNoPreviewImpl.java +++ b/python/src/com/jetbrains/python/refactoring/classes/membersManager/vp/MembersBasedPresenterNoPreviewImpl.java @@ -14,10 +14,12 @@ import org.jetbrains.annotations.NotNull; * To "preview" button would be displayed * * @param view for this presenter + * @param Type of model * @author Ilya.Kazakevich */ -public abstract class MembersBasedPresenterNoPreviewImpl> extends MembersBasedPresenterImpl { +public abstract class MembersBasedPresenterNoPreviewImpl, + M extends MemberInfoModel>> extends MembersBasedPresenterImpl { /** * @param view view for this presenter * @param classUnderRefactoring class to refactor @@ -27,7 +29,7 @@ public abstract class MembersBasedPresenterNoPreviewImpl> model) { + @NotNull final M model) { super(view, classUnderRefactoring, infoStorage, model); } diff --git a/python/src/com/jetbrains/python/refactoring/classes/membersManager/vp/MembersBasedPresenterWithPreviewImpl.java b/python/src/com/jetbrains/python/refactoring/classes/membersManager/vp/MembersBasedPresenterWithPreviewImpl.java index c247c745ca5e..8950a14b6108 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/membersManager/vp/MembersBasedPresenterWithPreviewImpl.java +++ b/python/src/com/jetbrains/python/refactoring/classes/membersManager/vp/MembersBasedPresenterWithPreviewImpl.java @@ -14,9 +14,11 @@ import org.jetbrains.annotations.NotNull; * "Preview" button would be displayed. * * @param view for this presenter + * @param Type of model * @author Ilya.Kazakevich */ -public abstract class MembersBasedPresenterWithPreviewImpl> extends MembersBasedPresenterImpl { +public abstract class MembersBasedPresenterWithPreviewImpl, + M extends MemberInfoModel>> extends MembersBasedPresenterImpl { /** @@ -28,7 +30,7 @@ public abstract class MembersBasedPresenterWithPreviewImpl> model) { + @NotNull final M model) { super(view, classUnderRefactoring, infoStorage, model); } diff --git a/python/src/com/jetbrains/python/refactoring/classes/pullUp/PyPullUpPresenter.java b/python/src/com/jetbrains/python/refactoring/classes/pullUp/PyPullUpPresenter.java index d98433f57c37..1e97f77ff19d 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/pullUp/PyPullUpPresenter.java +++ b/python/src/com/jetbrains/python/refactoring/classes/pullUp/PyPullUpPresenter.java @@ -27,4 +27,8 @@ import com.jetbrains.python.refactoring.classes.membersManager.vp.MembersBasedPr */ public interface PyPullUpPresenter extends MembersBasedPresenter { + /** + * To be called when user changed parent + */ + void parentChanged(); } diff --git a/python/src/com/jetbrains/python/refactoring/classes/pullUp/PyPullUpPresenterImpl.java b/python/src/com/jetbrains/python/refactoring/classes/pullUp/PyPullUpPresenterImpl.java index b15fd2afda88..74276974a392 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/pullUp/PyPullUpPresenterImpl.java +++ b/python/src/com/jetbrains/python/refactoring/classes/pullUp/PyPullUpPresenterImpl.java @@ -35,7 +35,7 @@ import java.util.Collections; * * @author Ilya.Kazakevich */ -class PyPullUpPresenterImpl extends MembersBasedPresenterWithPreviewImpl implements PyPullUpPresenter { +class PyPullUpPresenterImpl extends MembersBasedPresenterWithPreviewImpl implements PyPullUpPresenter { @NotNull private final Collection myParents; @@ -89,6 +89,10 @@ class PyPullUpPresenterImpl extends MembersBasedPresenterWithPreviewImpl implements PyPushDownPresenter { +public class PyPushDownPresenterImpl extends MembersBasedPresenterWithPreviewImpl>> implements PyPushDownPresenter { @NotNull private final Project myProject; diff --git a/python/testData/refactoring/dependenciesTest.py b/python/testData/refactoring/dependenciesTest.py new file mode 100644 index 000000000000..aa245b2e1a57 --- /dev/null +++ b/python/testData/refactoring/dependenciesTest.py @@ -0,0 +1,9 @@ +class Foo: + """ + Some doc + """ + + def my_method(self): + pass + + CLASS_FIELD = 42 diff --git a/python/testData/refactoring/pullup/dependenciesOrder.after.py b/python/testData/refactoring/pullup/dependenciesOrder.after.py new file mode 100644 index 000000000000..151587a06228 --- /dev/null +++ b/python/testData/refactoring/pullup/dependenciesOrder.after.py @@ -0,0 +1,30 @@ +class DataHolder: + VAR = 1 + +class Parent: + A_FIELD = DataHolder.VAR + CLASS_FIELD = 42 + ANOTHER_CLASS_FIELD = CLASS_FIELD + BOO = 12 + FIELD = BOO + + def __init__(self): + self.d = Parent.BOO + self.c = 1 + self.b = self.c + + @staticmethod + def foo(): + return "A" + + SOME_VAR = foo() + + +class Child(Parent): # Try to pull members up + + + + def __init__(self): + super(Child, self).__init__() + self.a = 12 + i = 1 \ No newline at end of file diff --git a/python/testData/refactoring/pullup/dependenciesOrder.py b/python/testData/refactoring/pullup/dependenciesOrder.py new file mode 100644 index 000000000000..789bd9978403 --- /dev/null +++ b/python/testData/refactoring/pullup/dependenciesOrder.py @@ -0,0 +1,28 @@ +class DataHolder: + VAR = 1 + +class Parent: + BOO = 12 + + def __init__(self): + self.c = 1 + + +class Child(Parent): # Try to pull members up + CLASS_FIELD = 42 + ANOTHER_CLASS_FIELD = CLASS_FIELD + FIELD = Parent.BOO + A_FIELD = DataHolder.VAR + + @staticmethod + def foo(): + return "A" + + SOME_VAR = foo() + + def __init__(self): + super(Child, self).__init__() + self.a = 12 + self.b = self.c + self.d = Parent.BOO + i = 1 \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/refactoring/classes/PyDependenciesComparatorTest.java b/python/testSrc/com/jetbrains/python/refactoring/classes/PyDependenciesComparatorTest.java new file mode 100644 index 000000000000..9ebe01b127dc --- /dev/null +++ b/python/testSrc/com/jetbrains/python/refactoring/classes/PyDependenciesComparatorTest.java @@ -0,0 +1,40 @@ +package com.jetbrains.python.refactoring.classes; + +import com.intellij.psi.PsiElement; +import com.intellij.util.text.Matcher; +import com.jetbrains.python.fixtures.PyTestCase; +import com.jetbrains.python.psi.*; +import org.hamcrest.Matchers; +import org.junit.Assert; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * @author Ilya.Kazakevich + */ +public class PyDependenciesComparatorTest extends PyTestCase { + + public void test() { + myFixture.configureByFile("/refactoring/dependenciesTest.py"); + final PyClass clazz = getClassByName("Foo"); + + @SuppressWarnings("ConstantConditions") // Can't be null (class has docstring) + PsiElement docStringExpression = clazz.getDocStringExpression().getParent(); + PyFunction method = clazz.getMethods()[0]; + PsiElement classField = clazz.getClassAttributes().get(0).getParent(); + + final List elementList = new ArrayList(); + elementList.addAll(Arrays.asList(clazz.getStatementList().getStatements())); + Collections.sort(elementList, PyDependenciesComparator.INSTANCE); + + Assert.assertThat("Members returned in wrong order", elementList, Matchers.contains( + docStringExpression, classField, method + )); + + + + } +} diff --git a/python/testSrc/com/jetbrains/python/refactoring/classes/pullUp/PyPullUpTest.java b/python/testSrc/com/jetbrains/python/refactoring/classes/pullUp/PyPullUpTest.java index d18f5b79e3c2..d725c1736083 100644 --- a/python/testSrc/com/jetbrains/python/refactoring/classes/pullUp/PyPullUpTest.java +++ b/python/testSrc/com/jetbrains/python/refactoring/classes/pullUp/PyPullUpTest.java @@ -84,6 +84,11 @@ public class PyPullUpTest extends PyClassRefactoringTest { doHelperTest("Child", "#foo", "Parent"); } + // Ensures than all fields are moved in correct order + public void testDependenciesOrder() { + doHelperTestSeveralMembers("Child", "Parent", "#CLASS_FIELD", "#ANOTHER_CLASS_FIELD", "#FIELD", ".foo", "#SOME_VAR", "#b", "#d", "#A_FIELD"); + } + public void testMultiFile() { // PY-2810 doMultiFileTest(); } @@ -142,8 +147,12 @@ public class PyPullUpTest extends PyClassRefactoringTest { private void doHelperTest(final String className, final String memberName, final String superClassName) { + doHelperTestSeveralMembers(className, superClassName, memberName); + } + + private void doHelperTestSeveralMembers(@NotNull final String className, @NotNull final String superClassName, @NotNull final String... memberNames) { myFixture.configureByFile(getMultiFileBaseName() + ".py"); - doPullUp(className, superClassName, memberName); + doPullUp(className, superClassName, false, memberNames); myFixture.checkResultByFile(getMultiFileBaseName() + ".after.py"); }