diff --git a/python/src/com/jetbrains/python/codeInsight/imports/PyImportOptimizer.java b/python/src/com/jetbrains/python/codeInsight/imports/PyImportOptimizer.java index 4daf1172959d..106e187fb975 100644 --- a/python/src/com/jetbrains/python/codeInsight/imports/PyImportOptimizer.java +++ b/python/src/com/jetbrains/python/codeInsight/imports/PyImportOptimizer.java @@ -147,27 +147,30 @@ public class PyImportOptimizer implements ImportOptimizer { final PyFromImportStatement fromImportStatement = (PyFromImportStatement)statement; final QualifiedName source = fromImportStatement.getImportSourceQName(); final String sourceText = Objects.toString(source, ""); - if (myPySettings.OPTIMIZE_IMPORTS_JOIN_FROM_IMPORTS_WITH_SAME_SOURCE) { - final Collection sameSourceImports = fromImportSources.get(source); - if (!sameSourceImports.isEmpty()) { - final List allImportElements = new ArrayList<>(); - for (PyFromImportStatement sameSourceImport : sameSourceImports) { - ContainerUtil.addAll(allImportElements, sameSourceImport.getImportElements()); - } - if (myPySettings.OPTIMIZE_IMPORTS_SORT_NAMES_IN_FROM_IMPORTS) { - Collections.sort(allImportElements, IMPORT_ELEMENT_COMPARATOR); - } - final String importedNames = StringUtil.join(allImportElements, PsiElement::getText, ", "); - result.add(generator.createFromImportStatement(langLevel, sourceText, importedNames, null)); - - // remember that we have checked imports from this source already - fromImportSources.remove(source); + final Collection sameSourceImports = fromImportSources.get(source); + if (sameSourceImports.isEmpty()) continue; + + 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()); } + // Remember that we have checked imports with this source already + fromImportSources.remove(source); } else if (myPySettings.OPTIMIZE_IMPORTS_SORT_NAMES_IN_FROM_IMPORTS) { - final PyImportElement[] importElements = fromImportStatement.getImportElements(); - Arrays.sort(importElements, IMPORT_ELEMENT_COMPARATOR); - final String importedNames = StringUtil.join(importElements, PsiElement::getText, ", "); + final List originalElements = Arrays.asList(fromImportStatement.getImportElements()); + if (!Ordering.from(IMPORT_ELEMENT_COMPARATOR).isOrdered(originalElements)) { + ContainerUtil.addAll(newStatementElements, originalElements); + } + } + + if (!newStatementElements.isEmpty()) { + if (myPySettings.OPTIMIZE_IMPORTS_SORT_NAMES_IN_FROM_IMPORTS) { + Collections.sort(newStatementElements, IMPORT_ELEMENT_COMPARATOR); + } + final String importedNames = StringUtil.join(newStatementElements, PsiElement::getText, ", "); result.add(generator.createFromImportStatement(langLevel, sourceText, importedNames, null)); } else { diff --git a/python/testData/optimizeImports/joinFromImportsDoesntAffectSingleImports.after.py b/python/testData/optimizeImports/joinFromImportsDoesntAffectSingleImports.after.py new file mode 100644 index 000000000000..c40f764f1510 --- /dev/null +++ b/python/testData/optimizeImports/joinFromImportsDoesntAffectSingleImports.after.py @@ -0,0 +1,6 @@ +# Optimize imports should not reformat unaffected statements +from module1 import a, b + +from module2 import A, B + +print(A, B, a, b) diff --git a/python/testData/optimizeImports/joinFromImportsDoesntAffectSingleImports.py b/python/testData/optimizeImports/joinFromImportsDoesntAffectSingleImports.py new file mode 100644 index 000000000000..c40f764f1510 --- /dev/null +++ b/python/testData/optimizeImports/joinFromImportsDoesntAffectSingleImports.py @@ -0,0 +1,6 @@ +# Optimize imports should not reformat unaffected statements +from module1 import a, b + +from module2 import A, B + +print(A, B, a, b) diff --git a/python/testData/optimizeImports/orderNamesInsightFromImportDoesntAffectAlreadyOrderedImports.after.py b/python/testData/optimizeImports/orderNamesInsightFromImportDoesntAffectAlreadyOrderedImports.after.py new file mode 100644 index 000000000000..5d3e29f3b9c6 --- /dev/null +++ b/python/testData/optimizeImports/orderNamesInsightFromImportDoesntAffectAlreadyOrderedImports.after.py @@ -0,0 +1,6 @@ +# Optimize Imports should not re-create imports statements that are in order +from module1 import a, b, c + +from module2 import A, B, C + +print(A, B, C, a, b, c) diff --git a/python/testData/optimizeImports/orderNamesInsightFromImportDoesntAffectAlreadyOrderedImports.py b/python/testData/optimizeImports/orderNamesInsightFromImportDoesntAffectAlreadyOrderedImports.py new file mode 100644 index 000000000000..5d3e29f3b9c6 --- /dev/null +++ b/python/testData/optimizeImports/orderNamesInsightFromImportDoesntAffectAlreadyOrderedImports.py @@ -0,0 +1,6 @@ +# Optimize Imports should not re-create imports statements that are in order +from module1 import a, b, c + +from module2 import A, B, C + +print(A, B, C, a, b, c) diff --git a/python/testSrc/com/jetbrains/python/PyOptimizeImportsTest.java b/python/testSrc/com/jetbrains/python/PyOptimizeImportsTest.java index a97a33e08da9..f4e6f2f393eb 100644 --- a/python/testSrc/com/jetbrains/python/PyOptimizeImportsTest.java +++ b/python/testSrc/com/jetbrains/python/PyOptimizeImportsTest.java @@ -145,6 +145,12 @@ public class PyOptimizeImportsTest extends PyTestCase { doTest(); } + // PY-18792, PY-19292 + public void testOrderNamesInsightFromImportDoesntAffectAlreadyOrderedImports() { + getPythonCodeStyleSettings().OPTIMIZE_IMPORTS_SORT_NAMES_IN_FROM_IMPORTS = true; + doTest(); + } + // PY-18792, PY-12926 public void testJoinFromImportsForSameSource() { getPythonCodeStyleSettings().OPTIMIZE_IMPORTS_JOIN_FROM_IMPORTS_WITH_SAME_SOURCE = true; @@ -158,6 +164,12 @@ public class PyOptimizeImportsTest extends PyTestCase { doTest(); } + // PY-18792, PY-12926 + public void testJoinFromImportsDoesntAffectSingleImports() { + 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()));