|
7 | 7 |
|
8 | 8 | import ast |
9 | 9 | from fnmatch import fnmatch |
10 | | -from typing import TYPE_CHECKING, NamedTuple, TypeVar |
| 10 | +from typing import TYPE_CHECKING, NamedTuple, TypeVar, cast |
11 | 11 |
|
12 | 12 | import libcst as cst |
13 | 13 | import libcst.matchers as m |
@@ -341,3 +341,62 @@ def func_has_decorator(func: cst.FunctionDef, *names: str) -> bool: |
341 | 341 | ), |
342 | 342 | ) |
343 | 343 | ) |
| 344 | + |
| 345 | + |
| 346 | +def get_comments(node: cst.CSTNode | Iterable[cst.CSTNode]) -> Iterator[cst.EmptyLine]: |
| 347 | + # pyright can't use hasattr to narrow the type, so need a bunch of casts |
| 348 | + if hasattr(node, "__iter__"): |
| 349 | + for n in cast("Iterable[cst.CSTNode]", node): |
| 350 | + yield from get_comments(n) |
| 351 | + return |
| 352 | + yield from ( |
| 353 | + cst.EmptyLine(comment=ensure_type(c, cst.Comment)) |
| 354 | + for c in m.findall(cast("cst.CSTNode", node), m.Comment()) |
| 355 | + ) |
| 356 | + return |
| 357 | + |
| 358 | + |
| 359 | +# used in TRIO100 |
| 360 | +def flatten_preserving_comments(node: cst.BaseCompoundStatement): |
| 361 | + # add leading lines (comments and empty lines) for the node to be removed |
| 362 | + new_leading_lines = list(node.leading_lines) |
| 363 | + |
| 364 | + # add other comments belonging to the node as empty lines with comments |
| 365 | + for attr in "lpar", "items", "rpar": |
| 366 | + # pragma, since this is currently only used to flatten `With` statements |
| 367 | + if comment_nodes := getattr(node, attr, None): # pragma: no cover |
| 368 | + new_leading_lines.extend(get_comments(comment_nodes)) |
| 369 | + |
| 370 | + # node.body is a BaseSuite, whose subclasses are SimpleStatementSuite |
| 371 | + # and IndentedBlock |
| 372 | + if isinstance(node.body, cst.SimpleStatementSuite): |
| 373 | + # `with ...: pass;pass;pass` -> pass;pass;pass |
| 374 | + return cst.SimpleStatementLine(node.body.body, leading_lines=new_leading_lines) |
| 375 | + |
| 376 | + assert isinstance(node.body, cst.IndentedBlock) |
| 377 | + nodes = list(node.body.body) |
| 378 | + |
| 379 | + # nodes[0] is a BaseStatement, whose subclasses are SimpleStatementLine |
| 380 | + # and BaseCompoundStatement - both of which has leading_lines |
| 381 | + assert isinstance(nodes[0], (cst.SimpleStatementLine, cst.BaseCompoundStatement)) |
| 382 | + |
| 383 | + # add body header comment - i.e. comments on the same/last line of the statement |
| 384 | + if node.body.header and node.body.header.comment: |
| 385 | + new_leading_lines.append( |
| 386 | + cst.EmptyLine(indent=True, comment=node.body.header.comment) |
| 387 | + ) |
| 388 | + # add the leading lines of the first node |
| 389 | + new_leading_lines.extend(nodes[0].leading_lines) |
| 390 | + # update the first node with all the above constructed lines |
| 391 | + nodes[0] = nodes[0].with_changes(leading_lines=new_leading_lines) |
| 392 | + |
| 393 | + # if there's comments in the footer of the indented block, add a pass |
| 394 | + # statement with the comments as leading lines |
| 395 | + if node.body.footer: |
| 396 | + nodes.append( |
| 397 | + cst.SimpleStatementLine( |
| 398 | + [cst.Pass()], |
| 399 | + node.body.footer, |
| 400 | + ) |
| 401 | + ) |
| 402 | + return cst.FlattenSentinel(nodes) |
0 commit comments