diff --git a/python/src/com/jetbrains/python/formatter/PyBlock.java b/python/src/com/jetbrains/python/formatter/PyBlock.java index 468faafaec7b..543e761973e9 100644 --- a/python/src/com/jetbrains/python/formatter/PyBlock.java +++ b/python/src/com/jetbrains/python/formatter/PyBlock.java @@ -90,18 +90,18 @@ public class PyBlock implements ASTBlock { private List mySubBlocks = null; private Map mySubBlockByNode = null; private final boolean myEmptySequence; - + // Shared among multiple children sub-blocks private Alignment myChildAlignment = null; private Alignment myDictAlignment = null; private Wrap myDictWrapping = null; private Wrap myFromImportWrapping = null; - public PyBlock(@Nullable PyBlock parent, - @NotNull ASTNode node, - @Nullable Alignment alignment, - @NotNull Indent indent, - @Nullable Wrap wrap, + public PyBlock(@Nullable PyBlock parent, + @NotNull ASTNode node, + @Nullable Alignment alignment, + @NotNull Indent indent, + @Nullable Wrap wrap, @NotNull PyBlockContext context) { myParent = parent; myAlignment = alignment; @@ -189,7 +189,7 @@ public class PyBlock implements ASTBlock { Wrap childWrap = null; Indent childIndent = Indent.getNoneIndent(); Alignment childAlignment = null; - + final PyCodeStyleSettings settings = myContext.getPySettings(); if (parentType == PyElementTypes.BINARY_EXPRESSION && !isInControlStatement()) { @@ -260,7 +260,7 @@ public class PyBlock implements ASTBlock { } } - + if (parentType == PyElementTypes.LIST_LITERAL_EXPRESSION || parentType == PyElementTypes.LIST_COMP_EXPRESSION) { if ((childType == PyTokenTypes.RBRACKET && !settings.HANG_CLOSING_BRACKETS) || childType == PyTokenTypes.LBRACKET) { childIndent = Indent.getNoneIndent(); @@ -617,7 +617,7 @@ public class PyBlock implements ASTBlock { myContext.getMode() == FormattingMode.ADJUST_INDENT) { return true; } - return !hasHangingIndent(myNode.getPsi()) && !(myNode.getElementType() == PyElementTypes.DICT_LITERAL_EXPRESSION && + return !hasHangingIndent(myNode.getPsi()) && !(myNode.getElementType() == PyElementTypes.DICT_LITERAL_EXPRESSION && myContext.getPySettings().DICT_NEW_LINE_AFTER_LEFT_BRACE); } if (myNode.getElementType() == PyElementTypes.ARGUMENT_LIST) { @@ -726,6 +726,13 @@ public class PyBlock implements ASTBlock { final PsiElement psi1 = node1.getPsi(); PsiElement psi2 = node2.getPsi(); + + // pycodestyle.py enforces at most 2 blank lines only between comments directly + // at the top-level of a file, not inside if, try/except, etc. + if (psi1 instanceof PsiComment && myNode.getPsi() instanceof PsiFile) { + return Spacing.createSpacing(0, 0, 1, true, 2); + } + // skip not inline comments to handles blank lines between various declarations if (psi2 instanceof PsiComment && hasLineBreaksBeforeInSameParent(node2, 1)) { final PsiElement nonCommentAfter = PyPsiUtils.getNextNonCommentSibling(psi2, true); @@ -746,7 +753,7 @@ public class PyBlock implements ASTBlock { return Spacing.createSpacing(1, 1, 0, settings.KEEP_LINE_BREAKS, settings.KEEP_BLANK_LINES_IN_CODE); } } - + if (childType1 == PyTokenTypes.COLON && psi2 instanceof PyStatementList) { if (needLineBreakInStatement()) { return Spacing.createSpacing(0, 0, 1, true, settings.KEEP_BLANK_LINES_IN_CODE); @@ -794,9 +801,6 @@ public class PyBlock implements ASTBlock { } } - if (psi2 instanceof PsiComment && !hasLineBreaksBeforeInSameParent(psi2.getNode(), 1) && pySettings.SPACE_BEFORE_NUMBER_SIGN) { - return Spacing.createSpacing(2, 0, 0, false, 0); - } } return myContext.getSpacingBuilder().getSpacing(this, child1, child2); } @@ -877,7 +881,7 @@ public class PyBlock implements ASTBlock { // delegation sometimes causes NPEs in formatter core, so we calculate the // correct indent manually. if (statementListsBelow > 0) { // was 1... strange - @SuppressWarnings("ConstantConditions") + @SuppressWarnings("ConstantConditions") final int indent = myContext.getSettings().getIndentOptions().INDENT_SIZE; return new ChildAttributes(Indent.getSpaceIndent(indent * statementListsBelow), null); } diff --git a/python/src/com/jetbrains/python/formatter/PyLanguageCodeStyleSettingsProvider.java b/python/src/com/jetbrains/python/formatter/PyLanguageCodeStyleSettingsProvider.java index 511cca5aafed..3f493e310055 100644 --- a/python/src/com/jetbrains/python/formatter/PyLanguageCodeStyleSettingsProvider.java +++ b/python/src/com/jetbrains/python/formatter/PyLanguageCodeStyleSettingsProvider.java @@ -162,6 +162,9 @@ public class PyLanguageCodeStyleSettingsProvider extends LanguageCodeStyleSettin indentOptions.INDENT_SIZE = 4; defaultSettings.ALIGN_MULTILINE_PARAMETERS_IN_CALLS = true; defaultSettings.KEEP_BLANK_LINES_IN_DECLARATIONS = 1; + // Don't set it to 2 -- this setting is used implicitly in a lot of methods related to spacing, + // e.g. in SpacingBuilder#blankLines(), and can lead to unexpected side-effects in formatter's + // behavior defaultSettings.KEEP_BLANK_LINES_IN_CODE = 1; return defaultSettings; } diff --git a/python/src/com/jetbrains/python/formatter/PythonFormattingModelBuilder.java b/python/src/com/jetbrains/python/formatter/PythonFormattingModelBuilder.java index b24cf12d6940..948d99ea42de 100644 --- a/python/src/com/jetbrains/python/formatter/PythonFormattingModelBuilder.java +++ b/python/src/com/jetbrains/python/formatter/PythonFormattingModelBuilder.java @@ -77,6 +77,8 @@ public class PythonFormattingModelBuilder implements FormattingModelBuilderEx, C final CommonCodeStyleSettings commonSettings = settings.getCommonSettings(PythonLanguage.getInstance()); return new SpacingBuilder(commonSettings) + .before(END_OF_LINE_COMMENT).spacing(2, 0, 0, commonSettings.KEEP_LINE_BREAKS, commonSettings.KEEP_BLANK_LINES_IN_CODE) + .after(END_OF_LINE_COMMENT).spacing(0, 0, 1, commonSettings.KEEP_LINE_BREAKS, commonSettings.KEEP_BLANK_LINES_IN_CODE) .between(CLASS_DECLARATION, STATEMENT_OR_DECLARATION).blankLines(commonSettings.BLANK_LINES_AROUND_CLASS) .between(STATEMENT_OR_DECLARATION, CLASS_DECLARATION).blankLines(commonSettings.BLANK_LINES_AROUND_CLASS) .between(FUNCTION_DECLARATION, STATEMENT_OR_DECLARATION).blankLines(commonSettings.BLANK_LINES_AROUND_METHOD) diff --git a/python/testData/formatter/commentsSpacing.py b/python/testData/formatter/commentsSpacing.py new file mode 100644 index 000000000000..781e7e4aecbc --- /dev/null +++ b/python/testData/formatter/commentsSpacing.py @@ -0,0 +1,35 @@ +# line comment 1 +# line comment 2 + + +# line comment 3 + +def f(): + # inner line comment 1 + # inner line comment 2 + + + # inner line comment 3 + + + x = 42 # trailing comment 1 + +# line comment 4 + + +def g(): + pass + +if True: + # semi top-level comment 1 + + + + # semi top-level comment 2 + + + # semi top-level comment 3 + + + class C: + pass \ No newline at end of file diff --git a/python/testData/formatter/commentsSpacing_after.py b/python/testData/formatter/commentsSpacing_after.py new file mode 100644 index 000000000000..97bc420ec9a2 --- /dev/null +++ b/python/testData/formatter/commentsSpacing_after.py @@ -0,0 +1,31 @@ +# line comment 1 +# line comment 2 + + +# line comment 3 + +def f(): + # inner line comment 1 + # inner line comment 2 + + # inner line comment 3 + + x = 42 # trailing comment 1 + + +# line comment 4 + + +def g(): + pass + + +if True: + # semi top-level comment 1 + + # semi top-level comment 2 + + # semi top-level comment 3 + + class C: + pass diff --git a/python/testSrc/com/jetbrains/python/PyFormatterTest.java b/python/testSrc/com/jetbrains/python/PyFormatterTest.java index c96979845745..7c793e3d1f5a 100644 --- a/python/testSrc/com/jetbrains/python/PyFormatterTest.java +++ b/python/testSrc/com/jetbrains/python/PyFormatterTest.java @@ -830,6 +830,11 @@ public class PyFormatterTest extends PyTestCase { doTest(); } + // PY-25356 + public void testCommentsSpacing() { + doTest(); + } + public void testVariableAnnotations() { runWithLanguageLevel(LanguageLevel.PYTHON36, this::doTest); }