diff --git a/python/src/com/jetbrains/python/codeInsight/imports/AddImportHelper.java b/python/src/com/jetbrains/python/codeInsight/imports/AddImportHelper.java index 7b4cd06c6d35..db8cd2dba966 100644 --- a/python/src/com/jetbrains/python/codeInsight/imports/AddImportHelper.java +++ b/python/src/com/jetbrains/python/codeInsight/imports/AddImportHelper.java @@ -12,6 +12,7 @@ import com.jetbrains.python.PythonDocStringFinder; import com.jetbrains.python.codeInsight.PyCodeInsightSettings; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyQualifiedName; +import com.jetbrains.python.psi.resolve.QualifiedNameFinder; import com.jetbrains.python.psi.resolve.ResolveImportUtil; import com.jetbrains.python.sdk.PythonSdkType; import org.jetbrains.annotations.NotNull; @@ -214,7 +215,7 @@ public class AddImportHelper { final boolean useQualified = !PyCodeInsightSettings.getInstance().PREFER_FROM_IMPORT; final PsiFileSystemItem toImport = target instanceof PsiFileSystemItem ? ((PsiFileSystemItem)target).getParent() : target.getContainingFile(); final ImportPriority priority = getImportPriority(file, toImport); - final PyQualifiedName qName = ResolveImportUtil.findCanonicalImportPath(target, element); + final PyQualifiedName qName = QualifiedNameFinder.findCanonicalImportPath(target, element); if (qName == null) return; String path = qName.toString(); if (target instanceof PsiFileSystemItem) { diff --git a/python/src/com/jetbrains/python/codeInsight/imports/PythonReferenceImporter.java b/python/src/com/jetbrains/python/codeInsight/imports/PythonReferenceImporter.java index 35cfc4c657fb..ecc8102627fd 100644 --- a/python/src/com/jetbrains/python/codeInsight/imports/PythonReferenceImporter.java +++ b/python/src/com/jetbrains/python/codeInsight/imports/PythonReferenceImporter.java @@ -29,7 +29,7 @@ import com.jetbrains.python.psi.impl.PyFileImpl; import com.jetbrains.python.psi.impl.PyQualifiedName; import com.jetbrains.python.psi.resolve.CollectProcessor; import com.jetbrains.python.psi.resolve.PyResolveUtil; -import com.jetbrains.python.psi.resolve.ResolveImportUtil; +import com.jetbrains.python.psi.resolve.QualifiedNameFinder; import com.jetbrains.python.psi.search.PyProjectScopeBuilder; import com.jetbrains.python.psi.stubs.PyClassNameIndex; import com.jetbrains.python.psi.stubs.PyFunctionNameIndex; @@ -158,7 +158,7 @@ public class PythonReferenceImporter implements ReferenceImporter { if (srcfile != null && srcfile != existing_import_file && srcfile != node.getContainingFile() && (ImportFromExistingAction.isRoot(srcfile) || PyNames.isIdentifier(FileUtil.getNameWithoutExtension(srcfile.getName()))) && !isShadowedModule(srcfile)) { - PyQualifiedName import_path = ResolveImportUtil.findCanonicalImportPath(srcfile, node); + PyQualifiedName import_path = QualifiedNameFinder.findCanonicalImportPath(srcfile, node); if (import_path != null && !seen_file_names.contains(import_path.toString())) { // a new, valid hit fix.addImport(symbol, srcfile, import_path, proposeAsName(node.getContainingFile(), refText, import_path)); diff --git a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibDocumentationLinkProvider.java b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibDocumentationLinkProvider.java index 05f413d67a56..690e2ba971ed 100644 --- a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibDocumentationLinkProvider.java +++ b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibDocumentationLinkProvider.java @@ -15,7 +15,7 @@ import com.jetbrains.python.psi.PyFile; import com.jetbrains.python.psi.PyFunction; import com.jetbrains.python.psi.impl.PyBuiltinCache; import com.jetbrains.python.psi.impl.PyQualifiedName; -import com.jetbrains.python.psi.resolve.ResolveImportUtil; +import com.jetbrains.python.psi.resolve.QualifiedNameFinder; import com.jetbrains.python.sdk.PythonSdkType; import java.util.List; @@ -537,7 +537,7 @@ public class PyStdlibDocumentationLinkProvider implements PythonDocumentationLin Sdk sdk = PyBuiltinCache.findSdkForFile(file); VirtualFile vFile = file.getVirtualFile(); if (vFile != null && sdk != null && PythonSdkType.isStdLib(vFile, sdk)) { - PyQualifiedName qName = ResolveImportUtil.findCanonicalImportPath(element, originalElement); + PyQualifiedName qName = QualifiedNameFinder.findCanonicalImportPath(element, originalElement); return getStdlibUrlFor(element, qName, sdk); } return null; diff --git a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java index 6ff38392c1b5..60659cd5f306 100644 --- a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java +++ b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibTypeProvider.java @@ -10,7 +10,7 @@ import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyBuiltinCache; import com.jetbrains.python.psi.impl.PyQualifiedName; import com.jetbrains.python.psi.impl.PyTypeProvider; -import com.jetbrains.python.psi.resolve.ResolveImportUtil; +import com.jetbrains.python.psi.resolve.QualifiedNameFinder; import com.jetbrains.python.psi.types.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -216,7 +216,7 @@ public class PyStdlibTypeProvider extends PyTypeProviderBase { final PyClass c = f.getContainingClass(); final VirtualFile vfile = f.getContainingFile().getVirtualFile(); if (vfile != null) { - String module = ResolveImportUtil.findShortestImportableName(callSite != null ? callSite : f, vfile); + String module = QualifiedNameFinder.findShortestImportableName(callSite != null ? callSite : f, vfile); if ("builtins".equals(module)) { module = "__builtin__"; } diff --git a/python/src/com/jetbrains/python/documentation/DocStringTypeReference.java b/python/src/com/jetbrains/python/documentation/DocStringTypeReference.java index 00446ffd04c4..3d52cc675c10 100644 --- a/python/src/com/jetbrains/python/documentation/DocStringTypeReference.java +++ b/python/src/com/jetbrains/python/documentation/DocStringTypeReference.java @@ -8,7 +8,7 @@ import com.jetbrains.python.PyNames; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyImportedModule; import com.jetbrains.python.psi.impl.PyQualifiedName; -import com.jetbrains.python.psi.resolve.ResolveImportUtil; +import com.jetbrains.python.psi.resolve.QualifiedNameFinder; import com.jetbrains.python.psi.types.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -37,7 +37,7 @@ public class DocStringTypeReference extends PsiReferenceBase { if (myElement instanceof PyStringLiteralExpression && element instanceof PyClass) { final PyStringLiteralExpression e = (PyStringLiteralExpression)myElement; final PyClass cls = (PyClass)element; - PyQualifiedName qname = ResolveImportUtil.findCanonicalImportPath(cls, element); + PyQualifiedName qname = QualifiedNameFinder.findCanonicalImportPath(cls, element); if (qname != null) { qname = qname.append(cls.getName()); ElementManipulator manipulator = ElementManipulators.getManipulator(e); diff --git a/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java b/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java index 10be9c53babd..d36d71d6c5e4 100644 --- a/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java +++ b/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java @@ -25,7 +25,7 @@ import com.jetbrains.python.console.PydevDocumentationProvider; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyBuiltinCache; import com.jetbrains.python.psi.impl.PyQualifiedName; -import com.jetbrains.python.psi.resolve.ResolveImportUtil; +import com.jetbrains.python.psi.resolve.QualifiedNameFinder; import com.jetbrains.python.psi.types.*; import com.jetbrains.python.toolbox.ChainIterable; import com.jetbrains.python.toolbox.FP; @@ -348,7 +348,7 @@ public class PythonDocumentationProvider extends AbstractDocumentationProvider i if (sdk == null) { return null; } - PyQualifiedName qName = ResolveImportUtil.findCanonicalImportPath(element, originalElement); + PyQualifiedName qName = QualifiedNameFinder.findCanonicalImportPath(element, originalElement); if (qName == null) { return null; } @@ -432,7 +432,7 @@ public class PythonDocumentationProvider extends AbstractDocumentationProvider i final Project project = element.getProject(); final VirtualFile vFile = containingFile.getVirtualFile(); if (vFile != null && ProjectRootManager.getInstance(project).getFileIndex().isInLibraryClasses(vFile)) { - final PyQualifiedName qName = ResolveImportUtil.findCanonicalImportPath(element, element); + final PyQualifiedName qName = QualifiedNameFinder.findCanonicalImportPath(element, element); if (qName != null && qName.getComponentCount() > 0) { return true; } @@ -444,7 +444,7 @@ public class PythonDocumentationProvider extends AbstractDocumentationProvider i @Override public void promptToConfigureDocumentation(PsiElement element) { final Project project = element.getProject(); - final PyQualifiedName qName = ResolveImportUtil.findCanonicalImportPath(element, element); + final PyQualifiedName qName = QualifiedNameFinder.findCanonicalImportPath(element, element); if (qName != null && qName.getComponentCount() > 0) { ApplicationManager.getApplication().invokeLater(new Runnable() { @Override diff --git a/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java b/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java index 1f0cb4b33a3f..7c1f4df79c2a 100644 --- a/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java @@ -49,10 +49,7 @@ import com.jetbrains.python.psi.impl.PyImportedModule; import com.jetbrains.python.psi.impl.PyQualifiedName; import com.jetbrains.python.psi.impl.references.PyImportReference; import com.jetbrains.python.psi.impl.references.PyOperatorReference; -import com.jetbrains.python.psi.resolve.ImportedResolveResult; -import com.jetbrains.python.psi.resolve.PyResolveContext; -import com.jetbrains.python.psi.resolve.RatedResolveResult; -import com.jetbrains.python.psi.resolve.ResolveImportUtil; +import com.jetbrains.python.psi.resolve.*; import com.jetbrains.python.psi.types.*; import com.jetbrains.python.sdk.PythonSdkType; import org.jetbrains.annotations.Nls; @@ -765,7 +762,7 @@ public class PyUnresolvedReferencesInspection extends PyInspection { dunderAll = ((PyFile)file).getDunderAll(); } if (file != null && PyUtil.isPackage(file)) { - packageQName = ResolveImportUtil.findShortestImportableQName(file); + packageQName = QualifiedNameFinder.findShortestImportableQName(file); } } PyImportStatementBase importStatement = PsiTreeUtil.getParentOfType(unusedImport, PyImportStatementBase.class); @@ -809,7 +806,7 @@ public class PyUnresolvedReferencesInspection extends PyInspection { } } if (packageQName != null && importedElement instanceof PsiFileSystemItem) { - final PyQualifiedName importedQName = ResolveImportUtil.findShortestImportableQName(importedElement); + final PyQualifiedName importedQName = QualifiedNameFinder.findShortestImportableQName(importedElement); if (importedQName != null && importedQName.matchesPrefix(packageQName)) { continue; } diff --git a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java index d00a2160daab..04beccdff341 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java @@ -20,6 +20,7 @@ import com.jetbrains.python.codeInsight.controlflow.ScopeOwner; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.resolve.PyResolveContext; import com.jetbrains.python.psi.resolve.PyResolveUtil; +import com.jetbrains.python.psi.resolve.QualifiedNameFinder; import com.jetbrains.python.psi.resolve.ResolveImportUtil; import com.jetbrains.python.psi.stubs.PropertyStubStorage; import com.jetbrains.python.psi.stubs.PyClassStub; @@ -213,7 +214,7 @@ public class PyClassImpl extends PyPresentableElementImpl implement if (!psiFile.equals(builtins)) { VirtualFile vFile = psiFile.getVirtualFile(); if (vFile != null) { - final String packageName = ResolveImportUtil.findShortestImportableName(this, vFile); + final String packageName = QualifiedNameFinder.findShortestImportableName(this, vFile); return packageName + "." + name; } } diff --git a/python/src/com/jetbrains/python/psi/impl/PyPresentableElementImpl.java b/python/src/com/jetbrains/python/psi/impl/PyPresentableElementImpl.java index 897b1b7c00f4..387b76ac6f92 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyPresentableElementImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyPresentableElementImpl.java @@ -7,7 +7,7 @@ import com.intellij.psi.PsiFile; import com.intellij.psi.PsiNamedElement; import com.intellij.psi.stubs.IStubElementType; import com.intellij.psi.stubs.StubElement; -import com.jetbrains.python.psi.resolve.ResolveImportUtil; +import com.jetbrains.python.psi.resolve.QualifiedNameFinder; import javax.swing.*; @@ -48,7 +48,7 @@ public abstract class PyPresentableElementImpl extends Py final VirtualFile vFile = containingFile.getVirtualFile(); if (vFile != null) { - final String importableName = ResolveImportUtil.findShortestImportableName(containingFile, vFile); + final String importableName = QualifiedNameFinder.findShortestImportableName(containingFile, vFile); if (importableName != null) { return importableName; } diff --git a/python/src/com/jetbrains/python/psi/impl/PyPsiFacadeImpl.java b/python/src/com/jetbrains/python/psi/impl/PyPsiFacadeImpl.java index ca3c74af862d..9623e1578255 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyPsiFacadeImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyPsiFacadeImpl.java @@ -5,9 +5,9 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.jetbrains.python.psi.PyClass; import com.jetbrains.python.psi.PyPsiFacade; +import com.jetbrains.python.psi.resolve.QualifiedNameFinder; import com.jetbrains.python.psi.resolve.QualifiedNameResolver; import com.jetbrains.python.psi.resolve.QualifiedNameResolverImpl; -import com.jetbrains.python.psi.resolve.ResolveImportUtil; import com.jetbrains.python.psi.stubs.PyClassNameIndex; import com.jetbrains.python.psi.types.PyClassType; import com.jetbrains.python.psi.types.PyClassTypeImpl; @@ -47,6 +47,6 @@ public class PyPsiFacadeImpl extends PyPsiFacade { @Nullable @Override public String findShortestImportableName(PsiElement importer, VirtualFile targetFile) { - return ResolveImportUtil.findShortestImportableName(importer, targetFile); + return QualifiedNameFinder.findShortestImportableName(importer, targetFile); } } diff --git a/python/src/com/jetbrains/python/psi/impl/references/PyImportReference.java b/python/src/com/jetbrains/python/psi/impl/references/PyImportReference.java index 68546dad57e9..84526758d6e4 100644 --- a/python/src/com/jetbrains/python/psi/impl/references/PyImportReference.java +++ b/python/src/com/jetbrains/python/psi/impl/references/PyImportReference.java @@ -227,7 +227,7 @@ public class PyImportReference extends PyReferenceImpl { if (myCurrentFile != null && (relativeLevel >= 0 || !ResolveImportUtil.isAbsoluteImportEnabledFor(myCurrentFile))) { final PsiDirectory containingDirectory = myCurrentFile.getContainingDirectory(); if (containingDirectory != null) { - PyQualifiedName thisQName = ResolveImportUtil.findShortestImportableQName(containingDirectory); + PyQualifiedName thisQName = QualifiedNameFinder.findShortestImportableQName(containingDirectory); if (thisQName == null || thisQName.getComponentCount() == relativeLevel) { fillFromDir(ResolveImportUtil.stepBackFrom(myCurrentFile, relativeLevel), insertHandler); } diff --git a/python/src/com/jetbrains/python/psi/impl/references/PyQualifiedReference.java b/python/src/com/jetbrains/python/psi/impl/references/PyQualifiedReference.java index 01a367883bf1..5665ad66ac21 100644 --- a/python/src/com/jetbrains/python/psi/impl/references/PyQualifiedReference.java +++ b/python/src/com/jetbrains/python/psi/impl/references/PyQualifiedReference.java @@ -174,7 +174,7 @@ public class PyQualifiedReference extends PyReferenceImpl { if (ProjectScope.getProjectScope(myElement.getProject()).contains(vFile)) { rate += 80; } - final PyQualifiedName qName = ResolveImportUtil.findShortestImportableQName(myElement, vFile); + final PyQualifiedName qName = QualifiedNameFinder.findShortestImportableQName(myElement, vFile); if (qName != null && imports.contains(qName)) { rate += 70; } diff --git a/python/src/com/jetbrains/python/psi/resolve/CompletionVariantsProcessor.java b/python/src/com/jetbrains/python/psi/resolve/CompletionVariantsProcessor.java index d8303826b60e..3e86952c9989 100644 --- a/python/src/com/jetbrains/python/psi/resolve/CompletionVariantsProcessor.java +++ b/python/src/com/jetbrains/python/psi/resolve/CompletionVariantsProcessor.java @@ -87,7 +87,7 @@ public class CompletionVariantsProcessor extends VariantsProcessor { source = cls.getName(); } else if (myContext == null || !PyUtil.inSameFile(myContext, element)) { - PyQualifiedName path = ResolveImportUtil.findCanonicalImportPath(element, null); + PyQualifiedName path = QualifiedNameFinder.findCanonicalImportPath(element, null); if (path != null) { if (element instanceof PyFile) { path = path.removeLastComponent(); diff --git a/python/src/com/jetbrains/python/psi/resolve/QualifiedNameFinder.java b/python/src/com/jetbrains/python/psi/resolve/QualifiedNameFinder.java new file mode 100644 index 000000000000..2f51bbea3018 --- /dev/null +++ b/python/src/com/jetbrains/python/psi/resolve/QualifiedNameFinder.java @@ -0,0 +1,167 @@ +package com.jetbrains.python.psi.resolve; + +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VfsUtilCore; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.*; +import com.jetbrains.python.PyNames; +import com.jetbrains.python.psi.PyClass; +import com.jetbrains.python.psi.PyFile; +import com.jetbrains.python.psi.PyFunction; +import com.jetbrains.python.psi.impl.PyQualifiedName; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +/** + * @author yole + */ +public class QualifiedNameFinder { + /** + * Looks for a way to import given file. + * + * @param foothold an element in the file to import to (maybe the file itself); used to determine module, roots, etc. + * @param vfile file which importable name we want to find. + * @return a possibly qualified name under which the file may be imported, or null. If there's more than one way (overlapping roots), + * the name with fewest qualifiers is selected. + */ + @Nullable + public static String findShortestImportableName(PsiElement foothold, @NotNull VirtualFile vfile) { + final PyQualifiedName qName = findShortestImportableQName(foothold, vfile); + return qName == null ? null : qName.toString(); + } + + @Nullable + public static PyQualifiedName findShortestImportableQName(@Nullable PsiFileSystemItem fsItem) { + VirtualFile vFile = fsItem != null ? fsItem.getVirtualFile() : null; + return vFile != null ? findShortestImportableQName(fsItem, vFile) : null; + } + + @Nullable + public static PyQualifiedName findShortestImportableQName(@NotNull PsiElement foothold, @NotNull VirtualFile vfile) { + final PythonPathCache cache = ResolveImportUtil.getPathCache(foothold); + final PyQualifiedName name = cache != null ? cache.getName(vfile) : null; + if (name != null) { + return name; + } + PathChoosingVisitor visitor = new PathChoosingVisitor(vfile); + RootVisitorHost.visitRoots(foothold, visitor); + final PyQualifiedName result = visitor.getResult(); + if (cache != null) { + cache.putName(vfile, result); + } + return result; + } + + @Nullable + public static String findShortestImportableName(Module module, @NotNull VirtualFile vfile) { + final PythonPathCache cache = PythonModulePathCache.getInstance(module); + final PyQualifiedName name = cache.getName(vfile); + if (name != null) { + return name.toString(); + } + PathChoosingVisitor visitor = new PathChoosingVisitor(vfile); + RootVisitorHost.visitRoots(module, false, visitor); + final PyQualifiedName result = visitor.getResult(); + cache.putName(vfile, result); + return result == null ? null : result.toString(); + } + + /** + * Returns the name through which the specified symbol should be imported. This can be different from the qualified name of the + * symbol (the place where a symbol is defined). For example, Python 2.7 unittest defines TestCase in unittest.case module + * but it should be imported directly from unittest. + * + * @param symbol the symbol to be imported + * @param foothold the location where the import statement would be added + * @return the qualified name, or null if it wasn't possible to calculate one + */ + @Nullable + public static PyQualifiedName findCanonicalImportPath(@NotNull PsiElement symbol, @Nullable PsiElement foothold) { + PsiFileSystemItem srcfile = symbol instanceof PsiFileSystemItem ? (PsiFileSystemItem)symbol : symbol.getContainingFile(); + if (srcfile == null) { + return null; + } + VirtualFile virtualFile = srcfile.getVirtualFile(); + if (virtualFile == null) { + return null; + } + if (srcfile instanceof PsiFile && symbol instanceof PsiNamedElement && !(symbol instanceof PsiFileSystemItem)) { + PsiElement toplevel = symbol; + if (symbol instanceof PyFunction) { + final PyClass containingClass = ((PyFunction)symbol).getContainingClass(); + if (containingClass != null) { + toplevel = containingClass; + } + } + PsiDirectory dir = ((PsiFile)srcfile).getContainingDirectory(); + while (dir != null) { + PsiFile initPy = dir.findFile(PyNames.INIT_DOT_PY); + if (initPy == null) { + break; + } + if (initPy instanceof PyFile && toplevel.equals(((PyFile)initPy).getElementNamed(((PsiNamedElement)toplevel).getName()))) { + virtualFile = dir.getVirtualFile(); + } + dir = dir.getParentDirectory(); + } + } + final PyQualifiedName qname = findShortestImportableQName(foothold != null ? foothold : symbol, virtualFile); + if (qname != null) { + for (PyCanonicalPathProvider provider : Extensions.getExtensions(PyCanonicalPathProvider.EP_NAME)) { + final PyQualifiedName restored = provider.getCanonicalPath(qname); + if (restored != null) { + return restored; + } + } + } + return qname; + } + + /** + * Tries to find roots that contain given vfile, and among them the root that contains at the smallest depth. + */ + private static class PathChoosingVisitor implements RootVisitor { + + private final VirtualFile myVFile; + private List myResult; + + private PathChoosingVisitor(VirtualFile file) { + if (!file.isDirectory() && file.getName().equals(PyNames.INIT_DOT_PY)) { + myVFile = file.getParent(); + } + else { + myVFile = file; + } + } + + public boolean visitRoot(VirtualFile root, Module module, Sdk sdk) { + final String relativePath = VfsUtilCore.getRelativePath(myVFile, root, '/'); + if (relativePath != null) { + List result = StringUtil.split(relativePath, "/"); + if (myResult == null || result.size() < myResult.size()) { + if (result.size() > 0) { + result.set(result.size() - 1, FileUtil.getNameWithoutExtension(result.get(result.size() - 1))); + } + for (String component : result) { + if (!PyNames.isIdentifier(component)) { + return true; + } + } + myResult = result; + } + } + return myResult == null || myResult.size() > 0; + } + + @Nullable + public PyQualifiedName getResult() { + return myResult != null ? PyQualifiedName.fromComponents(myResult) : null; + } + } +} diff --git a/python/src/com/jetbrains/python/psi/resolve/ResolveImportUtil.java b/python/src/com/jetbrains/python/psi/resolve/ResolveImportUtil.java index 801e35f11953..80b11cc0a75f 100644 --- a/python/src/com/jetbrains/python/psi/resolve/ResolveImportUtil.java +++ b/python/src/com/jetbrains/python/psi/resolve/ResolveImportUtil.java @@ -6,7 +6,6 @@ import com.intellij.openapi.module.ModuleUtilCore; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.psi.util.PsiTreeUtil; @@ -240,7 +239,7 @@ public class ResolveImportUtil { @NotNull PyQualifiedName qualifiedName) { final PsiDirectory containingDirectory = foothold.getContainingDirectory(); if (containingDirectory != null) { - final PyQualifiedName containingPath = findCanonicalImportPath(containingDirectory, null); + final PyQualifiedName containingPath = QualifiedNameFinder.findCanonicalImportPath(containingDirectory, null); if (containingPath != null && containingPath.getComponentCount() > 0) { final PyQualifiedName absolutePath = containingPath.append(qualifiedName.toString()); final QualifiedNameResolver absoluteVisitor = new QualifiedNameResolverImpl(absolutePath).fromElement(foothold); @@ -258,7 +257,7 @@ public class ResolveImportUtil { } @Nullable - private static PythonPathCache getPathCache(PsiElement foothold) { + static PythonPathCache getPathCache(PsiElement foothold) { PythonPathCache cache = null; final Module module = ModuleUtilCore.findModuleForPsiElement(foothold); if (module != null) { @@ -412,149 +411,6 @@ public class ResolveImportUtil { return ret; } - /** - * Tries to find roots that contain given vfile, and among them the root that contains at the smallest depth. - */ - private static class PathChoosingVisitor implements RootVisitor { - - private final VirtualFile myVFile; - private List myResult; - - private PathChoosingVisitor(VirtualFile file) { - if (!file.isDirectory() && file.getName().equals(PyNames.INIT_DOT_PY)) { - myVFile = file.getParent(); - } - else { - myVFile = file; - } - } - - public boolean visitRoot(VirtualFile root, Module module, Sdk sdk) { - final String relativePath = VfsUtilCore.getRelativePath(myVFile, root, '/'); - if (relativePath != null) { - List result = StringUtil.split(relativePath, "/"); - if (myResult == null || result.size() < myResult.size()) { - if (result.size() > 0) { - result.set(result.size() - 1, FileUtil.getNameWithoutExtension(result.get(result.size() - 1))); - } - for (String component : result) { - if (!PyNames.isIdentifier(component)) { - return true; - } - } - myResult = result; - } - } - return myResult == null || myResult.size() > 0; - } - - @Nullable - public PyQualifiedName getResult() { - return myResult != null ? PyQualifiedName.fromComponents(myResult) : null; - } - } - - /** - * Looks for a way to import given file. - * - * @param foothold an element in the file to import to (maybe the file itself); used to determine module, roots, etc. - * @param vfile file which importable name we want to find. - * @return a possibly qualified name under which the file may be imported, or null. If there's more than one way (overlapping roots), - * the name with fewest qualifiers is selected. - */ - @Nullable - public static String findShortestImportableName(PsiElement foothold, @NotNull VirtualFile vfile) { - final PyQualifiedName qName = findShortestImportableQName(foothold, vfile); - return qName == null ? null : qName.toString(); - } - - @Nullable - public static PyQualifiedName findShortestImportableQName(@Nullable PsiFileSystemItem fsItem) { - VirtualFile vFile = fsItem != null ? fsItem.getVirtualFile() : null; - return vFile != null ? findShortestImportableQName(fsItem, vFile) : null; - } - - @Nullable - public static PyQualifiedName findShortestImportableQName(@NotNull PsiElement foothold, @NotNull VirtualFile vfile) { - final PythonPathCache cache = getPathCache(foothold); - final PyQualifiedName name = cache != null ? cache.getName(vfile) : null; - if (name != null) { - return name; - } - PathChoosingVisitor visitor = new PathChoosingVisitor(vfile); - RootVisitorHost.visitRoots(foothold, visitor); - final PyQualifiedName result = visitor.getResult(); - if (cache != null) { - cache.putName(vfile, result); - } - return result; - } - - @Nullable - public static String findShortestImportableName(Module module, @NotNull VirtualFile vfile) { - final PythonPathCache cache = PythonModulePathCache.getInstance(module); - final PyQualifiedName name = cache.getName(vfile); - if (name != null) { - return name.toString(); - } - PathChoosingVisitor visitor = new PathChoosingVisitor(vfile); - RootVisitorHost.visitRoots(module, false, visitor); - final PyQualifiedName result = visitor.getResult(); - cache.putName(vfile, result); - return result == null ? null : result.toString(); - } - - /** - * Returns the name through which the specified symbol should be imported. This can be different from the qualified name of the - * symbol (the place where a symbol is defined). For example, Python 2.7 unittest defines TestCase in unittest.case module - * but it should be imported directly from unittest. - * - * @param symbol the symbol to be imported - * @param foothold the location where the import statement would be added - * @return the qualified name, or null if it wasn't possible to calculate one - */ - @Nullable - public static PyQualifiedName findCanonicalImportPath(@NotNull PsiElement symbol, @Nullable PsiElement foothold) { - PsiFileSystemItem srcfile = symbol instanceof PsiFileSystemItem ? (PsiFileSystemItem)symbol : symbol.getContainingFile(); - if (srcfile == null) { - return null; - } - VirtualFile virtualFile = srcfile.getVirtualFile(); - if (virtualFile == null) { - return null; - } - if (srcfile instanceof PsiFile && symbol instanceof PsiNamedElement && !(symbol instanceof PsiFileSystemItem)) { - PsiElement toplevel = symbol; - if (symbol instanceof PyFunction) { - final PyClass containingClass = ((PyFunction)symbol).getContainingClass(); - if (containingClass != null) { - toplevel = containingClass; - } - } - PsiDirectory dir = ((PsiFile)srcfile).getContainingDirectory(); - while (dir != null) { - PsiFile initPy = dir.findFile(PyNames.INIT_DOT_PY); - if (initPy == null) { - break; - } - if (initPy instanceof PyFile && toplevel.equals(((PyFile)initPy).getElementNamed(((PsiNamedElement)toplevel).getName()))) { - virtualFile = dir.getVirtualFile(); - } - dir = dir.getParentDirectory(); - } - } - final PyQualifiedName qname = findShortestImportableQName(foothold != null ? foothold : symbol, virtualFile); - if (qname != null) { - for (PyCanonicalPathProvider provider : Extensions.getExtensions(PyCanonicalPathProvider.EP_NAME)) { - final PyQualifiedName restored = provider.getCanonicalPath(qname); - if (restored != null) { - return restored; - } - } - } - return qname; - } - /** * @param element what we test (identifier, reference, import element, etc) * @return the how the element relates to an enclosing import statement, if any diff --git a/python/src/com/jetbrains/python/psi/types/PyModuleType.java b/python/src/com/jetbrains/python/psi/types/PyModuleType.java index f1fe01c684ca..b52e7f50def8 100644 --- a/python/src/com/jetbrains/python/psi/types/PyModuleType.java +++ b/python/src/com/jetbrains/python/psi/types/PyModuleType.java @@ -99,7 +99,7 @@ public class PyModuleType implements PyType { // Modules don't descend from obje @Nullable private List resolveImplicitPackageMember(@NotNull String name, @NotNull List importElements) { - final PyQualifiedName packageQName = ResolveImportUtil.findCanonicalImportPath(myModule, null); + final PyQualifiedName packageQName = QualifiedNameFinder.findCanonicalImportPath(myModule, null); if (packageQName != null) { final PyQualifiedName resolvingQName = packageQName.append(name); for (PyImportElement importElement : importElements) { @@ -146,7 +146,7 @@ public class PyModuleType implements PyType { // Modules don't descend from obje if (file != null) { file = file.getOriginalFile(); } - final PyQualifiedName absoluteQName = ResolveImportUtil.findShortestImportableQName(file); + final PyQualifiedName absoluteQName = QualifiedNameFinder.findShortestImportableQName(file); if (file != null && absoluteQName != null) { final PyQualifiedName prefixQName = PyUtil.isPackage(file) ? absoluteQName : absoluteQName.removeLastComponent(); if (prefixQName.getComponentCount() > 0) { diff --git a/python/src/com/jetbrains/python/refactoring/classes/PyClassRefactoringUtil.java b/python/src/com/jetbrains/python/refactoring/classes/PyClassRefactoringUtil.java index 13e3e0c47bc9..305d1186c7af 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/PyClassRefactoringUtil.java +++ b/python/src/com/jetbrains/python/refactoring/classes/PyClassRefactoringUtil.java @@ -18,7 +18,7 @@ import com.jetbrains.python.psi.impl.PyBuiltinCache; import com.jetbrains.python.psi.impl.PyImportedModule; import com.jetbrains.python.psi.impl.PyPsiUtils; import com.jetbrains.python.psi.impl.PyQualifiedName; -import com.jetbrains.python.psi.resolve.ResolveImportUtil; +import com.jetbrains.python.psi.resolve.QualifiedNameFinder; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -244,7 +244,7 @@ public class PyClassRefactoringUtil { final PsiFile newFile = element.getContainingFile(); final PsiFile file = anchor.getContainingFile(); if (newFile == file) return false; - final PyQualifiedName qname = ResolveImportUtil.findCanonicalImportPath(element, anchor); + final PyQualifiedName qname = QualifiedNameFinder.findCanonicalImportPath(element, anchor); if (qname == null || !isValidQualifiedName(qname)) { return false; } diff --git a/python/src/com/jetbrains/python/refactoring/move/PyMoveClassOrFunctionProcessor.java b/python/src/com/jetbrains/python/refactoring/move/PyMoveClassOrFunctionProcessor.java index 5a25ccc02940..4892a9b8f0ff 100644 --- a/python/src/com/jetbrains/python/refactoring/move/PyMoveClassOrFunctionProcessor.java +++ b/python/src/com/jetbrains/python/refactoring/move/PyMoveClassOrFunctionProcessor.java @@ -21,7 +21,7 @@ import com.jetbrains.python.codeInsight.imports.PyImportOptimizer; import com.jetbrains.python.documentation.DocStringTypeReference; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyQualifiedName; -import com.jetbrains.python.psi.resolve.ResolveImportUtil; +import com.jetbrains.python.psi.resolve.QualifiedNameFinder; import com.jetbrains.python.refactoring.PyRefactoringUtil; import com.jetbrains.python.refactoring.classes.PyClassRefactoringUtil; import org.jetbrains.annotations.NotNull; @@ -228,7 +228,7 @@ public class PyMoveClassOrFunctionProcessor extends BaseRefactoringProcessor { } private static void checkValidImportableFile(PsiElement anchor, VirtualFile file) { - final PyQualifiedName qName = ResolveImportUtil.findShortestImportableQName(anchor, file); + final PyQualifiedName qName = QualifiedNameFinder.findShortestImportableQName(anchor, file); if (!PyClassRefactoringUtil.isValidQualifiedName(qName)) { throw new IncorrectOperationException(PyBundle.message("refactoring.move.class.or.function.error.cannot.use.module.name.$0", qName)); } diff --git a/python/src/com/jetbrains/python/refactoring/move/PyMoveFileHandler.java b/python/src/com/jetbrains/python/refactoring/move/PyMoveFileHandler.java index ef081d2ba79c..bb901fc22048 100644 --- a/python/src/com/jetbrains/python/refactoring/move/PyMoveFileHandler.java +++ b/python/src/com/jetbrains/python/refactoring/move/PyMoveFileHandler.java @@ -12,7 +12,7 @@ import com.jetbrains.python.actions.CreatePackageAction; 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.psi.resolve.QualifiedNameFinder; import com.jetbrains.python.refactoring.PyRefactoringUtil; import com.jetbrains.python.refactoring.classes.PyClassRefactoringUtil; import org.jetbrains.annotations.NotNull; @@ -80,13 +80,13 @@ public class PyMoveFileHandler extends MoveFileHandler { if (importStmt instanceof PyFromImportStatement && PsiTreeUtil.getParentOfType(element, PyImportElement.class) != null) { continue; } - final PyQualifiedName newElementName = ResolveImportUtil.findCanonicalImportPath(newElement, element); + final PyQualifiedName newElementName = QualifiedNameFinder.findCanonicalImportPath(newElement, element); replaceWithQualifiedExpression(element, newElementName); } else if (element instanceof PyReferenceExpression) { updatedFiles.add(file); if (((PyReferenceExpression)element).getQualifier() != null) { - final PyQualifiedName newQualifiedName = ResolveImportUtil.findCanonicalImportPath(newElement, element); + final PyQualifiedName newQualifiedName = QualifiedNameFinder.findCanonicalImportPath(newElement, element); replaceWithQualifiedExpression(element, newQualifiedName); } else { final PyQualifiedName newName = PyQualifiedName.fromComponents(PyClassRefactoringUtil.getOriginalName(newElement));