From 6de5015a72b36d49c87a125babc2f16aceeaf516 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Thu, 16 Mar 2017 17:28:14 +0300 Subject: [PATCH] PY-23125 Preserve blank lines before the first comment in optimize imports I tweaked Python import optimizer so that it inserts new imports and their comments after the last of original imports, not before the first of them (and then deletes the old ones). The reason is that it turned out to be practically impossible to use PsiComment as the anchor for PsiElement#addRangeBefore(), because of the hardcoded behavior of CodeEditUtil.addChildren() which skips preceding whitespaces in this scenario. Otherwise, if the blank line before "# noinspection" comment preceding the first imports statement gets removed, next time this comment will be left in place, considered part of the top-level module comment e.g. license. --- .../imports/PyImportOptimizer.java | 23 ++++++++----------- .../stackDanglingCommentsAtEnd.after.py | 2 ++ .../stackDanglingCommentsAtEnd.py | 2 ++ .../python/PyOptimizeImportsTest.java | 1 + 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/python/src/com/jetbrains/python/codeInsight/imports/PyImportOptimizer.java b/python/src/com/jetbrains/python/codeInsight/imports/PyImportOptimizer.java index 8201d8fd4d04..75d3e40aed81 100644 --- a/python/src/com/jetbrains/python/codeInsight/imports/PyImportOptimizer.java +++ b/python/src/com/jetbrains/python/codeInsight/imports/PyImportOptimizer.java @@ -282,15 +282,14 @@ public class PyImportOptimizer implements ImportOptimizer { } final PyImportStatementBase firstImport = myImportBlock.get(0); final List boundComments = collectPrecedingLineComments(firstImport).getFirst(); - final PsiElement insertionAnchor = boundComments.isEmpty() ? firstImport : boundComments.get(0); - addImportsBefore(insertionAnchor); - // Remove together with extra whitespaces preceding - final PsiElement nonWhitespaceBefore = PyPsiUtils.getPrevNonWhitespaceSibling(insertionAnchor); - myFile.deleteChildRange(nonWhitespaceBefore != null ? nonWhitespaceBefore.getNextSibling() : insertionAnchor, - ContainerUtil.getLastItem(myImportBlock)); + final PsiElement firstElementToRemove = boundComments.isEmpty() ? firstImport : boundComments.get(0); + final PyImportStatementBase lastImport = ContainerUtil.getLastItem(myImportBlock); + assert lastImport != null; + addImportsAfter(lastImport); + myFile.deleteChildRange(firstElementToRemove, PyPsiUtils.getNextNonWhitespaceSibling(lastImport).getPrevSibling()); } - private void addImportsBefore(@NotNull PsiElement anchor) { + private void addImportsAfter(@NotNull PsiElement anchor) { final StringBuilder content = new StringBuilder(); for (List imports : myGroups.values()) { @@ -325,14 +324,12 @@ public class PyImportOptimizer implements ImportOptimizer { final Project project = anchor.getProject(); final PyElementGenerator generator = PyElementGenerator.getInstance(project); - PyFile file = (PyFile)generator.createDummyFile(LanguageLevel.forElement(anchor), content.toString()); - file = (PyFile)CodeStyleManager.getInstance(project).reformat(file); - final List newImportBlock = file.getImportBlock(); + final PyFile file = (PyFile)generator.createDummyFile(LanguageLevel.forElement(anchor), content.toString()); + final PyFile reformattedFile = (PyFile)CodeStyleManager.getInstance(project).reformat(file); + final List newImportBlock = reformattedFile.getImportBlock(); assert newImportBlock != null; - final PyImportStatementBase lastImport = ContainerUtil.getLastItem(newImportBlock); - assert lastImport != null; - myFile.addRangeBefore(file.getFirstChild(), file.getLastChild(), anchor); + myFile.addRangeAfter(reformattedFile.getFirstChild(), reformattedFile.getLastChild(), anchor); } } } diff --git a/python/testData/optimizeImports/stackDanglingCommentsAtEnd.after.py b/python/testData/optimizeImports/stackDanglingCommentsAtEnd.after.py index 1504c94f3255..11ccb7d33036 100644 --- a/python/testData/optimizeImports/stackDanglingCommentsAtEnd.after.py +++ b/python/testData/optimizeImports/stackDanglingCommentsAtEnd.after.py @@ -4,6 +4,8 @@ # comment after the top-level one + + # comment for abc import abc # comment for sys diff --git a/python/testData/optimizeImports/stackDanglingCommentsAtEnd.py b/python/testData/optimizeImports/stackDanglingCommentsAtEnd.py index de0b28ca68ca..b74eeec56609 100644 --- a/python/testData/optimizeImports/stackDanglingCommentsAtEnd.py +++ b/python/testData/optimizeImports/stackDanglingCommentsAtEnd.py @@ -4,6 +4,8 @@ # comment after the top-level one + + # comment for b import b diff --git a/python/testSrc/com/jetbrains/python/PyOptimizeImportsTest.java b/python/testSrc/com/jetbrains/python/PyOptimizeImportsTest.java index 8269a3b7d994..ac49b50c414d 100644 --- a/python/testSrc/com/jetbrains/python/PyOptimizeImportsTest.java +++ b/python/testSrc/com/jetbrains/python/PyOptimizeImportsTest.java @@ -273,6 +273,7 @@ public class PyOptimizeImportsTest extends PyTestCase { doTest(); } + // PY-23125 public void testStackDanglingCommentsAtEnd() { doTest(); }