mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-12178 Support pull up / push down for properties
This commit is contained in:
@@ -20,6 +20,8 @@ import com.jetbrains.python.psi.resolve.PyResolveContext;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Represents an argument list of a function call.
|
||||
*
|
||||
@@ -27,6 +29,12 @@ import org.jetbrains.annotations.Nullable;
|
||||
*/
|
||||
public interface PyArgumentList extends PyElement {
|
||||
|
||||
/**
|
||||
* @return all argument list param expressions (keyword argument or nameless)
|
||||
*/
|
||||
@NotNull
|
||||
Collection<PyExpression> getArgumentExpressions();
|
||||
|
||||
@NotNull
|
||||
PyExpression[] getArguments();
|
||||
|
||||
|
||||
@@ -48,6 +48,23 @@ public class PyArgumentListImpl extends PyElementImpl implements PyArgumentList
|
||||
pyVisitor.visitPyArgumentList(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Collection<PyExpression> getArgumentExpressions() {
|
||||
final PyExpression[] arguments = getArguments();
|
||||
final Collection<PyExpression> result = new ArrayList<PyExpression>(arguments.length);
|
||||
for (final PyExpression expression : arguments) {
|
||||
if (expression instanceof PyKeywordArgument) {
|
||||
final PyExpression valueExpression = ((PyKeywordArgument)expression).getValueExpression();
|
||||
result.add(valueExpression);
|
||||
}
|
||||
if (expression instanceof PyReferenceExpression) {
|
||||
result.add(expression);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public PyExpression[] getArguments() {
|
||||
return childrenToPsi(PythonDialectsTokenSetProvider.INSTANCE.getExpressionTokens(), PyExpression.EMPTY_ARRAY);
|
||||
|
||||
@@ -38,9 +38,24 @@ class DependencyVisitor extends PyRecursiveElementVisitor {
|
||||
}
|
||||
final String calleeName = callee.getName();
|
||||
|
||||
if ((calleeName != null) && calleeName.equals(myElementToFind.getName())) { // Check by name also
|
||||
final String name = myElementToFind.getName();
|
||||
if ((calleeName != null) && calleeName.equals(name)) { // Check by name also
|
||||
myDependencyFound = true;
|
||||
}
|
||||
|
||||
// Member could be used as method param
|
||||
final PyArgumentList list = node.getArgumentList();
|
||||
if (list != null) {
|
||||
for (final PyExpression expression : node.getArgumentList().getArgumentExpressions()) {
|
||||
final PsiReference reference = expression.getReference();
|
||||
if ((reference != null) && reference.isReferenceTo(myElementToFind)) {
|
||||
myDependencyFound = true;
|
||||
}
|
||||
if ((name != null) && name.equals(expression.getName())) {
|
||||
myDependencyFound = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@ public final class PyClassRefactoringUtil {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static List<PyFunction> copyMethods(Collection<PyFunction> methods, PyClass superClass) {
|
||||
public static List<PyFunction> copyMethods(Collection<PyFunction> methods, PyClass superClass, boolean skipIfExist ) {
|
||||
if (methods.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
@@ -133,7 +133,7 @@ public final class PyClassRefactoringUtil {
|
||||
rememberNamedReferences(e);
|
||||
}
|
||||
final PyFunction[] elements = methods.toArray(new PyFunction[methods.size()]);
|
||||
return addMethods(superClass, true, elements);
|
||||
return addMethods(superClass, skipIfExist, elements);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+13
@@ -31,6 +31,19 @@ class ClassFieldsManager extends FieldsManager {
|
||||
protected Collection<PyElement> moveAssignments(@NotNull final PyClass from,
|
||||
@NotNull final Collection<PyAssignmentStatement> statements,
|
||||
@NotNull final PyClass... to) {
|
||||
return moveAssignmentsImpl(from, statements, to);
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves assignments from one class to anothers
|
||||
* @param from source
|
||||
* @param statements assignments
|
||||
* @param to destination
|
||||
* @return newly created assignments
|
||||
*/
|
||||
static Collection<PyElement> moveAssignmentsImpl(@NotNull final PyClass from,
|
||||
@NotNull final Collection<PyAssignmentStatement> statements,
|
||||
@NotNull final PyClass... to) {
|
||||
//TODO: Copy/paste with InstanceFieldsManager. Move to parent?
|
||||
final Collection<PyElement> result = new ArrayList<PyElement>();
|
||||
for (final PyClass destClass : to) {
|
||||
|
||||
+8
-3
@@ -20,7 +20,6 @@ import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Collections2;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiNamedElement;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.containers.MultiMap;
|
||||
import com.jetbrains.NotNullPredicate;
|
||||
@@ -45,7 +44,11 @@ public abstract class MembersManager<T extends PyElement> implements Function<T,
|
||||
* List of managers. Class delegates all logic to them.
|
||||
*/
|
||||
private static final Collection<? extends MembersManager<? extends PyElement>> MANAGERS =
|
||||
Arrays.asList(new MethodsManager(), new SuperClassesManager(), new ClassFieldsManager(), new InstanceFieldsManager());
|
||||
Arrays.asList(new MethodsManager(),
|
||||
new SuperClassesManager(),
|
||||
new ClassFieldsManager(),
|
||||
new InstanceFieldsManager(),
|
||||
new PropertiesManager());
|
||||
|
||||
@NotNull
|
||||
private final Class<T> myExpectedClass;
|
||||
@@ -175,7 +178,8 @@ public abstract class MembersManager<T extends PyElement> implements Function<T,
|
||||
|
||||
/**
|
||||
* Finds member in class.
|
||||
* @param pyClass class to find member in
|
||||
*
|
||||
* @param pyClass class to find member in
|
||||
* @param pyElement element to find
|
||||
* @return member info with element
|
||||
*/
|
||||
@@ -212,6 +216,7 @@ public abstract class MembersManager<T extends PyElement> implements Function<T,
|
||||
/**
|
||||
* Moves element from one class to another. Returns members that may require reference restoring aid from
|
||||
* ({@link com.jetbrains.python.refactoring.classes.PyClassRefactoringUtil#restoreNamedReferences(com.intellij.psi.PsiElement)})
|
||||
* Sort members according to their dependncies, before calling this method
|
||||
*
|
||||
* @see #getElementsToStoreReferences(java.util.Collection)
|
||||
*/
|
||||
|
||||
+4
-3
@@ -76,7 +76,7 @@ class MethodsManager extends MembersManager<PyFunction> {
|
||||
final Collection<PyFunction> methodsToAbstract = fetchElements(Collections2.filter(members, new AbstractFilter(true)));
|
||||
|
||||
makeMethodsAbstract(methodsToAbstract, to);
|
||||
return moveMethods(from, methodsToMove, to);
|
||||
return moveMethods(from, methodsToMove, true, to);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -158,9 +158,10 @@ class MethodsManager extends MembersManager<PyFunction> {
|
||||
* @param from source
|
||||
* @param methodsToMove what to move
|
||||
* @param to where
|
||||
* @param skipIfExist skip (do not add) if method already exists
|
||||
* @return newly added methods
|
||||
*/
|
||||
private static List<PyElement> moveMethods(final PyClass from, final Collection<PyFunction> methodsToMove, final PyClass... to) {
|
||||
static List<PyElement> moveMethods(final PyClass from, final Collection<PyFunction> methodsToMove, final boolean skipIfExist, final PyClass... to) {
|
||||
final List<PyElement> result = new ArrayList<PyElement>();
|
||||
for (final PyClass destClass : to) {
|
||||
//We move copies here because there may be several destinations
|
||||
@@ -170,7 +171,7 @@ class MethodsManager extends MembersManager<PyFunction> {
|
||||
copies.add(newMethod);
|
||||
}
|
||||
|
||||
result.addAll(PyClassRefactoringUtil.copyMethods(copies, destClass));
|
||||
result.addAll(PyClassRefactoringUtil.copyMethods(copies, destClass, skipIfExist));
|
||||
}
|
||||
deleteElements(methodsToMove);
|
||||
|
||||
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
package com.jetbrains.python.refactoring.classes.membersManager;
|
||||
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.util.containers.MultiMap;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Plugin that moves class properties.
|
||||
* It represents property (whatever old or new) as one of its methods.
|
||||
*
|
||||
* @author Ilya.Kazakevich
|
||||
*/
|
||||
class PropertiesManager extends MembersManager<PyElement> {
|
||||
|
||||
PropertiesManager() {
|
||||
super(PyElement.class);
|
||||
}
|
||||
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected List<? extends PyElement> getMembersCouldBeMoved(@NotNull final PyClass pyClass) {
|
||||
final List<PyElement> elements = new ArrayList<PyElement>(pyClass.getProperties().size());
|
||||
for (final Property property : pyClass.getProperties().values()) {
|
||||
elements.add(getElement(property));
|
||||
}
|
||||
return elements;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static PyElement getElement(@NotNull final Property property) {
|
||||
final Callable getter = property.getGetter().valueOrNull();
|
||||
final Callable setter = property.getSetter().valueOrNull();
|
||||
final Callable deleter = property.getDeleter().valueOrNull();
|
||||
|
||||
if (getter != null) {
|
||||
return getter;
|
||||
}
|
||||
else if (setter != null) {
|
||||
return setter;
|
||||
}
|
||||
else if (deleter != null) {
|
||||
return deleter;
|
||||
}
|
||||
else {
|
||||
final PyTargetExpression site = property.getDefinitionSite();
|
||||
assert site != null : "Property has no methods nor declaration. That is not property";
|
||||
return site;
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Property getProperty(@NotNull final PyClass pyClass, @NotNull final PyElement element) {
|
||||
final Collection<Property> properties = pyClass.getProperties().values();
|
||||
if (element instanceof PyTargetExpression) {
|
||||
return getPropertyByTargetExpression(properties, (PyTargetExpression)element);
|
||||
}
|
||||
if (element instanceof PyFunction) {
|
||||
return getPropertyByFunction(properties, (PyFunction)element);
|
||||
}
|
||||
throw new IllegalArgumentException("Not function nor target");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Property getPropertyByFunction(@NotNull final Collection<Property> properties,
|
||||
@NotNull final PyFunction functionToSearch) {
|
||||
for (final Property property : properties) {
|
||||
for (final PyFunction function : getAllFunctions(property)) {
|
||||
if (function.equals(functionToSearch)) {
|
||||
return property;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("No property found");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Property getPropertyByTargetExpression(@NotNull final Iterable<Property> properties,
|
||||
@NotNull final PyTargetExpression element) {
|
||||
for (final Property property : properties) {
|
||||
if (element.equals(property.getDefinitionSite())) {
|
||||
return property;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("No property found");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Collection<PyFunction> getAllFunctions(@NotNull final Property property) {
|
||||
final Collection<PyFunction> result = new ArrayList<PyFunction>(3);
|
||||
final Callable getter = property.getGetter().valueOrNull();
|
||||
final Callable setter = property.getSetter().valueOrNull();
|
||||
final Callable deleter = property.getDeleter().valueOrNull();
|
||||
|
||||
if (getter instanceof PyFunction) {
|
||||
result.add((PyFunction)getter);
|
||||
}
|
||||
if (setter instanceof PyFunction) {
|
||||
result.add((PyFunction)setter);
|
||||
}
|
||||
if (deleter instanceof PyFunction) {
|
||||
result.add((PyFunction)deleter);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<PyElement> moveMembers(@NotNull final PyClass from,
|
||||
@NotNull final Collection<PyMemberInfo<PyElement>> members,
|
||||
@NotNull final PyClass... to) {
|
||||
final Collection<PyElement> result = new ArrayList<PyElement>();
|
||||
|
||||
final Collection<PyElement> elements = fetchElements(members);
|
||||
for (final PyElement element : elements) {
|
||||
final Property property = getProperty(from, element);
|
||||
final Collection<PyFunction> functions = getAllFunctions(property);
|
||||
MethodsManager.moveMethods(from, functions, false, to);
|
||||
final PyTargetExpression definitionSite = property.getDefinitionSite();
|
||||
if (definitionSite != null) {
|
||||
final PyAssignmentStatement assignmentStatement = PsiTreeUtil.getParentOfType(definitionSite, PyAssignmentStatement.class);
|
||||
ClassFieldsManager.moveAssignmentsImpl(from, Collections.singleton(assignmentStatement), to);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public PyMemberInfo<PyElement> apply(@NotNull final PyElement input) {
|
||||
return new PyMemberInfo<PyElement>(input, false, getName(input), false, this, false);
|
||||
}
|
||||
|
||||
private static String getName(@NotNull final PyElement input) {
|
||||
final PyClass clazz = PsiTreeUtil.getParentOfType(input, PyClass.class);
|
||||
assert clazz != null : "Element not declared in class";
|
||||
final Property property = getProperty(clazz, input);
|
||||
return property.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasConflict(@NotNull final PyElement member, @NotNull final PyClass aClass) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected MultiMap<PyClass, PyElement> getDependencies(@NotNull final PyElement member) {
|
||||
return new MultiMap<PyClass, PyElement>();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected Collection<PyElement> getDependencies(@NotNull final MultiMap<PyClass, PyElement> usedElements) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
class ToClass(object):
|
||||
C = 12
|
||||
|
||||
def __init__(self):
|
||||
self.a = 1
|
||||
|
||||
def _get(self):
|
||||
return 1
|
||||
|
||||
def _set(self, value):
|
||||
pass
|
||||
|
||||
def _delete(self):
|
||||
pass
|
||||
|
||||
old_property = property(_get, _set, _delete)
|
||||
|
||||
def foo(self):
|
||||
pass
|
||||
|
||||
|
||||
class FromClass(ToClass):
|
||||
def __init__(self): pass
|
||||
|
||||
|
||||
def lala(self):
|
||||
pass
|
||||
@@ -0,0 +1,22 @@
|
||||
class FromClass(object):
|
||||
C = 12
|
||||
|
||||
def __init__(self):
|
||||
self.a = 1
|
||||
|
||||
def _get(self):
|
||||
return 1
|
||||
|
||||
def _set(self, value):
|
||||
pass
|
||||
|
||||
def _delete(self):
|
||||
pass
|
||||
|
||||
old_property = property(_get, _set, _delete)
|
||||
|
||||
def foo(self):
|
||||
pass
|
||||
|
||||
def lala(self):
|
||||
pass
|
||||
@@ -0,0 +1,19 @@
|
||||
from SuperClass import SuperClass
|
||||
|
||||
|
||||
class AnyClass(SuperClass):
|
||||
C = 1
|
||||
|
||||
def __init__(self):
|
||||
super(AnyClass, self).__init__()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def foo(self):
|
||||
pass
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
from SuperClass import SuperClass
|
||||
|
||||
|
||||
class AnyClass(SuperClass):
|
||||
C = 1
|
||||
|
||||
def __init__(self):
|
||||
super(AnyClass, self).__init__()
|
||||
|
||||
|
||||
@property
|
||||
def new_property(self):
|
||||
return 1
|
||||
|
||||
@new_property.setter
|
||||
def new_property(self, value):
|
||||
pass
|
||||
|
||||
@new_property.deleter
|
||||
def new_property(self):
|
||||
pass
|
||||
|
||||
def foo(self):
|
||||
pass
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
class SuperClass(object):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@property
|
||||
def new_property(self):
|
||||
return 1
|
||||
|
||||
@new_property.setter
|
||||
def new_property(self, value):
|
||||
pass
|
||||
|
||||
@new_property.deleter
|
||||
def new_property(self):
|
||||
pass
|
||||
@@ -0,0 +1,3 @@
|
||||
class SuperClass(object):
|
||||
def __init__(self):
|
||||
pass
|
||||
+5
@@ -125,6 +125,11 @@ public class PyExtractSuperclassTest extends PyClassRefactoringTest {
|
||||
doSimpleTest("FromClass", "ToClass", null, true, "#instance_field", "#CLASS_FIELD");
|
||||
}
|
||||
|
||||
|
||||
public void testProperties() throws Exception {
|
||||
doSimpleTest("FromClass", "ToClass", null, true, "#C", "#a", "._get", ".foo");
|
||||
}
|
||||
|
||||
private void doSimpleTest(final String className,
|
||||
final String superclassName,
|
||||
final String expectedError,
|
||||
|
||||
+3
-1
@@ -144,7 +144,9 @@ public class PyPullUpPresenterTest extends PyRefactoringPresenterTestCase<PyPull
|
||||
new PyPresenterTestMemberEntry("static_2()", true, true, py3K),
|
||||
new PyPresenterTestMemberEntry("self.instance_field_1", true, false, false),
|
||||
new PyPresenterTestMemberEntry("self.instance_field_2", true, false, false),
|
||||
new PyPresenterTestMemberEntry("bad_method()", true, false, true));
|
||||
new PyPresenterTestMemberEntry("bad_method()", true, false, true),
|
||||
new PyPresenterTestMemberEntry("name", true, false, false),
|
||||
new PyPresenterTestMemberEntry("some_property", true, false, false));
|
||||
compareMembers(memberNamesAndStatus, matcher);
|
||||
}
|
||||
|
||||
|
||||
@@ -97,6 +97,13 @@ public class PyPullUpTest extends PyClassRefactoringTest {
|
||||
doMultiFileTest();
|
||||
}
|
||||
|
||||
public void testProperties() {
|
||||
final String[] modules = {"Class", "SuperClass"};
|
||||
configureMultiFile(modules);
|
||||
doPullUp("AnyClass", "SuperClass", ".new_property");
|
||||
checkMultiFile(modules);
|
||||
}
|
||||
|
||||
public void testFieldMove() {
|
||||
final String[] modules = {"Class", "SuperClass"};
|
||||
configureMultiFile(modules);
|
||||
|
||||
Reference in New Issue
Block a user