• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

psf / black / 18643242364

20 Oct 2025 05:37AM UTC coverage: 95.927% (-0.03%) from 95.956%
18643242364

Pull #4800

github

web-flow
Merge 3c59c8f0e into e97cf5bdb
Pull Request #4800: fix_fmt_skip_in_one_liners

3604 of 3864 branches covered (93.27%)

36 of 40 new or added lines in 1 file covered. (90.0%)

1 existing line in 1 file now uncovered.

7772 of 8102 relevant lines covered (95.93%)

4.79 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.73
/src/black/comments.py
1
import re
5✔
2
from collections.abc import Collection, Iterator
5✔
3
from dataclasses import dataclass
5✔
4
from functools import lru_cache
5✔
5
from typing import Final, Optional, Union
5✔
6

7
from black.mode import Mode, Preview
5✔
8
from black.nodes import (
5✔
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
)
20
from blib2to3.pgen2 import token
5✔
21
from blib2to3.pytree import Leaf, Node
5✔
22

23
# types
24
LN = Union[Leaf, Node]
5✔
25

26
FMT_OFF: Final = {"# fmt: off", "# fmt:off", "# yapf: disable"}
5✔
27
FMT_SKIP: Final = {"# fmt: skip", "# fmt:skip"}
5✔
28
FMT_ON: Final = {"# fmt: on", "# fmt:on", "# yapf: enable"}
5✔
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}
5✔
33

34
COMMENT_EXCEPTIONS = " !:#'"
5✔
35
_COMMENT_PREFIX = "# "
5✔
36
_COMMENT_LIST_SEPARATOR = ";"
5✔
37

38

39
@dataclass
5✔
40
class ProtoComment:
5✔
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
5✔
52
    value: str  # content of the comment
5✔
53
    newlines: int  # how many newlines before the comment
5✔
54
    consumed: int  # how many characters of the original leaf's prefix did we consume
5✔
55
    form_feed: bool  # is there a form feed before the comment
5✔
56
    leading_whitespace: str  # leading whitespace before the comment, if any
5✔
57

58

59
def generate_comments(leaf: LN, mode: Mode) -> Iterator[Leaf]:
5✔
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
5✔
79
    for pc in list_comments(
5✔
80
        leaf.prefix, is_endmarker=leaf.type == token.ENDMARKER, mode=mode
81
    ):
82
        total_consumed = pc.consumed
5✔
83
        prefix = make_simple_prefix(pc.newlines, pc.form_feed)
5✔
84
        yield Leaf(pc.type, pc.value, prefix=prefix)
5✔
85
    normalize_trailing_prefix(leaf, total_consumed)
5✔
86

87

88
@lru_cache(maxsize=4096)
5✔
89
def list_comments(prefix: str, *, is_endmarker: bool, mode: Mode) -> list[ProtoComment]:
5✔
90
    """Return a list of :class:`ProtoComment` objects parsed from the given `prefix`."""
91
    result: list[ProtoComment] = []
5✔
92
    if not prefix or "#" not in prefix:
5✔
93
        return result
5✔
94

95
    consumed = 0
5✔
96
    nlines = 0
5✔
97
    ignored_lines = 0
5✔
98
    form_feed = False
5✔
99
    for index, full_line in enumerate(re.split("\r?\n|\r", prefix)):
5✔
100
        consumed += len(full_line) + 1  # adding the length of the split '\n'
5✔
101
        match = re.match(r"^(\s*)(\S.*|)$", full_line)
5✔
102
        assert match
5✔
103
        whitespace, line = match.groups()
5✔
104
        if not line:
5✔
105
            nlines += 1
5✔
106
            if "\f" in full_line:
5✔
107
                form_feed = True
5✔
108
        if not line.startswith("#"):
5✔
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("\\"):
5✔
113
                ignored_lines += 1
5✔
114
            continue
5✔
115

116
        if index == ignored_lines and not is_endmarker:
5✔
117
            comment_type = token.COMMENT  # simple trailing comment
5✔
118
        else:
119
            comment_type = STANDALONE_COMMENT
5✔
120
        comment = make_comment(line, mode=mode)
5✔
121
        result.append(
5✔
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
5✔
132
        nlines = 0
5✔
133
    return result
5✔
134

135

136
def normalize_trailing_prefix(leaf: LN, total_consumed: int) -> None:
5✔
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:]
5✔
142
    if "\\" not in remainder:
5✔
143
        nl_count = remainder.count("\n")
5✔
144
        form_feed = "\f" in remainder and remainder.endswith("\n")
5✔
145
        leaf.prefix = make_simple_prefix(nl_count, form_feed)
5✔
146
        return
5✔
147

148
    leaf.prefix = ""
5✔
149

150

151
def make_comment(content: str, mode: Mode) -> str:
5✔
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
    content = content.rstrip()
5✔
160
    if not content:
5!
161
        return "#"
×
162

163
    if content[0] == "#":
5!
164
        content = content[1:]
5✔
165
    if (
5✔
166
        content
167
        and content[0] == "\N{NO-BREAK SPACE}"
168
        and not is_type_comment_string("# " + content.lstrip(), mode=mode)
169
    ):
170
        content = " " + content[1:]  # Replace NBSP by a simple space
5✔
171
    if (
5✔
172
        Preview.standardize_type_comments in mode
173
        and content
174
        and "\N{NO-BREAK SPACE}" not in content
175
        and is_type_comment_string("#" + content, mode=mode)
176
    ):
177
        type_part, value_part = content.split(":", 1)
5✔
178
        content = type_part.strip() + ": " + value_part.strip()
5✔
179

180
    if content and content[0] not in COMMENT_EXCEPTIONS:
5✔
181
        content = " " + content
5✔
182
    return "#" + content
5✔
183

184

185
def normalize_fmt_off(
5✔
186
    node: Node, mode: Mode, lines: Collection[tuple[int, int]]
187
) -> None:
188
    """Convert content between `# fmt: off`/`# fmt: on` into standalone comments."""
189
    try_again = True
5✔
190
    while try_again:
5✔
191
        try_again = convert_one_fmt_off_pair(node, mode, lines)
5✔
192

193

194
def convert_one_fmt_off_pair(
5✔
195
    node: Node, mode: Mode, lines: Collection[tuple[int, int]]
196
) -> bool:
197
    """Convert content of a single `# fmt: off`/`# fmt: on` into a standalone comment.
198

199
    Returns True if a pair was converted.
200
    """
201
    for leaf in node.leaves():
5✔
202
        previous_consumed = 0
5✔
203
        for comment in list_comments(leaf.prefix, is_endmarker=False, mode=mode):
5✔
204
            is_fmt_off = comment.value in FMT_OFF
5✔
205
            is_fmt_skip = _contains_fmt_skip_comment(comment.value, mode)
5✔
206
            if (not is_fmt_off and not is_fmt_skip) or (
5✔
207
                # Invalid use when `# fmt: off` is applied before a closing bracket.
208
                is_fmt_off
209
                and leaf.type in CLOSING_BRACKETS
210
            ):
211
                previous_consumed = comment.consumed
5✔
212
                continue
5✔
213
            # We only want standalone comments. If there's no previous leaf or
214
            # the previous leaf is indentation, it's a standalone comment in
215
            # disguise.
216
            if comment.type != STANDALONE_COMMENT:
5✔
217
                prev = preceding_leaf(leaf)
5✔
218
                if prev:
5✔
219
                    if is_fmt_off and prev.type not in WHITESPACE:
5✔
220
                        continue
5✔
221
                    if is_fmt_skip and prev.type in WHITESPACE:
5✔
222
                        continue
5✔
223

224
            ignored_nodes = list(generate_ignored_nodes(leaf, comment, mode))
5✔
225
            if not ignored_nodes:
5✔
226
                continue
5✔
227

228
            first = ignored_nodes[0]  # Can be a container node with the `leaf`.
5✔
229
            parent = first.parent
5✔
230
            prefix = first.prefix
5✔
231
            if comment.value in FMT_OFF:
5✔
232
                first.prefix = prefix[comment.consumed :]
5✔
233
            if is_fmt_skip:
5✔
234
                first.prefix = ""
5✔
235
                standalone_comment_prefix = prefix
5✔
236
            else:
237
                standalone_comment_prefix = (
5✔
238
                    prefix[:previous_consumed] + "\n" * comment.newlines
239
                )
240
            hidden_value = "".join(str(n) for n in ignored_nodes)
5✔
241
            comment_lineno = leaf.lineno - comment.newlines
5✔
242
            if comment.value in FMT_OFF:
5✔
243
                fmt_off_prefix = ""
5✔
244
                if len(lines) > 0 and not any(
5✔
245
                    line[0] <= comment_lineno <= line[1] for line in lines
246
                ):
247
                    # keeping indentation of comment by preserving original whitespaces.
248
                    fmt_off_prefix = prefix.split(comment.value)[0]
5✔
249
                    if "\n" in fmt_off_prefix:
5✔
250
                        fmt_off_prefix = fmt_off_prefix.split("\n")[-1]
5✔
251
                standalone_comment_prefix += fmt_off_prefix
5✔
252
                hidden_value = comment.value + "\n" + hidden_value
5✔
253
            if is_fmt_skip:
5✔
254
                hidden_value += comment.leading_whitespace + comment.value
5✔
255
            if hidden_value.endswith("\n"):
5✔
256
                # That happens when one of the `ignored_nodes` ended with a NEWLINE
257
                # leaf (possibly followed by a DEDENT).
258
                hidden_value = hidden_value[:-1]
5✔
259
            first_idx: Optional[int] = None
5✔
260
            for ignored in ignored_nodes:
5✔
261
                index = ignored.remove()
5✔
262
                if first_idx is None:
5✔
263
                    first_idx = index
5✔
264
            assert parent is not None, "INTERNAL ERROR: fmt: on/off handling (1)"
5✔
265
            assert first_idx is not None, "INTERNAL ERROR: fmt: on/off handling (2)"
5✔
266
            parent.insert_child(
5✔
267
                first_idx,
268
                Leaf(
269
                    STANDALONE_COMMENT,
270
                    hidden_value,
271
                    prefix=standalone_comment_prefix,
272
                    fmt_pass_converted_first_leaf=first_leaf_of(first),
273
                ),
274
            )
275
            return True
5✔
276

277
    return False
5✔
278

279

280
def generate_ignored_nodes(
5✔
281
    leaf: Leaf, comment: ProtoComment, mode: Mode
282
) -> Iterator[LN]:
283
    """Starting from the container of `leaf`, generate all leaves until `# fmt: on`.
284

285
    If comment is skip, returns leaf only.
286
    Stops at the end of the block.
287
    """
288
    if _contains_fmt_skip_comment(comment.value, mode):
5✔
289
        yield from _generate_ignored_nodes_from_fmt_skip(leaf, comment, mode)
5✔
290
        return
5✔
291
    container: Optional[LN] = container_of(leaf)
5✔
292
    while container is not None and container.type != token.ENDMARKER:
5✔
293
        if is_fmt_on(container, mode=mode):
5✔
294
            return
5✔
295

296
        # fix for fmt: on in children
297
        if children_contains_fmt_on(container, mode=mode):
5✔
298
            for index, child in enumerate(container.children):
5!
299
                if isinstance(child, Leaf) and is_fmt_on(child, mode=mode):
5✔
300
                    if child.type in CLOSING_BRACKETS:
5✔
301
                        # This means `# fmt: on` is placed at a different bracket level
302
                        # than `# fmt: off`. This is an invalid use, but as a courtesy,
303
                        # we include this closing bracket in the ignored nodes.
304
                        # The alternative is to fail the formatting.
305
                        yield child
5✔
306
                    return
5✔
307
                if (
5✔
308
                    child.type == token.INDENT
309
                    and index < len(container.children) - 1
310
                    and children_contains_fmt_on(
311
                        container.children[index + 1], mode=mode
312
                    )
313
                ):
314
                    # This means `# fmt: on` is placed right after an indentation
315
                    # level, and we shouldn't swallow the previous INDENT token.
316
                    return
5✔
317
                if children_contains_fmt_on(child, mode=mode):
5✔
318
                    return
5✔
319
                yield child
5✔
320
        else:
321
            if container.type == token.DEDENT and container.next_sibling is None:
5✔
322
                # This can happen when there is no matching `# fmt: on` comment at the
323
                # same level as `# fmt: on`. We need to keep this DEDENT.
324
                return
5✔
325
            yield container
5✔
326
            container = container.next_sibling
5✔
327

328

329
def _find_compound_statement_context(
5✔
330
    parent: Optional[LN], mode: Mode
331
) -> Optional[Node]:
332
    """Return the body node of a compound statement if we should respect fmt: skip.
333

334
    This handles one-line compound statements like:
335
        if condition: body  # fmt: skip
336

337
    When Black expands such statements, they temporarily look like:
338
        if condition:
339
            body  # fmt: skip
340

341
    In both cases, we want to return the body node (either the simple_stmt directly
342
    or the suite containing it).
343
    """
344
    if Preview.fix_fmt_skip_in_one_liners not in mode:
5!
NEW
345
        return None
×
346

347
    if parent is None or parent.type != syms.simple_stmt:
5✔
348
        return None
5✔
349

350
    assert isinstance(parent, Node)
5✔
351

352
    # Case 1: Expanded form after Black's initial formatting pass.
353
    # The one-liner has been split across multiple lines:
354
    #     if True:
355
    #         print("a"); print("b")  # fmt: skip
356
    # Structure: compound_stmt -> suite -> simple_stmt
357
    if (
5✔
358
        isinstance(parent.parent, Node)
359
        and parent.parent.type == syms.suite
360
        and isinstance(parent.parent.parent, Node)
361
        and parent.parent.parent.type in _COMPOUND_STATEMENTS
362
    ):
363
        return parent.parent
5✔
364

365
    # Case 2: Original one-line form from the input source.
366
    # The statement is still on a single line:
367
    #     if True: print("a"); print("b")  # fmt: skip
368
    # Structure: compound_stmt -> simple_stmt
369
    if (
5✔
370
        isinstance(parent.parent, Node)
371
        and parent.parent.type in _COMPOUND_STATEMENTS
372
    ):
373
        return parent
5✔
374

375
    return None
5✔
376

377

378
def _should_keep_compound_statement_inline(
5✔
379
    body_node: Node, simple_stmt_parent: Node
380
) -> bool:
381
    """Check if a compound statement should be kept on one line.
382

383
    Returns True only for compound statements with semicolon-separated bodies,
384
    like: if True: print("a"); print("b")  # fmt: skip
385
    """
386
    # Check if there are semicolons in the body
387
    for leaf in body_node.leaves():
5✔
388
        if leaf.type == token.SEMI:
5✔
389
            # Verify it's a single-line body (one simple_stmt)
390
            if body_node.type == syms.suite:
5!
391
                # After formatting: check suite has one simple_stmt child
NEW
392
                simple_stmts = [
×
393
                    child
394
                    for child in body_node.children
395
                    if child.type == syms.simple_stmt
396
                ]
NEW
397
                return len(simple_stmts) == 1 and simple_stmts[0] is simple_stmt_parent
×
398
            else:
399
                # Original form: body_node IS the simple_stmt
400
                return body_node is simple_stmt_parent
5✔
401
    return False
5✔
402

403

404
def _get_compound_statement_header(body_node: Node, simple_stmt_parent: Node) -> list[LN]:
5✔
405
    """Get header nodes for a compound statement that should be preserved inline."""
406
    if not _should_keep_compound_statement_inline(body_node, simple_stmt_parent):
5✔
407
        return []
5✔
408

409
    # Get the compound statement (parent of body)
410
    compound_stmt = body_node.parent
5✔
411
    if compound_stmt is None or compound_stmt.type not in _COMPOUND_STATEMENTS:
5!
NEW
412
        return []
×
413

414
    # Collect all header leaves before the body
415
    header_leaves: list[LN] = []
5✔
416
    for child in compound_stmt.children:
5!
417
        if child is body_node:
5✔
418
            break
5✔
419
        if isinstance(child, Leaf):
5✔
420
            if child.type not in (token.NEWLINE, token.INDENT):
5!
421
                header_leaves.append(child)
5✔
422
        else:
423
            header_leaves.extend(child.leaves())
5✔
424
    return header_leaves
5✔
425

426

427
def _generate_ignored_nodes_from_fmt_skip(
5✔
428
    leaf: Leaf, comment: ProtoComment, mode: Mode
429
) -> Iterator[LN]:
430
    """Generate all leaves that should be ignored by the `# fmt: skip` from `leaf`."""
431
    prev_sibling = leaf.prev_sibling
5✔
432
    parent = leaf.parent
5✔
433
    ignored_nodes: list[LN] = []
5✔
434
    # Need to properly format the leaf prefix to compare it to comment.value,
435
    # which is also formatted
436
    comments = list_comments(leaf.prefix, is_endmarker=False, mode=mode)
5✔
437
    if not comments or comment.value != comments[0].value:
5!
UNCOV
438
        return
×
439
    if prev_sibling is not None:
5✔
440
        leaf.prefix = leaf.prefix[comment.consumed :]
5✔
441

442
        if Preview.fix_fmt_skip_in_one_liners not in mode:
5✔
443
            siblings = [prev_sibling]
5✔
444
            while (
5✔
445
                "\n" not in prev_sibling.prefix
446
                and prev_sibling.prev_sibling is not None
447
            ):
448
                prev_sibling = prev_sibling.prev_sibling
5✔
449
                siblings.insert(0, prev_sibling)
5✔
450
            yield from siblings
5✔
451
            return
5✔
452

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

455
        # Nodes to ignore are the ones on the same line as the
456
        # `# fmt: skip` comment, excluding the `# fmt: skip`
457
        # node itself.
458

459
        # Traversal process (starting at the `# fmt: skip` node):
460
        # 1. Move to the `prev_sibling` of the current node.
461
        # 2. If `prev_sibling` has children, go to its rightmost leaf.
462
        # 3. If there's no `prev_sibling`, move up to the parent
463
        # node and repeat.
464
        # 4. Continue until:
465
        #    a. You encounter an `INDENT` or `NEWLINE` node (indicates
466
        #       start of the line).
467
        #    b. You reach the root node.
468

469
        # Include all visited LEAVES in the ignored list, except INDENT
470
        # or NEWLINE leaves.
471

472
        current_node = prev_sibling
5✔
473
        ignored_nodes = [current_node]
5✔
474
        if current_node.prev_sibling is None and current_node.parent is not None:
5✔
475
            current_node = current_node.parent
5✔
476
        while "\n" not in current_node.prefix and current_node.prev_sibling is not None:
5✔
477
            leaf_nodes = list(current_node.prev_sibling.leaves())
5✔
478
            current_node = leaf_nodes[-1] if leaf_nodes else current_node
5✔
479

480
            if current_node.type in (token.NEWLINE, token.INDENT):
5✔
481
                current_node.prefix = ""
5✔
482
                break
5✔
483

484
            ignored_nodes.insert(0, current_node)
5✔
485

486
            if current_node.prev_sibling is None and current_node.parent is not None:
5✔
487
                current_node = current_node.parent
5✔
488
        # Special handling for compound statements with semicolon-separated bodies
489
        body_node = _find_compound_statement_context(parent, mode)
5✔
490
        if body_node is not None and isinstance(parent, Node):
5✔
491
            header_nodes = _get_compound_statement_header(body_node, parent)
5✔
492
            if header_nodes:
5✔
493
                ignored_nodes = header_nodes + ignored_nodes
5✔
494

495
        yield from ignored_nodes
5✔
496
    elif (
5!
497
        parent is not None and parent.type == syms.suite and leaf.type == token.NEWLINE
498
    ):
499
        # The `# fmt: skip` is on the colon line of the if/while/def/class/...
500
        # statements. The ignored nodes should be previous siblings of the
501
        # parent suite node.
502
        leaf.prefix = ""
5✔
503
        parent_sibling = parent.prev_sibling
5✔
504
        while parent_sibling is not None and parent_sibling.type != syms.suite:
5✔
505
            ignored_nodes.insert(0, parent_sibling)
5✔
506
            parent_sibling = parent_sibling.prev_sibling
5✔
507
        # Special case for `async_stmt` where the ASYNC token is on the
508
        # grandparent node.
509
        grandparent = parent.parent
5✔
510
        if (
5✔
511
            grandparent is not None
512
            and grandparent.prev_sibling is not None
513
            and grandparent.prev_sibling.type == token.ASYNC
514
        ):
515
            ignored_nodes.insert(0, grandparent.prev_sibling)
5✔
516
        yield from iter(ignored_nodes)
5✔
517

518

519
def is_fmt_on(container: LN, mode: Mode) -> bool:
5✔
520
    """Determine whether formatting is switched on within a container.
521
    Determined by whether the last `# fmt:` comment is `on` or `off`.
522
    """
523
    fmt_on = False
5✔
524
    for comment in list_comments(container.prefix, is_endmarker=False, mode=mode):
5✔
525
        if comment.value in FMT_ON:
5✔
526
            fmt_on = True
5✔
527
        elif comment.value in FMT_OFF:
5✔
528
            fmt_on = False
5✔
529
    return fmt_on
5✔
530

531

532
def children_contains_fmt_on(container: LN, mode: Mode) -> bool:
5✔
533
    """Determine if children have formatting switched on."""
534
    for child in container.children:
5✔
535
        leaf = first_leaf_of(child)
5✔
536
        if leaf is not None and is_fmt_on(leaf, mode=mode):
5✔
537
            return True
5✔
538

539
    return False
5✔
540

541

542
def contains_pragma_comment(comment_list: list[Leaf]) -> bool:
5✔
543
    """
544
    Returns:
545
        True iff one of the comments in @comment_list is a pragma used by one
546
        of the more common static analysis tools for python (e.g. mypy, flake8,
547
        pylint).
548
    """
549
    for comment in comment_list:
5✔
550
        if comment.value.startswith(("# type:", "# noqa", "# pylint:")):
5✔
551
            return True
5✔
552

553
    return False
5✔
554

555

556
def _contains_fmt_skip_comment(comment_line: str, mode: Mode) -> bool:
5✔
557
    """
558
    Checks if the given comment contains FMT_SKIP alone or paired with other comments.
559
    Matching styles:
560
      # fmt:skip                           <-- single comment
561
      # noqa:XXX # fmt:skip # a nice line  <-- multiple comments (Preview)
562
      # pylint:XXX; fmt:skip               <-- list of comments (; separated, Preview)
563
    """
564
    semantic_comment_blocks = [
5✔
565
        comment_line,
566
        *[
567
            _COMMENT_PREFIX + comment.strip()
568
            for comment in comment_line.split(_COMMENT_PREFIX)[1:]
569
        ],
570
        *[
571
            _COMMENT_PREFIX + comment.strip()
572
            for comment in comment_line.strip(_COMMENT_PREFIX).split(
573
                _COMMENT_LIST_SEPARATOR
574
            )
575
        ],
576
    ]
577

578
    return any(comment in FMT_SKIP for comment in semantic_comment_blocks)
5✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc