Update references when moving Python modules (PY-4379)

This commit is contained in:
Andrey Vlasovskikh
2011-11-28 12:21:40 +04:00
parent d7bc0eac8c
commit 3856b5fece
12 changed files with 209 additions and 21 deletions
@@ -334,6 +334,7 @@
<automaticRenamerFactory implementation="com.jetbrains.python.refactoring.rename.PyInheritorRenameFactory"/>
<automaticRenamerFactory implementation="com.jetbrains.python.refactoring.rename.PyParametersRenameFactory"/>
<moveFileHandler implementation="com.jetbrains.python.refactoring.move.PyMoveFileHandler"/>
<refactoring.moveHandler implementation="com.jetbrains.python.refactoring.move.PyMoveClassOrFunctionDelegate"/>
<lang.importOptimizer language="Python" implementationClass="com.jetbrains.python.codeInsight.imports.PyImportOptimizer"/>
@@ -194,7 +194,12 @@ public class PyClassRefactoringUtil {
if (target == null) return;
if (PyBuiltinCache.getInstance(target).hasInBuiltins(target)) return;
if (PsiTreeUtil.isAncestor(node.getContainingFile(), target, false)) return;
insertImport(node, target, asName);
if (target instanceof PyFile) {
insertImport(node, target, asName, false);
}
else {
insertImport(node, target, asName);
}
node.putCopyableUserData(ENCODED_IMPORT, null);
node.putCopyableUserData(ENCODED_IMPORT_AS, null);
}
@@ -232,23 +237,28 @@ public class PyClassRefactoringUtil {
public static void insertImport(PsiElement anchor, PsiNamedElement element, @Nullable String asName, boolean preferFromImport) {
if (PyBuiltinCache.getInstance(element).hasInBuiltins(element)) return;
final PsiFile newFile = element.getContainingFile();
final VirtualFile vFile = newFile.getVirtualFile();
assert vFile != null;
final PsiFile file = anchor.getContainingFile();
if (newFile == file) return;
final PyQualifiedName qName = ResolveImportUtil.findCanonicalImportPath(element, anchor);
assert isValidQualifiedName(qName);
final String importableName = (qName != null) ? qName.toString() : null;
final PyQualifiedName qname = ResolveImportUtil.findCanonicalImportPath(element, anchor);
if (qname == null || !isValidQualifiedName(qname)) {
return;
}
final PyQualifiedName containingQName;
final String importedName;
if (element instanceof PyFile) {
containingQName = qname.removeLastComponent();
importedName = qname.getLastComponent();
}
else {
containingQName = qname;
importedName = getOriginalName(element);
}
final AddImportHelper.ImportPriority priority = AddImportHelper.getImportPriority(anchor, newFile);
if (!preferFromImport || element instanceof PyFile) {
if (element instanceof PyFile) {
AddImportHelper.addImportStatement(file, importableName, asName, priority);
} else {
final String name = element.getName();
AddImportHelper.addImportStatement(file, importableName + "." + name, asName, priority);
}
} else {
AddImportHelper.addImportFrom(file, importableName, element.getName(), asName, priority);
if (preferFromImport && !containingQName.getComponents().isEmpty()) {
AddImportHelper.addImportFrom(file, containingQName.toString(), importedName, asName, priority);
}
else {
AddImportHelper.addImportStatement(file, containingQName.append(importedName).toString(), asName, priority);
}
}
@@ -306,7 +316,7 @@ public class PyClassRefactoringUtil {
}
public static void updateImportOfElement(PyImportStatementBase importStatement, PsiNamedElement element) {
final String name = element.getName();
final String name = getOriginalName(element);
if (name != null) {
PyImportElement importElement = null;
for (PyImportElement e: importStatement.getImportElements()) {
@@ -327,8 +337,23 @@ public class PyClassRefactoringUtil {
}
@Nullable
private static String getOriginalName(PyImportElement e) {
final PyQualifiedName qname = e.getImportedQName();
public static String getOriginalName(PsiNamedElement element) {
if (element instanceof PyFile) {
final PsiElement e = PyUtil.turnInitIntoDir(element);
if (e instanceof PsiFileSystemItem) {
final VirtualFile virtualFile = ((PsiFileSystemItem)e).getVirtualFile();
if (virtualFile != null) {
return virtualFile.getNameWithoutExtension();
}
}
return null;
}
return element.getName();
}
@Nullable
private static String getOriginalName(PyImportElement element) {
final PyQualifiedName qname = element.getImportedQName();
if (qname != null && qname.getComponentCount() > 0) {
return qname.getComponents().get(0);
}
@@ -0,0 +1,104 @@
package com.jetbrains.python.refactoring.move;
import com.intellij.openapi.util.Key;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.move.moveFilesOrDirectories.MoveFileHandler;
import com.intellij.usageView.UsageInfo;
import com.intellij.util.IncorrectOperationException;
import com.jetbrains.python.codeInsight.imports.PyImportOptimizer;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyQualifiedName;
import com.jetbrains.python.psi.resolve.ResolveImportUtil;
import com.jetbrains.python.refactoring.PyRefactoringUtil;
import com.jetbrains.python.refactoring.classes.PyClassRefactoringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author vlan
*/
public class PyMoveFileHandler extends MoveFileHandler {
private static final Key<PsiNamedElement> REFERENCED_ELEMENT = Key.create("PY_REFERENCED_ELEMENT");
@Override
public boolean canProcessElement(PsiFile element) {
return element instanceof PyFile;
}
@Override
public void prepareMovedFile(PsiFile file, PsiDirectory moveDestination, Map<PsiElement, PsiElement> oldToNewMap) {
// TODO: Update relative imports
}
@Override
public List<UsageInfo> findUsages(PsiFile file, PsiDirectory newParent, boolean searchInComments, boolean searchInNonJavaFiles) {
if (file != null) {
final List<UsageInfo> usages = PyRefactoringUtil.findUsages(file);
for (UsageInfo usage : usages) {
final PsiElement element = usage.getElement();
if (element != null) {
element.putCopyableUserData(REFERENCED_ELEMENT, file);
}
}
return usages;
}
return null;
}
@Override
public void retargetUsages(List<UsageInfo> usages, Map<PsiElement, PsiElement> oldToNewMap) {
final Set<PsiFile> updatedFiles = new HashSet<PsiFile>();
for (UsageInfo usage : usages) {
final PsiElement element = usage.getElement();
if (element != null) {
// TODO: Check if the element still exists/valid
final PsiNamedElement newElement = element.getCopyableUserData(REFERENCED_ELEMENT);
element.putCopyableUserData(REFERENCED_ELEMENT, null);
if (newElement != null) {
final PsiFile file = element.getContainingFile();
final PyImportStatementBase importStmt = PsiTreeUtil.getParentOfType(usage.getElement(), PyImportStatementBase.class);
// TODO: Retarget qualified expressions in docstrings
if (importStmt != null) {
PyClassRefactoringUtil.updateImportOfElement(importStmt, newElement);
final PyQualifiedName newElementName = ResolveImportUtil.findCanonicalImportPath(newElement, element);
replaceWithQualifiedExpression(element, newElementName);
updatedFiles.add(file);
}
else if (element instanceof PyReferenceExpression) {
final PyQualifiedName newElementName = PyQualifiedName.fromComponents(PyClassRefactoringUtil.getOriginalName(newElement));
replaceWithQualifiedExpression(element, newElementName);
PyClassRefactoringUtil.insertImport(element, newElement, null);
updatedFiles.add(file);
}
}
}
}
if (!updatedFiles.isEmpty()) {
final PyImportOptimizer optimizer = new PyImportOptimizer();
for (PsiFile file : updatedFiles) {
optimizer.processFile(file).run();
}
}
}
private static void replaceWithQualifiedExpression(@NotNull PsiElement oldElement,
@Nullable PyQualifiedName newElementName) {
if (newElementName != null && PyClassRefactoringUtil.isValidQualifiedName(newElementName)) {
final PyElementGenerator generator = PyElementGenerator.getInstance(oldElement.getProject());
final PsiElement newElement = generator.createExpressionFromText(LanguageLevel.forElement(oldElement), newElementName.toString());
if (newElement != null) {
oldElement.replace(newElement);
}
}
}
@Override
public void updateMovedFile(PsiFile file) throws IncorrectOperationException {
}
}
@@ -0,0 +1,7 @@
from p1 import m1
from p1.m1 import foo
m1.foo
m1.bar
m1
foo
@@ -0,0 +1,2 @@
foo = 42
bar = -1
@@ -0,0 +1,9 @@
from p1.p2 import m1
from p1.p2.m1 import foo
import p1.p2.m1
import p1
p1.p2.m1.foo
m1.bar
m1
foo
@@ -0,0 +1,2 @@
foo = 42
bar = -1
@@ -1,9 +1,10 @@
package com.jetbrains.python.refactoring;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiNamedElement;
import com.intellij.psi.*;
import com.intellij.refactoring.move.moveFilesOrDirectories.MoveFilesOrDirectoriesProcessor;
import com.intellij.testFramework.PlatformTestUtil;
import com.intellij.util.SystemProperties;
import com.jetbrains.python.PythonTestUtil;
@@ -69,7 +70,44 @@ public class PyMoveTest extends PyTestCase {
doMoveSymbolTest("B", "b.py");
}
private void doMoveSymbolTest(final String symbolName, final String toFileName) {
// PY-4379
public void testModule() {
doMoveFileTest("p1/p2/m1.py", "p1");
}
private void doMoveFileTest(String fileName, String toDirName) {
Project project = myFixture.getProject();
PsiManager manager = PsiManager.getInstance(project);
String root = "/refactoring/move/" + getTestName(true);
String rootBefore = root + "/before/src";
String rootAfter = root + "/after/src";
VirtualFile dir1 = myFixture.copyDirectoryToProject(rootBefore, "");
PsiDocumentManager.getInstance(project).commitAllDocuments();
VirtualFile virtualFile = dir1.findFileByRelativePath(fileName);
assertNotNull(virtualFile);
PsiElement file = manager.findFile(virtualFile);
if (file == null) {
file = manager.findDirectory(virtualFile);
}
assertNotNull(file);
VirtualFile toVirtualDir = dir1.findFileByRelativePath(toDirName);
assertNotNull(toVirtualDir);
PsiDirectory toDir = manager.findDirectory(toVirtualDir);
new MoveFilesOrDirectoriesProcessor(project, new PsiElement[] {file}, toDir, false, false, null, null).run();
VirtualFile dir2 = getVirtualFileByName(PythonTestUtil.getTestDataPath() + rootAfter);
try {
PlatformTestUtil.assertDirectoriesEqual(dir2, dir1, null);
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
private void doMoveSymbolTest(String symbolName, String toFileName) {
String root = "/refactoring/move/" + getTestName(true);
String rootBefore = root + "/before/src";
String rootAfter = root + "/after/src";