mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Update references when moving Python modules (PY-4379)
This commit is contained in:
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user