mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-06 03:21:12 +07:00
PY-2858 Pull Up Abstract Method (finally!)
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -16,7 +16,7 @@ class PyExtractSuperclassInfoModel extends AbstractUsesDependencyMemberInfoModel
|
||||
|
||||
@Override
|
||||
public boolean isAbstractEnabled(final PyMemberInfo<PyElement> member) {
|
||||
return false;
|
||||
return member.isCouldBeAbstract() && isMemberEnabled(member);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>());
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
# Stubs
|
||||
|
||||
class ABCMeta:
|
||||
pass
|
||||
|
||||
|
||||
def abstractmethod(foo):
|
||||
pass
|
||||
@@ -0,0 +1,10 @@
|
||||
from abc import abstractmethod
|
||||
from abc import ABCMeta
|
||||
|
||||
|
||||
class NewParent(object):
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
@abstractmethod
|
||||
def foo_method(self):
|
||||
pass
|
||||
@@ -0,0 +1,6 @@
|
||||
from dest_module import NewParent
|
||||
|
||||
|
||||
class MyClass(NewParent):
|
||||
def foo_method(self):
|
||||
spam = "eggs"
|
||||
@@ -0,0 +1,3 @@
|
||||
class MyClass(object):
|
||||
def foo_method(self):
|
||||
spam = "eggs"
|
||||
@@ -0,0 +1,8 @@
|
||||
# Stubs
|
||||
|
||||
class ABCMeta:
|
||||
pass
|
||||
|
||||
|
||||
def abstractmethod(foo):
|
||||
pass
|
||||
@@ -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
|
||||
@@ -0,0 +1,6 @@
|
||||
from abc import ABCMeta
|
||||
from abc import abstractmethod
|
||||
|
||||
|
||||
ABCMeta()
|
||||
abstractmethod()
|
||||
@@ -0,0 +1,7 @@
|
||||
from dest_module import NewParent
|
||||
|
||||
|
||||
class MyClass(NewParent):
|
||||
@classmethod
|
||||
def foo_method(cls):
|
||||
spam = "eggs"
|
||||
@@ -0,0 +1,5 @@
|
||||
from shared_module import object
|
||||
class MyClass(object):
|
||||
@classmethod
|
||||
def foo_method(cls):
|
||||
spam = "eggs"
|
||||
@@ -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
|
||||
@@ -0,0 +1,4 @@
|
||||
from SuperClass import Parent
|
||||
class Child(Parent):
|
||||
def my_method(self, foo):
|
||||
bar = foo
|
||||
@@ -0,0 +1,4 @@
|
||||
from SuperClass import Parent
|
||||
class Child(Parent):
|
||||
def my_method(self, foo):
|
||||
bar = foo
|
||||
@@ -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
|
||||
@@ -0,0 +1,9 @@
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
|
||||
class Parent(object):
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
@abstractmethod
|
||||
def my_method2(self):
|
||||
pass
|
||||
@@ -0,0 +1,8 @@
|
||||
# Stubs
|
||||
|
||||
class ABCMeta:
|
||||
pass
|
||||
|
||||
|
||||
def abstractmethod(foo):
|
||||
pass
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,2 @@
|
||||
class Parent(object):
|
||||
pass
|
||||
@@ -0,0 +1,8 @@
|
||||
# Stubs
|
||||
|
||||
class ABCMeta:
|
||||
pass
|
||||
|
||||
|
||||
def abstractmethod(foo):
|
||||
pass
|
||||
@@ -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")
|
||||
@@ -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")
|
||||
@@ -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
|
||||
@@ -0,0 +1,3 @@
|
||||
from abc import object
|
||||
class Parent(object):
|
||||
pass
|
||||
@@ -0,0 +1,11 @@
|
||||
# Stubs
|
||||
|
||||
class object:
|
||||
pass
|
||||
|
||||
class ABCMeta:
|
||||
pass
|
||||
|
||||
|
||||
def abstractmethod(foo):
|
||||
pass
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user