Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/black/comments.py: 14%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

323 statements  

1import re 

2from collections.abc import Collection, Iterator 

3from dataclasses import dataclass 

4from functools import lru_cache 

5from typing import Final, Optional, Union 

6 

7from black.mode import Mode, Preview 

8from black.nodes import ( 

9 CLOSING_BRACKETS, 

10 STANDALONE_COMMENT, 

11 STATEMENT, 

12 WHITESPACE, 

13 container_of, 

14 first_leaf_of, 

15 is_type_comment_string, 

16 make_simple_prefix, 

17 preceding_leaf, 

18 syms, 

19) 

20from blib2to3.pgen2 import token 

21from blib2to3.pytree import Leaf, Node 

22 

23# types 

24LN = Union[Leaf, Node] 

25 

26FMT_OFF: Final = {"# fmt: off", "# fmt:off", "# yapf: disable"} 

27FMT_SKIP: Final = {"# fmt: skip", "# fmt:skip"} 

28FMT_ON: Final = {"# fmt: on", "# fmt:on", "# yapf: enable"} 

29 

30# Compound statements we care about for fmt: skip handling 

31# (excludes except_clause and case_block which aren't standalone compound statements) 

32_COMPOUND_STATEMENTS: Final = STATEMENT - {syms.except_clause, syms.case_block} 

33 

34COMMENT_EXCEPTIONS = " !:#'" 

35_COMMENT_PREFIX = "# " 

36_COMMENT_LIST_SEPARATOR = ";" 

37 

38 

39@dataclass 

40class ProtoComment: 

41 """Describes a piece of syntax that is a comment. 

42 

43 It's not a :class:`blib2to3.pytree.Leaf` so that: 

44 

45 * it can be cached (`Leaf` objects should not be reused more than once as 

46 they store their lineno, column, prefix, and parent information); 

47 * `newlines` and `consumed` fields are kept separate from the `value`. This 

48 simplifies handling of special marker comments like ``# fmt: off/on``. 

49 """ 

50 

51 type: int # token.COMMENT or STANDALONE_COMMENT 

52 value: str # content of the comment 

53 newlines: int # how many newlines before the comment 

54 consumed: int # how many characters of the original leaf's prefix did we consume 

55 form_feed: bool # is there a form feed before the comment 

56 leading_whitespace: str # leading whitespace before the comment, if any 

57 

58 

59def generate_comments(leaf: LN, mode: Mode) -> Iterator[Leaf]: 

60 """Clean the prefix of the `leaf` and generate comments from it, if any. 

61 

62 Comments in lib2to3 are shoved into the whitespace prefix. This happens 

63 in `pgen2/driver.py:Driver.parse_tokens()`. This was a brilliant implementation 

64 move because it does away with modifying the grammar to include all the 

65 possible places in which comments can be placed. 

66 

67 The sad consequence for us though is that comments don't "belong" anywhere. 

68 This is why this function generates simple parentless Leaf objects for 

69 comments. We simply don't know what the correct parent should be. 

70 

71 No matter though, we can live without this. We really only need to 

72 differentiate between inline and standalone comments. The latter don't 

73 share the line with any code. 

74 

75 Inline comments are emitted as regular token.COMMENT leaves. Standalone 

76 are emitted with a fake STANDALONE_COMMENT token identifier. 

77 """ 

78 total_consumed = 0 

79 for pc in list_comments( 

80 leaf.prefix, is_endmarker=leaf.type == token.ENDMARKER, mode=mode 

81 ): 

82 total_consumed = pc.consumed 

83 prefix = make_simple_prefix(pc.newlines, pc.form_feed) 

84 yield Leaf(pc.type, pc.value, prefix=prefix) 

85 normalize_trailing_prefix(leaf, total_consumed) 

86 

87 

88@lru_cache(maxsize=4096) 

89def list_comments(prefix: str, *, is_endmarker: bool, mode: Mode) -> list[ProtoComment]: 

90 """Return a list of :class:`ProtoComment` objects parsed from the given `prefix`.""" 

91 result: list[ProtoComment] = [] 

92 if not prefix or "#" not in prefix: 

93 return result 

94 

95 consumed = 0 

96 nlines = 0 

97 ignored_lines = 0 

98 form_feed = False 

99 for index, full_line in enumerate(re.split("\r?\n|\r", prefix)): 

100 consumed += len(full_line) + 1 # adding the length of the split '\n' 

101 match = re.match(r"^(\s*)(\S.*|)$", full_line) 

102 assert match 

103 whitespace, line = match.groups() 

104 if not line: 

105 nlines += 1 

106 if "\f" in full_line: 

107 form_feed = True 

108 if not line.startswith("#"): 

109 # Escaped newlines outside of a comment are not really newlines at 

110 # all. We treat a single-line comment following an escaped newline 

111 # as a simple trailing comment. 

112 if line.endswith("\\"): 

113 ignored_lines += 1 

114 continue 

115 

116 if index == ignored_lines and not is_endmarker: 

117 comment_type = token.COMMENT # simple trailing comment 

118 else: 

119 comment_type = STANDALONE_COMMENT 

120 comment = make_comment(line, mode=mode) 

121 result.append( 

122 ProtoComment( 

123 type=comment_type, 

124 value=comment, 

125 newlines=nlines, 

126 consumed=consumed, 

127 form_feed=form_feed, 

128 leading_whitespace=whitespace, 

129 ) 

130 ) 

131 form_feed = False 

132 nlines = 0 

133 return result 

134 

135 

136def normalize_trailing_prefix(leaf: LN, total_consumed: int) -> None: 

137 """Normalize the prefix that's left over after generating comments. 

138 

139 Note: don't use backslashes for formatting or you'll lose your voting rights. 

140 """ 

141 remainder = leaf.prefix[total_consumed:] 

142 if "\\" not in remainder: 

143 nl_count = remainder.count("\n") 

144 form_feed = "\f" in remainder and remainder.endswith("\n") 

145 leaf.prefix = make_simple_prefix(nl_count, form_feed) 

146 return 

147 

148 leaf.prefix = "" 

149 

150 

151def make_comment(content: str, mode: Mode) -> str: 

152 """Return a consistently formatted comment from the given `content` string. 

153 

154 All comments (except for "##", "#!", "#:", '#'") should have a single 

155 space between the hash sign and the content. 

156 

157 If `content` didn't start with a hash sign, one is provided. 

158 

159 Comments containing fmt directives are preserved exactly as-is to respect 

160 user intent (e.g., `#no space # fmt: skip` stays as-is). 

161 """ 

162 content = content.rstrip() 

163 if not content: 

164 return "#" 

165 

166 # Preserve comments with fmt directives exactly as-is 

167 if content.startswith("#") and _contains_fmt_directive(content): 

168 return content 

169 

170 if content[0] == "#": 

171 content = content[1:] 

172 if ( 

173 content 

174 and content[0] == "\N{NO-BREAK SPACE}" 

175 and not is_type_comment_string("# " + content.lstrip(), mode=mode) 

176 ): 

177 content = " " + content[1:] # Replace NBSP by a simple space 

178 if ( 

179 Preview.standardize_type_comments in mode 

180 and content 

181 and "\N{NO-BREAK SPACE}" not in content 

182 and is_type_comment_string("#" + content, mode=mode) 

183 ): 

184 type_part, value_part = content.split(":", 1) 

185 content = type_part.strip() + ": " + value_part.strip() 

186 

187 if content and content[0] not in COMMENT_EXCEPTIONS: 

188 content = " " + content 

189 return "#" + content 

190 

191 

192def normalize_fmt_off( 

193 node: Node, mode: Mode, lines: Collection[tuple[int, int]] 

194) -> None: 

195 """Convert content between `# fmt: off`/`# fmt: on` into standalone comments.""" 

196 try_again = True 

197 while try_again: 

198 try_again = convert_one_fmt_off_pair(node, mode, lines) 

199 

200 

201def _should_process_fmt_comment( 

202 comment: ProtoComment, leaf: Leaf 

203) -> tuple[bool, bool, bool]: 

204 """Check if comment should be processed for fmt handling. 

205 

206 Returns (should_process, is_fmt_off, is_fmt_skip). 

207 """ 

208 is_fmt_off = _contains_fmt_directive(comment.value, FMT_OFF) 

209 is_fmt_skip = _contains_fmt_directive(comment.value, FMT_SKIP) 

210 

211 if not is_fmt_off and not is_fmt_skip: 

212 return False, False, False 

213 

214 # Invalid use when `# fmt: off` is applied before a closing bracket 

215 if is_fmt_off and leaf.type in CLOSING_BRACKETS: 

216 return False, False, False 

217 

218 return True, is_fmt_off, is_fmt_skip 

219 

220 

221def _is_valid_standalone_fmt_comment( 

222 comment: ProtoComment, leaf: Leaf, is_fmt_off: bool, is_fmt_skip: bool 

223) -> bool: 

224 """Check if comment is a valid standalone fmt directive. 

225 

226 We only want standalone comments. If there's no previous leaf or if 

227 the previous leaf is indentation, it's a standalone comment in disguise. 

228 """ 

229 if comment.type == STANDALONE_COMMENT: 

230 return True 

231 

232 prev = preceding_leaf(leaf) 

233 if not prev: 

234 return True 

235 

236 # Treat STANDALONE_COMMENT nodes as whitespace for check 

237 if is_fmt_off and prev.type not in WHITESPACE and prev.type != STANDALONE_COMMENT: 

238 return False 

239 if is_fmt_skip and prev.type in WHITESPACE: 

240 return False 

241 

242 return True 

243 

244 

245def _handle_comment_only_fmt_block( 

246 leaf: Leaf, 

247 comment: ProtoComment, 

248 previous_consumed: int, 

249 mode: Mode, 

250) -> bool: 

251 """Handle fmt:off/on blocks that contain only comments. 

252 

253 Returns True if a block was converted, False otherwise. 

254 """ 

255 all_comments = list_comments(leaf.prefix, is_endmarker=False, mode=mode) 

256 

257 # Find the first fmt:off and its matching fmt:on 

258 fmt_off_idx = None 

259 fmt_on_idx = None 

260 for idx, c in enumerate(all_comments): 

261 if fmt_off_idx is None and c.value in FMT_OFF: 

262 fmt_off_idx = idx 

263 if fmt_off_idx is not None and idx > fmt_off_idx and c.value in FMT_ON: 

264 fmt_on_idx = idx 

265 break 

266 

267 # Only proceed if we found both directives 

268 if fmt_on_idx is None or fmt_off_idx is None: 

269 return False 

270 

271 comment = all_comments[fmt_off_idx] 

272 fmt_on_comment = all_comments[fmt_on_idx] 

273 original_prefix = leaf.prefix 

274 

275 # Build the hidden value 

276 start_pos = comment.consumed 

277 end_pos = fmt_on_comment.consumed 

278 content_between_and_fmt_on = original_prefix[start_pos:end_pos] 

279 hidden_value = comment.value + "\n" + content_between_and_fmt_on 

280 

281 if hidden_value.endswith("\n"): 

282 hidden_value = hidden_value[:-1] 

283 

284 # Build the standalone comment prefix 

285 standalone_comment_prefix = ( 

286 original_prefix[:previous_consumed] + "\n" * comment.newlines 

287 ) 

288 

289 fmt_off_prefix = original_prefix.split(comment.value)[0] 

290 if "\n" in fmt_off_prefix: 

291 fmt_off_prefix = fmt_off_prefix.split("\n")[-1] 

292 standalone_comment_prefix += fmt_off_prefix 

293 

294 # Update leaf prefix 

295 leaf.prefix = original_prefix[fmt_on_comment.consumed :] 

296 

297 # Insert the STANDALONE_COMMENT 

298 parent = leaf.parent 

299 assert parent is not None, "INTERNAL ERROR: fmt: on/off handling (prefix only)" 

300 

301 leaf_idx = None 

302 for idx, child in enumerate(parent.children): 

303 if child is leaf: 

304 leaf_idx = idx 

305 break 

306 

307 assert leaf_idx is not None, "INTERNAL ERROR: fmt: on/off handling (leaf index)" 

308 

309 parent.insert_child( 

310 leaf_idx, 

311 Leaf( 

312 STANDALONE_COMMENT, 

313 hidden_value, 

314 prefix=standalone_comment_prefix, 

315 fmt_pass_converted_first_leaf=None, 

316 ), 

317 ) 

318 return True 

319 

320 

321def convert_one_fmt_off_pair( 

322 node: Node, mode: Mode, lines: Collection[tuple[int, int]] 

323) -> bool: 

324 """Convert content of a single `# fmt: off`/`# fmt: on` into a standalone comment. 

325 

326 Returns True if a pair was converted. 

327 """ 

328 for leaf in node.leaves(): 

329 previous_consumed = 0 

330 for comment in list_comments(leaf.prefix, is_endmarker=False, mode=mode): 

331 should_process, is_fmt_off, is_fmt_skip = _should_process_fmt_comment( 

332 comment, leaf 

333 ) 

334 if not should_process: 

335 previous_consumed = comment.consumed 

336 continue 

337 

338 if not _is_valid_standalone_fmt_comment( 

339 comment, leaf, is_fmt_off, is_fmt_skip 

340 ): 

341 previous_consumed = comment.consumed 

342 continue 

343 

344 ignored_nodes = list(generate_ignored_nodes(leaf, comment, mode)) 

345 

346 # Handle comment-only blocks 

347 if not ignored_nodes and is_fmt_off: 

348 if _handle_comment_only_fmt_block( 

349 leaf, comment, previous_consumed, mode 

350 ): 

351 return True 

352 continue 

353 

354 # Need actual nodes to process 

355 if not ignored_nodes: 

356 continue 

357 

358 # Handle regular fmt blocks 

359 

360 _handle_regular_fmt_block( 

361 ignored_nodes, 

362 comment, 

363 previous_consumed, 

364 is_fmt_skip, 

365 lines, 

366 leaf, 

367 ) 

368 return True 

369 

370 return False 

371 

372 

373def _handle_regular_fmt_block( 

374 ignored_nodes: list[LN], 

375 comment: ProtoComment, 

376 previous_consumed: int, 

377 is_fmt_skip: bool, 

378 lines: Collection[tuple[int, int]], 

379 leaf: Leaf, 

380) -> None: 

381 """Handle fmt blocks with actual AST nodes.""" 

382 first = ignored_nodes[0] # Can be a container node with the `leaf`. 

383 parent = first.parent 

384 prefix = first.prefix 

385 

386 if comment.value in FMT_OFF: 

387 first.prefix = prefix[comment.consumed :] 

388 if is_fmt_skip: 

389 first.prefix = "" 

390 standalone_comment_prefix = prefix 

391 else: 

392 standalone_comment_prefix = prefix[:previous_consumed] + "\n" * comment.newlines 

393 

394 hidden_value = "".join(str(n) for n in ignored_nodes) 

395 comment_lineno = leaf.lineno - comment.newlines 

396 

397 if comment.value in FMT_OFF: 

398 fmt_off_prefix = "" 

399 if len(lines) > 0 and not any( 

400 line[0] <= comment_lineno <= line[1] for line in lines 

401 ): 

402 # keeping indentation of comment by preserving original whitespaces. 

403 fmt_off_prefix = prefix.split(comment.value)[0] 

404 if "\n" in fmt_off_prefix: 

405 fmt_off_prefix = fmt_off_prefix.split("\n")[-1] 

406 standalone_comment_prefix += fmt_off_prefix 

407 hidden_value = comment.value + "\n" + hidden_value 

408 

409 if is_fmt_skip: 

410 hidden_value += comment.leading_whitespace + comment.value 

411 

412 if hidden_value.endswith("\n"): 

413 # That happens when one of the `ignored_nodes` ended with a NEWLINE 

414 # leaf (possibly followed by a DEDENT). 

415 hidden_value = hidden_value[:-1] 

416 

417 first_idx: Optional[int] = None 

418 for ignored in ignored_nodes: 

419 index = ignored.remove() 

420 if first_idx is None: 

421 first_idx = index 

422 

423 assert parent is not None, "INTERNAL ERROR: fmt: on/off handling (1)" 

424 assert first_idx is not None, "INTERNAL ERROR: fmt: on/off handling (2)" 

425 

426 parent.insert_child( 

427 first_idx, 

428 Leaf( 

429 STANDALONE_COMMENT, 

430 hidden_value, 

431 prefix=standalone_comment_prefix, 

432 fmt_pass_converted_first_leaf=first_leaf_of(first), 

433 ), 

434 ) 

435 

436 

437def generate_ignored_nodes( 

438 leaf: Leaf, comment: ProtoComment, mode: Mode 

439) -> Iterator[LN]: 

440 """Starting from the container of `leaf`, generate all leaves until `# fmt: on`. 

441 

442 If comment is skip, returns leaf only. 

443 Stops at the end of the block. 

444 """ 

445 if _contains_fmt_directive(comment.value, FMT_SKIP): 

446 yield from _generate_ignored_nodes_from_fmt_skip(leaf, comment, mode) 

447 return 

448 container: Optional[LN] = container_of(leaf) 

449 while container is not None and container.type != token.ENDMARKER: 

450 if is_fmt_on(container, mode=mode): 

451 return 

452 

453 # fix for fmt: on in children 

454 if children_contains_fmt_on(container, mode=mode): 

455 for index, child in enumerate(container.children): 

456 if isinstance(child, Leaf) and is_fmt_on(child, mode=mode): 

457 if child.type in CLOSING_BRACKETS: 

458 # This means `# fmt: on` is placed at a different bracket level 

459 # than `# fmt: off`. This is an invalid use, but as a courtesy, 

460 # we include this closing bracket in the ignored nodes. 

461 # The alternative is to fail the formatting. 

462 yield child 

463 return 

464 if ( 

465 child.type == token.INDENT 

466 and index < len(container.children) - 1 

467 and children_contains_fmt_on( 

468 container.children[index + 1], mode=mode 

469 ) 

470 ): 

471 # This means `# fmt: on` is placed right after an indentation 

472 # level, and we shouldn't swallow the previous INDENT token. 

473 return 

474 if children_contains_fmt_on(child, mode=mode): 

475 return 

476 yield child 

477 else: 

478 if container.type == token.DEDENT and container.next_sibling is None: 

479 # This can happen when there is no matching `# fmt: on` comment at the 

480 # same level as `# fmt: on`. We need to keep this DEDENT. 

481 return 

482 yield container 

483 container = container.next_sibling 

484 

485 

486def _find_compound_statement_context(parent: Node) -> Optional[Node]: 

487 """Return the body node of a compound statement if we should respect fmt: skip. 

488 

489 This handles one-line compound statements like: 

490 if condition: body # fmt: skip 

491 

492 When Black expands such statements, they temporarily look like: 

493 if condition: 

494 body # fmt: skip 

495 

496 In both cases, we want to return the body node (either the simple_stmt directly 

497 or the suite containing it). 

498 """ 

499 if parent.type != syms.simple_stmt: 

500 return None 

501 

502 if not isinstance(parent.parent, Node): 

503 return None 

504 

505 # Case 1: Expanded form after Black's initial formatting pass. 

506 # The one-liner has been split across multiple lines: 

507 # if True: 

508 # print("a"); print("b") # fmt: skip 

509 # Structure: compound_stmt -> suite -> simple_stmt 

510 if ( 

511 parent.parent.type == syms.suite 

512 and isinstance(parent.parent.parent, Node) 

513 and parent.parent.parent.type in _COMPOUND_STATEMENTS 

514 ): 

515 return parent.parent 

516 

517 # Case 2: Original one-line form from the input source. 

518 # The statement is still on a single line: 

519 # if True: print("a"); print("b") # fmt: skip 

520 # Structure: compound_stmt -> simple_stmt 

521 if parent.parent.type in _COMPOUND_STATEMENTS: 

522 return parent 

523 

524 return None 

525 

526 

527def _should_keep_compound_statement_inline( 

528 body_node: Node, simple_stmt_parent: Node 

529) -> bool: 

530 """Check if a compound statement should be kept on one line. 

531 

532 Returns True only for compound statements with semicolon-separated bodies, 

533 like: if True: print("a"); print("b") # fmt: skip 

534 """ 

535 # Check if there are semicolons in the body 

536 for leaf in body_node.leaves(): 

537 if leaf.type == token.SEMI: 

538 # Verify it's a single-line body (one simple_stmt) 

539 if body_node.type == syms.suite: 

540 # After formatting: check suite has one simple_stmt child 

541 simple_stmts = [ 

542 child 

543 for child in body_node.children 

544 if child.type == syms.simple_stmt 

545 ] 

546 return len(simple_stmts) == 1 and simple_stmts[0] is simple_stmt_parent 

547 else: 

548 # Original form: body_node IS the simple_stmt 

549 return body_node is simple_stmt_parent 

550 return False 

551 

552 

553def _get_compound_statement_header( 

554 body_node: Node, simple_stmt_parent: Node 

555) -> list[LN]: 

556 """Get header nodes for a compound statement that should be preserved inline.""" 

557 if not _should_keep_compound_statement_inline(body_node, simple_stmt_parent): 

558 return [] 

559 

560 # Get the compound statement (parent of body) 

561 compound_stmt = body_node.parent 

562 if compound_stmt is None or compound_stmt.type not in _COMPOUND_STATEMENTS: 

563 return [] 

564 

565 # Collect all header leaves before the body 

566 header_leaves: list[LN] = [] 

567 for child in compound_stmt.children: 

568 if child is body_node: 

569 break 

570 if isinstance(child, Leaf): 

571 if child.type not in (token.NEWLINE, token.INDENT): 

572 header_leaves.append(child) 

573 else: 

574 header_leaves.extend(child.leaves()) 

575 return header_leaves 

576 

577 

578def _generate_ignored_nodes_from_fmt_skip( 

579 leaf: Leaf, comment: ProtoComment, mode: Mode 

580) -> Iterator[LN]: 

581 """Generate all leaves that should be ignored by the `# fmt: skip` from `leaf`.""" 

582 prev_sibling = leaf.prev_sibling 

583 parent = leaf.parent 

584 ignored_nodes: list[LN] = [] 

585 # Need to properly format the leaf prefix to compare it to comment.value, 

586 # which is also formatted 

587 comments = list_comments(leaf.prefix, is_endmarker=False, mode=mode) 

588 if not comments or comment.value != comments[0].value: 

589 return 

590 if prev_sibling is not None: 

591 leaf.prefix = leaf.prefix[comment.consumed :] 

592 

593 if Preview.fix_fmt_skip_in_one_liners not in mode: 

594 siblings = [prev_sibling] 

595 while ( 

596 "\n" not in prev_sibling.prefix 

597 and prev_sibling.prev_sibling is not None 

598 ): 

599 prev_sibling = prev_sibling.prev_sibling 

600 siblings.insert(0, prev_sibling) 

601 yield from siblings 

602 return 

603 

604 # Generates the nodes to be ignored by `fmt: skip`. 

605 

606 # Nodes to ignore are the ones on the same line as the 

607 # `# fmt: skip` comment, excluding the `# fmt: skip` 

608 # node itself. 

609 

610 # Traversal process (starting at the `# fmt: skip` node): 

611 # 1. Move to the `prev_sibling` of the current node. 

612 # 2. If `prev_sibling` has children, go to its rightmost leaf. 

613 # 3. If there's no `prev_sibling`, move up to the parent 

614 # node and repeat. 

615 # 4. Continue until: 

616 # a. You encounter an `INDENT` or `NEWLINE` node (indicates 

617 # start of the line). 

618 # b. You reach the root node. 

619 

620 # Include all visited LEAVES in the ignored list, except INDENT 

621 # or NEWLINE leaves. 

622 

623 current_node = prev_sibling 

624 ignored_nodes = [current_node] 

625 if current_node.prev_sibling is None and current_node.parent is not None: 

626 current_node = current_node.parent 

627 while "\n" not in current_node.prefix and current_node.prev_sibling is not None: 

628 leaf_nodes = list(current_node.prev_sibling.leaves()) 

629 current_node = leaf_nodes[-1] if leaf_nodes else current_node 

630 

631 if current_node.type in (token.NEWLINE, token.INDENT): 

632 current_node.prefix = "" 

633 break 

634 

635 ignored_nodes.insert(0, current_node) 

636 

637 if current_node.prev_sibling is None and current_node.parent is not None: 

638 current_node = current_node.parent 

639 # Special handling for compound statements with semicolon-separated bodies 

640 if Preview.fix_fmt_skip_in_one_liners in mode and isinstance(parent, Node): 

641 body_node = _find_compound_statement_context(parent) 

642 if body_node is not None: 

643 header_nodes = _get_compound_statement_header(body_node, parent) 

644 if header_nodes: 

645 ignored_nodes = header_nodes + ignored_nodes 

646 

647 yield from ignored_nodes 

648 elif ( 

649 parent is not None and parent.type == syms.suite and leaf.type == token.NEWLINE 

650 ): 

651 # The `# fmt: skip` is on the colon line of the if/while/def/class/... 

652 # statements. The ignored nodes should be previous siblings of the 

653 # parent suite node. 

654 leaf.prefix = "" 

655 parent_sibling = parent.prev_sibling 

656 while parent_sibling is not None and parent_sibling.type != syms.suite: 

657 ignored_nodes.insert(0, parent_sibling) 

658 parent_sibling = parent_sibling.prev_sibling 

659 # Special case for `async_stmt` where the ASYNC token is on the 

660 # grandparent node. 

661 grandparent = parent.parent 

662 if ( 

663 grandparent is not None 

664 and grandparent.prev_sibling is not None 

665 and grandparent.prev_sibling.type == token.ASYNC 

666 ): 

667 ignored_nodes.insert(0, grandparent.prev_sibling) 

668 yield from iter(ignored_nodes) 

669 

670 

671def is_fmt_on(container: LN, mode: Mode) -> bool: 

672 """Determine whether formatting is switched on within a container. 

673 Determined by whether the last `# fmt:` comment is `on` or `off`. 

674 """ 

675 fmt_on = False 

676 for comment in list_comments(container.prefix, is_endmarker=False, mode=mode): 

677 if comment.value in FMT_ON: 

678 fmt_on = True 

679 elif comment.value in FMT_OFF: 

680 fmt_on = False 

681 return fmt_on 

682 

683 

684def children_contains_fmt_on(container: LN, mode: Mode) -> bool: 

685 """Determine if children have formatting switched on.""" 

686 for child in container.children: 

687 leaf = first_leaf_of(child) 

688 if leaf is not None and is_fmt_on(leaf, mode=mode): 

689 return True 

690 

691 return False 

692 

693 

694def contains_pragma_comment(comment_list: list[Leaf]) -> bool: 

695 """ 

696 Returns: 

697 True iff one of the comments in @comment_list is a pragma used by one 

698 of the more common static analysis tools for python (e.g. mypy, flake8, 

699 pylint). 

700 """ 

701 for comment in comment_list: 

702 if comment.value.startswith(("# type:", "# noqa", "# pylint:")): 

703 return True 

704 

705 return False 

706 

707 

708def _contains_fmt_directive( 

709 comment_line: str, directives: set[str] = FMT_OFF | FMT_ON | FMT_SKIP 

710) -> bool: 

711 """ 

712 Checks if the given comment contains format directives, alone or paired with 

713 other comments. 

714 

715 Defaults to checking all directives (skip, off, on, yapf), but can be 

716 narrowed to specific ones. 

717 

718 Matching styles: 

719 # foobar <-- single comment 

720 # foobar # foobar # foobar <-- multiple comments 

721 # foobar; foobar <-- list of comments (; separated) 

722 """ 

723 semantic_comment_blocks = [ 

724 comment_line, 

725 *[ 

726 _COMMENT_PREFIX + comment.strip() 

727 for comment in comment_line.split(_COMMENT_PREFIX)[1:] 

728 ], 

729 *[ 

730 _COMMENT_PREFIX + comment.strip() 

731 for comment in comment_line.strip(_COMMENT_PREFIX).split( 

732 _COMMENT_LIST_SEPARATOR 

733 ) 

734 ], 

735 ] 

736 

737 return any(comment in directives for comment in semantic_comment_blocks)