Skip to content

Commit c9cf376

Browse files
author
Espen Nilsen
committed
Support multiple_blank_lines_between_functions
1 parent 1200509 commit c9cf376

File tree

4 files changed

+97
-1
lines changed

4 files changed

+97
-1
lines changed

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,21 @@ optional arguments:
423423
pass
424424
```
425425

426+
#### `BLANK_LINES_BETWEEN_CLASS_DEFS`
427+
428+
> Sets the number of desired blank lines between methods inside
429+
> class definitions. For example:
430+
431+
```python
432+
class Foo:
433+
def method1():
434+
pass
435+
# <------ multiple
436+
# <------ blank lines here
437+
def method2():
438+
pass
439+
```
440+
426441
#### `BLANK_LINE_BEFORE_CLASS_DOCSTRING`
427442

428443
> Insert a blank line before a class-level docstring.

yapf/pytree/blank_line_calculator.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,16 +62,25 @@ def __init__(self):
6262
self.last_comment_lineno = 0
6363
self.last_was_decorator = False
6464
self.last_was_class_or_function = False
65+
self._prev_stmt = None
6566

6667
def Visit_simple_stmt(self, node): # pylint: disable=invalid-name
6768
self.DefaultNodeVisit(node)
6869
if node.children[0].type == grammar_token.COMMENT:
6970
self.last_comment_lineno = node.children[0].lineno
71+
else:
72+
# Do NOT set _prev_stmt on pure comment lines; keep the last real stmt.
73+
self._prev_stmt = node
7074

7175
def Visit_decorator(self, node): # pylint: disable=invalid-name
76+
func = _DecoratedFuncdef(node)
7277
if (self.last_comment_lineno and
7378
self.last_comment_lineno == node.children[0].lineno - 1):
7479
_SetNumNewlines(node.children[0], _NO_BLANK_LINES)
80+
elif self.last_was_decorator:
81+
_SetNumNewlines(node.children[0], _NO_BLANK_LINES)
82+
elif func is not None and self._prev_stmt is not None and _MethodsInSameClass(self._prev_stmt, func):
83+
_SetNumNewlines(node.children[0], max(_ONE_BLANK_LINE, 1 + style.Get('BLANK_LINES_BETWEEN_CLASS_DEFS')))
7584
else:
7685
_SetNumNewlines(node.children[0], self._GetNumNewlines(node))
7786
for child in node.children:
@@ -87,6 +96,7 @@ def Visit_classdef(self, node): # pylint: disable=invalid-name
8796
self.Visit(child)
8897
self.class_level -= 1
8998
self.last_was_class_or_function = True
99+
self._prev_stmt = node
90100

91101
def Visit_funcdef(self, node): # pylint: disable=invalid-name
92102
self.last_was_class_or_function = False
@@ -103,6 +113,7 @@ def Visit_funcdef(self, node): # pylint: disable=invalid-name
103113
self.Visit(child)
104114
self.function_level -= 1
105115
self.last_was_class_or_function = True
116+
self._prev_stmt = node
106117

107118
def DefaultNodeVisit(self, node):
108119
"""Override the default visitor for Node.
@@ -156,7 +167,11 @@ def _GetNumNewlines(self, node):
156167
return _NO_BLANK_LINES
157168
elif self._IsTopLevel(node):
158169
return 1 + style.Get('BLANK_LINES_AROUND_TOP_LEVEL_DEFINITION')
159-
return _ONE_BLANK_LINE
170+
elif self._prev_stmt is not None and _MethodsInSameClass(self._prev_stmt, node):
171+
# Only between consecutive methods *in the same class*.
172+
# Keep at least one blank line as a floor (to avoid 0 if user misconfigures).
173+
return max(_ONE_BLANK_LINE, 1 + style.Get('BLANK_LINES_BETWEEN_CLASS_DEFS'))
174+
return _NO_BLANK_LINES
160175

161176
def _IsTopLevel(self, node):
162177
return (not (self.class_level or self.function_level) and
@@ -175,3 +190,24 @@ def _StartsInZerothColumn(node):
175190

176191
def _AsyncFunction(node):
177192
return (node.prev_sibling and node.prev_sibling.type == grammar_token.ASYNC)
193+
194+
195+
def _MethodsInSameClass(prev_node, curr_node):
196+
# 1) Walk up from each node to find the nearest *enclosing function* (def …).
197+
prev_func = pytree_utils.EnclosingFunc(prev_node)
198+
curr_func = pytree_utils.EnclosingFunc(curr_node)
199+
200+
# 2) If either enclosing thing is not actually a function definition, bail out.
201+
if not (pytree_utils.IsFuncDef(prev_func) and pytree_utils.IsFuncDef(curr_func)):
202+
return False
203+
204+
# 3) From each function, walk up to find the *enclosing class* (class …).
205+
prev_cls = pytree_utils.EnclosingClass(prev_func.parent)
206+
curr_cls = pytree_utils.EnclosingClass(curr_func.parent)
207+
208+
# 4) True only if both functions live inside a class, and it’s the *same class node*.
209+
return prev_cls is not None and prev_cls is curr_cls
210+
211+
212+
def _DecoratedFuncdef(node):
213+
return pytree_utils.DecoratedTarget(node, ('funcdef',))

yapf/pytree/pytree_utils.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,3 +332,34 @@ def _PytreeNodeRepr(node):
332332
def IsCommentStatement(node):
333333
return (NodeName(node) == 'simple_stmt' and
334334
node.children[0].type == token.COMMENT)
335+
336+
337+
def AscendTo(node, target_names):
338+
n = node
339+
while n is not None and NodeName(n) not in target_names:
340+
n = getattr(n, 'parent', None)
341+
return n if n is not None and NodeName(n) in target_names else None
342+
343+
344+
def EnclosingFunc(node):
345+
return node if NodeName(node) == 'funcdef' else AscendTo(node, {'funcdef'})
346+
347+
348+
def EnclosingClass(node):
349+
return node if NodeName(node) == 'classdef' else AscendTo(node, {'classdef'})
350+
351+
352+
def IsFuncDef(node):
353+
return node is not None and NodeName(node) == 'funcdef'
354+
355+
356+
def IsClassDef(node):
357+
return node is not None and NodeName(node) == 'classdef'
358+
359+
360+
def DecoratedTarget(node, target_names=('funcdef', 'classdef')):
361+
n = node
362+
while n.next_sibling is not None and NodeName(n.next_sibling) == 'decorator':
363+
n = n.next_sibling
364+
cand = n.next_sibling
365+
return cand if cand is not None and NodeName(cand) in target_names else None

yapf/yapflib/style.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,18 @@ class Foo:
115115
def method():
116116
pass
117117
"""),
118+
BLANK_LINES_BETWEEN_CLASS_DEFS=textwrap.dedent("""\
119+
Sets the number of desired blank lines between methods inside
120+
class definitions. For example:
121+
122+
class Foo:
123+
def method1():
124+
pass
125+
# <------ multiple
126+
# <------ blank lines
127+
def method2():
128+
pass
129+
"""),
118130
BLANK_LINES_AROUND_TOP_LEVEL_DEFINITION=textwrap.dedent("""\
119131
Number of blank lines surrounding top-level function and class
120132
definitions.
@@ -486,6 +498,7 @@ def CreatePEP8Style():
486498
BLANK_LINE_BEFORE_MODULE_DOCSTRING=False,
487499
BLANK_LINE_BEFORE_NESTED_CLASS_OR_DEF=True,
488500
BLANK_LINES_AROUND_TOP_LEVEL_DEFINITION=2,
501+
BLANK_LINES_BETWEEN_CLASS_DEFS=1,
489502
BLANK_LINES_BETWEEN_TOP_LEVEL_IMPORTS_AND_VARIABLES=1,
490503
COALESCE_BRACKETS=False,
491504
COLUMN_LIMIT=79,
@@ -675,6 +688,7 @@ def _IntOrIntListConverter(s):
675688
BLANK_LINE_BEFORE_MODULE_DOCSTRING=_BoolConverter,
676689
BLANK_LINE_BEFORE_NESTED_CLASS_OR_DEF=_BoolConverter,
677690
BLANK_LINES_AROUND_TOP_LEVEL_DEFINITION=int,
691+
BLANK_LINES_BETWEEN_CLASS_DEFS=int,
678692
BLANK_LINES_BETWEEN_TOP_LEVEL_IMPORTS_AND_VARIABLES=int,
679693
COALESCE_BRACKETS=_BoolConverter,
680694
COLUMN_LIMIT=int,

0 commit comments

Comments
 (0)