diff --git a/python/src/com/jetbrains/python/codeInsight/imports/PyImportOptimizer.java b/python/src/com/jetbrains/python/codeInsight/imports/PyImportOptimizer.java index 07d921bd2f60..2aff60a1af4e 100644 --- a/python/src/com/jetbrains/python/codeInsight/imports/PyImportOptimizer.java +++ b/python/src/com/jetbrains/python/codeInsight/imports/PyImportOptimizer.java @@ -182,6 +182,7 @@ public class PyImportOptimizer implements ImportOptimizer { final PyFromImportStatement fromImport = (PyFromImportStatement)statement; final String source = getNormalizedFromImportSource(fromImport); final List newStatementElements = new ArrayList<>(); + boolean forceParentheses = false; // We can neither sort, nor combine star imports if (!fromImport.isStarImport()) { @@ -190,6 +191,8 @@ public class PyImportOptimizer implements ImportOptimizer { continue; } + forceParentheses = sameSourceImports.size() == 1 && fromImport.getLeftParen() != null; + // 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) { @@ -210,7 +213,10 @@ public class PyImportOptimizer implements ImportOptimizer { if (myPySettings.OPTIMIZE_IMPORTS_SORT_NAMES_IN_FROM_IMPORTS) { Collections.sort(newStatementElements, IMPORT_ELEMENT_COMPARATOR); } - final String importedNames = StringUtil.join(newStatementElements, ImportSorter::getNormalizedImportElementText, ", "); + String importedNames = StringUtil.join(newStatementElements, ImportSorter::getNormalizedImportElementText, ", "); + if (forceParentheses) { + importedNames = "(" + importedNames + ")"; + } final PyFromImportStatement combinedImport = generator.createFromImportStatement(langLevel, source, importedNames, null); ContainerUtil.map2LinkedSet(newStatementElements, e -> (PyImportStatementBase)e.getParent()).forEach(affected -> { myNewImportToLineComments.putValues(combinedImport, myOldImportToLineComments.get(affected)); diff --git a/python/testData/optimizeImports/existingParenthesesInCombinedFromImports.after.py b/python/testData/optimizeImports/existingParenthesesInCombinedFromImports.after.py new file mode 100644 index 000000000000..78dae51abb46 --- /dev/null +++ b/python/testData/optimizeImports/existingParenthesesInCombinedFromImports.after.py @@ -0,0 +1,4 @@ +from mod1 import a1, b1, c1, d1 +from mod2 import a2, b2, c2, d2 + +print(a1, a2, b1, b2, c1, c2, d1, d2) diff --git a/python/testData/optimizeImports/existingParenthesesInCombinedFromImports.py b/python/testData/optimizeImports/existingParenthesesInCombinedFromImports.py new file mode 100644 index 000000000000..dbf58b0a66b4 --- /dev/null +++ b/python/testData/optimizeImports/existingParenthesesInCombinedFromImports.py @@ -0,0 +1,15 @@ +from mod1 import ( + a1, + b1 +) + +from mod1 import c1, d1 + +from mod2 import ( + a2, + b2 +) + +from mod2 import (c2, d2) + +print(a1, a2, b1, b2, c1, c2, d1, d2) diff --git a/python/testData/optimizeImports/existingParenthesesInReorderedFromImport.after.py b/python/testData/optimizeImports/existingParenthesesInReorderedFromImport.after.py new file mode 100644 index 000000000000..99eb6f036927 --- /dev/null +++ b/python/testData/optimizeImports/existingParenthesesInReorderedFromImport.after.py @@ -0,0 +1,4 @@ +from indico_audiovisual.util import (compare_data_identifiers, count_capable_contributions, get_data_identifiers, + is_av_manager) + +print(get_data_identifiers, compare_data_identifiers, is_av_manager, count_capable_contributions) diff --git a/python/testData/optimizeImports/existingParenthesesInReorderedFromImport.py b/python/testData/optimizeImports/existingParenthesesInReorderedFromImport.py new file mode 100644 index 000000000000..12d6cd15dabb --- /dev/null +++ b/python/testData/optimizeImports/existingParenthesesInReorderedFromImport.py @@ -0,0 +1,4 @@ +from indico_audiovisual.util import (get_data_identifiers, compare_data_identifiers, is_av_manager, + count_capable_contributions) + +print(get_data_identifiers, compare_data_identifiers, is_av_manager, count_capable_contributions) diff --git a/python/testSrc/com/jetbrains/python/PyOptimizeImportsTest.java b/python/testSrc/com/jetbrains/python/PyOptimizeImportsTest.java index b198c7ea09da..17f49fc71102 100644 --- a/python/testSrc/com/jetbrains/python/PyOptimizeImportsTest.java +++ b/python/testSrc/com/jetbrains/python/PyOptimizeImportsTest.java @@ -304,6 +304,18 @@ public class PyOptimizeImportsTest extends PyTestCase { doTest(); } + // PY-25567 + public void testExistingParenthesesInReorderedFromImport() { + getPythonCodeStyleSettings().OPTIMIZE_IMPORTS_SORT_NAMES_IN_FROM_IMPORTS = true; + doTest(); + } + + // PY-25567 + public void testExistingParenthesesInCombinedFromImports() { + getPythonCodeStyleSettings().OPTIMIZE_IMPORTS_JOIN_FROM_IMPORTS_WITH_SAME_SOURCE = true; + doTest(); + } + private void doMultiFileTest() { final String testName = getTestName(true); myFixture.copyDirectoryToProject(testName, "");