diff --git a/python/src/com/jetbrains/python/formatter/PyBlock.java b/python/src/com/jetbrains/python/formatter/PyBlock.java index 7ca5abdf9a29..4d3be1c9bf87 100644 --- a/python/src/com/jetbrains/python/formatter/PyBlock.java +++ b/python/src/com/jetbrains/python/formatter/PyBlock.java @@ -49,10 +49,10 @@ import static com.jetbrains.python.psi.PyUtil.as; public class PyBlock implements ASTBlock { private static final TokenSet ourListElementTypes = TokenSet.create(PyElementTypes.LIST_LITERAL_EXPRESSION, PyElementTypes.LIST_COMP_EXPRESSION, - PyElementTypes.DICT_COMP_EXPRESSION, - PyElementTypes.SET_COMP_EXPRESSION, PyElementTypes.DICT_LITERAL_EXPRESSION, + PyElementTypes.DICT_COMP_EXPRESSION, PyElementTypes.SET_LITERAL_EXPRESSION, + PyElementTypes.SET_COMP_EXPRESSION, PyElementTypes.ARGUMENT_LIST, PyElementTypes.PARAMETER_LIST, PyElementTypes.TUPLE_EXPRESSION, @@ -66,8 +66,11 @@ public class PyBlock implements ASTBlock { PyTokenTypes.LBRACKET, PyTokenTypes.RBRACKET); private static final TokenSet ourHangingIndentOwners = TokenSet.create(PyElementTypes.LIST_LITERAL_EXPRESSION, + PyElementTypes.LIST_COMP_EXPRESSION, PyElementTypes.DICT_LITERAL_EXPRESSION, + PyElementTypes.DICT_COMP_EXPRESSION, PyElementTypes.SET_LITERAL_EXPRESSION, + PyElementTypes.SET_COMP_EXPRESSION, PyElementTypes.ARGUMENT_LIST, PyElementTypes.PARAMETER_LIST, PyElementTypes.TUPLE_EXPRESSION, @@ -315,9 +318,9 @@ public class PyBlock implements ASTBlock { } } else if (parentType == PyElementTypes.GENERATOR_EXPRESSION || parentType == PyElementTypes.PARENTHESIZED_EXPRESSION) { - final boolean insideTuple = parentType == PyElementTypes.PARENTHESIZED_EXPRESSION && - myNode.getPsi(PyParenthesizedExpression.class).getContainedExpression() instanceof PyTupleExpression; - if ((childType == PyTokenTypes.RPAR && !(insideTuple && settings.HANG_CLOSING_BRACKETS)) || + final boolean tupleOrGenerator = parentType == PyElementTypes.GENERATOR_EXPRESSION || + myNode.getPsi(PyParenthesizedExpression.class).getContainedExpression() instanceof PyTupleExpression; + if ((childType == PyTokenTypes.RPAR && !(tupleOrGenerator && settings.HANG_CLOSING_BRACKETS)) || !hasLineBreaksBeforeInSameParent(child, 1)) { childIndent = Indent.getNoneIndent(); } @@ -471,12 +474,11 @@ public class PyBlock implements ASTBlock { if (hasLineBreakAfterIgnoringComments(firstChildNode)) { return true; } - final PsiElement[] items = getItems(elem); - if (items.length == 0) { + final PsiElement firstItem = getFirstItem(elem); + if (firstItem == null) { return !PyTokenTypes.CLOSE_BRACES.contains(elem.getLastChild().getNode().getElementType()); } else { - final PsiElement firstItem = items[0]; if (firstItem instanceof PyNamedParameter) { final PyExpression defaultValue = ((PyNamedParameter)firstItem).getDefaultValue(); return defaultValue != null && hasHangingIndent(defaultValue); @@ -497,30 +499,34 @@ public class PyBlock implements ASTBlock { } } - @NotNull - private static PsiElement[] getItems(@NotNull PsiElement elem) { + @Nullable + private static PsiElement getFirstItem(@NotNull PsiElement elem) { + PsiElement[] items = PsiElement.EMPTY_ARRAY; if (elem instanceof PySequenceExpression) { - return ((PySequenceExpression)elem).getElements(); + items = ((PySequenceExpression)elem).getElements(); } else if (elem instanceof PyParameterList) { - return ((PyParameterList)elem).getParameters(); + items = ((PyParameterList)elem).getParameters(); } else if (elem instanceof PyArgumentList) { - return ((PyArgumentList)elem).getArguments(); + items = ((PyArgumentList)elem).getArguments(); } else if (elem instanceof PyFromImportStatement) { - return ((PyFromImportStatement)elem).getImportElements(); + items = ((PyFromImportStatement)elem).getImportElements(); } else if (elem instanceof PyParenthesizedExpression) { final PyExpression containedExpression = ((PyParenthesizedExpression)elem).getContainedExpression(); if (containedExpression instanceof PyTupleExpression) { - return ((PyTupleExpression)containedExpression).getElements(); + items = ((PyTupleExpression)containedExpression).getElements(); } else if (containedExpression != null) { - return new PsiElement[]{containedExpression}; + return containedExpression; } } - return PsiElement.EMPTY_ARRAY; + else if (elem instanceof PyComprehensionElement) { + return ((PyComprehensionElement)elem).getResultExpression(); + } + return ArrayUtil.getFirstElement(items); } private static boolean breaksAlignment(IElementType type) { diff --git a/python/testData/formatter/hangClosingOffComprehensionsAndGeneratorExpressions.py b/python/testData/formatter/hangClosingOffComprehensionsAndGeneratorExpressions.py new file mode 100644 index 000000000000..41b23d924ee9 --- /dev/null +++ b/python/testData/formatter/hangClosingOffComprehensionsAndGeneratorExpressions.py @@ -0,0 +1,23 @@ +xs1 = [ + x + for x in range(10) + if True + ] + +xs2 = { + x + for x in range(10) + if True + } + +xs3 = { + x: None + for x in range(10) + if True + } + +xs4 = ( + x + for x in range(10) + if True + ) diff --git a/python/testData/formatter/hangClosingOffComprehensionsAndGeneratorExpressions_after.py b/python/testData/formatter/hangClosingOffComprehensionsAndGeneratorExpressions_after.py new file mode 100644 index 000000000000..1814d2c676a7 --- /dev/null +++ b/python/testData/formatter/hangClosingOffComprehensionsAndGeneratorExpressions_after.py @@ -0,0 +1,23 @@ +xs1 = [ + x + for x in range(10) + if True +] + +xs2 = { + x + for x in range(10) + if True +} + +xs3 = { + x: None + for x in range(10) + if True +} + +xs4 = ( + x + for x in range(10) + if True +) diff --git a/python/testData/formatter/hangClosingOnComprehensionsAndGeneratorExpressions.py b/python/testData/formatter/hangClosingOnComprehensionsAndGeneratorExpressions.py new file mode 100644 index 000000000000..41b23d924ee9 --- /dev/null +++ b/python/testData/formatter/hangClosingOnComprehensionsAndGeneratorExpressions.py @@ -0,0 +1,23 @@ +xs1 = [ + x + for x in range(10) + if True + ] + +xs2 = { + x + for x in range(10) + if True + } + +xs3 = { + x: None + for x in range(10) + if True + } + +xs4 = ( + x + for x in range(10) + if True + ) diff --git a/python/testData/formatter/hangClosingOnComprehensionsAndGeneratorExpressions_after.py b/python/testData/formatter/hangClosingOnComprehensionsAndGeneratorExpressions_after.py new file mode 100644 index 000000000000..41b23d924ee9 --- /dev/null +++ b/python/testData/formatter/hangClosingOnComprehensionsAndGeneratorExpressions_after.py @@ -0,0 +1,23 @@ +xs1 = [ + x + for x in range(10) + if True + ] + +xs2 = { + x + for x in range(10) + if True + } + +xs3 = { + x: None + for x in range(10) + if True + } + +xs4 = ( + x + for x in range(10) + if True + ) diff --git a/python/testSrc/com/jetbrains/python/PyFormatterTest.java b/python/testSrc/com/jetbrains/python/PyFormatterTest.java index 913a5386859e..4020827704e0 100644 --- a/python/testSrc/com/jetbrains/python/PyFormatterTest.java +++ b/python/testSrc/com/jetbrains/python/PyFormatterTest.java @@ -700,6 +700,19 @@ public class PyFormatterTest extends PyTestCase { doTest(); } + // PY-15874 + public void testHangClosingOffComprehensionsAndGeneratorExpressions() { + getPythonCodeStyleSettings().HANG_CLOSING_BRACKETS = false; + doTest(); + } + + // PY-15874 + public void testHangClosingOnComprehensionsAndGeneratorExpressions() { + getPythonCodeStyleSettings().HANG_CLOSING_BRACKETS = true; + doTest(); + } + + public void testVariableAnnotations() { runWithLanguageLevel(LanguageLevel.PYTHON36, this::doTest); }