From 8166fab58b9bd741ef0f2724617a04176f36f913 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Tue, 16 Jun 2015 16:48:24 +0300 Subject: [PATCH] PY-14617 Ignore relative imports in modules moved together with files these imports refer to --- .../classes/PyClassRefactoringUtil.java | 14 ++-- .../refactoring/move/PyMoveFileHandler.java | 81 +++++++++++-------- .../move/PyMoveSymbolProcessor.java | 2 +- .../after/src/subpkg1/mod1.py | 2 +- .../after/src/pkg/__init__.py | 0 .../after/src/pkg/m5.py | 0 .../after/src/subpkg/__init__.py | 0 .../after/src/subpkg/m1.py | 8 ++ .../after/src/subpkg/m2.py | 0 .../after/src/subpkg/m3.py | 0 .../after/src/subpkg/subsubpkg/__init__.py | 0 .../after/src/subpkg/subsubpkg/m4.py | 0 .../before/src/pkg/__init__.py | 0 .../before/src/pkg/m5.py | 0 .../before/src/pkg/subpkg/__init__.py | 0 .../before/src/pkg/subpkg/m1.py | 8 ++ .../before/src/pkg/subpkg/m2.py | 0 .../before/src/pkg/subpkg/m3.py | 0 .../src/pkg/subpkg/subsubpkg/__init__.py | 0 .../before/src/pkg/subpkg/subsubpkg/m4.py | 0 .../python/refactoring/PyMoveTest.java | 5 ++ 21 files changed, 77 insertions(+), 43 deletions(-) create mode 100644 python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/after/src/pkg/__init__.py create mode 100644 python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/after/src/pkg/m5.py create mode 100644 python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/after/src/subpkg/__init__.py create mode 100644 python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/after/src/subpkg/m1.py create mode 100644 python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/after/src/subpkg/m2.py create mode 100644 python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/after/src/subpkg/m3.py create mode 100644 python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/after/src/subpkg/subsubpkg/__init__.py create mode 100644 python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/after/src/subpkg/subsubpkg/m4.py create mode 100644 python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/before/src/pkg/__init__.py create mode 100644 python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/before/src/pkg/m5.py create mode 100644 python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/before/src/pkg/subpkg/__init__.py create mode 100644 python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/before/src/pkg/subpkg/m1.py create mode 100644 python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/before/src/pkg/subpkg/m2.py create mode 100644 python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/before/src/pkg/subpkg/m3.py create mode 100644 python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/before/src/pkg/subpkg/subsubpkg/__init__.py create mode 100644 python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/before/src/pkg/subpkg/subsubpkg/m4.py diff --git a/python/src/com/jetbrains/python/refactoring/classes/PyClassRefactoringUtil.java b/python/src/com/jetbrains/python/refactoring/classes/PyClassRefactoringUtil.java index 29d8b1e10461..946df592baaa 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/PyClassRefactoringUtil.java +++ b/python/src/com/jetbrains/python/refactoring/classes/PyClassRefactoringUtil.java @@ -435,8 +435,9 @@ public final class PyClassRefactoringUtil { * * @param importStatement parent import statement that contains reference to given element * @param element PSI element reference to which should be updated + * @return whether import statement was actually updated */ - public static void updateImportOfElement(@NotNull PyImportStatementBase importStatement, @NotNull PsiNamedElement element) { + public static boolean updateUnqualifiedImportOfElement(@NotNull PyImportStatementBase importStatement, @NotNull PsiNamedElement element) { final String name = getOriginalName(element); if (name != null) { PyImportElement importElement = null; @@ -448,14 +449,7 @@ public final class PyClassRefactoringUtil { if (importElement != null) { final PsiFile file = importStatement.getContainingFile(); final PsiFile newFile = element.getContainingFile(); - boolean deleteImportElement = false; - if (newFile == file) { - deleteImportElement = true; - } - else if (insertImport(importStatement, element, importElement.getAsName(), true)) { - deleteImportElement = true; - } - if (deleteImportElement) { + if (newFile == file || insertImport(importStatement, element, importElement.getAsName(), true)) { if (importStatement.getImportElements().length == 1) { final boolean isInjected = InjectedLanguageManager.getInstance(importElement.getProject()).isInjectedFragment(importElement.getContainingFile()); @@ -469,9 +463,11 @@ public final class PyClassRefactoringUtil { else { importElement.delete(); } + return true; } } } + return false; } private static void deleteImportStatementFromInjected(@NotNull final PyImportStatementBase importStatement) { diff --git a/python/src/com/jetbrains/python/refactoring/move/PyMoveFileHandler.java b/python/src/com/jetbrains/python/refactoring/move/PyMoveFileHandler.java index e7afb5b8dc09..bf6c8b5b78e9 100644 --- a/python/src/com/jetbrains/python/refactoring/move/PyMoveFileHandler.java +++ b/python/src/com/jetbrains/python/refactoring/move/PyMoveFileHandler.java @@ -28,13 +28,16 @@ import com.intellij.refactoring.move.moveFilesOrDirectories.MoveFileHandler; import com.intellij.usageView.UsageInfo; import com.intellij.util.IncorrectOperationException; import com.intellij.util.PathUtil; +import com.intellij.util.containers.ContainerUtil; import com.jetbrains.python.PyNames; import com.jetbrains.python.PyTokenTypes; import com.jetbrains.python.PythonFileType; import com.jetbrains.python.actions.CreatePackageAction; import com.jetbrains.python.codeInsight.imports.PyImportOptimizer; import com.jetbrains.python.psi.*; +import com.jetbrains.python.psi.resolve.PyResolveContext; import com.jetbrains.python.psi.resolve.QualifiedNameFinder; +import com.jetbrains.python.psi.types.TypeEvalContext; import com.jetbrains.python.refactoring.PyRefactoringUtil; import com.jetbrains.python.refactoring.classes.PyClassRefactoringUtil; import org.jetbrains.annotations.NotNull; @@ -70,9 +73,13 @@ public class PyMoveFileHandler extends MoveFileHandler { if (moveDestination != root && root != null && searchForReferences && !probablyNamespacePackage(file, moveDestination, root)) { CreatePackageAction.createInitPyInHierarchy(moveDestination, root); } - if (file instanceof PyFile) { - updateRelativeImportsInModule((PyFile)file); - } + } + } + + @Override + public void updateMovedFile(PsiFile file) throws IncorrectOperationException { + if (file instanceof PyFile) { + updateRelativeImportsInModule((PyFile)file); } } @@ -81,7 +88,7 @@ public class PyMoveFileHandler extends MoveFileHandler { if (originalLocation == null) { return; } - module.putUserData(ORIGINAL_FILE_LOCATION, null); + //module.putUserData(ORIGINAL_FILE_LOCATION, null); for (PyFromImportStatement statement : module.getFromImports()) { if (!canBeRelative(statement)) { continue; @@ -117,11 +124,11 @@ public class PyMoveFileHandler extends MoveFileHandler { return !LanguageLevel.forElement(statement).isPy3K() || statement.getRelativeLevel() > 0; } + private static boolean canBeRelative(@NotNull PyImportElement statement) { return !LanguageLevel.forElement(statement).isPy3K(); } - /** * @param referenceExpr is null if we resolve import of type "from .. import bar", and "foo" for import of type "from foo import bar" */ @@ -199,40 +206,54 @@ public class PyMoveFileHandler extends MoveFileHandler { public void retargetUsages(List usages, Map oldToNewMap) { final Set updatedFiles = new HashSet(); for (UsageInfo usage : usages) { - final PsiElement element = usage.getElement(); - if (element != null) { - 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(element, PyImportStatementBase.class); + final PsiElement usageElement = usage.getElement(); + if (usageElement != null) { + final PsiNamedElement movedElement = usageElement.getCopyableUserData(REFERENCED_ELEMENT); + usageElement.putCopyableUserData(REFERENCED_ELEMENT, null); + if (movedElement != null) { + final PsiFile usageFile = usageElement.getContainingFile(); + + final PyImportStatementBase importStmt = PsiTreeUtil.getParentOfType(usageElement, PyImportStatementBase.class); // TODO: Retarget qualified expressions in docstrings if (importStmt != null) { - updatedFiles.add(file); - if (PsiTreeUtil.getParentOfType(element, PyImportElement.class) != null) { - PyClassRefactoringUtil.updateImportOfElement(importStmt, newElement); + + if (usageFile.getUserData(ORIGINAL_FILE_LOCATION) != null) { + // Leave relative imports as they are after #updateRelativeImportsInModule + final TypeEvalContext typeEvalContext = TypeEvalContext.userInitiated(usageFile.getProject(), usageFile); + final PyResolveContext resolveContext = PyResolveContext.defaultContext().withTypeEvalContext(typeEvalContext); + if (ContainerUtil.getFirstItem(PyUtil.multiResolveTopPriority(usageElement, resolveContext)) == movedElement) { + continue; + } } - if (importStmt instanceof PyFromImportStatement && PsiTreeUtil.getParentOfType(element, PyImportElement.class) != null) { - continue; + + updatedFiles.add(usageFile); + final boolean usageInsideImportElement = PsiTreeUtil.getParentOfType(usageElement, PyImportElement.class) != null; + if (usageInsideImportElement) { + // Handles imported element in "from import" statement (from some.package import module) + // or simple unqualified import of the module (import module). + if (PyClassRefactoringUtil.updateUnqualifiedImportOfElement(importStmt, movedElement)) { + continue; + } } - final QualifiedName newElementName = QualifiedNameFinder.findCanonicalImportPath(newElement, element); + final QualifiedName newElementName = QualifiedNameFinder.findCanonicalImportPath(movedElement, usageElement); if (importStmt instanceof PyFromImportStatement) { - replaceRelativeImportSourceWithQualifiedExpression((PyFromImportStatement)importStmt, newElementName); + if (!usageInsideImportElement) { + replaceRelativeImportSourceWithQualifiedExpression((PyFromImportStatement)importStmt, newElementName); + } } else { - replaceWithQualifiedExpression(element, newElementName); + replaceWithQualifiedExpression(usageElement, newElementName); } } - else if (element instanceof PyReferenceExpression) { - updatedFiles.add(file); - if (((PyReferenceExpression)element).isQualified()) { - final QualifiedName newQualifiedName = QualifiedNameFinder.findCanonicalImportPath(newElement, element); - replaceWithQualifiedExpression(element, newQualifiedName); + else if (usageElement instanceof PyReferenceExpression) { + updatedFiles.add(usageFile); + if (((PyReferenceExpression)usageElement).isQualified()) { + final QualifiedName newQualifiedName = QualifiedNameFinder.findCanonicalImportPath(movedElement, usageElement); + replaceWithQualifiedExpression(usageElement, newQualifiedName); } else { - final QualifiedName newName = QualifiedName.fromComponents(PyClassRefactoringUtil.getOriginalName(newElement)); - final PsiElement replaced = replaceWithQualifiedExpression(element, newName); - PyClassRefactoringUtil.insertImport(replaced, newElement, null); + final QualifiedName newName = QualifiedName.fromComponents(PyClassRefactoringUtil.getOriginalName(movedElement)); + replaceWithQualifiedExpression(usageElement, newName); } } } @@ -314,8 +335,4 @@ public class PyMoveFileHandler extends MoveFileHandler { } return null; } - - @Override - public void updateMovedFile(PsiFile file) throws IncorrectOperationException { - } } diff --git a/python/src/com/jetbrains/python/refactoring/move/PyMoveSymbolProcessor.java b/python/src/com/jetbrains/python/refactoring/move/PyMoveSymbolProcessor.java index 27c44f40ad9a..8c24ebcac5dc 100644 --- a/python/src/com/jetbrains/python/refactoring/move/PyMoveSymbolProcessor.java +++ b/python/src/com/jetbrains/python/refactoring/move/PyMoveSymbolProcessor.java @@ -160,7 +160,7 @@ public class PyMoveSymbolProcessor { else { final PyImportStatementBase importStmt = getImportStatementByElement(usage); if (importStmt != null) { - PyClassRefactoringUtil.updateImportOfElement(importStmt, newElement); + PyClassRefactoringUtil.updateUnqualifiedImportOfElement(importStmt, newElement); } } if (resolvesToLocalStarImport(usage)) { diff --git a/python/testData/refactoring/move/relativeImportsInsideMovedModule/after/src/subpkg1/mod1.py b/python/testData/refactoring/move/relativeImportsInsideMovedModule/after/src/subpkg1/mod1.py index f3926e749629..e9cea1c3d39b 100644 --- a/python/testData/refactoring/move/relativeImportsInsideMovedModule/after/src/subpkg1/mod1.py +++ b/python/testData/refactoring/move/relativeImportsInsideMovedModule/after/src/subpkg1/mod1.py @@ -1,7 +1,7 @@ from pkg1 import subpkg2 from pkg1.subpkg2 import mod2 from pkg1.subpkg2.mod2 import VAR -from subpkg1 import mod3 +from . import mod3 # malformed imports from diff --git a/python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/after/src/pkg/__init__.py b/python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/after/src/pkg/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/after/src/pkg/m5.py b/python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/after/src/pkg/m5.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/after/src/subpkg/__init__.py b/python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/after/src/subpkg/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/after/src/subpkg/m1.py b/python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/after/src/subpkg/m1.py new file mode 100644 index 000000000000..4a40476cffa0 --- /dev/null +++ b/python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/after/src/subpkg/m1.py @@ -0,0 +1,8 @@ +from . import m2 +import m3 +import subsubpkg as foo +from . import subsubpkg as bar +from .subsubpkg import m4 +from pkg import m5 + +print(m2, m3, m4, m5, foo, bar) diff --git a/python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/after/src/subpkg/m2.py b/python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/after/src/subpkg/m2.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/after/src/subpkg/m3.py b/python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/after/src/subpkg/m3.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/after/src/subpkg/subsubpkg/__init__.py b/python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/after/src/subpkg/subsubpkg/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/after/src/subpkg/subsubpkg/m4.py b/python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/after/src/subpkg/subsubpkg/m4.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/before/src/pkg/__init__.py b/python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/before/src/pkg/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/before/src/pkg/m5.py b/python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/before/src/pkg/m5.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/before/src/pkg/subpkg/__init__.py b/python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/before/src/pkg/subpkg/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/before/src/pkg/subpkg/m1.py b/python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/before/src/pkg/subpkg/m1.py new file mode 100644 index 000000000000..a674518912c6 --- /dev/null +++ b/python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/before/src/pkg/subpkg/m1.py @@ -0,0 +1,8 @@ +from . import m2 +import m3 +import subsubpkg as foo +from . import subsubpkg as bar +from .subsubpkg import m4 +from .. import m5 + +print(m2, m3, m4, m5, foo, bar) diff --git a/python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/before/src/pkg/subpkg/m2.py b/python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/before/src/pkg/subpkg/m2.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/before/src/pkg/subpkg/m3.py b/python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/before/src/pkg/subpkg/m3.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/before/src/pkg/subpkg/subsubpkg/__init__.py b/python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/before/src/pkg/subpkg/subsubpkg/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/before/src/pkg/subpkg/subsubpkg/m4.py b/python/testData/refactoring/move/relativeImportsToModulesInSameMovedPackageNotUpdated/before/src/pkg/subpkg/subsubpkg/m4.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testSrc/com/jetbrains/python/refactoring/PyMoveTest.java b/python/testSrc/com/jetbrains/python/refactoring/PyMoveTest.java index 3e106d537581..86ac0e56da3a 100644 --- a/python/testSrc/com/jetbrains/python/refactoring/PyMoveTest.java +++ b/python/testSrc/com/jetbrains/python/refactoring/PyMoveTest.java @@ -324,6 +324,11 @@ public class PyMoveTest extends PyTestCase { doMoveFileTest("pkg/a.py", ""); } + // PY-14617 + public void testRelativeImportsToModulesInSameMovedPackageNotUpdated() { + doMoveFileTest("pkg/subpkg", ""); + } + // PY-15324 public void testInterdependentSymbols() { doMoveSymbolsTest("b.py", "f", "A");