diff --git a/python/src/com/jetbrains/python/codeInsight/imports/PyImportOptimizer.java b/python/src/com/jetbrains/python/codeInsight/imports/PyImportOptimizer.java index 106e187fb975..4e64acc1cc2e 100644 --- a/python/src/com/jetbrains/python/codeInsight/imports/PyImportOptimizer.java +++ b/python/src/com/jetbrains/python/codeInsight/imports/PyImportOptimizer.java @@ -23,7 +23,6 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.codeStyle.CodeStyleSettingsManager; -import com.intellij.psi.util.QualifiedName; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.MultiMap; import com.jetbrains.python.codeInsight.imports.AddImportHelper.ImportPriority; @@ -119,11 +118,14 @@ public class PyImportOptimizer implements ImportOptimizer { final PyElementGenerator generator = PyElementGenerator.getInstance(myFile.getProject()); final LanguageLevel langLevel = LanguageLevel.forElement(myFile); - final MultiMap fromImportSources = MultiMap.create(); + final MultiMap fromImportSources = MultiMap.create(); for (PyImportStatementBase statement : imports) { final PyFromImportStatement fromImport = as(statement, PyFromImportStatement.class); if (fromImport != null) { - fromImportSources.putValue(fromImport.getImportSourceQName(), fromImport); + if (fromImport.isStarImport()) { + continue; + } + fromImportSources.putValue(getNormalizedFromImportSource(fromImport), fromImport); } } @@ -144,25 +146,30 @@ public class PyImportOptimizer implements ImportOptimizer { } } else if (statement instanceof PyFromImportStatement) { - final PyFromImportStatement fromImportStatement = (PyFromImportStatement)statement; - final QualifiedName source = fromImportStatement.getImportSourceQName(); - final String sourceText = Objects.toString(source, ""); - final Collection sameSourceImports = fromImportSources.get(source); - if (sameSourceImports.isEmpty()) continue; - + final PyFromImportStatement fromImport = (PyFromImportStatement)statement; + final String source = getNormalizedFromImportSource(fromImport); final List newStatementElements = new ArrayList<>(); - // Join multiple "from" imports with the same source, like "from module import foo; from module import bar as b" - if (myPySettings.OPTIMIZE_IMPORTS_JOIN_FROM_IMPORTS_WITH_SAME_SOURCE && sameSourceImports.size() > 1) { - for (PyFromImportStatement sameSourceImport : sameSourceImports) { - ContainerUtil.addAll(newStatementElements, sameSourceImport.getImportElements()); + + // We cannot neither sort, not combine star imports + if (!fromImport.isStarImport()) { + final Collection sameSourceImports = fromImportSources.get(source); + if (sameSourceImports.isEmpty()) { + continue; } - // Remember that we have checked imports with this source already - fromImportSources.remove(source); - } - else if (myPySettings.OPTIMIZE_IMPORTS_SORT_NAMES_IN_FROM_IMPORTS) { - final List originalElements = Arrays.asList(fromImportStatement.getImportElements()); - if (!Ordering.from(IMPORT_ELEMENT_COMPARATOR).isOrdered(originalElements)) { - ContainerUtil.addAll(newStatementElements, originalElements); + + // Join multiple "from" imports with the same source, like "from module import foo; from module import bar as b" + if (myPySettings.OPTIMIZE_IMPORTS_JOIN_FROM_IMPORTS_WITH_SAME_SOURCE && sameSourceImports.size() > 1) { + for (PyFromImportStatement sameSourceImport : sameSourceImports) { + ContainerUtil.addAll(newStatementElements, sameSourceImport.getImportElements()); + } + // Remember that we have checked imports with this source already + fromImportSources.remove(source); + } + else if (myPySettings.OPTIMIZE_IMPORTS_SORT_NAMES_IN_FROM_IMPORTS) { + final List originalElements = Arrays.asList(fromImport.getImportElements()); + if (!Ordering.from(IMPORT_ELEMENT_COMPARATOR).isOrdered(originalElements)) { + ContainerUtil.addAll(newStatementElements, originalElements); + } } } @@ -171,10 +178,10 @@ public class PyImportOptimizer implements ImportOptimizer { Collections.sort(newStatementElements, IMPORT_ELEMENT_COMPARATOR); } final String importedNames = StringUtil.join(newStatementElements, PsiElement::getText, ", "); - result.add(generator.createFromImportStatement(langLevel, sourceText, importedNames, null)); + result.add(generator.createFromImportStatement(langLevel, source, importedNames, null)); } else { - result.add(fromImportStatement); + result.add(fromImport); } } } @@ -183,6 +190,11 @@ public class PyImportOptimizer implements ImportOptimizer { return result; } + @NotNull + public static String getNormalizedFromImportSource(@NotNull PyFromImportStatement statement) { + return StringUtil.repeatSymbol('.', statement.getRelativeLevel()) + Objects.toString(statement.getImportSourceQName(), ""); + } + private boolean groupsNotSorted() { if (!myPySettings.OPTIMIZE_IMPORTS_SORT_ALPHABETICALLY) { return false; diff --git a/python/testData/optimizeImports/joinFromImportsAndRelativeImports.after.py b/python/testData/optimizeImports/joinFromImportsAndRelativeImports.after.py new file mode 100644 index 000000000000..044b56da884b --- /dev/null +++ b/python/testData/optimizeImports/joinFromImportsAndRelativeImports.after.py @@ -0,0 +1,11 @@ +from .module import b +from ..module import a +from ..module import c +from .module import d +from . import module1 +from . import module2 +from .. import pkg1 +from .. import pkg2 + +print(a, b, c, d, module1, module2, pkg1, pkg2) + diff --git a/python/testData/optimizeImports/joinFromImportsAndRelativeImports.py b/python/testData/optimizeImports/joinFromImportsAndRelativeImports.py new file mode 100644 index 000000000000..044b56da884b --- /dev/null +++ b/python/testData/optimizeImports/joinFromImportsAndRelativeImports.py @@ -0,0 +1,11 @@ +from .module import b +from ..module import a +from ..module import c +from .module import d +from . import module1 +from . import module2 +from .. import pkg1 +from .. import pkg2 + +print(a, b, c, d, module1, module2, pkg1, pkg2) + diff --git a/python/testData/optimizeImports/joinFromImportsIgnoresStarImports.after.py b/python/testData/optimizeImports/joinFromImportsIgnoresStarImports.after.py new file mode 100644 index 000000000000..620b6d31abbb --- /dev/null +++ b/python/testData/optimizeImports/joinFromImportsIgnoresStarImports.after.py @@ -0,0 +1,4 @@ +from module import * +from module import _hidden2, _hidden1 + +print(_hidden1, _hidden2) diff --git a/python/testData/optimizeImports/joinFromImportsIgnoresStarImports.py b/python/testData/optimizeImports/joinFromImportsIgnoresStarImports.py new file mode 100644 index 000000000000..58611bcf163e --- /dev/null +++ b/python/testData/optimizeImports/joinFromImportsIgnoresStarImports.py @@ -0,0 +1,5 @@ +from module import _hidden2 +from module import * +from module import _hidden1 + +print(_hidden1, _hidden2) diff --git a/python/testSrc/com/jetbrains/python/PyOptimizeImportsTest.java b/python/testSrc/com/jetbrains/python/PyOptimizeImportsTest.java index f4e6f2f393eb..251a49d8560d 100644 --- a/python/testSrc/com/jetbrains/python/PyOptimizeImportsTest.java +++ b/python/testSrc/com/jetbrains/python/PyOptimizeImportsTest.java @@ -151,25 +151,37 @@ public class PyOptimizeImportsTest extends PyTestCase { doTest(); } - // PY-18792, PY-12926 + // PY-18792, PY-14176 public void testJoinFromImportsForSameSource() { getPythonCodeStyleSettings().OPTIMIZE_IMPORTS_JOIN_FROM_IMPORTS_WITH_SAME_SOURCE = true; doTest(); } - // PY-18792, PY-12926 + // PY-18792, PY-14176 public void testJoinFromImportsForSameSourceAndSortNames() { getPythonCodeStyleSettings().OPTIMIZE_IMPORTS_JOIN_FROM_IMPORTS_WITH_SAME_SOURCE = true; getPythonCodeStyleSettings().OPTIMIZE_IMPORTS_SORT_NAMES_IN_FROM_IMPORTS = true; doTest(); } - // PY-18792, PY-12926 + // PY-18792, PY-14176 public void testJoinFromImportsDoesntAffectSingleImports() { getPythonCodeStyleSettings().OPTIMIZE_IMPORTS_JOIN_FROM_IMPORTS_WITH_SAME_SOURCE = true; doTest(); } + // PY-18792, PY-14176 + public void testJoinFromImportsIgnoresStarImports() { + getPythonCodeStyleSettings().OPTIMIZE_IMPORTS_JOIN_FROM_IMPORTS_WITH_SAME_SOURCE = true; + doTest(); + } + + // PY-18792, PY-14176 + public void testJoinFromImportsAndRelativeImports() { + getPythonCodeStyleSettings().OPTIMIZE_IMPORTS_JOIN_FROM_IMPORTS_WITH_SAME_SOURCE = true; + doTest(); + } + private void doTest() { myFixture.configureByFile(getTestName(true) + ".py"); OptimizeImportsAction.actionPerformedImpl(DataManager.getInstance().getDataContext(myFixture.getEditor().getContentComponent()));