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
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
1import re
2from collections.abc import Collection, Iterator
3from dataclasses import dataclass
4from functools import lru_cache
5from typing import Final, Union
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
23# types
24LN = Union[Leaf, Node]
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"}
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}
34COMMENT_EXCEPTIONS = " !:#'"
35_COMMENT_PREFIX = "# "
36_COMMENT_LIST_SEPARATOR = ";"
39@dataclass
40class ProtoComment:
41 """Describes a piece of syntax that is a comment.
43 It's not a :class:`blib2to3.pytree.Leaf` so that:
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 """
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
59def generate_comments(leaf: LN, mode: Mode) -> Iterator[Leaf]:
60 """Clean the prefix of the `leaf` and generate comments from it, if any.
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.
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.
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.
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)
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
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
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
136def normalize_trailing_prefix(leaf: LN, total_consumed: int) -> None:
137 """Normalize the prefix that's left over after generating comments.
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
148 leaf.prefix = ""
151def make_comment(content: str, mode: Mode) -> str:
152 """Return a consistently formatted comment from the given `content` string.
154 All comments (except for "##", "#!", "#:", '#'") should have a single
155 space between the hash sign and the content.
157 If `content` didn't start with a hash sign, one is provided.
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 "#"
166 # Preserve comments with fmt directives exactly as-is
167 if content.startswith("#") and _contains_fmt_directive(content):
168 return content
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()
187 if content and content[0] not in COMMENT_EXCEPTIONS:
188 content = " " + content
189 return "#" + content
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)
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.
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)
211 if not is_fmt_off and not is_fmt_skip:
212 return False, False, False
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
218 return True, is_fmt_off, is_fmt_skip
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.
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
232 prev = preceding_leaf(leaf)
233 if not prev:
234 return True
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
242 return True
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.
253 Returns True if a block was converted, False otherwise.
254 """
255 all_comments = list_comments(leaf.prefix, is_endmarker=False, mode=mode)
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
267 # Only proceed if we found both directives
268 if fmt_on_idx is None or fmt_off_idx is None:
269 return False
271 comment = all_comments[fmt_off_idx]
272 fmt_on_comment = all_comments[fmt_on_idx]
273 original_prefix = leaf.prefix
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
281 if hidden_value.endswith("\n"):
282 hidden_value = hidden_value[:-1]
284 # Build the standalone comment prefix - preserve all content before fmt:off
285 # including any comments that precede it
286 if fmt_off_idx == 0:
287 # No comments before fmt:off, use previous_consumed
288 pre_fmt_off_consumed = previous_consumed
289 else:
290 # Use the consumed position of the last comment before fmt:off
291 # This preserves all comments and content before the fmt:off directive
292 pre_fmt_off_consumed = all_comments[fmt_off_idx - 1].consumed
294 standalone_comment_prefix = (
295 original_prefix[:pre_fmt_off_consumed] + "\n" * comment.newlines
296 )
298 fmt_off_prefix = original_prefix.split(comment.value)[0]
299 if "\n" in fmt_off_prefix:
300 fmt_off_prefix = fmt_off_prefix.split("\n")[-1]
301 standalone_comment_prefix += fmt_off_prefix
303 # Update leaf prefix
304 leaf.prefix = original_prefix[fmt_on_comment.consumed :]
306 # Insert the STANDALONE_COMMENT
307 parent = leaf.parent
308 assert parent is not None, "INTERNAL ERROR: fmt: on/off handling (prefix only)"
310 leaf_idx = None
311 for idx, child in enumerate(parent.children):
312 if child is leaf:
313 leaf_idx = idx
314 break
316 assert leaf_idx is not None, "INTERNAL ERROR: fmt: on/off handling (leaf index)"
318 parent.insert_child(
319 leaf_idx,
320 Leaf(
321 STANDALONE_COMMENT,
322 hidden_value,
323 prefix=standalone_comment_prefix,
324 fmt_pass_converted_first_leaf=None,
325 ),
326 )
327 return True
330def convert_one_fmt_off_pair(
331 node: Node, mode: Mode, lines: Collection[tuple[int, int]]
332) -> bool:
333 """Convert content of a single `# fmt: off`/`# fmt: on` into a standalone comment.
335 Returns True if a pair was converted.
336 """
337 for leaf in node.leaves():
338 # Skip STANDALONE_COMMENT nodes that were created by fmt:off/on processing
339 # to avoid reprocessing them in subsequent iterations
340 if (
341 leaf.type == STANDALONE_COMMENT
342 and hasattr(leaf, "fmt_pass_converted_first_leaf")
343 and leaf.fmt_pass_converted_first_leaf is None
344 ):
345 continue
347 previous_consumed = 0
348 for comment in list_comments(leaf.prefix, is_endmarker=False, mode=mode):
349 should_process, is_fmt_off, is_fmt_skip = _should_process_fmt_comment(
350 comment, leaf
351 )
352 if not should_process:
353 previous_consumed = comment.consumed
354 continue
356 if not _is_valid_standalone_fmt_comment(
357 comment, leaf, is_fmt_off, is_fmt_skip
358 ):
359 previous_consumed = comment.consumed
360 continue
362 ignored_nodes = list(generate_ignored_nodes(leaf, comment, mode))
364 # Handle comment-only blocks
365 if not ignored_nodes and is_fmt_off:
366 if _handle_comment_only_fmt_block(
367 leaf, comment, previous_consumed, mode
368 ):
369 return True
370 continue
372 # Need actual nodes to process
373 if not ignored_nodes:
374 continue
376 # Handle regular fmt blocks
378 _handle_regular_fmt_block(
379 ignored_nodes,
380 comment,
381 previous_consumed,
382 is_fmt_skip,
383 lines,
384 leaf,
385 )
386 return True
388 return False
391def _handle_regular_fmt_block(
392 ignored_nodes: list[LN],
393 comment: ProtoComment,
394 previous_consumed: int,
395 is_fmt_skip: bool,
396 lines: Collection[tuple[int, int]],
397 leaf: Leaf,
398) -> None:
399 """Handle fmt blocks with actual AST nodes."""
400 first = ignored_nodes[0] # Can be a container node with the `leaf`.
401 parent = first.parent
402 prefix = first.prefix
404 if comment.value in FMT_OFF:
405 first.prefix = prefix[comment.consumed :]
406 if is_fmt_skip:
407 first.prefix = ""
408 standalone_comment_prefix = prefix
409 else:
410 standalone_comment_prefix = prefix[:previous_consumed] + "\n" * comment.newlines
412 hidden_value = "".join(str(n) for n in ignored_nodes)
413 comment_lineno = leaf.lineno - comment.newlines
415 if comment.value in FMT_OFF:
416 fmt_off_prefix = ""
417 if len(lines) > 0 and not any(
418 line[0] <= comment_lineno <= line[1] for line in lines
419 ):
420 # keeping indentation of comment by preserving original whitespaces.
421 fmt_off_prefix = prefix.split(comment.value)[0]
422 if "\n" in fmt_off_prefix:
423 fmt_off_prefix = fmt_off_prefix.split("\n")[-1]
424 standalone_comment_prefix += fmt_off_prefix
425 hidden_value = comment.value + "\n" + hidden_value
427 if is_fmt_skip:
428 hidden_value += comment.leading_whitespace + comment.value
430 if hidden_value.endswith("\n"):
431 # That happens when one of the `ignored_nodes` ended with a NEWLINE
432 # leaf (possibly followed by a DEDENT).
433 hidden_value = hidden_value[:-1]
435 first_idx: int | None = None
436 for ignored in ignored_nodes:
437 index = ignored.remove()
438 if first_idx is None:
439 first_idx = index
441 assert parent is not None, "INTERNAL ERROR: fmt: on/off handling (1)"
442 assert first_idx is not None, "INTERNAL ERROR: fmt: on/off handling (2)"
444 parent.insert_child(
445 first_idx,
446 Leaf(
447 STANDALONE_COMMENT,
448 hidden_value,
449 prefix=standalone_comment_prefix,
450 fmt_pass_converted_first_leaf=first_leaf_of(first),
451 ),
452 )
455def generate_ignored_nodes(
456 leaf: Leaf, comment: ProtoComment, mode: Mode
457) -> Iterator[LN]:
458 """Starting from the container of `leaf`, generate all leaves until `# fmt: on`.
460 If comment is skip, returns leaf only.
461 Stops at the end of the block.
462 """
463 if _contains_fmt_directive(comment.value, FMT_SKIP):
464 yield from _generate_ignored_nodes_from_fmt_skip(leaf, comment, mode)
465 return
466 container: LN | None = container_of(leaf)
467 while container is not None and container.type != token.ENDMARKER:
468 if is_fmt_on(container, mode=mode):
469 return
471 # fix for fmt: on in children
472 if children_contains_fmt_on(container, mode=mode):
473 for index, child in enumerate(container.children):
474 if isinstance(child, Leaf) and is_fmt_on(child, mode=mode):
475 if child.type in CLOSING_BRACKETS:
476 # This means `# fmt: on` is placed at a different bracket level
477 # than `# fmt: off`. This is an invalid use, but as a courtesy,
478 # we include this closing bracket in the ignored nodes.
479 # The alternative is to fail the formatting.
480 yield child
481 return
482 if (
483 child.type == token.INDENT
484 and index < len(container.children) - 1
485 and children_contains_fmt_on(
486 container.children[index + 1], mode=mode
487 )
488 ):
489 # This means `# fmt: on` is placed right after an indentation
490 # level, and we shouldn't swallow the previous INDENT token.
491 return
492 if children_contains_fmt_on(child, mode=mode):
493 return
494 yield child
495 else:
496 if container.type == token.DEDENT and container.next_sibling is None:
497 # This can happen when there is no matching `# fmt: on` comment at the
498 # same level as `# fmt: on`. We need to keep this DEDENT.
499 return
500 yield container
501 container = container.next_sibling
504def _find_compound_statement_context(parent: Node) -> Node | None:
505 """Return the body node of a compound statement if we should respect fmt: skip.
507 This handles one-line compound statements like:
508 if condition: body # fmt: skip
510 When Black expands such statements, they temporarily look like:
511 if condition:
512 body # fmt: skip
514 In both cases, we want to return the body node (either the simple_stmt directly
515 or the suite containing it).
516 """
517 if parent.type != syms.simple_stmt:
518 return None
520 if not isinstance(parent.parent, Node):
521 return None
523 # Case 1: Expanded form after Black's initial formatting pass.
524 # The one-liner has been split across multiple lines:
525 # if True:
526 # print("a"); print("b") # fmt: skip
527 # Structure: compound_stmt -> suite -> simple_stmt
528 if (
529 parent.parent.type == syms.suite
530 and isinstance(parent.parent.parent, Node)
531 and parent.parent.parent.type in _COMPOUND_STATEMENTS
532 ):
533 return parent.parent
535 # Case 2: Original one-line form from the input source.
536 # The statement is still on a single line:
537 # if True: print("a"); print("b") # fmt: skip
538 # Structure: compound_stmt -> simple_stmt
539 if parent.parent.type in _COMPOUND_STATEMENTS:
540 return parent
542 return None
545def _should_keep_compound_statement_inline(
546 body_node: Node, simple_stmt_parent: Node
547) -> bool:
548 """Check if a compound statement should be kept on one line.
550 Returns True only for compound statements with semicolon-separated bodies,
551 like: if True: print("a"); print("b") # fmt: skip
552 """
553 # Check if there are semicolons in the body
554 for leaf in body_node.leaves():
555 if leaf.type == token.SEMI:
556 # Verify it's a single-line body (one simple_stmt)
557 if body_node.type == syms.suite:
558 # After formatting: check suite has one simple_stmt child
559 simple_stmts = [
560 child
561 for child in body_node.children
562 if child.type == syms.simple_stmt
563 ]
564 return len(simple_stmts) == 1 and simple_stmts[0] is simple_stmt_parent
565 else:
566 # Original form: body_node IS the simple_stmt
567 return body_node is simple_stmt_parent
568 return False
571def _get_compound_statement_header(
572 body_node: Node, simple_stmt_parent: Node
573) -> list[LN]:
574 """Get header nodes for a compound statement that should be preserved inline."""
575 if not _should_keep_compound_statement_inline(body_node, simple_stmt_parent):
576 return []
578 # Get the compound statement (parent of body)
579 compound_stmt = body_node.parent
580 if compound_stmt is None or compound_stmt.type not in _COMPOUND_STATEMENTS:
581 return []
583 # Collect all header leaves before the body
584 header_leaves: list[LN] = []
585 for child in compound_stmt.children:
586 if child is body_node:
587 break
588 if isinstance(child, Leaf):
589 if child.type not in (token.NEWLINE, token.INDENT):
590 header_leaves.append(child)
591 else:
592 header_leaves.extend(child.leaves())
593 return header_leaves
596def _generate_ignored_nodes_from_fmt_skip(
597 leaf: Leaf, comment: ProtoComment, mode: Mode
598) -> Iterator[LN]:
599 """Generate all leaves that should be ignored by the `# fmt: skip` from `leaf`."""
600 prev_sibling = leaf.prev_sibling
601 parent = leaf.parent
602 ignored_nodes: list[LN] = []
603 # Need to properly format the leaf prefix to compare it to comment.value,
604 # which is also formatted
605 comments = list_comments(leaf.prefix, is_endmarker=False, mode=mode)
606 if not comments or comment.value != comments[0].value:
607 return
608 if prev_sibling is not None:
609 leaf.prefix = leaf.prefix[comment.consumed :]
611 if Preview.fix_fmt_skip_in_one_liners not in mode:
612 siblings = [prev_sibling]
613 while (
614 "\n" not in prev_sibling.prefix
615 and prev_sibling.prev_sibling is not None
616 ):
617 prev_sibling = prev_sibling.prev_sibling
618 siblings.insert(0, prev_sibling)
619 yield from siblings
620 return
622 # Generates the nodes to be ignored by `fmt: skip`.
624 # Nodes to ignore are the ones on the same line as the
625 # `# fmt: skip` comment, excluding the `# fmt: skip`
626 # node itself.
628 # Traversal process (starting at the `# fmt: skip` node):
629 # 1. Move to the `prev_sibling` of the current node.
630 # 2. If `prev_sibling` has children, go to its rightmost leaf.
631 # 3. If there's no `prev_sibling`, move up to the parent
632 # node and repeat.
633 # 4. Continue until:
634 # a. You encounter an `INDENT` or `NEWLINE` node (indicates
635 # start of the line).
636 # b. You reach the root node.
638 # Include all visited LEAVES in the ignored list, except INDENT
639 # or NEWLINE leaves.
641 current_node = prev_sibling
642 ignored_nodes = [current_node]
643 if current_node.prev_sibling is None and current_node.parent is not None:
644 current_node = current_node.parent
645 while "\n" not in current_node.prefix and current_node.prev_sibling is not None:
646 leaf_nodes = list(current_node.prev_sibling.leaves())
647 current_node = leaf_nodes[-1] if leaf_nodes else current_node
649 if current_node.type in (token.NEWLINE, token.INDENT):
650 current_node.prefix = ""
651 break
653 # Special case for with expressions
654 # Without this, we can stuck inside the asexpr_test's children's children
655 if (
656 current_node.parent
657 and current_node.parent.type == syms.asexpr_test
658 and current_node.parent.parent
659 and current_node.parent.parent.type == syms.with_stmt
660 ):
661 current_node = current_node.parent
663 ignored_nodes.insert(0, current_node)
665 if current_node.prev_sibling is None and current_node.parent is not None:
666 current_node = current_node.parent
668 # Special handling for compound statements with semicolon-separated bodies
669 if Preview.fix_fmt_skip_in_one_liners in mode and isinstance(parent, Node):
670 body_node = _find_compound_statement_context(parent)
671 if body_node is not None:
672 header_nodes = _get_compound_statement_header(body_node, parent)
673 if header_nodes:
674 ignored_nodes = header_nodes + ignored_nodes
676 yield from ignored_nodes
677 elif (
678 parent is not None and parent.type == syms.suite and leaf.type == token.NEWLINE
679 ):
680 # The `# fmt: skip` is on the colon line of the if/while/def/class/...
681 # statements. The ignored nodes should be previous siblings of the
682 # parent suite node.
683 leaf.prefix = ""
684 parent_sibling = parent.prev_sibling
685 while parent_sibling is not None and parent_sibling.type != syms.suite:
686 ignored_nodes.insert(0, parent_sibling)
687 parent_sibling = parent_sibling.prev_sibling
688 # Special case for `async_stmt` where the ASYNC token is on the
689 # grandparent node.
690 grandparent = parent.parent
691 if (
692 grandparent is not None
693 and grandparent.prev_sibling is not None
694 and grandparent.prev_sibling.type == token.ASYNC
695 ):
696 ignored_nodes.insert(0, grandparent.prev_sibling)
697 yield from iter(ignored_nodes)
700def is_fmt_on(container: LN, mode: Mode) -> bool:
701 """Determine whether formatting is switched on within a container.
702 Determined by whether the last `# fmt:` comment is `on` or `off`.
703 """
704 fmt_on = False
705 for comment in list_comments(container.prefix, is_endmarker=False, mode=mode):
706 if comment.value in FMT_ON:
707 fmt_on = True
708 elif comment.value in FMT_OFF:
709 fmt_on = False
710 return fmt_on
713def children_contains_fmt_on(container: LN, mode: Mode) -> bool:
714 """Determine if children have formatting switched on."""
715 for child in container.children:
716 leaf = first_leaf_of(child)
717 if leaf is not None and is_fmt_on(leaf, mode=mode):
718 return True
720 return False
723def contains_pragma_comment(comment_list: list[Leaf]) -> bool:
724 """
725 Returns:
726 True iff one of the comments in @comment_list is a pragma used by one
727 of the more common static analysis tools for python (e.g. mypy, flake8,
728 pylint).
729 """
730 for comment in comment_list:
731 if comment.value.startswith(("# type:", "# noqa", "# pylint:")):
732 return True
734 return False
737def _contains_fmt_directive(
738 comment_line: str, directives: set[str] = FMT_OFF | FMT_ON | FMT_SKIP
739) -> bool:
740 """
741 Checks if the given comment contains format directives, alone or paired with
742 other comments.
744 Defaults to checking all directives (skip, off, on, yapf), but can be
745 narrowed to specific ones.
747 Matching styles:
748 # foobar <-- single comment
749 # foobar # foobar # foobar <-- multiple comments
750 # foobar; foobar <-- list of comments (; separated)
751 """
752 semantic_comment_blocks = [
753 comment_line,
754 *[
755 _COMMENT_PREFIX + comment.strip()
756 for comment in comment_line.split(_COMMENT_PREFIX)[1:]
757 ],
758 *[
759 _COMMENT_PREFIX + comment.strip()
760 for comment in comment_line.strip(_COMMENT_PREFIX).split(
761 _COMMENT_LIST_SEPARATOR
762 )
763 ],
764 ]
766 return any(comment in directives for comment in semantic_comment_blocks)