Skip to content
This repository was archived by the owner on Jan 13, 2026. It is now read-only.

Commit bf22809

Browse files
committed
Implement support for blank_lines.keep_maximum styles
1 parent 0c93349 commit bf22809

File tree

3 files changed

+65
-27
lines changed

3 files changed

+65
-27
lines changed

rewrite/rewrite/python/_parser_visitor.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -734,10 +734,7 @@ def visit_GeneratorExp(self, node):
734734
)
735735

736736
def visit_Expr(self, node):
737-
return py.ExpressionStatement(
738-
random_id(),
739-
self.__convert(node.value)
740-
)
737+
return self.__convert(node.value)
741738

742739
def visit_Yield(self, node):
743740
return py.StatementExpression(
@@ -1941,6 +1938,15 @@ def __convert_type_mapper(self, node) -> Optional[TypeTree]:
19411938
def __convert(self, node) -> Optional[J]:
19421939
return self.__convert_internal(node, self.__convert)
19431940

1941+
def __convert_statement(self, node) -> Optional[J]:
1942+
converted = self.__convert_internal(node, self.__convert_statement)
1943+
if is_of_type(converted, Statement):
1944+
return converted
1945+
return py.ExpressionStatement(
1946+
random_id(),
1947+
converted
1948+
)
1949+
19441950
def __convert_internal(self, node, recursion, mapping = None) -> Optional[J]:
19451951
if not node or not isinstance(node, ast.expr) or isinstance(node, ast.GeneratorExp):
19461952
return self.visit(cast(ast.AST, node)) if node else None
@@ -2065,7 +2071,7 @@ def __convert_block(self, statements: Sequence, prefix: str = ':') -> j.Block:
20652071
)
20662072

20672073
def __pad_statement(self, stmt: ast.stmt) -> JRightPadded[Statement]:
2068-
statement = self.__convert(stmt)
2074+
statement = self.__convert_statement(stmt)
20692075
# use whitespace until end of line as padding; what follows will be the prefix of next element
20702076
save_cursor = self._cursor
20712077
padding = self.__whitespace('\n')

rewrite/rewrite/python/format/blank_lines.py

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def visit_statement(self, statement: Statement, p: P) -> J:
4141
else:
4242
min_lines = max(self._style.minimum.around_top_level_classes_functions if isinstance(statement, (ClassDeclaration, MethodDeclaration)) else 0,
4343
self._style.minimum.after_top_level_imports if prev_import else 0)
44-
statement = minimum_lines_for_tree(statement, min_lines)
44+
statement = _adjusted_lines_for_tree(statement, min_lines, self._style.keep_maximum.in_declarations)
4545
else:
4646
in_block = isinstance(parent_cursor.value, Block)
4747
in_class = in_block and isinstance(parent_cursor.parent_tree_cursor().value, ClassDeclaration)
@@ -54,9 +54,16 @@ def visit_statement(self, statement: Statement, p: P) -> J:
5454
min_lines = max(min_lines, self._style.minimum.around_class)
5555
elif is_first and isinstance(statement, MethodDeclaration):
5656
min_lines = max(min_lines, self._style.minimum.before_first_method)
57+
58+
# This seems to correspond to how IntelliJ interprets this configuration
59+
max_lines = self._style.keep_maximum.in_declarations if \
60+
isinstance(statement, (ClassDeclaration, MethodDeclaration)) else \
61+
self._style.keep_maximum.in_code
62+
5763
if prev_import:
5864
min_lines = max(min_lines, self._style.minimum.after_local_imports)
59-
statement = minimum_lines_for_tree(statement, min_lines)
65+
66+
statement = _adjusted_lines_for_tree(statement, min_lines, max_lines)
6067
return statement
6168

6269
def post_visit(self, tree: T, p: P) -> Optional[T]:
@@ -68,39 +75,35 @@ def visit(self, tree: Optional[Tree], p: P, parent: Optional[Cursor] = None) ->
6875
return tree if self._stop else super().visit(tree, p, parent)
6976

7077

71-
def minimum_lines_for_right_padded(tree: JRightPadded[J2], min_lines) -> JRightPadded[J2]:
72-
return tree.with_element(minimum_lines_for_tree(tree.element, min_lines))
78+
def _adjusted_lines_for_right_padded(tree: JRightPadded[J2], min_lines: int, max_lines: int) -> JRightPadded[J2]:
79+
return tree.with_element(_adjusted_lines_for_tree(tree.element, min_lines, max_lines))
7380

7481

75-
def minimum_lines_for_tree(tree: J, min_lines) -> J:
76-
if min_lines == 0:
77-
return tree
78-
return tree.with_prefix(minimum_lines_for_space(tree.prefix, min_lines))
82+
def _adjusted_lines_for_tree(tree: J, min_lines: int, max_lines: int) -> J:
83+
return tree.with_prefix(_adjusted_lines_for_space(tree.prefix, min_lines, max_lines))
7984

8085

81-
def minimum_lines_for_space(prefix: Space, min_lines) -> Space:
82-
if min_lines == 0:
83-
return prefix
86+
def _adjusted_lines_for_space(prefix: Space, min_lines: int, max_lines: int) -> Space:
8487
if not prefix.comments or \
8588
'\n' in prefix.whitespace or \
8689
(prefix.comments[0].multiline and '\n' in prefix.comments[0].text):
87-
return prefix.with_whitespace(minimum_lines_for_string(prefix.whitespace, min_lines))
90+
return prefix.with_whitespace(_adjusted_lines_for_string(prefix.whitespace, min_lines, max_lines))
8891

8992
# the first comment is a trailing comment on the previous line
90-
c0 = prefix.comments[0].with_suffix(minimum_lines_for_string(prefix.comments[0].suffix, min_lines))
93+
c0 = prefix.comments[0].with_suffix(_adjusted_lines_for_string(prefix.comments[0].suffix, min_lines, max_lines))
9194
return prefix if c0 is prefix.comments[0] else prefix.with_comments([c0] + prefix.comments[1:])
9295

9396

94-
def minimum_lines_for_string(whitespace, min_lines):
95-
if min_lines == 0:
97+
def _adjusted_lines_for_string(whitespace, min_lines: int, max_lines: int):
98+
existing_blank_lines = max(_count_line_breaks(whitespace) - 1, 0)
99+
max_lines = max(min_lines, max_lines)
100+
if min_lines <= existing_blank_lines <= max_lines:
96101
return whitespace
97-
98-
min_whitespace = whitespace
99-
for _ in range(min_lines - get_new_line_count(whitespace) + 1):
100-
min_whitespace = '\n' + min_whitespace
101-
102-
return min_whitespace
102+
elif existing_blank_lines < min_lines:
103+
return '\n' * (min_lines - existing_blank_lines) + whitespace
104+
else:
105+
return '\n' * max_lines + whitespace[whitespace.rfind('\n'):]
103106

104107

105-
def get_new_line_count(whitespace):
108+
def _count_line_breaks(whitespace):
106109
return whitespace.count('\n')

rewrite/tests/python/all/format/blank_lines_test.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,3 +187,32 @@ def __init__(self):
187187
from_visitor(BlankLinesVisitor(style))
188188
)
189189
)
190+
191+
192+
def test_max_lines_in_code():
193+
style = IntelliJ.blank_lines()
194+
style = style.with_keep_maximum(
195+
style.keep_maximum.with_in_code(1)
196+
)
197+
rewrite_run(
198+
# language=python
199+
python(
200+
"""\
201+
def foo():
202+
print('foo')
203+
204+
205+
print('bar')
206+
""",
207+
"""\
208+
def foo():
209+
print('foo')
210+
211+
print('bar')
212+
"""
213+
),
214+
spec=RecipeSpec()
215+
.with_recipes(
216+
from_visitor(BlankLinesVisitor(style))
217+
)
218+
)

0 commit comments

Comments
 (0)