Skip to content

create-vale-rule: document the pitfalls that pass every local gate #170

Description

@theCodeDrift

A place to accumulate what goes wrong when authoring Vale rules, so the recipe can prevent it rather than each author rediscovering it.

The organizing principle: everything below passes verify and test. That is what makes these worth documenting. A malformed Vale rule is caught immediately; a wrong one is silent, ships green, and is discovered when someone notices the rule has never reported anything. The recipe already documents the %s interpolation trap on exactly this basis — these are the same class.

Each entry is measured against the pinned binary, not reasoned from the docs.


1. A token made of punctuation needs nonword: true

Vale wraps every token in word boundaries. An em dash is non-word on both sides, so \b—\b can never match.

extends: existence
level: error
tokens: ['—', '–']          # parses, verifies, and never fires

Adding nonword: true fixes it. Measured both ways: without it the fail/ fixture does not fire and test reports the rule as passing its own fixtures only because nothing fires anywhere.

This one bit us for real — the em-dash rule specified in #104 carries exactly this shape, so it would have shipped as a no-op.

Rule of thumb for the recipe: if a token is not made of word characters, it needs nonword: true.

2. The scope table, measured

scope prose inline code fenced block
text ✓
code ✓
[code, text] ✓ ✓
raw ✓ ✓ ✓

raw subsumes the other two, so a bare raw and [raw, code, text] behave identically.

3. scope is per-rule, and rules do not interfere

Vale assembles one config for the whole run, which makes it reasonable to assume scopes interact. They do not. Same document, same run: a default-scoped rule stayed out of a fenced block and honored a suppression comment, while a raw-scoped rule read through both.

Worth stating in the recipe explicitly, because the assumption otherwise discourages using raw at all.

4. raw and Vale's suppression comments are mutually exclusive

scope <!-- vale Style.Rule = NO --> honored
[raw, code, text] no
[code, text] yes

Under raw Vale reads the unparsed text, so a markup-level directive is invisible. A rule that reaches into fenced blocks cannot be annotated away case by case — which matters because a rule about a command needs raw (see #167). Detail in the comment on that issue.

5. Prefer collocations to bare words

A token that is a whole word will find the sense you did not mean, and the near-miss is rarely the one you predicted. #104 anticipated this for land/landed and proposed landed on as a safe narrowing. Measured, landed on fires on "the plane landed on time" — the literal sense the issue was protecting. we landed already covered the decision sense, so the broader token added only false positives.

For the recipe: when narrowing a banned word to a collocation, write the pass/ fixture from the literal sense first, then check the collocation against it.

6. Fixture design should follow the rule's subject

The recipe says a pass/ fixture must contain the phrase outside the scope being narrowed. The converse is missing and is what catches #167: when the rule's subject normally appears in code, the fail/ fixture must contain it inline, fenced, and in prose. A fixture written only as prose passes while the rule is inert against every real document.

7. How to exclude files from a rule

The recipe explains how to scope a rule in, and never how to scope it out. A second matcher with = NO works:

[**/README.md]
tskl) rule = my-rule
BasedOnStyles =
my-rule.my-rule = YES

# Test fixtures are inputs to a test suite, not documentation.
[**/test/fixtures/**/README.md]
tskl) rule = my-rule
BasedOnStyles =
my-rule.my-rule = NO

Measured working, and needed immediately: without it the first house-style rules fired on the CLI's own test fixtures, which hold deliberately wrong prose. #104 anticipates the need and does not say how.


Refs #167
Refs #104

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions