Skip to content

Vale: a backreference in a substitution swap key is silently inert #392

Description

@thecodedrift

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    CLIRelated to the taskless CLI

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions