PY-12178 Support pull up / push down for properties (not ready yet: only properties excluded from other managers)

This commit is contained in:
Ilya.Kazakevich
2014-02-26 21:05:39 +04:00
parent 91863341b8
commit 3717b9aaea
7 changed files with 122 additions and 37 deletions
@@ -32,6 +32,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Map;
/**
* Represents a class declaration in source.
@@ -112,6 +113,13 @@ public interface PyClass extends PsiNameIdentifierOwner, PyStatement, NameDefine
@NotNull
PyFunction[] getMethods();
/**
* Get class properties.
* @return Map [property_name] = [{@link com.jetbrains.python.psi.Property}]
*/
@NotNull
Map<String, Property> getProperties();
/**
* Finds a method with given name.
* @param name what to look for
@@ -380,6 +380,13 @@ public class PyClassImpl extends PyPresentableElementImpl<PyClassStub> implement
return getClassChildren(PythonDialectsTokenSetProvider.INSTANCE.getFunctionDeclarationTokens(), PyFunction.ARRAY_FACTORY);
}
@Override
@NotNull
public Map<String, Property> getProperties() {
initProperties();
return new HashMap<String, Property>(myPropertyCache);
}
@Override
public PyClass[] getNestedClasses() {
return getClassChildren(TokenSet.create(PyElementTypes.CLASS_DECLARATION), PyClass.ARRAY_FACTORY);
@@ -607,9 +614,7 @@ public class PyClassImpl extends PyPresentableElementImpl<PyClassStub> implement
@Override
public Property findPropertyByCallable(Callable callable) {
if (myPropertyCache == null) {
myPropertyCache = initializePropertyCache();
}
initProperties();
for (Property property : myPropertyCache.values()) {
if (property.getGetter().valueOrNull() == callable ||
property.getSetter().valueOrNull() == callable ||
@@ -621,10 +626,14 @@ public class PyClassImpl extends PyPresentableElementImpl<PyClassStub> implement
}
private Property findLocalProperty(String name) {
initProperties();
return myPropertyCache.get(name);
}
private synchronized void initProperties() {
if (myPropertyCache == null) {
myPropertyCache = initializePropertyCache();
}
return myPropertyCache.get(name);
}
private Map<String, Property> initializePropertyCache() {
@@ -1,9 +1,9 @@
package com.jetbrains.python.refactoring.classes.membersManager;
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.google.common.collect.FluentIterable;
import com.jetbrains.NotNullPredicate;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.refactoring.classes.PyClassRefactoringUtil;
import org.jetbrains.annotations.NotNull;
@@ -32,7 +32,7 @@ class ClassFieldsManager extends FieldsManager {
@NotNull final Collection<PyAssignmentStatement> statements,
@NotNull final PyClass... to) {
//TODO: Copy/paste with InstanceFieldsManager. Move to parent?
final List<PyElement> result = new ArrayList<PyElement>();
final Collection<PyElement> result = new ArrayList<PyElement>();
for (final PyClass destClass : to) {
result.addAll(PyClassRefactoringUtil.copyFieldDeclarationToStatement(statements, destClass.getStatementList(), destClass));
}
@@ -49,6 +49,39 @@ class ClassFieldsManager extends FieldsManager {
@NotNull
@Override
protected List<PyTargetExpression> getFieldsByClass(@NotNull final PyClass pyClass) {
return pyClass.getClassAttributes();
return FluentIterable.from(pyClass.getClassAttributes()).filter(new NoMetaAndProperties(pyClass)).toList();
}
/**
* Exclude "__metaclass__" field and properties (there should be separate managers for them)
* TODO: Check type and filter out any builtin element instead?
*/
private static class NoMetaAndProperties extends NotNullPredicate<PyTargetExpression> {
@NotNull
private final PyClass myClass;
private NoMetaAndProperties(@NotNull final PyClass aClass) {
myClass = aClass;
}
@Override
public boolean applyNotNull(@NotNull final PyTargetExpression input) {
final String name = input.getName();
if (name == null) {
return false;
}
if (name.equals(PyNames.DUNDER_METACLASS)) {
return false;
}
final PyExpression assignedValue = input.findAssignedValue();
if (assignedValue instanceof PyCallExpression) {
final PyExpression callee = ((PyCallExpression)assignedValue).getCallee();
if ((callee != null) && PyNames.PROPERTY.equals(callee.getName()) && (myClass.findProperty(name, false) != null)) {
return false;
}
}
return true;
}
}
}
@@ -84,7 +84,7 @@ public abstract class MembersManager<T extends PyElement> implements Function<T,
@SuppressWarnings({"unchecked", "rawtypes"}) //We check type at runtime
private static Collection<PyMemberInfo<PyElement>> transformSafely(@NotNull final PyClass pyClass,
@NotNull final MembersManager<?> manager) {
final List<PyElement> membersCouldBeMoved = manager.getMembersCouldBeMoved(pyClass);
final List<? extends PyElement> membersCouldBeMoved = manager.getMembersCouldBeMoved(pyClass);
manager.checkElementTypes((Iterable)membersCouldBeMoved);
return (Collection<PyMemberInfo<PyElement>>)Collections2.transform(membersCouldBeMoved, (Function)manager);
}
@@ -195,24 +195,9 @@ public abstract class MembersManager<T extends PyElement> implements Function<T,
* @return list of members
*/
@NotNull
protected abstract List<PyElement> getMembersCouldBeMoved(@NotNull PyClass pyClass);
protected abstract List<? extends PyElement> getMembersCouldBeMoved(@NotNull PyClass pyClass);
/**
* Filters out named elements (ones that subclasses {@link com.intellij.psi.PsiNamedElement}) and {@link com.jetbrains.python.psi.PyElement})
* that are null or has null name.
* You need it sometimes when code has errors (i.e. bad formatted code with annotation may treat annotation as method with null name.
* note: we should probably throw exceptions in such cases and display "refactoring not available" window in handler)
*
* @param elementsToFilter collection of elements to filter
* @param <T> element type
* @return collection of T with out of nulls and elemens whos {@link com.intellij.psi.PsiNamedElement#getName()} returns null
*/
@NotNull
protected static <T extends PsiNamedElement & PyElement> Collection<T> filterNameless(@NotNull final Collection<T> elementsToFilter) {
return Collections2.filter(elementsToFilter, new NamelessFilter<T>());
}
/**
* Returns list of elements that may require reference storing aid from {@link com.jetbrains.python.refactoring.classes.PyClassRefactoringUtil#rememberNamedReferences(com.intellij.psi.PsiElement, String...)}
*
@@ -357,13 +342,6 @@ public abstract class MembersManager<T extends PyElement> implements Function<T,
}
}
private static class NamelessFilter<T extends PyElement & PsiNamedElement> extends NotNullPredicate<T> {
@Override
public boolean applyNotNull(@NotNull final T input) {
return input.getName() != null;
}
}
private static class FindByElement extends NotNullPredicate<PyMemberInfo<PyElement>> {
private final PyElement myPyElement;
@@ -1,7 +1,8 @@
package com.jetbrains.python.refactoring.classes.membersManager;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
import com.google.common.collect.FluentIterable;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
@@ -35,6 +36,7 @@ class MethodsManager extends MembersManager<PyFunction> {
{PyNames.PROPERTY, PyNames.CLASSMETHOD, PyNames.STATICMETHOD};
public static final String ABC_META_PACKAGE = "abc";
private static final NoPropertiesPredicate NO_PROPERTIES = new NoPropertiesPredicate();
MethodsManager() {
super(PyFunction.class);
@@ -62,8 +64,8 @@ class MethodsManager extends MembersManager<PyFunction> {
@NotNull
@Override
protected List<PyElement> getMembersCouldBeMoved(@NotNull final PyClass pyClass) {
return Lists.<PyElement>newArrayList(filterNameless(Arrays.asList(pyClass.getMethods())));
protected List<? extends PyElement> getMembersCouldBeMoved(@NotNull final PyClass pyClass) {
return FluentIterable.from(Arrays.asList(pyClass.getMethods())).filter(new NamelessFilter<PyFunction>()).filter(NO_PROPERTIES).toList();
}
@Override
@@ -281,4 +283,14 @@ class MethodsManager extends MembersManager<PyFunction> {
}
}
}
/**
* Filter out property setters and getters
*/
private static class NoPropertiesPredicate implements Predicate<PyFunction> {
@Override
public boolean apply(@NotNull PyFunction input) {
return input.getProperty() == null;
}
}
}
@@ -0,0 +1,21 @@
package com.jetbrains.python.refactoring.classes.membersManager;
import com.intellij.psi.PsiNamedElement;
import com.jetbrains.NotNullPredicate;
import com.jetbrains.python.psi.PyElement;
import org.jetbrains.annotations.NotNull;
/**
* Filters out named elements (ones that subclasses {@link com.intellij.psi.PsiNamedElement}) and {@link com.jetbrains.python.psi.PyElement})
* that are null or has null name.
* You need it sometimes when code has errors (i.e. bad formatted code with annotation may treat annotation as method with null name.
*
* @author Ilya.Kazakevich
*/
class NamelessFilter<T extends PyElement & PsiNamedElement> extends NotNullPredicate<T> {
@Override
public boolean applyNotNull(@NotNull final T input) {
return input.getName() != null;
}
}
@@ -30,12 +30,36 @@ class BadMro(MainParent, object, SubParent1, SubParent2):
pass
class HugeChild(SubParent1, date): #SubParent1 is disabled
__metaclass__ = None # Anyway, this field should be ignored and processed separately as "metaclass", not "class field"
def __init__(self):
self.instance_field_1 = 42
self.instance_field_2 = 100500
CLASS_FIELD = 42
(CLASS_FIELD_A,CLASS_FIELD_B) = (42,100500) #We do not support tuples in class assignments for now (see ClassFieldsManager)
def _set(self, val): # Should not be treated as method (part of property)
pass
def _get(self): # Should not be treated as method (part of property)
return None
name = property(fget=_get, fset=_set)
@property
def some_property(self): # Should not be treated as method (part of property)
return None
@some_property.setter
def some_property(self, val): # Should not be treated as method (part of property)
pass
def foo(self): #should be disabled
pass
def bar(self):