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,1107 @@
1
+ """Expressions: operators, calls and member chains, initializers, lambdas, patterns."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import functools
6
+ from collections.abc import Sequence
7
+ from dataclasses import dataclass, field
8
+
9
+ from prettycsharp.doc import (
10
+ HARDLINE,
11
+ LINE,
12
+ SOFTLINE,
13
+ Align,
14
+ Doc,
15
+ Fill,
16
+ IfBreak,
17
+ conditional_group,
18
+ group,
19
+ indent,
20
+ join,
21
+ print_doc,
22
+ )
23
+ from prettycsharp.printer.core import Core
24
+ from prettycsharp.syntax import Node
25
+
26
+ # Binary operator precedence, lowest first. Operators of one level chain.
27
+ _PRECEDENCE = {
28
+ "??": 1,
29
+ "||": 2,
30
+ "&&": 3,
31
+ "|": 4,
32
+ "^": 5,
33
+ "&": 6,
34
+ "==": 7,
35
+ "!=": 7,
36
+ "<": 8,
37
+ ">": 8,
38
+ "<=": 8,
39
+ ">=": 8,
40
+ "<<": 9,
41
+ ">>": 9,
42
+ ">>>": 9,
43
+ "+": 10,
44
+ "-": 10,
45
+ "*": 11,
46
+ "/": 11,
47
+ "%": 11,
48
+ }
49
+
50
+ # Values that break across lines by themselves, so an assignment keeps them
51
+ # on its own line instead of breaking after the operator.
52
+ _SELF_BREAKING = frozenset(
53
+ {
54
+ "invocation_expression",
55
+ "object_creation_expression",
56
+ "implicit_object_creation_expression",
57
+ "anonymous_object_creation_expression",
58
+ "array_creation_expression",
59
+ "implicit_array_creation_expression",
60
+ "collection_expression",
61
+ "initializer_expression",
62
+ "lambda_expression",
63
+ "anonymous_method_expression",
64
+ "switch_expression",
65
+ "with_expression",
66
+ "conditional_expression",
67
+ "binary_expression",
68
+ "query_expression",
69
+ "stackalloc_expression",
70
+ "implicit_stackalloc_expression",
71
+ "tuple_expression",
72
+ "is_pattern_expression",
73
+ }
74
+ )
75
+
76
+ # Values that are laid out as a block of lines after the operator.
77
+ _BLOCK_LIKE = frozenset(
78
+ {
79
+ "switch_expression",
80
+ "anonymous_object_creation_expression",
81
+ "collection_expression",
82
+ "initializer_expression",
83
+ "lambda_expression",
84
+ "anonymous_method_expression",
85
+ }
86
+ )
87
+
88
+ _SIMPLE_VALUE_TYPES = frozenset(
89
+ {
90
+ "integer_literal",
91
+ "real_literal",
92
+ "string_literal",
93
+ "verbatim_string_literal",
94
+ "raw_string_literal",
95
+ "character_literal",
96
+ "boolean_literal",
97
+ "null_literal",
98
+ "identifier",
99
+ "predefined_type",
100
+ }
101
+ )
102
+
103
+ _CREATION_TYPES = frozenset(
104
+ {
105
+ "object_creation_expression",
106
+ "implicit_object_creation_expression",
107
+ "array_creation_expression",
108
+ "implicit_array_creation_expression",
109
+ "anonymous_object_creation_expression",
110
+ "with_expression",
111
+ "stackalloc_expression",
112
+ "implicit_stackalloc_expression",
113
+ }
114
+ )
115
+
116
+
117
+ @dataclass
118
+ class _Link:
119
+ """One step of a member chain: ``.Name`` or ``?.Name`` and what follows it."""
120
+
121
+ access: list[Node]
122
+ name: Node | None
123
+ suffixes: list[Node] = field(default_factory=list)
124
+
125
+ @property
126
+ def is_call(self) -> bool:
127
+ return any(s.type == "argument_list" for s in self.suffixes)
128
+
129
+
130
+ class Expressions(Core):
131
+ def block(self, block: Node, context: str) -> Doc: # pragma: no cover - Statements defines it
132
+ raise NotImplementedError
133
+
134
+ # Binary operators ---------------------------------------------------
135
+
136
+ def p_binary_expression(self, node: Node) -> Doc:
137
+ return self.binary(node, in_condition=False)
138
+
139
+ def binary(self, node: Node, *, in_condition: bool) -> Doc:
140
+ operands, operators = self._flatten_binary(node)
141
+ first = self.print(operands[0])
142
+ rest: list[Doc] = []
143
+ after = self.settings.binary_wrap == "after_operator"
144
+ for op, operand in zip(operators, operands[1:], strict=True):
145
+ if after:
146
+ rest.append([" ", self.tok(op), LINE, self.print(operand)])
147
+ else:
148
+ rest.append([LINE, self.tok(op), " ", self.print(operand)])
149
+ if in_condition:
150
+ return group([first, rest])
151
+ return group([first, indent(rest)])
152
+
153
+ def _flatten_binary(self, node: Node) -> tuple[list[Node], list[Node]]:
154
+ op = node.child("operator")
155
+ level = _PRECEDENCE.get(op.text or "", 0) if op is not None else 0
156
+ operands: list[Node] = []
157
+ operators: list[Node] = []
158
+ stack: list[Node] = [node]
159
+ while stack:
160
+ current = stack.pop()
161
+ current_op = current.child("operator") if current.type == "binary_expression" else None
162
+ if (
163
+ current_op is not None
164
+ and _PRECEDENCE.get(current_op.text or "", -1) == level
165
+ and not current.verbatim
166
+ ):
167
+ left = current.child("left")
168
+ right = current.child("right")
169
+ if left is None or right is None:
170
+ operands.append(current)
171
+ continue
172
+ # Visit left, then the operator, then right.
173
+ stack.append(right)
174
+ stack.append(_OperatorMarker(current_op))
175
+ stack.append(left)
176
+ elif isinstance(current, _OperatorMarker):
177
+ operators.append(current.op)
178
+ else:
179
+ operands.append(current)
180
+ return operands, operators
181
+
182
+ # Conditional expressions ---------------------------------------------
183
+
184
+ def p_conditional_expression(self, node: Node) -> Doc:
185
+ condition = node.child("condition")
186
+ consequence = node.child("consequence")
187
+ alternative = node.child("alternative")
188
+ question = node.first_of("?")
189
+ colon = node.first_of(":")
190
+ if condition is None or consequence is None or alternative is None or question is None or colon is None:
191
+ return self.generic(node)
192
+ style = self.settings.ternary_wrap
193
+ cond = self.print(condition)
194
+ yes = self.print(consequence)
195
+ no = self.print(alternative)
196
+ if style == "after":
197
+ return group([cond, " ", self.tok(question), indent([LINE, yes, " ", self.tok(colon), LINE, no])])
198
+ return group(
199
+ [cond, indent([LINE, self.tok(question), " ", yes, LINE, self.tok(colon), " ", no])],
200
+ should_break=style == "always",
201
+ )
202
+
203
+ # Assignment ----------------------------------------------------------
204
+
205
+ def p_assignment_expression(self, node: Node) -> Doc:
206
+ left = node.child("left")
207
+ op = node.child("operator")
208
+ right = node.child("right")
209
+ if left is None or op is None or right is None:
210
+ return self.generic(node)
211
+ return self.rhs(self.print(left), op, right)
212
+
213
+ def rhs(self, left: Doc, op: Node, value: Node) -> Doc:
214
+ """``left op value``, breaking after the operator only for values that cannot break."""
215
+ # A comment at the very end of the value needs no break of its own.
216
+ after, value_doc = self.split_trailing(value, lambda: self.print(value))
217
+ if self.self_breaking(value):
218
+ return [group([left, " ", self.tok(op), " ", value_doc]), after]
219
+ return [group([left, " ", self.tok(op), group(indent([LINE, value_doc]))]), after]
220
+
221
+ def self_breaking(self, value: Node) -> bool:
222
+ if value.text is not None and ("\n" in value.text or "\r" in value.text):
223
+ # A multi-line string starts on the operator's line.
224
+ return True
225
+ if value.verbatim:
226
+ return False
227
+ kind = value.type
228
+ if kind in _SELF_BREAKING:
229
+ return True
230
+ if kind == "await_expression":
231
+ awaited = value.named_children()
232
+ return bool(awaited) and self.self_breaking(awaited[-1])
233
+ if kind in ("member_access_expression", "element_access_expression", "conditional_access_expression"):
234
+ return self.chain_calls(value) > 0
235
+ if kind == "cast_expression":
236
+ operand = value.child("value")
237
+ return operand is not None and self.self_breaking(operand)
238
+ return False
239
+
240
+ def expression_body(self, clause: Node) -> Doc:
241
+ """`` => expression`` for a member or accessor, without the semicolon."""
242
+ arrow = clause.first_of("=>")
243
+ body = next((c for c in clause.children if c is not arrow), None)
244
+ if arrow is None or body is None:
245
+ return [" ", self.generic(clause)]
246
+ body_doc = self.print(body)
247
+ if self.block_like(body):
248
+ return [" ", self.tok(arrow), " ", body_doc]
249
+ if self.forced_chain(body):
250
+ if self.settings.expression_body_chain == "own_line":
251
+ return [" ", self.tok(arrow), indent([HARDLINE, body_doc])]
252
+ return [" ", self.tok(arrow), " ", body_doc]
253
+ return group([" ", self.tok(arrow), group(indent([LINE, body_doc]))])
254
+
255
+ def multiline_value(self, value: Node) -> bool:
256
+ """Whether a value will take several lines whatever the width."""
257
+ if value.text is not None:
258
+ return "\n" in value.text or "\r" in value.text
259
+ if self.forced_chain(value):
260
+ return True
261
+ if value.type in ("lambda_expression", "anonymous_method_expression"):
262
+ body = value.child("body") or value.first_of("block")
263
+ return body is not None and (body.type == "block" or self.multiline_value(body))
264
+ if value.type == "switch_expression":
265
+ return True
266
+ if self.block_like(value):
267
+ return self.initializer_breaks(value) or value.type == "collection_expression" and self._collection_forced(value)
268
+ return False
269
+
270
+ def _collection_forced(self, node: Node) -> bool:
271
+ items, _ = self.items_and_commas(node)
272
+ if self.settings.simple_lists_inline and all(len(i) == 1 and _simple_value(_element_value(i[0])) for i in items):
273
+ return False
274
+ return self._forced_initializer([[_element_value(i[0])] for i in items if len(i) == 1])
275
+
276
+ def block_like(self, value: Node) -> bool:
277
+ if value.verbatim:
278
+ return False
279
+ if value.type in _BLOCK_LIKE:
280
+ return True
281
+ if value.type in _CREATION_TYPES:
282
+ return value.child("initializer") is not None or value.first_of("initializer_expression") is not None
283
+ return False
284
+
285
+ # Member chains -------------------------------------------------------
286
+
287
+ def p_invocation_expression(self, node: Node) -> Doc:
288
+ return self.chain(node)
289
+
290
+ def p_member_access_expression(self, node: Node) -> Doc:
291
+ return self.chain(node)
292
+
293
+ def p_conditional_access_expression(self, node: Node) -> Doc:
294
+ return self.chain(node)
295
+
296
+ def p_element_access_expression(self, node: Node) -> Doc:
297
+ return self.chain(node)
298
+
299
+ def chain_calls(self, node: Node) -> int:
300
+ _, receiver_suffixes, links = self._flatten_chain(node)
301
+ del receiver_suffixes
302
+ return sum(1 for link in links if link.is_call)
303
+
304
+ def forced_chain(self, node: Node) -> bool:
305
+ threshold = self.settings.chain_break_threshold
306
+ if threshold <= 0 or node.type not in (
307
+ "invocation_expression",
308
+ "member_access_expression",
309
+ "conditional_access_expression",
310
+ "element_access_expression",
311
+ ):
312
+ return False
313
+ return self.chain_calls(node) >= threshold
314
+
315
+ def _flatten_chain(self, node: Node) -> tuple[Node, list[Node], list[_Link]]:
316
+ links: list[_Link] = []
317
+ suffixes: list[Node] = []
318
+ current = node
319
+ while True:
320
+ if current.verbatim:
321
+ break
322
+ kind = current.type
323
+ if kind == "invocation_expression":
324
+ function = current.child("function")
325
+ arguments = current.child("arguments")
326
+ if function is None or arguments is None:
327
+ break
328
+ suffixes.append(arguments)
329
+ current = function
330
+ elif kind == "element_access_expression":
331
+ expression = current.child("expression")
332
+ subscript = current.child("subscript")
333
+ if expression is None or subscript is None:
334
+ break
335
+ suffixes.append(subscript)
336
+ current = expression
337
+ elif kind == "postfix_unary_expression" and current.children and current.children[-1].text == "!":
338
+ suffixes.append(current.children[-1])
339
+ current = current.children[0]
340
+ elif kind == "member_access_expression":
341
+ expression = current.child("expression")
342
+ name = current.child("name")
343
+ access = [c for c in current.children if c is not expression and c is not name]
344
+ if expression is None or name is None or len(access) != 1:
345
+ break
346
+ links.append(_Link(access, name, list(reversed(suffixes))))
347
+ suffixes = []
348
+ current = expression
349
+ elif kind == "conditional_access_expression":
350
+ condition = current.child("condition")
351
+ question = current.first_of("?")
352
+ binding = current.children[-1]
353
+ if condition is None or question is None or binding is condition or len(current.children) != 3:
354
+ break
355
+ if binding.type == "member_binding_expression" and len(binding.children) == 2:
356
+ links.append(_Link([question, binding.children[0]], binding.children[1], list(reversed(suffixes))))
357
+ elif binding.type == "element_binding_expression":
358
+ # ?[i]: an index on what comes before, behind its question mark.
359
+ links.append(_Link([question], None, [binding, *reversed(suffixes)]))
360
+ else:
361
+ break
362
+ suffixes = []
363
+ current = condition
364
+ else:
365
+ break
366
+ links.reverse()
367
+ return current, list(reversed(suffixes)), links
368
+
369
+ def chain(self, node: Node) -> Doc:
370
+ receiver, receiver_suffixes, links = self._flatten_chain(node)
371
+ if not links:
372
+ return self._call_or_access(receiver, receiver_suffixes, node)
373
+ head: list[Doc] = [self.print(receiver), *[self.suffix(s) for s in receiver_suffixes]]
374
+ calls = sum(1 for link in links if link.is_call)
375
+ threshold = self.settings.chain_break_threshold
376
+ # Property accesses before the first call stay on the receiver's line.
377
+ first_call = next((i for i, link in enumerate(links) if link.is_call), len(links))
378
+ for link in links[:first_call]:
379
+ head.append(self.link(link))
380
+ rest = links[first_call:]
381
+ if not rest:
382
+ return head
383
+ # Each group ends at a call; accesses after the last call join it.
384
+ groups: list[list[_Link]] = []
385
+ current: list[_Link] = []
386
+ for link in rest:
387
+ current.append(link)
388
+ if link.is_call:
389
+ groups.append(current)
390
+ current = []
391
+ if current:
392
+ groups[-1].extend(current)
393
+ group_docs = [[self.link(link) for link in g] for g in groups]
394
+ if threshold > 0 and calls >= threshold:
395
+ return [head, indent([[HARDLINE, g] for g in group_docs])]
396
+ if threshold <= 0 and len(groups) >= 2:
397
+ return group([head, indent([[SOFTLINE, g] for g in group_docs])])
398
+ return [head, *group_docs]
399
+
400
+ def _call_or_access(self, receiver: Node, suffixes: list[Node], node: Node) -> Doc:
401
+ if not suffixes:
402
+ if receiver is node:
403
+ return self.generic(node)
404
+ return self.print(receiver)
405
+ return [self.print(receiver), *[self.suffix(s) for s in suffixes]]
406
+
407
+ def link(self, link: _Link) -> Doc:
408
+ parts: list[Doc] = [self.tok(t) for t in link.access]
409
+ if link.name is not None:
410
+ parts.append(self.print(link.name))
411
+ parts.extend(self.suffix(s) for s in link.suffixes)
412
+ return parts
413
+
414
+ def suffix(self, node: Node) -> Doc:
415
+ if node.type == "argument_list":
416
+ return self.arguments(node)
417
+ return self.print(node)
418
+
419
+ # Arguments -----------------------------------------------------------
420
+
421
+ def p_argument_list(self, node: Node) -> Doc:
422
+ return self.arguments(node)
423
+
424
+ def p_bracketed_argument_list(self, node: Node) -> Doc:
425
+ return self.arguments(node)
426
+
427
+ def p_tuple_expression(self, node: Node) -> Doc:
428
+ return self.arguments(node)
429
+
430
+ def p_attribute_argument_list(self, node: Node) -> Doc:
431
+ return self.arguments(node)
432
+
433
+ def p_argument(self, node: Node) -> Doc:
434
+ # name: value, out var x, ref y: a space between parts, none before the colon.
435
+ parts: list[Doc] = []
436
+ for i, child in enumerate(node.children):
437
+ if i and child.text != ":":
438
+ parts.append(" ")
439
+ parts.append(self.print(child))
440
+ return parts
441
+
442
+ def p_attribute_argument(self, node: Node) -> Doc:
443
+ return self.p_argument(node)
444
+
445
+ def arguments(self, node: Node) -> Doc:
446
+ """``(a, b)`` or ``[a, b]``: on one line, or one argument per line."""
447
+ if len(node.children) < 2:
448
+ return self.generic(node)
449
+ open_tok = node.children[0]
450
+ close_tok = node.children[-1]
451
+ items, commas = self.items_and_commas(node)
452
+ if not items:
453
+ return group([self.tok(open_tok), self.closing(close_tok, SOFTLINE)])
454
+ docs = [self.print_item(item) for item in items]
455
+ for i, comma in enumerate(commas):
456
+ if i < len(docs):
457
+ docs[i] = [docs[i], self.tok(comma)]
458
+ return self.argument_layout(open_tok, docs, close_tok, items)
459
+
460
+ def argument_layout(self, open_tok: Node, docs: list[Doc], close_tok: Node, items: Sequence[Sequence[Node]]) -> Doc:
461
+ style = self.settings.argument_wrap
462
+ open_doc = self.tok(open_tok)
463
+ hugging_close = self.closing(close_tok, "")
464
+ if style == "fill":
465
+ parts: list[Doc] = []
466
+ for i, doc in enumerate(docs):
467
+ if i:
468
+ parts.append(LINE)
469
+ parts.append(doc)
470
+ return group([open_doc, indent([SOFTLINE, Fill(parts)]), hugging_close])
471
+ if style == "align":
472
+ return group([open_doc, Align(-1, join(LINE, docs)), hugging_close])
473
+ if style == "one_per_line_close_on_last":
474
+ expanded: Doc = group([open_doc, indent([SOFTLINE, join(LINE, docs)]), hugging_close])
475
+ else:
476
+ expanded = group([open_doc, indent([SOFTLINE, join(LINE, docs)]), self.closing(close_tok, SOFTLINE)])
477
+ if self.settings.lambda_body == "hug" and not close_tok.leading and self._can_hug(items):
478
+ close_doc = self.tok(close_tok)
479
+ last = items[-1][0]
480
+ hugged_last = group(self.print(last), should_break=True)
481
+ if len(items) - 1 < len(docs) and isinstance(docs[-1], list) and len(docs[-1]) == 2:
482
+ # A trailing comma after the hugged argument stays with it.
483
+ hugged_last = group([hugged_last, docs[-1][1]], should_break=True)
484
+ head = docs[:-1]
485
+ flat = [open_doc, join(" ", docs), close_doc]
486
+ hugged = [open_doc, join(" ", [*head, hugged_last]), close_doc]
487
+ return conditional_group([flat, hugged, expanded])
488
+ return expanded
489
+
490
+ def _can_hug(self, items: Sequence[Sequence[Node]]) -> bool:
491
+ if not items or any(len(item) != 1 for item in items):
492
+ return False
493
+ last = items[-1][0]
494
+ target = last.children[-1] if last.type == "argument" and last.children else last
495
+ if not self.huggable(target):
496
+ return False
497
+ for item in items[:-1]:
498
+ node = item[0]
499
+ if any(t.leading or t.trailing for t in node.tokens()):
500
+ return False
501
+ if any(self.huggable(n) for n in _descendants(node)):
502
+ return False
503
+ return not any(t.leading for t in last.tokens())
504
+
505
+ def huggable(self, node: Node) -> bool:
506
+ if node.verbatim:
507
+ return False
508
+ if node.type in ("lambda_expression", "anonymous_method_expression"):
509
+ body = node.child("body") or node.first_of("block")
510
+ if body is None:
511
+ return False
512
+ # A block, or a body that takes several lines anyway, such as a
513
+ # broken member chain, hangs off the call's line.
514
+ return body.type == "block" or self.huggable(body) or self.forced_chain(body)
515
+ if node.type in _CREATION_TYPES:
516
+ return self.block_like(node) and self.initializer_breaks(node)
517
+ return node.type == "switch_expression"
518
+
519
+ def print_item(self, item: Sequence[Node]) -> Doc:
520
+ if len(item) == 1:
521
+ return self.print(item[0])
522
+ return self.generic_sequence(item)
523
+
524
+ def generic_sequence(self, nodes: Sequence[Node]) -> Doc:
525
+ parts: list[Doc] = []
526
+ previous: Node | None = None
527
+ for node in nodes:
528
+ if previous is not None and self.space_between(previous, node):
529
+ parts.append(" ")
530
+ parts.append(self.print(node))
531
+ previous = node
532
+ return parts
533
+
534
+ def items_and_commas(self, node: Node) -> tuple[list[list[Node]], list[Node]]:
535
+ items = self.separated(node)
536
+ return items, self.separators(node)
537
+
538
+ # Creation and initializers ---------------------------------------------
539
+
540
+ def p_object_creation_expression(self, node: Node) -> Doc:
541
+ return self.creation(node)
542
+
543
+ def p_implicit_object_creation_expression(self, node: Node) -> Doc:
544
+ return self.creation(node)
545
+
546
+ def p_array_creation_expression(self, node: Node) -> Doc:
547
+ return self.creation(node)
548
+
549
+ def p_implicit_array_creation_expression(self, node: Node) -> Doc:
550
+ return self.creation(node)
551
+
552
+ def p_stackalloc_expression(self, node: Node) -> Doc:
553
+ return self.creation(node)
554
+
555
+ def p_implicit_stackalloc_expression(self, node: Node) -> Doc:
556
+ return self.creation(node)
557
+
558
+ def creation(self, node: Node) -> Doc:
559
+ parts: list[Doc] = []
560
+ previous: Node | None = None
561
+ for child in node.children:
562
+ if child.type == "initializer_expression":
563
+ parts.append(self.initializer(child))
564
+ else:
565
+ if previous is not None and self._space_in_creation(previous, child):
566
+ parts.append(" ")
567
+ parts.append(self.print(child))
568
+ previous = child
569
+ return parts
570
+
571
+ def _space_in_creation(self, previous: Node, child: Node) -> bool:
572
+ if child.type in ("argument_list", "array_rank_specifier", "bracketed_argument_list"):
573
+ return False
574
+ if previous.text in ("new", "stackalloc"):
575
+ # new() and new[] stay closed up; new Foo takes a space.
576
+ return child.text not in ("(", "[") and child.type not in ("argument_list",)
577
+ return self.space_between(previous, child)
578
+
579
+ def initializer_breaks(self, node: Node) -> bool:
580
+ init = node if node.type == "initializer_expression" else (
581
+ node.child("initializer") or node.first_of("initializer_expression")
582
+ )
583
+ if init is None:
584
+ if node.type == "anonymous_object_creation_expression":
585
+ items = self.separated(_braced_part(node))
586
+ return len(items) >= max(self.settings.initializer_break_threshold, 1)
587
+ return False
588
+ items, _ = self.items_and_commas(init)
589
+ return self._forced_initializer(items)
590
+
591
+ def _forced_initializer(self, items: Sequence[Sequence[Node]]) -> bool:
592
+ threshold = self.settings.initializer_break_threshold
593
+ if not items:
594
+ return False
595
+ if self.settings.simple_lists_inline and all(len(i) == 1 and _simple_value(i[0]) for i in items):
596
+ return False
597
+ return threshold > 0 and len(items) >= threshold
598
+
599
+ def initializer(self, node: Node, *, leading_space: bool = True) -> Doc:
600
+ """``{ a, b }`` after a creation: inline, or a braced block of lines."""
601
+ if len(node.children) < 2:
602
+ return self.generic(node)
603
+ open_tok = node.children[0]
604
+ close_tok = node.children[-1]
605
+ items, commas = self.items_and_commas(node)
606
+ if not items:
607
+ return group([" " if leading_space else "", self.tok(open_tok), self.closing(close_tok, LINE)])
608
+ docs = self._comma_docs(items, commas, may_add=True)
609
+ simple = self.settings.simple_lists_inline and all(len(i) == 1 and _simple_value(i[0]) for i in items)
610
+ force = self._forced_initializer(items)
611
+ # Allman: a broken initializer's brace takes a line of its own.
612
+ before: Doc = LINE if (self.settings.brace_style == "allman" and leading_space) else (" " if leading_space else "")
613
+ if simple:
614
+ body = self._packed(docs, items, commas)
615
+ return group([before, self.tok(open_tok), indent([LINE, body]), self.closing(close_tok, LINE)])
616
+ return group(
617
+ [before, self.tok(open_tok), indent([LINE, join(LINE, docs)]), self.closing(close_tok, LINE)],
618
+ should_break=force,
619
+ )
620
+
621
+ def _packed(self, docs: list[Doc], items: Sequence[Sequence[Node]], commas: Sequence[Node]) -> Doc:
622
+ """Simple values as many to a line as fit; one to a line when any has a comment."""
623
+ nodes = [n for item in items for n in item] + list(commas)
624
+ if any(t.leading or t.trailing for n in nodes for t in n.tokens()):
625
+ return join(LINE, docs)
626
+ return Fill(_fill_parts(docs))
627
+
628
+ def _comma_docs(
629
+ self, items: Sequence[Sequence[Node]], commas: Sequence[Node], *, may_add: bool = False
630
+ ) -> list[Doc]:
631
+ """Each item with the comma after it; ``may_add`` lets trailing_commas = add add one."""
632
+ docs: list[Doc] = []
633
+ last = len(items) - 1
634
+ for i, item in enumerate(items):
635
+ if i < len(commas) and i < last:
636
+ docs.append([self.print_item(item), self.tok(commas[i])])
637
+ elif i < len(commas):
638
+ # A trailing comma that was kept: printed only when broken.
639
+ docs.append([self.print_item(item), IfBreak(self.tok(commas[i]), "")])
640
+ elif i == last and may_add and self.settings.trailing_commas == "add" and len(item) == 1:
641
+ # The added comma goes before a comment at the end of the item.
642
+ after, doc = self.split_trailing(item[0], functools.partial(self.print_item, item))
643
+ docs.append([doc, IfBreak(",", ""), after])
644
+ else:
645
+ docs.append(self.print_item(item))
646
+ return docs
647
+
648
+ def p_initializer_expression(self, node: Node) -> Doc:
649
+ # A nested initializer, such as B = { 1, 2 }, or one in an array.
650
+ return self.initializer(node, leading_space=False)
651
+
652
+ def p_collection_expression(self, node: Node) -> Doc:
653
+ if len(node.children) < 2:
654
+ return self.generic(node)
655
+ open_tok = node.children[0]
656
+ close_tok = node.children[-1]
657
+ items, commas = self.items_and_commas(node)
658
+ if not items:
659
+ return group([self.tok(open_tok), self.closing(close_tok, SOFTLINE)])
660
+ docs = self._comma_docs(items, commas, may_add=True)
661
+ simple = self.settings.simple_lists_inline and all(
662
+ len(i) == 1 and _simple_value(_element_value(i[0])) for i in items
663
+ )
664
+ if simple:
665
+ body = self._packed(docs, items, commas)
666
+ return group([self.tok(open_tok), indent([SOFTLINE, body]), self.closing(close_tok, SOFTLINE)])
667
+ force = self._forced_initializer([[_element_value(i[0])] for i in items if len(i) == 1])
668
+ return group(
669
+ [SOFTLINE, self.tok(open_tok), indent([LINE, join(LINE, docs)]), self.closing(close_tok, LINE)],
670
+ should_break=force,
671
+ )
672
+
673
+ def p_collection_element(self, node: Node) -> Doc:
674
+ return self.generic(node)
675
+
676
+ def p_anonymous_object_creation_expression(self, node: Node) -> Doc:
677
+ new = node.first_of("new")
678
+ open_tok = node.first_of("{")
679
+ close_tok = node.children[-1] if node.children else None
680
+ if new is None or open_tok is None or close_tok is None or close_tok.text != "}":
681
+ return self.generic(node)
682
+ braced = _braced_part(node)
683
+ items, commas = self.items_and_commas(braced)
684
+ if not items:
685
+ return [self.tok(new), " ", self.tok(open_tok), " ", self.tok(close_tok)]
686
+ docs = self._comma_docs(items, commas, may_add=True)
687
+ threshold = self.settings.initializer_break_threshold
688
+ force = threshold > 0 and len(items) >= threshold
689
+ before: Doc = LINE if self.settings.brace_style == "allman" else " "
690
+ return group(
691
+ [self.tok(new), before, self.tok(open_tok), indent([LINE, join(LINE, docs)]), self.closing(close_tok, LINE)],
692
+ should_break=force,
693
+ )
694
+
695
+ def p_with_expression(self, node: Node) -> Doc:
696
+ open_tok = node.first_of("{")
697
+ close_tok = node.children[-1] if node.children else None
698
+ with_tok = node.first_of("with")
699
+ if open_tok is None or close_tok is None or with_tok is None or close_tok.text != "}":
700
+ return self.generic(node)
701
+ expression = node.children[0]
702
+ braced = _braced_part(node)
703
+ items, commas = self.items_and_commas(braced)
704
+ # with_initializer wraps the assignments in some grammar versions.
705
+ if len(items) == 1 and len(items[0]) == 1 and items[0][0].type == "with_initializer":
706
+ inner = items[0][0]
707
+ items = [[c for c in group_] for group_ in _split_commas(inner.children)]
708
+ commas = [c for c in inner.children if c.text == "," and not c.named]
709
+ head = [self.print(expression), " ", self.tok(with_tok)]
710
+ if not items:
711
+ return group([head, " ", self.tok(open_tok), self.closing(close_tok, LINE)])
712
+ docs = self._comma_docs(items, commas, may_add=True)
713
+ force = self._forced_initializer(items)
714
+ before: Doc = LINE if self.settings.brace_style == "allman" else " "
715
+ return group([head, before, self.tok(open_tok), indent([LINE, join(LINE, docs)]), self.closing(close_tok, LINE)], should_break=force)
716
+
717
+ def p_with_initializer(self, node: Node) -> Doc:
718
+ return self.generic_sequence(node.children)
719
+
720
+ # Lambdas -------------------------------------------------------------
721
+
722
+ def p_lambda_expression(self, node: Node) -> Doc:
723
+ arrow = node.first_of("=>")
724
+ body = node.child("body")
725
+ if arrow is None or body is None:
726
+ return self.generic(node)
727
+ head: list[Doc] = []
728
+ previous: Node | None = None
729
+ for child in node.children:
730
+ if child is arrow:
731
+ break
732
+ if previous is not None:
733
+ head.append(" ")
734
+ head.append(self.print(child))
735
+ previous = child
736
+ if body.type == "block":
737
+ return [head, " ", self.tok(arrow), self.block(body, "lambda")]
738
+ body_doc = self.print(body)
739
+ if self.self_breaking(body) or self.block_like(body):
740
+ return [head, " ", self.tok(arrow), " ", body_doc]
741
+ return group([head, " ", self.tok(arrow), group(indent([LINE, body_doc]))])
742
+
743
+ def p_anonymous_method_expression(self, node: Node) -> Doc:
744
+ block = node.first_of("block")
745
+ if block is None or node.children[-1] is not block:
746
+ return self.generic(node)
747
+ head = self.generic_sequence(node.children[:-1])
748
+ return [head, self.block(block, "lambda")]
749
+
750
+ # Switch expressions ---------------------------------------------------
751
+
752
+ def p_switch_expression(self, node: Node) -> Doc:
753
+ switch = node.first_of("switch")
754
+ open_tok = node.first_of("{")
755
+ close_tok = node.children[-1] if node.children else None
756
+ if switch is None or open_tok is None or close_tok is None or close_tok.text != "}":
757
+ return self.generic(node)
758
+ governing = node.children[0]
759
+ arms = [c for c in node.children if c.type == "switch_expression_arm"]
760
+ commas = [c for c in node.children if c.text == "," and not c.named]
761
+ head = [self.print(governing), " ", self.tok(switch)]
762
+ if not arms:
763
+ return group([head, " ", self.tok(open_tok), self.closing(close_tok, LINE)])
764
+ comments: list[Doc] = []
765
+ lefts: list[Doc] = []
766
+ rights: list[Doc] = []
767
+ widths: list[int | None] = []
768
+ for arm in arms:
769
+ above, (left, right) = self.split_leading(arm, functools.partial(self._arm_parts, arm))
770
+ comments.append(above)
771
+ lefts.append(left)
772
+ rights.append(right)
773
+ widths.append(self.flat_width(left) if self.settings.align_switch_arms else None)
774
+ known = [w for w in widths if w is not None]
775
+ target = max(known) if known else 0
776
+ lines: list[Doc] = []
777
+ for i in range(len(arms)):
778
+ pad = ""
779
+ if widths[i] is not None and len(known) > 1:
780
+ pad = " " * (target - (widths[i] or 0))
781
+ doc: list[Doc] = [comments[i], lefts[i], pad, rights[i]]
782
+ comma = commas[i] if i < len(commas) else None
783
+ if comma is not None and (i < len(arms) - 1 or self.settings.trailing_commas != "remove"):
784
+ doc.append(self.tok(comma))
785
+ elif comma is None and i < len(arms) - 1:
786
+ doc.append(",")
787
+ lines.append(doc)
788
+ return [
789
+ head,
790
+ self.open_brace("statement"),
791
+ self.tok(open_tok),
792
+ indent([HARDLINE, join(HARDLINE, lines)]),
793
+ self.close_comments(close_tok),
794
+ HARDLINE,
795
+ self.tok(close_tok, leading=False),
796
+ ]
797
+
798
+ def _arm_parts(self, arm: Node) -> tuple[Doc, Doc]:
799
+ arrow = arm.first_of("=>")
800
+ if arrow is None:
801
+ return self.generic(arm), ""
802
+ index = arm.children.index(arrow)
803
+ left = self.generic_sequence(arm.children[:index])
804
+ value = arm.children[index + 1 :]
805
+ if len(value) != 1:
806
+ return left, [" ", self.tok(arrow), " ", self.generic_sequence(value)]
807
+ value_node = value[0]
808
+ if self.self_breaking(value_node) or self.block_like(value_node):
809
+ return left, [" ", self.tok(arrow), " ", self.print(value_node)]
810
+ return left, group([" ", self.tok(arrow), group(indent([LINE, self.print(value_node)]))])
811
+
812
+ def flat_width(self, doc: Doc) -> int | None:
813
+ """The width of a document printed on one line, or None if it cannot be."""
814
+ text = print_doc(doc, 1_000_000)
815
+ if "\n" in text or "\r" in text:
816
+ return None
817
+ return len(text)
818
+
819
+ # Operators and small forms ---------------------------------------------
820
+
821
+ def p_prefix_unary_expression(self, node: Node) -> Doc:
822
+ if len(node.children) != 2:
823
+ return self.generic(node)
824
+ op, operand = node.children
825
+ return self.join_tokens([(op, self.tok(op)), (operand, self.print(operand))])
826
+
827
+ def p_postfix_unary_expression(self, node: Node) -> Doc:
828
+ if len(node.children) != 2:
829
+ return self.generic(node)
830
+ operand, op = node.children
831
+ if op.text == "!":
832
+ return self.chain(node)
833
+ return self.join_tokens([(operand, self.print(operand)), (op, self.tok(op))])
834
+
835
+ def p_cast_expression(self, node: Node) -> Doc:
836
+ return self.join_tokens([(c, self.print(c)) for c in node.children])
837
+
838
+ def p_parenthesized_expression(self, node: Node) -> Doc:
839
+ return self.join_tokens([(c, self.print(c)) for c in node.children])
840
+
841
+ def p_await_expression(self, node: Node) -> Doc:
842
+ return self.spaced(node.children)
843
+
844
+ def p_throw_expression(self, node: Node) -> Doc:
845
+ return self.spaced(node.children)
846
+
847
+ def p_ref_expression(self, node: Node) -> Doc:
848
+ return self.spaced(node.children)
849
+
850
+ def p_declaration_expression(self, node: Node) -> Doc:
851
+ return self.spaced(node.children)
852
+
853
+ def p_as_expression(self, node: Node) -> Doc:
854
+ return self.spaced(node.children)
855
+
856
+ def p_is_expression(self, node: Node) -> Doc:
857
+ return self.spaced(node.children)
858
+
859
+ def p_is_pattern_expression(self, node: Node) -> Doc:
860
+ return self.spaced(node.children)
861
+
862
+ def p_range_expression(self, node: Node) -> Doc:
863
+ return self.join_tokens([(c, self.print(c)) for c in node.children])
864
+
865
+ def p_typeof_expression(self, node: Node) -> Doc:
866
+ return self.join_tokens([(c, self.print(c)) for c in node.children])
867
+
868
+ def p_sizeof_expression(self, node: Node) -> Doc:
869
+ return self.join_tokens([(c, self.print(c)) for c in node.children])
870
+
871
+ def p_default_expression(self, node: Node) -> Doc:
872
+ return self.join_tokens([(c, self.print(c)) for c in node.children])
873
+
874
+ def p_checked_expression(self, node: Node) -> Doc:
875
+ return self.join_tokens([(c, self.print(c)) for c in node.children])
876
+
877
+ def p_makeref_expression(self, node: Node) -> Doc:
878
+ return self.join_tokens([(c, self.print(c)) for c in node.children])
879
+
880
+ def p_member_binding_expression(self, node: Node) -> Doc:
881
+ return self.join_tokens([(c, self.print(c)) for c in node.children])
882
+
883
+ def p_element_binding_expression(self, node: Node) -> Doc:
884
+ return self.arguments(node)
885
+
886
+ # Patterns ------------------------------------------------------------
887
+
888
+ def p_property_pattern_clause(self, node: Node) -> Doc:
889
+ if len(node.children) < 2:
890
+ return self.generic(node)
891
+ open_tok = node.children[0]
892
+ close_tok = node.children[-1]
893
+ items, commas = self.items_and_commas(node)
894
+ if not items:
895
+ return group([self.tok(open_tok), self.closing(close_tok, LINE)])
896
+ docs = self._comma_docs(items, commas)
897
+ return group([self.tok(open_tok), indent([LINE, join(LINE, docs)]), self.closing(close_tok, LINE)])
898
+
899
+ def p_positional_pattern_clause(self, node: Node) -> Doc:
900
+ return self.arguments(node)
901
+
902
+ def p_list_pattern(self, node: Node) -> Doc:
903
+ return self.arguments(node)
904
+
905
+ def p_tuple_pattern(self, node: Node) -> Doc:
906
+ return self.arguments(node)
907
+
908
+ def p_parenthesized_variable_designation(self, node: Node) -> Doc:
909
+ return self.arguments(node)
910
+
911
+ def p_subpattern(self, node: Node) -> Doc:
912
+ colon = node.first_of(":")
913
+ if colon is None:
914
+ return self.generic(node)
915
+ index = node.children.index(colon)
916
+ return [
917
+ self.generic_sequence(node.children[:index]),
918
+ self.tok(colon),
919
+ " ",
920
+ self.generic_sequence(node.children[index + 1 :]),
921
+ ]
922
+
923
+ def p_relational_pattern(self, node: Node) -> Doc:
924
+ return self.spaced(node.children)
925
+
926
+ def p_and_pattern(self, node: Node) -> Doc:
927
+ return self.spaced(node.children)
928
+
929
+ def p_or_pattern(self, node: Node) -> Doc:
930
+ return self.spaced(node.children)
931
+
932
+ def p_negated_pattern(self, node: Node) -> Doc:
933
+ return self.spaced(node.children)
934
+
935
+ def p_declaration_pattern(self, node: Node) -> Doc:
936
+ return self.spaced(node.children)
937
+
938
+ def p_recursive_pattern(self, node: Node) -> Doc:
939
+ parts: list[Doc] = []
940
+ previous: Node | None = None
941
+ for child in node.children:
942
+ if previous is not None and not (child.type == "positional_pattern_clause" and previous.named):
943
+ parts.append(" ")
944
+ parts.append(self.print(child))
945
+ previous = child
946
+ return parts
947
+
948
+ def p_var_pattern(self, node: Node) -> Doc:
949
+ return self.spaced(node.children)
950
+
951
+ def p_when_clause(self, node: Node) -> Doc:
952
+ return self.spaced(node.children)
953
+
954
+ # Queries -------------------------------------------------------------
955
+
956
+ def p_query_expression(self, node: Node) -> Doc:
957
+ segments: list[list[Node]] = []
958
+ for child in node.children:
959
+ # A clause starts a line; `into name` continues the clause before it.
960
+ if child.type.endswith("_clause") or not segments:
961
+ segments.append([child])
962
+ else:
963
+ segments[-1].append(child)
964
+ docs = [self.generic_sequence(segment) for segment in segments]
965
+ if len(docs) < 2:
966
+ return docs
967
+ return group([docs[0], indent([[LINE, d] for d in docs[1:]])])
968
+
969
+ # Names and types ------------------------------------------------------
970
+
971
+ def p_generic_name(self, node: Node) -> Doc:
972
+ return [self.print(c) for c in node.children]
973
+
974
+ def p_type_argument_list(self, node: Node) -> Doc:
975
+ return self.angle_list(node)
976
+
977
+ def p_type_parameter_list(self, node: Node) -> Doc:
978
+ return self.angle_list(node)
979
+
980
+ def angle_list(self, node: Node) -> Doc:
981
+ if len(node.children) < 2:
982
+ return self.generic(node)
983
+ inner = node.children[1:-1]
984
+ if all(c.text == "," and not c.named for c in inner):
985
+ # An open generic type, typeof(Dictionary<,>): only commas.
986
+ return self.join_tokens([(c, self.print(c)) for c in node.children])
987
+ items, commas = self.items_and_commas(node)
988
+ docs: list[Doc] = []
989
+ for i, item in enumerate(items):
990
+ doc = self.print_item(item)
991
+ if i < len(commas):
992
+ doc = [doc, self.tok(commas[i])]
993
+ docs.append(doc)
994
+ return [self.tok(node.children[0]), join(" ", docs), self.tok(node.children[-1])]
995
+
996
+ def p_type_parameter(self, node: Node) -> Doc:
997
+ return self.spaced(node.children)
998
+
999
+ def p_qualified_name(self, node: Node) -> Doc:
1000
+ return self.join_tokens([(c, self.print(c)) for c in node.children])
1001
+
1002
+ def p_alias_qualified_name(self, node: Node) -> Doc:
1003
+ return self.join_tokens([(c, self.print(c)) for c in node.children])
1004
+
1005
+ def p_array_type(self, node: Node) -> Doc:
1006
+ return self.join_tokens([(c, self.print(c)) for c in node.children])
1007
+
1008
+ def p_array_rank_specifier(self, node: Node) -> Doc:
1009
+ parts: list[Doc] = []
1010
+ for child in node.children:
1011
+ parts.append(self.print(child))
1012
+ if child.text == "," and child is not node.children[-2] and node.children[node.children.index(child) + 1].text not in (",", "]"):
1013
+ parts.append(" ")
1014
+ return parts
1015
+
1016
+ def p_nullable_type(self, node: Node) -> Doc:
1017
+ return self.join_tokens([(c, self.print(c)) for c in node.children])
1018
+
1019
+ def p_pointer_type(self, node: Node) -> Doc:
1020
+ return self.join_tokens([(c, self.print(c)) for c in node.children])
1021
+
1022
+ def p_tuple_type(self, node: Node) -> Doc:
1023
+ return self.arguments(node)
1024
+
1025
+ def p_tuple_element(self, node: Node) -> Doc:
1026
+ return self.spaced(node.children)
1027
+
1028
+ def p_ref_type(self, node: Node) -> Doc:
1029
+ return self.spaced(node.children)
1030
+
1031
+ def p_scoped_type(self, node: Node) -> Doc:
1032
+ return self.spaced(node.children)
1033
+
1034
+ def p_explicit_interface_specifier(self, node: Node) -> Doc:
1035
+ return self.join_tokens([(c, self.print(c)) for c in node.children])
1036
+
1037
+ def p_expression_element(self, node: Node) -> Doc:
1038
+ return self.generic(node)
1039
+
1040
+ def p_spread_element(self, node: Node) -> Doc:
1041
+ return self.join_tokens([(c, self.print(c)) for c in node.children])
1042
+
1043
+
1044
+ class _OperatorMarker(Node):
1045
+ """A placeholder in the binary-expression flattening stack."""
1046
+
1047
+ def __init__(self, op: Node) -> None:
1048
+ super().__init__(type="__operator__")
1049
+ self.op = op
1050
+
1051
+
1052
+ def _descendants(node: Node) -> list[Node]:
1053
+ out: list[Node] = []
1054
+ stack = list(node.children)
1055
+ while stack:
1056
+ current = stack.pop()
1057
+ out.append(current)
1058
+ stack.extend(current.children)
1059
+ return out
1060
+
1061
+
1062
+ def _simple_value(node: Node) -> bool:
1063
+ if node.type in _SIMPLE_VALUE_TYPES:
1064
+ return True
1065
+ if node.type == "prefix_unary_expression" and len(node.children) == 2:
1066
+ return node.children[0].text in ("-", "+") and node.children[1].type in ("integer_literal", "real_literal")
1067
+ if node.type in ("member_access_expression", "qualified_name"):
1068
+ return all(c.type in ("identifier", "member_access_expression", "qualified_name") or c.text == "." for c in node.children)
1069
+ return False
1070
+
1071
+
1072
+ def _element_value(node: Node) -> Node:
1073
+ # collection_element > expression_element > value; ..value spreads a value.
1074
+ while True:
1075
+ if node.type in ("collection_element", "expression_element") and len(node.children) == 1:
1076
+ node = node.children[0]
1077
+ elif node.type == "spread_element" and len(node.children) == 2:
1078
+ node = node.children[1]
1079
+ else:
1080
+ return node
1081
+
1082
+
1083
+ def _fill_parts(docs: Sequence[Doc]) -> list[Doc]:
1084
+ parts: list[Doc] = []
1085
+ for i, doc in enumerate(docs):
1086
+ if i:
1087
+ parts.append(LINE)
1088
+ parts.append(doc)
1089
+ return parts
1090
+
1091
+
1092
+ def _braced_part(node: Node) -> Node:
1093
+ """A stand-in node holding ``{`` ... ``}`` of a node, for splitting at commas."""
1094
+ open_index = next(i for i, c in enumerate(node.children) if c.text == "{")
1095
+ holder = Node(type="braced")
1096
+ holder.children = node.children[open_index:]
1097
+ return holder
1098
+
1099
+
1100
+ def _split_commas(children: Sequence[Node]) -> list[list[Node]]:
1101
+ groups: list[list[Node]] = [[]]
1102
+ for child in children:
1103
+ if child.text == "," and not child.named:
1104
+ groups.append([])
1105
+ else:
1106
+ groups[-1].append(child)
1107
+ return [g for g in groups if g]