mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-9764 Allow to force trailing comma in multiline "from" import statements
This commit is contained in:
@@ -1035,6 +1035,7 @@ formatter.align.when.multiline=Align when multiline
|
||||
formatter.collections.and.comprehensions=Collections and Comprehensions
|
||||
formatter.from.import.statements="From" Import Statements
|
||||
formatter.from.import.statements.force.parentheses=Force parentheses
|
||||
formatter.from.import.statements.force.comma.if.multline=Force trailing comma if multiline
|
||||
formatter.dictionary.literals=Dictionary literals
|
||||
formatter.hang.closing.brackets=Hang closing brackets
|
||||
|
||||
|
||||
@@ -120,6 +120,7 @@ public class PyCodeStyleSettings extends CustomCodeStyleSettings {
|
||||
CommonCodeStyleSettings.FORCE_BRACES_ALWAYS}
|
||||
)
|
||||
public int FROM_IMPORT_PARENTHESES_FORCE = CommonCodeStyleSettings.DO_NOT_FORCE;
|
||||
public boolean FROM_IMPORT_TRAILING_COMMA_IF_MULTILINE = false;
|
||||
|
||||
/**
|
||||
* Corresponds to the option of pycodestyle.py "--hang-closing". Basically, it means that the closing brace of a collection literal,
|
||||
|
||||
@@ -25,7 +25,10 @@ import com.intellij.psi.codeStyle.CodeStyleSettings;
|
||||
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
|
||||
import com.intellij.psi.impl.source.codeStyle.PostFormatProcessor;
|
||||
import com.intellij.psi.impl.source.codeStyle.PostFormatProcessorHelper;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.jetbrains.python.PyTokenTypes;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.impl.PyPsiUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -57,14 +60,24 @@ public class PyFromImportPostFormatProcessor implements PostFormatProcessor {
|
||||
@Override
|
||||
public void visitPyFromImportStatement(PyFromImportStatement node) {
|
||||
if (myHelper.isElementFullyInRange(node)) {
|
||||
final PyImportElement[] importedNames = node.getImportElements();
|
||||
final PyCodeStyleSettings pySettings = ((CodeStyleSettings)myHelper.getSettings()).getCustomSettings(PyCodeStyleSettings.class);
|
||||
final PsiElement leftParen = node.getLeftParen();
|
||||
final boolean enabledInSettings = pySettings.FROM_IMPORT_PARENTHESES_FORCE == CommonCodeStyleSettings.FORCE_BRACES_ALWAYS ||
|
||||
pySettings.FROM_IMPORT_PARENTHESES_FORCE == CommonCodeStyleSettings.FORCE_BRACES_IF_MULTILINE &&
|
||||
PostFormatProcessorHelper.isMultiline(node);
|
||||
if (enabledInSettings && importedNames.length > 1 && leftParen == null) {
|
||||
myImportStatements.add(node);
|
||||
// If non-parenthesized "from" import ends with one or more of trailing commas, the array returned by getImportElements()
|
||||
// contains empty import elements at the end
|
||||
final List<PyImportElement> importedNames = ContainerUtil.filter(node.getImportElements(), elem -> elem.getTextLength() != 0);
|
||||
if (importedNames.size() > 1) {
|
||||
|
||||
final PyCodeStyleSettings pySettings = ((CodeStyleSettings)myHelper.getSettings()).getCustomSettings(PyCodeStyleSettings.class);
|
||||
final boolean forcedParentheses = pySettings.FROM_IMPORT_PARENTHESES_FORCE == CommonCodeStyleSettings.FORCE_BRACES_ALWAYS ||
|
||||
pySettings.FROM_IMPORT_PARENTHESES_FORCE == CommonCodeStyleSettings.FORCE_BRACES_IF_MULTILINE &&
|
||||
PostFormatProcessorHelper.isMultiline(node);
|
||||
final boolean forcedComma = pySettings.FROM_IMPORT_TRAILING_COMMA_IF_MULTILINE && PostFormatProcessorHelper.isMultiline(node);
|
||||
final PyImportElement lastImportedName = importedNames.get(importedNames.size() - 1);
|
||||
final PsiElement afterLastName = PyPsiUtils.getNextNonCommentSibling(lastImportedName, true);
|
||||
final PsiElement openingParen = node.getLeftParen();
|
||||
final boolean missingComma = afterLastName == null || afterLastName.getNode().getElementType() != PyTokenTypes.COMMA;
|
||||
// Trailing comma is allowed only in "from" imports wrapped in parentheses
|
||||
if (forcedParentheses && openingParen == null || forcedComma && missingComma && openingParen != null) {
|
||||
myImportStatements.add(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -94,34 +107,55 @@ public class PyFromImportPostFormatProcessor implements PostFormatProcessor {
|
||||
|
||||
@NotNull
|
||||
private PyFromImportStatement replaceFromImport(@NotNull PyFromImportStatement fromImport) {
|
||||
final PyImportElement firstName = fromImport.getImportElements()[0];
|
||||
final String beforeFirstName = fromImport.getText().substring(0, firstName.getStartOffsetInParent());
|
||||
final StringBuilder newStatementText = new StringBuilder(beforeFirstName);
|
||||
newStatementText.append("(");
|
||||
boolean lastElementWasComment = false;
|
||||
for (PsiElement cur = firstName; cur != null; cur = cur.getNextSibling()) {
|
||||
if (cur instanceof PsiWhiteSpace) {
|
||||
newStatementText.append(cur.getText().replace("\\", ""));
|
||||
}
|
||||
else {
|
||||
newStatementText.append(cur.getText());
|
||||
}
|
||||
lastElementWasComment = cur instanceof PsiComment;
|
||||
}
|
||||
if (lastElementWasComment) {
|
||||
newStatementText.append("\n");
|
||||
}
|
||||
newStatementText.append(")");
|
||||
|
||||
final PyImportElement[] allNames = fromImport.getImportElements();
|
||||
final PyImportElement firstName = allNames[0];
|
||||
final PyElementGenerator generator = PyElementGenerator.getInstance(fromImport.getProject());
|
||||
final CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(fromImport.getProject());
|
||||
|
||||
final LanguageLevel level = LanguageLevel.forElement(fromImport);
|
||||
PyFromImportStatement newFromImport = generator.createFromText(level, PyFromImportStatement.class, newStatementText.toString());
|
||||
newFromImport = (PyFromImportStatement)fromImport.replace(newFromImport);
|
||||
newFromImport = (PyFromImportStatement)codeStyleManager.reformat(newFromImport);
|
||||
myHelper.updateResultRange(fromImport.getTextLength(), newFromImport.getTextLength());
|
||||
return newFromImport;
|
||||
|
||||
if (fromImport.getLeftParen() == null) {
|
||||
// Surround with parentheses stripping obsolete continuation backslashes and added trailing comma if necessary
|
||||
final String beforeFirstName = fromImport.getText().substring(0, firstName.getStartOffsetInParent());
|
||||
final StringBuilder newStatementText = new StringBuilder(beforeFirstName);
|
||||
newStatementText.append("(");
|
||||
boolean lastElementWasComment = false;
|
||||
int lastVisibleNameCommaOffset = -1;
|
||||
for (PsiElement cur = firstName; cur != null; cur = cur.getNextSibling()) {
|
||||
if (cur instanceof PsiWhiteSpace) {
|
||||
newStatementText.append(cur.getText().replace("\\", ""));
|
||||
}
|
||||
else {
|
||||
newStatementText.append(cur.getText());
|
||||
}
|
||||
if (cur instanceof PyImportElement && cur.getTextLength() != 0) {
|
||||
lastVisibleNameCommaOffset = newStatementText.length();
|
||||
}
|
||||
else if (lastVisibleNameCommaOffset != -1 && cur.getNode().getElementType() == PyTokenTypes.COMMA) {
|
||||
lastVisibleNameCommaOffset = -1;
|
||||
}
|
||||
lastElementWasComment = cur instanceof PsiComment;
|
||||
}
|
||||
final PyCodeStyleSettings pySettings = ((CodeStyleSettings)myHelper.getSettings()).getCustomSettings(PyCodeStyleSettings.class);
|
||||
if (lastVisibleNameCommaOffset != -1 && pySettings.FROM_IMPORT_TRAILING_COMMA_IF_MULTILINE) {
|
||||
newStatementText.insert(lastVisibleNameCommaOffset, ",");
|
||||
}
|
||||
if (lastElementWasComment) {
|
||||
newStatementText.append("\n");
|
||||
}
|
||||
newStatementText.append(")");
|
||||
|
||||
final LanguageLevel level = LanguageLevel.forElement(fromImport);
|
||||
PyFromImportStatement newFromImport = generator.createFromText(level, PyFromImportStatement.class, newStatementText.toString());
|
||||
newFromImport = (PyFromImportStatement)fromImport.replace(newFromImport);
|
||||
newFromImport = (PyFromImportStatement)codeStyleManager.reformat(newFromImport, true);
|
||||
myHelper.updateResultRange(fromImport.getTextLength(), newFromImport.getTextLength());
|
||||
return newFromImport;
|
||||
}
|
||||
else {
|
||||
// Add only trailing comma
|
||||
final PsiElement comma = fromImport.addAfter(generator.createComma().getPsi(), allNames[allNames.length - 1]);
|
||||
codeStyleManager.reformat(comma);
|
||||
return fromImport;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -135,6 +135,9 @@ public class PyLanguageCodeStyleSettingsProvider extends LanguageCodeStyleSettin
|
||||
PyBundle.message("formatter.from.import.statements.force.parentheses"),
|
||||
PyBundle.message("formatter.from.import.statements"),
|
||||
BRACE_OPTIONS, BRACE_VALUES);
|
||||
consumer.showCustomOption(PyCodeStyleSettings.class, "FROM_IMPORT_TRAILING_COMMA_IF_MULTILINE",
|
||||
PyBundle.message("formatter.from.import.statements.force.comma.if.multline"),
|
||||
PyBundle.message("formatter.from.import.statements"));
|
||||
|
||||
consumer.showCustomOption(PyCodeStyleSettings.class, "DICT_WRAPPING",
|
||||
PyBundle.message("formatter.dictionary.literals"), null, WRAP_OPTIONS, WRAP_VALUES);
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
from module import foo
|
||||
from module import foo, bar
|
||||
from module import foo, bar,
|
||||
# | margin
|
||||
from module import foo, bar, baz
|
||||
from module import foo, \
|
||||
bar
|
||||
from module import foo, \
|
||||
bar,
|
||||
|
||||
from module import foo, \
|
||||
bar # comment
|
||||
|
||||
from module import (foo,
|
||||
bar)
|
||||
|
||||
from module import (foo,
|
||||
bar,)
|
||||
|
||||
from module import (
|
||||
foo,
|
||||
bar # comment
|
||||
)
|
||||
@@ -0,0 +1,27 @@
|
||||
from module import foo
|
||||
from module import (foo,
|
||||
bar, )
|
||||
from module import (foo,
|
||||
bar, )
|
||||
# | margin
|
||||
from module import (foo, bar,
|
||||
baz, )
|
||||
from module import (foo,
|
||||
bar, )
|
||||
from module import (foo,
|
||||
bar, )
|
||||
|
||||
from module import (foo,
|
||||
bar, # comment
|
||||
)
|
||||
|
||||
from module import (foo,
|
||||
bar, )
|
||||
|
||||
from module import (foo,
|
||||
bar, )
|
||||
|
||||
from module import (
|
||||
foo,
|
||||
bar, # comment
|
||||
)
|
||||
@@ -0,0 +1,23 @@
|
||||
from module import foo
|
||||
from module import foo, bar
|
||||
from module import foo, bar,
|
||||
# | margin
|
||||
from module import foo, bar, baz
|
||||
from module import foo, \
|
||||
bar
|
||||
from module import foo, \
|
||||
bar,
|
||||
|
||||
from module import foo, \
|
||||
bar # comment
|
||||
|
||||
from module import (foo,
|
||||
bar)
|
||||
|
||||
from module import (foo,
|
||||
bar,)
|
||||
|
||||
from module import (
|
||||
foo,
|
||||
bar # comment
|
||||
)
|
||||
@@ -0,0 +1,24 @@
|
||||
from module import foo
|
||||
from module import foo, bar
|
||||
from module import foo, bar,
|
||||
# | margin
|
||||
from module import foo, bar, \
|
||||
baz
|
||||
from module import foo, \
|
||||
bar
|
||||
from module import foo, \
|
||||
bar,
|
||||
|
||||
from module import foo, \
|
||||
bar # comment
|
||||
|
||||
from module import (foo,
|
||||
bar, )
|
||||
|
||||
from module import (foo,
|
||||
bar, )
|
||||
|
||||
from module import (
|
||||
foo,
|
||||
bar, # comment
|
||||
)
|
||||
@@ -1,15 +1,15 @@
|
||||
from module import foo
|
||||
from module import (
|
||||
foo,
|
||||
bar
|
||||
bar,
|
||||
)
|
||||
from module import (
|
||||
foo,
|
||||
bar
|
||||
bar,
|
||||
)
|
||||
from module import (
|
||||
foo,
|
||||
bar # comment
|
||||
bar, # comment
|
||||
)
|
||||
from module import (
|
||||
foo,
|
||||
|
||||
@@ -786,10 +786,27 @@ public class PyFormatterTest extends PyTestCase {
|
||||
getPythonCodeStyleSettings().FROM_IMPORT_NEW_LINE_AFTER_LEFT_PARENTHESIS = true;
|
||||
getPythonCodeStyleSettings().FROM_IMPORT_NEW_LINE_BEFORE_RIGHT_PARENTHESIS = true;
|
||||
getPythonCodeStyleSettings().FROM_IMPORT_WRAPPING = WrapType.ALWAYS.getLegacyRepresentation();
|
||||
getPythonCodeStyleSettings().FROM_IMPORT_TRAILING_COMMA_IF_MULTILINE = true;
|
||||
getPythonCodeStyleSettings().HANG_CLOSING_BRACKETS = true;
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-9764
|
||||
public void testFromImportTrailingCommaWithParentheses() {
|
||||
getCodeStyleSettings().setRightMargin(PythonLanguage.INSTANCE, 30);
|
||||
getPythonCodeStyleSettings().FROM_IMPORT_PARENTHESES_FORCE = CommonCodeStyleSettings.FORCE_BRACES_ALWAYS;
|
||||
getPythonCodeStyleSettings().FROM_IMPORT_TRAILING_COMMA_IF_MULTILINE = true;
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-9764
|
||||
public void testFromImportTrailingCommaWithoutParentheses() {
|
||||
getCodeStyleSettings().setRightMargin(PythonLanguage.INSTANCE, 30);
|
||||
getPythonCodeStyleSettings().FROM_IMPORT_PARENTHESES_FORCE = CommonCodeStyleSettings.DO_NOT_FORCE;
|
||||
getPythonCodeStyleSettings().FROM_IMPORT_TRAILING_COMMA_IF_MULTILINE = true;
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testVariableAnnotations() {
|
||||
runWithLanguageLevel(LanguageLevel.PYTHON36, this::doTest);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user