PY-12178 Support pull up / push down for properties

This commit is contained in:
Ilya.Kazakevich
2014-03-04 17:14:00 +04:00
parent 7805c3d589
commit dd2e76a0ff
17 changed files with 356 additions and 10 deletions
@@ -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);
}
/**
@@ -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) {
@@ -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)
*/
@@ -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);
@@ -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
@@ -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,
@@ -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);