Repository navigation
Vale: a backreference in a substitution swap key is silently inert #392
Description
Found while measuring #371's correction on the vendored binary (Vale 3.22.0). Documented and pinned in packages/cli/test/vale-vendor-contract.test.ts on #384, but not explained, and possibly worth reporting upstream once we have a minimal reproduction.
What was measured
The same pattern, under each of the three keys, against the same document:
| pattern | tokens |
raw |
swap |
|---|---|---|---|
foo(?= bar) |
fires | fires | fires |
foo(?=bar) |
nothing | fires | nothing |
\b(\w+) \1\b |
the the |
the the |
nothing |
(the) \1 |
the the |
the the |
nothing |
the the (literal control) |
the the |
the the |
fires |
A backreference used as a substitution swap key never fires. The rule loads, the run exits clean, and stderr is empty — there is no diagnostic of any kind. The literal control in the same document does fire, which isolates the cause to the backreference rather than to the test scaffolding.
The obvious mechanism was tested and disproved. If substitution wrapped each key in a capture group and renumbered, \1 would shift and a hand-written \2 would work: (\w+) \2 errors under all three keys, so there is no renumbering. Mechanism unexplained.
The second row is a separate, milder finding and is already documented in the recipe: a trailing lookahead under tokens/swap must peek at a non-word character, because the implicit \b is appended after the zero-width lookahead, putting the boundary between the match and the text being peeked at. raw is verbatim and has no such limit.
Why it matters to us
Silent inertness is the failure mode this repository's rule tooling exists to prevent. A rule that loads, verifies, and reports nothing is indistinguishable from a rule that found nothing, and test only reports pass or fail (see #386). An author writing a repeated-word check as a substitution gets a rule that looks healthy and never fires.
agent create-vale-rule (topic v13) now states the constraint: a repeated-word check has to be an existence rule, it cannot be a substitution. That is a workaround, not an explanation.
What to do
- Build a minimal reproduction outside this repository: a bare
.vale.iniplus a single-rule style directory, run with thevalebinary directly rather than throughtaskless check, so nothing of ours is in the path. - Establish whether it is
substitution-specific or affects every key-based rule type, and whetherregexp2is even reached for a swap key (the fallback is what makes backreferences work at all, so a plausible cause is thatsubstitutioncompiles keys through a path that never falls back). - Confirm against a current upstream Vale, not just our vendored 3.22.0, before reporting.
- If it reproduces cleanly, file upstream with the measurement table and link it back here.
- If it turns out to be documented upstream behaviour, say so here and leave the recipe's constraint in place.
Activity