PY-10963 Pushing methods down should add needed imports (test only added)

This commit is contained in:
Ilya.Kazakevich
2014-02-11 16:16:13 +04:00
parent ecc247901b
commit fe6b753f5b
22 changed files with 223 additions and 78 deletions
@@ -39,6 +39,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -56,10 +57,13 @@ public final class PyExtractSuperclassHelper {
}
static void extractSuperclass(final PyClass clazz,
final Collection<PyMemberInfo> selectedMemberInfos,
@NotNull Collection<PyMemberInfo> selectedMemberInfos,
final String superBaseName,
final String targetFile) {
//We will need to change it probably while param may be read-only
//noinspection AssignmentToMethodParameter
selectedMemberInfos = new ArrayList<PyMemberInfo>(selectedMemberInfos);
// 'object' superclass is always pulled up, even if not selected explicitly
if (MembersManager.findMember(selectedMemberInfos, ALLOW_OBJECT) == null) {
final PyMemberInfo object = MembersManager.findMember(clazz, ALLOW_OBJECT);
@@ -1,5 +1,6 @@
package com.jetbrains.python.refactoring.classes.membersManager;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.refactoring.BaseRefactoringProcessor;
import com.intellij.usageView.UsageInfo;
@@ -32,10 +33,11 @@ public abstract class PyMembersRefactoringBaseProcessor extends BaseRefactoringP
* @param to where to move
*/
protected PyMembersRefactoringBaseProcessor(
@NotNull final Project project,
@NotNull final Collection<PyMemberInfo> membersToMove,
@NotNull final PyClass from,
@NotNull final PyClass... to) {
super(from.getProject());
super(project);
myFrom = from;
myMembersToMove = new ArrayList<PyMemberInfo>(membersToMove);
myTo = to.clone();
@@ -17,7 +17,7 @@ import java.util.Collection;
class PyPullUpProcessor extends PyMembersRefactoringBaseProcessor {
PyPullUpProcessor(@NotNull final PyClass from, @NotNull final PyClass to, @NotNull final Collection<PyMemberInfo> membersToMove) {
super(membersToMove, from, to);
super(from.getProject(), membersToMove, from, to);
}
@@ -20,9 +20,7 @@ import com.intellij.openapi.project.Project;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.intellij.util.Query;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyUtil;
import com.jetbrains.python.psi.search.PyClassInheritorsSearch;
import com.jetbrains.python.refactoring.classes.PyClassRefactoringHandler;
import com.jetbrains.python.refactoring.classes.PyMemberInfoStorage;
@@ -50,19 +48,20 @@ public class PyPushDownHandler extends PyClassRefactoringHandler {
return;
}
ViewPresenterUtils.linkViewWithPresenterAndLaunch(PyPushDownPresenter.class, PyPushDownView.class, new Creator<PyPushDownView, PyPushDownPresenter>() {
@NotNull
@Override
public PyPushDownPresenter createPresenter(@NotNull PyPushDownView view) {
return new PyPushDownPresenterImpl(view, classUnderRefactoring, infoStorage);
}
ViewPresenterUtils
.linkViewWithPresenterAndLaunch(PyPushDownPresenter.class, PyPushDownView.class, new Creator<PyPushDownView, PyPushDownPresenter>() {
@NotNull
@Override
public PyPushDownPresenter createPresenter(@NotNull PyPushDownView view) {
return new PyPushDownPresenterImpl(project, view, classUnderRefactoring, infoStorage);
}
@NotNull
@Override
public PyPushDownView createView(@NotNull PyPushDownPresenter presenter) {
return new PyPushDownViewSwingImpl(classUnderRefactoring, project, presenter);
}
});
@NotNull
@Override
public PyPushDownView createView(@NotNull PyPushDownPresenter presenter) {
return new PyPushDownViewSwingImpl(classUnderRefactoring, project, presenter);
}
});
}
@Override
@@ -74,5 +73,4 @@ public class PyPushDownHandler extends PyClassRefactoringHandler {
protected String getHelpId() {
return "members.push.down";
}
}
@@ -1,5 +1,6 @@
package com.jetbrains.python.refactoring.classes.pushDown;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.refactoring.BaseRefactoringProcessor;
import com.intellij.refactoring.classMembers.UsedByDependencyMemberInfoModel;
@@ -7,29 +8,31 @@ import com.intellij.util.containers.MultiMap;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.psi.PyUtil;
import com.jetbrains.python.psi.search.PyClassInheritorsSearch;
import com.jetbrains.python.refactoring.classes.PyMemberInfoStorage;
import com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo;
import com.jetbrains.python.refactoring.classes.membersManager.vp.MembersBasedPresenterWithPreviewImpl;
import com.jetbrains.python.refactoring.classes.membersManager.vp.MembersViewInitializationInfo;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
/**
* Created by Ilya.Kazakevich on 10.02.14.
* @author Ilya.Kazakevich
*/
public class PyPushDownPresenterImpl extends MembersBasedPresenterWithPreviewImpl<PyPushDownView> implements PyPushDownPresenter {
public PyPushDownPresenterImpl(@NotNull PyPushDownView view,
@NotNull PyClass classUnderRefactoring,
@NotNull PyMemberInfoStorage infoStorage) {
@NotNull
private final Project myProject;
public PyPushDownPresenterImpl(@NotNull final Project project,
@NotNull final PyPushDownView view,
@NotNull final PyClass classUnderRefactoring,
@NotNull final PyMemberInfoStorage infoStorage) {
super(view, classUnderRefactoring, infoStorage);
myProject = project;
}
@NotNull
@Override
public BaseRefactoringProcessor createProcessor() {
return new PyPushDownProcessor(myView.getSelectedMemberInfos(), myClassUnderRefactoring);
return new PyPushDownProcessor(myProject, myView.getSelectedMemberInfos(), myClassUnderRefactoring);
}
@NotNull
@@ -42,7 +45,8 @@ public class PyPushDownPresenterImpl extends MembersBasedPresenterWithPreviewImp
public void launch() {
UsedByDependencyMemberInfoModel<PyElement, PyClass, PyMemberInfo> model =
new UsedByDependencyMemberInfoModel<PyElement, PyClass, PyMemberInfo>(myClassUnderRefactoring);
myView.configure(new MembersViewInitializationInfo(model, PyUtil.filterOutObject(myStorage.getClassMemberInfos(myClassUnderRefactoring))));
myView
.configure(new MembersViewInitializationInfo(model, PyUtil.filterOutObject(myStorage.getClassMemberInfos(myClassUnderRefactoring))));
myView.initAndShow();
}
}
@@ -15,6 +15,7 @@
*/
package com.jetbrains.python.refactoring.classes.pushDown;
import com.intellij.openapi.project.Project;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.usageView.UsageViewBundle;
import com.jetbrains.python.psi.PyClass;
@@ -33,9 +34,11 @@ public class PyPushDownProcessor extends PyMembersRefactoringBaseProcessor {
private static final String HEADER = RefactoringBundle.message("push.down.members.elements.header");
public PyPushDownProcessor(@NotNull Collection<PyMemberInfo> membersToMove,
@NotNull PyClass from) {
super(membersToMove, from, getChildren(from));
public PyPushDownProcessor(
@NotNull final Project project,
@NotNull final Collection<PyMemberInfo> membersToMove,
@NotNull final PyClass from) {
super(project, membersToMove, from, getChildren(from));
}
@NotNull
@@ -0,0 +1,6 @@
import shutil
class NewParent(object):
def do_useful_stuff(self):
shutil.rmtree("/", ignore_errors=True)
@@ -0,0 +1,4 @@
import dest_module
class MyClass(dest_module.MyNewParent):
pass
@@ -0,0 +1,6 @@
import shutil
class MyClass(object):
def do_useful_stuff(self):
shutil.rmtree("/", ignore_errors=True)
@@ -0,0 +1,7 @@
from SuperClass import SuperClass
class AnyClass(SuperClass):
pass
@@ -1,4 +1,6 @@
from SuperClass import SuperClass
import os
from sys import argv
@@ -7,3 +9,4 @@ class AnyClass(SuperClass):
if not self.args:
self.args = argv
self.argument = some_argument
os.abort()
@@ -1,4 +1,6 @@
from sys import argv
import os
class SuperClass(object):
def __init__(self):
@@ -8,3 +10,4 @@ class SuperClass(object):
if not self.args:
self.args = argv
self.argument = some_argument
os.abort()
@@ -0,0 +1,4 @@
from SuperClass import SuperClass
class AnyClass(SuperClass):
pass
@@ -0,0 +1,6 @@
from os import abort
from parent_module import Parent
class Child(Parent):
def should_be_pushed(self):
abort()
@@ -0,0 +1,4 @@
from parent_module import Parent
class Child(Parent):
pass
@@ -0,0 +1,4 @@
class Parent(object):
pass
@@ -0,0 +1,6 @@
from os import abort
class Parent(object):
def should_be_pushed(self):
abort()
@@ -26,19 +26,31 @@ import com.jetbrains.python.psi.PyFunction;
import com.jetbrains.python.psi.PyTargetExpression;
import com.jetbrains.python.psi.stubs.PyClassNameIndex;
import com.jetbrains.python.refactoring.classes.membersManager.MembersManager;
import com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo;
import org.hamcrest.Matchers;
import org.jetbrains.annotations.NotNull;
import org.junit.Assert;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* @author Dennis.Ushakov
*/
public abstract class PyClassRefactoringTest extends PyTestCase {
@NotNull
private final String myRefactoringName;
protected PyClassRefactoringTest(@NotNull final String refactoringName) {
myRefactoringName = refactoringName;
}
//TODO: Doct
@NotNull
protected PyMemberInfo findMemberInfo(@NotNull final String clazzName, @NotNull final String memberName) {
final PyClass clazz = findClass(clazzName);
return MembersManager.findMember(clazz, findMember(clazzName, memberName));
}
/**
* @param className class where member should be found
* @param memberName member that starts with dot (<code>.</code>) is treated as method.
@@ -47,7 +59,7 @@ public abstract class PyClassRefactoringTest extends PyTestCase {
* @return member or null if not found
*/
@NotNull
protected PyElement findMember(@NotNull String className, @NotNull String memberName) {
protected PyElement findMember(@NotNull final String className, @NotNull String memberName) {
final PyElement result;
//TODO: Get rid of this chain of copy pastes
if (memberName.contains(".")) {
@@ -98,4 +110,29 @@ public abstract class PyClassRefactoringTest extends PyTestCase {
}
}, null, null);
}
//TODO: Doc
protected void configureMultiFile(@NotNull final String... fileNamesNoExtensions) {
final String baseName = getMultiFileBaseName() + "/";
for (final String fileNameNoExtension : fileNamesNoExtensions) {
final String fileNameBefore = String.format("%s.py", fileNameNoExtension);
myFixture.copyFileToProject(baseName + fileNameBefore, fileNameBefore);
}
}
//TODO: Doc
protected void checkMultiFile(@NotNull final String... fileNamesNoExtensions) {
for (final String fileNameNoExtension : fileNamesNoExtensions) {
final String fileNameAfter = String.format("%s.after.py", fileNameNoExtension);
final String fileNameBefore = String.format("%s.py", fileNameNoExtension);
myFixture.checkResultByFile(fileNameBefore, "/" + getMultiFileBaseName() + "/" + fileNameAfter, true);
}
}
//TODO: Doc
@NotNull
protected String getMultiFileBaseName() {
return "refactoring/" + myRefactoringName + "/" + getTestName(true);
}
}
@@ -15,8 +15,6 @@
*/
package com.jetbrains.python.refactoring.classes.extractSuperclass;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
@@ -26,7 +24,6 @@ import com.jetbrains.python.PyNames;
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.extractSuperclass.PyExtractSuperclassHelper;
import com.jetbrains.python.refactoring.classes.membersManager.MembersManager;
import com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo;
@@ -39,6 +36,28 @@ import java.util.List;
* @author Dennis.Ushakov
*/
public class PyExtractSuperclassTest extends PyClassRefactoringTest {
public PyExtractSuperclassTest() {
super("extractsuperclass");
}
//TODO: Doc
public void testImportMultiFile() throws Throwable {
final String[] modules = {"dest_module", "source_module"};
configureMultiFile(modules);
myFixture.configureByFile("source_module.py");
final String sourceClass = "MyClass";
final PyMemberInfo member = findMemberInfo(sourceClass, ".do_useful_stuff");
final String destUrl = myFixture.getFile().getVirtualFile().getParent().findChild("dest_module.py").getUrl();
new WriteCommandAction.Simple(myFixture.getProject()) {
@Override
protected void run() throws Throwable {
PyExtractSuperclassHelper.extractSuperclass(findClass(sourceClass), Collections.singleton(member), "NewParent", destUrl);
}
}.execute();
checkMultiFile(modules);
}
public void testSimple() throws Exception {
doSimpleTest("Foo", "Suppa", null, true, ".foo");
}
@@ -55,28 +74,33 @@ public class PyExtractSuperclassTest extends PyClassRefactoringTest {
doSimpleTest("FromClass", "ToClass", null, true, "#instance_field", "#CLASS_FIELD");
}
private void doSimpleTest(final String className, final String superclassName, final String expectedError, final boolean sameFile, final String... membersName) throws Exception {
private void doSimpleTest(final String className,
final String superclassName,
final String expectedError,
final boolean sameFile,
final String... membersName) throws Exception {
try {
String baseName = "/refactoring/extractsuperclass/" + getTestName(true);
myFixture.configureByFile(baseName + ".before.py");
final PyClass clazz = findClass(className);
final List<PyMemberInfo> members = new ArrayList<PyMemberInfo>();
for (String memberName : membersName) {
final PyElement member = findMember(className, memberName);
members.add(MembersManager.findMember(clazz, member));
}
new WriteCommandAction.Simple(myFixture.getProject()) {
@Override
protected void run() throws Throwable {
//noinspection ConstantConditions
final String url = sameFile ? myFixture.getFile().getVirtualFile().getUrl() :
myFixture.getFile().getVirtualFile().getParent().getUrl();
PyExtractSuperclassHelper.extractSuperclass(clazz, members, superclassName, url);
String baseName = "/refactoring/extractsuperclass/" + getTestName(true);
myFixture.configureByFile(baseName + ".before.py");
final PyClass clazz = findClass(className);
final List<PyMemberInfo> members = new ArrayList<PyMemberInfo>();
for (String memberName : membersName) {
final PyElement member = findMember(className, memberName);
members.add(MembersManager.findMember(clazz, member));
}
}.execute();
myFixture.checkResultByFile(baseName + ".after.py");
} catch (Exception e) {
new WriteCommandAction.Simple(myFixture.getProject()) {
@Override
protected void run() throws Throwable {
//noinspection ConstantConditions
final String url = sameFile ? myFixture.getFile().getVirtualFile().getUrl() :
myFixture.getFile().getVirtualFile().getParent().getUrl();
PyExtractSuperclassHelper.extractSuperclass(clazz, members, superclassName, url);
}
}.execute();
myFixture.checkResultByFile(baseName + ".after.py");
}
catch (Exception e) {
if (expectedError == null) throw e;
assertEquals(expectedError, e.getMessage());
}
@@ -164,5 +188,4 @@ public class PyExtractSuperclassTest extends PyClassRefactoringTest {
String expected = psi_mgr.findFile(LocalFileSystem.getInstance().findFileByIoFile(expected_file)).getText().trim();
assertEquals(expected, result);
}
}
@@ -19,6 +19,7 @@ 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 org.jetbrains.annotations.NotNull;
import java.util.Collections;
@@ -26,6 +27,11 @@ import java.util.Collections;
* @author Dennis.Ushakov
*/
public class PyPullUpTest extends PyClassRefactoringTest {
public PyPullUpTest() {
super("pullup");
}
public void testSimple() {
doHelperTest("Boo", ".boo", "Foo");
}
@@ -79,18 +85,16 @@ public class PyPullUpTest extends PyClassRefactoringTest {
}
private void doMultiFileTest() {
String baseName = "refactoring/pullup/" + getTestName(true) + "/";
myFixture.copyFileToProject(baseName + "Class.py", "Class.py");
myFixture.copyFileToProject(baseName + "SuperClass.py", "SuperClass.py");
final String[] modules = {"Class", "SuperClass"};
configureMultiFile(modules);
doPullUp("AnyClass", ".this_should_be_in_super", "SuperClass");
myFixture.checkResultByFile("SuperClass.py", "/" + baseName + "/SuperClass.after.py", true);
checkMultiFile(modules);
}
private void doHelperTest(final String className, final String memberName, final String superClassName) {
String baseName = "/refactoring/pullup/" + getTestName(true);
myFixture.configureByFile(baseName + ".py");
myFixture.configureByFile(getMultiFileBaseName() + ".py");
doPullUp(className, memberName, superClassName);
myFixture.checkResultByFile(baseName + ".after.py");
myFixture.checkResultByFile(getMultiFileBaseName() + ".after.py");
}
private void doPullUp(String className, String memberName, String superClassName) {
@@ -15,13 +15,11 @@
*/
package com.jetbrains.python.refactoring.classes.pushDown;
import com.intellij.openapi.command.WriteCommandAction;
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 com.jetbrains.python.refactoring.classes.pushDown.PyPushDownProcessor;
import java.util.ArrayList;
import java.util.Collections;
@@ -31,6 +29,22 @@ import java.util.List;
* @author Dennis.Ushakov
*/
public class PyPushDownTest extends PyClassRefactoringTest {
public PyPushDownTest() {
super("pushdown");
}
//TODO: Doc
public void testMultiFileImports() {
final String[] modules = {"child_module", "parent_module"};
configureMultiFile(modules);
final PyClass parentClass = findClass("Parent");
final PyMemberInfo methodToMove = MembersManager.findMember(parentClass, findMember("Parent", ".should_be_pushed"));
moveViaProcessor(myFixture.getProject(), new PyPushDownProcessor(myFixture.getProject(), Collections.singletonList(methodToMove), parentClass));
checkMultiFile(modules);
}
public void testSimple() throws Exception {
doProcessorTest("Foo", null, ".foo");
}
@@ -52,24 +66,27 @@ public class PyPushDownTest extends PyClassRefactoringTest {
}
public void testExistingmethod() throws Exception {
doProcessorTest("Foo", "method <b><code>foo</code></b> is already overridden in class <b><code>Boo</code></b>. Method will not be pushed down to that class.", ".foo");
doProcessorTest("Foo",
"method <b><code>foo</code></b> is already overridden in class <b><code>Boo</code></b>. Method will not be pushed down to that class.",
".foo");
}
private void doProcessorTest(final String className, final String expectedError, final String... memberNames) throws Exception {
try {
String baseName = "/refactoring/pushdown/" + getTestName(true);
myFixture.configureByFile(baseName + ".before.py");
final PyClass clazz = findClass(className);
final List<PyMemberInfo> members = new ArrayList<PyMemberInfo>();
for (String memberName : memberNames) {
final PyElement member = findMember(className, memberName);
members.add(MembersManager.findMember(clazz, member));
}
String baseName = "/refactoring/pushdown/" + getTestName(true);
myFixture.configureByFile(baseName + ".before.py");
final PyClass clazz = findClass(className);
final List<PyMemberInfo> members = new ArrayList<PyMemberInfo>();
for (String memberName : memberNames) {
final PyElement member = findMember(className, memberName);
members.add(MembersManager.findMember(clazz, member));
}
final PyPushDownProcessor processor = new PyPushDownProcessor(members, clazz);
final PyPushDownProcessor processor = new PyPushDownProcessor(myFixture.getProject(), members, clazz);
moveViaProcessor(myFixture.getProject(), processor);
myFixture.checkResultByFile(baseName + ".after.py");
} catch (Exception e) {
}
catch (Exception e) {
if (expectedError == null) throw e;
assertTrue(e.getMessage(), e.getMessage().contains(expectedError));
}