fix: replace the line scanner with a real one; three more bypasses were open

Adversarial review demonstrated that the previous fix closed one class and
left three, then reintroduced the bug this file's own history is about.

- A ``` fence in a PLAIN `//` comment earned the doctest exemption. rustdoc
  runs no test there ("running 0 tests"), so two lines exempted anything after
  them. The cheapest bypass yet, and `--list-doctests` in the same file already
  had it right: one of the two was wrong and it was this one.
- A block comment opened after code on the same line was invisible, since only
  a line STARTING with /* was seen. That is the most natural way to write one.
- Nested block comments closed at the first */ and the rest read as code.
- And the false positive: `in_block` had no reset path, so a /* inside a raw
  string swallowed the rest of the file. Latent — no .rs file starts a line
  with /* today, so the block fix had zero effect on this repo — but 8 files
  hold raw strings and this repo's habit is Rust fixtures inside them. The
  first such fixture breaks CI with a diagnosis pointing at a string.

A line-based scanner cannot decide any of these, so `scan()` now walks
characters, tracking nested block comments, strings, raw strings (with hashes)
and char literals. `#![...]` ends a run where `#[...]` stays transparent,
because a crate doc and an item doc separated by an inner attribute are two
blocks a reader does not meet as one. `#[doc = "..."]` counts: it is the one
attribute that is a comment.

The self-test carries all of it: 12 bypasses, 6 shapes that must stay legal.
Each new fix was proved discriminating by injecting it alone.

One of those injections caught a VACUOUS test of my own: with raw-string
handling disabled, a plain-string fallback reached the same verdict, so the
fixture proved nothing. It now contains r#"x"y, which a non-raw parse closes
early — so the fault is visible. That is the defect this repo is named for,
found in the test written to prevent it.

Closes improvise-mbmf, which PR #45 claimed to close and did not.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Edward Langley
2026-09-20 11:13:00 -07:00
parent 0a9e342d2e
commit a6451363ca
2 changed files with 240 additions and 63 deletions
+14 -8
View File
File diff suppressed because one or more lines are too long
+226 -55
View File
@@ -21,81 +21,170 @@ the rest is prose wearing a fence and the cap applies to it.
import argparse
import os
import re
import sys
LIMIT = 2
_RAW_STR = re.compile(r'(?:b|c)?r(#*)"')
_CHAR_LIT = re.compile(r"'(?:\\.|[^'\\])'")
def scan(lines):
"""Classify each line, respecting Rust strings and NESTED block comments.
A line-based scanner cannot do this: it granted the doctest exemption to
plain `//`, missed a block comment opened after code, and read `/*` inside
a raw string as a comment. See context/code/tooling.md.
"""
out = []
in_block = 0
in_raw = None
in_str = False
for raw in lines:
i, n = 0, len(raw)
has_code = False
has_comment = in_block > 0
body = None
while i < n:
if in_block:
has_comment = True
if raw.startswith("/*", i):
in_block += 1
i += 2
elif raw.startswith("*/", i):
in_block -= 1
i += 2
else:
i += 1
continue
if in_raw is not None:
closer = '"' + "#" * in_raw
if raw.startswith(closer, i):
in_raw = None
i += len(closer)
else:
i += 1
has_code = True
continue
if in_str:
has_code = True
if raw[i] == "\\":
i += 2
continue
if raw[i] == '"':
in_str = False
i += 1
continue
if raw[i].isspace():
i += 1
continue
if raw.startswith("//", i):
has_comment = True
if not has_code:
body = raw[i:]
break
if raw.startswith("/*", i):
has_comment = True
in_block = 1
i += 2
continue
m = _RAW_STR.match(raw, i)
if m:
in_raw = len(m.group(1))
has_code = True
i = m.end()
continue
m = _CHAR_LIT.match(raw, i)
if m:
has_code = True
i = m.end()
continue
if raw[i] == '"':
in_str = True
has_code = True
i += 1
continue
has_code = True
i += 1
out.append((has_code, has_comment, body))
return out
def comment_runs(lines):
"""Yield (start_line_number, length) for each run, skipping runnable doctests.
Fence state and exemption are TWO facts and must be tracked separately.
Deciding both from one flag meant a non-runnable fence's bare closing ```
looked runnable and opened an exemption, so prose after a ```text block
evaded the cap entirely (found by review on PR #42 -- the sixth silent
success in this file, and the one introduced by fixing the fourth).
Fence state and exemption are TWO facts and must be tracked separately, and
the exemption belongs to DOC comments only -- rustdoc runs no test in a
plain `//`. Attributes are transparent, except `#![...]`, which separates a
crate doc from an item doc and so ends the run.
"""
scanned = scan(lines)
run_start = None
run_len = 0
in_fence = False # are we between an opening and a closing fence?
fence_exempt = False # is THIS fence's body a doctest rustdoc will run?
in_block = False # inside a /* ... */ or /** ... */ comment?
for n, raw in enumerate(lines, 1):
s = raw.strip()
in_fence = False
fence_exempt = False
attr_depth = 0
attr_inner = False
# A block comment is a comment run like any other, and /** */ is a DOC
# comment: rustdoc renders it exactly as /// does (improvise-9c8c).
if in_block:
if run_start is None:
run_start, run_len = n, 0
run_len += 1
if "*/" in s:
in_block = False
continue
if s.startswith("/*"):
if run_start is None:
run_start, run_len = n, 0
run_len += 1
if "*/" not in s[2:]:
in_block = True
def start(n):
nonlocal run_start, run_len
if run_start is None:
run_start, run_len = n, 0
run_len += 1
for n, (has_code, has_comment, body) in enumerate(scanned, 1):
s = lines[n - 1].strip()
is_comment = has_comment and not has_code
if attr_depth > 0:
attr_depth += s.count("[") - s.count("]")
if attr_depth <= 0:
attr_depth = 0
if attr_inner and run_start is not None and run_len > 0:
yield run_start, run_len
run_start, run_len = None, 0
continue
is_comment = s.startswith("//")
if is_comment and s.lstrip("/!").strip().startswith("```"):
if in_fence:
# The DELIMITERS belong to the block they close, so a runnable
# fence's closing ``` is exempt too -- otherwise it lands in the
# prose run and a two-line doc reads as three.
exempt_here = fence_exempt
in_fence = False
fence_exempt = False
else:
info = s.lstrip("/!").strip()[3:].strip().lower()
# rustdoc runs a bare or `rust` fence and does not run `text`,
# `ignore` or `no_run`. Only a runnable body earns the exemption.
in_fence = True
fence_exempt = info in ("", "rust") or info.startswith("rust,")
exempt_here = fence_exempt
if exempt_here:
if not is_comment and (s.startswith("#[") or s.startswith("#![")):
# `#[doc = "..."]` is the one attribute that IS a comment.
if s.startswith("#[doc"):
start(n)
continue
# A non-runnable fence line is prose and counts like any other.
if run_start is None:
run_start, run_len = n, 0
run_len += 1
continue
if is_comment and in_fence and fence_exempt:
inner = s.startswith("#![")
depth = s.count("[") - s.count("]")
if depth > 0:
attr_depth, attr_inner = depth, inner
continue
if inner and run_start is not None and run_len > 0:
yield run_start, run_len
run_start, run_len = None, 0
continue
if is_comment:
if run_start is None:
run_start, run_len = n, 0
run_len += 1
doc = body is not None and (body.startswith("///") or body.startswith("//!"))
text = body.lstrip("/!").strip() if body is not None else ""
if doc and text.startswith("```"):
if in_fence:
exempt_here = fence_exempt
in_fence = False
fence_exempt = False
else:
info = text[3:].strip().lower()
in_fence = True
fence_exempt = info in ("", "rust") or info.startswith("rust,")
exempt_here = fence_exempt
if exempt_here:
continue
start(n)
continue
if doc and in_fence and fence_exempt:
continue
start(n)
else:
# Fence state belongs to the comment block, not to the file: an
# unclosed ``` used to exempt every later comment in the file.
in_fence = False
fence_exempt = False
if _is_attribute(s):
continue
if run_start is not None and run_len > 0:
yield run_start, run_len
run_start, run_len = None, 0
@@ -266,6 +355,63 @@ fn c() {}
/// two
/// ```
fn d() {}
"""),
("a fence in a PLAIN // comment earns no exemption", 7, """
// ```
// one
// two
// three
// four
// five
// ```
fn f() {}
"""),
("a block comment opened after code on the same line", 5, """
fn f() {} /* one
two
three
four
five
*/
"""),
("nested block comments do not close early", 7, """
/* /* */
one
two
three
four
five
*/
fn f() {}
"""),
("a multi-line attribute does not split a run", 4, """
/// one
/// two
#[cfg(all(
unix,
target_pointer_width = "64"
))]
/// three
/// four
fn f() {}
"""),
("an attribute inside a fence does not reopen it", 5, """
/// one
/// ```
/// let x = 1;
#[inline]
/// ```
/// hidden A
/// hidden B
/// hidden C
/// hidden D
fn g() {}
"""),
("#[doc = ...] is the one attribute that is a comment", 3, """
#[doc = "one"]
#[doc = "two"]
#[doc = "three"]
fn f() {}
"""),
]
@@ -294,6 +440,31 @@ fn a() {}
// two
// three
fn b() {}
"""),
("a block comment inside a raw string is not a comment", """
pub const S: &str = r#"x"y /* not a comment
one
two
three
four
"#;
fn a() {}
"""),
("// inside a raw string is not a comment", """
pub const S: &str = r#"x"y // not a comment
// nor this
// nor this
// nor this
"#;
fn a() {}
"""),
("a crate doc and an item doc are separated by the inner attribute", """
//! crate one
//! crate two
#![allow(clippy::needless_return)]
/// item one
/// item two
pub fn go() {}
"""),
]