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

Commit 166abf4

Browse files
committed
Merge remote-tracking branch 'origin/main' into autoformat/spacevisitor
2 parents c7b5331 + 320564c commit 166abf4

File tree

13 files changed

+259
-60
lines changed

13 files changed

+259
-60
lines changed

.github/workflows/receive-pr.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
name: receive-pr
22

33
on:
4+
pull_request:
5+
types: []
6+
47
# pull_request:
58
# types: [opened, synchronize]
69
# branches:

rewrite/rewrite/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from .execution import ExecutionContext, DelegatingExecutionContext, InMemoryExecutionContext, Recipe, RecipeRunException
88
from .markers import *
99
from .tree import Checksum, FileAttributes, SourceFile, Tree, PrintOutputCapture, PrinterFactory
10-
from .utils import random_id
10+
from .utils import random_id, list_map
1111
from .visitor import Cursor, TreeVisitor
1212
from .parser import *
1313
from .result import *
@@ -21,6 +21,7 @@
2121
'PrintOutputCapture',
2222
'PrinterFactory',
2323
'random_id',
24+
'list_map',
2425
'Cursor',
2526
'TreeVisitor',
2627
'ExecutionContext',

rewrite/rewrite/java/extensions.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from typing import Optional, TypeVar, TYPE_CHECKING
33

44
from .support_types import J, JRightPadded, JLeftPadded, JContainer, Space
5+
from .. import list_map
56
from ..visitor import Cursor
67
from ..tree import Tree
78

@@ -19,7 +20,7 @@ def visit_container(v: 'JavaVisitor', container: Optional[JContainer[J2]], loc:
1920

2021
v.cursor = Cursor(v.cursor, container)
2122
before = v.visit_space(container.before, loc.before_location, p)
22-
js = [v.visit_right_padded(el, loc.element_location, p) for el in container.padding.elements]
23+
js = list_map(lambda el: v.visit_right_padded(el, loc.element_location, p), container.padding.elements)
2324
v.cursor = v.cursor.parent
2425

2526
return container if js is container.padding.elements and before is container.before else JContainer(before, js, container.markers)

rewrite/rewrite/java/visitor.py

Lines changed: 35 additions & 35 deletions
Large diffs are not rendered by default.

rewrite/rewrite/python/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,5 +68,6 @@
6868
'AutoFormat',
6969
'BlankLinesVisitor',
7070
'NormalizeFormatVisitor',
71+
'NormalizeTabsOrSpacesVisitor',
7172
'SpacesVisitor',
7273
]

rewrite/rewrite/python/format/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@
88
'AutoFormat',
99
'BlankLinesVisitor',
1010
'NormalizeFormatVisitor',
11+
'NormalizeTabsOrSpacesVisitor',
1112
'SpacesVisitor',
1213
]

rewrite/rewrite/python/format/auto_format.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
from .blank_lines import BlankLinesVisitor
44
from .normalize_format import NormalizeFormatVisitor
55
from .spaces_visitor import SpacesVisitor
6+
from .normalize_tabs_or_spaces import NormalizeTabsOrSpacesVisitor
7+
from .. import TabsAndIndentsStyle
68
from ..style import BlankLinesStyle, SpacesStyle, IntelliJ
79
from ..visitor import PythonVisitor
810
from ... import Recipe, Tree, Cursor
@@ -24,9 +26,10 @@ def visit(self, tree: Optional[Tree], p: P, parent: Optional[Cursor] = None) ->
2426
cu = tree if isinstance(tree, JavaSourceFile) else self._cursor.first_enclosing_or_throw(JavaSourceFile)
2527

2628
tree = NormalizeFormatVisitor(self._stop_after).visit(tree, p, self._cursor.fork())
27-
tree = BlankLinesVisitor(cu.get_style(BlankLinesStyle) or IntelliJ.blank_lines(), self._stop_after).visit(tree,
28-
p,
29-
self._cursor.fork())
30-
tree = SpacesVisitor(cu.get_style(SpacesStyle) or IntelliJ.spaces(), self._stop_after).visit(tree, p,
31-
self._cursor.fork())
29+
tree = BlankLinesVisitor(cu.get_style(BlankLinesStyle) or IntelliJ.blank_lines(), self._stop_after).visit(tree, p, self._cursor.fork())
30+
tree = SpacesVisitor(cu.get_style(SpacesStyle) or IntelliJ.spaces(), self._stop_after).visit(tree, p, self._cursor.fork())
31+
tree = NormalizeTabsOrSpacesVisitor(
32+
cu.get_style(TabsAndIndentsStyle) or IntelliJ.tabs_and_indents(),
33+
self._stop_after
34+
).visit(tree, p, self._cursor.fork())
3235
return tree

rewrite/rewrite/python/format/normalize_format.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
from __future__ import annotations
22

3-
from typing import cast, Optional, TypeVar
3+
from typing import cast, Optional, TypeVar, List
44

5-
from rewrite import Tree, P, Cursor
5+
from rewrite import Tree, P, Cursor, list_map
66
from rewrite.java import MethodDeclaration, J, Space, ClassDeclaration
77
from rewrite.python import PythonVisitor, PyComment
88
from rewrite.visitor import T
@@ -65,19 +65,19 @@ def _common_margin(s1, s2):
6565
def _concatenate_prefix(j: J, prefix: Space) -> J2:
6666
shift = _common_margin(None, j.prefix.whitespace)
6767

68-
def modify_comment(c: PyComment):
68+
def modify_comment(c: PyComment) -> PyComment:
6969
if len(shift) == 0:
7070
return c
7171
c = c.with_text(c.text.replace('\n', '\n' + shift))
7272
if '\n' in c.suffix:
7373
c = c.with_suffix(c.suffix.replace('\n', '\n' + shift))
7474
return c
7575

76-
comments = j.prefix.comments + \
77-
[modify_comment(cast(PyComment, comment)) for comment in prefix.comments]
76+
comments = j.prefix.comments + list_map(modify_comment, cast(List[PyComment], prefix.comments))
7877

7978
new_prefix = j.prefix
8079
new_prefix = new_prefix.with_whitespace(new_prefix.whitespace + prefix.whitespace)
81-
new_prefix = new_prefix.with_comments(comments)
80+
if comments:
81+
new_prefix = new_prefix.with_comments(comments)
8282

8383
return j.with_prefix(new_prefix)
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
from __future__ import annotations
2+
3+
from typing import cast, Optional, TypeVar, List
4+
5+
from rewrite import Tree, P, Cursor, list_map
6+
from rewrite.java import J, Space
7+
from rewrite.python import PythonVisitor, PyComment, TabsAndIndentsStyle
8+
from rewrite.visitor import T
9+
10+
J2 = TypeVar('J2', bound=J)
11+
12+
13+
# TODO consider supporting multiline string literals
14+
class NormalizeTabsOrSpacesVisitor(PythonVisitor):
15+
16+
def __init__(self, style: TabsAndIndentsStyle, stop_after: Tree = None):
17+
self._stop_after = stop_after
18+
self._style = style
19+
self._stop = False
20+
21+
def visit_space(self, space: Space, loc, p):
22+
if not space or space is Space.EMPTY:
23+
return space
24+
25+
s = space.with_whitespace(self.normalize_after_first_newline(space.whitespace))
26+
return s.with_comments(list_map(self.process_comment, cast(List[PyComment], s.comments)))
27+
28+
def process_comment(self, comment: PyComment) -> PyComment:
29+
return comment.with_suffix(self.normalize_after_first_newline(comment.suffix))
30+
31+
def normalize_after_first_newline(self, text: str) -> str:
32+
first_newline = text.find('\n')
33+
if first_newline >= 0 and first_newline != len(text) - 1:
34+
return text[:first_newline + 1] + self.normalize(text[first_newline + 1:], False)
35+
return text
36+
37+
def normalize(self, text: str, is_comment: bool) -> str:
38+
if not text:
39+
return text
40+
41+
if ' ' not in text if self._style.use_tab_character else '\t' not in text:
42+
return text
43+
44+
text_builder = []
45+
consecutive_spaces = 0
46+
in_margin = True
47+
i = 0
48+
49+
while i < len(text):
50+
c = text[i]
51+
52+
if c in '\n\r':
53+
in_margin = True
54+
consecutive_spaces = 0
55+
56+
elif in_margin:
57+
if self._style.use_tab_character and c == ' ' and (
58+
not is_comment or (i + 1 < len(text) and text[i + 1] != '*')):
59+
end_idx = i + self._style.tab_size
60+
if text[i:end_idx] == ' ' * self._style.tab_size:
61+
text_builder.append('\t')
62+
i += self._style.tab_size
63+
continue
64+
65+
elif not self._style.use_tab_character and c == '\t':
66+
text_builder.append(' ' * (self._style.tab_size - (consecutive_spaces % self._style.tab_size)))
67+
consecutive_spaces = 0
68+
i += 1
69+
continue
70+
71+
text_builder.append(c)
72+
in_margin = in_margin and c.isspace()
73+
consecutive_spaces = consecutive_spaces + 1 if c.isspace() else 0
74+
i += 1
75+
76+
return ''.join(text_builder)
77+
78+
def post_visit(self, tree: T, p: P) -> Optional[T]:
79+
if self._stop_after and tree == self._stop_after:
80+
self._stop = True
81+
return tree
82+
83+
def visit(self, tree: Optional[Tree], p: P, parent: Optional[Cursor] = None) -> Optional[T]:
84+
return tree if self._stop else super().visit(tree, p, parent)

rewrite/rewrite/python/visitor.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from typing import cast, TypeVar, Union
22

3-
from rewrite import SourceFile, TreeVisitor
4-
from .extensions import *
3+
from rewrite import SourceFile, TreeVisitor, list_map
4+
from . import extensions
55
from .support_types import *
66
from .tree import *
77
from rewrite.java import JavaVisitor
@@ -51,7 +51,7 @@ def visit_chained_assignment(self, chained_assignment: ChainedAssignment, p: P)
5151
return temp_statement
5252
chained_assignment = cast(ChainedAssignment, temp_statement)
5353
chained_assignment = chained_assignment.with_markers(self.visit_markers(chained_assignment.markers, p))
54-
chained_assignment = chained_assignment.padding.with_variables([self.visit_right_padded(v, PyRightPadded.Location.CHAINED_ASSIGNMENT_VARIABLES, p) for v in chained_assignment.padding.variables])
54+
chained_assignment = chained_assignment.padding.with_variables(list_map(lambda v: self.visit_right_padded(v, PyRightPadded.Location.CHAINED_ASSIGNMENT_VARIABLES, p), chained_assignment.padding.variables))
5555
chained_assignment = chained_assignment.with_assignment(self.visit_and_cast(chained_assignment.assignment, Expression, p))
5656
return chained_assignment
5757

@@ -92,8 +92,8 @@ def visit_type_hint(self, type_hint: TypeHint, p: P) -> J:
9292
def visit_compilation_unit(self, compilation_unit: CompilationUnit, p: P) -> J:
9393
compilation_unit = compilation_unit.with_prefix(self.visit_space(compilation_unit.prefix, Space.Location.COMPILATION_UNIT_PREFIX, p))
9494
compilation_unit = compilation_unit.with_markers(self.visit_markers(compilation_unit.markers, p))
95-
compilation_unit = compilation_unit.padding.with_imports([self.visit_right_padded(v, JRightPadded.Location.IMPORT, p) for v in compilation_unit.padding.imports])
96-
compilation_unit = compilation_unit.padding.with_statements([self.visit_right_padded(v, PyRightPadded.Location.COMPILATION_UNIT_STATEMENTS, p) for v in compilation_unit.padding.statements])
95+
compilation_unit = compilation_unit.padding.with_imports(list_map(lambda v: self.visit_right_padded(v, JRightPadded.Location.IMPORT, p), compilation_unit.padding.imports))
96+
compilation_unit = compilation_unit.padding.with_statements(list_map(lambda v: self.visit_right_padded(v, PyRightPadded.Location.COMPILATION_UNIT_STATEMENTS, p), compilation_unit.padding.statements))
9797
compilation_unit = compilation_unit.with_eof(self.visit_space(compilation_unit.eof, Space.Location.COMPILATION_UNIT_EOF, p))
9898
return compilation_unit
9999

@@ -180,7 +180,7 @@ def visit_formatted_string(self, formatted_string: FormattedString, p: P) -> J:
180180
return temp_expression
181181
formatted_string = cast(FormattedString, temp_expression)
182182
formatted_string = formatted_string.with_markers(self.visit_markers(formatted_string.markers, p))
183-
formatted_string = formatted_string.with_parts([self.visit_and_cast(v, Expression, p) for v in formatted_string.parts])
183+
formatted_string = formatted_string.with_parts(list_map(lambda v: self.visit_and_cast(v, Expression, p), formatted_string.parts))
184184
return formatted_string
185185

186186
def visit_formatted_string_value(self, value: FormattedString.Value, p: P) -> J:
@@ -223,7 +223,7 @@ def visit_comprehension_expression(self, comprehension_expression: Comprehension
223223
comprehension_expression = cast(ComprehensionExpression, temp_expression)
224224
comprehension_expression = comprehension_expression.with_markers(self.visit_markers(comprehension_expression.markers, p))
225225
comprehension_expression = comprehension_expression.with_result(self.visit_and_cast(comprehension_expression.result, Expression, p))
226-
comprehension_expression = comprehension_expression.with_clauses([self.visit_and_cast(v, ComprehensionExpression.Clause, p) for v in comprehension_expression.clauses])
226+
comprehension_expression = comprehension_expression.with_clauses(list_map(lambda v: self.visit_and_cast(v, ComprehensionExpression.Clause, p), comprehension_expression.clauses))
227227
comprehension_expression = comprehension_expression.with_suffix(self.visit_space(comprehension_expression.suffix, PySpace.Location.COMPREHENSION_EXPRESSION_SUFFIX, p))
228228
return comprehension_expression
229229

@@ -239,7 +239,7 @@ def visit_comprehension_clause(self, clause: ComprehensionExpression.Clause, p:
239239
clause = clause.padding.with_async(self.visit_right_padded(clause.padding.async_, PyRightPadded.Location.COMPREHENSION_EXPRESSION_CLAUSE_ASYNC, p))
240240
clause = clause.with_iterator_variable(self.visit_and_cast(clause.iterator_variable, Expression, p))
241241
clause = clause.padding.with_iterated_list(self.visit_left_padded(clause.padding.iterated_list, PyLeftPadded.Location.COMPREHENSION_EXPRESSION_CLAUSE_ITERATED_LIST, p))
242-
clause = clause.with_conditions([self.visit_and_cast(v, ComprehensionExpression.Condition, p) for v in clause.conditions])
242+
clause = clause.with_conditions(list_map(lambda v: self.visit_and_cast(v, ComprehensionExpression.Condition, p), clause.conditions))
243243
return clause
244244

245245
def visit_type_alias(self, type_alias: TypeAlias, p: P) -> J:
@@ -270,7 +270,7 @@ def visit_union_type(self, union_type: UnionType, p: P) -> J:
270270
return temp_expression
271271
union_type = cast(UnionType, temp_expression)
272272
union_type = union_type.with_markers(self.visit_markers(union_type.markers, p))
273-
union_type = union_type.padding.with_types([self.visit_right_padded(v, PyRightPadded.Location.UNION_TYPE_TYPES, p) for v in union_type.padding.types])
273+
union_type = union_type.padding.with_types(list_map(lambda v: self.visit_right_padded(v, PyRightPadded.Location.UNION_TYPE_TYPES, p), union_type.padding.types))
274274
return union_type
275275

276276
def visit_variable_scope(self, variable_scope: VariableScope, p: P) -> J:
@@ -280,7 +280,7 @@ def visit_variable_scope(self, variable_scope: VariableScope, p: P) -> J:
280280
return temp_statement
281281
variable_scope = cast(VariableScope, temp_statement)
282282
variable_scope = variable_scope.with_markers(self.visit_markers(variable_scope.markers, p))
283-
variable_scope = variable_scope.padding.with_names([self.visit_right_padded(v, PyRightPadded.Location.VARIABLE_SCOPE_NAMES, p) for v in variable_scope.padding.names])
283+
variable_scope = variable_scope.padding.with_names(list_map(lambda v: self.visit_right_padded(v, PyRightPadded.Location.VARIABLE_SCOPE_NAMES, p), variable_scope.padding.names))
284284
return variable_scope
285285

286286
def visit_del(self, del_: Del, p: P) -> J:
@@ -290,7 +290,7 @@ def visit_del(self, del_: Del, p: P) -> J:
290290
return temp_statement
291291
del_ = cast(Del, temp_statement)
292292
del_ = del_.with_markers(self.visit_markers(del_.markers, p))
293-
del_ = del_.padding.with_targets([self.visit_right_padded(v, PyRightPadded.Location.DEL_TARGETS, p) for v in del_.padding.targets])
293+
del_ = del_.padding.with_targets(list_map(lambda v: self.visit_right_padded(v, PyRightPadded.Location.DEL_TARGETS, p), del_.padding.targets))
294294
return del_
295295

296296
def visit_special_parameter(self, special_parameter: SpecialParameter, p: P) -> J:

0 commit comments

Comments
 (0)