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.
@@ -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"]