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.ini plus a single-rule style directory, run with the vale binary directly rather than through taskless check, so nothing of ours is in the path.
- Establish whether it is
substitution-specific or affects every key-based rule type, and whether regexp2 is even reached for a swap key (the fallback is what makes backreferences work at all, so a plausible cause is that substitution compiles 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.
Refs #371
Refs #386
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.tson #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:
tokensrawswapfoo(?= bar)foo(?=bar)\b(\w+) \1\bthe thethe the(the) \1the thethe thethe the(literal control)the thethe theA backreference used as a
substitutionswap 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
substitutionwrapped each key in a capture group and renumbered,\1would shift and a hand-written\2would work:(\w+) \2errors 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/swapmust peek at a non-word character, because the implicit\bis appended after the zero-width lookahead, putting the boundary between the match and the text being peeked at.rawis 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
testonly reports pass or fail (see #386). An author writing a repeated-word check as asubstitutiongets 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 anexistencerule, it cannot be asubstitution. That is a workaround, not an explanation.What to do
.vale.iniplus a single-rule style directory, run with thevalebinary directly rather than throughtaskless check, so nothing of ours is in the path.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).Refs #371
Refs #386