diff --git a/python/psi-api/src/com/jetbrains/python/psi/PyClass.java b/python/psi-api/src/com/jetbrains/python/psi/PyClass.java index 657a54f78299..a988714c144a 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/PyClass.java +++ b/python/psi-api/src/com/jetbrains/python/psi/PyClass.java @@ -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 getProperties(); + /** * Finds a method with given name. * @param name what to look for diff --git a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java index bea0e10312bb..2bef07f7a4bb 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java @@ -380,6 +380,13 @@ public class PyClassImpl extends PyPresentableElementImpl implement return getClassChildren(PythonDialectsTokenSetProvider.INSTANCE.getFunctionDeclarationTokens(), PyFunction.ARRAY_FACTORY); } + @Override + @NotNull + public Map getProperties() { + initProperties(); + return new HashMap(myPropertyCache); + } + @Override public PyClass[] getNestedClasses() { return getClassChildren(TokenSet.create(PyElementTypes.CLASS_DECLARATION), PyClass.ARRAY_FACTORY); @@ -607,9 +614,7 @@ public class PyClassImpl extends PyPresentableElementImpl 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 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 initializePropertyCache() { 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 3bf4f00553e1..3d6909005f12 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/membersManager/ClassFieldsManager.java +++ b/python/src/com/jetbrains/python/refactoring/classes/membersManager/ClassFieldsManager.java @@ -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 statements, @NotNull final PyClass... to) { //TODO: Copy/paste with InstanceFieldsManager. Move to parent? - final List result = new ArrayList(); + final Collection result = new ArrayList(); 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 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 { + @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; + } } } 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 437b8085d765..31180c434e3a 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/membersManager/MembersManager.java +++ b/python/src/com/jetbrains/python/refactoring/classes/membersManager/MembersManager.java @@ -84,7 +84,7 @@ public abstract class MembersManager implements Function> transformSafely(@NotNull final PyClass pyClass, @NotNull final MembersManager manager) { - final List membersCouldBeMoved = manager.getMembersCouldBeMoved(pyClass); + final List membersCouldBeMoved = manager.getMembersCouldBeMoved(pyClass); manager.checkElementTypes((Iterable)membersCouldBeMoved); return (Collection>)Collections2.transform(membersCouldBeMoved, (Function)manager); } @@ -195,24 +195,9 @@ public abstract class MembersManager implements Function getMembersCouldBeMoved(@NotNull PyClass pyClass); + protected abstract List 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 element type - * @return collection of T with out of nulls and elemens whos {@link com.intellij.psi.PsiNamedElement#getName()} returns null - */ - @NotNull - protected static Collection filterNameless(@NotNull final Collection elementsToFilter) { - return Collections2.filter(elementsToFilter, new NamelessFilter()); - } - /** * 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 implements Function extends NotNullPredicate { - @Override - public boolean applyNotNull(@NotNull final T input) { - return input.getName() != null; - } - } - private static class FindByElement extends NotNullPredicate> { private final PyElement myPyElement; diff --git a/python/src/com/jetbrains/python/refactoring/classes/membersManager/MethodsManager.java b/python/src/com/jetbrains/python/refactoring/classes/membersManager/MethodsManager.java index 7cba0a2a639c..69cc9837f1ca 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/membersManager/MethodsManager.java +++ b/python/src/com/jetbrains/python/refactoring/classes/membersManager/MethodsManager.java @@ -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 { {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 { @NotNull @Override - protected List getMembersCouldBeMoved(@NotNull final PyClass pyClass) { - return Lists.newArrayList(filterNameless(Arrays.asList(pyClass.getMethods()))); + protected List getMembersCouldBeMoved(@NotNull final PyClass pyClass) { + return FluentIterable.from(Arrays.asList(pyClass.getMethods())).filter(new NamelessFilter()).filter(NO_PROPERTIES).toList(); } @Override @@ -281,4 +283,14 @@ class MethodsManager extends MembersManager { } } } + + /** + * Filter out property setters and getters + */ + private static class NoPropertiesPredicate implements Predicate { + @Override + public boolean apply(@NotNull PyFunction input) { + return input.getProperty() == null; + } + } } diff --git a/python/src/com/jetbrains/python/refactoring/classes/membersManager/NamelessFilter.java b/python/src/com/jetbrains/python/refactoring/classes/membersManager/NamelessFilter.java new file mode 100644 index 000000000000..ec688e3f4a9c --- /dev/null +++ b/python/src/com/jetbrains/python/refactoring/classes/membersManager/NamelessFilter.java @@ -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 extends NotNullPredicate { + + @Override + public boolean applyNotNull(@NotNull final T input) { + return input.getName() != null; + } +} diff --git a/python/testData/refactoring/pullup/presenter/file.py b/python/testData/refactoring/pullup/presenter/file.py index cb6ca9511209..04eecf38db15 100644 --- a/python/testData/refactoring/pullup/presenter/file.py +++ b/python/testData/refactoring/pullup/presenter/file.py @@ -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):