prettycsharp 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- prettycsharp/__init__.py +9 -0
- prettycsharp/__main__.py +5 -0
- prettycsharp/cli.py +250 -0
- prettycsharp/config.py +263 -0
- prettycsharp/doc.py +537 -0
- prettycsharp/formatter.py +106 -0
- prettycsharp/ignore.py +184 -0
- prettycsharp/parsing.py +53 -0
- prettycsharp/printer/__init__.py +28 -0
- prettycsharp/printer/core.py +540 -0
- prettycsharp/printer/declarations.py +623 -0
- prettycsharp/printer/expressions.py +1107 -0
- prettycsharp/printer/statements.py +603 -0
- prettycsharp/project.py +214 -0
- prettycsharp/py.typed +0 -0
- prettycsharp/rewrites.py +732 -0
- prettycsharp/settings.py +192 -0
- prettycsharp/syntax.py +367 -0
- prettycsharp/verify.py +312 -0
- prettycsharp-1.0.0.dist-info/METADATA +212 -0
- prettycsharp-1.0.0.dist-info/RECORD +24 -0
- prettycsharp-1.0.0.dist-info/WHEEL +4 -0
- prettycsharp-1.0.0.dist-info/entry_points.txt +2 -0
- prettycsharp-1.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
"""Statements and blocks."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import functools
|
|
6
|
+
from collections.abc import Callable, Sequence
|
|
7
|
+
|
|
8
|
+
from prettycsharp.doc import HARDLINE, LINE, SOFTLINE, Doc, conditional_group, group, indent, join
|
|
9
|
+
from prettycsharp.printer.expressions import Expressions
|
|
10
|
+
from prettycsharp.syntax import DIRECTIVE_TYPES, Node, token
|
|
11
|
+
|
|
12
|
+
# (left side, operator, value, what ends the item) for an alignable item.
|
|
13
|
+
Splitter = Callable[[Node], "tuple[Doc, Node, Node, Doc] | None"]
|
|
14
|
+
|
|
15
|
+
BLOCK_STATEMENTS = frozenset(
|
|
16
|
+
{
|
|
17
|
+
"if_statement",
|
|
18
|
+
"for_statement",
|
|
19
|
+
"foreach_statement",
|
|
20
|
+
"while_statement",
|
|
21
|
+
"do_statement",
|
|
22
|
+
"switch_statement",
|
|
23
|
+
"try_statement",
|
|
24
|
+
"using_statement",
|
|
25
|
+
"lock_statement",
|
|
26
|
+
"fixed_statement",
|
|
27
|
+
"checked_statement",
|
|
28
|
+
"unsafe_statement",
|
|
29
|
+
"block",
|
|
30
|
+
}
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# Statements whose body is an embedded statement printed after a header.
|
|
34
|
+
_EMBEDDED_PARENTS = frozenset(
|
|
35
|
+
{"if_statement", "for_statement", "foreach_statement", "while_statement", "using_statement", "lock_statement", "fixed_statement"}
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class Statements(Expressions):
|
|
40
|
+
# Blocks ---------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
def block(self, block: Node, context: str) -> Doc:
|
|
43
|
+
"""A block after a header: the brace's line break or space, then the braces."""
|
|
44
|
+
if self.is_empty_block(block) and self.settings.empty_bodies == "inline":
|
|
45
|
+
return [" ", self.block_braces(block)]
|
|
46
|
+
return [self.open_brace(context), self.block_braces(block)]
|
|
47
|
+
|
|
48
|
+
def is_empty_block(self, block: Node) -> bool:
|
|
49
|
+
if len(block.children) != 2 or block.verbatim:
|
|
50
|
+
return False
|
|
51
|
+
close = block.children[-1]
|
|
52
|
+
return not close.leading and not block.children[0].trailing
|
|
53
|
+
|
|
54
|
+
def block_braces(self, block: Node) -> Doc:
|
|
55
|
+
if block.verbatim or len(block.children) < 2:
|
|
56
|
+
return self.print(block)
|
|
57
|
+
open_tok = block.children[0]
|
|
58
|
+
close_tok = block.children[-1]
|
|
59
|
+
items = block.children[1:-1]
|
|
60
|
+
if not items and not close_tok.leading:
|
|
61
|
+
if self.settings.empty_bodies == "inline" or open_tok.trailing:
|
|
62
|
+
return [self.tok(open_tok), " ", self.tok(close_tok)]
|
|
63
|
+
return [self.tok(open_tok), HARDLINE, self.tok(close_tok)]
|
|
64
|
+
body: Doc = ""
|
|
65
|
+
if items:
|
|
66
|
+
body = indent([HARDLINE, self.statements(items)])
|
|
67
|
+
return [self.tok(open_tok), body, self.close_comments(close_tok), HARDLINE, self.tok(close_tok, leading=False)]
|
|
68
|
+
|
|
69
|
+
def p_block(self, node: Node) -> Doc:
|
|
70
|
+
return self.block_braces(node)
|
|
71
|
+
|
|
72
|
+
def statements(self, items: Sequence[Node]) -> Doc:
|
|
73
|
+
return self.print_list(items, self.statement_blank, aligned=self.aligned_assignments(items))
|
|
74
|
+
|
|
75
|
+
def statement_blank(self, previous: Node, item: Node) -> bool:
|
|
76
|
+
if previous.type in DIRECTIVE_TYPES or item.type in DIRECTIVE_TYPES:
|
|
77
|
+
return self.has_blank_before(item)
|
|
78
|
+
if self.settings.blank_lines_around_blocks and (self.is_block_statement(previous) or self.is_block_statement(item)):
|
|
79
|
+
return self.settings.max_blank_lines > 0
|
|
80
|
+
return self.has_blank_before(item)
|
|
81
|
+
|
|
82
|
+
def is_block_statement(self, node: Node) -> bool:
|
|
83
|
+
if node.type == "global_statement" and node.children:
|
|
84
|
+
node = node.children[0]
|
|
85
|
+
if node.type == "labeled_statement" and node.children:
|
|
86
|
+
node = node.children[-1]
|
|
87
|
+
if node.type in BLOCK_STATEMENTS:
|
|
88
|
+
return True
|
|
89
|
+
if node.type == "local_function_statement":
|
|
90
|
+
return node.child("body") is not None and node.child("body").type == "block" # type: ignore[union-attr]
|
|
91
|
+
return False
|
|
92
|
+
|
|
93
|
+
# Embedded statements ------------------------------------------------
|
|
94
|
+
|
|
95
|
+
def embedded(self, body: Node, context_token: Node, parent: Node) -> Doc:
|
|
96
|
+
"""The statement after a header such as ``if (...)``."""
|
|
97
|
+
if body.type == "block" and not body.verbatim:
|
|
98
|
+
return self.block(body, "statement")
|
|
99
|
+
if body.type == "empty_statement":
|
|
100
|
+
return self.print(body)
|
|
101
|
+
if body.type in ("using_statement", "fixed_statement") and body.type == parent.type:
|
|
102
|
+
# Stacked using or fixed statements share one body.
|
|
103
|
+
return [HARDLINE, self.print(body)]
|
|
104
|
+
statement = self.print(body)
|
|
105
|
+
if self.settings.braces == "single_line_only":
|
|
106
|
+
braced = self._braced(body)
|
|
107
|
+
# The braced form prints in break mode, so its own groups can still break.
|
|
108
|
+
return conditional_group([[" ", statement], group(braced, should_break=True), braced])
|
|
109
|
+
same_line = (
|
|
110
|
+
body.line >= 0
|
|
111
|
+
and context_token.end_line >= 0
|
|
112
|
+
and body.line == context_token.end_line
|
|
113
|
+
and not _starts_with_comment(body)
|
|
114
|
+
)
|
|
115
|
+
if same_line:
|
|
116
|
+
return group(indent([LINE, statement]))
|
|
117
|
+
return indent([HARDLINE, statement])
|
|
118
|
+
|
|
119
|
+
def _braced(self, body: Node) -> Doc:
|
|
120
|
+
open_tok = token("{")
|
|
121
|
+
close_tok = token("}")
|
|
122
|
+
return [self.open_brace("statement"), self.tok(open_tok), indent([HARDLINE, self.print(body)]), HARDLINE, self.tok(close_tok)]
|
|
123
|
+
|
|
124
|
+
def continuation(self, before: Node, keyword: Node) -> Doc:
|
|
125
|
+
"""What separates ``}`` from ``else``, ``catch``, ``finally`` or a do loop's ``while``."""
|
|
126
|
+
style = self.settings.continuation_keywords
|
|
127
|
+
braced = before.type == "block" or (before.type in ("catch_clause", "finally_clause"))
|
|
128
|
+
if keyword.leading:
|
|
129
|
+
# A comment before the keyword needs a line of its own.
|
|
130
|
+
style = "blank_line" if style == "blank_line" else "new_line"
|
|
131
|
+
if style == "same_line" and braced and self.settings.brace_style != "allman":
|
|
132
|
+
return " "
|
|
133
|
+
if style == "blank_line" and self.settings.max_blank_lines > 0:
|
|
134
|
+
return [HARDLINE, HARDLINE]
|
|
135
|
+
return HARDLINE
|
|
136
|
+
|
|
137
|
+
# Conditions ---------------------------------------------------------
|
|
138
|
+
|
|
139
|
+
def condition(self, open_tok: Node, condition: Node, close_tok: Node) -> Doc:
|
|
140
|
+
if (
|
|
141
|
+
self.settings.binary_wrap == "before_operator_paren_own_line"
|
|
142
|
+
and condition.type == "binary_expression"
|
|
143
|
+
and not condition.verbatim
|
|
144
|
+
):
|
|
145
|
+
return group(
|
|
146
|
+
[
|
|
147
|
+
self.tok(open_tok),
|
|
148
|
+
indent([SOFTLINE, self.binary(condition, in_condition=True)]),
|
|
149
|
+
SOFTLINE,
|
|
150
|
+
self.tok(close_tok),
|
|
151
|
+
]
|
|
152
|
+
)
|
|
153
|
+
return [self.tok(open_tok), self.print(condition), self.tok(close_tok)]
|
|
154
|
+
|
|
155
|
+
# if / else ----------------------------------------------------------
|
|
156
|
+
|
|
157
|
+
def p_if_statement(self, node: Node) -> Doc:
|
|
158
|
+
keyword = node.first_of("if")
|
|
159
|
+
open_tok = node.first_of("(")
|
|
160
|
+
condition = node.child("condition")
|
|
161
|
+
close_tok = node.first_of(")")
|
|
162
|
+
consequence = node.child("consequence")
|
|
163
|
+
if keyword is None or open_tok is None or condition is None or close_tok is None or consequence is None:
|
|
164
|
+
return self.generic(node)
|
|
165
|
+
parts: list[Doc] = [self.tok(keyword), " ", self.condition(open_tok, condition, close_tok)]
|
|
166
|
+
parts.append(self.embedded(consequence, close_tok, node))
|
|
167
|
+
else_tok = node.first_of("else")
|
|
168
|
+
alternative = node.child("alternative")
|
|
169
|
+
if else_tok is not None and alternative is not None:
|
|
170
|
+
parts.append(self.continuation(consequence, else_tok))
|
|
171
|
+
parts.append(self.tok(else_tok))
|
|
172
|
+
if alternative.type == "if_statement" and not alternative.verbatim and not _starts_with_comment(alternative):
|
|
173
|
+
parts.append(" ")
|
|
174
|
+
parts.append(self.print(alternative))
|
|
175
|
+
else:
|
|
176
|
+
parts.append(self.embedded(alternative, else_tok, node))
|
|
177
|
+
return parts
|
|
178
|
+
|
|
179
|
+
# Loops --------------------------------------------------------------
|
|
180
|
+
|
|
181
|
+
def p_while_statement(self, node: Node) -> Doc:
|
|
182
|
+
keyword = node.first_of("while")
|
|
183
|
+
open_tok = node.first_of("(")
|
|
184
|
+
condition = node.child("condition")
|
|
185
|
+
close_tok = node.first_of(")")
|
|
186
|
+
body = node.child("body") or (node.children[-1] if node.children else None)
|
|
187
|
+
if keyword is None or open_tok is None or condition is None or close_tok is None or body is None:
|
|
188
|
+
return self.generic(node)
|
|
189
|
+
return [self.tok(keyword), " ", self.condition(open_tok, condition, close_tok), self.embedded(body, close_tok, node)]
|
|
190
|
+
|
|
191
|
+
def p_do_statement(self, node: Node) -> Doc:
|
|
192
|
+
do = node.first_of("do")
|
|
193
|
+
body = node.child("body")
|
|
194
|
+
keyword = node.first_of("while")
|
|
195
|
+
open_tok = node.first_of("(")
|
|
196
|
+
condition = node.child("condition")
|
|
197
|
+
close_tok = node.first_of(")")
|
|
198
|
+
semicolon = node.first_of(";")
|
|
199
|
+
if None in (do, body, keyword, open_tok, condition, close_tok, semicolon):
|
|
200
|
+
return self.generic(node)
|
|
201
|
+
assert do and body and keyword and open_tok and condition and close_tok and semicolon
|
|
202
|
+
if body.type == "block":
|
|
203
|
+
body_doc = self.block(body, "statement")
|
|
204
|
+
else:
|
|
205
|
+
body_doc = indent([HARDLINE, self.print(body)])
|
|
206
|
+
return [
|
|
207
|
+
self.tok(do),
|
|
208
|
+
body_doc,
|
|
209
|
+
self.continuation(body, keyword),
|
|
210
|
+
self.tok(keyword),
|
|
211
|
+
" ",
|
|
212
|
+
self.condition(open_tok, condition, close_tok),
|
|
213
|
+
self.tok(semicolon),
|
|
214
|
+
]
|
|
215
|
+
|
|
216
|
+
def p_for_statement(self, node: Node) -> Doc:
|
|
217
|
+
keyword = node.first_of("for")
|
|
218
|
+
open_tok = node.first_of("(")
|
|
219
|
+
close_index = _last_index(node.children, ")")
|
|
220
|
+
if keyword is None or open_tok is None or close_index is None:
|
|
221
|
+
return self.generic(node)
|
|
222
|
+
close_tok = node.children[close_index]
|
|
223
|
+
body = node.children[close_index + 1] if close_index + 1 < len(node.children) else None
|
|
224
|
+
open_index = node.children.index(open_tok)
|
|
225
|
+
inner: list[Doc] = []
|
|
226
|
+
pending_space = False
|
|
227
|
+
for child in node.children[open_index + 1 : close_index]:
|
|
228
|
+
if child.text in (";", ",") and not child.named:
|
|
229
|
+
inner.append(self.tok(child))
|
|
230
|
+
pending_space = True
|
|
231
|
+
continue
|
|
232
|
+
if pending_space:
|
|
233
|
+
inner.append(LINE)
|
|
234
|
+
pending_space = False
|
|
235
|
+
inner.append(self.print(child))
|
|
236
|
+
header = group([self.tok(keyword), " ", self.tok(open_tok), indent([SOFTLINE, inner]), SOFTLINE, self.tok(close_tok)])
|
|
237
|
+
if body is None:
|
|
238
|
+
return header
|
|
239
|
+
return [header, self.embedded(body, close_tok, node)]
|
|
240
|
+
|
|
241
|
+
def p_foreach_statement(self, node: Node) -> Doc:
|
|
242
|
+
open_tok = node.first_of("(")
|
|
243
|
+
close_index = _last_index(node.children, ")")
|
|
244
|
+
if open_tok is None or close_index is None:
|
|
245
|
+
return self.generic(node)
|
|
246
|
+
open_index = node.children.index(open_tok)
|
|
247
|
+
close_tok = node.children[close_index]
|
|
248
|
+
keywords = self.spaced(node.children[:open_index])
|
|
249
|
+
inner = self.spaced(node.children[open_index + 1 : close_index])
|
|
250
|
+
body = node.children[close_index + 1] if close_index + 1 < len(node.children) else None
|
|
251
|
+
header = [keywords, " ", self.tok(open_tok), inner, self.tok(close_tok)]
|
|
252
|
+
if body is None:
|
|
253
|
+
return header
|
|
254
|
+
return [header, self.embedded(body, close_tok, node)]
|
|
255
|
+
|
|
256
|
+
# Other statements with a header and a body ---------------------------
|
|
257
|
+
|
|
258
|
+
def _headed(self, node: Node) -> Doc:
|
|
259
|
+
"""keyword(s) ``(...)`` body: using, lock, fixed."""
|
|
260
|
+
open_tok = node.first_of("(")
|
|
261
|
+
close_index = _last_index(node.children, ")")
|
|
262
|
+
if open_tok is None or close_index is None:
|
|
263
|
+
return self.generic(node)
|
|
264
|
+
open_index = node.children.index(open_tok)
|
|
265
|
+
close_tok = node.children[close_index]
|
|
266
|
+
keywords = self.spaced(node.children[:open_index])
|
|
267
|
+
inner = self.generic_sequence(node.children[open_index + 1 : close_index])
|
|
268
|
+
body = node.children[close_index + 1] if close_index + 1 < len(node.children) else None
|
|
269
|
+
header = [keywords, " ", self.tok(open_tok), inner, self.tok(close_tok)]
|
|
270
|
+
if body is None:
|
|
271
|
+
return header
|
|
272
|
+
return [header, self.embedded(body, close_tok, node)]
|
|
273
|
+
|
|
274
|
+
def p_using_statement(self, node: Node) -> Doc:
|
|
275
|
+
return self._headed(node)
|
|
276
|
+
|
|
277
|
+
def p_lock_statement(self, node: Node) -> Doc:
|
|
278
|
+
return self._headed(node)
|
|
279
|
+
|
|
280
|
+
def p_fixed_statement(self, node: Node) -> Doc:
|
|
281
|
+
return self._headed(node)
|
|
282
|
+
|
|
283
|
+
def p_checked_statement(self, node: Node) -> Doc:
|
|
284
|
+
return self._keyword_block(node)
|
|
285
|
+
|
|
286
|
+
def p_unsafe_statement(self, node: Node) -> Doc:
|
|
287
|
+
return self._keyword_block(node)
|
|
288
|
+
|
|
289
|
+
def _keyword_block(self, node: Node) -> Doc:
|
|
290
|
+
block = node.children[-1] if node.children else None
|
|
291
|
+
if block is None or block.type != "block":
|
|
292
|
+
return self.generic(node)
|
|
293
|
+
return [self.spaced(node.children[:-1]), self.block(block, "statement")]
|
|
294
|
+
|
|
295
|
+
# switch -------------------------------------------------------------
|
|
296
|
+
|
|
297
|
+
def p_switch_statement(self, node: Node) -> Doc:
|
|
298
|
+
body = node.child("body")
|
|
299
|
+
if body is None or body.type != "switch_body":
|
|
300
|
+
return self.generic(node)
|
|
301
|
+
index = node.children.index(body)
|
|
302
|
+
keyword = node.children[0]
|
|
303
|
+
# switch (x), or switch (a, b) where the tuple brings its own parentheses.
|
|
304
|
+
governing = self.join_tokens([(c, self.print(c)) for c in node.children[1:index]])
|
|
305
|
+
return [self.tok(keyword), " ", governing, self.open_brace("statement"), self.print(body)]
|
|
306
|
+
|
|
307
|
+
def p_switch_body(self, node: Node) -> Doc:
|
|
308
|
+
if len(node.children) < 2:
|
|
309
|
+
return self.generic(node)
|
|
310
|
+
open_tok = node.children[0]
|
|
311
|
+
close_tok = node.children[-1]
|
|
312
|
+
sections = node.children[1:-1]
|
|
313
|
+
body: Doc = ""
|
|
314
|
+
if sections:
|
|
315
|
+
lines = [HARDLINE, self.print_list(sections, self.section_blank)]
|
|
316
|
+
# Case labels sit one level in, or flush with switch.
|
|
317
|
+
body = indent(lines) if self.settings.switch_case_indent else lines
|
|
318
|
+
return [self.tok(open_tok), body, self.close_comments(close_tok), HARDLINE, self.tok(close_tok, leading=False)]
|
|
319
|
+
|
|
320
|
+
def section_blank(self, previous: Node, item: Node) -> bool:
|
|
321
|
+
if previous.type in DIRECTIVE_TYPES or item.type in DIRECTIVE_TYPES:
|
|
322
|
+
return self.has_blank_before(item)
|
|
323
|
+
if not self.settings.switch_case_blank_lines or self.settings.max_blank_lines == 0:
|
|
324
|
+
return False
|
|
325
|
+
return bool(self._section_statements(previous))
|
|
326
|
+
|
|
327
|
+
def _section_statements(self, section: Node) -> list[Node]:
|
|
328
|
+
colon = next((i for i, c in enumerate(section.children) if c.text == ":" and not c.named), None)
|
|
329
|
+
if colon is None:
|
|
330
|
+
return []
|
|
331
|
+
return section.children[colon + 1 :]
|
|
332
|
+
|
|
333
|
+
def p_switch_section(self, node: Node) -> Doc:
|
|
334
|
+
colon = next((i for i, c in enumerate(node.children) if c.text == ":" and not c.named), None)
|
|
335
|
+
if colon is None:
|
|
336
|
+
return self.generic(node)
|
|
337
|
+
label = [self.spaced(node.children[:colon]), self.tok(node.children[colon])]
|
|
338
|
+
statements = node.children[colon + 1 :]
|
|
339
|
+
if not statements:
|
|
340
|
+
return label
|
|
341
|
+
body = [HARDLINE, self.statements(statements)]
|
|
342
|
+
return [label, indent(body)]
|
|
343
|
+
|
|
344
|
+
# try ----------------------------------------------------------------
|
|
345
|
+
|
|
346
|
+
def p_try_statement(self, node: Node) -> Doc:
|
|
347
|
+
keyword = node.first_of("try")
|
|
348
|
+
body = node.child("body")
|
|
349
|
+
if keyword is None or body is None:
|
|
350
|
+
return self.generic(node)
|
|
351
|
+
parts: list[Doc] = [self.tok(keyword), self.block(body, "statement")]
|
|
352
|
+
previous: Node = body
|
|
353
|
+
for clause in node.children:
|
|
354
|
+
if clause.type not in ("catch_clause", "finally_clause"):
|
|
355
|
+
continue
|
|
356
|
+
first = clause.first_token()
|
|
357
|
+
if first is None:
|
|
358
|
+
return self.generic(node)
|
|
359
|
+
parts.append(self.continuation(previous, first))
|
|
360
|
+
parts.append(self.print(clause))
|
|
361
|
+
previous = clause
|
|
362
|
+
return parts
|
|
363
|
+
|
|
364
|
+
def p_catch_clause(self, node: Node) -> Doc:
|
|
365
|
+
block = node.children[-1] if node.children else None
|
|
366
|
+
if block is None or block.type != "block":
|
|
367
|
+
return self.generic(node)
|
|
368
|
+
return [self.spaced(node.children[:-1]), self.block(block, "statement")]
|
|
369
|
+
|
|
370
|
+
def p_finally_clause(self, node: Node) -> Doc:
|
|
371
|
+
return self.p_catch_clause(node)
|
|
372
|
+
|
|
373
|
+
def p_catch_declaration(self, node: Node) -> Doc:
|
|
374
|
+
if len(node.children) < 2:
|
|
375
|
+
return self.generic(node)
|
|
376
|
+
return [self.tok(node.children[0]), self.spaced(node.children[1:-1]), self.tok(node.children[-1])]
|
|
377
|
+
|
|
378
|
+
def p_catch_filter_clause(self, node: Node) -> Doc:
|
|
379
|
+
keyword = node.first_of("when")
|
|
380
|
+
open_tok = node.first_of("(")
|
|
381
|
+
close_tok = node.children[-1] if node.children else None
|
|
382
|
+
if keyword is None or open_tok is None or close_tok is None or close_tok.text != ")":
|
|
383
|
+
return self.generic(node)
|
|
384
|
+
inner = node.children[node.children.index(open_tok) + 1 : -1]
|
|
385
|
+
if len(inner) != 1:
|
|
386
|
+
return self.generic(node)
|
|
387
|
+
return [self.tok(keyword), " ", self.condition(open_tok, inner[0], close_tok)]
|
|
388
|
+
|
|
389
|
+
# Simple statements --------------------------------------------------
|
|
390
|
+
|
|
391
|
+
def p_expression_statement(self, node: Node) -> Doc:
|
|
392
|
+
return self.join_tokens([(c, self.print(c)) for c in node.children])
|
|
393
|
+
|
|
394
|
+
def p_return_statement(self, node: Node) -> Doc:
|
|
395
|
+
return self._keyword_statement(node)
|
|
396
|
+
|
|
397
|
+
def p_throw_statement(self, node: Node) -> Doc:
|
|
398
|
+
return self._keyword_statement(node)
|
|
399
|
+
|
|
400
|
+
def p_yield_statement(self, node: Node) -> Doc:
|
|
401
|
+
return self._keyword_statement(node)
|
|
402
|
+
|
|
403
|
+
def p_goto_statement(self, node: Node) -> Doc:
|
|
404
|
+
return self._keyword_statement(node)
|
|
405
|
+
|
|
406
|
+
def p_break_statement(self, node: Node) -> Doc:
|
|
407
|
+
return self._keyword_statement(node)
|
|
408
|
+
|
|
409
|
+
def p_continue_statement(self, node: Node) -> Doc:
|
|
410
|
+
return self._keyword_statement(node)
|
|
411
|
+
|
|
412
|
+
def _keyword_statement(self, node: Node) -> Doc:
|
|
413
|
+
parts: list[Doc] = []
|
|
414
|
+
for i, child in enumerate(node.children):
|
|
415
|
+
if i and child.text != ";":
|
|
416
|
+
parts.append(" ")
|
|
417
|
+
parts.append(self.print(child))
|
|
418
|
+
return parts
|
|
419
|
+
|
|
420
|
+
def p_empty_statement(self, node: Node) -> Doc:
|
|
421
|
+
return self.generic(node)
|
|
422
|
+
|
|
423
|
+
def p_labeled_statement(self, node: Node) -> Doc:
|
|
424
|
+
colon = node.first_of(":")
|
|
425
|
+
if colon is None or node.children.index(colon) != 1 or len(node.children) != 3:
|
|
426
|
+
return self.generic(node)
|
|
427
|
+
return [self.print(node.children[0]), self.tok(colon), HARDLINE, self.print(node.children[2])]
|
|
428
|
+
|
|
429
|
+
def p_local_declaration_statement(self, node: Node) -> Doc:
|
|
430
|
+
parts: list[Doc] = []
|
|
431
|
+
for i, child in enumerate(node.children):
|
|
432
|
+
if i and child.text != ";":
|
|
433
|
+
parts.append(" ")
|
|
434
|
+
parts.append(self.print(child))
|
|
435
|
+
return parts
|
|
436
|
+
|
|
437
|
+
def p_global_statement(self, node: Node) -> Doc:
|
|
438
|
+
return self.generic(node)
|
|
439
|
+
|
|
440
|
+
def p_variable_declaration(self, node: Node) -> Doc:
|
|
441
|
+
type_node = node.child("type")
|
|
442
|
+
declarators = [c for c in node.children if c.type == "variable_declarator"]
|
|
443
|
+
if type_node is None or not declarators:
|
|
444
|
+
return self.generic(node)
|
|
445
|
+
commas = [c for c in node.children if c.text == "," and not c.named]
|
|
446
|
+
docs: list[Doc] = []
|
|
447
|
+
for i, declarator in enumerate(declarators):
|
|
448
|
+
doc = self.print(declarator)
|
|
449
|
+
if i < len(commas):
|
|
450
|
+
doc = [doc, self.tok(commas[i])]
|
|
451
|
+
docs.append(doc)
|
|
452
|
+
if len(docs) == 1:
|
|
453
|
+
return [self.print(type_node), " ", docs[0]]
|
|
454
|
+
return group([self.print(type_node), " ", docs[0], indent([[LINE, d] for d in docs[1:]])])
|
|
455
|
+
|
|
456
|
+
def p_variable_declarator(self, node: Node) -> Doc:
|
|
457
|
+
equals = node.first_of("=")
|
|
458
|
+
if equals is None:
|
|
459
|
+
return self.join_tokens([(c, self.print(c)) for c in node.children])
|
|
460
|
+
index = node.children.index(equals)
|
|
461
|
+
left = node.children[:index]
|
|
462
|
+
value = node.children[index + 1 :]
|
|
463
|
+
if len(value) != 1:
|
|
464
|
+
return self.generic(node)
|
|
465
|
+
return self.rhs(self.join_tokens([(c, self.print(c)) for c in left]), equals, value[0])
|
|
466
|
+
|
|
467
|
+
# Assignment alignment -------------------------------------------------
|
|
468
|
+
|
|
469
|
+
def aligned_assignments(self, items: Sequence[Node]) -> dict[int, Doc] | None:
|
|
470
|
+
if not self.settings.align_assignments:
|
|
471
|
+
return None
|
|
472
|
+
return self.align_runs(items, self.assignment_split, self.statement_blank)
|
|
473
|
+
|
|
474
|
+
def assignment_split(self, item: Node) -> tuple[Doc, Node, Node, Doc] | None:
|
|
475
|
+
"""(left, operator, value, end) for ``left = value;`` statements and fields."""
|
|
476
|
+
node = item.children[0] if item.type == "global_statement" and item.children else item
|
|
477
|
+
if node.verbatim:
|
|
478
|
+
return None
|
|
479
|
+
if node.type == "expression_statement" and len(node.children) == 2:
|
|
480
|
+
assignment = node.children[0]
|
|
481
|
+
if assignment.type != "assignment_expression":
|
|
482
|
+
return None
|
|
483
|
+
op = assignment.child("operator")
|
|
484
|
+
left = assignment.child("left")
|
|
485
|
+
value = assignment.child("right")
|
|
486
|
+
if op is None or op.text != "=" or left is None or value is None:
|
|
487
|
+
return None
|
|
488
|
+
return (self.print(left), op, value, self.tok(node.children[1]))
|
|
489
|
+
if node.type in ("local_declaration_statement", "field_declaration"):
|
|
490
|
+
declaration = node.first_of("variable_declaration")
|
|
491
|
+
if declaration is None or node.children[-1].text != ";":
|
|
492
|
+
return None
|
|
493
|
+
declarators = [c for c in declaration.children if c.type == "variable_declarator"]
|
|
494
|
+
if len(declarators) != 1:
|
|
495
|
+
return None
|
|
496
|
+
declarator = declarators[0]
|
|
497
|
+
equals = declarator.first_of("=")
|
|
498
|
+
if equals is None:
|
|
499
|
+
return None
|
|
500
|
+
index = declarator.children.index(equals)
|
|
501
|
+
values = declarator.children[index + 1 :]
|
|
502
|
+
if len(values) != 1:
|
|
503
|
+
return None
|
|
504
|
+
type_node = declaration.child("type")
|
|
505
|
+
if type_node is None:
|
|
506
|
+
return None
|
|
507
|
+
before = node.children[: node.children.index(declaration)]
|
|
508
|
+
if any(c.type == "attribute_list" for c in before):
|
|
509
|
+
return None
|
|
510
|
+
left_parts: list[Doc] = [self.print(c) for c in before]
|
|
511
|
+
left_parts.append(self.print(type_node))
|
|
512
|
+
left_parts.append(self.join_tokens([(c, self.print(c)) for c in declarator.children[:index]]))
|
|
513
|
+
return (join(" ", left_parts), equals, values[0], self.tok(node.children[-1]))
|
|
514
|
+
return None
|
|
515
|
+
|
|
516
|
+
def align_runs(
|
|
517
|
+
self, items: Sequence[Node], splitter: Splitter, blank: Callable[[Node, Node], bool]
|
|
518
|
+
) -> dict[int, Doc] | None:
|
|
519
|
+
"""Pad the left sides of consecutive ``x = y`` items so the ``=`` line up.
|
|
520
|
+
|
|
521
|
+
A run is items printed on consecutive lines (``blank`` is the list's
|
|
522
|
+
own blank-line rule), each on one line. An item whose line would pass
|
|
523
|
+
the width, aligned or not, is not aligned, so a run never makes a
|
|
524
|
+
line break that formatting the output again would undo.
|
|
525
|
+
"""
|
|
526
|
+
result: dict[int, Doc] = {}
|
|
527
|
+
# (item, parts, left width, value width, indentation)
|
|
528
|
+
run: list[tuple[Node, tuple[Doc, Node, Node, Doc], int, int, int]] = []
|
|
529
|
+
above: dict[int, Doc] = {}
|
|
530
|
+
|
|
531
|
+
def flush() -> None:
|
|
532
|
+
if len(run) >= 2:
|
|
533
|
+
target = max(entry[2] for entry in run)
|
|
534
|
+
if all(depth + target + 3 + value + 1 <= self.settings.width for *_, value, depth in run):
|
|
535
|
+
for item, (left, op, value_node, end), width, _, _ in run:
|
|
536
|
+
padded = [left, " " * (target - width)]
|
|
537
|
+
result[id(item)] = [above.get(id(item), ""), self.rhs(padded, op, value_node), end]
|
|
538
|
+
run.clear()
|
|
539
|
+
|
|
540
|
+
kind = ""
|
|
541
|
+
previous: Node | None = None
|
|
542
|
+
for item in items:
|
|
543
|
+
# A comment above an item prints before it; only the code aligns.
|
|
544
|
+
comments, parts = self.split_leading(item, functools.partial(splitter, item))
|
|
545
|
+
above[id(item)] = comments
|
|
546
|
+
measured: tuple[int, int, int] | None = None
|
|
547
|
+
if (
|
|
548
|
+
parts is not None
|
|
549
|
+
and not any(t.leading or t.trailing for t in _left_tokens(item))
|
|
550
|
+
and not self.multiline_value(parts[2])
|
|
551
|
+
):
|
|
552
|
+
width = self.flat_width(parts[0])
|
|
553
|
+
value_width = self.flat_width(self.print(parts[2]))
|
|
554
|
+
depth = self.indent_depth(item) * self.settings.indent_size
|
|
555
|
+
if width is not None and value_width is not None and depth + width + 3 + value_width + 1 <= self.settings.width:
|
|
556
|
+
measured = (width, value_width, depth)
|
|
557
|
+
item_kind = _kind(item)
|
|
558
|
+
# A blank line, a comment or a different kind of item ends a run;
|
|
559
|
+
# a comment above the first item of a run is fine.
|
|
560
|
+
continues = (
|
|
561
|
+
bool(run)
|
|
562
|
+
and previous is run[-1][0]
|
|
563
|
+
and not (blank(run[-1][0], item) or self.has_comments_before(item) or item_kind != kind)
|
|
564
|
+
)
|
|
565
|
+
if parts is None or measured is None or not continues:
|
|
566
|
+
flush()
|
|
567
|
+
if parts is not None and measured is not None:
|
|
568
|
+
run.append((item, parts, *measured))
|
|
569
|
+
kind = item_kind
|
|
570
|
+
previous = item
|
|
571
|
+
flush()
|
|
572
|
+
return result or None
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
def _kind(item: Node) -> str:
|
|
576
|
+
node = item.children[0] if item.type == "global_statement" and item.children else item
|
|
577
|
+
return node.type
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
def _left_tokens(item: Node) -> list[Node]:
|
|
581
|
+
"""The tokens up to the first ``=``, other than the item's first token."""
|
|
582
|
+
tokens: list[Node] = []
|
|
583
|
+
for i, t in enumerate(item.tokens()):
|
|
584
|
+
if t.text == "=":
|
|
585
|
+
break
|
|
586
|
+
if i:
|
|
587
|
+
tokens.append(t)
|
|
588
|
+
return tokens
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
def _starts_with_comment(node: Node) -> bool:
|
|
592
|
+
first = node.first_token()
|
|
593
|
+
return first is not None and bool(first.leading)
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
def _last_index(children: Sequence[Node], text: str) -> int | None:
|
|
597
|
+
for i in range(len(children) - 1, -1, -1):
|
|
598
|
+
if children[i].text == text and not children[i].named:
|
|
599
|
+
return i
|
|
600
|
+
return None
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
__all__ = ["BLOCK_STATEMENTS", "Statements", "SOFTLINE"]
|