PY-19705 Add blanks lines around methods of a class as required by PEP 8

This commit is contained in:
Mikhail Golubev
2017-06-14 11:50:07 +03:00
parent a06277bc84
commit e6ec37ccfe
27 changed files with 194 additions and 17 deletions

View File

@@ -719,6 +719,8 @@ public class PyBlock implements ASTBlock {
@Override
@Nullable
public Spacing getSpacing(@Nullable Block child1, @NotNull Block child2) {
final CommonCodeStyleSettings settings = myContext.getSettings();
final PyCodeStyleSettings pySettings = myContext.getPySettings();
if (child1 instanceof ASTBlock && child2 instanceof ASTBlock) {
final ASTNode node1 = ((ASTBlock)child1).getNode();
ASTNode node2 = ((ASTBlock)child2).getNode();
@@ -727,6 +729,22 @@ public class PyBlock implements ASTBlock {
PsiElement psi2 = node2.getPsi();
if (psi2 instanceof PyStatementList) {
// Quite odd getSpacing() doesn't get called with child1=null for the first statement
// in the statement list of a class, yet it does get called for the preceding colon and
// the statement list itself. Hence we have to handle blank lines around methods here in
// addition to SpacingBuilder.
if (myNode.getElementType() == PyElementTypes.CLASS_DECLARATION) {
final PyStatement[] statements = ((PyStatementList)psi2).getStatements();
if (statements.length > 0 && statements[0] instanceof PyFunction) {
return getBlankLinesForOption(settings.BLANK_LINES_AROUND_METHOD);
}
}
if (childType1 == PyTokenTypes.COLON && needLineBreakInStatement()) {
return Spacing.createSpacing(0, 0, 1, true, settings.KEEP_BLANK_LINES_IN_CODE);
}
}
// 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) {
@@ -744,8 +762,6 @@ public class PyBlock implements ASTBlock {
final IElementType childType2 = psi2.getNode().getElementType();
//noinspection ConstantConditions
child2 = getSubBlockByNode(node2);
final CommonCodeStyleSettings settings = myContext.getSettings();
final PyCodeStyleSettings pySettings = myContext.getPySettings();
if ((childType1 == PyTokenTypes.EQ || childType2 == PyTokenTypes.EQ)) {
final PyNamedParameter namedParameter = as(myNode.getPsi(), PyNamedParameter.class);
@@ -754,19 +770,13 @@ public class PyBlock implements ASTBlock {
}
}
if (childType1 == PyTokenTypes.COLON && psi2 instanceof PyStatementList) {
if (needLineBreakInStatement()) {
return Spacing.createSpacing(0, 0, 1, true, settings.KEEP_BLANK_LINES_IN_CODE);
}
}
if (psi1 instanceof PyImportStatementBase) {
if (psi2 instanceof PyImportStatementBase) {
final Boolean leftImportIsGroupStart = psi1.getCopyableUserData(IMPORT_GROUP_BEGIN);
final Boolean rightImportIsGroupStart = psi2.getCopyableUserData(IMPORT_GROUP_BEGIN);
// Cleanup user data, it's no longer needed
psi1.putCopyableUserData(IMPORT_GROUP_BEGIN, null);
// Don't remove IMPORT_GROUP_BEGIN from the element psi2 yet, because spacing is constructed pairwise:
// Don't remove IMPORT_GROUP_BEGIN from the element psi2 yet, because spacing is constructed pairwise:
// it might be needed on the next iteration.
//psi2.putCopyableUserData(IMPORT_GROUP_BEGIN, null);
if (rightImportIsGroupStart != null) {
@@ -962,7 +972,7 @@ public class PyBlock implements ASTBlock {
}
}
else if (lastChild != null && PyElementTypes.LIST_LIKE_EXPRESSIONS.contains(lastChild.getElementType())) {
// handle pressing enter at the end of a list literal when there's no closing paren or bracket
// handle pressing enter at the end of a list literal when there's no closing paren or bracket
final ASTNode lastLastChild = lastChild.getLastChildNode();
if (lastLastChild != null && lastLastChild.getPsi() instanceof PsiErrorElement) {
// we're at a place like this: [foo, ... bar, <caret>

View File

@@ -79,13 +79,10 @@ public class PythonFormattingModelBuilder implements FormattingModelBuilderEx, C
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)
.between(STATEMENT_OR_DECLARATION, FUNCTION_DECLARATION).blankLines(commonSettings.BLANK_LINES_AROUND_METHOD)
.after(FUNCTION_DECLARATION).blankLines(commonSettings.BLANK_LINES_AROUND_METHOD)
.after(CLASS_DECLARATION).blankLines(commonSettings.BLANK_LINES_AROUND_CLASS)
// Remove excess blank lines between imports (at most one is allowed).
// Top-level definitions are supposed to be handled in PyBlock#getSpacing
.around(CLASS_DECLARATION).blankLines(commonSettings.BLANK_LINES_AROUND_CLASS)
.around(FUNCTION_DECLARATION).blankLines(commonSettings.BLANK_LINES_AROUND_METHOD)
// Remove excess blank lines between imports (at most one is allowed).
// Note that ImportOptimizer gets rid of them anyway.
// Empty lines between import groups are handles in PyBlock#getSpacing
.between(IMPORT_STATEMENTS, IMPORT_STATEMENTS).spacing(0, Integer.MAX_VALUE, 1, false, 1)

View File

@@ -2,6 +2,7 @@ from unittest import TestCase
class Spam(TestCase):
def eggs(self):
self.fail()

View File

@@ -1,4 +1,5 @@
class Checkpoints(webapp2.RequestHandler):
def get(self):
self.response.write(json.dumps({"meta": {"code": 400,
"errorType": "paramError",

View File

@@ -2,5 +2,6 @@ from unittest import TestCase
class MyTest(TestCase):
def test_pass(self):
self.assertEqual(1 + 1, 2)

View File

@@ -1,4 +1,5 @@
class Adjunct:
def apply(self, right, arg):
pass

View File

@@ -1,4 +1,5 @@
class C:
def foo(self):
pass

View File

@@ -0,0 +1,65 @@
class C1:
# comment 1
# comment 2
def __init__(self):
pass
class C2:
# comment 2
def __init__(self):
pass
class C3:
def __init__(self):
pass
class C4:
"""Docstring."""
# comment 1
# comment 2
def __init__(self):
pass
class C5:
"""Docstring."""
# comment 2
def __init__(self):
pass
class C6:
"""Docstring."""
def __init__(self):
pass
class C7:
attr = 42
# comment 1
# comment 2
def __init__(self):
pass
class C8:
attr = 42
# comment 2
def __init__(self):
pass
class C9:
attr = 42
def __init__(self):
pass
class C10: # comment before statement list
def __init__(self):
pass

View File

@@ -0,0 +1,76 @@
class C1:
# comment 1
# comment 2
def __init__(self):
pass
class C2:
# comment 2
def __init__(self):
pass
class C3:
def __init__(self):
pass
class C4:
"""Docstring."""
# comment 1
# comment 2
def __init__(self):
pass
class C5:
"""Docstring."""
# comment 2
def __init__(self):
pass
class C6:
"""Docstring."""
def __init__(self):
pass
class C7:
attr = 42
# comment 1
# comment 2
def __init__(self):
pass
class C8:
attr = 42
# comment 2
def __init__(self):
pass
class C9:
attr = 42
def __init__(self):
pass
class C10: # comment before statement list
def __init__(self):
pass

View File

@@ -1,4 +1,5 @@
class T1(object):
def m1(self):
pass
@@ -6,5 +7,6 @@ class T1(object):
# comment about T2
class T2(object):
def m2(self):
pass

View File

@@ -1,4 +1,5 @@
class Foo(object):
def wrong_blank_lines(self):
pass

View File

@@ -1,4 +1,5 @@
class A(object):
def foo(self):
pass

View File

@@ -2,5 +2,6 @@ class Dialog:
def validate(self): pass
class B(Dialog):
def validate(self):
<selection>Dialog.validate(self)</selection>

View File

@@ -1,4 +1,5 @@
class A:
def y(self):
pass

View File

@@ -6,6 +6,7 @@ def f(x):
class Child(Base):
def __init__(self):
super(Child, self).__init__()

View File

@@ -1,4 +1,5 @@
class Ancestor(object):
def __init__(self, a, b):
self.a = a
self.b = b
@@ -8,5 +9,6 @@ class Ancestor(object):
class Basic(Ancestor):
def func2(self):
return self.func1()

View File

@@ -3,6 +3,7 @@ from shared_module import module_function as my_function, ModuleClass
class NewParent(object):
def do_useful_stuff(self):
i = shared_module.MODULE_CONTANT
my_function()

View File

@@ -1,4 +1,5 @@
class Parent(object):
def __init__(self):
self.eggs = 12

View File

@@ -7,6 +7,7 @@ abstractmethod()
class NewParent(metaclass=ABCMeta):
@classmethod
@abstractmethod
def foo_method(cls):

View File

@@ -3,5 +3,6 @@ A = 1
class Suppa:
def foo(self):
print "bar"

View File

@@ -1,3 +1,4 @@
class Suppa:
def foo(self):
print "bar"

View File

@@ -20,6 +20,7 @@ class ToClass(object):
class FromClass(ToClass):
def __init__(self):
pass

View File

@@ -1,4 +1,5 @@
class Parent(object):
def spam(self):
pass

View File

@@ -1,4 +1,5 @@
class Suppa:
def foo(self):
print "bar"

View File

@@ -1,4 +1,5 @@
class Suppa(object):
def foo(self):
print "bar"

View File

@@ -2,5 +2,6 @@ from SuperClass import SuperClass
class AnyClass(SuperClass):
def __init__(self):
super().__init__()

View File

@@ -835,6 +835,11 @@ public class PyFormatterTest extends PyTestCase {
doTest();
}
// PY-19705
public void testBlankLinesAroundFirstMethod() {
doTest();
}
public void testVariableAnnotations() {
runWithLanguageLevel(LanguageLevel.PYTHON36, this::doTest);
}