From c5ae9550ceefec5b01a4a45f213a6e82ddc3ece7 Mon Sep 17 00:00:00 2001 From: "andrey.matveev" Date: Tue, 11 Aug 2020 12:59:09 +0700 Subject: [PATCH] PY-42750 Allow same directory imports in Python 3 and introduce explicit namespace packages There are 3 types of directories: plain directories, ordinary packages (with __init__.py) and PEP 420 namespace packages. There are 3 types of imports: absolute (from a root), same directory (absolute import from the current directory when it's not explicitly marked as a root) and relative imports (imports that start with dot). Absolute imports are correct in all kinds of directories. Same directory imports are correct in Python 2 in all cases and in Python 3 if we have the directory containing the script with this import in Python path at runtime. Users of Python 3 often face the problem when they can run the script from the console because the directory containing this script got into Python path but still have red underline and an unresolved reference error in the same directory import because PyCharm didn't know that this file will be used as a program's entry point. Previously, the way to fix such a problem was marking it as a source root. But this action was not so obvious, especially for newcomers. With this feature, such imports resolve successfully and now it is not necessary to mark directories as source roots. Relative imports are correct only in Python 3 namespace or ordinary packages and should not be used in plain directories. If we have a relative import in plain directory we highlight it with a weak warning and suggest 2 ways of fixing that: marking directory as a namespace package explicitly (with quick fix or with Mark As | Namespace Package) or changing this import to the same directory import with a quickfix or manually. Explicitly marking namespace packages can later be used for automatically running files from them and ordinary packages with "-m". The new resolve policy and explicit namespace packages can be disabled with the Registry flag "python.explicit.namespace.packages". These changes also address PY-40396. Namely, now any directory with __init__.py inside or explicitly marked as a namespace package has a package icon, regardless of its name or parents. GitOrigin-RevId: 310fa562eb60121243cb6d68386ffc3e45c73245 --- .../PyRelativeImportInspection.html | 5 + .../messages/PyBundle.properties | 15 ++ .../resources/messages/PyPsiBundle.properties | 4 + .../src/com/jetbrains/python/psi/PyUtil.java | 40 ++-- .../python/psi/resolve/ResolveImportUtil.java | 5 + python/src/META-INF/python-core-common.xml | 14 ++ .../python/PyDirectoryIconProvider.java | 17 +- .../python/actions/CreatePackageAction.java | 172 ++++++++++++----- .../inspections/PyRelativeImportInspection.kt | 132 +++++++++++++ .../python/module/PyContentEntriesEditor.java | 11 +- .../PyMarkAsNamespacePackageAction.kt | 47 +++++ .../PyNamespacePackageCustomIdentifier.kt | 26 +++ .../PyNamespacePackageRootProvider.kt | 95 ++++++++++ .../PyNamespacePackagesService.java | 177 ++++++++++++++++++ .../PyNamespacePackagesStatisticsCollector.kt | 32 ++++ .../nestedNamespacePackage/mod.py | 0 .../ordinaryPackage/__init__.py | 0 .../PlainDirectory/plainDirectory/script.py | 0 .../ordinaryPackage/__init__.py | 0 .../nestedPlainDirectory/script.py | 0 .../ImportedTypedDict/a.py | 2 +- .../namespacePackage/mod.py | 2 + .../namespacePackage/util.py | 2 + .../namespacePackage/mod.py | 2 + .../namespacePackage/util.py | 2 + .../namespacePackage/mod.py | 2 + .../namespacePackage/util.py | 2 + .../nestedNamespacePackage/mod.py | 2 + .../nestedNamespacePackage/util.py | 2 + .../nestedPlainDirectory/dottedImport.py | 1 + .../dottedImport_after.py | 1 + .../nestedPlainDirectory/util.py | 2 + .../ordinaryPackage/__init__.py | 0 .../nestedNamespacePackage/mod.py | 2 + .../nestedNamespacePackage/util.py | 2 + .../ordinaryPackage/__init__.py | 0 .../ordinaryPackage/script.py | 1 + .../ordinaryPackage/util.py | 2 + .../ordinaryPackage/__init__.py | 0 .../ordinaryPackage/script.py | 1 + .../ordinaryPackage/util.py | 2 + .../plainDirectory/dottedImport.py | 1 + .../plainDirectory/dottedImport_after.py | 1 + .../plainDirectory/util.py | 2 + .../plainDirectory/dottedImport.py | 1 + .../plainDirectory/dottedImport_after.py | 1 + .../plainDirectory/util.py | 2 + .../plainDirectory/dottedImport.py | 1 + .../plainDirectory/util.py | 2 + .../ordinaryPackage/__init__.py | 0 .../nestedPlainDirectory/dottedImport.py | 2 + .../nestedPlainDirectory/util.py | 2 + .../plainDirectory/script.py | 2 + .../plainDirectory/util.py | 2 + .../plainDirectory/sourceRoot/script.py | 2 + .../plainDirectory/util.py | 2 + .../ImportedTypedDict/a.py | 2 +- .../nspkg/pkg/__init__.py | 0 .../nspkg/pkg/directory/.keep | 0 .../pkg/__init__.py | 0 .../pkg/nspkg/.keep | 0 .../pkg/__init__.py | 0 .../pkg/nspkg/.keep | 0 .../nspkg/subpkg/.keep | 0 .../nspkg/pkg/__init__.py | 0 .../pkg/__init__.py | 0 .../pkg/subpkg/__init__.py | 0 .../pkg/__init__.py | 0 .../pkg/plainDirectory/.keep | 0 .../namespacePackage/mod.py | 2 + .../namespacePackage/util.py | 0 .../namespacePackage/mod.py | 2 + .../namespacePackage/util.py | 0 .../nestedNamespacePackage/mod.py | 2 + .../nestedNamespacePackage/util.py | 0 .../nestedNamespacePackage/mod.py | 2 + .../nestedNamespacePackage/util.py | 0 .../ordinaryPackage/__init__.py | 0 .../ordinaryPackage/mod.py | 2 + .../ordinaryPackage/util.py | 2 + .../ordinaryPackage/__init__.py | 0 .../ordinaryPackage/mod.py | 2 + .../ordinaryPackage/util.py | 2 + .../plainDirectory/mod.py | 2 + .../plainDirectory/util.py | 2 + .../plainDirectory/mod.py | 2 + .../plainDirectory/util.py | 2 + .../plainDirectory/mod.py | 2 + .../plainDirectory/util.py | 2 + .../plainDirectory/mod.py | 2 + .../plainDirectory/util.py | 2 + .../jetbrains/python/PyPackageIconTest.java | 69 ++++++- .../python/PyRelativeImportResolveTest.java | 153 +++++++++++++++ .../PyMarkAsNamespacePackageActionTest.java | 108 +++++++++++ .../jetbrains/python/fixtures/PyTestCase.java | 2 + .../PyRelativeImportInspectionTest.kt | 149 +++++++++++++++ .../python/refactoring/PyMoveTest.java | 26 ++- 97 files changed, 1302 insertions(+), 86 deletions(-) create mode 100644 python/pluginResources/inspectionDescriptions/PyRelativeImportInspection.html create mode 100644 python/src/com/jetbrains/python/inspections/PyRelativeImportInspection.kt create mode 100644 python/src/com/jetbrains/python/namespacePackages/PyMarkAsNamespacePackageAction.kt create mode 100644 python/src/com/jetbrains/python/namespacePackages/PyNamespacePackageCustomIdentifier.kt create mode 100644 python/src/com/jetbrains/python/namespacePackages/PyNamespacePackageRootProvider.kt create mode 100644 python/src/com/jetbrains/python/namespacePackages/PyNamespacePackagesService.java create mode 100644 python/src/com/jetbrains/python/namespacePackages/PyNamespacePackagesStatisticsCollector.kt create mode 100644 python/testData/actions/MarkAsNamespacePackage/NestedNamespacePackage/namespacePackage/nestedNamespacePackage/mod.py create mode 100644 python/testData/actions/MarkAsNamespacePackage/OrdinaryPackage/ordinaryPackage/__init__.py create mode 100644 python/testData/actions/MarkAsNamespacePackage/PlainDirectory/plainDirectory/script.py create mode 100644 python/testData/actions/MarkAsNamespacePackage/PlainDirectoryInOrdinaryPackage/ordinaryPackage/__init__.py create mode 100644 python/testData/actions/MarkAsNamespacePackage/PlainDirectoryInOrdinaryPackage/ordinaryPackage/nestedPlainDirectory/script.py create mode 100644 python/testData/inspections/PyRelativeImportInspection/NamespacePackageDottedImportNoInspection/namespacePackage/mod.py create mode 100644 python/testData/inspections/PyRelativeImportInspection/NamespacePackageDottedImportNoInspection/namespacePackage/util.py create mode 100644 python/testData/inspections/PyRelativeImportInspection/NamespacePackageSameDirectoryImportNoInspection/namespacePackage/mod.py create mode 100644 python/testData/inspections/PyRelativeImportInspection/NamespacePackageSameDirectoryImportNoInspection/namespacePackage/util.py create mode 100644 python/testData/inspections/PyRelativeImportInspection/NamespacePackageSameDirectoryImportRegistryOffNoInspection/namespacePackage/mod.py create mode 100644 python/testData/inspections/PyRelativeImportInspection/NamespacePackageSameDirectoryImportRegistryOffNoInspection/namespacePackage/util.py create mode 100644 python/testData/inspections/PyRelativeImportInspection/NestedNamespacePackageSameDirectoryImportRegistryOffNoInspection/namespacePackage/nestedNamespacePackage/mod.py create mode 100644 python/testData/inspections/PyRelativeImportInspection/NestedNamespacePackageSameDirectoryImportRegistryOffNoInspection/namespacePackage/nestedNamespacePackage/util.py create mode 100644 python/testData/inspections/PyRelativeImportInspection/NestedPlainDirectoryDottedImportMarkDirectory/plainDirectory/nestedPlainDirectory/dottedImport.py create mode 100644 python/testData/inspections/PyRelativeImportInspection/NestedPlainDirectoryDottedImportMarkDirectory/plainDirectory/nestedPlainDirectory/dottedImport_after.py create mode 100644 python/testData/inspections/PyRelativeImportInspection/NestedPlainDirectoryDottedImportMarkDirectory/plainDirectory/nestedPlainDirectory/util.py create mode 100644 python/testData/inspections/PyRelativeImportInspection/NotMarkedNamespacePackageInsidePackageSameDirectoryImportNoInspection/namespacePackage/ordinaryPackage/__init__.py create mode 100644 python/testData/inspections/PyRelativeImportInspection/NotMarkedNamespacePackageInsidePackageSameDirectoryImportNoInspection/namespacePackage/ordinaryPackage/nestedNamespacePackage/mod.py create mode 100644 python/testData/inspections/PyRelativeImportInspection/NotMarkedNamespacePackageInsidePackageSameDirectoryImportNoInspection/namespacePackage/ordinaryPackage/nestedNamespacePackage/util.py create mode 100644 python/testData/inspections/PyRelativeImportInspection/OrdinaryPackageDottedImportNoInspection/ordinaryPackage/__init__.py create mode 100644 python/testData/inspections/PyRelativeImportInspection/OrdinaryPackageDottedImportNoInspection/ordinaryPackage/script.py create mode 100644 python/testData/inspections/PyRelativeImportInspection/OrdinaryPackageDottedImportNoInspection/ordinaryPackage/util.py create mode 100644 python/testData/inspections/PyRelativeImportInspection/OrdinaryPackageSameDirectoryImportNoInspection/ordinaryPackage/__init__.py create mode 100644 python/testData/inspections/PyRelativeImportInspection/OrdinaryPackageSameDirectoryImportNoInspection/ordinaryPackage/script.py create mode 100644 python/testData/inspections/PyRelativeImportInspection/OrdinaryPackageSameDirectoryImportNoInspection/ordinaryPackage/util.py create mode 100644 python/testData/inspections/PyRelativeImportInspection/PlainDirectoryDottedImportDeleteDot/plainDirectory/dottedImport.py create mode 100644 python/testData/inspections/PyRelativeImportInspection/PlainDirectoryDottedImportDeleteDot/plainDirectory/dottedImport_after.py create mode 100644 python/testData/inspections/PyRelativeImportInspection/PlainDirectoryDottedImportDeleteDot/plainDirectory/util.py create mode 100644 python/testData/inspections/PyRelativeImportInspection/PlainDirectoryDottedImportMarkDirectory/plainDirectory/dottedImport.py create mode 100644 python/testData/inspections/PyRelativeImportInspection/PlainDirectoryDottedImportMarkDirectory/plainDirectory/dottedImport_after.py create mode 100644 python/testData/inspections/PyRelativeImportInspection/PlainDirectoryDottedImportMarkDirectory/plainDirectory/util.py create mode 100644 python/testData/inspections/PyRelativeImportInspection/PlainDirectoryDottedImportRegistryOffNoInspection/plainDirectory/dottedImport.py create mode 100644 python/testData/inspections/PyRelativeImportInspection/PlainDirectoryDottedImportRegistryOffNoInspection/plainDirectory/util.py create mode 100644 python/testData/inspections/PyRelativeImportInspection/PlainDirectoryInsidePackageInsidePlainDirectoryNoInspection/plainDirectory/ordinaryPackage/__init__.py create mode 100644 python/testData/inspections/PyRelativeImportInspection/PlainDirectoryInsidePackageInsidePlainDirectoryNoInspection/plainDirectory/ordinaryPackage/nestedPlainDirectory/dottedImport.py create mode 100644 python/testData/inspections/PyRelativeImportInspection/PlainDirectoryInsidePackageInsidePlainDirectoryNoInspection/plainDirectory/ordinaryPackage/nestedPlainDirectory/util.py create mode 100644 python/testData/inspections/PyRelativeImportInspection/Python2PlainDirectoryNoInspection/plainDirectory/script.py create mode 100644 python/testData/inspections/PyRelativeImportInspection/Python2PlainDirectoryNoInspection/plainDirectory/util.py create mode 100644 python/testData/inspections/PyRelativeImportInspection/SourceRootDottedImportInspectionWithoutQuickFixes/plainDirectory/sourceRoot/script.py create mode 100644 python/testData/inspections/PyRelativeImportInspection/SourceRootDottedImportInspectionWithoutQuickFixes/plainDirectory/util.py create mode 100644 python/testData/packageIcon/DirectoryInOrdinaryPackageInNamespacePackage/nspkg/pkg/__init__.py create mode 100644 python/testData/packageIcon/DirectoryInOrdinaryPackageInNamespacePackage/nspkg/pkg/directory/.keep create mode 100644 python/testData/packageIcon/NamespacePackageInsideOldStylePackage/pkg/__init__.py create mode 100644 python/testData/packageIcon/NamespacePackageInsideOldStylePackage/pkg/nspkg/.keep create mode 100644 python/testData/packageIcon/NamespacePackageInsideOldStylePackageRegistryOff/pkg/__init__.py create mode 100644 python/testData/packageIcon/NamespacePackageInsideOldStylePackageRegistryOff/pkg/nspkg/.keep create mode 100644 python/testData/packageIcon/NamespacePackageRegistryOff/nspkg/subpkg/.keep create mode 100644 python/testData/packageIcon/OldStylePackageInsideNamespacePackageRegistryOff/nspkg/pkg/__init__.py create mode 100644 python/testData/packageIcon/OldStylePackageRegistryOff/pkg/__init__.py create mode 100644 python/testData/packageIcon/OldStylePackageRegistryOff/pkg/subpkg/__init__.py create mode 100644 python/testData/packageIcon/PlainDirectoryInsideOldStylePackage/pkg/__init__.py create mode 100644 python/testData/packageIcon/PlainDirectoryInsideOldStylePackage/pkg/plainDirectory/.keep create mode 100644 python/testData/resolve/multiFile/relativeAndSameDirectoryImports/namespacePackageDottedRelativeImport/namespacePackage/mod.py create mode 100644 python/testData/resolve/multiFile/relativeAndSameDirectoryImports/namespacePackageDottedRelativeImport/namespacePackage/util.py create mode 100644 python/testData/resolve/multiFile/relativeAndSameDirectoryImports/namespacePackageSameDirectoryImport/namespacePackage/mod.py create mode 100644 python/testData/resolve/multiFile/relativeAndSameDirectoryImports/namespacePackageSameDirectoryImport/namespacePackage/util.py create mode 100644 python/testData/resolve/multiFile/relativeAndSameDirectoryImports/nestedNamespacePackageDottedRelativeImport/namespacePackage/nestedNamespacePackage/mod.py create mode 100644 python/testData/resolve/multiFile/relativeAndSameDirectoryImports/nestedNamespacePackageDottedRelativeImport/namespacePackage/nestedNamespacePackage/util.py create mode 100644 python/testData/resolve/multiFile/relativeAndSameDirectoryImports/nestedNamespacePackageSameDirectoryImport/namespacePackage/nestedNamespacePackage/mod.py create mode 100644 python/testData/resolve/multiFile/relativeAndSameDirectoryImports/nestedNamespacePackageSameDirectoryImport/namespacePackage/nestedNamespacePackage/util.py create mode 100644 python/testData/resolve/multiFile/relativeAndSameDirectoryImports/ordinaryPackageDottedRelativeFromImport/ordinaryPackage/__init__.py create mode 100644 python/testData/resolve/multiFile/relativeAndSameDirectoryImports/ordinaryPackageDottedRelativeFromImport/ordinaryPackage/mod.py create mode 100644 python/testData/resolve/multiFile/relativeAndSameDirectoryImports/ordinaryPackageDottedRelativeFromImport/ordinaryPackage/util.py create mode 100644 python/testData/resolve/multiFile/relativeAndSameDirectoryImports/ordinaryPackageSameDirectoryFromImport/ordinaryPackage/__init__.py create mode 100644 python/testData/resolve/multiFile/relativeAndSameDirectoryImports/ordinaryPackageSameDirectoryFromImport/ordinaryPackage/mod.py create mode 100644 python/testData/resolve/multiFile/relativeAndSameDirectoryImports/ordinaryPackageSameDirectoryFromImport/ordinaryPackage/util.py create mode 100644 python/testData/resolve/multiFile/relativeAndSameDirectoryImports/plainDirectoryDottedRelativeImport/plainDirectory/mod.py create mode 100644 python/testData/resolve/multiFile/relativeAndSameDirectoryImports/plainDirectoryDottedRelativeImport/plainDirectory/util.py create mode 100644 python/testData/resolve/multiFile/relativeAndSameDirectoryImports/plainDirectoryDottedRelativeImportRegistryOff/plainDirectory/mod.py create mode 100644 python/testData/resolve/multiFile/relativeAndSameDirectoryImports/plainDirectoryDottedRelativeImportRegistryOff/plainDirectory/util.py create mode 100644 python/testData/resolve/multiFile/relativeAndSameDirectoryImports/plainDirectorySameDirectoryImport/plainDirectory/mod.py create mode 100644 python/testData/resolve/multiFile/relativeAndSameDirectoryImports/plainDirectorySameDirectoryImport/plainDirectory/util.py create mode 100644 python/testData/resolve/multiFile/relativeAndSameDirectoryImports/plainDirectorySameDirectoryImportRegistryOff/plainDirectory/mod.py create mode 100644 python/testData/resolve/multiFile/relativeAndSameDirectoryImports/plainDirectorySameDirectoryImportRegistryOff/plainDirectory/util.py create mode 100644 python/testSrc/com/jetbrains/python/PyRelativeImportResolveTest.java create mode 100644 python/testSrc/com/jetbrains/python/actions/PyMarkAsNamespacePackageActionTest.java create mode 100644 python/testSrc/com/jetbrains/python/inspections/PyRelativeImportInspectionTest.kt diff --git a/python/pluginResources/inspectionDescriptions/PyRelativeImportInspection.html b/python/pluginResources/inspectionDescriptions/PyRelativeImportInspection.html new file mode 100644 index 000000000000..bcb0d791a590 --- /dev/null +++ b/python/pluginResources/inspectionDescriptions/PyRelativeImportInspection.html @@ -0,0 +1,5 @@ + + +This inspection reports usages of relative imports inside plain directories, i.e. directories neither containing __init__.py nor explicitly marked as namespace packages. + + \ No newline at end of file diff --git a/python/pluginResources/messages/PyBundle.properties b/python/pluginResources/messages/PyBundle.properties index faf68d1afb80..b1270c365e6e 100644 --- a/python/pluginResources/messages/PyBundle.properties +++ b/python/pluginResources/messages/PyBundle.properties @@ -23,6 +23,10 @@ QFIX.generate.binary.stubs=Generate binary stubs QFIX.reformat.file=Reformat file QFIX.bold.html.text={0} +# Namespace packages +QFIX.mark.as.namespace.package=Mark directory ''{0}'' as namespace package +QFIX.change.to.same.directory.import=Change to same directory import + # Intentions: INTN # Conflict checker @@ -1207,3 +1211,14 @@ goto.superclass.choose=Choose superclass python.symbol.chooser.dialog.title=Choose Symbol button.install.to.user.site.packages.directory=Install to user's site packages directory framework.not.supported.for.the.selected.interpreter={0} is not supported for the selected interpreter + +action.MarkNamespacePackageDirectory.text=Namespace package +python.unmark.as.namespace.package=Unmark as Namespace Package +python.namespace.package.folder=Namespace Package +python.namespace.packages.name=Namespace packages +python.namespace.packages.description=Namespace Package Folders +dialog.title.can.t.create.package=Create Package Error +command.name.create.new.package=Create New Package {0} +dialog.title.new.python.package=New Python Package +new.package.list.item.ordinary.package=Package +new.package.list.item.namespace.package=Namespace package \ No newline at end of file diff --git a/python/python-psi-impl/resources/messages/PyPsiBundle.properties b/python/python-psi-impl/resources/messages/PyPsiBundle.properties index 828846dd7611..7b767a61cbc4 100644 --- a/python/python-psi-impl/resources/messages/PyPsiBundle.properties +++ b/python/python-psi-impl/resources/messages/PyPsiBundle.properties @@ -1073,3 +1073,7 @@ python.find.usages.usage.in.isinstance=Usage in isinstance() python.find.usages.untyped.probable.usage=Untyped (probable) usage python.find.usages.usage.in.import.statement=Usage in import statement +# PyPackagesInspection +INSP.NAME.relative.import=Suspicious relative import +INSP.relative.import.relative.import.outside.package=Relative import outside of a package + diff --git a/python/python-psi-impl/src/com/jetbrains/python/psi/PyUtil.java b/python/python-psi-impl/src/com/jetbrains/python/psi/PyUtil.java index f2695854e5d7..6b57ec92bd2d 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/psi/PyUtil.java +++ b/python/python-psi-impl/src/com/jetbrains/python/psi/PyUtil.java @@ -44,6 +44,7 @@ import com.jetbrains.python.psi.impl.PyTypeProvider; import com.jetbrains.python.psi.resolve.PyResolveContext; import com.jetbrains.python.psi.resolve.QualifiedNameFinder; import com.jetbrains.python.psi.resolve.RatedResolveResult; +import com.jetbrains.python.psi.resolve.ResolveImportUtil; import com.jetbrains.python.psi.stubs.PySetuptoolsNamespaceIndex; import com.jetbrains.python.psi.types.*; import one.util.streamex.StreamEx; @@ -813,10 +814,15 @@ public final class PyUtil { if (directory == null) return true; VirtualFile vFile = directory.getVirtualFile(); if (vFile == null) return true; - ProjectFileIndex fileIndex = ProjectFileIndex.SERVICE.getInstance(directory.getProject()); - return Comparing.equal(fileIndex.getClassRootForFile(vFile), vFile) || - Comparing.equal(fileIndex.getContentRootForFile(vFile), vFile) || - Comparing.equal(fileIndex.getSourceRootForFile(vFile), vFile); + Project project = directory.getProject(); + return isRoot(vFile, project); + } + + public static boolean isRoot(@NotNull VirtualFile directory, @NotNull Project project) { + ProjectFileIndex fileIndex = ProjectFileIndex.SERVICE.getInstance(project); + return Comparing.equal(fileIndex.getClassRootForFile(directory), directory) || + Comparing.equal(fileIndex.getContentRootForFile(directory), directory) || + Comparing.equal(fileIndex.getSourceRootForFile(directory), directory); } /** @@ -964,14 +970,7 @@ public final class PyUtil { * @see PyNames#isIdentifier(String) */ public static boolean isPackage(@NotNull PsiDirectory directory, boolean checkSetupToolsPackages, @Nullable PsiElement anchor) { - for (PyCustomPackageIdentifier customPackageIdentifier : PyCustomPackageIdentifier.EP_NAME.getExtensions()) { - if (customPackageIdentifier.isPackage(directory)) { - return true; - } - } - if (directory.findFile(PyNames.INIT_DOT_PY) != null) { - return true; - } + if (isExplicitPackage(directory)) return true; final LanguageLevel level = anchor != null ? LanguageLevel.forElement(anchor) : LanguageLevel.forElement(directory); if (!level.isPython2()) { return true; @@ -993,6 +992,19 @@ public final class PyUtil { anchor instanceof PsiDirectory && isPackage((PsiDirectory)anchor, location); } + public static boolean isCustomPackage(@NotNull PsiDirectory directory) { + for (PyCustomPackageIdentifier customPackageIdentifier : PyCustomPackageIdentifier.EP_NAME.getExtensions()) { + if (customPackageIdentifier.isPackage(directory)) { + return true; + } + } + return false; + } + + public static boolean isExplicitPackage(@NotNull PsiDirectory directory) { + return isOrdinaryPackage(directory) || isCustomPackage(directory); + } + private static boolean isSetuptoolsNamespacePackage(@NotNull PsiDirectory directory) { final String packagePath = getPackagePath(directory); return packagePath != null && !PySetuptoolsNamespaceIndex.find(packagePath, directory.getProject()).isEmpty(); @@ -1639,6 +1651,10 @@ public final class PyUtil { } } + public static boolean isOrdinaryPackage(@NotNull PsiDirectory directory) { + return directory.findFile(PyNames.INIT_DOT_PY) != null; + } + /** * This helper class allows to collect various information about AST nodes composing {@link PyStringLiteralExpression}. */ diff --git a/python/python-psi-impl/src/com/jetbrains/python/psi/resolve/ResolveImportUtil.java b/python/python-psi-impl/src/com/jetbrains/python/psi/resolve/ResolveImportUtil.java index 68228d062a20..f2c83a7c39bb 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/psi/resolve/ResolveImportUtil.java +++ b/python/python-psi-impl/src/com/jetbrains/python/psi/resolve/ResolveImportUtil.java @@ -8,6 +8,7 @@ import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.util.RecursionManager; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.io.FileUtilRt; +import com.intellij.openapi.util.registry.Registry; import com.intellij.psi.PsiDirectory; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; @@ -35,6 +36,7 @@ import static com.jetbrains.python.psi.FutureFeature.ABSOLUTE_IMPORT; * @author dcheryasov */ public final class ResolveImportUtil { + private ResolveImportUtil() { } @@ -44,6 +46,9 @@ public final class ResolveImportUtil { if (file instanceof PyFile) { final PyFile pyFile = (PyFile)file; if (pyFile.getLanguageLevel().isPy3K()) { + if (foothold.getManager().isInProject(foothold) && Registry.is("python.explicit.namespace.packages")) { + return false; + } return true; } return pyFile.hasImportFromFuture(ABSOLUTE_IMPORT); diff --git a/python/src/META-INF/python-core-common.xml b/python/src/META-INF/python-core-common.xml index baf175fc625f..5cc504c91d43 100644 --- a/python/src/META-INF/python-core-common.xml +++ b/python/src/META-INF/python-core-common.xml @@ -161,6 +161,7 @@ + @@ -324,6 +325,11 @@ + + + + + @@ -361,6 +367,7 @@ + @@ -537,6 +544,9 @@ + + + @@ -632,6 +642,10 @@ + + + + diff --git a/python/src/com/jetbrains/python/PyDirectoryIconProvider.java b/python/src/com/jetbrains/python/PyDirectoryIconProvider.java index f2f73802dfd5..12abe1df5487 100644 --- a/python/src/com/jetbrains/python/PyDirectoryIconProvider.java +++ b/python/src/com/jetbrains/python/PyDirectoryIconProvider.java @@ -38,7 +38,8 @@ public class PyDirectoryIconProvider extends IconProvider { if (element instanceof PsiDirectory) { final PsiDirectory directory = (PsiDirectory)element; // Preserve original icons for excluded directories and source roots - if (!isSpecialDirectory(directory) && isImportableOldStylePackage(directory)) { + if (isSpecialDirectory(directory)) return null; + if (PyUtil.isExplicitPackage(directory)) { return PlatformIcons.PACKAGE_ICON; } } @@ -53,18 +54,4 @@ public class PyDirectoryIconProvider extends IconProvider { final Module module = ModuleUtilCore.findModuleForPsiElement(directory); return module == null || PyUtil.getSourceRoots(module).contains(vFile); } - - private static boolean isImportableOldStylePackage(@NotNull PsiDirectory directory) { - final Collection sourceRoots = PyUtil.getSourceRoots(directory); - for (PsiDirectory dir = directory; dir != null; dir = dir.getParentDirectory()) { - if (sourceRoots.contains(dir.getVirtualFile())) { - return true; - } - if (!PyNames.isIdentifier(dir.getName()) || (dir.findFile(PyNames.INIT_DOT_PY) == null && - dir.findFile(PyNames.INIT_DOT_PYI) == null)) { - return false; - } - } - return false; - } } diff --git a/python/src/com/jetbrains/python/actions/CreatePackageAction.java b/python/src/com/jetbrains/python/actions/CreatePackageAction.java index e1ffbbb6037d..28860b692457 100644 --- a/python/src/com/jetbrains/python/actions/CreatePackageAction.java +++ b/python/src/com/jetbrains/python/actions/CreatePackageAction.java @@ -1,36 +1,43 @@ // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.jetbrains.python.actions; -import com.intellij.ide.IdeBundle; +import com.intellij.application.options.RegistryManager; import com.intellij.ide.IdeView; import com.intellij.ide.actions.CreateDirectoryOrPackageHandler; +import com.intellij.ide.actions.CreateFileFromTemplateDialog; import com.intellij.ide.fileTemplates.FileTemplate; import com.intellij.ide.fileTemplates.FileTemplateManager; import com.intellij.ide.fileTemplates.FileTemplateUtil; -import com.intellij.ide.ui.newItemPopup.NewItemPopupUtil; -import com.intellij.ide.ui.newItemPopup.NewItemSimplePopupPanel; import com.intellij.ide.util.DirectoryChooserUtil; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.actionSystem.LangDataKeys; -import com.intellij.openapi.application.Experiments; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.module.Module; import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.ui.popup.JBPopup; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDirectory; import com.intellij.psi.PsiFileSystemItem; import com.intellij.psi.SmartPointerManager; import com.intellij.psi.SmartPsiElementPointer; +import com.intellij.util.EmptyConsumer; +import com.intellij.util.PlatformIcons; +import com.jetbrains.python.PyBundle; import com.jetbrains.python.PyNames; +import com.jetbrains.python.namespacePackages.PyNamespacePackagesService; +import com.jetbrains.python.namespacePackages.PyNamespacePackagesStatisticsCollector; +import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; -import javax.swing.*; import java.util.function.Consumer; public final class CreatePackageAction extends DumbAwareAction { private static final Logger LOG = Logger.getInstance(CreatePackageAction.class); + private static final @NonNls String NAMESPACE_PACKAGE_TYPE = "Namespace Package"; + private static final @NonNls String ORDINARY_PACKAGE_TYPE = "Package"; + @Override public void actionPerformed(@NotNull AnActionEvent e) { @@ -38,13 +45,62 @@ public final class CreatePackageAction extends DumbAwareAction { if (view == null) { return; } - final Project project = e.getData(CommonDataKeys.PROJECT); - final PsiDirectory directory = DirectoryChooserUtil.getOrChooseDirectory(view); + final Project project = e.getData(CommonDataKeys.PROJECT); + if (project == null) return; + + final PsiDirectory directory = DirectoryChooserUtil.getOrChooseDirectory(view); if (directory == null) return; + + Module module = e.getData(LangDataKeys.MODULE); + if (module == null) return; + + final CreateFileFromTemplateDialog.Builder builder = createDialogBuilder(project); + final SmartPointerManager pointerManager = SmartPointerManager.getInstance(project); final SmartPsiElementPointer directoryPointer = pointerManager.createSmartPsiElementPointer(directory); - final CreateDirectoryOrPackageHandler validator = new CreateDirectoryOrPackageHandler(project, directory, false, ".") { + final CreateDirectoryOrPackageHandler newOrdinaryPackageHandler = getNewOrdinaryPackageHandler(project, directory, directoryPointer); + final CreateDirectoryOrPackageHandler newNamespacePackageHandler = getNewNamespacePackageHandler(project, directory, directoryPointer, module); + + builder.show(PyBundle.message("dialog.title.can.t.create.package"), ORDINARY_PACKAGE_TYPE, + new CreateFileFromTemplateDialog.FileCreator() { + @Override + public PsiDirectory createFile(@NotNull String name, @NotNull String templateName) { + if (templateName.equals(ORDINARY_PACKAGE_TYPE)) { + createNewPackage(name, newOrdinaryPackageHandler, item -> { + if (item != null) { + view.selectElement(item); + } + }); + } + else if (templateName.equals(NAMESPACE_PACKAGE_TYPE)) { + createNewPackage(name, newNamespacePackageHandler, item -> { + if (item != null) { + view.selectElement(item); + } + }); + PyNamespacePackagesStatisticsCollector.Companion.logNamespacePackageCreatedByUser(); + } + return directory; + } + + @Override + public boolean startInWriteAction() { + return false; + } + + @Override + @NotNull + public String getActionName(@NotNull String name, @NotNull String templateName) { + return PyBundle.message("command.name.create.new.package", name); + } + }, EmptyConsumer.getInstance()); + } + + private static CreateDirectoryOrPackageHandler getNewOrdinaryPackageHandler(@NotNull Project project, + @NotNull PsiDirectory directory, + @NotNull SmartPsiElementPointer directoryPointer) { + return new CreateDirectoryOrPackageHandler(project, directory, false, ".") { @Override protected void createDirectories(String subDirName) { super.createDirectories(subDirName); @@ -55,57 +111,69 @@ public final class CreatePackageAction extends DumbAwareAction { } } }; - - Consumer consumer = item -> { - if (item != null) { - view.selectElement(item); - } - }; - - if (Experiments.getInstance().isFeatureEnabled("show.create.new.element.in.popup")) { - JBPopup popup = createLightWeightPopup(validator, consumer); - if (project != null) { - popup.showCenteredInCurrentWindow(project); - } - else { - popup.showInFocusCenter(); - } - } - else { - Messages.showInputDialog(project, IdeBundle.message("prompt.enter.new.package.name"), IdeBundle.message("title.new.package"), Messages.getQuestionIcon(), "", validator); - consumer.accept(validator.getCreatedElement()); - } - } - public static void createInitPyInHierarchy(PsiDirectory created, PsiDirectory ancestor) { + private static CreateDirectoryOrPackageHandler getNewNamespacePackageHandler(@NotNull Project project, + @NotNull PsiDirectory directory, + @NotNull SmartPsiElementPointer directoryPointer, + @NotNull Module module) { + return new CreateDirectoryOrPackageHandler(project, directory, false, ".") { + @Override + protected void createDirectories(String subDirName) { + super.createDirectories(subDirName); + + final PsiDirectory restoredDirectory = directoryPointer.getElement(); + if (restoredDirectory == null) return; + PsiFileSystemItem element = getCreatedElement(); + PsiFileSystemItem lastElement = element; + while (element != null && !element.equals(restoredDirectory)) { + lastElement = element; + element = element.getParent(); + } + if (element == null) return; + + VirtualFile topmostCreatedDirectory = lastElement.getVirtualFile(); + if (topmostCreatedDirectory == null) return; + PyNamespacePackagesService.getInstance(module).toggleMarkingAsNamespacePackage(topmostCreatedDirectory); + } + }; + } + + private static void createNewPackage(@NotNull String name, + @NotNull CreateDirectoryOrPackageHandler createHandler, + @NotNull Consumer consumer) { + if (createHandler.checkInput(name) && createHandler.canClose(name)) { + consumer.accept(createHandler.getCreatedElement()); + } + else { + String errorMessage = createHandler.getErrorText(name); + Messages.showErrorDialog(errorMessage, PyBundle.message("dialog.title.can.t.create.package")); + } + } + + @SuppressWarnings("TestOnlyProblems") + private static CreateFileFromTemplateDialog.Builder createDialogBuilder(@NotNull Project project) { + CreateFileFromTemplateDialog.Builder builder = CreateFileFromTemplateDialog.createDialog(project); + builder + .setTitle(PyBundle.message("dialog.title.new.python.package")) + .addKind(PyBundle.message("new.package.list.item.ordinary.package"), PlatformIcons.PACKAGE_ICON, ORDINARY_PACKAGE_TYPE); + + if (PyNamespacePackagesService.isEnabled() && RegistryManager.getInstance().is("python.create.namespace.package.action")) { + builder.addKind(PyBundle.message("new.package.list.item.namespace.package"), PlatformIcons.PACKAGE_ICON, NAMESPACE_PACKAGE_TYPE); + } + + return builder; + } + + + public static void createInitPyInHierarchy(@NotNull PsiDirectory created, @NotNull PsiDirectory ancestor) { do { createInitPy(created); created = created.getParent(); } while(created != null && !created.equals(ancestor)); } - private static JBPopup createLightWeightPopup(CreateDirectoryOrPackageHandler validator, - Consumer consumer) { - NewItemSimplePopupPanel contentPanel = new NewItemSimplePopupPanel(); - JTextField nameField = contentPanel.getTextField(); - JBPopup popup = NewItemPopupUtil.createNewItemPopup(IdeBundle.message("title.new.package"), contentPanel, nameField); - contentPanel.setApplyAction(event -> { - String name = nameField.getText(); - if (validator.checkInput(name) && validator.canClose(name)) { - popup.closeOk(event); - consumer.accept(validator.getCreatedElement()); - } - else { - String errorMessage = validator.getErrorText(name); - contentPanel.setError(errorMessage); - } - }); - - return popup; - } - - private static void createInitPy(PsiDirectory directory) { + private static void createInitPy(@NotNull PsiDirectory directory) { final FileTemplateManager fileTemplateManager = FileTemplateManager.getInstance(directory.getProject()); final FileTemplate template = fileTemplateManager.getInternalTemplate("Python Script"); if (directory.findFile(PyNames.INIT_DOT_PY) != null) { diff --git a/python/src/com/jetbrains/python/inspections/PyRelativeImportInspection.kt b/python/src/com/jetbrains/python/inspections/PyRelativeImportInspection.kt new file mode 100644 index 000000000000..6ec812052905 --- /dev/null +++ b/python/src/com/jetbrains/python/inspections/PyRelativeImportInspection.kt @@ -0,0 +1,132 @@ +// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.jetbrains.python.inspections + +import com.intellij.codeInspection.LocalInspectionToolSession +import com.intellij.codeInspection.LocalQuickFix +import com.intellij.codeInspection.ProblemDescriptor +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.openapi.command.undo.BasicUndoableAction +import com.intellij.openapi.command.undo.UndoManager +import com.intellij.openapi.module.Module +import com.intellij.openapi.module.ModuleUtilCore +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.PsiDirectory +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.PsiElementVisitor +import com.intellij.psi.util.PsiTreeUtil +import com.jetbrains.python.PyBundle +import com.jetbrains.python.PyPsiBundle +import com.jetbrains.python.PyTokenTypes +import com.jetbrains.python.namespacePackages.PyNamespacePackagesService +import com.jetbrains.python.psi.* +import com.jetbrains.python.psi.impl.PythonLanguageLevelPusher + +class PyRelativeImportInspection : PyInspection() { + override fun buildVisitor(holder: ProblemsHolder, + isOnTheFly: Boolean, + session: LocalInspectionToolSession): PsiElementVisitor { + if (!PyNamespacePackagesService.isEnabled() || + LanguageLevel.forElement(holder.file).isOlderThan(LanguageLevel.PYTHON34)) { + return PsiElementVisitor.EMPTY_VISITOR + } + return Visitor(holder, session) + } + + private class Visitor(holder: ProblemsHolder, session: LocalInspectionToolSession) : PyInspectionVisitor(holder, session) { + override fun visitPyFromImportStatement(node: PyFromImportStatement) { + val directory = node.containingFile?.containingDirectory ?: return + if (node.relativeLevel > 0 && !PyUtil.isExplicitPackage(directory) && !isInsideOrdinaryPackage(directory)) { + handleRelativeImportNotInsidePackage(node, directory) + } + } + + private fun isInsideOrdinaryPackage(directory: PsiDirectory): Boolean { + var curDir: PsiDirectory? = directory + while (curDir != null) { + if (PyUtil.isOrdinaryPackage(curDir)) return true + curDir = curDir.parentDirectory + } + return false + } + + private fun handleRelativeImportNotInsidePackage(node: PyFromImportStatement, directory: PsiDirectory) { + val fixes = mutableListOf() + getMarkAsNamespacePackageQuickFix(directory) ?.let { fixes.add(it) } + if (node.relativeLevel == 1) { + fixes.add(PyChangeToSameDirectoryImportQuickFix()) + } + val message = PyPsiBundle.message("INSP.relative.import.relative.import.outside.package") + registerProblem(node, message, *fixes.toTypedArray()) + } + + private fun getMarkAsNamespacePackageQuickFix(directory: PsiDirectory): PyMarkAsNamespacePackageQuickFix? { + val module = ModuleUtilCore.findModuleForPsiElement(directory) ?: return null + + var curDir: PsiDirectory? = directory + while (curDir != null) { + val virtualFile = curDir.virtualFile + if (PyUtil.isRoot(curDir)) return null + + val parentDir = curDir.parentDirectory + if (parentDir != null && (PyUtil.isRoot(parentDir) || PyUtil.isOrdinaryPackage(parentDir))) { + return PyMarkAsNamespacePackageQuickFix(module, virtualFile) + } + + curDir = parentDir + } + + return null + } + } + + private class PyMarkAsNamespacePackageQuickFix(val module: Module, val directory: VirtualFile) : LocalQuickFix { + override fun getFamilyName(): String = PyBundle.message("QFIX.mark.as.namespace.package", directory.name) + + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + val document = PsiDocumentManager.getInstance(project).getDocument(descriptor.psiElement.containingFile) + val undoableAction = object: BasicUndoableAction(document) { + override fun undo() { + PyNamespacePackagesService.getInstance(module).toggleMarkingAsNamespacePackage(directory) + } + + override fun redo() { + PyNamespacePackagesService.getInstance(module).toggleMarkingAsNamespacePackage(directory) + } + } + undoableAction.redo() + UndoManager.getInstance(project).undoableActionPerformed(undoableAction) + } + } + + private class PyChangeToSameDirectoryImportQuickFix : LocalQuickFix { + override fun getFamilyName(): String = PyBundle.message("QFIX.change.to.same.directory.import") + + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + val oldImport = descriptor.psiElement as? PyFromImportStatement ?: return + assert(oldImport.relativeLevel == 1) + val qualifier = oldImport.importSource + if (qualifier != null) { + val possibleDot = PsiTreeUtil.prevVisibleLeaf(qualifier) + assert(possibleDot != null && possibleDot.node.elementType == PyTokenTypes.DOT) + possibleDot?.delete() + } + else { + replaceByImportStatements(oldImport) + } + } + + private fun replaceByImportStatements(oldImport: PyFromImportStatement) { + val project = oldImport.project + val generator = PyElementGenerator.getInstance(project) + val names = oldImport.importElements.map { it.text } + if (names.isEmpty()) return + val langLevel = LanguageLevel.forElement(oldImport) + for (name in names.reversed()) { + val newImport = generator.createImportStatement(langLevel, name, null) + oldImport.parent.addAfter(newImport, oldImport) + } + oldImport.delete() + } + } +} \ No newline at end of file diff --git a/python/src/com/jetbrains/python/module/PyContentEntriesEditor.java b/python/src/com/jetbrains/python/module/PyContentEntriesEditor.java index 02ad56880453..192d45b1ae0e 100644 --- a/python/src/com/jetbrains/python/module/PyContentEntriesEditor.java +++ b/python/src/com/jetbrains/python/module/PyContentEntriesEditor.java @@ -16,6 +16,7 @@ import com.intellij.openapi.util.Disposer; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.pointers.VirtualFilePointer; import com.intellij.util.EventDispatcher; +import com.intellij.util.PlatformIcons; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.MultiMap; import org.jetbrains.annotations.NotNull; @@ -30,6 +31,7 @@ import java.awt.*; import java.util.Collection; import java.util.List; import java.util.Objects; +import com.jetbrains.python.namespacePackages.PyNamespacePackagesService; public class PyContentEntriesEditor extends CommonContentEntriesEditor { private final List myRootTypeProviders; @@ -280,7 +282,14 @@ public class PyContentEntriesEditor extends CommonContentEntriesEditor { return provider.getIcon(); } } - return super.updateIcon(entry, file, originalIcon); + // JavaModuleSourceRootEditHandler gives every directory under a source root a package icon. + // Since we use the same icon for explicit namespace package "roots", we forcibly replace icons + // for other "false" packages with the one for a plain directory to avoid confusion. + Icon defaultIcon = super.updateIcon(entry, file, originalIcon); + if (defaultIcon == PlatformIcons.PACKAGE_ICON) { + return PlatformIcons.FOLDER_ICON; + } + return defaultIcon; } }; } diff --git a/python/src/com/jetbrains/python/namespacePackages/PyMarkAsNamespacePackageAction.kt b/python/src/com/jetbrains/python/namespacePackages/PyMarkAsNamespacePackageAction.kt new file mode 100644 index 000000000000..e64319e4d346 --- /dev/null +++ b/python/src/com/jetbrains/python/namespacePackages/PyMarkAsNamespacePackageAction.kt @@ -0,0 +1,47 @@ +// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.jetbrains.python.namespacePackages + +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.actionSystem.CommonDataKeys +import com.intellij.openapi.actionSystem.LangDataKeys +import com.intellij.util.PlatformIcons +import com.jetbrains.python.PyBundle + +class PyMarkAsNamespacePackageAction : AnAction() { + override fun update(e: AnActionEvent) { + val presentation = e.presentation + presentation.isEnabled = false + presentation.isVisible = false + + val virtualFiles = e.getData(CommonDataKeys.VIRTUAL_FILE_ARRAY) ?: return + if (virtualFiles.isEmpty()) return + + val module = e.getData(LangDataKeys.MODULE) ?: return + val service = PyNamespacePackagesService.getInstance(module) + if (!PyNamespacePackagesService.isEnabled()) return + presentation.isVisible = true + + presentation.icon = PlatformIcons.PACKAGE_ICON + when { + virtualFiles.all { service.canBeMarked(it) } -> { + presentation.isEnabled = true + presentation.text = PyBundle.message("python.namespace.package.folder") + } + virtualFiles.all { service.isMarked(it) } -> { + presentation.isEnabled = true + presentation.text = PyBundle.message("python.unmark.as.namespace.package") + } + else -> { + presentation.isEnabled = false + } + } + } + + override fun actionPerformed(e: AnActionEvent) { + val module = e.getData(LangDataKeys.MODULE) ?: return + val virtualFiles = e.getData(CommonDataKeys.VIRTUAL_FILE_ARRAY) ?: return + val service = PyNamespacePackagesService.getInstance(module) + virtualFiles.forEach { service.toggleMarkingAsNamespacePackage(it) } + } +} \ No newline at end of file diff --git a/python/src/com/jetbrains/python/namespacePackages/PyNamespacePackageCustomIdentifier.kt b/python/src/com/jetbrains/python/namespacePackages/PyNamespacePackageCustomIdentifier.kt new file mode 100644 index 000000000000..bb08b89c7583 --- /dev/null +++ b/python/src/com/jetbrains/python/namespacePackages/PyNamespacePackageCustomIdentifier.kt @@ -0,0 +1,26 @@ +// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.jetbrains.python.namespacePackages + +import com.intellij.openapi.extensions.ExtensionNotApplicableException +import com.intellij.openapi.module.ModuleUtilCore +import com.intellij.openapi.util.registry.Registry +import com.intellij.psi.PsiDirectory +import com.intellij.psi.PsiFile +import com.jetbrains.python.psi.PyCustomPackageIdentifier + +class PyNamespacePackageCustomIdentifier : PyCustomPackageIdentifier { + init { + if (!Registry.`is`("python.explicit.namespace.packages")) { + throw ExtensionNotApplicableException.INSTANCE + } + } + + override fun isPackage(directory: PsiDirectory?): Boolean { + if (directory == null) return false + if (!Registry.`is`("python.explicit.namespace.packages")) return false + val module = ModuleUtilCore.findModuleForPsiElement(directory) ?: return false + return PyNamespacePackagesService.getInstance(module).isNamespacePackage(directory.virtualFile) + } + + override fun isPackageFile(file: PsiFile): Boolean = false +} \ No newline at end of file diff --git a/python/src/com/jetbrains/python/namespacePackages/PyNamespacePackageRootProvider.kt b/python/src/com/jetbrains/python/namespacePackages/PyNamespacePackageRootProvider.kt new file mode 100644 index 000000000000..cbc801656c3c --- /dev/null +++ b/python/src/com/jetbrains/python/namespacePackages/PyNamespacePackageRootProvider.kt @@ -0,0 +1,95 @@ +// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.jetbrains.python.namespacePackages + +import com.intellij.openapi.Disposable +import com.intellij.openapi.extensions.ExtensionNotApplicableException +import com.intellij.openapi.module.Module +import com.intellij.openapi.roots.ContentEntry +import com.intellij.openapi.roots.ModifiableRootModel +import com.intellij.openapi.roots.ui.configuration.actions.ContentEntryEditingAction +import com.intellij.openapi.util.Comparing +import com.intellij.openapi.util.registry.Registry +import com.intellij.openapi.vfs.VfsUtilCore +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.openapi.vfs.pointers.VirtualFilePointer +import com.intellij.openapi.vfs.pointers.VirtualFilePointerManager +import com.intellij.ui.JBColor +import com.intellij.util.PlatformIcons +import com.intellij.util.containers.MultiMap +import com.jetbrains.python.PyBundle +import com.jetbrains.python.module.PyContentEntriesEditor +import com.jetbrains.python.module.PyRootTypeProvider +import java.awt.Color +import javax.swing.Icon +import javax.swing.JTree + +class PyNamespacePackageRootProvider: PyRootTypeProvider() { + private val myNamespacePackages = MultiMap() + + init { + if (!Registry.`is`("python.explicit.namespace.packages")) { + throw ExtensionNotApplicableException.INSTANCE + } + } + + override fun reset(disposable: Disposable, editor: PyContentEntriesEditor, module: Module) { + myNamespacePackages.clear() + val namespacePackages = PyNamespacePackagesService.getInstance(module).namespacePackageFoldersVirtualFiles + for (namespacePackage in namespacePackages) { + val contentEntry = findContentEntryForFile(namespacePackage, editor) ?: continue + val pointer = VirtualFilePointerManager.getInstance().create(namespacePackage, disposable, DUMMY_LISTENER) + myNamespacePackages.putValue(contentEntry, pointer) + } + } + + override fun apply(module: Module) { + val instance = PyNamespacePackagesService.getInstance(module) + val currentNamespacePackages = getCurrentNamespacePackages() + if (!Comparing.haveEqualElements(instance.namespacePackageFoldersVirtualFiles, currentNamespacePackages)) { + instance.namespacePackageFoldersVirtualFiles = currentNamespacePackages + PyNamespacePackagesStatisticsCollector.logApplyInNamespacePackageRootProvider() + } + } + + override fun isModified(module: Module): Boolean = + !Comparing.haveEqualElements(PyNamespacePackagesService.getInstance(module).namespacePackageFoldersVirtualFiles, + getCurrentNamespacePackages()) + + override fun getRoots(): MultiMap = myNamespacePackages + + override fun getIcon(): Icon { + return PlatformIcons.PACKAGE_ICON + } + + override fun getName(): String { + return PyBundle.message("python.namespace.packages.name") + } + + override fun getDescription(): String { + return PyBundle.message("python.namespace.packages.description") + } + + override fun getColor(): Color { + return EASTERN_BLUE + } + + override fun createRootEntryEditingAction(tree: JTree?, + disposable: Disposable?, + editor: PyContentEntriesEditor?, + model: ModifiableRootModel?): ContentEntryEditingAction { + return RootEntryEditingAction(tree, disposable, editor, model) + } + + private fun getCurrentNamespacePackages(): List = myNamespacePackages.values().mapNotNull { it.file } + + companion object { + private fun findContentEntryForFile(virtualFile: VirtualFile, editor: PyContentEntriesEditor): ContentEntry? { + return editor.contentEntries.find { + val possibleContentEntry = it.file + possibleContentEntry != null && VfsUtilCore.isAncestor(possibleContentEntry, virtualFile, false) + } + } + + private val EASTERN_BLUE: Color = JBColor(0x29A5AD, 0x29A5AD) + } +} \ No newline at end of file diff --git a/python/src/com/jetbrains/python/namespacePackages/PyNamespacePackagesService.java b/python/src/com/jetbrains/python/namespacePackages/PyNamespacePackagesService.java new file mode 100644 index 000000000000..7e9d9538e3bb --- /dev/null +++ b/python/src/com/jetbrains/python/namespacePackages/PyNamespacePackagesService.java @@ -0,0 +1,177 @@ +// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.jetbrains.python.namespacePackages; + +import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; +import com.intellij.ide.projectView.ProjectView; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.components.PersistentStateComponent; +import com.intellij.openapi.components.State; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.registry.Registry; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiDirectory; +import com.intellij.psi.PsiManager; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.xmlb.XmlSerializerUtil; +import com.intellij.util.xmlb.annotations.Transient; +import com.jetbrains.python.psi.LanguageLevel; +import com.jetbrains.python.psi.PyUtil; +import com.jetbrains.python.psi.impl.PythonLanguageLevelPusher; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@State(name = "PyNamespacePackagesService") +public class PyNamespacePackagesService implements PersistentStateComponent { + private final List myNamespacePackageFolders = new ArrayList<>(); + private final Module myModule; + + public PyNamespacePackagesService() { + myModule = null; + } + + public PyNamespacePackagesService(@Nullable Module module) { + myModule = module; + } + + public static @NotNull PyNamespacePackagesService getInstance(@NotNull Module module) { + return module.getService(PyNamespacePackagesService.class); + } + + public @NotNull List getNamespacePackageFolders() { + removeInvalidNamespacePackageFolders(); + return Collections.unmodifiableList(ContainerUtil.map(myNamespacePackageFolders, it -> it.getPath())); + } + + @Transient + public @NotNull List getNamespacePackageFoldersVirtualFiles() { + removeInvalidNamespacePackageFolders(); + return Collections.unmodifiableList(myNamespacePackageFolders); + } + + public void setNamespacePackageFolders(@NotNull List folders) { + myNamespacePackageFolders.clear(); + for (String path: folders) { + VirtualFile virtualFile = LocalFileSystem.getInstance().findFileByPath(path); + if (virtualFile != null) { + myNamespacePackageFolders.add(virtualFile); + } + } + } + + @Transient + public void setNamespacePackageFoldersVirtualFiles(@NotNull List folders) { + myNamespacePackageFolders.clear(); + myNamespacePackageFolders.addAll(folders); + } + + public void toggleMarkingAsNamespacePackage(@NotNull VirtualFile directory) { + if (!directory.isDirectory()) return; + + if (canBeMarked(directory)) { + myNamespacePackageFolders.add(directory); + PyNamespacePackagesStatisticsCollector.Companion.logToggleMarkingAsNamespacePackage(true); + } + else if (isMarked(directory)) { + myNamespacePackageFolders.remove(directory); + PyNamespacePackagesStatisticsCollector.Companion.logToggleMarkingAsNamespacePackage(false); + } + else { + throw new IllegalStateException("Can't toggle namespace package state for: " + directory.getName()); + } + + refreshView(); + } + + public boolean canBeMarked(@NotNull VirtualFile virtualFile) { + if (myModule == null) return false; + Project project = myModule.getProject(); + + if (PythonLanguageLevelPusher.getLanguageLevelForVirtualFile(project, virtualFile).isOlderThan(LanguageLevel.PYTHON34)) return false; + if (PyUtil.isRoot(virtualFile, project)) return false; + if (!isInProject(virtualFile, project)) return false; + PsiDirectory psiDirectory = PsiManager.getInstance(project).findDirectory(virtualFile); + if (psiDirectory != null && PyUtil.isOrdinaryPackage(psiDirectory)) return false; + + VirtualFile curDir = virtualFile; + while (curDir != null) { + if (!curDir.isDirectory()) return false; + if (myNamespacePackageFolders.contains(curDir)) return false; + if (PyUtil.isRoot(curDir, project)) break; + psiDirectory = PsiManager.getInstance(myModule.getProject()).findDirectory(curDir); + if (psiDirectory != null && PyUtil.isOrdinaryPackage(psiDirectory)) break; + curDir = curDir.getParent(); + } + + return true; + } + + @Nullable + @Override + public PyNamespacePackagesService getState() { + return this; + } + + @Override + public void loadState(@NotNull PyNamespacePackagesService state) { + XmlSerializerUtil.copyBean(state, this); + } + + public boolean isMarked(@NotNull VirtualFile dir) { + return myNamespacePackageFolders.contains(dir); + } + + public boolean isNamespacePackage(VirtualFile directory) { + if (myModule != null) { + PsiDirectory psiDirectory = PsiManager.getInstance(myModule.getProject()).findDirectory(directory); + if (psiDirectory != null && PyUtil.isOrdinaryPackage(psiDirectory)) return false; + } + VirtualFile curDir = directory; + while (curDir != null) { + if (isMarked(curDir)) return true; + if (myModule != null) { + if (PyUtil.isRoot(directory, myModule.getProject())) break; + PsiDirectory psiDirectory = PsiManager.getInstance(myModule.getProject()).findDirectory(curDir); + if (psiDirectory != null && PyUtil.isOrdinaryPackage(psiDirectory)) break; + } + curDir = curDir.getParent(); + } + return false; + } + + public static boolean isEnabled() { + return Registry.is("python.explicit.namespace.packages"); + } + + private void removeInvalidNamespacePackageFolders() { + myNamespacePackageFolders.removeIf(it -> it == null || !it.isValid()); + } + + private static boolean isInProject(@NotNull VirtualFile virtualFile, @NotNull Project project) { + PsiManager psiManager = PsiManager.getInstance(project); + PsiDirectory psiDirectory = psiManager.findDirectory(virtualFile); + if (psiDirectory == null) return false; + if (!psiManager.isInProject(psiDirectory)) return false; + return true; + } + + private void refreshView() { + if (!ApplicationManager.getApplication().isWriteThread()) return; + if (myModule == null) return; + Project project = myModule.getProject(); + ProjectView.getInstance(project).refresh(); + PsiManager.getInstance(project).dropPsiCaches(); + DaemonCodeAnalyzer.getInstance(project).restart(); + } + + @TestOnly + public void resetAllNamespacePackages() { + myNamespacePackageFolders.clear(); + } +} diff --git a/python/src/com/jetbrains/python/namespacePackages/PyNamespacePackagesStatisticsCollector.kt b/python/src/com/jetbrains/python/namespacePackages/PyNamespacePackagesStatisticsCollector.kt new file mode 100644 index 000000000000..8c70c67c4e54 --- /dev/null +++ b/python/src/com/jetbrains/python/namespacePackages/PyNamespacePackagesStatisticsCollector.kt @@ -0,0 +1,32 @@ +// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.jetbrains.python.namespacePackages + +import com.intellij.internal.statistic.eventLog.EventLogGroup +import com.intellij.internal.statistic.eventLog.events.EventFields +import com.intellij.internal.statistic.service.fus.collectors.CounterUsagesCollector + +class PyNamespacePackagesStatisticsCollector : CounterUsagesCollector() { + override fun getGroup(): EventLogGroup = GROUP + + companion object { + fun logNamespacePackageCreatedByUser() { + namespacePackageCreatedEvent.log() + } + + fun logToggleMarkingAsNamespacePackage(mark: Boolean) { + toggleMarkingAsNamespacePackageEvent.log(mark) + } + + fun logApplyInNamespacePackageRootProvider() { + namespacePackagesEditedViaProjectStructure.log() + } + + private val GROUP = EventLogGroup("python.namespace.packages.events", 1) + + private val namespacePackageCreatedEvent = GROUP.registerEvent("namespace.package.created") + + private val toggleMarkingAsNamespacePackageEvent = GROUP.registerEvent("namespace.package.mark.or.unmark", EventFields.Boolean("is_mark")) + + private val namespacePackagesEditedViaProjectStructure = GROUP.registerEvent("namespace.package.apply.in.root.provider") + } +} \ No newline at end of file diff --git a/python/testData/actions/MarkAsNamespacePackage/NestedNamespacePackage/namespacePackage/nestedNamespacePackage/mod.py b/python/testData/actions/MarkAsNamespacePackage/NestedNamespacePackage/namespacePackage/nestedNamespacePackage/mod.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/actions/MarkAsNamespacePackage/OrdinaryPackage/ordinaryPackage/__init__.py b/python/testData/actions/MarkAsNamespacePackage/OrdinaryPackage/ordinaryPackage/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/actions/MarkAsNamespacePackage/PlainDirectory/plainDirectory/script.py b/python/testData/actions/MarkAsNamespacePackage/PlainDirectory/plainDirectory/script.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/actions/MarkAsNamespacePackage/PlainDirectoryInOrdinaryPackage/ordinaryPackage/__init__.py b/python/testData/actions/MarkAsNamespacePackage/PlainDirectoryInOrdinaryPackage/ordinaryPackage/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/actions/MarkAsNamespacePackage/PlainDirectoryInOrdinaryPackage/ordinaryPackage/nestedPlainDirectory/script.py b/python/testData/actions/MarkAsNamespacePackage/PlainDirectoryInOrdinaryPackage/ordinaryPackage/nestedPlainDirectory/script.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/inspections/PyArgumentListInspection/ImportedTypedDict/a.py b/python/testData/inspections/PyArgumentListInspection/ImportedTypedDict/a.py index 2246c390a5dc..dd0097e8a493 100644 --- a/python/testData/inspections/PyArgumentListInspection/ImportedTypedDict/a.py +++ b/python/testData/inspections/PyArgumentListInspection/ImportedTypedDict/a.py @@ -1,4 +1,4 @@ -from .test import Test1, Base +from test import Test1, Base b = Base(a=2) t = Test1(a=2) diff --git a/python/testData/inspections/PyRelativeImportInspection/NamespacePackageDottedImportNoInspection/namespacePackage/mod.py b/python/testData/inspections/PyRelativeImportInspection/NamespacePackageDottedImportNoInspection/namespacePackage/mod.py new file mode 100644 index 000000000000..845bf4d4daab --- /dev/null +++ b/python/testData/inspections/PyRelativeImportInspection/NamespacePackageDottedImportNoInspection/namespacePackage/mod.py @@ -0,0 +1,2 @@ +from util import foo +foo() \ No newline at end of file diff --git a/python/testData/inspections/PyRelativeImportInspection/NamespacePackageDottedImportNoInspection/namespacePackage/util.py b/python/testData/inspections/PyRelativeImportInspection/NamespacePackageDottedImportNoInspection/namespacePackage/util.py new file mode 100644 index 000000000000..b8df4015bef1 --- /dev/null +++ b/python/testData/inspections/PyRelativeImportInspection/NamespacePackageDottedImportNoInspection/namespacePackage/util.py @@ -0,0 +1,2 @@ +def foo(): + print("ok") \ No newline at end of file diff --git a/python/testData/inspections/PyRelativeImportInspection/NamespacePackageSameDirectoryImportNoInspection/namespacePackage/mod.py b/python/testData/inspections/PyRelativeImportInspection/NamespacePackageSameDirectoryImportNoInspection/namespacePackage/mod.py new file mode 100644 index 000000000000..94533a60018b --- /dev/null +++ b/python/testData/inspections/PyRelativeImportInspection/NamespacePackageSameDirectoryImportNoInspection/namespacePackage/mod.py @@ -0,0 +1,2 @@ +from .util import foo +foo() \ No newline at end of file diff --git a/python/testData/inspections/PyRelativeImportInspection/NamespacePackageSameDirectoryImportNoInspection/namespacePackage/util.py b/python/testData/inspections/PyRelativeImportInspection/NamespacePackageSameDirectoryImportNoInspection/namespacePackage/util.py new file mode 100644 index 000000000000..b8df4015bef1 --- /dev/null +++ b/python/testData/inspections/PyRelativeImportInspection/NamespacePackageSameDirectoryImportNoInspection/namespacePackage/util.py @@ -0,0 +1,2 @@ +def foo(): + print("ok") \ No newline at end of file diff --git a/python/testData/inspections/PyRelativeImportInspection/NamespacePackageSameDirectoryImportRegistryOffNoInspection/namespacePackage/mod.py b/python/testData/inspections/PyRelativeImportInspection/NamespacePackageSameDirectoryImportRegistryOffNoInspection/namespacePackage/mod.py new file mode 100644 index 000000000000..845bf4d4daab --- /dev/null +++ b/python/testData/inspections/PyRelativeImportInspection/NamespacePackageSameDirectoryImportRegistryOffNoInspection/namespacePackage/mod.py @@ -0,0 +1,2 @@ +from util import foo +foo() \ No newline at end of file diff --git a/python/testData/inspections/PyRelativeImportInspection/NamespacePackageSameDirectoryImportRegistryOffNoInspection/namespacePackage/util.py b/python/testData/inspections/PyRelativeImportInspection/NamespacePackageSameDirectoryImportRegistryOffNoInspection/namespacePackage/util.py new file mode 100644 index 000000000000..b8df4015bef1 --- /dev/null +++ b/python/testData/inspections/PyRelativeImportInspection/NamespacePackageSameDirectoryImportRegistryOffNoInspection/namespacePackage/util.py @@ -0,0 +1,2 @@ +def foo(): + print("ok") \ No newline at end of file diff --git a/python/testData/inspections/PyRelativeImportInspection/NestedNamespacePackageSameDirectoryImportRegistryOffNoInspection/namespacePackage/nestedNamespacePackage/mod.py b/python/testData/inspections/PyRelativeImportInspection/NestedNamespacePackageSameDirectoryImportRegistryOffNoInspection/namespacePackage/nestedNamespacePackage/mod.py new file mode 100644 index 000000000000..845bf4d4daab --- /dev/null +++ b/python/testData/inspections/PyRelativeImportInspection/NestedNamespacePackageSameDirectoryImportRegistryOffNoInspection/namespacePackage/nestedNamespacePackage/mod.py @@ -0,0 +1,2 @@ +from util import foo +foo() \ No newline at end of file diff --git a/python/testData/inspections/PyRelativeImportInspection/NestedNamespacePackageSameDirectoryImportRegistryOffNoInspection/namespacePackage/nestedNamespacePackage/util.py b/python/testData/inspections/PyRelativeImportInspection/NestedNamespacePackageSameDirectoryImportRegistryOffNoInspection/namespacePackage/nestedNamespacePackage/util.py new file mode 100644 index 000000000000..b8df4015bef1 --- /dev/null +++ b/python/testData/inspections/PyRelativeImportInspection/NestedNamespacePackageSameDirectoryImportRegistryOffNoInspection/namespacePackage/nestedNamespacePackage/util.py @@ -0,0 +1,2 @@ +def foo(): + print("ok") \ No newline at end of file diff --git a/python/testData/inspections/PyRelativeImportInspection/NestedPlainDirectoryDottedImportMarkDirectory/plainDirectory/nestedPlainDirectory/dottedImport.py b/python/testData/inspections/PyRelativeImportInspection/NestedPlainDirectoryDottedImportMarkDirectory/plainDirectory/nestedPlainDirectory/dottedImport.py new file mode 100644 index 000000000000..e9a2ed34bf74 --- /dev/null +++ b/python/testData/inspections/PyRelativeImportInspection/NestedPlainDirectoryDottedImportMarkDirectory/plainDirectory/nestedPlainDirectory/dottedImport.py @@ -0,0 +1 @@ +from .util import foo \ No newline at end of file diff --git a/python/testData/inspections/PyRelativeImportInspection/NestedPlainDirectoryDottedImportMarkDirectory/plainDirectory/nestedPlainDirectory/dottedImport_after.py b/python/testData/inspections/PyRelativeImportInspection/NestedPlainDirectoryDottedImportMarkDirectory/plainDirectory/nestedPlainDirectory/dottedImport_after.py new file mode 100644 index 000000000000..f3fa9f3e05f9 --- /dev/null +++ b/python/testData/inspections/PyRelativeImportInspection/NestedPlainDirectoryDottedImportMarkDirectory/plainDirectory/nestedPlainDirectory/dottedImport_after.py @@ -0,0 +1 @@ +from .util import foo \ No newline at end of file diff --git a/python/testData/inspections/PyRelativeImportInspection/NestedPlainDirectoryDottedImportMarkDirectory/plainDirectory/nestedPlainDirectory/util.py b/python/testData/inspections/PyRelativeImportInspection/NestedPlainDirectoryDottedImportMarkDirectory/plainDirectory/nestedPlainDirectory/util.py new file mode 100644 index 000000000000..cb332504aa2e --- /dev/null +++ b/python/testData/inspections/PyRelativeImportInspection/NestedPlainDirectoryDottedImportMarkDirectory/plainDirectory/nestedPlainDirectory/util.py @@ -0,0 +1,2 @@ +def foo(): + print('ok') \ No newline at end of file diff --git a/python/testData/inspections/PyRelativeImportInspection/NotMarkedNamespacePackageInsidePackageSameDirectoryImportNoInspection/namespacePackage/ordinaryPackage/__init__.py b/python/testData/inspections/PyRelativeImportInspection/NotMarkedNamespacePackageInsidePackageSameDirectoryImportNoInspection/namespacePackage/ordinaryPackage/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/inspections/PyRelativeImportInspection/NotMarkedNamespacePackageInsidePackageSameDirectoryImportNoInspection/namespacePackage/ordinaryPackage/nestedNamespacePackage/mod.py b/python/testData/inspections/PyRelativeImportInspection/NotMarkedNamespacePackageInsidePackageSameDirectoryImportNoInspection/namespacePackage/ordinaryPackage/nestedNamespacePackage/mod.py new file mode 100644 index 000000000000..845bf4d4daab --- /dev/null +++ b/python/testData/inspections/PyRelativeImportInspection/NotMarkedNamespacePackageInsidePackageSameDirectoryImportNoInspection/namespacePackage/ordinaryPackage/nestedNamespacePackage/mod.py @@ -0,0 +1,2 @@ +from util import foo +foo() \ No newline at end of file diff --git a/python/testData/inspections/PyRelativeImportInspection/NotMarkedNamespacePackageInsidePackageSameDirectoryImportNoInspection/namespacePackage/ordinaryPackage/nestedNamespacePackage/util.py b/python/testData/inspections/PyRelativeImportInspection/NotMarkedNamespacePackageInsidePackageSameDirectoryImportNoInspection/namespacePackage/ordinaryPackage/nestedNamespacePackage/util.py new file mode 100644 index 000000000000..cb332504aa2e --- /dev/null +++ b/python/testData/inspections/PyRelativeImportInspection/NotMarkedNamespacePackageInsidePackageSameDirectoryImportNoInspection/namespacePackage/ordinaryPackage/nestedNamespacePackage/util.py @@ -0,0 +1,2 @@ +def foo(): + print('ok') \ No newline at end of file diff --git a/python/testData/inspections/PyRelativeImportInspection/OrdinaryPackageDottedImportNoInspection/ordinaryPackage/__init__.py b/python/testData/inspections/PyRelativeImportInspection/OrdinaryPackageDottedImportNoInspection/ordinaryPackage/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/inspections/PyRelativeImportInspection/OrdinaryPackageDottedImportNoInspection/ordinaryPackage/script.py b/python/testData/inspections/PyRelativeImportInspection/OrdinaryPackageDottedImportNoInspection/ordinaryPackage/script.py new file mode 100644 index 000000000000..f3fa9f3e05f9 --- /dev/null +++ b/python/testData/inspections/PyRelativeImportInspection/OrdinaryPackageDottedImportNoInspection/ordinaryPackage/script.py @@ -0,0 +1 @@ +from .util import foo \ No newline at end of file diff --git a/python/testData/inspections/PyRelativeImportInspection/OrdinaryPackageDottedImportNoInspection/ordinaryPackage/util.py b/python/testData/inspections/PyRelativeImportInspection/OrdinaryPackageDottedImportNoInspection/ordinaryPackage/util.py new file mode 100644 index 000000000000..cb332504aa2e --- /dev/null +++ b/python/testData/inspections/PyRelativeImportInspection/OrdinaryPackageDottedImportNoInspection/ordinaryPackage/util.py @@ -0,0 +1,2 @@ +def foo(): + print('ok') \ No newline at end of file diff --git a/python/testData/inspections/PyRelativeImportInspection/OrdinaryPackageSameDirectoryImportNoInspection/ordinaryPackage/__init__.py b/python/testData/inspections/PyRelativeImportInspection/OrdinaryPackageSameDirectoryImportNoInspection/ordinaryPackage/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/inspections/PyRelativeImportInspection/OrdinaryPackageSameDirectoryImportNoInspection/ordinaryPackage/script.py b/python/testData/inspections/PyRelativeImportInspection/OrdinaryPackageSameDirectoryImportNoInspection/ordinaryPackage/script.py new file mode 100644 index 000000000000..1882243a642a --- /dev/null +++ b/python/testData/inspections/PyRelativeImportInspection/OrdinaryPackageSameDirectoryImportNoInspection/ordinaryPackage/script.py @@ -0,0 +1 @@ +from util import foo \ No newline at end of file diff --git a/python/testData/inspections/PyRelativeImportInspection/OrdinaryPackageSameDirectoryImportNoInspection/ordinaryPackage/util.py b/python/testData/inspections/PyRelativeImportInspection/OrdinaryPackageSameDirectoryImportNoInspection/ordinaryPackage/util.py new file mode 100644 index 000000000000..cb332504aa2e --- /dev/null +++ b/python/testData/inspections/PyRelativeImportInspection/OrdinaryPackageSameDirectoryImportNoInspection/ordinaryPackage/util.py @@ -0,0 +1,2 @@ +def foo(): + print('ok') \ No newline at end of file diff --git a/python/testData/inspections/PyRelativeImportInspection/PlainDirectoryDottedImportDeleteDot/plainDirectory/dottedImport.py b/python/testData/inspections/PyRelativeImportInspection/PlainDirectoryDottedImportDeleteDot/plainDirectory/dottedImport.py new file mode 100644 index 000000000000..e9a2ed34bf74 --- /dev/null +++ b/python/testData/inspections/PyRelativeImportInspection/PlainDirectoryDottedImportDeleteDot/plainDirectory/dottedImport.py @@ -0,0 +1 @@ +from .util import foo \ No newline at end of file diff --git a/python/testData/inspections/PyRelativeImportInspection/PlainDirectoryDottedImportDeleteDot/plainDirectory/dottedImport_after.py b/python/testData/inspections/PyRelativeImportInspection/PlainDirectoryDottedImportDeleteDot/plainDirectory/dottedImport_after.py new file mode 100644 index 000000000000..1882243a642a --- /dev/null +++ b/python/testData/inspections/PyRelativeImportInspection/PlainDirectoryDottedImportDeleteDot/plainDirectory/dottedImport_after.py @@ -0,0 +1 @@ +from util import foo \ No newline at end of file diff --git a/python/testData/inspections/PyRelativeImportInspection/PlainDirectoryDottedImportDeleteDot/plainDirectory/util.py b/python/testData/inspections/PyRelativeImportInspection/PlainDirectoryDottedImportDeleteDot/plainDirectory/util.py new file mode 100644 index 000000000000..cb332504aa2e --- /dev/null +++ b/python/testData/inspections/PyRelativeImportInspection/PlainDirectoryDottedImportDeleteDot/plainDirectory/util.py @@ -0,0 +1,2 @@ +def foo(): + print('ok') \ No newline at end of file diff --git a/python/testData/inspections/PyRelativeImportInspection/PlainDirectoryDottedImportMarkDirectory/plainDirectory/dottedImport.py b/python/testData/inspections/PyRelativeImportInspection/PlainDirectoryDottedImportMarkDirectory/plainDirectory/dottedImport.py new file mode 100644 index 000000000000..e9a2ed34bf74 --- /dev/null +++ b/python/testData/inspections/PyRelativeImportInspection/PlainDirectoryDottedImportMarkDirectory/plainDirectory/dottedImport.py @@ -0,0 +1 @@ +from .util import foo \ No newline at end of file diff --git a/python/testData/inspections/PyRelativeImportInspection/PlainDirectoryDottedImportMarkDirectory/plainDirectory/dottedImport_after.py b/python/testData/inspections/PyRelativeImportInspection/PlainDirectoryDottedImportMarkDirectory/plainDirectory/dottedImport_after.py new file mode 100644 index 000000000000..f3fa9f3e05f9 --- /dev/null +++ b/python/testData/inspections/PyRelativeImportInspection/PlainDirectoryDottedImportMarkDirectory/plainDirectory/dottedImport_after.py @@ -0,0 +1 @@ +from .util import foo \ No newline at end of file diff --git a/python/testData/inspections/PyRelativeImportInspection/PlainDirectoryDottedImportMarkDirectory/plainDirectory/util.py b/python/testData/inspections/PyRelativeImportInspection/PlainDirectoryDottedImportMarkDirectory/plainDirectory/util.py new file mode 100644 index 000000000000..cb332504aa2e --- /dev/null +++ b/python/testData/inspections/PyRelativeImportInspection/PlainDirectoryDottedImportMarkDirectory/plainDirectory/util.py @@ -0,0 +1,2 @@ +def foo(): + print('ok') \ No newline at end of file diff --git a/python/testData/inspections/PyRelativeImportInspection/PlainDirectoryDottedImportRegistryOffNoInspection/plainDirectory/dottedImport.py b/python/testData/inspections/PyRelativeImportInspection/PlainDirectoryDottedImportRegistryOffNoInspection/plainDirectory/dottedImport.py new file mode 100644 index 000000000000..f3fa9f3e05f9 --- /dev/null +++ b/python/testData/inspections/PyRelativeImportInspection/PlainDirectoryDottedImportRegistryOffNoInspection/plainDirectory/dottedImport.py @@ -0,0 +1 @@ +from .util import foo \ No newline at end of file diff --git a/python/testData/inspections/PyRelativeImportInspection/PlainDirectoryDottedImportRegistryOffNoInspection/plainDirectory/util.py b/python/testData/inspections/PyRelativeImportInspection/PlainDirectoryDottedImportRegistryOffNoInspection/plainDirectory/util.py new file mode 100644 index 000000000000..cb332504aa2e --- /dev/null +++ b/python/testData/inspections/PyRelativeImportInspection/PlainDirectoryDottedImportRegistryOffNoInspection/plainDirectory/util.py @@ -0,0 +1,2 @@ +def foo(): + print('ok') \ No newline at end of file diff --git a/python/testData/inspections/PyRelativeImportInspection/PlainDirectoryInsidePackageInsidePlainDirectoryNoInspection/plainDirectory/ordinaryPackage/__init__.py b/python/testData/inspections/PyRelativeImportInspection/PlainDirectoryInsidePackageInsidePlainDirectoryNoInspection/plainDirectory/ordinaryPackage/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/inspections/PyRelativeImportInspection/PlainDirectoryInsidePackageInsidePlainDirectoryNoInspection/plainDirectory/ordinaryPackage/nestedPlainDirectory/dottedImport.py b/python/testData/inspections/PyRelativeImportInspection/PlainDirectoryInsidePackageInsidePlainDirectoryNoInspection/plainDirectory/ordinaryPackage/nestedPlainDirectory/dottedImport.py new file mode 100644 index 000000000000..94533a60018b --- /dev/null +++ b/python/testData/inspections/PyRelativeImportInspection/PlainDirectoryInsidePackageInsidePlainDirectoryNoInspection/plainDirectory/ordinaryPackage/nestedPlainDirectory/dottedImport.py @@ -0,0 +1,2 @@ +from .util import foo +foo() \ No newline at end of file diff --git a/python/testData/inspections/PyRelativeImportInspection/PlainDirectoryInsidePackageInsidePlainDirectoryNoInspection/plainDirectory/ordinaryPackage/nestedPlainDirectory/util.py b/python/testData/inspections/PyRelativeImportInspection/PlainDirectoryInsidePackageInsidePlainDirectoryNoInspection/plainDirectory/ordinaryPackage/nestedPlainDirectory/util.py new file mode 100644 index 000000000000..cb332504aa2e --- /dev/null +++ b/python/testData/inspections/PyRelativeImportInspection/PlainDirectoryInsidePackageInsidePlainDirectoryNoInspection/plainDirectory/ordinaryPackage/nestedPlainDirectory/util.py @@ -0,0 +1,2 @@ +def foo(): + print('ok') \ No newline at end of file diff --git a/python/testData/inspections/PyRelativeImportInspection/Python2PlainDirectoryNoInspection/plainDirectory/script.py b/python/testData/inspections/PyRelativeImportInspection/Python2PlainDirectoryNoInspection/plainDirectory/script.py new file mode 100644 index 000000000000..94533a60018b --- /dev/null +++ b/python/testData/inspections/PyRelativeImportInspection/Python2PlainDirectoryNoInspection/plainDirectory/script.py @@ -0,0 +1,2 @@ +from .util import foo +foo() \ No newline at end of file diff --git a/python/testData/inspections/PyRelativeImportInspection/Python2PlainDirectoryNoInspection/plainDirectory/util.py b/python/testData/inspections/PyRelativeImportInspection/Python2PlainDirectoryNoInspection/plainDirectory/util.py new file mode 100644 index 000000000000..cb332504aa2e --- /dev/null +++ b/python/testData/inspections/PyRelativeImportInspection/Python2PlainDirectoryNoInspection/plainDirectory/util.py @@ -0,0 +1,2 @@ +def foo(): + print('ok') \ No newline at end of file diff --git a/python/testData/inspections/PyRelativeImportInspection/SourceRootDottedImportInspectionWithoutQuickFixes/plainDirectory/sourceRoot/script.py b/python/testData/inspections/PyRelativeImportInspection/SourceRootDottedImportInspectionWithoutQuickFixes/plainDirectory/sourceRoot/script.py new file mode 100644 index 000000000000..0b906a32a1fd --- /dev/null +++ b/python/testData/inspections/PyRelativeImportInspection/SourceRootDottedImportInspectionWithoutQuickFixes/plainDirectory/sourceRoot/script.py @@ -0,0 +1,2 @@ +from ..util import foo +foo() \ No newline at end of file diff --git a/python/testData/inspections/PyRelativeImportInspection/SourceRootDottedImportInspectionWithoutQuickFixes/plainDirectory/util.py b/python/testData/inspections/PyRelativeImportInspection/SourceRootDottedImportInspectionWithoutQuickFixes/plainDirectory/util.py new file mode 100644 index 000000000000..cb332504aa2e --- /dev/null +++ b/python/testData/inspections/PyRelativeImportInspection/SourceRootDottedImportInspectionWithoutQuickFixes/plainDirectory/util.py @@ -0,0 +1,2 @@ +def foo(): + print('ok') \ No newline at end of file diff --git a/python/testData/inspections/PyTypedDictInspection/ImportedTypedDict/a.py b/python/testData/inspections/PyTypedDictInspection/ImportedTypedDict/a.py index 0504b759d7fb..c69369b14172 100644 --- a/python/testData/inspections/PyTypedDictInspection/ImportedTypedDict/a.py +++ b/python/testData/inspections/PyTypedDictInspection/ImportedTypedDict/a.py @@ -1,4 +1,4 @@ -from .test import Test1, Base +from test import Test1, Base b = Base(a=2) t = Test1(a=2) diff --git a/python/testData/packageIcon/DirectoryInOrdinaryPackageInNamespacePackage/nspkg/pkg/__init__.py b/python/testData/packageIcon/DirectoryInOrdinaryPackageInNamespacePackage/nspkg/pkg/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/packageIcon/DirectoryInOrdinaryPackageInNamespacePackage/nspkg/pkg/directory/.keep b/python/testData/packageIcon/DirectoryInOrdinaryPackageInNamespacePackage/nspkg/pkg/directory/.keep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/packageIcon/NamespacePackageInsideOldStylePackage/pkg/__init__.py b/python/testData/packageIcon/NamespacePackageInsideOldStylePackage/pkg/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/packageIcon/NamespacePackageInsideOldStylePackage/pkg/nspkg/.keep b/python/testData/packageIcon/NamespacePackageInsideOldStylePackage/pkg/nspkg/.keep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/packageIcon/NamespacePackageInsideOldStylePackageRegistryOff/pkg/__init__.py b/python/testData/packageIcon/NamespacePackageInsideOldStylePackageRegistryOff/pkg/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/packageIcon/NamespacePackageInsideOldStylePackageRegistryOff/pkg/nspkg/.keep b/python/testData/packageIcon/NamespacePackageInsideOldStylePackageRegistryOff/pkg/nspkg/.keep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/packageIcon/NamespacePackageRegistryOff/nspkg/subpkg/.keep b/python/testData/packageIcon/NamespacePackageRegistryOff/nspkg/subpkg/.keep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/packageIcon/OldStylePackageInsideNamespacePackageRegistryOff/nspkg/pkg/__init__.py b/python/testData/packageIcon/OldStylePackageInsideNamespacePackageRegistryOff/nspkg/pkg/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/packageIcon/OldStylePackageRegistryOff/pkg/__init__.py b/python/testData/packageIcon/OldStylePackageRegistryOff/pkg/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/packageIcon/OldStylePackageRegistryOff/pkg/subpkg/__init__.py b/python/testData/packageIcon/OldStylePackageRegistryOff/pkg/subpkg/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/packageIcon/PlainDirectoryInsideOldStylePackage/pkg/__init__.py b/python/testData/packageIcon/PlainDirectoryInsideOldStylePackage/pkg/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/packageIcon/PlainDirectoryInsideOldStylePackage/pkg/plainDirectory/.keep b/python/testData/packageIcon/PlainDirectoryInsideOldStylePackage/pkg/plainDirectory/.keep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/namespacePackageDottedRelativeImport/namespacePackage/mod.py b/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/namespacePackageDottedRelativeImport/namespacePackage/mod.py new file mode 100644 index 000000000000..ea1b04396bf9 --- /dev/null +++ b/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/namespacePackageDottedRelativeImport/namespacePackage/mod.py @@ -0,0 +1,2 @@ +from . import util +# \ No newline at end of file diff --git a/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/namespacePackageDottedRelativeImport/namespacePackage/util.py b/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/namespacePackageDottedRelativeImport/namespacePackage/util.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/namespacePackageSameDirectoryImport/namespacePackage/mod.py b/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/namespacePackageSameDirectoryImport/namespacePackage/mod.py new file mode 100644 index 000000000000..2862c8ddd788 --- /dev/null +++ b/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/namespacePackageSameDirectoryImport/namespacePackage/mod.py @@ -0,0 +1,2 @@ +import util +# \ No newline at end of file diff --git a/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/namespacePackageSameDirectoryImport/namespacePackage/util.py b/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/namespacePackageSameDirectoryImport/namespacePackage/util.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/nestedNamespacePackageDottedRelativeImport/namespacePackage/nestedNamespacePackage/mod.py b/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/nestedNamespacePackageDottedRelativeImport/namespacePackage/nestedNamespacePackage/mod.py new file mode 100644 index 000000000000..ea1b04396bf9 --- /dev/null +++ b/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/nestedNamespacePackageDottedRelativeImport/namespacePackage/nestedNamespacePackage/mod.py @@ -0,0 +1,2 @@ +from . import util +# \ No newline at end of file diff --git a/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/nestedNamespacePackageDottedRelativeImport/namespacePackage/nestedNamespacePackage/util.py b/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/nestedNamespacePackageDottedRelativeImport/namespacePackage/nestedNamespacePackage/util.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/nestedNamespacePackageSameDirectoryImport/namespacePackage/nestedNamespacePackage/mod.py b/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/nestedNamespacePackageSameDirectoryImport/namespacePackage/nestedNamespacePackage/mod.py new file mode 100644 index 000000000000..2862c8ddd788 --- /dev/null +++ b/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/nestedNamespacePackageSameDirectoryImport/namespacePackage/nestedNamespacePackage/mod.py @@ -0,0 +1,2 @@ +import util +# \ No newline at end of file diff --git a/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/nestedNamespacePackageSameDirectoryImport/namespacePackage/nestedNamespacePackage/util.py b/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/nestedNamespacePackageSameDirectoryImport/namespacePackage/nestedNamespacePackage/util.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/ordinaryPackageDottedRelativeFromImport/ordinaryPackage/__init__.py b/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/ordinaryPackageDottedRelativeFromImport/ordinaryPackage/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/ordinaryPackageDottedRelativeFromImport/ordinaryPackage/mod.py b/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/ordinaryPackageDottedRelativeFromImport/ordinaryPackage/mod.py new file mode 100644 index 000000000000..32fb0f8c00f4 --- /dev/null +++ b/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/ordinaryPackageDottedRelativeFromImport/ordinaryPackage/mod.py @@ -0,0 +1,2 @@ +from .util import foo +# \ No newline at end of file diff --git a/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/ordinaryPackageDottedRelativeFromImport/ordinaryPackage/util.py b/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/ordinaryPackageDottedRelativeFromImport/ordinaryPackage/util.py new file mode 100644 index 000000000000..cb332504aa2e --- /dev/null +++ b/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/ordinaryPackageDottedRelativeFromImport/ordinaryPackage/util.py @@ -0,0 +1,2 @@ +def foo(): + print('ok') \ No newline at end of file diff --git a/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/ordinaryPackageSameDirectoryFromImport/ordinaryPackage/__init__.py b/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/ordinaryPackageSameDirectoryFromImport/ordinaryPackage/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/ordinaryPackageSameDirectoryFromImport/ordinaryPackage/mod.py b/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/ordinaryPackageSameDirectoryFromImport/ordinaryPackage/mod.py new file mode 100644 index 000000000000..fe1e2830ee2b --- /dev/null +++ b/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/ordinaryPackageSameDirectoryFromImport/ordinaryPackage/mod.py @@ -0,0 +1,2 @@ +from util import foo +# \ No newline at end of file diff --git a/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/ordinaryPackageSameDirectoryFromImport/ordinaryPackage/util.py b/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/ordinaryPackageSameDirectoryFromImport/ordinaryPackage/util.py new file mode 100644 index 000000000000..cb332504aa2e --- /dev/null +++ b/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/ordinaryPackageSameDirectoryFromImport/ordinaryPackage/util.py @@ -0,0 +1,2 @@ +def foo(): + print('ok') \ No newline at end of file diff --git a/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/plainDirectoryDottedRelativeImport/plainDirectory/mod.py b/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/plainDirectoryDottedRelativeImport/plainDirectory/mod.py new file mode 100644 index 000000000000..32fb0f8c00f4 --- /dev/null +++ b/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/plainDirectoryDottedRelativeImport/plainDirectory/mod.py @@ -0,0 +1,2 @@ +from .util import foo +# \ No newline at end of file diff --git a/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/plainDirectoryDottedRelativeImport/plainDirectory/util.py b/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/plainDirectoryDottedRelativeImport/plainDirectory/util.py new file mode 100644 index 000000000000..cb332504aa2e --- /dev/null +++ b/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/plainDirectoryDottedRelativeImport/plainDirectory/util.py @@ -0,0 +1,2 @@ +def foo(): + print('ok') \ No newline at end of file diff --git a/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/plainDirectoryDottedRelativeImportRegistryOff/plainDirectory/mod.py b/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/plainDirectoryDottedRelativeImportRegistryOff/plainDirectory/mod.py new file mode 100644 index 000000000000..32fb0f8c00f4 --- /dev/null +++ b/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/plainDirectoryDottedRelativeImportRegistryOff/plainDirectory/mod.py @@ -0,0 +1,2 @@ +from .util import foo +# \ No newline at end of file diff --git a/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/plainDirectoryDottedRelativeImportRegistryOff/plainDirectory/util.py b/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/plainDirectoryDottedRelativeImportRegistryOff/plainDirectory/util.py new file mode 100644 index 000000000000..cb332504aa2e --- /dev/null +++ b/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/plainDirectoryDottedRelativeImportRegistryOff/plainDirectory/util.py @@ -0,0 +1,2 @@ +def foo(): + print('ok') \ No newline at end of file diff --git a/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/plainDirectorySameDirectoryImport/plainDirectory/mod.py b/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/plainDirectorySameDirectoryImport/plainDirectory/mod.py new file mode 100644 index 000000000000..fe1e2830ee2b --- /dev/null +++ b/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/plainDirectorySameDirectoryImport/plainDirectory/mod.py @@ -0,0 +1,2 @@ +from util import foo +# \ No newline at end of file diff --git a/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/plainDirectorySameDirectoryImport/plainDirectory/util.py b/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/plainDirectorySameDirectoryImport/plainDirectory/util.py new file mode 100644 index 000000000000..cb332504aa2e --- /dev/null +++ b/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/plainDirectorySameDirectoryImport/plainDirectory/util.py @@ -0,0 +1,2 @@ +def foo(): + print('ok') \ No newline at end of file diff --git a/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/plainDirectorySameDirectoryImportRegistryOff/plainDirectory/mod.py b/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/plainDirectorySameDirectoryImportRegistryOff/plainDirectory/mod.py new file mode 100644 index 000000000000..fe1e2830ee2b --- /dev/null +++ b/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/plainDirectorySameDirectoryImportRegistryOff/plainDirectory/mod.py @@ -0,0 +1,2 @@ +from util import foo +# \ No newline at end of file diff --git a/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/plainDirectorySameDirectoryImportRegistryOff/plainDirectory/util.py b/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/plainDirectorySameDirectoryImportRegistryOff/plainDirectory/util.py new file mode 100644 index 000000000000..cb332504aa2e --- /dev/null +++ b/python/testData/resolve/multiFile/relativeAndSameDirectoryImports/plainDirectorySameDirectoryImportRegistryOff/plainDirectory/util.py @@ -0,0 +1,2 @@ +def foo(): + print('ok') \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyPackageIconTest.java b/python/testSrc/com/jetbrains/python/PyPackageIconTest.java index be32cfc6bb84..aa34f001bc7e 100644 --- a/python/testSrc/com/jetbrains/python/PyPackageIconTest.java +++ b/python/testSrc/com/jetbrains/python/PyPackageIconTest.java @@ -1,6 +1,7 @@ // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.jetbrains.python; +import com.intellij.application.options.RegistryManager; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDirectory; import com.intellij.psi.PsiManager; @@ -8,6 +9,7 @@ import com.intellij.testFramework.LightProjectDescriptor; import com.intellij.util.PlatformIcons; import com.intellij.util.PsiIconUtil; import com.jetbrains.python.fixtures.PyTestCase; +import com.jetbrains.python.namespacePackages.PyNamespacePackagesService; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -26,27 +28,75 @@ public class PyPackageIconTest extends PyTestCase { public void setUp() throws Exception { super.setUp(); myFixture.copyDirectoryToProject(getTestName(false), ""); + RegistryManager.getInstance().get("python.explicit.namespace.packages").resetToDefault(); + } + + @Override + public void tearDown() throws Exception { + RegistryManager.getInstance().get("python.explicit.namespace.packages").resetToDefault(); + super.tearDown(); } // PY-38642 + // upd: PY-42750 public void testNamespacePackage() { + toggleNamespacePackageDirectory("nspkg"); + checkPackageIcon("nspkg", true); + checkPackageIcon("nspkg/subpkg", true); + } + + public void testNamespacePackageRegistryOff() { + RegistryManager.getInstance().get("python.explicit.namespace.packages").setValue(false); + toggleNamespacePackageDirectory("nspkg"); checkPackageIcon("nspkg", false); checkPackageIcon("nspkg/subpkg", false); } + public void testPlainDirectoryInsideOldStylePackage() { + checkPackageIcon("pkg", true); + checkPackageIcon("pkg/plainDirectory", false); + } + + public void testNamespacePackageInsideOldStylePackage() { + toggleNamespacePackageDirectory("pkg/nspkg"); + checkPackageIcon("pkg", true); + checkPackageIcon("pkg/nspkg", true); + } + + public void testNamespacePackageInsideOldStylePackageRegistryOff() { + RegistryManager.getInstance().get("python.explicit.namespace.packages").setValue(false); + toggleNamespacePackageDirectory("pkg/nspkg"); + checkPackageIcon("pkg", true); + checkPackageIcon("pkg/nspkg", false); + } + public void testOldStylePackage() { checkPackageIcon("pkg", true); checkPackageIcon("pkg/subpkg", true); } + public void testOldStylePackageRegistryOff() { + RegistryManager.getInstance().get("python.explicit.namespace.packages").setValue(false); + checkPackageIcon("pkg", true); + checkPackageIcon("pkg/subpkg", true); + } + public void testOldStylePackageWithIllegalName() { - checkPackageIcon("illegally named", false); - checkPackageIcon(".pkg", false); + checkPackageIcon("illegally named", true); + checkPackageIcon(".pkg", true); } public void testOldStylePackageInsideNamespacePackage() { + toggleNamespacePackageDirectory("nspkg"); + checkPackageIcon("nspkg", true); + checkPackageIcon("nspkg/pkg", true); + } + + public void testOldStylePackageInsideNamespacePackageRegistryOff() { + RegistryManager.getInstance().get("python.explicit.namespace.packages").setValue(false); + toggleNamespacePackageDirectory("nspkg"); checkPackageIcon("nspkg", false); - checkPackageIcon("nspkg/pkg", false); + checkPackageIcon("nspkg/pkg", true); } public void testOldStylePackageInsideSourceRoot() { @@ -57,6 +107,13 @@ public class PyPackageIconTest extends PyTestCase { }); } + public void testDirectoryInOrdinaryPackageInNamespacePackage() { + toggleNamespacePackageDirectory("nspkg"); + checkPackageIcon("nspkg", true); + checkPackageIcon("nspkg/pkg", true); + checkPackageIcon("nspkg/pkg/directory", false); + } + // PY-39274 public void testStubPackage() { checkPackageIcon("stubpkg", true); @@ -72,6 +129,12 @@ public class PyPackageIconTest extends PyTestCase { assertEquals(PlatformIcons.PACKAGE_ICON.equals(icon), has); } + private void toggleNamespacePackageDirectory(@NotNull String directory) { + PyNamespacePackagesService + .getInstance(myFixture.getModule()) + .toggleMarkingAsNamespacePackage(myFixture.findFileInTempDir(directory)); + } + @Override protected String getTestDataPath() { return super.getTestDataPath() + "/packageIcon"; diff --git a/python/testSrc/com/jetbrains/python/PyRelativeImportResolveTest.java b/python/testSrc/com/jetbrains/python/PyRelativeImportResolveTest.java new file mode 100644 index 000000000000..571d7ec63a5d --- /dev/null +++ b/python/testSrc/com/jetbrains/python/PyRelativeImportResolveTest.java @@ -0,0 +1,153 @@ +/* + * Copyright 2000-2018 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.python; + +import com.intellij.application.options.RegistryManager; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.testFramework.LightProjectDescriptor; +import com.jetbrains.python.fixtures.PyMultiFileResolveTestCase; +import com.jetbrains.python.fixtures.PyTestCase; +import com.jetbrains.python.namespacePackages.PyNamespacePackagesService; +import com.jetbrains.python.psi.LanguageLevel; +import com.jetbrains.python.psi.PyFile; +import com.jetbrains.python.psi.PyFunction; +import com.jetbrains.python.psi.impl.PythonLanguageLevelPusher; +import org.jetbrains.annotations.NotNull; + +/** + * @author yole + */ +public class PyRelativeImportResolveTest extends PyMultiFileResolveTestCase { + private static final String PLAIN_DIR = "plainDirectory"; + private static final String NAMESPACE_PACK_DIR = "namespacePackage"; + private static final String ORDINARY_PACK_DIR = "ordinaryPackage"; + + private String myNamespacePackageDirectory = null; + + @Override + protected String getTestDataPath() { + return PythonTestUtil.getTestDataPath() + "/resolve/multiFile/relativeAndSameDirectoryImports/"; + } + + @Override + protected LightProjectDescriptor getProjectDescriptor() { + return PyTestCase.ourPy3Descriptor; + } + + @Override + protected void prepareTestDirectory() { + final String testName = getTestName(true); + myFixture.copyDirectoryToProject(testName, ""); + if (myNamespacePackageDirectory != null) { + toggleNamespacePackageDirectory(myNamespacePackageDirectory); + } + PsiDocumentManager.getInstance(myFixture.getProject()).commitAllDocuments(); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + myNamespacePackageDirectory = null; + PythonLanguageLevelPusher.setForcedLanguageLevel(myFixture.getProject(), LanguageLevel.getLatest()); + RegistryManager.getInstance().get("python.explicit.namespace.packages").resetToDefault(); + } + + @Override + protected void tearDown() throws Exception { + try { + PythonLanguageLevelPusher.setForcedLanguageLevel(myFixture.getProject(), null); + myNamespacePackageDirectory = null; + } + catch (Throwable e) { + addSuppressedException(e); + } + finally { + RegistryManager.getInstance().get("python.explicit.namespace.packages").resetToDefault(); + super.tearDown(); + } + } + + public void testOrdinaryPackageDottedRelativeFromImport() { + doTestOrdinaryPackageFromImportOfFunction(); + } + + public void testOrdinaryPackageSameDirectoryFromImport() { + doTestOrdinaryPackageFromImportOfFunction(); + } + + private void doTestOrdinaryPackageFromImportOfFunction() { + myTestFileName = ORDINARY_PACK_DIR + "/mod.py"; + assertResolvesTo(PyFunction.class, "foo"); + } + + public void testNamespacePackageDottedRelativeImport() { + doTestNamespacePackageImportOfModule(); + } + + public void testNamespacePackageSameDirectoryImport() { + doTestNamespacePackageImportOfModule(); + } + + private void doTestNamespacePackageImportOfModule() { + myTestFileName = NAMESPACE_PACK_DIR + "/mod.py"; + assertResolvesTo(PyFile.class, "util.py"); + } + + public void testNestedNamespacePackageDottedRelativeImport() { + doTestNestedNamespacePackageImportOfModule(); + } + + public void testNestedNamespacePackageSameDirectoryImport() { + doTestNestedNamespacePackageImportOfModule(); + } + + private void doTestNestedNamespacePackageImportOfModule() { + myTestFileName = NAMESPACE_PACK_DIR + "/nestedNamespacePackage/mod.py"; + myNamespacePackageDirectory = NAMESPACE_PACK_DIR; + assertResolvesTo(PyFile.class, "util.py"); + } + + public void testPlainDirectoryDottedRelativeImport() { + doTestPlainDirectoryImportOfModule(); + } + + public void testPlainDirectorySameDirectoryImport() { + doTestPlainDirectoryImportOfModule(); + } + + private void doTestPlainDirectoryImportOfModule() { + myTestFileName = PLAIN_DIR + "/mod.py"; + assertResolvesTo(PyFunction.class, "foo"); + } + + public void testPlainDirectoryDottedRelativeImportRegistryOff() { + RegistryManager.getInstance().get("python.explicit.namespace.packages").setValue(false); + doTestPlainDirectoryImportOfModule(); + } + + + public void testPlainDirectorySameDirectoryImportRegistryOff() { + RegistryManager.getInstance().get("python.explicit.namespace.packages").setValue(false); + myTestFileName = PLAIN_DIR + "/mod.py"; + assertUnresolved(); + } + + private void toggleNamespacePackageDirectory(@NotNull String directory) { + PyNamespacePackagesService + .getInstance(myFixture.getModule()) + .toggleMarkingAsNamespacePackage(myFixture.findFileInTempDir(directory)); + } +} diff --git a/python/testSrc/com/jetbrains/python/actions/PyMarkAsNamespacePackageActionTest.java b/python/testSrc/com/jetbrains/python/actions/PyMarkAsNamespacePackageActionTest.java new file mode 100644 index 000000000000..b5175449d024 --- /dev/null +++ b/python/testSrc/com/jetbrains/python/actions/PyMarkAsNamespacePackageActionTest.java @@ -0,0 +1,108 @@ +// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.jetbrains.python.actions; + +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.actionSystem.LangDataKeys; +import com.intellij.openapi.actionSystem.Presentation; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.testFramework.MapDataContext; +import com.intellij.testFramework.TestActionEvent; +import com.jetbrains.python.PyBundle; +import com.jetbrains.python.PythonTestUtil; +import com.jetbrains.python.fixtures.PyTestCase; +import com.jetbrains.python.namespacePackages.PyMarkAsNamespacePackageAction; +import com.jetbrains.python.namespacePackages.PyNamespacePackagesService; +import com.jetbrains.python.psi.LanguageLevel; +import org.jetbrains.annotations.NotNull; + +public class PyMarkAsNamespacePackageActionTest extends PyTestCase { + private static final String PLAIN_DIR = "plainDirectory"; + private static final String NAMESPACE_PACK_DIR = "namespacePackage"; + private static final String ORDINARY_PACK_DIR = "ordinaryPackage"; + + private PyNamespacePackagesService myNspService; + + @Override + protected void setUp() throws Exception { + super.setUp(); + myNspService = PyNamespacePackagesService.getInstance(myFixture.getModule()); + setLanguageLevel(LanguageLevel.getLatest()); + } + + public void testPlainDirectory() { + doCopyDirectory(); + + Presentation presentation = doLaunchAction(PLAIN_DIR); + assertTrue(presentation.isVisible()); + assertTrue(presentation.isEnabled()); + assertEquals(presentation.getText(), PyBundle.message("python.namespace.package.folder")); + assertTrue(myNspService.isMarked(myFixture.findFileInTempDir(PLAIN_DIR))); + + presentation = doLaunchAction(PLAIN_DIR); + assertTrue(presentation.isVisible()); + assertTrue(presentation.isEnabled()); + assertEquals(presentation.getText(), PyBundle.message("python.unmark.as.namespace.package")); + assertFalse(myNspService.isMarked(myFixture.findFileInTempDir(PLAIN_DIR))); + } + + public void testOrdinaryPackage() { + doCopyDirectory(); + + Presentation presentation = doLaunchAction(ORDINARY_PACK_DIR); + assertTrue(presentation.isVisible()); + assertFalse(presentation.isEnabled()); + assertFalse(myNspService.isMarked(myFixture.findFileInTempDir(ORDINARY_PACK_DIR))); + } + + public void testNestedNamespacePackage() { + doCopyDirectory(); + myNspService.toggleMarkingAsNamespacePackage(myFixture.findFileInTempDir(NAMESPACE_PACK_DIR)); + assertTrue(myNspService.isMarked(myFixture.findFileInTempDir(NAMESPACE_PACK_DIR))); + assertTrue(myNspService.isNamespacePackage(myFixture.findFileInTempDir(NAMESPACE_PACK_DIR + "/nestedNamespacePackage"))); + + Presentation presentation = doLaunchAction(NAMESPACE_PACK_DIR + "/nestedNamespacePackage"); + assertTrue(presentation.isVisible()); + assertFalse(presentation.isEnabled()); + } + + public void testPlainDirectoryInOrdinaryPackage() { + doCopyDirectory(); + + Presentation presentation = doLaunchAction(ORDINARY_PACK_DIR + "/nestedPlainDirectory"); + assertTrue(presentation.isVisible()); + assertTrue(presentation.isEnabled()); + assertEquals(presentation.getText(), PyBundle.message("python.namespace.package.folder")); + assertTrue(myNspService.isMarked(myFixture.findFileInTempDir(ORDINARY_PACK_DIR + "/nestedPlainDirectory"))); + + presentation = doLaunchAction(ORDINARY_PACK_DIR + "/nestedPlainDirectory"); + assertTrue(presentation.isVisible()); + assertTrue(presentation.isEnabled()); + assertEquals(presentation.getText(), PyBundle.message("python.unmark.as.namespace.package")); + assertFalse(myNspService.isMarked(myFixture.findFileInTempDir(ORDINARY_PACK_DIR + "/nestedPlainDirectory"))); + } + + private @NotNull Presentation doLaunchAction(@NotNull String directoryPath) { + MapDataContext mapDataContext = new MapDataContext(); + mapDataContext.put(CommonDataKeys.VIRTUAL_FILE_ARRAY, new VirtualFile[] {myFixture.findFileInTempDir(directoryPath)}); + mapDataContext.put(LangDataKeys.MODULE, myFixture.getModule()); + + AnAction action = new PyMarkAsNamespacePackageAction(); + TestActionEvent e = new TestActionEvent(mapDataContext, action); + action.beforeActionPerformedUpdate(e); + if (e.getPresentation().isEnabledAndVisible()) { + action.actionPerformed(e); + } + + return e.getPresentation(); + } + + private void doCopyDirectory() { + myFixture.copyDirectoryToProject(getTestName(false), ""); + } + + @Override + protected String getTestDataPath() { + return PythonTestUtil.getTestDataPath() + "/actions/MarkAsNamespacePackage"; + } +} diff --git a/python/testSrc/com/jetbrains/python/fixtures/PyTestCase.java b/python/testSrc/com/jetbrains/python/fixtures/PyTestCase.java index 3d98fa5dc8fc..5b7b61d24d44 100644 --- a/python/testSrc/com/jetbrains/python/fixtures/PyTestCase.java +++ b/python/testSrc/com/jetbrains/python/fixtures/PyTestCase.java @@ -48,6 +48,7 @@ import com.jetbrains.python.PythonTestUtil; import com.jetbrains.python.documentation.PyDocumentationSettings; import com.jetbrains.python.documentation.PythonDocumentationProvider; import com.jetbrains.python.documentation.docstrings.DocStringFormat; +import com.jetbrains.python.namespacePackages.PyNamespacePackagesService; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyFileImpl; import com.jetbrains.python.psi.impl.PythonLanguageLevelPusher; @@ -265,6 +266,7 @@ public abstract class PyTestCase extends UsefulTestCase { @Override protected void tearDown() throws Exception { try { + PyNamespacePackagesService.getInstance(myFixture.getModule()).resetAllNamespacePackages(); setLanguageLevel(null); myFixture.tearDown(); myFixture = null; diff --git a/python/testSrc/com/jetbrains/python/inspections/PyRelativeImportInspectionTest.kt b/python/testSrc/com/jetbrains/python/inspections/PyRelativeImportInspectionTest.kt new file mode 100644 index 000000000000..84bdfdd90fc7 --- /dev/null +++ b/python/testSrc/com/jetbrains/python/inspections/PyRelativeImportInspectionTest.kt @@ -0,0 +1,149 @@ +// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.jetbrains.python.inspections + +import com.intellij.application.options.RegistryManager +import com.jetbrains.python.PyBundle +import com.jetbrains.python.fixtures.PyInspectionTestCase +import com.jetbrains.python.namespacePackages.PyNamespacePackagesService +import com.jetbrains.python.psi.LanguageLevel +import org.jetbrains.annotations.NonNls + +class PyRelativeImportInspectionTest: PyInspectionTestCase() { + override fun setUp() { + super.setUp() + setLanguageLevel(LanguageLevel.getLatest()) + RegistryManager.getInstance()["python.explicit.namespace.packages"].resetToDefault() + } + + override fun tearDown() { + setLanguageLevel(LanguageLevel.getDefault()) + RegistryManager.getInstance()["python.explicit.namespace.packages"].resetToDefault() + super.tearDown() + } + + override fun getInspectionClass(): Class { + return PyRelativeImportInspection::class.java + } + + fun testPlainDirectoryDottedImportDeleteDot() { + doRelativeImportInspectionTest("$PLAIN_DIR/dottedImport.py", PyBundle.message("QFIX.change.to.same.directory.import")) + } + + fun testPlainDirectoryDottedImportMarkDirectory() { + doRelativeImportInspectionTest("$PLAIN_DIR/dottedImport.py", PyBundle.message("QFIX.mark.as.namespace.package", PLAIN_DIR)) + val service = PyNamespacePackagesService.getInstance(myFixture.module) + val plainDirVirtualFile = myFixture.findFileInTempDir(PLAIN_DIR) + assertTrue(service.isMarked(plainDirVirtualFile)) + } + + fun testPlainDirectoryInsidePackageInsidePlainDirectoryNoInspection() { + doRelativeImportInspectionTest("$PLAIN_DIR/ordinaryPackage/nestedPlainDirectory/dottedImport.py") + } + + fun testNestedPlainDirectoryDottedImportMarkDirectory() { + doRelativeImportInspectionTest("$PLAIN_DIR/nestedPlainDirectory/dottedImport.py", PyBundle.message("QFIX.mark.as.namespace.package", + PLAIN_DIR)) + val service = PyNamespacePackagesService.getInstance(myFixture.module) + val plainDirVirtualFile = myFixture.findFileInTempDir(PLAIN_DIR) + val nestedPlainDirVirtualFile = myFixture.findFileInTempDir("$PLAIN_DIR/nestedPlainDirectory") + assertTrue(service.isMarked(plainDirVirtualFile)) + assertFalse(service.isMarked(nestedPlainDirVirtualFile)) + assertTrue(service.isNamespacePackage(plainDirVirtualFile)) + assertTrue(service.isNamespacePackage(nestedPlainDirVirtualFile)) + } + + fun testPlainDirectoryDottedImportRegistryOffNoInspection() { + RegistryManager.getInstance()["python.explicit.namespace.packages"].setValue(false) + doMultiFileTest("$PLAIN_DIR/dottedImport.py") + } + + fun testSourceRootDottedImportInspectionWithoutQuickFixes() { + myFixture.copyDirectoryToProject(testDirectoryPath, "") + runWithSourceRoots(listOf(myFixture.findFileInTempDir("$PLAIN_DIR/sourceRoot"))) { + val currentFile = myFixture.configureFromTempProjectFile("$PLAIN_DIR/sourceRoot/script.py") + configureInspection() + assertProjectFilesNotParsed(currentFile) + assertSdkRootsNotParsed(currentFile) + assertEmpty(myFixture.filterAvailableIntentions(PyBundle.message("QFIX.mark.as.namespace.package", PLAIN_DIR))) + assertEmpty(myFixture.filterAvailableIntentions(PyBundle.message("QFIX.mark.as.namespace.package", "$PLAIN_DIR/sourceRoot"))) + assertEmpty(myFixture.filterAvailableIntentions(PyBundle.message("QFIX.change.to.same.directory.import"))) + } + } + + fun testNamespacePackageSameDirectoryImportNoInspection() { + doNamespacePackageTest("$NAMESPACE_PACK_DIR/mod.py", NAMESPACE_PACK_DIR) + } + + fun testNamespacePackageDottedImportNoInspection() { + doNamespacePackageTest("$NAMESPACE_PACK_DIR/mod.py", NAMESPACE_PACK_DIR) + } + + fun testNamespacePackageSameDirectoryImportRegistryOffNoInspection() { + RegistryManager.getInstance()["python.explicit.namespace.packages"].setValue(false) + doNamespacePackageTest("$NAMESPACE_PACK_DIR/mod.py", NAMESPACE_PACK_DIR) + } + + fun testNestedNamespacePackageSameDirectoryImportRegistryOffNoInspection() { + RegistryManager.getInstance()["python.explicit.namespace.packages"].setValue(false) + doNamespacePackageTest("$NAMESPACE_PACK_DIR/nestedNamespacePackage/mod.py", NAMESPACE_PACK_DIR) + } + + fun testNotMarkedNamespacePackageInsidePackageSameDirectoryImportNoInspection() { + doNamespacePackageTest("$NAMESPACE_PACK_DIR/$ORDINARY_PACK_DIR/nestedNamespacePackage/mod.py", NAMESPACE_PACK_DIR) + } + + fun testOrdinaryPackageSameDirectoryImportNoInspection() { + doRelativeImportInspectionTest("$ORDINARY_PACK_DIR/script.py") + } + + fun testOrdinaryPackageDottedImportNoInspection() { + doRelativeImportInspectionTest("$ORDINARY_PACK_DIR/script.py") + } + + fun testPython2PlainDirectoryNoInspection() { + runWithLanguageLevel(LanguageLevel.PYTHON27) { + doRelativeImportInspectionTest("$PLAIN_DIR/script.py") + } + } + + private fun doRelativeImportInspectionTest(filename: String, hint: String? = null) { + doMultiFileTest(filename) + if (hint != null) { + val intentionAction = myFixture.findSingleIntention(hint) + assertNotNull(intentionAction) + myFixture.launchAction(intentionAction) + myFixture.checkHighlighting(isWarning, isInfo, isWeakWarning) + myFixture.checkResultByFile(filename, getExpectedFilePathAfterFix(filename), true) + } + } + + private fun doNamespacePackageTest(filename: String, directoryToMark: String, hint: String? = null) { + myFixture.copyDirectoryToProject(testDirectoryPath, "") + toggleNamespacePackageDirectory(directoryToMark) + val currentFile = myFixture.configureFromTempProjectFile(filename) + configureInspection() + assertProjectFilesNotParsed(currentFile) + assertSdkRootsNotParsed(currentFile) + if (hint != null) { + val intentionAction = myFixture.findSingleIntention(hint) + assertNotNull(intentionAction) + myFixture.launchAction(intentionAction) + myFixture.checkResultByFile(filename, getExpectedFilePathAfterFix(filename), true) + } + } + + private fun toggleNamespacePackageDirectory(directory: String) { + PyNamespacePackagesService + .getInstance(myFixture.module) + .toggleMarkingAsNamespacePackage(myFixture.findFileInTempDir(directory)) + } + + private fun getExpectedFilePathAfterFix(originalFileName: String): @NonNls String = + "$testDirectoryPath/${originalFileName.removeSuffix(".py")}_after.py" + + companion object { + private const val PLAIN_DIR = "plainDirectory" + private const val NAMESPACE_PACK_DIR = "namespacePackage" + private const val ORDINARY_PACK_DIR = "ordinaryPackage" + } +} \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/refactoring/PyMoveTest.java b/python/testSrc/com/jetbrains/python/refactoring/PyMoveTest.java index d25bc7eaf408..ea9f000f4c10 100644 --- a/python/testSrc/com/jetbrains/python/refactoring/PyMoveTest.java +++ b/python/testSrc/com/jetbrains/python/refactoring/PyMoveTest.java @@ -18,6 +18,7 @@ import com.jetbrains.python.PythonTestUtil; import com.jetbrains.python.codeInsight.PyCodeInsightSettings; import com.jetbrains.python.fixtures.PyTestCase; import com.jetbrains.python.formatter.PyCodeStyleSettings; +import com.jetbrains.python.namespacePackages.PyNamespacePackagesService; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.stubs.PyClassNameIndex; import com.jetbrains.python.psi.stubs.PyFunctionNameIndex; @@ -28,6 +29,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; import java.util.List; @@ -203,7 +205,29 @@ public class PyMoveTest extends PyTestCase { // PY-14384 public void testRelativeImportInsideNamespacePackage() { - runWithLanguageLevel(LanguageLevel.PYTHON34, () -> doMoveFileTest("nspkg/nssubpkg", "")); + runWithLanguageLevel(LanguageLevel.PYTHON34, () -> { + String fileName = "nspkg/nssubpkg"; + String toDirName = ""; + doComparingDirectories(testDir -> { + PyNamespacePackagesService.getInstance(myFixture.getModule()).toggleMarkingAsNamespacePackage(testDir.findFileByRelativePath("nspkg")); + + final Project project = myFixture.getProject(); + final PsiManager manager = PsiManager.getInstance(project); + final VirtualFile virtualFile = testDir.findFileByRelativePath(fileName); + assertNotNull(virtualFile); + PsiElement file = manager.findFile(virtualFile); + if (file == null) { + file = manager.findDirectory(virtualFile); + } + assertNotNull(file); + final VirtualFile toVirtualDir = testDir.findFileByRelativePath(toDirName); + assertNotNull(toVirtualDir); + final PsiDirectory toDir = manager.findDirectory(toVirtualDir); + new MoveFilesOrDirectoriesProcessor(project, new PsiElement[]{file}, toDir, false, false, null, null).run(); + + PyNamespacePackagesService.getInstance(myFixture.getModule()).setNamespacePackageFolders(new ArrayList<>()); + }); + }); } // PY-14384