Accept only the braced test-item shapes this crate uses
Lead fix, folded in. The previous repair accepted any `#[cfg(test)]` item whose
first line ended in `{`, which is the same latch in another spelling: a
semicolon-terminated `static` or `const` can open a block initializer there and
close with `};`, so the exemption ran past it into the next function. Verified
by reverting the condition — the scanner returned no offender at all for a
`std::fs::write` in the function following a `LazyLock` initializer.
Only `mod` and `impl` are accepted as braced shapes, being the two the crate
actually uses. Everything else fails the guard by name rather than being
bounded by a brace that may not be the item's own.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XKzM69CHmBuDcA3qN1jFdh
This commit is contained in:
parent
c0df18f15f
commit
ef73028887
|
|
@ -315,9 +315,12 @@ const FORBIDDEN: &[&str] = &[
|
|||
/// ```
|
||||
///
|
||||
/// the first column-zero `}` is *`shipping_code`'s*, so every line of it was
|
||||
/// skipped. A semicolon-terminated item therefore exempts only itself, and any
|
||||
/// third shape — including an item header rustfmt has split across lines — is a
|
||||
/// scanner error rather than a guess.
|
||||
/// skipped. A semicolon-terminated item therefore exempts only itself. Of the
|
||||
/// braced shapes, this crate currently needs only `mod` and `impl`, so those are
|
||||
/// the only two accepted: merely ending a line in `{` is not enough, because a
|
||||
/// semicolon-terminated `static` or `const` can begin a block initializer there
|
||||
/// and close with `};`. Any other shape — including an item header rustfmt has
|
||||
/// split across lines — is a scanner error rather than a guess.
|
||||
fn scan_for_unfunnelled_calls(text: &str) -> Result<Vec<(usize, String)>, String> {
|
||||
#[derive(PartialEq)]
|
||||
enum Exempt {
|
||||
|
|
@ -361,7 +364,9 @@ fn scan_for_unfunnelled_calls(text: &str) -> Result<Vec<(usize, String)>, String
|
|||
index + 1
|
||||
));
|
||||
};
|
||||
if item.ends_with('{') {
|
||||
let item = item.trim_start();
|
||||
let recognized_braced_item = item.starts_with("mod ") || item.starts_with("impl ");
|
||||
if item.ends_with('{') && recognized_braced_item {
|
||||
exempt = Exempt::UntilUnindentedBrace;
|
||||
index = head + 1;
|
||||
continue;
|
||||
|
|
@ -493,6 +498,29 @@ fn ship() {
|
|||
"a `#[cfg(test)] use ...;` must not exempt the function that follows it"
|
||||
);
|
||||
|
||||
// A semicolon-terminated item can *start* with a line ending in `{`. It is
|
||||
// not a braced item: the closure belongs to the initializer, and the item
|
||||
// closes with `});`. Treating the first `{` as the item's delimiter latches
|
||||
// the exemption through `ship`, exactly like the single-line `use` defect
|
||||
// above. `static` is not one of the two braced shapes this crate needs, so
|
||||
// the scanner refuses to guess where it ends.
|
||||
let block_initializer = "\
|
||||
#[cfg(test)]
|
||||
static HOOK: LazyLock<()> = LazyLock::new(|| {
|
||||
setup();
|
||||
});
|
||||
|
||||
fn ship() {
|
||||
std::fs::write(p, b\"\");
|
||||
}
|
||||
";
|
||||
let reason = scan_for_unfunnelled_calls(block_initializer)
|
||||
.expect_err("a block initializer must not be mistaken for a braced test item");
|
||||
assert!(
|
||||
reason.contains("cannot bound") && reason.contains("static HOOK"),
|
||||
"the scanner must name the unsupported item rather than latch its exemption: {reason}"
|
||||
);
|
||||
|
||||
// Stacked attributes and doc comments between the trigger and the item.
|
||||
let stacked = "\
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
|
|
@ -1284,13 +1284,16 @@ and the failure is the signal that the documented assumption changed. §3.1 reco
|
|||
the "directory holding only `LOCK` is empty" special case that startup state 1 depends on.
|
||||
|
||||
**The funnel guard's exemption could still latch, and the fix is a scanner that refuses to guess.**
|
||||
Bounding a `#[cfg(test)]` exemption at the next column-zero `}` is right for a braced item and wrong
|
||||
for every other shape: after `#[cfg(test)] use crate::test_support;` the first such brace belongs to
|
||||
the *next* function, so all of it was skipped. The scanner now reads the shape of the attributed
|
||||
item — braced items are exempt to their closing brace, semicolon-terminated items exempt only
|
||||
themselves, and any third shape (including an item header rustfmt split across lines) **fails the
|
||||
guard** rather than being assumed safe. A shape the scanner cannot bound is not a shape it may
|
||||
treat as harmless.
|
||||
Bounding a `#[cfg(test)]` exemption at the next column-zero `}` is right for a recognized braced
|
||||
item and wrong for every other shape: after `#[cfg(test)] use crate::test_support;` the first such
|
||||
brace belongs to the *next* function, so all of it was skipped. A first repair still accepted
|
||||
anything whose first item line ended in `{`; that was the same latch in another spelling, because a
|
||||
semicolon-terminated `static` or `const` can begin a block initializer there and close with `};`.
|
||||
The scanner now accepts only the two braced top-level shapes the crate actually uses (`mod` and
|
||||
`impl`), exempts a one-line semicolon-terminated item only for that line, and **fails the guard** on
|
||||
every other shape (including a block initializer or an item header rustfmt split across lines)
|
||||
rather than assuming it is safe. A shape the scanner cannot bound is not a shape it may treat as
|
||||
harmless.
|
||||
|
||||
It is also now a function over `&str` with synthetic tests, which is the load-bearing half. Mutating
|
||||
real sources only ever probes the shapes those sources happen to contain: no file in the crate has a
|
||||
|
|
|
|||
Loading…
Reference in New Issue