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
prettycsharp/rewrites.py
ADDED
|
@@ -0,0 +1,732 @@
|
|
|
1
|
+
"""Syntax rewrites: the changes that move or replace tokens.
|
|
2
|
+
|
|
3
|
+
Each rewrite edits the syntax model in place before printing. A token that
|
|
4
|
+
is removed hands its comments to a neighbour, so no comment is lost; the
|
|
5
|
+
verifier checks that every rewrite left the code meaning the same.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from collections.abc import Iterable, Sequence
|
|
11
|
+
|
|
12
|
+
from prettycsharp.settings import Settings
|
|
13
|
+
from prettycsharp.syntax import Comment, Node, SourceFile, token, walk
|
|
14
|
+
from prettycsharp.verify import is_embedded
|
|
15
|
+
|
|
16
|
+
_MODIFIER_OWNERS = frozenset(
|
|
17
|
+
{
|
|
18
|
+
"class_declaration",
|
|
19
|
+
"struct_declaration",
|
|
20
|
+
"interface_declaration",
|
|
21
|
+
"record_declaration",
|
|
22
|
+
"enum_declaration",
|
|
23
|
+
"delegate_declaration",
|
|
24
|
+
"method_declaration",
|
|
25
|
+
"constructor_declaration",
|
|
26
|
+
"destructor_declaration",
|
|
27
|
+
"operator_declaration",
|
|
28
|
+
"conversion_operator_declaration",
|
|
29
|
+
"property_declaration",
|
|
30
|
+
"indexer_declaration",
|
|
31
|
+
"event_declaration",
|
|
32
|
+
"event_field_declaration",
|
|
33
|
+
"field_declaration",
|
|
34
|
+
"local_function_statement",
|
|
35
|
+
}
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# Declarations whose attribute lists go one attribute per list.
|
|
39
|
+
_ATTRIBUTE_OWNERS = _MODIFIER_OWNERS | {"accessor_declaration", "enum_member_declaration", "compilation_unit"}
|
|
40
|
+
|
|
41
|
+
_BODY_OWNERS = frozenset(
|
|
42
|
+
{
|
|
43
|
+
"method_declaration",
|
|
44
|
+
"constructor_declaration",
|
|
45
|
+
"destructor_declaration",
|
|
46
|
+
"operator_declaration",
|
|
47
|
+
"conversion_operator_declaration",
|
|
48
|
+
"local_function_statement",
|
|
49
|
+
}
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
_LIST_WITH_TRAILING_COMMA = frozenset(
|
|
53
|
+
{
|
|
54
|
+
"initializer_expression",
|
|
55
|
+
"collection_expression",
|
|
56
|
+
"enum_member_declaration_list",
|
|
57
|
+
"anonymous_object_creation_expression",
|
|
58
|
+
"switch_expression",
|
|
59
|
+
"with_expression",
|
|
60
|
+
"list_pattern",
|
|
61
|
+
"property_pattern_clause",
|
|
62
|
+
}
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
_LITERALS = frozenset(
|
|
66
|
+
{
|
|
67
|
+
"integer_literal",
|
|
68
|
+
"real_literal",
|
|
69
|
+
"string_literal",
|
|
70
|
+
"verbatim_string_literal",
|
|
71
|
+
"raw_string_literal",
|
|
72
|
+
"character_literal",
|
|
73
|
+
"boolean_literal",
|
|
74
|
+
"null_literal",
|
|
75
|
+
}
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def rewrite(file: SourceFile, settings: Settings, lang_version: float) -> None:
|
|
80
|
+
root = file.root
|
|
81
|
+
if settings.namespace_style != "preserve":
|
|
82
|
+
target = settings.namespace_style
|
|
83
|
+
if target == "file_scoped_if_supported":
|
|
84
|
+
target = "file_scoped" if lang_version >= 10 else "block"
|
|
85
|
+
if target == "file_scoped":
|
|
86
|
+
to_file_scoped(file)
|
|
87
|
+
else:
|
|
88
|
+
to_block_scoped(file)
|
|
89
|
+
if settings.using_order != "preserve":
|
|
90
|
+
sort_usings(root, settings.using_order)
|
|
91
|
+
if settings.modifiers is not None:
|
|
92
|
+
order_modifiers(root, settings.modifiers)
|
|
93
|
+
if settings.attributes == "separate":
|
|
94
|
+
separate_attributes(root)
|
|
95
|
+
elif settings.attributes == "combine":
|
|
96
|
+
combine_attributes(root)
|
|
97
|
+
if settings.expression_bodies != "preserve":
|
|
98
|
+
expression_bodies(root, settings.expression_bodies, lang_version)
|
|
99
|
+
if settings.parentheses != "preserve":
|
|
100
|
+
remove_parentheses(root, settings.parentheses)
|
|
101
|
+
if settings.braces == "always":
|
|
102
|
+
add_braces(root)
|
|
103
|
+
if settings.trailing_commas == "remove":
|
|
104
|
+
remove_trailing_commas(root)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# Trivia ------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _frozen(node: Node) -> bool:
|
|
111
|
+
current: Node | None = node
|
|
112
|
+
while current is not None:
|
|
113
|
+
if current.verbatim:
|
|
114
|
+
return True
|
|
115
|
+
current = current.parent
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _has_comments(nodes: Iterable[Node]) -> bool:
|
|
120
|
+
return any(t.leading or t.trailing for n in nodes for t in n.tokens())
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _has_verbatim(node: Node) -> bool:
|
|
124
|
+
"""Whether any part of the node is printed as written, and so must not move."""
|
|
125
|
+
return any(n.verbatim for n in walk(node))
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _set_children(node: Node, children: list[Node]) -> None:
|
|
129
|
+
node.children = children
|
|
130
|
+
for child in children:
|
|
131
|
+
child.parent = node
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _as_leading(comments: Sequence[Comment]) -> list[Comment]:
|
|
135
|
+
for comment in comments:
|
|
136
|
+
comment.own_line = True
|
|
137
|
+
comment.newline_after = True
|
|
138
|
+
comment.newlines_before = max(comment.newlines_before, 1)
|
|
139
|
+
return list(comments)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
# Namespaces --------------------------------------------------------------
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def to_file_scoped(file: SourceFile) -> None:
|
|
146
|
+
root = file.root
|
|
147
|
+
children = root.children
|
|
148
|
+
namespaces = [c for c in children if c.type == "namespace_declaration"]
|
|
149
|
+
if len(namespaces) != 1 or any(c.type == "file_scoped_namespace_declaration" for c in children):
|
|
150
|
+
return
|
|
151
|
+
namespace = namespaces[0]
|
|
152
|
+
if _frozen(namespace):
|
|
153
|
+
return
|
|
154
|
+
index = children.index(namespace)
|
|
155
|
+
allowed = ("using_directive", "extern_alias_directive", "global_attribute")
|
|
156
|
+
if any(c.type not in allowed and not c.type.startswith("preproc") for c in children[:index]):
|
|
157
|
+
return
|
|
158
|
+
if children[index + 1 :]:
|
|
159
|
+
return
|
|
160
|
+
body = namespace.child("body")
|
|
161
|
+
if body is None or body.type != "declaration_list" or len(body.children) < 2:
|
|
162
|
+
return
|
|
163
|
+
head = [c for c in namespace.children if c is not body]
|
|
164
|
+
if head[-1:] and head[-1].text == ";":
|
|
165
|
+
return
|
|
166
|
+
members = body.children[1:-1]
|
|
167
|
+
if any(n.type in ("namespace_declaration", "file_scoped_namespace_declaration") for m in members for n in walk(m)):
|
|
168
|
+
return
|
|
169
|
+
open_tok, close_tok = body.children[0], body.children[-1]
|
|
170
|
+
semicolon = token(";")
|
|
171
|
+
# Comments around the braces move: after { to the first member, before
|
|
172
|
+
# and after } to the end of the file.
|
|
173
|
+
moved = _as_leading([*open_tok.leading, *open_tok.trailing])
|
|
174
|
+
first = members[0].first_token() if members else None
|
|
175
|
+
if first is not None:
|
|
176
|
+
first.leading[:0] = moved
|
|
177
|
+
else:
|
|
178
|
+
file.trailing_comments[:0] = moved
|
|
179
|
+
file.trailing_comments[:0] = _as_leading([*close_tok.leading, *close_tok.trailing])
|
|
180
|
+
scoped = Node(type="file_scoped_namespace_declaration")
|
|
181
|
+
_set_children(scoped, [*head, semicolon])
|
|
182
|
+
_set_children(root, [*children[:index], scoped, *members])
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def to_block_scoped(file: SourceFile) -> None:
|
|
186
|
+
root = file.root
|
|
187
|
+
children = root.children
|
|
188
|
+
scoped = next((c for c in children if c.type == "file_scoped_namespace_declaration"), None)
|
|
189
|
+
if scoped is None or _frozen(scoped) or not scoped.children or scoped.children[-1].text != ";":
|
|
190
|
+
return
|
|
191
|
+
index = children.index(scoped)
|
|
192
|
+
members = children[index + 1 :]
|
|
193
|
+
semicolon = scoped.children[-1]
|
|
194
|
+
open_tok = token("{")
|
|
195
|
+
close_tok = token("}")
|
|
196
|
+
open_tok.trailing = list(semicolon.trailing)
|
|
197
|
+
open_tok.leading = list(semicolon.leading)
|
|
198
|
+
close_tok.leading = _as_leading(file.trailing_comments)
|
|
199
|
+
file.trailing_comments = []
|
|
200
|
+
body = Node(type="declaration_list", field="body")
|
|
201
|
+
_set_children(body, [open_tok, *members, close_tok])
|
|
202
|
+
namespace = Node(type="namespace_declaration")
|
|
203
|
+
_set_children(namespace, [*scoped.children[:-1], body])
|
|
204
|
+
_set_children(root, [*children[:index], namespace])
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
# Usings ----------------------------------------------------------------
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def using_key(node: Node, order: str) -> tuple[int, int, int, str]:
|
|
211
|
+
texts = [t.text or "" for t in node.tokens()]
|
|
212
|
+
is_global = 0 if texts[:1] == ["global"] else 1
|
|
213
|
+
words = [w for w in texts if w not in ("global", "using", ";")]
|
|
214
|
+
if words[:1] == ["static"]:
|
|
215
|
+
kind, words = 1, words[1:]
|
|
216
|
+
elif "=" in words:
|
|
217
|
+
kind = 2
|
|
218
|
+
else:
|
|
219
|
+
kind = 0
|
|
220
|
+
if words[:1] == ["unsafe"]:
|
|
221
|
+
words = words[1:]
|
|
222
|
+
name = "".join(words)
|
|
223
|
+
system = 1
|
|
224
|
+
if order in ("system_first", "system_first_grouped") and (name == "System" or name.startswith("System.")):
|
|
225
|
+
system = 0
|
|
226
|
+
return (is_global, kind, system, name)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def sort_usings(root: Node, order: str) -> None:
|
|
230
|
+
for node in walk(root):
|
|
231
|
+
if node.type not in ("compilation_unit", "declaration_list") or _frozen(node):
|
|
232
|
+
continue
|
|
233
|
+
children = node.children
|
|
234
|
+
i = 0
|
|
235
|
+
while i < len(children):
|
|
236
|
+
if children[i].type != "using_directive":
|
|
237
|
+
i += 1
|
|
238
|
+
continue
|
|
239
|
+
end = i
|
|
240
|
+
while end < len(children) and children[end].type == "using_directive":
|
|
241
|
+
end += 1
|
|
242
|
+
run = children[i:end]
|
|
243
|
+
ordered = sorted(run, key=lambda n: using_key(n, order))
|
|
244
|
+
if ordered != run:
|
|
245
|
+
_keep_header(run, ordered)
|
|
246
|
+
children[i:end] = ordered
|
|
247
|
+
i = end
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _keep_header(run: Sequence[Node], ordered: Sequence[Node]) -> None:
|
|
251
|
+
"""The comments above the first item stay above the first item."""
|
|
252
|
+
old = run[0].first_token()
|
|
253
|
+
new = ordered[0].first_token()
|
|
254
|
+
if old is None or new is None or old is new:
|
|
255
|
+
return
|
|
256
|
+
header = old.leading
|
|
257
|
+
old.leading = []
|
|
258
|
+
new.leading[:0] = header
|
|
259
|
+
old.newlines_before, new.newlines_before = new.newlines_before, old.newlines_before
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
# Modifiers ---------------------------------------------------------------
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def modifier_rank(text: str, order: Sequence[str]) -> float:
|
|
266
|
+
if text == "partial":
|
|
267
|
+
return 1000
|
|
268
|
+
if text == "ref":
|
|
269
|
+
return 999
|
|
270
|
+
if text == "const":
|
|
271
|
+
text = "static"
|
|
272
|
+
if text in order:
|
|
273
|
+
return order.index(text)
|
|
274
|
+
return 900
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def order_modifiers(root: Node, order: Sequence[str]) -> None:
|
|
278
|
+
for node in walk(root):
|
|
279
|
+
if node.type not in _MODIFIER_OWNERS or _frozen(node):
|
|
280
|
+
continue
|
|
281
|
+
indexes = [i for i, c in enumerate(node.children) if c.type == "modifier"]
|
|
282
|
+
if len(indexes) < 2 or indexes[-1] - indexes[0] != len(indexes) - 1:
|
|
283
|
+
continue
|
|
284
|
+
run = [node.children[i] for i in indexes]
|
|
285
|
+
ordered = sorted(run, key=lambda m: modifier_rank(_token_text(m), order))
|
|
286
|
+
if ordered != run:
|
|
287
|
+
_keep_header(run, ordered)
|
|
288
|
+
node.children[indexes[0] : indexes[-1] + 1] = ordered
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
# Attributes --------------------------------------------------------------
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def separate_attributes(root: Node) -> None:
|
|
295
|
+
for node in list(walk(root)):
|
|
296
|
+
if node.type not in _ATTRIBUTE_OWNERS or _frozen(node):
|
|
297
|
+
continue
|
|
298
|
+
children: list[Node] = []
|
|
299
|
+
changed = False
|
|
300
|
+
for child in node.children:
|
|
301
|
+
if child.type in ("attribute_list", "global_attribute"):
|
|
302
|
+
pieces = _split_list(child)
|
|
303
|
+
changed = changed or len(pieces) > 1
|
|
304
|
+
children.extend(pieces)
|
|
305
|
+
else:
|
|
306
|
+
children.append(child)
|
|
307
|
+
if changed:
|
|
308
|
+
_set_children(node, children)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def _split_list(node: Node) -> list[Node]:
|
|
312
|
+
kids = node.children
|
|
313
|
+
attributes = [c for c in kids if c.type == "attribute"]
|
|
314
|
+
if len(attributes) < 2 or kids[0].text != "[" or kids[-1].text != "]":
|
|
315
|
+
return [node]
|
|
316
|
+
if any(c.type.startswith("preproc") for c in kids):
|
|
317
|
+
return [node]
|
|
318
|
+
target = [c for c in kids[1:-1] if c.type not in ("attribute",) and c.text != ","]
|
|
319
|
+
commas = [c for c in kids if c.text == "," and not c.named]
|
|
320
|
+
# A comma's comments go to the attribute before it.
|
|
321
|
+
for comma, attribute in zip(commas, attributes, strict=False):
|
|
322
|
+
last = attribute.last_token()
|
|
323
|
+
if last is not None:
|
|
324
|
+
last.trailing.extend(comma.trailing)
|
|
325
|
+
last.trailing.extend(comma.leading)
|
|
326
|
+
pieces: list[Node] = []
|
|
327
|
+
for i, attribute in enumerate(attributes):
|
|
328
|
+
open_tok = kids[0] if i == 0 else token("[")
|
|
329
|
+
close_tok = kids[-1] if i == len(attributes) - 1 else token("]")
|
|
330
|
+
piece_target = target if i == 0 else [_copy_tokens(t) for t in target]
|
|
331
|
+
piece = Node(type=node.type, field=node.field)
|
|
332
|
+
_set_children(piece, [open_tok, *piece_target, attribute, close_tok])
|
|
333
|
+
pieces.append(piece)
|
|
334
|
+
return pieces
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def _copy_tokens(node: Node) -> Node:
|
|
338
|
+
if node.text is not None:
|
|
339
|
+
return Node(type=node.type, named=node.named, text=node.text, field=node.field)
|
|
340
|
+
copy = Node(type=node.type, named=node.named, field=node.field)
|
|
341
|
+
_set_children(copy, [_copy_tokens(c) for c in node.children])
|
|
342
|
+
return copy
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def combine_attributes(root: Node) -> None:
|
|
346
|
+
for node in list(walk(root)):
|
|
347
|
+
if node.type not in _ATTRIBUTE_OWNERS or _frozen(node):
|
|
348
|
+
continue
|
|
349
|
+
children: list[Node] = []
|
|
350
|
+
for child in node.children:
|
|
351
|
+
previous = children[-1] if children else None
|
|
352
|
+
if (
|
|
353
|
+
child.type == "attribute_list"
|
|
354
|
+
and previous is not None
|
|
355
|
+
and previous.type == "attribute_list"
|
|
356
|
+
and _target_text(previous) == _target_text(child)
|
|
357
|
+
and not _has_comments([child])
|
|
358
|
+
and not any(c.type.startswith("preproc") for c in child.children + previous.children)
|
|
359
|
+
):
|
|
360
|
+
attributes = [c for c in child.children if c.type == "attribute"]
|
|
361
|
+
merged = previous.children[:-1]
|
|
362
|
+
for attribute in attributes:
|
|
363
|
+
merged.extend([token(","), attribute])
|
|
364
|
+
merged.append(previous.children[-1])
|
|
365
|
+
_set_children(previous, merged)
|
|
366
|
+
else:
|
|
367
|
+
children.append(child)
|
|
368
|
+
_set_children(node, children)
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def _target_text(node: Node) -> str:
|
|
372
|
+
target = node.first_of("attribute_target_specifier")
|
|
373
|
+
return "".join(t.text or "" for t in target.tokens()) if target is not None else ""
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
# Expression bodies -------------------------------------------------------
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def expression_bodies(root: Node, mode: str, lang_version: float) -> None:
|
|
380
|
+
for node in list(walk(root)):
|
|
381
|
+
if _frozen(node):
|
|
382
|
+
continue
|
|
383
|
+
if node.type in _BODY_OWNERS:
|
|
384
|
+
if mode == "prefer":
|
|
385
|
+
if node.type in ("constructor_declaration", "destructor_declaration") and lang_version < 7:
|
|
386
|
+
continue
|
|
387
|
+
if lang_version >= 6:
|
|
388
|
+
_block_to_arrow(node)
|
|
389
|
+
elif mode in ("block", "properties_only"):
|
|
390
|
+
_arrow_to_block(node, _returns_value(node))
|
|
391
|
+
elif node.type in ("property_declaration", "indexer_declaration", "event_declaration"):
|
|
392
|
+
accessors = node.child("accessors")
|
|
393
|
+
if mode in ("prefer", "properties_only"):
|
|
394
|
+
if accessors is not None and lang_version >= 7:
|
|
395
|
+
for accessor in accessors.children:
|
|
396
|
+
if accessor.type == "accessor_declaration":
|
|
397
|
+
_block_to_arrow(accessor)
|
|
398
|
+
if node.type != "event_declaration" and lang_version >= 6:
|
|
399
|
+
_getter_to_property_arrow(node)
|
|
400
|
+
elif mode == "block":
|
|
401
|
+
if node.type != "event_declaration":
|
|
402
|
+
_property_arrow_to_getter(node)
|
|
403
|
+
accessors = node.child("accessors")
|
|
404
|
+
if accessors is not None:
|
|
405
|
+
for accessor in accessors.children:
|
|
406
|
+
if accessor.type == "accessor_declaration":
|
|
407
|
+
keyword = accessor.child("name") or accessor.first_token()
|
|
408
|
+
returns = keyword is not None and keyword.text == "get"
|
|
409
|
+
_arrow_to_block(accessor, returns)
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def _block_to_arrow(node: Node) -> None:
|
|
413
|
+
body = node.child("body")
|
|
414
|
+
if body is None or body.type != "block" or len(body.children) != 3:
|
|
415
|
+
return
|
|
416
|
+
statement = body.children[1]
|
|
417
|
+
if _has_comments([body.children[0], body.children[2]]) or _has_comments([statement]) or _has_verbatim(statement):
|
|
418
|
+
return
|
|
419
|
+
expression = _statement_expression(statement)
|
|
420
|
+
if expression is None:
|
|
421
|
+
return
|
|
422
|
+
arrow = Node(type="arrow_expression_clause", field="body")
|
|
423
|
+
_set_children(arrow, [token("=>"), expression])
|
|
424
|
+
semicolon = statement.children[-1]
|
|
425
|
+
index = node.children.index(body)
|
|
426
|
+
_set_children(node, [*node.children[:index], arrow, semicolon, *node.children[index + 1 :]])
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def _statement_expression(statement: Node) -> Node | None:
|
|
430
|
+
kids = statement.children
|
|
431
|
+
if not kids or kids[-1].text != ";":
|
|
432
|
+
return None
|
|
433
|
+
if statement.type == "expression_statement" and len(kids) == 2:
|
|
434
|
+
return kids[0]
|
|
435
|
+
if statement.type == "return_statement" and len(kids) == 3 and kids[1].text != "ref":
|
|
436
|
+
return kids[1]
|
|
437
|
+
if statement.type == "throw_statement" and len(kids) == 3 and kids[1].named:
|
|
438
|
+
thrown = Node(type="throw_expression")
|
|
439
|
+
_set_children(thrown, [kids[0], kids[1]])
|
|
440
|
+
return thrown
|
|
441
|
+
return None
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
def _returns_value(node: Node) -> bool:
|
|
445
|
+
"""Whether a block body for this member needs ``return``."""
|
|
446
|
+
if node.type in ("constructor_declaration", "destructor_declaration"):
|
|
447
|
+
return False
|
|
448
|
+
if node.type in ("operator_declaration", "conversion_operator_declaration"):
|
|
449
|
+
return True
|
|
450
|
+
returns = node.child("returns") or node.child("type")
|
|
451
|
+
if returns is None:
|
|
452
|
+
return False
|
|
453
|
+
text = "".join(t.text or "" for t in returns.tokens())
|
|
454
|
+
if text == "void":
|
|
455
|
+
return False
|
|
456
|
+
is_async = any(c.type == "modifier" and c.first_token() and c.first_token().text == "async" for c in node.children) # type: ignore[union-attr]
|
|
457
|
+
if is_async and text in ("Task", "ValueTask", "System.Threading.Tasks.Task", "System.Threading.Tasks.ValueTask"):
|
|
458
|
+
return False
|
|
459
|
+
return True
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def _arrow_to_block(node: Node, returns: bool) -> None:
|
|
463
|
+
arrow = next((c for c in node.children if c.type == "arrow_expression_clause"), None)
|
|
464
|
+
if arrow is None or len(arrow.children) != 2:
|
|
465
|
+
return
|
|
466
|
+
index = node.children.index(arrow)
|
|
467
|
+
after = node.children[index + 1 :]
|
|
468
|
+
if not after or after[0].text != ";":
|
|
469
|
+
return
|
|
470
|
+
semicolon = after[0]
|
|
471
|
+
arrow_tok, expression = arrow.children
|
|
472
|
+
if arrow_tok.leading or arrow_tok.trailing:
|
|
473
|
+
return
|
|
474
|
+
if expression.type == "throw_expression" and len(expression.children) == 2:
|
|
475
|
+
statement = Node(type="throw_statement")
|
|
476
|
+
_set_children(statement, [expression.children[0], expression.children[1], semicolon])
|
|
477
|
+
elif returns:
|
|
478
|
+
statement = Node(type="return_statement")
|
|
479
|
+
_set_children(statement, [token("return"), expression, semicolon])
|
|
480
|
+
else:
|
|
481
|
+
statement = Node(type="expression_statement")
|
|
482
|
+
_set_children(statement, [expression, semicolon])
|
|
483
|
+
body = Node(type="block", field="body")
|
|
484
|
+
_set_children(body, [token("{"), statement, token("}")])
|
|
485
|
+
_set_children(node, [*node.children[:index], body, *after[1:]])
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def _getter_to_property_arrow(node: Node) -> None:
|
|
489
|
+
accessors = node.child("accessors")
|
|
490
|
+
if accessors is None or node.children[-1] is not accessors or _frozen(accessors) or _has_verbatim(accessors):
|
|
491
|
+
return
|
|
492
|
+
inner = accessors.children[1:-1]
|
|
493
|
+
if len(inner) != 1 or inner[0].type != "accessor_declaration":
|
|
494
|
+
return
|
|
495
|
+
accessor = inner[0]
|
|
496
|
+
if [c.text for c in accessor.children[:1]] != ["get"]:
|
|
497
|
+
return
|
|
498
|
+
arrow = accessor.first_of("arrow_expression_clause")
|
|
499
|
+
if arrow is None or len(accessor.children) != 3 or accessor.children[2].text != ";":
|
|
500
|
+
return
|
|
501
|
+
if _has_comments([accessors.children[0], accessors.children[-1], accessor.children[0]]):
|
|
502
|
+
return
|
|
503
|
+
arrow.field = "value"
|
|
504
|
+
_set_children(node, [*node.children[:-1], arrow, accessor.children[2]])
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def _property_arrow_to_getter(node: Node) -> None:
|
|
508
|
+
arrow = node.child("value")
|
|
509
|
+
if arrow is None or arrow.type != "arrow_expression_clause" or len(arrow.children) != 2:
|
|
510
|
+
return
|
|
511
|
+
index = node.children.index(arrow)
|
|
512
|
+
after = node.children[index + 1 :]
|
|
513
|
+
if [c.text for c in after] != [";"]:
|
|
514
|
+
return
|
|
515
|
+
accessor = Node(type="accessor_declaration")
|
|
516
|
+
get = token("get")
|
|
517
|
+
get.field = "name"
|
|
518
|
+
block_arrow = Node(type="arrow_expression_clause", field="body")
|
|
519
|
+
_set_children(block_arrow, arrow.children)
|
|
520
|
+
_set_children(accessor, [get, block_arrow, after[0]])
|
|
521
|
+
_arrow_to_block(accessor, True)
|
|
522
|
+
accessors = Node(type="accessor_list", field="accessors")
|
|
523
|
+
_set_children(accessors, [token("{"), accessor, token("}")])
|
|
524
|
+
_set_children(node, [*node.children[:index], accessors])
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
# Parentheses -------------------------------------------------------------
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
def remove_parentheses(root: Node, mode: str) -> None:
|
|
531
|
+
for node in list(walk(root)):
|
|
532
|
+
if node.type != "parenthesized_expression" or _frozen(node) or node.parent is None:
|
|
533
|
+
continue
|
|
534
|
+
kids = node.children
|
|
535
|
+
if len(kids) != 3 or kids[0].text != "(" or kids[2].text != ")":
|
|
536
|
+
continue
|
|
537
|
+
if kids[0].leading or kids[0].trailing or kids[2].leading or kids[2].trailing:
|
|
538
|
+
continue
|
|
539
|
+
inner = kids[1]
|
|
540
|
+
if mode == "remove_obvious" and not _obviously_redundant(node, inner):
|
|
541
|
+
continue
|
|
542
|
+
if mode == "remove_redundant" and not (_obviously_redundant(node, inner) or _precedence_redundant(node, inner)):
|
|
543
|
+
continue
|
|
544
|
+
if _next_token_text(node) == "<" and inner.type == "identifier":
|
|
545
|
+
continue
|
|
546
|
+
parent = node.parent
|
|
547
|
+
inner.field = node.field
|
|
548
|
+
index = parent.children.index(node)
|
|
549
|
+
parent.children[index] = inner
|
|
550
|
+
inner.parent = parent
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
def _obviously_redundant(node: Node, inner: Node) -> bool:
|
|
554
|
+
parent = node.parent
|
|
555
|
+
assert parent is not None
|
|
556
|
+
if node.field == "condition" and parent.type in ("if_statement", "while_statement", "do_statement"):
|
|
557
|
+
return True
|
|
558
|
+
if parent.type in ("return_statement", "throw_statement", "yield_statement"):
|
|
559
|
+
return parent.children[-1].text == ";" and parent.children[-2] is node
|
|
560
|
+
if inner.type == "identifier":
|
|
561
|
+
return True
|
|
562
|
+
if inner.type in _LITERALS:
|
|
563
|
+
numeric = inner.type in ("integer_literal", "real_literal")
|
|
564
|
+
return not (numeric and parent.type in ("member_access_expression", "element_access_expression", "invocation_expression", "conditional_access_expression", "postfix_unary_expression"))
|
|
565
|
+
if inner.type == "parenthesized_expression":
|
|
566
|
+
return True
|
|
567
|
+
return False
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
_PRIMARY = frozenset(
|
|
571
|
+
{
|
|
572
|
+
"identifier",
|
|
573
|
+
"member_access_expression",
|
|
574
|
+
"invocation_expression",
|
|
575
|
+
"element_access_expression",
|
|
576
|
+
"object_creation_expression",
|
|
577
|
+
"typeof_expression",
|
|
578
|
+
"default_expression",
|
|
579
|
+
"sizeof_expression",
|
|
580
|
+
"checked_expression",
|
|
581
|
+
"generic_name",
|
|
582
|
+
"this_expression",
|
|
583
|
+
"parenthesized_expression",
|
|
584
|
+
"interpolated_string_expression",
|
|
585
|
+
}
|
|
586
|
+
)
|
|
587
|
+
|
|
588
|
+
_PRECEDENCE = {
|
|
589
|
+
"??": 2,
|
|
590
|
+
"||": 3,
|
|
591
|
+
"&&": 4,
|
|
592
|
+
"|": 5,
|
|
593
|
+
"^": 6,
|
|
594
|
+
"&": 7,
|
|
595
|
+
"==": 8,
|
|
596
|
+
"!=": 8,
|
|
597
|
+
"<": 9,
|
|
598
|
+
">": 9,
|
|
599
|
+
"<=": 9,
|
|
600
|
+
">=": 9,
|
|
601
|
+
"<<": 10,
|
|
602
|
+
">>": 10,
|
|
603
|
+
">>>": 10,
|
|
604
|
+
"+": 11,
|
|
605
|
+
"-": 11,
|
|
606
|
+
"*": 12,
|
|
607
|
+
"/": 12,
|
|
608
|
+
"%": 12,
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
# Contexts where any expression may stand without parentheses.
|
|
612
|
+
_OPEN_CONTEXTS = frozenset(
|
|
613
|
+
{
|
|
614
|
+
"argument",
|
|
615
|
+
"arrow_expression_clause",
|
|
616
|
+
"variable_declarator",
|
|
617
|
+
"expression_statement",
|
|
618
|
+
"initializer_expression",
|
|
619
|
+
"return_statement",
|
|
620
|
+
"throw_statement",
|
|
621
|
+
"yield_statement",
|
|
622
|
+
"interpolation",
|
|
623
|
+
"attribute_argument",
|
|
624
|
+
}
|
|
625
|
+
)
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
def _precedence_redundant(node: Node, inner: Node) -> bool:
|
|
629
|
+
parent = node.parent
|
|
630
|
+
assert parent is not None
|
|
631
|
+
# Next to a type test or a pattern, the grammar can read the operators
|
|
632
|
+
# differently without the parentheses: x is T || (y is A or B).
|
|
633
|
+
neighbours = [inner, *(c for c in parent.children if c is not node)] if parent.type == "binary_expression" else [inner]
|
|
634
|
+
if any(_has_type_test(n) for n in neighbours) or _misparsed_access(inner):
|
|
635
|
+
return False
|
|
636
|
+
if parent.type in _OPEN_CONTEXTS:
|
|
637
|
+
return inner.type not in ("assignment_expression",) or parent.type == "expression_statement"
|
|
638
|
+
if parent.type == "assignment_expression" and node.field == "right":
|
|
639
|
+
return True
|
|
640
|
+
if inner.type in _PRIMARY and parent.type not in ("cast_expression",):
|
|
641
|
+
return inner.type not in ("integer_literal", "real_literal")
|
|
642
|
+
if parent.type == "binary_expression" and inner.type == "binary_expression":
|
|
643
|
+
outer_op = parent.child("operator")
|
|
644
|
+
inner_op = inner.child("operator")
|
|
645
|
+
if outer_op is None or inner_op is None:
|
|
646
|
+
return False
|
|
647
|
+
outer = _PRECEDENCE.get(outer_op.text or "")
|
|
648
|
+
own = _PRECEDENCE.get(inner_op.text or "")
|
|
649
|
+
if outer is None or own is None:
|
|
650
|
+
return False
|
|
651
|
+
if own > outer:
|
|
652
|
+
return True
|
|
653
|
+
if own == outer and node.field == "left" and outer_op.text != "??":
|
|
654
|
+
return True
|
|
655
|
+
return False
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
def _misparsed_access(node: Node) -> bool:
|
|
659
|
+
"""A binary expression as the target of an access or call, which only a
|
|
660
|
+
misparse makes: the grammar reads a?[i] ?? b?.M() as (a?[i] ?? b)?.M()."""
|
|
661
|
+
for n in walk(node):
|
|
662
|
+
if n.type in ("conditional_access_expression", "member_access_expression", "invocation_expression"):
|
|
663
|
+
target = n.child("condition") or n.child("expression") or n.child("function")
|
|
664
|
+
if target is not None and target.type == "binary_expression":
|
|
665
|
+
return True
|
|
666
|
+
return False
|
|
667
|
+
|
|
668
|
+
|
|
669
|
+
def _has_type_test(node: Node) -> bool:
|
|
670
|
+
return any(
|
|
671
|
+
n.type in ("is_pattern_expression", "is_expression", "as_expression") or n.type.endswith("_pattern")
|
|
672
|
+
for n in walk(node)
|
|
673
|
+
)
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
def _next_token_text(node: Node) -> str | None:
|
|
677
|
+
last = node.last_token()
|
|
678
|
+
current: Node | None = node
|
|
679
|
+
while current is not None and current.parent is not None:
|
|
680
|
+
siblings = current.parent.children
|
|
681
|
+
index = siblings.index(current)
|
|
682
|
+
if index + 1 < len(siblings):
|
|
683
|
+
following = siblings[index + 1].first_token()
|
|
684
|
+
return following.text if following is not None else None
|
|
685
|
+
current = current.parent
|
|
686
|
+
del last
|
|
687
|
+
return None
|
|
688
|
+
|
|
689
|
+
|
|
690
|
+
# Braces ----------------------------------------------------------------
|
|
691
|
+
|
|
692
|
+
|
|
693
|
+
def add_braces(root: Node) -> None:
|
|
694
|
+
for node in list(walk(root)):
|
|
695
|
+
if _frozen(node):
|
|
696
|
+
continue
|
|
697
|
+
for index, child in enumerate(node.children):
|
|
698
|
+
if not is_embedded(node, child) or child.type == "block":
|
|
699
|
+
continue
|
|
700
|
+
if node.type == "if_statement" and child.field == "alternative" and child.type == "if_statement":
|
|
701
|
+
continue
|
|
702
|
+
if child.type in ("using_statement", "fixed_statement") and child.type == node.type:
|
|
703
|
+
continue
|
|
704
|
+
block = Node(type="block", field=child.field)
|
|
705
|
+
child.field = None
|
|
706
|
+
_set_children(block, [token("{"), child, token("}")])
|
|
707
|
+
node.children[index] = block
|
|
708
|
+
block.parent = node
|
|
709
|
+
|
|
710
|
+
|
|
711
|
+
# Trailing commas -----------------------------------------------------------
|
|
712
|
+
|
|
713
|
+
|
|
714
|
+
def remove_trailing_commas(root: Node) -> None:
|
|
715
|
+
for node in walk(root):
|
|
716
|
+
if node.type not in _LIST_WITH_TRAILING_COMMA or _frozen(node) or len(node.children) < 3:
|
|
717
|
+
continue
|
|
718
|
+
comma = node.children[-2]
|
|
719
|
+
close = node.children[-1]
|
|
720
|
+
if comma.text != "," or comma.named or close.text not in ("}", "]"):
|
|
721
|
+
continue
|
|
722
|
+
before = node.children[-3].last_token()
|
|
723
|
+
if before is None:
|
|
724
|
+
continue
|
|
725
|
+
before.trailing.extend(comma.trailing)
|
|
726
|
+
close.leading[:0] = _as_leading(comma.leading)
|
|
727
|
+
del node.children[-2]
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
def _token_text(node: Node) -> str:
|
|
731
|
+
first = node.first_token()
|
|
732
|
+
return (first.text or "") if first is not None else ""
|