Vale 3.20.0 ships a set of features aimed at style authoring at library scale. Std, the new standard library, is built on all three and walks through each; Voices is the first package to consume them.
A rule can extend another rule
An extends value containing a dot names a rule rather than a check: the new rule starts from that rule's full definition and lays its own keys on top. Two styles can share one carefully built pattern and disagree only about message, level, or a handful of entries:
# styles/House/OxfordComma.yml
extends: Std.Punctuation.OxfordComma
message: 'Serial comma, always.'
level: errorThree lines, your name on the alert, the parent's machinery underneath — including every false-positive guard it ever gains. The parent has to be present on the StylesPath, not enabled: inheritance is a file reference, and vale sync is what puts the file there. A child can itself be extended, up to ten rules deep. The one thing you can't extend is a Vale.* built-in, which is generated at runtime and has no file.
A bare key replaces the parent's value outright. Lists and maps also take overlay edits:
extends: Direct.Hedging
message: "We don't hedge."
tokens+:
- perhaps the best answer is
tokens-:
- one could arguekey+ appends to a parent list, or merges into a parent map with the child's entries winning; key- removes entries by their source text, or the named keys from a map. Removing something the parent doesn't have is a compile error, on purpose: if upstream renames an entry your child was removing, you hear about it when the rule compiles instead of diverging silently. Writing both key and key+ in one file is refused.
A directory whose name starts with _ or . is skipped at load time but stays visible to extends, so a pattern shared by several rules can live in one file without itself becoming a rule. A fragment is validated as the chain's root, so it must carry a message — even one no alert will show.
Docs: Extending another rule
Nested rule directories
A style can organize its rules in subdirectories, and the path joins the name: Std/Grammar/PassiveVoice.yml is Std.Grammar.PassiveVoice everywhere a rule name goes — alerts, configuration, in-text comments, filters, and extends. There is no depth limit, and directories prefixed with . or _ are inert, so drafts and shared fragments can sit inside a style without loading.
Docs: Nested directories
Tune a rule's scalars from configuration
A bracketed key overrides one scalar on a rule you have enabled, keeping the rule's identity:
[*.md]
BasedOnStyles = Std
Std.Readability.SentenceLength = error
Std.Readability.SentenceLength[max] = 30The word in brackets is the key as it appears in the rule's YAML, so any scalar a rule declares is a dial. Values coerce at compile time, and an unknown parameter fails the rule's compilation rather than being silently dropped. Structural fields — tokens, swap, message — are refused with a pointer at extending the rule in a style, which is what authoring is for; [level] is refused because the classic Style.Rule = error syntax already says that.
Docs: Parameters
Changelog
- fdc4cc7 fix: report an occurrence shortfall at the scope that fell short
- d978056 feat: add an apply mode to vale fix
- 58392ff fix: honor comment directives at any indent
- fcd23e6 feat: report per-check alert counts under
--counts - 2545b00 docs: add contributor license agreement and PR comments
- 9972e93 docs: add AI-Assisted Contributions
- 33f473d ix: mask an inline element in SkippedScopes instead of dropping it
- 03e45ee feat(spell): honor Hunspell BREAK rules
- 29e67d2 fix(config): keep escaped commas in TokenIgnores and BlockIgnores
- 71b13ca fix(spell): narrow COMPOUNDMIN inside its range check
- 8bd83b5 chore: bump golang.org/x/crypto to v0.55.0
- af123f4 feat: tune a rule's scalars from configuration
- 05875a9 feat: a rule can extend another rule
- 6d97c2d feat: nested rule directories, and test cases in-source
- 5d33823 feat: satisfy negated sequence tokens at boundaries; add region exceptions
- e9caf5f Satisfy negated sequence tokens at sentence boundaries
- c62d75f fix: parse a compound stanza at a fixed width, not the target's (#1159)
- 6d22b96 refactor: other org refs
- 3f26c98 refactor: migrate org
- b8560d8 fix: read a self-closing tag as the boundary it is (#1157)