PY-14838, PY-13955 Do not align function arguments when first of them contains "hanging indent"

Regardless of option "Align multiline call arguments", alignment should
take place only when there is no, so called, "hanging indent" in function
call (see PEP-8). In accordance to behavior of 'pep8' utility several such
indents at the end of line are collapsed into one. E.g. fragment
'func([{\n' is considered as having "hanging indent" both for function
call, list and dict literals.
This commit is contained in:
Mikhail Golubev
2015-01-15 20:28:52 +03:00
parent 65cc6f3fbf
commit 697afe15ec
6 changed files with 102 additions and 9 deletions

View File

@@ -243,12 +243,13 @@ public class PyBlock implements ASTBlock {
}
}
//Align elements vertically if there is an argument in the first line of parenthesized expression
else if (((parentType == PyElementTypes.PARENTHESIZED_EXPRESSION && myContext.getSettings().ALIGN_MULTILINE_PARENTHESIZED_EXPRESSION)
|| (parentType == PyElementTypes.ARGUMENT_LIST && myContext.getSettings().ALIGN_MULTILINE_PARAMETERS_IN_CALLS)
|| (parentType == PyElementTypes.PARAMETER_LIST && myContext.getSettings().ALIGN_MULTILINE_PARAMETERS)) &&
else if (!hasHangingIndent(_node.getPsi()) &&
((parentType == PyElementTypes.PARENTHESIZED_EXPRESSION && myContext.getSettings().ALIGN_MULTILINE_PARENTHESIZED_EXPRESSION) ||
(parentType == PyElementTypes.ARGUMENT_LIST && myContext.getSettings().ALIGN_MULTILINE_PARAMETERS_IN_CALLS) ||
(parentType == PyElementTypes.PARAMETER_LIST && myContext.getSettings().ALIGN_MULTILINE_PARAMETERS)) &&
!isIndentNext(child) &&
!hasLineBreaksBefore(_node.getFirstChildNode(), 1)
&& !ourListElementTypes.contains(childType)) {
!hasLineBreaksBefore(_node.getFirstChildNode(), 1) &&
!ourListElementTypes.contains(childType)) {
if (!ourBrackets.contains(childType)) {
childAlignment = getAlignmentForChildren();
@@ -325,6 +326,35 @@ public class PyBlock implements ASTBlock {
return new PyBlock(this, child, childAlignment, childIndent, wrap, myContext);
}
// Check https://www.python.org/dev/peps/pep-0008/#indentation
private boolean hasHangingIndent(@NotNull PsiElement elem) {
final PsiElement[] items;
if (elem instanceof PyCallExpression) {
final PyArgumentList argumentList = ((PyCallExpression)elem).getArgumentList();
return argumentList != null && hasHangingIndent(argumentList);
}
else if (elem instanceof PyFunction) {
return hasHangingIndent(((PyFunction)elem).getParameterList());
}
else if (elem instanceof PySequenceExpression) {
items = ((PySequenceExpression)elem).getElements();
}
else if (elem instanceof PyParameterList) {
items = ((PyParameterList)elem).getParameters();
}
else if (elem instanceof PyArgumentList) {
items = ((PyArgumentList)elem).getArguments();
}
else {
return false;
}
if (items.length == 0) {
return true;
}
final PsiElement firstItem = items[0];
return hasLineBreaksBefore(firstItem.getNode(), 1) || hasHangingIndent(firstItem);
}
private static boolean breaksAlignment(IElementType type) {
return type != PyElementTypes.BINARY_EXPRESSION;
}
@@ -412,7 +442,7 @@ public class PyBlock implements ASTBlock {
return false;
}
if (_node.getElementType() == PyElementTypes.ARGUMENT_LIST) {
if (!myContext.getSettings().ALIGN_MULTILINE_PARAMETERS_IN_CALLS) {
if (!myContext.getSettings().ALIGN_MULTILINE_PARAMETERS_IN_CALLS || hasHangingIndent(_node.getPsi())) {
return false;
}
if (child.getElementType() == PyTokenTypes.COMMA) {
@@ -427,7 +457,7 @@ public class PyBlock implements ASTBlock {
return false;
}
if (_node.getElementType() == PyElementTypes.PARAMETER_LIST) {
return myContext.getSettings().ALIGN_MULTILINE_PARAMETERS;
return !hasHangingIndent(_node.getPsi()) && myContext.getSettings().ALIGN_MULTILINE_PARAMETERS;
}
if (_node.getElementType() == PyElementTypes.SUBSCRIPTION_EXPRESSION) {
return false;

View File

@@ -0,0 +1,10 @@
def test_function(*args):
pass
test_function({
'a': 'b',
}, 5)
test_function(1,
2,
3)

View File

@@ -0,0 +1,3 @@
handler = webapp2.WSGIApplication([
('/', UserHandler),<caret>
], debug=True)

View File

@@ -0,0 +1,4 @@
handler = webapp2.WSGIApplication([
('/', UserHandler),
()
], debug=True)

View File

@@ -0,0 +1,11 @@
def test_function(*args):
pass
test_function({
'a': 'b',
}, 5)
test_function(1,
2,
3)

View File

@@ -21,6 +21,7 @@ import com.intellij.psi.PsiFile;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.python.fixtures.PyTestCase;
import com.jetbrains.python.formatter.PyCodeStyleSettings;
@@ -159,7 +160,7 @@ public class PyFormatterTest extends PyTestCase {
}
public void testNoAlignForMethodArguments() { // PY-3995
settings().getCommonSettings(PythonLanguage.getInstance()).ALIGN_MULTILINE_PARAMETERS_IN_CALLS = false;
getCommonSettings().ALIGN_MULTILINE_PARAMETERS_IN_CALLS = false;
doTest();
}
@@ -435,6 +436,36 @@ public class PyFormatterTest extends PyTestCase {
doTest();
}
// PY-14838
public void testNoAlignmentAfterDictHangingIndentInFunctionCall() {
getCommonSettings().ALIGN_MULTILINE_PARAMETERS_IN_CALLS = true;
try {
doTest();
}
finally {
settings().ALIGN_MULTILINE_PARAMETERS_IN_CALLS = false;
}
}
// PY-13955
public void testNoAlignmentAfterDictHangingIndentInFunctionCallOnTyping() {
settings().ALIGN_MULTILINE_PARAMETERS_IN_CALLS = true;
try {
final String testName = "formatter/" + getTestName(true);
myFixture.configureByFile(testName + ".py");
WriteCommandAction.runWriteCommandAction(null, new Runnable() {
@Override
public void run() {
myFixture.type("\n(");
}
});
myFixture.checkResultByFile(testName + "_after.py");
}
finally {
settings().ALIGN_MULTILINE_PARAMETERS_IN_CALLS = false;
}
}
/**
* This test merely checks that call to {@link com.intellij.psi.codeStyle.CodeStyleManager#reformat(com.intellij.psi.PsiElement)}
* is possible for Python sources.
@@ -455,7 +486,11 @@ public class PyFormatterTest extends PyTestCase {
myFixture.checkResultByFile("formatter/" + getTestName(true) + "_after.py");
}
private CommonCodeStyleSettings getCommonSettings() {
return settings().getCommonSettings(PythonLanguage.getInstance());
}
private CodeStyleSettings settings() {
return CodeStyleSettingsManager.getInstance().getSettings(myFixture.getProject());
return CodeStyleSettingsManager.getSettings(myFixture.getProject());
}
}