PY-2858 Pull Up Abstract Method (finally!)

This commit is contained in:
Ilya.Kazakevich
2014-02-14 22:03:37 +04:00
parent 40f6fbe45c
commit b32156d3c5
59 changed files with 722 additions and 180 deletions

View File

@@ -31,6 +31,7 @@ import com.intellij.ui.*;
import com.intellij.ui.table.JBTable;
import com.intellij.util.ui.EmptyIcon;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.table.AbstractTableModel;
@@ -61,7 +62,7 @@ public abstract class AbstractMemberSelectionTable<T extends PsiElement, M exten
protected MemberInfoModel<T, M> myMemberInfoModel;
protected MyTableModel<T, M> myTableModel;
public AbstractMemberSelectionTable(Collection<M> memberInfos, MemberInfoModel<T, M> memberInfoModel, String abstractColumnHeader) {
public AbstractMemberSelectionTable(Collection<M> memberInfos, @Nullable MemberInfoModel<T, M> memberInfoModel, @Nullable String abstractColumnHeader) {
myAbstractEnabled = abstractColumnHeader != null;
myAbstractColumnHeader = abstractColumnHeader;
myTableModel = new MyTableModel<T, M>(this);
@@ -173,6 +174,7 @@ public abstract class AbstractMemberSelectionTable<T extends PsiElement, M exten
scrollSelectionInView();
}
@Nullable
protected abstract Object getAbstractColumnValue(M memberInfo);
protected abstract boolean isAbstractColumnEditable(int rowIndex);

View File

@@ -218,6 +218,7 @@ public interface PyClass extends PsiNameIdentifierOwner, PyStatement, NameDefine
boolean processClassLevelDeclarations(@NotNull PsiScopeProcessor processor);
boolean processInstanceLevelDeclarations(@NotNull PsiScopeProcessor processor, @Nullable PsiElement location);
//TODO: Add "addMetaClass" or move methods out of here
/**
* Returns the type representing the metaclass of the class if it is explicitly set, null otherwise.
*/

View File

@@ -21,6 +21,7 @@ import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.util.IncorrectOperationException;
@@ -179,7 +180,6 @@ public class AddImportHelper {
}
return true;
}
/**
* Adds an "import ... from ..." statement below other top-level imports.
*

View File

@@ -38,7 +38,6 @@ import org.jetbrains.annotations.NotNull;
import static com.jetbrains.python.psi.PyUtil.sure;
/**
* TODO: Refactor and move to {@link com.jetbrains.python.refactoring.classes.PyClassRefactoringUtil#createMethod(String, com.jetbrains.python.psi.PyClass, com.jetbrains.python.psi.PyFunction.Modifier, String...)}
* Adds a method foo to class X if X.foo() is unresolved.
* User: dcheryasov
* Date: Apr 5, 2009 6:51:26 PM

View File

@@ -1087,6 +1087,11 @@ public class PyUtil {
}
return null;
}
//TODO: Doc
public boolean isInstanceMethod() {
return ! (myIsClassMethod || myIsStaticMethod);
}
}
public static boolean isSuperCall(@NotNull PyCallExpression node) {
@@ -1438,7 +1443,7 @@ public class PyUtil {
return myAllowObjects == isObject(input);
}
private static boolean isObject(@NotNull final PyMemberInfo classMemberInfo) {
private static boolean isObject(@NotNull final PyMemberInfo<PyElement> classMemberInfo) {
final PyElement element = classMemberInfo.getMember();
if ((element instanceof PyClass) && PyNames.OBJECT.equals(element.getName())) {
return true;

View File

@@ -20,13 +20,11 @@ import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
import com.intellij.util.ArrayUtil;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.PythonFileType;
import com.jetbrains.python.psi.LanguageLevel;
import com.jetbrains.python.psi.PyElementGenerator;
import com.jetbrains.python.psi.PyFunction;
import com.jetbrains.python.psi.PyUtil;
import com.jetbrains.python.refactoring.classes.PyClassRefactoringUtil;
import com.jetbrains.python.psi.*;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collections;
@@ -42,6 +40,31 @@ public class PyFunctionBuilder {
private final List<String> myDecorators = new ArrayList<String>();
private String myAnnotation = null;
//TODO: Doc
@NotNull
public static PyFunctionBuilder copySignature(@NotNull final PyFunction source, @NotNull final String... decoratorsToCopyIfExist) {
final String name = source.getName();
final PyFunctionBuilder functionBuilder = new PyFunctionBuilder((name != null) ? name : "");
for (final PyParameter parameter : source.getParameterList().getParameters()) {
final String parameterName = parameter.getName();
if (parameterName != null) {
functionBuilder.parameter(parameterName);
}
}
final PyDecoratorList decoratorList = source.getDecoratorList();
if (decoratorList != null) {
for (final PyDecorator decorator : decoratorList.getDecorators()) {
final String decoratorName = decorator.getName();
if (decoratorName != null) {
if (ArrayUtil.contains(decoratorName, decoratorsToCopyIfExist)) {
functionBuilder.decorate(decoratorName);
}
}
}
}
return functionBuilder;
}
public PyFunctionBuilder(String name) {
myName = name;
}
@@ -49,7 +72,7 @@ public class PyFunctionBuilder {
public PyFunctionBuilder parameter(String baseName) {
String name = baseName;
int uniqueIndex = 0;
while(myParameters.contains(name)) {
while (myParameters.contains(name)) {
uniqueIndex++;
name = baseName + uniqueIndex;
}
@@ -68,11 +91,11 @@ public class PyFunctionBuilder {
}
public PyFunction addFunction(PsiElement target, final LanguageLevel languageLevel) {
return (PyFunction) target.add(buildFunction(target.getProject(), languageLevel));
return (PyFunction)target.add(buildFunction(target.getProject(), languageLevel));
}
public PyFunction addFunctionAfter(PsiElement target, PsiElement anchor, final LanguageLevel languageLevel) {
return (PyFunction) target.addAfter(buildFunction(target.getProject(), languageLevel), anchor);
return (PyFunction)target.addAfter(buildFunction(target.getProject(), languageLevel), anchor);
}
public PyFunction buildFunction(Project project, final LanguageLevel languageLevel) {

View File

@@ -21,7 +21,7 @@ import com.intellij.lang.injection.InjectedLanguageManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
@@ -35,20 +35,13 @@ import com.jetbrains.python.codeInsight.imports.AddImportHelper;
import com.jetbrains.python.codeInsight.imports.PyImportOptimizer;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyBuiltinCache;
import com.jetbrains.python.psi.impl.PyFunctionBuilder;
import com.jetbrains.python.psi.impl.PyImportedModule;
import com.jetbrains.python.psi.impl.PyPsiUtils;
import com.jetbrains.python.psi.resolve.QualifiedNameFinder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import static com.jetbrains.python.psi.PyFunction.Modifier.CLASSMETHOD;
import static com.jetbrains.python.psi.PyFunction.Modifier.STATICMETHOD;
import java.util.*;
/**
* @author Dennis.Ushakov
@@ -82,8 +75,11 @@ public final class PyClassRefactoringUtil {
return declations;
}
@NotNull
public static List<PyFunction> copyMethods(Collection<PyFunction> methods, PyClass superClass) {
if (methods.size() == 0) return null;
if (methods.isEmpty()) {
return Collections.emptyList();
}
for (PsiElement e : methods) {
rememberNamedReferences(e);
}
@@ -163,8 +159,9 @@ public final class PyClassRefactoringUtil {
/**
* Restores references saved by {@link #rememberNamedReferences(com.intellij.psi.PsiElement, String...)}.
* @see #rememberNamedReferences(com.intellij.psi.PsiElement, String...)
*
* @param element newly created element to restore references
* @see #rememberNamedReferences(com.intellij.psi.PsiElement, String...)
*/
public static void restoreNamedReferences(@NotNull final PsiElement element) {
restoreNamedReferences(element, null);
@@ -282,7 +279,7 @@ public final class PyClassRefactoringUtil {
* After that you can add element to some new parent. Newly created element then should be processed via {@link #restoreNamedReferences(com.intellij.psi.PsiElement)}
* and all references would be restored.
*
* @param element element to store references for
* @param element element to store references for
* @param namesToSkip if reference inside of element has one of this names, it will not be saved.
*/
public static void rememberNamedReferences(@NotNull final PsiElement element, @NotNull final String... namesToSkip) {
@@ -412,43 +409,6 @@ public final class PyClassRefactoringUtil {
return null;
}
/**
* Creates class method
*
* @param methodName name if new method (be sure to check {@link com.jetbrains.python.PyNames} for special methods)
* @param pyClass class to add method
* @param modifier if method static or class or simple instance method (null)>
* @param parameterNames method parameters
* @return newly created method
*/
@NotNull
public static PyFunction createMethod(@NotNull final String methodName,
@NotNull final PyClass pyClass,
@Nullable final PyFunction.Modifier modifier,
@NotNull final String... parameterNames) {
final PyFunctionBuilder builder = new PyFunctionBuilder(methodName);
//TODO: Take names from codestyle?
if (modifier == null) {
builder.parameter(PyNames.CANONICAL_SELF);
}
else if (modifier == CLASSMETHOD) {
builder.parameter(PyNames.CANONICAL_CLS);
builder.decorate(PyNames.CLASSMETHOD);
}
else if (modifier == STATICMETHOD) {
builder.decorate(PyNames.STATICMETHOD);
}
for (final String parameterName : parameterNames) {
builder.parameter(parameterName);
}
final PyFunction function = builder.buildFunction(pyClass.getProject(), LanguageLevel.getDefault());
return addMethods(pyClass, function).get(0);
}
/**
* Adds super classes to certain class.
*
@@ -470,34 +430,86 @@ public final class PyClassRefactoringUtil {
}
}
final PyArgumentList superClassExpressionList = clazz.getSuperClassExpressionList();
final PyElementGenerator generator = PyElementGenerator.getInstance(project);
addSuperClassExpressions(project, clazz, superClassNames, null);
}
if (superClassExpressionList != null) {
for (final String superClassName : superClassNames) {
superClassExpressionList.addArgument(generator.createExpressionFromText(superClassName));
/**
* Adds expressions to superclass list
*
* @param project project
* @param clazz class to add expressions to superclass list
* @param paramExpressions param expressions. Like "object" or "MySuperClass". Will not add any param exp. if null.
* @param keywordArguments keyword args like "metaclass=ABCMeta". key-value pairs. Will not add any keyword arg. if null.
*/
public static void addSuperClassExpressions(@NotNull final Project project,
@NotNull final PyClass clazz,
@Nullable final Collection<String> paramExpressions,
@Nullable final Collection<Pair<String, String>> keywordArguments) {
final PyElementGenerator generator = PyElementGenerator.getInstance(project);
final LanguageLevel languageLevel = LanguageLevel.forElement(clazz);
PyArgumentList superClassExpressionList = clazz.getSuperClassExpressionList();
boolean addExpression = false;
if (superClassExpressionList == null) {
superClassExpressionList = generator.createFromText(languageLevel, PyClass.class, "class foo():pass").getSuperClassExpressionList();
assert superClassExpressionList != null : "expression not created";
addExpression = true;
}
generator.createFromText(LanguageLevel.PYTHON34, PyClass.class, "class foo(object, metaclass=Foo): pass").getSuperClassExpressionList();
if (paramExpressions != null) {
for (final String paramExpression : paramExpressions) {
superClassExpressionList.addArgument(generator.createParameter(paramExpression));
}
}
//If class has no expression list, then we need to add it manually.
//TODO: Investigate how to do that on PSI level, with out of stupid string concatenation
else {
final String superClassText = String.format("(%s)", StringUtil.join(superClassNames, ","));
final ASTNode node = clazz.getNameNode();
if (node != null) {
clazz.addAfter(generator.createExpressionFromText(superClassText),
node.getPsi());
}
else {
LOG.error("Class has no name node nor superclass list " + clazz);
if (keywordArguments != null) {
for (final Pair<String, String> keywordArgument : keywordArguments) {
superClassExpressionList.addArgument(generator.createKeywordArgument(languageLevel, keywordArgument.first, keywordArgument.second));
}
}
// If class has no expression list, then we need to add it manually.
if (addExpression) {
final ASTNode classNameNode = clazz.getNameNode(); // For nameless classes we simply add expression list directly to them
final PsiElement elementToAddAfter = (classNameNode == null) ? clazz.getFirstChild() : classNameNode.getPsi();
clazz.addAfter(superClassExpressionList, elementToAddAfter);
}
}
/**
* Optimizes imports resorting them and removing unneeded
*
* @param file file to optimize imports
*/
public static void optimizeImports(@NotNull final PsiFile file) {
new PyImportOptimizer().processFile(file).run();
}
/**
* Adds class attributeName (field) if it does not exist. like __metaclass__ = ABCMeta. Or CLASS_FIELD = 42.
*
* @param aClass where to add
* @param attributeName attribute's name. Like __metaclass__ or CLASS_FIELD
* @param value it's value. Like ABCMeta or 42.
* @return newly inserted attribute
*/
@Nullable
public static PsiElement addClassAttributeIfNotExist(
@NotNull final PyClass aClass,
@NotNull final String attributeName,
@NotNull final String value) {
if (aClass.findClassAttribute(attributeName, false) != null) {
return null; //Do not add any if exist already
}
final PyElementGenerator generator = PyElementGenerator.getInstance(aClass.getProject());
final String text = String.format("%s = %s", attributeName, value);
final LanguageLevel level = LanguageLevel.forElement(aClass);
final PyAssignmentStatement assignmentStatement = generator.createFromText(level, PyAssignmentStatement.class, text);
//TODO: Add metaclass to the top. Add others between last attributeName and first method
return PyUtil.addElementToStatementList(assignmentStatement, aClass.getStatementList(), true);
}
}

View File

@@ -23,7 +23,10 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.*;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.psi.*;
import com.intellij.util.PathUtil;
import com.jetbrains.python.PyNames;

View File

@@ -16,7 +16,7 @@ class PyExtractSuperclassInfoModel extends AbstractUsesDependencyMemberInfoModel
@Override
public boolean isAbstractEnabled(final PyMemberInfo<PyElement> member) {
return false;
return member.isCouldBeAbstract() && isMemberEnabled(member);
}
@Override

View File

@@ -32,7 +32,7 @@ class PyExtractSuperclassViewSwingImpl
PyExtractSuperclassViewSwingImpl(@NotNull final PyClass classUnderRefactoring,
@NotNull final Project project,
@NotNull final PyExtractSuperclassPresenter presenter) {
super(project, presenter, RefactoringBundle.message("extract.superclass.from"));
super(project, presenter, RefactoringBundle.message("extract.superclass.from"), true);
setTitle(PyExtractSuperclassHandler.REFACTORING_NAME);
@@ -92,7 +92,6 @@ class PyExtractSuperclassViewSwingImpl
super.configure(configInfo);
myFileChooserDescriptor.setRoots(configInfo.getRoots());
myTargetDirField.setText(configInfo.getDefaultFilePath());
}
@NotNull

View File

@@ -80,7 +80,7 @@ abstract class FieldsManager extends MembersManager<PyTargetExpression> {
@NotNull
@Override
public PyMemberInfo<PyTargetExpression> apply(@NotNull final PyTargetExpression input) {
return new PyMemberInfo<PyTargetExpression>(input, myStatic, input.getText(), isOverrides(input), this);
return new PyMemberInfo<PyTargetExpression>(input, myStatic, input.getText(), isOverrides(input), this, false);
}
@Nullable

View File

@@ -2,6 +2,7 @@ package com.jetbrains.python.refactoring.classes.membersManager;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyFunctionBuilder;
import com.jetbrains.python.refactoring.classes.PyClassRefactoringUtil;
import org.jetbrains.annotations.NotNull;
@@ -52,12 +53,26 @@ class InstanceFieldsManager extends FieldsManager {
//We need __init__ method, and if there is no any -- we need to create it
PyFunction toInitMethod = to.findMethodByName(PyNames.INIT, false);
if (toInitMethod == null) {
toInitMethod = PyClassRefactoringUtil.createMethod(PyNames.INIT, to, null);
toInitMethod = createInitMethod(to);
}
final PyStatementList statementList = toInitMethod.getStatementList();
return PyClassRefactoringUtil.copyFieldDeclarationToStatement(members, statementList);
}
/**
* Creates init method and adds it to certain class.
* @param to Class where method should be added
* @return newly created method
*/
//TODO: Move to utils?
@NotNull
private static PyFunction createInitMethod(@NotNull final PyClass to) {
final PyFunctionBuilder functionBuilder = new PyFunctionBuilder(PyNames.INIT);
functionBuilder.parameter(PyNames.CANONICAL_SELF); //TODO: Take param from codestyle?
final PyFunction function = functionBuilder.buildFunction(to.getProject(), LanguageLevel.forElement(to));
return PyClassRefactoringUtil.addMethods(to, function).get(0);
}
@Override
protected boolean classHasField(@NotNull final PyClass pyClass, @NotNull final String fieldName) {
return pyClass.findInstanceAttribute(fieldName, true) != null;

View File

@@ -71,7 +71,13 @@ public abstract class MembersManager<T extends PyElement> implements Function<T,
}
//TODO: Doc
/**
* Transforms elements, manager says it could move to appropriate {@link com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo}.
* Types are checked at runtime.
* @param pyClass class whose members we want to move
* @param manager manager that should check class and report list of memebers
* @return member infos
*/
//TODO: Move to TypeSafeMovingStrategy
@NotNull
@SuppressWarnings({"unchecked", "rawtypes"}) //We check type at runtime
@@ -237,7 +243,12 @@ public abstract class MembersManager<T extends PyElement> implements Function<T,
}
}
//TODO: Doc
/**
* Fetches elements from member info.
* @param memberInfos member info to fetch elements from
* @param <T> type of element
* @return list of elements
*/
@NotNull
protected static <T extends PyElement> Collection<T> fetchElements(@NotNull final Collection<PyMemberInfo<T>> memberInfos) {
return Collections2.transform(memberInfos, new PyMemberExtractor<T>());

View File

@@ -1,15 +1,21 @@
package com.jetbrains.python.refactoring.classes.membersManager;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.PsiFile;
import com.jetbrains.NotNullPredicate;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.codeInsight.imports.AddImportHelper;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyFunctionBuilder;
import com.jetbrains.python.psi.types.PyClassLikeType;
import com.jetbrains.python.psi.types.TypeEvalContext;
import com.jetbrains.python.refactoring.classes.PyClassRefactoringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.*;
/**
* Plugin that moves class methods
@@ -17,6 +23,15 @@ import java.util.List;
* @author Ilya.Kazakevich
*/
class MethodsManager extends MembersManager<PyFunction> {
private static final String ABC_META_CLASS = "ABCMeta";
/**
* Some decorators should be copied with methods if method is marked abstract. Here is list.
*/
private static final String[] DECORATORS_MAY_BE_COPIED_TO_ABSTRACT =
{PyNames.PROPERTY, PyNames.CLASSMETHOD, PyNames.STATICMETHOD};
public static final String ABC_META_PACKAGE = "abc";
MethodsManager() {
super(PyFunction.class);
@@ -30,21 +45,109 @@ class MethodsManager extends MembersManager<PyFunction> {
@Override
protected Collection<PyElement> moveMembers(@NotNull final PyClass from,
@NotNull final Collection<PyMemberInfo<PyFunction>> members,
@NotNull final PyClass... to) {
final Collection<PyFunction> elements = fetchElements(members);
@NotNull final Collection<PyMemberInfo<PyFunction>> members,
@NotNull final PyClass... to) {
final Collection<PyFunction> methodsToMove = fetchElements(Collections2.filter(members, new AbstractFilter(false)));
final Collection<PyFunction> methodsToAbstract = fetchElements(Collections2.filter(members, new AbstractFilter(true)));
makeMethodsAbstract(methodsToAbstract, to);
return moveMethods(from, methodsToMove, to);
}
/**
* Creates abstract version of each method in each class (does not touch method itself as opposite to {@link #moveMethods(com.jetbrains.python.psi.PyClass, java.util.Collection, com.jetbrains.python.psi.PyClass...)})
*
* @param currentFunctions functions to make them abstract
* @param to classes where abstract method should be created
*/
private static void makeMethodsAbstract(final Collection<PyFunction> currentFunctions, final PyClass... to) {
final Set<PsiFile> filesToCheckImport = new HashSet<PsiFile>();
final Set<PyClass> classesToAddMetaAbc = new HashSet<PyClass>();
for (final PyFunction function : currentFunctions) {
for (final PyClass destClass : to) {
final PyFunctionBuilder functionBuilder = PyFunctionBuilder.copySignature(function, DECORATORS_MAY_BE_COPIED_TO_ABSTRACT);
functionBuilder.decorate(PyNames.ABSTRACTMETHOD);
final LanguageLevel level = LanguageLevel.forElement(destClass);
PyClassRefactoringUtil.addMethods(destClass, functionBuilder.buildFunction(destClass.getProject(), level));
classesToAddMetaAbc.add(destClass);
}
}
// Add ABCMeta to new classes if needed
for (final PyClass aClass : classesToAddMetaAbc) {
if (addMetaAbcIfNeeded(aClass)) {
filesToCheckImport.add(aClass.getContainingFile());
}
}
// Add imports for ABC if needed
for (final PsiFile file : filesToCheckImport) {
addImportFromAbc(file, PyNames.ABSTRACTMETHOD);
addImportFromAbc(file, ABC_META_CLASS);
PyClassRefactoringUtil.optimizeImports(file); //To remove redundant imports
}
}
/**
* Adds metaclass = ABCMeta for class if has no.
*
* @param aClass class where it should be added
* @return true if added. False if class already has metaclass so we did not touch it.
*/
// TODO: Copy/Paste with PyClass.getMeta..
private static boolean addMetaAbcIfNeeded(@NotNull final PyClass aClass) {
final PsiFile file = aClass.getContainingFile();
final PyClassLikeType type = aClass.getMetaClassType(TypeEvalContext.userInitiated(file));
if (type != null) {
return false; //User already has metaclass. He probably knows about metaclasses, so we should not add ABCMeta
}
final LanguageLevel languageLevel = LanguageLevel.forElement(aClass);
if (languageLevel.isPy3K()) { //TODO: Copy/paste, use strategy because we already has the same check in #couldBeAbstract
// Add (metaclass= for Py3K
PyClassRefactoringUtil
.addSuperClassExpressions(aClass.getProject(), aClass, null, Collections.singletonList(Pair.create(PyNames.METACLASS,
ABC_META_CLASS)));
}
else {
// Add __metaclass__ for Py2
PyClassRefactoringUtil.addClassAttributeIfNotExist(aClass, PyNames.DUNDER_METACLASS, ABC_META_CLASS);
}
return true;
}
/**
* Adds import from ABC module
*
* @param file where to add import
* @param nameToImport what to import
*/
private static void addImportFromAbc(@NotNull final PsiFile file, @NotNull final String nameToImport) {
AddImportHelper.addImportFromStatement(file, ABC_META_PACKAGE, nameToImport, null,
AddImportHelper.ImportPriority.BUILTIN);
}
/**
* Moves methods (as opposite to {@link #makeMethodsAbstract(java.util.Collection, com.jetbrains.python.psi.PyClass...)})
*
* @param from source
* @param methodsToMove what to move
* @param to where
* @return newly added methods
*/
private static List<PyElement> moveMethods(final PyClass from, final Collection<PyFunction> methodsToMove, final PyClass... to) {
final List<PyElement> result = new ArrayList<PyElement>();
for (final PyClass destClass : to) {
//We move copies here because we there may be several destinations
final List<PyFunction> copies = new ArrayList<PyFunction>(elements.size());
for (final PyFunction element : elements) {
//We move copies here because there may be several destinations
final List<PyFunction> copies = new ArrayList<PyFunction>(methodsToMove.size());
for (final PyFunction element : methodsToMove) {
final PyFunction newMethod = (PyFunction)element.copy();
copies.add(newMethod);
}
result.addAll(PyClassRefactoringUtil.copyMethods(copies, destClass));
}
deleteElements(elements);
deleteElements(methodsToMove);
PyClassRefactoringUtil.insertPassIfNeeded(from);
return result;
@@ -56,9 +159,27 @@ class MethodsManager extends MembersManager<PyFunction> {
final PyUtil.MethodFlags flags = PyUtil.MethodFlags.of(pyFunction);
assert flags != null : "No flags return while element is function " + pyFunction;
final boolean isStatic = flags.isStaticMethod() || flags.isClassMethod();
return new PyMemberInfo<PyFunction>(pyFunction, isStatic, buildDisplayMethodName(pyFunction), isOverrides(pyFunction), this);
return new PyMemberInfo<PyFunction>(pyFunction, isStatic, buildDisplayMethodName(pyFunction), isOverrides(pyFunction), this,
couldBeAbstract(pyFunction));
}
/**
* @return if method could be made abstract? (that means "create abstract version if method in parent class")
*/
private static boolean couldBeAbstract(@NotNull final PyFunction function) {
if (PyUtil.isInit(function)) {
return false; // Who wants to make __init__ abstract?!
}
final PyUtil.MethodFlags flags = PyUtil.MethodFlags.of(function);
assert flags != null : "Function should be called on method!";
final boolean py3K = LanguageLevel.forElement(function).isPy3K();
//TODO: use strategy because we already has the same check in #addMetaAbcIfNeeded
return flags.isInstanceMethod() || py3K; //Any method could be made abstract in py3
}
@Nullable
private static Boolean isOverrides(final PyFunction pyFunction) {
final PyClass clazz = PyUtil.getContainingClassOrSelf(pyFunction);
@@ -86,4 +207,24 @@ class MethodsManager extends MembersManager<PyFunction> {
builder.append(')');
return builder.toString();
}
/**
* Filters member infos to find if they should be abstracted
*/
private static class AbstractFilter extends NotNullPredicate<PyMemberInfo<PyFunction>> {
private final boolean myAllowAbstractOnly;
/**
* @param allowAbstractOnly returns only methods to be abstracted. Returns only methods to be moved otherwise.
*/
private AbstractFilter(final boolean allowAbstractOnly) {
myAllowAbstractOnly = allowAbstractOnly;
}
@Override
protected boolean applyNotNull(@NotNull final PyMemberInfo<PyFunction> input) {
return input.isToAbstract() == myAllowAbstractOnly;
}
}
}

View File

@@ -26,25 +26,29 @@ import org.jetbrains.annotations.Nullable;
public class PyMemberInfo<T extends PyElement> extends MemberInfoBase<T> {
@NotNull
private final MembersManager<T> myMembersManager;
private final boolean myCouldBeAbstract;
/**
* @param member element itself
* @param isStatic is it static or not?
* @param displayName element display name
* @param overrides does it overrides something? TRUE if is overriden, FALSE if implemented, null if not implemented or overriden
* TODO: use primitive instead? "Implemeneted" has nothing to do with python duck-typing
* TODO: Doc new param
* @param member element itself
* @param isStatic is it static or not?
* @param displayName element display name
* @param overrides does it overrides something? TRUE if is overriden, FALSE if implemented, null if not implemented or overriden
* TODO: use primitive instead? "Implemeneted" has nothing to do with python duck-typing
* @param membersManager manager that knows how to handle this member
*/
PyMemberInfo(@NotNull final T member,
final boolean isStatic,
@NotNull final String displayName,
@Nullable final Boolean overrides,
@NotNull final MembersManager<T> membersManager) {
final boolean isStatic,
@NotNull final String displayName,
@Nullable final Boolean overrides,
@NotNull final MembersManager<T> membersManager,
final boolean couldBeAbstract) {
super(member);
this.isStatic = isStatic;
this.displayName = displayName;
this.overrides = overrides;
myMembersManager = membersManager;
myCouldBeAbstract = couldBeAbstract;
}
@NotNull
@@ -52,6 +56,10 @@ public class PyMemberInfo<T extends PyElement> extends MemberInfoBase<T> {
return myMembersManager;
}
public boolean isCouldBeAbstract() {
return myCouldBeAbstract;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof PyMemberInfo) {

View File

@@ -52,6 +52,6 @@ class SuperClassesManager extends MembersManager<PyClass> {
public PyMemberInfo<PyClass> apply(@NotNull final PyClass input) {
final String name = RefactoringBundle.message("member.info.extends.0", PyClassCellRenderer.getClassText(input));
//TODO: Check for "overrides"
return new PyMemberInfo<PyClass>(input, false, name, false, this);
return new PyMemberInfo<PyClass>(input, false, name, false, this, false);
}
}

View File

@@ -10,7 +10,7 @@ import java.util.ArrayList;
import java.util.Collection;
/**
* TODO: Doc
* Moves members checking types at runtime.
*
* @author Ilya.Kazakevich
*/
@@ -20,6 +20,13 @@ class TypeSafeMovingStrategy<T extends PyElement> {
@NotNull private final Collection<PyMemberInfo<T>> myMemberInfoCollection;
@NotNull private final PyClass[] myTo;
/**
* Move members.
* @param from source
* @param manager manager to be used
* @param memberInfoCollection what to move
* @param to where
*/
@SuppressWarnings({"unchecked", "rawtypes"}) //We check types at runtime
static void moveCheckingTypesAtRunTime(@NotNull final PyClass from,
@NotNull final MembersManager<?> manager,
@@ -40,21 +47,26 @@ class TypeSafeMovingStrategy<T extends PyElement> {
}
//TODO: Doc
/**
* While types are already checked at runtime, this method could move everything in type-safe manner.
*/
private void moveTyped() {
final Collection<T> elementsCollection = MembersManager.fetchElements(myMemberInfoCollection);
final Collection<? extends PyElement> references = myManager.getElementsToStoreReferences(elementsCollection);
// Store references to add required imports
for (final PyElement element : references) {
PyClassRefactoringUtil.rememberNamedReferences(element, PyNames.CANONICAL_SELF); //"self" is not reference we need to move
}
// Move
final Collection<PyElement> newElements = myManager.moveMembers(myFrom, myMemberInfoCollection, myTo);
//Store/Restore to add appropriate imports
// Restore references to add appropriate imports
for (final PyElement element : newElements) {
PyClassRefactoringUtil.restoreNamedReferences(element);
}
PyClassRefactoringUtil.optimizeImports(myFrom.getContainingFile()); //To remove unneeded imports from source
PyClassRefactoringUtil.optimizeImports(myFrom.getContainingFile()); // To remove unneeded imports from source
}
}

View File

@@ -56,16 +56,20 @@ public abstract class MembersBasedViewSwingImpl<P extends MembersBasedPresenter,
/**
* @param project project this view runs
* @param presenter view's presenter
* @param title window title
*
* @param project project this view runs
* @param presenter view's presenter
* @param title window title
* @param supportAbstract supports "abstract" column?
*/
protected MembersBasedViewSwingImpl(@NotNull final Project project, @NotNull final P presenter, @NotNull final String title) {
protected MembersBasedViewSwingImpl(@NotNull final Project project, @NotNull final P presenter, @NotNull final String title,
final boolean supportAbstract) {
super(project, true);
myTopPanel = new JPanel(new BorderLayout());
myCenterPanel = new JPanel(new BorderLayout());
myPresenter = presenter;
myPyMemberSelectionPanel = new PyMemberSelectionPanel(title);
myPyMemberSelectionPanel = new PyMemberSelectionPanel(title, supportAbstract);
//TODO: Take this from presenter to prevent inconsistence: now it is possible to create view that supports abstract backed by presenter that does not. And vice versa.
}
@Override

View File

@@ -109,7 +109,7 @@ class PyPullUpPresenterImpl extends MembersBasedPresenterWithPreviewImpl<PyPullU
@Override
public boolean isAbstractEnabled(final PyMemberInfo<PyElement> member) {
return false;
return member.isCouldBeAbstract() && isMemberEnabled(member); // TODO: copy paste with other models, get rid of
}
@Override

View File

@@ -46,7 +46,7 @@ class PyPullUpViewSwingImpl extends MembersBasedViewSwingImpl<PyPullUpPresenter,
* @param clazz class to refactor
*/
PyPullUpViewSwingImpl(@NotNull final Project project, @NotNull final PyPullUpPresenter presenter, @NotNull final PyClass clazz) {
super(project, presenter, RefactoringBundle.message("members.to.be.pulled.up"));
super(project, presenter, RefactoringBundle.message("members.to.be.pulled.up"), true);
setTitle(PyPullUpHandler.REFACTORING_NAME);
myParentsComboBoxModel = new DefaultComboBoxModel();

View File

@@ -10,7 +10,7 @@ import org.jetbrains.annotations.NotNull;
import java.awt.*;
/**
* Created by Ilya.Kazakevich on 10.02.14.
* @author Ilya.Kazakevich
*/
public class PyPushDownViewSwingImpl extends MembersBasedViewSwingImpl<PyPushDownPresenter, MembersViewInitializationInfo>
implements PyPushDownView {
@@ -18,7 +18,7 @@ public class PyPushDownViewSwingImpl extends MembersBasedViewSwingImpl<PyPushDow
@NotNull final PyClass classUnderRefactoring,
@NotNull final Project project,
@NotNull final PyPushDownPresenter presenter) {
super(project, presenter, RefactoringBundle.message("push.members.from.0.down.label", classUnderRefactoring.getName()));
super(project, presenter, RefactoringBundle.message("push.members.from.0.down.label", classUnderRefactoring.getName()), false);
myCenterPanel.add(myPyMemberSelectionPanel, BorderLayout.CENTER);
setTitle(PyPushDownHandler.REFACTORING_NAME);

View File

@@ -46,8 +46,8 @@ public class PyMemberSelectionPanel extends JPanel {
*
* @param title
*/
public PyMemberSelectionPanel(@NotNull String title) {
this(title, EMPTY_MEMBER_INFO, null);
public PyMemberSelectionPanel(@NotNull String title, boolean supportAbstract) {
this(title, EMPTY_MEMBER_INFO, null, supportAbstract);
}
/**
@@ -57,14 +57,18 @@ public class PyMemberSelectionPanel extends JPanel {
* @param memberInfo list of members
* @param model model
*/
public PyMemberSelectionPanel(String title, List<PyMemberInfo<PyElement>> memberInfo, final MemberInfoModel<PyElement, PyMemberInfo<PyElement>> model) {
public PyMemberSelectionPanel(
String title,
List<PyMemberInfo<PyElement>> memberInfo,
final MemberInfoModel<PyElement, PyMemberInfo<PyElement>> model,
final boolean supportAbstract) {
Border titledBorder = IdeBorderFactory.createTitledBorder(title, false);
Border emptyBorder = BorderFactory.createEmptyBorder(0, 5, 5, 5);
Border border = BorderFactory.createCompoundBorder(titledBorder, emptyBorder);
setBorder(border);
setLayout(new BorderLayout());
myTable = new PyMemberSelectionTable(memberInfo, model);
myTable = new PyMemberSelectionTable(memberInfo, model, supportAbstract);
JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(myTable);

View File

@@ -17,12 +17,15 @@ package com.jetbrains.python.refactoring.classes.ui;
import com.intellij.icons.AllIcons;
import com.intellij.psi.PsiElement;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.classMembers.MemberInfoModel;
import com.intellij.refactoring.ui.AbstractMemberSelectionTable;
import com.intellij.ui.RowIcon;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.psi.PyFunction;
import com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.List;
@@ -31,21 +34,35 @@ import java.util.List;
* @author Dennis.Ushakov
*/
public class PyMemberSelectionTable extends AbstractMemberSelectionTable<PyElement, PyMemberInfo<PyElement>> {
public PyMemberSelectionTable(final List<PyMemberInfo<PyElement>> memberInfos,
final MemberInfoModel<PyElement, PyMemberInfo<PyElement>> model) {
super(memberInfos, model, null);
private static final String ABSTRACT_TITLE = RefactoringBundle.message("make.abstract");
private final boolean mySupportAbstract;
public PyMemberSelectionTable(
@NotNull final List<PyMemberInfo<PyElement>> memberInfos,
@Nullable final MemberInfoModel<PyElement, PyMemberInfo<PyElement>> model,
final boolean supportAbstract) {
super(memberInfos, model, (supportAbstract ? ABSTRACT_TITLE : null));
mySupportAbstract = supportAbstract;
}
protected Object getAbstractColumnValue(PyMemberInfo<PyElement> memberInfo) {
return null;
@Nullable
@Override
protected Object getAbstractColumnValue(final PyMemberInfo<PyElement> memberInfo) {
//TODO: Too many logic, move to presenters
return (mySupportAbstract && memberInfo.isChecked() && myMemberInfoModel.isAbstractEnabled(memberInfo)) ? memberInfo.isToAbstract() : null;
}
protected boolean isAbstractColumnEditable(int rowIndex) {
return false;
@Override
protected boolean isAbstractColumnEditable(final int rowIndex) {
return mySupportAbstract && myMemberInfoModel.isAbstractEnabled(myMemberInfos.get(rowIndex));
}
protected void setVisibilityIcon(PyMemberInfo<PyElement> memberInfo, RowIcon icon) {}
@Override
protected void setVisibilityIcon(PyMemberInfo<PyElement> memberInfo, RowIcon icon) {
}
@Override
protected Icon getOverrideIcon(PyMemberInfo<PyElement> memberInfo) {
final PsiElement member = memberInfo.getMember();
Icon overrideIcon = EMPTY_OVERRIDE_ICON;

View File

@@ -59,7 +59,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* * TODO: Merge with {@link com.jetbrains.python.refactoring.classes.PyClassRefactoringUtil#createMethod(String, com.jetbrains.python.psi.PyClass, com.jetbrains.python.psi.PyFunction.Modifier, String...)}
* * TODO: Merge with {@link com.jetbrains.python.refactoring.classes.PyClassRefactoringUtil#createMethod(String, com.jetbrains.python.psi.PyClass, com.jetbrains.python.psi.PyFunction.Modifier, java.util.Collection, String...)}
* @author oleg
*/
public class PyExtractMethodUtil {

View File

@@ -0,0 +1,8 @@
# Stubs
class ABCMeta:
pass
def abstractmethod(foo):
pass

View File

@@ -0,0 +1,10 @@
from abc import abstractmethod
from abc import ABCMeta
class NewParent(object):
__metaclass__ = ABCMeta
@abstractmethod
def foo_method(self):
pass

View File

@@ -0,0 +1,6 @@
from dest_module import NewParent
class MyClass(NewParent):
def foo_method(self):
spam = "eggs"

View File

@@ -0,0 +1,3 @@
class MyClass(object):
def foo_method(self):
spam = "eggs"

View File

@@ -0,0 +1,8 @@
# Stubs
class ABCMeta:
pass
def abstractmethod(foo):
pass

View File

@@ -0,0 +1,13 @@
from abc import ABCMeta
from abc import abstractmethod
ABCMeta()
abstractmethod()
class NewParent(object, metaclass=ABCMeta):
@classmethod
@abstractmethod
def foo_method(cls):
pass

View File

@@ -0,0 +1,6 @@
from abc import ABCMeta
from abc import abstractmethod
ABCMeta()
abstractmethod()

View File

@@ -0,0 +1,7 @@
from dest_module import NewParent
class MyClass(NewParent):
@classmethod
def foo_method(cls):
spam = "eggs"

View File

@@ -0,0 +1,5 @@
from shared_module import object
class MyClass(object):
@classmethod
def foo_method(cls):
spam = "eggs"

View File

@@ -2,7 +2,7 @@ from datetime import date
class Child(object, date):
CLASS_VAR = "spam"
def eggs(self):
def eggs(self): # May be abstract
pass
def __init__(self):
@@ -11,5 +11,5 @@ class Child(object, date):
class StaticOnly(object):
@staticmethod
def static_method():
def static_method(): # May be abstract in case of Py3
pass

View File

@@ -0,0 +1,4 @@
from SuperClass import Parent
class Child(Parent):
def my_method(self, foo):
bar = foo

View File

@@ -0,0 +1,4 @@
from SuperClass import Parent
class Child(Parent):
def my_method(self, foo):
bar = foo

View File

@@ -0,0 +1,13 @@
from abc import ABCMeta, abstractmethod
class Parent(object):
__metaclass__ = ABCMeta
@abstractmethod
def my_method2(self):
pass
@abstractmethod
def my_method(self, foo):
pass

View File

@@ -0,0 +1,9 @@
from abc import ABCMeta, abstractmethod
class Parent(object):
__metaclass__ = ABCMeta
@abstractmethod
def my_method2(self):
pass

View File

@@ -0,0 +1,8 @@
# Stubs
class ABCMeta:
pass
def abstractmethod(foo):
pass

View File

@@ -0,0 +1,7 @@
from SuperClass import Parent
class Child(Parent):
def my_method(self, foo):
bar = foo
def my_method_2(self, foo):
bar = foo

View File

@@ -0,0 +1,7 @@
from SuperClass import Parent
class Child(Parent):
def my_method(self, foo):
bar = foo
def my_method_2(self, foo):
bar = foo

View File

@@ -0,0 +1,14 @@
from abc import ABCMeta
from abc import abstractmethod
class Parent(object):
__metaclass__ = ABCMeta
@abstractmethod
def my_method(self, foo):
pass
@abstractmethod
def my_method_2(self, foo):
pass

View File

@@ -0,0 +1,2 @@
class Parent(object):
pass

View File

@@ -0,0 +1,8 @@
# Stubs
class ABCMeta:
pass
def abstractmethod(foo):
pass

View File

@@ -0,0 +1,8 @@
from SuperClass import Parent
class Child(Parent):
def my_method(self, foo):
bar = foo
@classmethod
def my_class_method():
print("Q")

View File

@@ -0,0 +1,8 @@
from SuperClass import Parent
class Child(Parent):
def my_method(self, foo):
bar = foo
@classmethod
def my_class_method():
print("Q")

View File

@@ -0,0 +1,12 @@
from abc import ABCMeta
from abc import abstractmethod
from abc import object
class Parent(object, metaclass=ABCMeta):
@abstractmethod
def my_method(self, foo):
pass
@classmethod
@abstractmethod
def my_class_method():
pass

View File

@@ -0,0 +1,3 @@
from abc import object
class Parent(object):
pass

View File

@@ -0,0 +1,11 @@
# Stubs
class object:
pass
class ABCMeta:
pass
def abstractmethod(foo):
pass

View File

@@ -42,11 +42,11 @@ class HugeChild(SubParent1, date): #SubParent1 is disabled
pass
@classmethod
def static_1(cls):
def static_1(cls): # Could be abstract in Py3K
pass
@staticmethod
def static_2():
def static_2(): # Could be abstract in Py3K
pass

View File

@@ -13,12 +13,12 @@ public class NameAndStatusTransformer implements Function<PyMemberInfo<PyElement
@NotNull
private final MemberInfoModel<PyElement, PyMemberInfo<PyElement>> myMemberInfoModel;
public NameAndStatusTransformer(MemberInfoModel<PyElement, PyMemberInfo<PyElement>> memberInfoModel) {
public NameAndStatusTransformer(@NotNull final MemberInfoModel<PyElement, PyMemberInfo<PyElement>> memberInfoModel) {
myMemberInfoModel = memberInfoModel;
}
@Override
public PyPresenterTestMemberEntry apply(final PyMemberInfo<PyElement> input) {
return new PyPresenterTestMemberEntry(input.getDisplayName(), myMemberInfoModel.isMemberEnabled(input), input.isStatic());
return new PyPresenterTestMemberEntry(input.getDisplayName(), myMemberInfoModel.isMemberEnabled(input), input.isStatic(), myMemberInfoModel.isAbstractEnabled(input));
}
}

View File

@@ -12,24 +12,28 @@ public class PyPresenterTestMemberEntry {
private final String myName;
private final boolean myEnabled;
private final boolean myStaticEntry;
private final boolean myMayBeAbstract;
/**
* @param name name of the member
* @param enabled is member enabled or not
* @param staticEntry is member static entry
* @param mayBeAbstract if element has "abstract" checkbox or not
*/
public PyPresenterTestMemberEntry(@NotNull final String name, final boolean enabled, final boolean staticEntry) {
public PyPresenterTestMemberEntry(@NotNull final String name, final boolean enabled, final boolean staticEntry, final boolean mayBeAbstract) {
myName = name;
myEnabled = enabled;
myStaticEntry = staticEntry;
myMayBeAbstract = mayBeAbstract;
}
@Override
public String toString() {
return "Entry{" +
return "PyPresenterTestMemberEntry{" +
"myName='" + myName + '\'' +
", myEnabled=" + myEnabled +
", myStaticEntry=" + myStaticEntry +
", myMayBeAbstract=" + myMayBeAbstract +
'}';
}
@@ -41,6 +45,7 @@ public class PyPresenterTestMemberEntry {
final PyPresenterTestMemberEntry entry = (PyPresenterTestMemberEntry)o;
if (myEnabled != entry.myEnabled) return false;
if (myMayBeAbstract != entry.myMayBeAbstract) return false;
if (myStaticEntry != entry.myStaticEntry) return false;
if (!myName.equals(entry.myName)) return false;
@@ -52,6 +57,7 @@ public class PyPresenterTestMemberEntry {
int result = myName.hashCode();
result = 31 * result + (myEnabled ? 1 : 0);
result = 31 * result + (myStaticEntry ? 1 : 0);
result = 31 * result + (myMayBeAbstract ? 1 : 0);
return result;
}
}

View File

@@ -1,5 +1,6 @@
package com.jetbrains.python.refactoring.classes.extractSuperclass;
import com.jetbrains.python.psi.LanguageLevel;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.refactoring.classes.PyMemberInfoStorage;
@@ -31,12 +32,32 @@ public class PyExtractSuperclassPresenterTest
/**
* Tests that static methods could be moved, but "extends object" is not in list
* Also checks that static method could NOT be made abstract in Py2K
*/
public void testStaticNoObject() {
public void testStaticNoObjectPy2() {
ensureStaticNoObject(false);
}
/**
* Tests that static methods could be moved, but "extends object" is not in list
* Also checks that static method COULD be made abstract in Py3K
*/
public void testStaticNoObjectPy3() {
setLanguageLevel(LanguageLevel.PYTHON32);
ensureStaticNoObject(true);
}
/**
* Tests that static methods could be moved, but "extends object" is not in list.
* Also checks that static method could be made abstract in Py3K, but not in Py2K
*
* @param py3k if py 3?
*/
private void ensureStaticNoObject( final boolean py3k) {
final Collection<PyPresenterTestMemberEntry> members = launchAndGetMembers("StaticOnly");
final Matcher<Iterable<? extends PyPresenterTestMemberEntry>> matcher =
Matchers.containsInAnyOrder(new PyPresenterTestMemberEntry("static_method()", true, true));
Matchers.containsInAnyOrder(new PyPresenterTestMemberEntry("static_method()", true, true, py3k));
compareMembers(members, matcher);
}
@@ -103,11 +124,11 @@ public class PyExtractSuperclassPresenterTest
final Collection<PyPresenterTestMemberEntry> members = launchAndGetMembers("Child");
final Matcher<Iterable<? extends PyPresenterTestMemberEntry>> matcher = Matchers
.containsInAnyOrder(new PyPresenterTestMemberEntry("CLASS_VAR", true, true),
new PyPresenterTestMemberEntry("eggs(self)", true, false),
new PyPresenterTestMemberEntry("__init__(self)", true, false),
new PyPresenterTestMemberEntry("self.artur", true, false),
new PyPresenterTestMemberEntry("extends date", true, false));
.containsInAnyOrder(new PyPresenterTestMemberEntry("CLASS_VAR", true, true, false),
new PyPresenterTestMemberEntry("eggs(self)", true, false, true),
new PyPresenterTestMemberEntry("__init__(self)", true, false, false),
new PyPresenterTestMemberEntry("self.artur", true, false, false),
new PyPresenterTestMemberEntry("extends date", true, false, false));
compareMembers(members, matcher);
}

View File

@@ -22,6 +22,7 @@ import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.util.ArrayUtil;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.psi.LanguageLevel;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.refactoring.classes.PyClassRefactoringTest;
@@ -45,12 +46,24 @@ public class PyExtractSuperclassTest extends PyClassRefactoringTest {
// Checks that moving methods between files moves imports as well
public void testImportMultiFile() throws Throwable {
multiFileTestHelper(".do_useful_stuff");
multiFileTestHelper(".do_useful_stuff", false);
}
// Checks that moving methods between files moves superclass expressions as well
public void testMoveExtends() throws Throwable {
multiFileTestHelper("TheParentOfItAll");
multiFileTestHelper("TheParentOfItAll", false);
}
// Extracts method as abstract
public void testMoveAndMakeAbstract() throws Throwable {
multiFileTestHelper(".foo_method", true);
}
// Extracts method as abstract and ensures that newly created class imports ABC in Py3
public void testMoveAndMakeAbstractImportExistsPy3() throws Throwable {
setLanguageLevel(LanguageLevel.PYTHON30);
configureMultiFile("abc");
multiFileTestHelper(".foo_method", true);
}
/**
@@ -59,12 +72,13 @@ public class PyExtractSuperclassTest extends PyClassRefactoringTest {
*
* @param memberToMove name of the member to move
*/
private void multiFileTestHelper(@NotNull final String memberToMove) {
private void multiFileTestHelper(@NotNull final String memberToMove, final boolean toAbstract) {
final String[] modules = {"dest_module", "source_module"};
configureMultiFile(ArrayUtil.mergeArrays(modules, "shared_module"));
myFixture.configureByFile("source_module.py");
final String sourceClass = "MyClass";
final PyMemberInfo<PyElement> member = findMemberInfo(sourceClass, memberToMove);
member.setToAbstract(toAbstract);
final String destUrl = myFixture.getFile().getVirtualFile().getParent().findChild("dest_module.py").getUrl();
new WriteCommandAction.Simple(myFixture.getProject()) {
@Override

View File

@@ -1,6 +1,7 @@
package com.jetbrains.python.refactoring.classes.pullUp;
import com.google.common.collect.Collections2;
import com.jetbrains.python.psi.LanguageLevel;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.refactoring.classes.PyMemberInfoStorage;
import com.jetbrains.python.refactoring.classes.PyPresenterTestMemberEntry;
@@ -68,34 +69,49 @@ public class PyPullUpPresenterTest extends PyRefactoringPresenterTestCase<PyPull
public void testNoMoveParentToItSelf() throws Exception {
final Collection<PyPresenterTestMemberEntry> memberNamesAndStatus = launchAndGetMembers("Foo", "Bar");
compareMembers(memberNamesAndStatus, Matchers.containsInAnyOrder(new PyPresenterTestMemberEntry("__init__(self)", true, false),
new PyPresenterTestMemberEntry("self.foo", true, false),
new PyPresenterTestMemberEntry("extends Bar", false, false)));
compareMembers(memberNamesAndStatus, Matchers.containsInAnyOrder(new PyPresenterTestMemberEntry("__init__(self)", true, false, false),
new PyPresenterTestMemberEntry("self.foo", true, false, false),
new PyPresenterTestMemberEntry("extends Bar", false, false, false)));
}
/**
* Checks that some members are not allowed, while others are
* Checks that some members are not allowed (and may nto be abstract), while others are for Py2
*/
public void testMembers() throws Exception {
public void testMembersPy2() throws Exception {
ensureCorrectMembersForHugeChild(false);
}
/**
* Checks that some members are not allowed (and may nto be abstract), while others are for Py3
*/
public void testMembersPy3() throws Exception {
setLanguageLevel(LanguageLevel.PYTHON30);
ensureCorrectMembersForHugeChild(true);
}
/**
* Checks members for class HugeChild
*
* @param py3K if python 3
*/
private void ensureCorrectMembersForHugeChild(final boolean py3K) {
final Collection<PyPresenterTestMemberEntry> memberNamesAndStatus = launchAndGetMembers("HugeChild", "SubParent1");
//Pair will return correct type
final Matcher<Iterable<? extends PyPresenterTestMemberEntry>> matcher = Matchers
.containsInAnyOrder(new PyPresenterTestMemberEntry("extends date", true, false),
new PyPresenterTestMemberEntry("CLASS_FIELD", true, true),
new PyPresenterTestMemberEntry("__init__(self)", true, false),
new PyPresenterTestMemberEntry("extends SubParent1", false, false),
new PyPresenterTestMemberEntry("foo(self)", false, false),
new PyPresenterTestMemberEntry("bar(self)", true, false),
new PyPresenterTestMemberEntry("static_1(cls)", true, true),
new PyPresenterTestMemberEntry("static_2()", true, true),
new PyPresenterTestMemberEntry("self.instance_field_1", true, false),
new PyPresenterTestMemberEntry("self.instance_field_2", true, false),
new PyPresenterTestMemberEntry("bad_method()", true, false));
.containsInAnyOrder(new PyPresenterTestMemberEntry("extends date", true, false, false),
new PyPresenterTestMemberEntry("CLASS_FIELD", true, true, false),
new PyPresenterTestMemberEntry("__init__(self)", true, false, false),
new PyPresenterTestMemberEntry("extends SubParent1", false, false, false),
new PyPresenterTestMemberEntry("foo(self)", false, false, false),
new PyPresenterTestMemberEntry("bar(self)", true, false, true),
new PyPresenterTestMemberEntry("static_1(cls)", true, true, py3K),
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));
compareMembers(memberNamesAndStatus, matcher);
}
/**
@@ -146,6 +162,4 @@ public class PyPullUpPresenterTest extends PyRefactoringPresenterTestCase<PyPull
final PyMemberInfoStorage storage = new PyMemberInfoStorage(childClass);
return new PyPullUpPresenterImpl(myView, storage, childClass);
}
}

View File

@@ -15,13 +15,17 @@
*/
package com.jetbrains.python.refactoring.classes.pullUp;
import com.intellij.util.ArrayUtil;
import com.jetbrains.python.psi.LanguageLevel;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.refactoring.classes.PyClassRefactoringTest;
import com.jetbrains.python.refactoring.classes.membersManager.MembersManager;
import com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo;
import org.jetbrains.annotations.NotNull;
import java.util.Collections;
import java.util.ArrayList;
import java.util.Collection;
/**
* @author Dennis.Ushakov
@@ -87,29 +91,72 @@ public class PyPullUpTest extends PyClassRefactoringTest {
public void testFieldMove() {
final String[] modules = {"Class", "SuperClass"};
configureMultiFile(modules);
doPullUp("AnyClass", "#COPYRIGHT", "SuperClass");
doPullUp("AnyClass", "#version", "SuperClass");
doPullUp("AnyClass", "SuperClass", "#COPYRIGHT");
doPullUp("AnyClass", "SuperClass", "#version");
checkMultiFile(modules);
}
private void doMultiFileTest() {
final String[] modules = {"Class", "SuperClass"};
configureMultiFile(modules);
doPullUp("AnyClass", ".this_should_be_in_super", "SuperClass");
doPullUp("AnyClass", "SuperClass", ".this_should_be_in_super");
checkMultiFile(modules);
}
/**
* Ensures that pulling abstract method up to class that already uses ABCMeta works correctly
*/
public void testAbstractMethodHasMeta() {
checkAbstract(".my_method");
}
/**
* Ensures that pulling abstract method up to class that has NO ABCMeta works correctly for py2 (__metaclass__ is added)
*/
public void testAbstractMethodPy2AddMeta() {
checkAbstract(".my_method", ".my_method_2");
}
/**
* Ensures that pulling abstract method up to class that has NO ABCMeta works correctly for py3k (metaclass is added)
*/
public void testAbstractMethodPy3AddMeta() {
setLanguageLevel(LanguageLevel.PYTHON34);
checkAbstract(".my_method", ".my_class_method");
}
/**
* Moves methods fromn Child to Parent and make them abstract
* @param methodNames methods to check
*/
private void checkAbstract(@NotNull final String... methodNames) {
final String[] modules = {"Class", "SuperClass"};
configureMultiFile(ArrayUtil.mergeArrays(modules, "abc"));
doPullUp("Child", "Parent", true, methodNames);
checkMultiFile(modules);
}
private void doHelperTest(final String className, final String memberName, final String superClassName) {
myFixture.configureByFile(getMultiFileBaseName() + ".py");
doPullUp(className, memberName, superClassName);
doPullUp(className, superClassName, memberName);
myFixture.checkResultByFile(getMultiFileBaseName() + ".after.py");
}
private void doPullUp(String className, String memberName, String superClassName) {
private void doPullUp(final String className, final String superClassName, final String memberName) {
doPullUp(className, superClassName, false, memberName);
}
private void doPullUp(final String className, final String superClassName, final boolean toAbstract, final String... memberNames ) {
final PyClass clazz = findClass(className);
final PyElement member = findMember(className, memberName);
final PyClass superClass = findClass(superClassName);
final Collection<PyMemberInfo<PyElement>> membersToMove = new ArrayList<PyMemberInfo<PyElement>>(memberNames.length);
for (final String memberName : memberNames) {
final PyElement member = findMember(className, memberName);
final PyMemberInfo<PyElement> memberInfo = MembersManager.findMember(clazz, member);
memberInfo.setToAbstract(toAbstract);
membersToMove.add(memberInfo);
}
moveViaProcessor(clazz.getProject(),
new PyPullUpProcessor(clazz, superClass, Collections.singleton(MembersManager.findMember(clazz, member))));
new PyPullUpProcessor(clazz, superClass, membersToMove));
}
}