# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
This site has an agentic ask interface you may use to query the documentation dynamically by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.vale.sh/index.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

---

# Introduction

Learn about what Vale is (and isn't).

Vale is a command-line tool that brings code-like linting to prose. Vale is cross-platform (Windows, macOS, and Linux), written in Go, and available on GitHub.

> *Linting* is the process of ensuring that written work (source code or prose) adheres to a particular style—for example, Python’s [PEP 8](https://www.python.org/dev/peps/pep-0008/) style guide (code) or the Google’s [Documentation Style Guide](https://developers.google.com/style/) (prose).

Before getting into the details of what makes Vale useful, there’s one point that needs clarification: **Vale is not a general-purpose writing aid**.

It doesn’t teach you *how* to write; it’s a tool *for* writers.

More specifically, Vale focuses (primarily) on the style of writing rather than its grammatical correctness—making it fundamentally different from, for example, Grammarly.

![Two authors each use writing tools of their own choosing, producing drafts that Vale then checks against one shared standard.](https://2533404145-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEMH7s0sC5N9L8xSMXGwW%2Fuploads%2Fgit-blob-5cece43d28c2ae5a0f232bbda723f619053b71de%2Fflow.svg?alt=media)

In other words, Vale focuses on ensuring consistency across multiple authors (according to customizable guidelines) rather than the general “correctness” of a single author’s work.

This distinction is particularly important to understand because Vale doesn’t offer any of its own advice. Instead, it offers a framework for creating and enforcing [custom rules](/topics/styles). Its approach is much more similar to code linters than it is to traditional grammar checkers.

## [Your style, our editor](#your-style-our-editor)

One of Vale’s most important features is its ability to support external styles through its extension system, which only requires some familiarity with the YAML file format (and, optionally, regular expressions).

![Most writing software runs one fixed set of checks to produce one result. Vale runs extension points, so each style produces its own result.](https://2533404145-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEMH7s0sC5N9L8xSMXGwW%2Fuploads%2Fgit-blob-390248a9ba13b78637bbc63171138636d182340f%2Foutput.svg?alt=media)

To get a better idea of how this works, let’s look at an example from the [Linode documentation](https://github.com/linode/docs/blob/master/ci/vale/styles/Linode/Terms.yml):

{% code title="Terms.yml" %}

```yaml
# `extends` specifies the extension point you're using. Here, we're
# using `substitution` to ensure correct usage of some technical and
# brand-specifc terminology.
extends: substitution
# `message` allows you to customize the output shown to your users.
message: Use '%s' instead of '%s'.
# We're setting this rule's severity to `error`, which will cause
# CI builds to fail.
level: error
# We're using case-insensitive patterns.
ignorecase: true
swap:
  "(?:LetsEncrypt|Let's Encrypt)": Let's Encrypt
  'node[.]?js': Node.js
  'Post?gr?e(?:SQL)': PostgreSQL
  'java[ -]?scripts?': JavaScript
  linode cli: Linode CLI
  linode manager: Linode Manager
  linode: Linode
  longview: Longview
  nodebalancer: NodeBalancer
```

{% endcode %}

In the above example, we’ve defined a few terms that have a particular capitalization style. If Vale finds an instance of a term that matches a pattern on the left of swap (case-insensitive) but doesn’t exactly match the value on the right, it issues an error. So, for example, `Nodebalancer`, `nodebalancer` or any other variation that doesn’t exactly match `NodeBalancer` will be flagged as an error.

While this example may appear quite simple, it’s possible to achieve fairly high coverage on complete editorial style guides. Check out the [Explorer](https://vale.sh/explorer) for more examples.

## [Syntax- and context-aware linting](#syntax--and-context-aware-linting)

Another feature that separates Vale from other linters is its ability to understand its input at both a syntactic and contextual level.

![A Markdown file with each region labelled by the scope Vale assigns it: heading, link text, list item, and a fenced code block that is skipped.](https://2533404145-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEMH7s0sC5N9L8xSMXGwW%2Fuploads%2Fgit-blob-0fe1857f97698a8478b378ca1a9ae494b4f0fa0b%2Fsyntax.svg?alt=media)

This level of understanding gives you fine-grained control over the linting process, including the ability to limit rules to certain sections (e.g., only headings) or ignore sections entirely (block and inline code are ignored by default).

Additionally, since Vale is built on top of an NLP library, you can also target specific segments of text—allowing you to, for example, warn about paragraphs that exceed a certain number of words or sentences that end with prepositions.

## [Tech stack](#tech-stack)

Vale is a 100% open-source, MIT-licensed project that consists of multiple parts:

| Name                                                     | Tech       | Info                                                                                                         |
| -------------------------------------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------ |
| [`vale`](https://github.com/vale-cli/vale)               | Go         | The main repository containing the Vale command-line interface.                                              |
| [`vale-ls`](https://github.com/vale-cli/vale-ls)         | Rust       | An implementation of the Language Server Protocol (LSP) for the Vale command-line tool.                      |
| [`vale.sh`](https://github.com/vale-cli/vale.sh)         | Svelte     | Website and documentation for the Vale CLI and related projects.                                             |
| [`vale-action`](https://github.com/vale-cli/vale-action) | TypeScript | The official GitHub Action for Vale -- install, manage, and run Vale with ease.                              |
| [`packages`](https://github.com/vale-cli/packages)       | YAML       | A collection of pre-packaged, Vale-compatible style guides and configurations.                               |
| [`vale-native`](https://github.com/vale-cli/vale-native) | Go         | A native messaging host for the Vale CLI: Use your local configurations in Chrome, Firefox, Opera, and Edge. |


# Quickstart

Get Vale running on your project in about five minutes.

This walks through a first working setup. Each step links to the page that covers it properly, so read on where you want the detail.

## [1. Install Vale](#1-install-vale)

Use your package manager—`brew install vale`, `choco install vale`, or one of the others on the [Installation](/topics/installation) page—then check it worked:

```bash
$ vale --version
```

## [2. Create a `.vale.ini`](#2-create-a-valeini)

Vale doesn't ship with opinions of its own. It needs a configuration file saying where to keep styles and which to apply, so create one in the root of your project:

{% code title=".vale.ini" %}

```ini
StylesPath = styles
MinAlertLevel = suggestion

Packages = Microsoft

[*.md]
BasedOnStyles = Vale, Microsoft
```

{% endcode %}

Four things are happening:

* `StylesPath` is the folder Vale will keep downloaded styles in. Add it to your `.gitignore`.
* `MinAlertLevel` is the lowest severity worth reporting.
* `Packages` names what to download—here, Microsoft's writing style guide.
* `[*.md]` applies the styles that follow it to Markdown files. `Vale` is built in; `Microsoft` is the package.

See [.vale.ini](/topics/.vale.ini) for everything the file accepts.

## [3. Download the styles](#3-download-the-styles)

```bash
$ vale sync
```

```
 SUCCESS  Synced 1 package(s) to 'styles'.
```

Run this again whenever you change `Packages`. See [Packages](/keys/packages) for other ways to name one, including a URL or a local path.

## [4. Lint something](#4-lint-something)

```bash
$ vale README.md
```

```
 README.md
 3:1   suggestion  Consider using 'to' instead of 'In order to'.  Microsoft.Wordiness
 3:13  suggestion  Consider using 'use' instead of 'utilize'.     Microsoft.Wordiness

✔ 0 errors, 0 warnings and 2 suggestions in 1 file.
```

You can pass a directory instead of a file, or a glob:

```bash
$ vale docs/
$ vale --glob='*.md' .
```

## [5. Read the output](#5-read-the-output)

Each line is one alert:

```
 3:13  suggestion  Consider using 'use' instead of 'utilize'.     Microsoft.Wordiness
 └─┬─┘ └────┬───┘  └──────────────────┬──────────────────────┘    └────────┬────────┘
 line:col  severity                 message                            rule name
```

The rule name is the useful part: it's `<style>.<rule>`, and it's how you turn one off. To silence that rule for Markdown, name it in your config:

```ini
[*.md]
BasedOnStyles = Vale, Microsoft

Microsoft.Wordiness = NO
```

Severity matters for automation. **Only `error` sets a non-zero exit code**, so a CI job fails on errors and passes with warnings and suggestions:

```bash
$ vale README.md   # 2 suggestions
$ echo $?
0
```

See [MinAlertLevel](/keys/minalertlevel) for changing what gets reported, and [BasedOnStyles](/keys/basedonstyles) for enabling and disabling rules.

## [Where to go next](#where-to-go-next)

* Browse the [Package Explorer](https://vale.sh/explorer) for styles beyond Microsoft.
* Write your own rules with [Styles](/topics/styles) and the [checks](/checks/existence) reference.
* Narrow what Vale reads using [Scopes](/topics/scopes).
* Put an assistant to work, and hold it to a style, with the [Agents](/guides/agents) guide.


# Installation

Get started with Vale in just a few minutes.

## [Pick your platform](#pick-your-platform)

The recommended approach on every platform is a package manager: it puts `vale` on your `$PATH` and keeps you up to date with new releases.

{% tabs %}
{% tab title="macOS" %}
[Homebrew](https://formulae.brew.sh/formula/vale) tracks new releases closely:

```bash
$ brew install vale
```

[MacPorts](https://ports.macports.org/port/vale/) also packages Vale, and may lag behind releases.
{% endtab %}

{% tab title="Windows" %}
[Chocolatey](https://chocolatey.org/packages/vale):

```powershell
> choco install vale
```

[Scoop](https://scoop.sh/#/apps?q=vale):

```powershell
> scoop install vale
```

[winget](https://winstall.app/apps/errata-ai.Vale):

```powershell
> winget install -e --id errata-ai.Vale
```

{% endtab %}

{% tab title="Linux" %}
On Debian and Ubuntu, the [pkg.haus](https://pkg.haus) APT archive ships Vale for stable, testing, and unstable (amd64 and arm64), built from source at release tags. Set up the archive per the instructions on [pkg.haus](https://pkg.haus), then:

```bash
$ sudo apt install vale
```

On Arch Linux, Vale is in the official repositories:

```bash
$ sudo pacman -S vale
```

[Snapcraft](https://snapcraft.io/vale) works across distributions:

```bash
$ sudo snap install vale
```

Many other distributions—Alpine, openSUSE, Void, and more—package Vale in their own repositories; see [the full list](https://repology.org/project/vale/versions).
{% endtab %}

{% tab title="FreeBSD" %}
Vale is in the ports collection as [`textproc/vale`](https://www.freshports.org/textproc/vale/):

```bash
$ pkg install vale
```

There are no official FreeBSD binaries on the releases page, so the port is also the answer for build-from-source setups.
{% endtab %}
{% endtabs %}

## [Installing Vale with a project](#installing-vale-with-a-project)

A system-wide install leaves each contributor on whatever version they happened to get, and your CI on another. Declaring Vale in the project instead pins one version for everyone—which matters because a new release can add rules or change what an existing one matches.

[mise](https://mise-versions.jdx.dev/tools/vale) does this for any project, whatever it's written in:

```bash
$ mise use vale@3.25.1
```

If your project already installs its tools through a language's package manager, Vale is packaged there too. Each of these downloads the same release binaries and puts `vale` on your `$PATH`:

{% tabs %}
{% tab title="npm" %}

```bash
$ npm install --save-dev @vvago/vale
```

Adds [`@vvago/vale`](https://www.npmjs.com/package/@vvago/vale) to `devDependencies`; run it with `npx vale`.
{% endtab %}

{% tab title="PyPI" %}

```bash
$ pip install vale
```

Installs [`vale`](https://pypi.org/project/vale/) into the active environment; pin it in `requirements.txt` or your `pyproject.toml`.
{% endtab %}

{% tab title="conda" %}

```bash
$ conda install conda-forge::vale
```

Installs [`conda-forge/vale`](https://anaconda.org/conda-forge/vale) into the active environment, or list it under `dependencies` in `environment.yml`.
{% endtab %}
{% endtabs %}

For linting in CI, the [Vale GitHub Action](https://github.com/vale-cli/vale-action) installs and runs Vale in one step, and Vale can also run as a [pre-commit hook](/integrations/pre-commit).

## [GitHub Releases](#github-releases)

[Archives of precompiled binaries](https://github.com/vale-cli/vale/releases) are available for Windows, macOS, and Linux (amd64 and arm64). Download the archive for your platform, extract it, and (optionally) add the extracted directory to your `$PATH`.

## [Building from source](#building-from-source)

Vale is a Go program, so `go install` builds it for any platform Go supports—including those without a release archive:

```bash
$ go install github.com/vale-cli/vale/v3/cmd/vale@latest
```

This needs Go 1.25.7 or later and a C compiler, since Vale's source-code parsers are built through cgo. A binary built this way reports its version as `master` rather than the release number.

## [Docker](#docker)

Vale is available on Docker Hub at [jdkato/vale](https://hub.docker.com/r/jdkato/vale):

```bash
$ docker pull jdkato/vale
```

Vale requires three components: a `.vale.ini` config file, a `StylesPath` directory (specified in the config file), and a document or directory to lint.

Here's an example of calling Vale with locally-defined components (assuming `$(pwd)/fixtures/styles/demo` contains a config file):

```bash
$ docker run --rm \
             -v $(pwd)/styles:/styles \
             -v $(pwd)/fixtures/styles/demo:/docs \
             -w /docs \
             jdkato/vale .
```

By default, the image supports HTML, Markdown, AsciiDoc, and reStructuredText content. If you need support for DITA as well, you'll need to add the relevant dependencies—for example,

```dockerfile
# Choose a version to pin:
FROM jdkato/vale:v3.25.1

# Copy a local installation of the DITA Open Toolkit:
COPY bin/dita-ot-3.6 /
ENV PATH="/dita-ot-3.6/bin:$PATH"

ENTRYPOINT ["/bin/vale"]
```

## [Keeping Vale up to date](#keeping-vale-up-to-date)

A system package manager updates Vale along with everything else it installed. A version pinned in a project stays put until someone moves it, which is what dependency-update bots are for. None of them know anything about Vale: they read the file where you wrote a version number, ask the registry that file points at whether a newer one exists, and open a pull request that changes the number. The pull request's CI run is the useful part. A new release can add rules to a package or change what an existing rule matches, so a red build on the bump shows the new alerts before anyone else sees them.

### [Renovate](#renovate)

[Renovate](https://github.com/renovatebot/renovate) runs either as the hosted Mend app for GitHub or self-hosted from an npm package, a Docker image, or its own GitHub Action, and it supports GitHub, GitLab, Bitbucket, Azure DevOps, and Forgejo. Once installed, it opens a first pull request that adds a `renovate.json` and lists everything it found; merging that turns it on.

From then on, each run scans the repository for the manifest files its *managers* recognize, looks each dependency up in the registry it belongs to, and opens one pull request per available update. Every way of pinning Vale from the previous section maps to a manager:

| Where Vale is pinned      | Manager            | Looks up versions in                          |
| ------------------------- | ------------------ | --------------------------------------------- |
| `mise.toml`               | `mise`             | Vale's GitHub releases, via the mise registry |
| `package.json`            | `npm`              | npm                                           |
| `requirements.txt`        | `pip_requirements` | PyPI                                          |
| `pyproject.toml`          | `pep621`           | PyPI                                          |
| `Dockerfile`              | `dockerfile`       | Docker Hub                                    |
| `.github/workflows/*.yml` | `github-actions`   | the action's tags                             |
| `.pre-commit-config.yaml` | `pre-commit`       | Vale's GitHub tags                            |

All of these are on by default except `pre-commit`, which Renovate leaves off because hook revisions are often deliberately old. A minimal `renovate.json` that enables it:

```json
{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": ["config:recommended", ":enablePreCommit"]
}
```

Two pins need a number to bump. A `FROM jdkato/vale` line with no tag floats, so Renovate has nothing to update; pin it to `jdkato/vale:v3.20.0` as in the Docker example above. And the Vale GitHub Action's `version` input defaults to `latest`: Renovate bumps the action's own reference, but a pinned `version: 3.20.0` inside a workflow is plain YAML to it and needs a [custom manager](https://docs.renovatebot.com/modules/manager/regex/) to be tracked.

### [Dependabot](#dependabot)

[Dependabot](https://docs.github.com/en/code-security/dependabot) is built into GitHub, so there is nothing to install. It reads one file, `.github/dependabot.yml`, and each `updates` entry names a package ecosystem, the directory holding its manifest, and how often to check. Every entry is checked independently and opens its own pull requests.

| Where Vale is pinned                   | `package-ecosystem` |
| -------------------------------------- | ------------------- |
| `package.json`                         | `npm`               |
| `requirements.txt` or `pyproject.toml` | `pip`               |
| `environment.yml`                      | `conda`             |
| `Dockerfile`                           | `docker`            |
| `.github/workflows/*.yml`              | `github-actions`    |

A config that covers an npm-managed project with a Docker image and the Vale action:

```yaml
version: 2
updates:
  - package-ecosystem: npm
    directory: /
    schedule:
      interval: weekly
  - package-ecosystem: docker
    directory: /
    schedule:
      interval: weekly
  - package-ecosystem: github-actions
    directory: /
    schedule:
      interval: weekly
```

Dependabot has no ecosystem for mise or pre-commit, so a `mise.toml` pin or a `.pre-commit-config.yaml` revision stays where it is. For pre-commit, [pre-commit.ci](https://pre-commit.ci) fills the gap with a weekly pull request that bumps every hook's `rev`. The same tag rule applies as with Renovate: an untagged `FROM jdkato/vale` is never updated.


# .vale.ini

Learn how a configuration file is shaped, where Vale finds it, and how several of them combine.

## [The file](#the-file)

A configuration is an INI file with three kinds of section. Keys above the first section header apply to the run as a whole. `[formats]` maps file extensions Vale does not know onto ones it does. Every other section header is a glob, and its keys apply to the files that match:

```ini
StylesPath = styles
MinAlertLevel = suggestion
Packages = Microsoft

[formats]
mdx = md

[*]
BasedOnStyles = Vale

[*.{md,mdx}]
BasedOnStyles = Vale, Microsoft
Microsoft.Contractions = NO
```

The [generator](https://vale.sh/generator) writes a starting file, and the View file beside it when a data format is chosen. After that, `vale sync` installs whatever `Packages` names, and `vale ls-config` prints the configuration a run would use, with every source resolved.

Each key has one home. A core key under a section, or a section key above the first header, is an error that names the key. A key Vale does not read at all is a warning and is otherwise ignored, so a URL left on a line of its own is noticed rather than silently parsed as a key named `https`.

## [Core settings](#core-settings)

| Name                                   | Type       | Description                                                                                                    |
| -------------------------------------- | ---------- | -------------------------------------------------------------------------------------------------------------- |
| [StylesPath](/keys/stylespath)         | `string`   | The directory holding styles and the `config/` assets. Relative to the file that sets it, and it has to exist. |
| [Packages](/keys/packages)             | `string[]` | What `vale sync` installs into the `StylesPath`.                                                               |
| [Vocab](/keys/vocabularies)            | `string[]` | Vocabularies to load from `config/vocabularies`. Each must be there.                                           |
| [MinAlertLevel](/keys/minalertlevel)   | `enum`     | `suggestion`, `warning`, or `error`. The `--minAlertLevel` flag overrides it.                                  |
| [IgnoredScopes](/keys/ignoredscopes)   | `string[]` | Inline HTML elements whose text is skipped.                                                                    |
| [SkippedScopes](/keys/skippedscopes)   | `string[]` | Block HTML elements whose text is skipped.                                                                     |
| [IgnoredClasses](/keys/ignoredclasses) | `string[]` | HTML classes whose elements are skipped.                                                                       |
| [WordTemplate](/keys/wordtemplate)     | `string`   | The pattern a word-level token is wrapped in, for languages where `\b` is the wrong boundary.                  |

## [Format associations](#format-associations)

`[formats]` tells Vale to read one extension as another:

```ini
[formats]
mdx = md
```

The association changes how a file is parsed, not what it is called: sections still match the name on disk, so the files above need a `[*.mdx]` section, or one whose glob covers both. Mapping a code or data extension onto a markup one lints the markup embedded in that code or data, such as the Markdown in a language's doc comments. Mapping an extension Vale doesn't know onto a code format parses the file with that language's grammar (Vale v3.25.0 or later), so it's for files that are that language under another extension, not for a language Vale doesn't support.

A key may also be a file name or a glob, for a file that has no extension to map:

```ini
[formats]
COMMIT_EDITMSG = md
Makefile = rst
notes/*.txt = md

[COMMIT_EDITMSG]
BasedOnStyles = Vale
```

The extension is looked up first; then the keys are tried in order, a bare name against the file's base name and a glob against the base name and the path. Naming a file or a glob requires Vale v3.22.0 or later.

A second named section, `[asciidoctor]`, holds attributes passed to Asciidoctor when it renders `.adoc` files. See [AsciiDoc](/formats/asciidoc).

A third, `[sphinx]`, names the directives and roles a Sphinx project's extensions add, so their text is read the right way in reStructuredText and MyST alike. See [reStructuredText](/formats/restructuredtext#directives-and-roles).

## [Sections](#sections)

A section header is a glob matched against a file's path as Vale was given it. `[*]` is every file, `[*.{md,rst}]` is two extensions, and `[docs/src/*.md]` is one part of the project when Vale runs from its root; see [Globbing](/guides/globbing). Sections are read in the order written, and a file that matches more than one takes all of them; see [A file in several sections](#a-file-in-several-sections).

| Name                                         | Type        | Description                                                                                             |
| -------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------- |
| [BasedOnStyles](/keys/basedonstyles)         | `string[]`  | The styles whose rules run. A later matching section replaces the list rather than adding to it.        |
| [BlockIgnores](/keys/blockignores)           | `string[]`  | Patterns whose matches are removed before parsing, for block-level markup Vale does not know.           |
| [TokenIgnores](/keys/tokenignores)           | `string[]`  | Patterns whose matches are removed before linting, for inline markup Vale does not know.                |
| [CommentDelimiters](/keys/commentdelimiters) | `string[2]` | The pair that opens and closes a comment, so in-text directives work in a format without HTML comments. |
| [Transform](/keys/transform)                 | `string`    | An XSLT 1.0 stylesheet applied to XML before linting.                                                   |
| [View](/keys/view)                           | `string`    | A view from `config/views` that extracts the prose from a code, data, or plain-text file.               |

A comma inside a `BlockIgnores` or `TokenIgnores` pattern is written `\,`, so a quantifier like `{2,}` survives the split.

### [Rules](#rules)

Any other key under a section names a rule, `Style.Rule`, and its value switches the rule or sets its level:

```ini
[*.md]
BasedOnStyles = Vale, Microsoft

Microsoft.Contractions = NO       # off
Microsoft.HeadingPunctuation = error
House.Hedging = YES               # on, even if House is not in BasedOnStyles
```

A level set in one section holds for that section's files only, so a rule can be an error in Markdown and a warning in HTML. A rule named on its own, without its style in `BasedOnStyles`, is loaded from its file.

A bracket key adjusts one of the rule's parameters, and holds wherever the rule runs:

```ini
Std.SentenceLength[max] = 30
```

It takes the scalars a rule reads: a number, a boolean, a level name. The keys that make a rule what it is, such as `message`, `tokens`, `swap`, and `exceptions`, are refused, since changing those is authoring and belongs in a style that [extends](/topics/styles#extending-another-rule) the rule.

### [A file in several sections](#a-file-in-several-sections)

A file matches every section whose glob fits it, and each one applies, in the order written. `guide.fr.md` matches both `[*.md]` and `[*.fr.md]`, so it takes the first section's settings and then the second's:

```ini
[*.md]
BasedOnStyles = Google

[*.fr.md]
Google.Spelling = NO
Google.Exclamation = NO
```

Every Markdown file runs the Google style. A French one runs it without those two rules, and nothing in `[*.fr.md]` repeats what `[*.md]` set. When both sections set the same key, the later one holds for that file: a rule at `error` in `[*.md]` and `suggestion` in `[*.fr.md]` is a suggestion in French files and an error elsewhere.

`BasedOnStyles` is the one key that replaces rather than adjusts. A `[*.fr.md]` that names its own styles runs those alone, so leave it out of a section that only means to adjust. `[*]` is the base under every section, wherever in the file it is written.

### [Conditional sections](#conditional-sections)

{% hint style="info" %}
Requires Vale v3.24.0 or later.
{% endhint %}

A header can filter the files its glob matches, the way a [list comprehension](https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions)'s `if` does. The section applies only where the [expression](https://expr-lang.org/docs/language-definition) after `if` holds:

```ini
[*.md]
BasedOnStyles = Vale, House

[*.md if .Meta.published == false]
BasedOnStyles = NONE
```

[`NONE`](/keys/basedonstyles#starting-over) runs nothing, so unpublished posts are skipped and never read. A condition can adjust instead of skipping, and every section key follows it:

```ini
[*.md if .Meta.draft]
House.Passive = NO
BlockIgnores = (?s)(<!-- notes -->.*?<!-- /notes -->)
```

The expression is written the way a [filter](/topics/filters) is, so the fields are capitalized and start with a dot:

| Field   | Example for `docs/_posts/draft.md`                                 |
| ------- | ------------------------------------------------------------------ |
| `.Path` | `"docs/_posts/draft.md"`, relative to the project root             |
| `.Dir`  | `"docs/_posts"`                                                    |
| `.Name` | `"draft.md"`                                                       |
| `.Ext`  | `".md"`                                                            |
| `.Meta` | The file's [front matter](/formats/front-matter), keyed as written |

`.Meta` holds YAML, TOML, or JSON front matter, and its keys keep their own case. A field a file doesn't have is `nil`, so `.Meta.published == false` is false for a file with no `published` key, and `.Meta.draft` on its own reads as false.

```ini
[*.md if .Meta.published == false or .Meta.draft]
[*.md if "internal" in .Meta.tags]
[*.md if .Name startsWith "_"]
[docs/*.md if .Path startsWith "docs/archive/" and .Meta.layout != "page"]
```

A conditional section is placed in order like any other, so write it after the sections it adjusts. An expression that doesn't compile is a configuration error, reported before any file is read.

## [Search](#search)

A run finds its configuration in this order and uses the first that applies:

1. The file named by `--config`.
2. The file named by `VALE_CONFIG_PATH`.
3. The first of `.vale.ini`, `_vale.ini`, `vale.ini`, `.vale`, `_vale`, or `.config/vale.ini` in the working directory or a directory above it, then in the home directory. A root-level file is read before one in `.config/`. The directory the file was found in is the project root, so a `.config/vale.ini` makes the directory holding `.config` the root while its own relative paths, such as `StylesPath`, still resolve against `.config/`. A file named by `--config` or `VALE_CONFIG_PATH` makes its own directory the root.

![Vale looks for a configuration file in this order: the --config option, then VALE\_CONFIG\_PATH, then a .vale.ini searched for from the working directory upwards. The global configuration is always loaded as well, and is read last so it can override the others.](https://2533404145-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEMH7s0sC5N9L8xSMXGwW%2Fuploads%2Fgit-blob-e6fee9d42459608550252a0a9f35984b7fdab764%2Fconfig-search.svg?alt=media)

Whichever one is found, the user-level file is read too, underneath it. It lives in the operating system's configuration directory, and `vale ls-dirs` prints the path:

| OS      | Location                                           |
| ------- | -------------------------------------------------- |
| Windows | `%LOCALAPPDATA%\vale\.vale.ini`                    |
| macOS   | `$HOME/Library/Application Support/vale/.vale.ini` |
| Linux   | `$XDG_CONFIG_HOME/vale/.vale.ini`                  |

That file is the place for preferences that hold across every project, and `--no-global` leaves it out of a run. When no project file is found, it is the configuration.

## [Layering](#layering)

When more than one file is read, the user-level file comes first and the project's file is laid over it, so where both set a single value, the project's holds: `MinAlertLevel` reads the last one set. A list key gathers every value from every file, in reading order and without duplicates, so a `BasedOnStyles` in the user-level file adds styles to a project's. `StylesPath` is both: each file's path is added, resolved against that file, and the project's is the one new assets are written to.

![The user-level file and the project file are laid over one another: each file's StylesPath is kept, the project's MinAlertLevel wins, and the two BasedOnStyles lists join in reading order.](https://2533404145-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEMH7s0sC5N9L8xSMXGwW%2Fuploads%2Fgit-blob-3e11385d3a4f929097ed11849f70b78138f34a11%2Fconfig-layering.svg?alt=media)

```ini
# ~/Library/Application Support/vale/.vale.ini
StylesPath = styles
MinAlertLevel = suggestion

[*.md]
BasedOnStyles = write-good
```

Under a project file that names `ProjectStyle` at `MinAlertLevel = error`, this runs both styles and reports errors: the list joined, and the project's level held.

`vale sync` is the exception. It reads exactly one file, the one the search finds, and installs that file's `Packages`. To install what the user-level file names, point the command at it with `--config`.


# CLI

Learn how the `vale` command reads its input, finds its configuration, and reports.

## [Input](#input)

`vale` takes files, directories, or nothing:

```sh
vale README.md docs/          # named files and directories, recursively
cat draft.md | vale --ext=.md # standard input
vale "Some text to check."    # a string that is not a path
```

A directory is walked for every file its configuration has a section for, narrowed by `--glob` when one is given; see [Globbing](/guides/globbing). Input on stdin is read as plain text unless `--ext` names a format, or `--path` names the file it came from, which also puts it under that path's configuration section. An argument that is neither a file nor a directory is linted as text, so a mistyped path that is nearly a command name is reported rather than checked.

With no input and nothing on stdin, `vale` prints a short introduction and an example configuration.

## [Configuration](#configuration)

A run needs a configuration file, found in this order:

1. The file named by `--config`.
2. The file named by `VALE_CONFIG_PATH`.
3. The first of `.vale.ini`, `_vale.ini`, `vale.ini`, `.vale`, or `_vale` found in the working directory or any directory above it, then in the home directory.

Whichever one is found, the user-level configuration in the operating system's config directory is read too, underneath it, so preferences that hold across projects live in one place and a project's own settings win. `vale ls-dirs` prints where that is, and `--no-global` leaves it out of a run.

The same two environment variables `ls-vars` reports are all Vale reads:

| Variable           | Description                                                                             |
| ------------------ | --------------------------------------------------------------------------------------- |
| `VALE_CONFIG_PATH` | A configuration file to use instead of searching for one.                               |
| `VALE_STYLES_PATH` | The default [`StylesPath`](/keys/stylespath), used when no configuration file sets one. |

## [Commands](#commands)

| Name         | Description                                                                                                                                                                                                                     |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `sync`       | <p>Install every entry in the configuration's <a href="/keys/packages"><code>Packages</code></a> key into the <code>StylesPath</code>. Run it after adding a package, and in CI before linting.<br><code>$ vale sync</code></p> |
| `test`       | <p>Run the test cases kept beside your rules, and report the ones that failed. See <a href="/topics/testing">Testing</a>.<br><code>$ vale test styles</code></p>                                                                |
| `ls-config`  | <p>Resolve the configuration the way a lint run would, every source in order, and print the result as JSON. The fastest way to see which styles and rules a directory has in scope.<br><code>$ vale ls-config</code></p>        |
| `ls-metrics` | <p>Print the counts Vale computed while reading a file, as JSON: words, sentences, paragraphs, and the elements of its markup. See <a href="/checks/metric">metric</a>.<br><code>$ vale ls-metrics README.md</code></p>         |
| `ls-dirs`    | <p>Print where Vale looks for the default <code>StylesPath</code>, the user-level configuration, and the native messaging host, and whether each is there.<br><code>$ vale ls-dirs</code></p>                                   |
| `ls-vars`    | <p>Print the environment variables Vale reads, what each does, and its current value.<br><code>$ vale ls-vars</code></p>                                                                                                        |
| `help`       | <p>Print the listing of options and commands, or one command's own help.<br><code>$ vale help sync</code></p>                                                                                                                   |

`ls-config`, `ls-dirs`, and `ls-vars` print JSON instead of a table under `--output=JSON`. The tables look like this on macOS:

```console
$ vale ls-dirs
Asset       | Default Location                                              | Found
StylesPath  | /Users/me/Library/Application Support/vale/styles             | ✓
.vale.ini   | /Users/me/Library/Application Support/vale/.vale.ini          | ✗
vale-native | /Users/me/Library/Application Support/vale/native/vale-native | ✗

$ vale ls-vars
Variable         | Description                                                         | Value
VALE_CONFIG_PATH | Override the default search process by specifying a .vale.ini file. | ✗
VALE_STYLES_PATH | Specify the location of the default StylesPath.                     | ✗
```

## [Options](#options)

| Name               | Description                                                                                                                                                                                                                                                 |                                      |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------ |
| `--config`         | <p>The configuration file to use, instead of searching for one.<br><code>$ vale --config='path/to/.vale.ini' README.md</code></p>                                                                                                                           |                                      |
| `--output`         | <p>The report format: <code>CLI</code>, <code>line</code>, <code>JSON</code>, or the path of a template. See <a href="#output">Output</a>.<br><code>$ vale --output=JSON README.md</code></p>                                                               |                                      |
| `--minAlertLevel`  | <p>The lowest level to report: <code>suggestion</code>, <code>warning</code>, or <code>error</code>. Overrides the configuration's <a href="/keys/minalertlevel"><code>MinAlertLevel</code></a>.<br><code>$ vale --minAlertLevel=error README.md</code></p> |                                      |
| `--glob`           | <p>A pattern the files under a directory must match. See <a href="/guides/globbing">Globbing</a>.<br><code>$ vale --glob='\*.md' docs/</code></p>                                                                                                           |                                      |
| `--filter`         | <p>An expression choosing which rules run. See <a href="/topics/filters">Filters</a>.<br><code>$ vale --filter='.Level == "error"' README.md</code></p>                                                                                                     |                                      |
| `--ext`            | <p>The format to read stdin as, by file extension.<br><code>$ echo "</code><em><code>This</code></em><code> is Markdown"                                                                                                                                    | vale --ext=.md</code></p>            |
| `--path`           | <p>The file stdin stands for, so its configuration section and format apply and its path appears in the report.<br><code>$ cat draft.md                                                                                                                     | vale --path=docs/draft.md</code></p> |
| `--ignore-syntax`  | <p>Read every file as plain text, line by line, with no markup parsing.<br><code>$ vale --ignore-syntax README.md</code></p>                                                                                                                                |                                      |
| `--no-exit`        | <p>Exit 0 even when errors were found.<br><code>$ vale --no-exit README.md</code></p>                                                                                                                                                                       |                                      |
| `--no-wrap`        | <p>Do not wrap long messages in the <code>CLI</code> format.<br><code>$ vale --no-wrap README.md</code></p>                                                                                                                                                 |                                      |
| `--no-color`       | <p>Do not color the <code>CLI</code> format.<br><code>$ vale --no-color README.md</code></p>                                                                                                                                                                |                                      |
| `--no-global`      | <p>Do not read the user-level configuration.<br><code>$ vale --no-global README.md</code></p>                                                                                                                                                               |                                      |
| `--plain-progress` | <p>Log each step of a long operation instead of redrawing a progress bar, for CI logs that keep every frame.<br><code>$ vale sync --plain-progress</code></p>                                                                                               |                                      |
| `--coverage`       | <p>With <code>vale test</code>, fail when a rule produced no alert in any case. See <a href="/topics/testing#coverage">Coverage</a>.<br><code>$ vale test --coverage styles</code></p>                                                                      |                                      |
| `--version`, `-v`  | <p>Print the version.<br><code>$ vale --version</code></p>                                                                                                                                                                                                  |                                      |
| `--help`, `-h`     | <p>Print the listing of options and commands. After a command, that command's help.<br><code>$ vale sync --help</code></p>                                                                                                                                  |                                      |

## [Output](#output)

`--output` chooses how alerts are reported. The built-in formats sort them by line and column within each file.

**`CLI`**, the default, is a colored table per file with a summary line, wrapped to the terminal's width unless `--no-wrap` says otherwise:

![Vale's default output for a README: a table with one row per alert giving the line and column, the level, the message, and the rule, then a summary line counting one error, two warnings, and no suggestions in one file.](https://2533404145-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEMH7s0sC5N9L8xSMXGwW%2Fuploads%2Fgit-blob-9802fad7a5279ad094141722ec8cddbd08cad05d%2Fcli-output.svg?alt=media)

Each row is the line and column, the level, the message, and the rule that fired. The summary counts every alert at or above `MinAlertLevel`, and the exit code follows the errors.

**`line`** is one alert per line, in the shape editors and scripts parse:

```console
$ vale --output=line README.md
README.md:3:17:write-good.Weasel:'very' is a weasel word!
README.md:3:37:write-good.Passive:'were fixed' may be passive voice. Use active voice if you can.
README.md:6:23:Vale.Spelling:Did you really mean 'detials'?
```

**`JSON`** is an object keyed by file path, each holding its alerts. An alert carries the rule, the level, the matched text, its line and span, the message, and any [action](/topics/actions) with what it resolves to:

```json
{
	"README.md": [
		{
			"Action": { "Name": "replace", "Params": ["use"] },
			"Suggestions": ["use"],
			"Span": [9, 15],
			"Check": "House.Terms",
			"Description": "",
			"Link": "",
			"Message": "Use 'use' instead of 'utilize'.",
			"Severity": "error",
			"Match": "utilize",
			"Line": 1
		}
	]
}
```

**A template** is anything else: the path of a Go template, or the name of one in the `StylesPath`'s `config/templates` directory, rendered with the same data. See [Templates](/topics/templates).

Errors in Vale's own configuration are printed to standard error, and follow `--output` where the format has a place for them: a rule that fails to load reports its file and line.

![Vale reporting an E201 configuration error, showing the offending line of the rule file and the accepted values.](https://2533404145-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEMH7s0sC5N9L8xSMXGwW%2Fuploads%2Fgit-blob-e576cced532c800be718dc51adba10655f35694d%2Ferror.svg?alt=media)

## [Exit codes](#exit-codes)

| Code | Meaning                                                                                                                                                                                                                                                                                               |
| ---- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `0`  | The run completed and found no alert at the `error` level. Warnings and suggestions do not change the code.                                                                                                                                                                                           |
| `1`  | The run completed and found at least one `error`. `--no-exit` turns this into `0`, for a CI job that reports only.                                                                                                                                                                                    |
| `2`  | Vale itself could not run: a missing configuration, a rule that failed to load, an argument that is not a path. Or a document couldn't be read, such as one with broken front matter: it's reported after the alerts, and every other document is still linted. `--no-exit` doesn't change this code. |


# Styles

Learn what a rule is, how rules are collected into styles, and how a configuration switches them on.

## [A rule](#a-rule)

A rule is four decisions. Which *check* runs, which says what kind of thing is wrong: a pattern that must not appear, a word that must be spelled one way, a count that must not be exceeded. Which *scope* it runs on, which says where to look: every sentence, only the headings, one section of the document. What the *message* says when it fires. And, when there is one right answer, what the *action* is that fixes it.

```yaml
extends: existence
message: "Don't use end punctuation in headings."
scope: heading
nonword: true
tokens:
  - '[a-z][.?!]$'
```

That is a complete rule. It runs the `existence` check on every heading and reports the span that matched. The same rule, fully dressed, from the Microsoft style:

```yaml
extends: existence
message: "Don't use end punctuation in headings."
link: https://learn.microsoft.com/en-us/style-guide/punctuation/periods
nonword: true
level: warning
scope: heading
action:
  name: edit
  params:
    - trim_right
    - '.?!'
tokens:
  - '[a-z][.?!]$'
```

The `link` is the guidance the rule enforces, shown with every alert. The `level` is how loudly to say it. The `action` is the fix, which an editor or an agent applies without deciding anything. `nonword` is an argument to the check itself: the pattern ends in punctuation, so the check is told not to wrap it in word boundaries.

![The rule's YAML with each key called out: extends is the check, message is what to say, link is the guidance, the next key is an argument to the check, level is how loudly, scope is where to look, action is the fix, and tokens is the pattern.](https://2533404145-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEMH7s0sC5N9L8xSMXGwW%2Fuploads%2Fgit-blob-669cff2922731b91b9a6e1ca4955863b48c242bc%2Frule-anatomy.svg?alt=media)

### [The header](#the-header)

Every rule opens with the same fields, followed by the arguments its check takes:

| Name          | Required | Default   | Description                                                                                                                                                                           |
| ------------- | -------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `extends`     | Yes      |           | <p>The check the rule runs, or another rule to build on.<br><code>extends: existence</code></p>                                                                                       |
| `message`     | Yes      |           | <p>What to say when the rule fires. Each check offers its own placeholders.<br><code>message: "Don't use '%s' in headings."</code></p>                                                |
| `level`       | No       | `warning` | <p><code>suggestion</code>, <code>warning</code>, or <code>error</code>. Only errors set a non-zero exit code.<br><code>level: error</code></p>                                       |
| `scope`       | No       | `text`    | <p>Where to look. See <a href="/topics/scopes">Scopes</a>. <code>metric</code> and <code>readability</code> default to the whole document instead.<br><code>scope: heading</code></p> |
| `link`        | No       |           | <p>The guidance the rule enforces, shown with the alert.<br><code>link: <https://example.com></code></p>                                                                              |
| `description` | No       |           | <p>A longer explanation, for outputs and editors that show one.<br><code>description: "Headings are labels, not sentences."</code></p>                                                |
| `action`      | No       |           | <p>The fix. See <a href="/topics/actions">Actions</a>.<br><code>action: { name: replace }</code></p>                                                                                  |
| `limit`       | No       |           | <p>The most times the rule may fire in one file.<br><code>limit: 3</code></p>                                                                                                         |
| `matchcase`   | No       | `false`   | <p>Adapt a suggested replacement to the case of the text it replaces.<br><code>matchcase: true</code></p>                                                                             |

### [The checks](#the-checks)

Each rule *extends* a check, which is what does the work. Every check runs on any scope.

| Name                                     | Description                                                                         |
| ---------------------------------------- | ----------------------------------------------------------------------------------- |
| [existence](/checks/existence)           | Report a pattern that is present.                                                   |
| [substitution](/checks/substitution)     | Report a pattern and carry its replacement.                                         |
| [occurrence](/checks/occurrence)         | Count a pattern, and report too few or too many.                                    |
| [repetition](/checks/repetition)         | Report a pattern that repeats.                                                      |
| [consistency](/checks/consistency)       | Report a file that uses two forms of one thing.                                     |
| [conditional](/checks/conditional)       | Report a pattern that appears without another it depends on.                        |
| [capitalization](/checks/capitalization) | Report text that is not cased the way the rule says.                                |
| [metric](/checks/metric)                 | Evaluate a formula over the counts of a block, and report when the condition holds. |
| [readability](/checks/readability)       | Score a block's prose, and report when the grade is too high.                       |
| [spelling](/checks/spelling)             | Spell check against Hunspell-compatible dictionaries.                               |
| [sequence](/checks/sequence)             | Report a sequence of tokens, with part-of-speech tags, in a sentence.               |
| [script](/checks/script)                 | Run a Tengo program over a block, and report what it returns.                       |

## [A style](#a-style)

A rule is one file, and a style is a directory of them, kept under the [`StylesPath`](/keys/stylespath):

```
styles/
├── House/
│   ├── Hedging.yml
│   ├── Terms.yml
│   └── dates/
│       └── TimeFormat.yml
└── Microsoft/
    ├── HeadingPunctuation.yml
    ...
```

The directory is the style's name, and the file is the rule's, joined with a dot: `Microsoft.HeadingPunctuation` is how the rule is addressed in configuration, in an in-text comment, in a filter, and in Vale's output. A subdirectory joins the name too, so `House/dates/TimeFormat.yml` is `House.dates.TimeFormat`.

Two things about the files themselves:

* A rule file ends in `.yml`. Vale reads no other extension as a rule.
* A directory whose name starts with `.` or `_` is skipped at load time, so drafts and shared fragments can sit inside a style without loading.

Packages installed with [`vale sync`](/keys/packages) land on the same path, so a style you wrote and a style you downloaded are the same kind of thing. The `config/` directory beside them holds what is not a style: vocabularies, dictionaries, views, filters, and templates.

## [Switching rules on](#switching-rules-on)

A style is enabled by name in [`BasedOnStyles`](/keys/basedonstyles), and every rule in it runs at the level the rule declares. The configuration can then adjust one rule, or a whole style, without touching the files:

```ini
[*.md]
BasedOnStyles = Vale, Microsoft

# Switch a rule off, or on, or change how loudly it reports.
Microsoft.Contractions = NO
Microsoft.HeadingPunctuation = error

# Set a level for every rule in a style, and keep exceptions beside it.
proselint = suggestion
proselint.Typography = warning

# Adjust a rule's parameter with the bracket key.
Std.SentenceLength[max] = 30
```

A rule named on its own, `House.Hedging = YES`, loads even when its style is not in `BasedOnStyles`: every segment after the style is a path, and Vale reads that one file.

The bracket key takes scalars: a number, a boolean, a level. It refuses the keys that make a rule what it is, `message`, `link`, `tokens`, `swap`, `exceptions`, and the rest, because changing those is authoring. For that, extend the rule.

## [Extending another rule](#extending-another-rule)

{% hint style="info" %}
Rule inheritance requires Vale v3.20.0 or later.
{% endhint %}

An `extends` value containing a dot names another rule rather than one of the checks. The new rule starts from that rule's full definition and lays its own keys on top, so two styles can share one carefully built pattern and differ only where they mean to.

The examples below extend these two parents:

```yaml
# Direct/Hedging.yml
extends: existence
message: "Consider removing '%s'."
level: warning
ignorecase: true
tokens:
  - perhaps
  - somewhat
  - it seems
```

```yaml
# Direct/Terms.yml
extends: substitution
message: "Use '%s' instead of '%s'."
level: warning
ignorecase: true
swap:
  e-mail: email
  utilize: use
  web site: website
```

The parent only has to exist on the `StylesPath`; it does not have to be switched on. Inheritance is a file reference, and `vale sync` is what puts the file there. The built-in `Vale` rules have no file, so they cannot be extended.

### [Values](#values)

A key the child writes replaces the parent's value. Every other key is inherited as it is:

```yaml
# House/Hedging.yml
extends: Direct.Hedging
message: "Hedge: '%s'. We state things plainly here."
level: error
```

`House.Hedging` reports the parent's three phrases as errors, with the new message. Its `ignorecase` and `tokens` are the parent's.

This holds for a list or a map too: a bare `tokens` or `swap` in the child replaces the parent's wholesale.

```yaml
# House/Basically.yml
extends: Direct.Hedging
message: "Only '%s'."
tokens:
  - basically
```

`House.Basically` reports `basically` and nothing else.

### [Lists](#lists)

A list can be edited instead of replaced. `tokens+` appends to the parent's list, and `tokens-` removes from it:

```yaml
# House/Stricter.yml
extends: Direct.Hedging
message: "Hedge: '%s'."
tokens+:
  - arguably
  - to some extent
tokens-:
  - perhaps
```

`House.Stricter` reports `somewhat`, `it seems`, `arguably`, and `to some extent`. An entry to remove has to match one of the parent's as YAML reads it, so `perhaps` and `'perhaps'` are the same entry.

Any list key takes `+` and `-`: `exceptions`, `tokens`, `raw`, and so on.

### [Maps](#maps)

A map takes the same two edits. `swap+` merges the child's entries into the parent's, and the child's value wins where both have the same key. `swap-` takes a list of keys to drop:

```yaml
# House/Terms.yml
extends: Direct.Terms
swap+:
  utilize: apply
  sign-in: sign in
swap-:
  - web site
```

`House.Terms` maps `e-mail` to `email`, `utilize` to `apply`, and `sign-in` to `sign in`. It no longer knows `web site`.

### [What is an error](#what-is-an-error)

Removing something the parent does not have is an error, so an upstream rename is heard about rather than silently diverged from:

```
'tokens-': the parent has no entry 'maybe' to remove
```

Writing both `key` and `key+` (or `key-`) in one file is an error: that says "replace" and "edit the replacement" at once. Only a rule that extends another rule can use `+` or `-`; a rule that extends a check has no parent for them to act on. A chain may run ten rules deep before Vale assumes it is a cycle.

### [Running the parent too](#running-the-parent-too)

Extending a rule does not switch the parent off. If the parent's style is also on, both rules run and both report:

```
test.md:1:20:Direct.Hedging:Consider removing 'perhaps'.
test.md:1:20:House.Hedging:Hedge: 'perhaps'. We state things plainly here.
```

Switch the parent off in the configuration when that is not what you want:

```ini
[*.md]
BasedOnStyles = Direct, House
Direct.Hedging = NO
```

### [Sharing a pattern](#sharing-a-pattern)

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:

```
styles/GenZ/
├── _shared/
│   └── Slang.yml   # never loads; Density, Budget, and Presence extend it
├── Budget.yml
├── Density.yml
└── Presence.yml
```

Every segment of the name after the style is a path component, so the rules refer to the file as `GenZ._shared.Slang`:

```yaml
# GenZ/Presence.yml
extends: GenZ._shared.Slang
message: "'%s' is slang."
level: error
```

A fragment is validated as the chain's root, so it must carry a `message`, even one no alert will ever show.

## [Linting a style](#linting-a-style)

{% hint style="info" %}
Requires Vale v3.22.0 or later.
{% endhint %}

A style holds rules and vocabularies, not prose, so a run over a directory skips the `StylesPath`. Name a file or directory inside it to lint it anyway:

```bash
vale styles/MyStyle
```

A rule file is then linted by its `message` and `description` alone. Its `tokens`, `swap`, `exceptions`, and the rest are patterns, and are never read as prose, so a pattern like `optimi[sz]e` is not reported as two misspellings.

## [Patterns](#patterns)

Most rules are patterns. Vale runs them on a [regexp2](https://github.com/jdkato/regexp2) engine in RE2 mode: Go's [regexp/syntax](https://pkg.go.dev/regexp/syntax), plus positive and negative lookahead (`(?=re)`, `(?!re)`) and positive and negative lookbehind (`(?<=re)`, `(?<!re)`). See the [Regex](/guides/regex) guide for how the checks wrap a pattern before running it.

## [The built-in style](#the-built-in-style)

One style, `Vale`, is generated at runtime rather than read from disk. Its four rules report as errors:

| Name              | Description                                                                                                                              |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `Vale.Spelling`   | Spell check, with any Hunspell-compatible dictionaries in `<StylesPath>/config/dictionaries` and any suggestion the dictionary can make. |
| `Vale.Terms`      | Enforce the casing of the project's accepted [Vocabulary](/keys/vocabularies) terms.                                                     |
| `Vale.Avoid`      | Report the project's rejected [Vocabulary](/keys/vocabularies) terms.                                                                    |
| `Vale.Repetition` | Report a repeated word, "the the", and carry the fix.                                                                                    |

They switch on with `BasedOnStyles = Vale`, and each can be adjusted from the configuration like any other rule.


# Testing

Learn how to check that your rules still fire where you think they do.

{% hint style="info" %}
Requires Vale v3.24.0 or later.
{% endhint %}

A rule is a small program, and a rule that matches nothing fails silently: it loads, runs, and reports success. `vale test` runs cases you keep beside your rules, each a short document and the alerts linting it must produce.

```console
$ vale test
 SUCCESS  2 files — 5 passed, 0 failed
```

## [Writing cases](#writing-cases)

Cases live in one of two places. A rule can carry its own under `tests`:

```yaml
# styles/House/Hedging.yml
extends: existence
message: "Consider removing '%s'."
level: warning
ignorecase: true
tokens:
  - perhaps
  - it seems
tests:
  - name: flags a hedge
    input: Perhaps we should ship.
    want: |
      1:1:House.Hedging:Consider removing 'Perhaps'.
  - name: leaves code alone
    input: |
      Run `perhaps` first.
    want: ''
```

Or a file ending in `.test.yml` holds a list of them:

````yaml
# styles/House/Hedging.test.yml
- name: flags every hedge in a sentence
  about: Both tokens, so neither one can be dropped from the list unnoticed.
  rule: Hedging.yml
  input: Perhaps it seems fine.
  contains:
    - "'Perhaps'"
    - "'it seems'"

- name: reStructuredText
  rule: Hedging.yml
  format: rst
  input: |
    Perhaps
    =======
  contains: House.Hedging

- name: code is never prose
  about: Run through the project's .vale.ini, as a real document would be.
  input: |
    ```
    perhaps
    ```
  absent: House.Hedging
````

| Key        | Description                                                                                                          |
| ---------- | -------------------------------------------------------------------------------------------------------------------- |
| `name`     | Required. Identifies the case in the report, and is unique within its file.                                          |
| `about`    | A note for the reader, such as an issue number. Never checked.                                                       |
| `input`    | The document to lint.                                                                                                |
| `format`   | The extension `input` is read as, such as `rst` or `.adoc`. Defaults to `md`.                                        |
| `rule`     | A rule file, relative to the test file, to run alone. See [Isolated and project cases](#isolated-and-project-cases). |
| `view`     | A [View](/topics/views) under `config/views` to read `input` through, for a rule scoped to one of its pieces.        |
| `want`     | The exact alerts, one `line:column:Check:message` per line. An empty `want` asserts that there are none.             |
| `contains` | Text, or a list of texts, that the alerts must include.                                                              |
| `absent`   | Text, or a list of texts, that the alerts must not include.                                                          |

Every case needs at least one of `want`, `contains`, or `absent`. A case that asserts nothing passes whatever the rule does, so Vale refuses to run it. Use `want` when the exact output matters, `contains` when only some of it does, such as which rule fired or which text it matched, and `absent` to pin down what a rule must leave alone. `about` is for the reader: why the case exists, or the issue it came from.

### [The input](#the-input)

`input` is the document itself, written in the case rather than read from a file. A multi-line document goes in a YAML block, `|`, which keeps its line breaks; the alerts' lines and columns count from its first line:

```yaml
- name: lines count from the input
  input: |
    # A heading

    This is perhaps it.
  want: |
    3:9:House.Hedging:Consider removing 'perhaps'.
```

A one-line `input` can be written plainly, but quote it if it contains `:` or starts with a character YAML reads as syntax, such as `#`, `-`, or `[`: `input: 'Note: this is perhaps it.'`

### [Formats](#formats)

A case is read as Markdown unless its `format` names another extension, and it's parsed the way a file of that format would be. A reStructuredText or AsciiDoc case needs the same tools a lint run of that format does, `rst2html` or `asciidoctor`. Any extension works, including one a [View](/topics/views) is attached to, such as `COMMIT_EDITMSG`.

### [Views](#views)

A rule scoped to one of a View's pieces, such as `subject`, sees nothing in an isolated case, since an isolated case loads nothing from the project, including its Views. `view` names the View to read `input` through, from `config/views` on the `StylesPath`:

```yaml
# styles/House/SubjectPeriod.yml
extends: existence
message: "A subject doesn't end with a period."
level: error
scope: subject
nonword: true
raw:
  - '\.$'
tests:
  - name: fires on the subject
    format: COMMIT_EDITMSG
    view: Commit
    input: |
      Fix the thing.

      The body can end with a period.
    want: |
      1:14:House.SubjectPeriod:A subject doesn't end with a period.
```

## [Isolated and project cases](#isolated-and-project-cases)

An **isolated** case runs one rule and nothing else: not the rest of its style, and not your `.vale.ini`. It reports at every level, so it says exactly what the rule matches. A case under a rule's `tests` is isolated to that rule, and a `.test.yml` case is isolated when it names a `rule`.

A **project** case, a `.test.yml` case with no `rule`, is linted by the configuration a `vale` run in that directory would use: its sections, scopes, `MinAlertLevel`, and every style it enables. It tells you whether the rule reaches real documents. Its `format` has to be one your configuration covers, or nothing is linted.

An isolated rule keeps the name a project run gives it, so a case can move between the two without its `want` changing.

## [Running them](#running-them)

With no arguments, `vale test` searches the working directory. Name files or directories to run only those:

```bash
vale test styles/House
vale test styles/House/Hedging.yml
```

A failed case shows what was expected against what came back:

```console
$ vale test

✗ flags a hedge — output does not match

  from  styles/House/Hedging.yml

    - 1:2:House.Hedging:Consider removing 'Perhaps'.
    + 1:1:House.Hedging:Consider removing 'Perhaps'.

    - expected   + actual

  ERROR   2 files — 4 passed, 1 failed
```

`--output=JSON` prints the same results as JSON, with each failure's `reason`, `got`, and `want`.

### [Coverage](#coverage)

A passing run says nothing about the rules no case touches. With `--coverage`, every rule under the given paths has to produce an alert in at least one case:

```console
$ vale test --coverage

✗ 1 rule produced no alert in any case

    House.Silent

  ERROR   2 files — 5 passed, 0 failed, 1 uncovered
```

## [Script rules](#script-rules)

A [`script`](/checks/script) rule is tested like any other, by linting an input with it. The case runs the rule's real script, whether it's written inline or kept in `config/scripts` on the `StylesPath`, so there's nothing to extract or load into a separate runner:

```yaml
# styles/House/Sections.yml
extends: script
message: 'Consider inserting a new section heading at this point.'
level: suggestion
scope: raw
script: Sections.tengo
tests:
  - name: flags the fourth paragraph of a section
    input: |
      # Intro

      One.

      Two.

      Three.

      Four.
    want: |
      9:1:House.Sections:Consider inserting a new section heading at this point.
  - name: a new heading resets the count
    input: |
      # Intro

      One.

      # Next

      Two.
    want: ''
```

Give each edge case its own case as you find it, so a later change to the script that breaks it fails the run. An off-by-one in the paragraph count shows up as a diff:

```console
✗ flags the fourth paragraph of a section — output does not match

  from  styles/House/Sections.yml

    - 9:1:House.Sections:Consider inserting a new section heading at this point.
    + 7:1:House.Sections:Consider inserting a new section heading at this point.
```

A script that doesn't compile fails every case of its rule as one that could not run, and the run exits `2`.

## [Packages](#packages)

A package whose rules [extend](/topics/styles#extending-another-rule) another package names it in the package's own `.vale.ini`, the way [Packages](/keys/packages) does for a project. `vale test` installs what a package names into a temporary `StylesPath` for the run, so its cases resolve without a synced project. A package already on the `StylesPath` is used as it is.

A package root is the nearest directory above a rule that holds both a `.vale.ini` and a `styles` directory, the layout `vale sync` installs.

## [In CI](#in-ci)

`vale test` exits like a lint run:

| Code | Meaning                                                                                                                 |
| ---- | ----------------------------------------------------------------------------------------------------------------------- |
| `0`  | Every case passed.                                                                                                      |
| `1`  | A case failed, or, with `--coverage`, a rule was uncovered.                                                             |
| `2`  | A case could not run: a rule that failed to load, a View that was not found, or no cases at all. The rest still report. |

```yaml
# .github/workflows/styles.yml
- run: vale test --coverage styles
```


# Scopes

Learn how Vale decides which parts of a file a rule applies to.

Vale is “markup aware”: it reads a file into the pieces a reader would recognize—headings, paragraphs, list items, links—and lets a rule say which of those pieces it applies to. That choice is the rule’s *scope*.

## [What a scope is](#what-a-scope-is)

Every file Vale reads becomes a sequence of *blocks*. Each block carries a scope made of dot-separated parts that say what the block is and where it came from. A list item in a Markdown file, for example, is `text.list.md`.

A rule’s `scope` is a selector over those parts. It matches a block when **all of its parts appear in the block’s scope**. It isn’t a prefix match or an exact match, and the order you write the parts in doesn’t matter:

| Selector       | Matches `text.list.md`? |
| -------------- | ----------------------- |
| `list`         | Yes                     |
| `list.md`      | Yes                     |
| `md.list`      | Yes—order is irrelevant |
| `text.list`    | Yes                     |
| `list.text.md` | Yes                     |
| `heading`      | No—not one of its parts |

Two things follow. Any selector can be qualified with a file extension to make it format-specific: `paragraph` matches paragraphs everywhere, and `paragraph.rst` matches them only in reStructuredText. And a selector with fewer parts is broader: `text` matches nearly everything, because nearly every scope contains it.

![One Markdown file becomes several scoped sections: the heading is text.heading.md, the paragraph text.md, the link inside it link.md, and the list item text.list.md. A selector matches when every one of its parts appears in the scope, which is why list.md matches a list item but link.text.md matches nothing.](https://2533404145-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEMH7s0sC5N9L8xSMXGwW%2Fuploads%2Fgit-blob-8041e2c62d0d0ed84812db8a1910b7f4040611e3%2Fscopes.svg?alt=media)

Vale classifies files into one of three types—`markup`, `code`, or `text`—and the type determines which parts exist. Within a type, every format shares the same parts, so a rule written for Markdown headings applies to AsciiDoc headings too.

## [Markup](#markup)

The default behavior for markup files is to apply rules to all non-ignored text. For most rules, you don’t need a scope at all.

For rules that target a kind of element, the parser assigns these parts:

| Name             | Description                                                                                                                                                               |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `heading`        | <p>Matches all <code>h{1,...}</code> tags. You can specify an exact level by<br>appending tags—for example, <code>heading.h1</code> matches all <code>h1</code> tags.</p> |
| `table.header`   | Matches all `th` tags.                                                                                                                                                    |
| `table.cell`     | Matches all `td` tags.                                                                                                                                                    |
| `table.caption`  | Matches all `caption` tags.                                                                                                                                               |
| `figure.caption` | Matches all `figcaption` tags.                                                                                                                                            |
| `list`           | Matches all `li` tags.                                                                                                                                                    |
| `blockquote`     | Matches all `blockquote` tags.                                                                                                                                            |
| `alt`            | Matches all alt attributes.                                                                                                                                               |

The supported formats for markup files are:

* [AsciiDoc](/formats/asciidoc)
* [DITA](/formats/dita)
* [HTML](/formats/html) Built-in
* [Markdown](/formats/markdown) Built-in, including [R Markdown](/formats/markdown#r-markdown)
* [MDX](/formats/mdx) Built-in
* [MyST](/formats/myst) Built-in
* [Org](/formats/org) Built-in
* [QDoc](/formats/qdoc) Built-in
* [Quarto](/formats/quarto) Built-in
* [reStructuredText](/formats/restructuredtext)
* [Typst](/formats/typst)
* [XML](/formats/xml)

The formats marked as `Built-in` are included with Vale by default. The other formats require a third-party dependency to be installed. See each format’s documentation for more information and installation instructions.

### [Class scopes](#class-scopes)

{% hint style="info" %}
Requires Vale v3.17.0 or later.
{% endhint %}

Markup that carries no distinct tag of its own can still be selected by the classes wrapping it. Any enclosing class is appended to the scope as `.class.<name>`.

An AsciiDoc block title, for example, renders as `<div class="title">`—indistinguishable from body text by tag alone:

```yaml
extends: capitalization
message: "'%s' should be in title case."
scope: text.class.title
level: warning
match: $title
```

A directive’s name lands here too: a [MyST](/formats/myst) or [Quarto](/formats/quarto) `:::{note}` scopes its content as `class.note`, and a [QDoc](/formats/qdoc) `\note` does the same.

Classes nest, so a block inside two classed elements is reachable as `text.class.outer.class.inner`—and every block inside a classed container carries its class, however many blocks that is. To ignore classed content rather than target it, see [`IgnoredClasses`](/keys/ignoredclasses).

### [Inline elements](#inline-elements)

{% hint style="info" %}
Requires Vale v3.17.0 or later.
{% endhint %}

Inline elements have scopes of their own, which let a rule target the text inside a link, a code span, or an emphasized phrase:

| Name       | Description                                    |
| ---------- | ---------------------------------------------- |
| `link`     | Matches the text of all `a` tags.              |
| `code`     | Matches all `code` and `tt` tags (code spans). |
| `strong`   | Matches all `strong` and `b` tags.             |
| `emphasis` | Matches all `em` and `i` tags.                 |

{% hint style="warning" %}
These are siblings of `text`, not children of it: the scope is `link`, **not** `text.link`.

Scopes are matched by containment, so a rule scoped to `text` would also match `text.link`—meaning every ordinary rule would run a second time over each link.
{% endhint %}

A rule that asks for one of these scopes is the only kind that runs against it:

```yaml
extends: existence
message: "Don't use '%s' as link text."
scope: link
level: error
tokens:
  - here
  - this
  - click here
```

The text inside code spans is skipped by default (`IgnoredScopes` defaults to `tt`, `code`, and `kbd`), so a rule has to ask for `code` explicitly. To exclude inline text from every rule, see [`IgnoredScopes`](/keys/ignoredscopes).

To exclude it from one rule, negate the scope. A negated inline scope keeps the rule running on the block around the element, with the element's text left out (Vale v3.22.0 or later):

```yaml
extends: substitution
message: "Use '%s' instead of '%s'."
scope: ~heading & ~link
swap:
  github: GitHub
```

This rule corrects the casing in paragraphs and list items, and leaves headings and link text, which follow title case, alone. See [URLs](/topics/urls) for more on links.

### [Metadata](#metadata)

{% hint style="info" %}
Requires Vale v3.18.0 or later.
{% endhint %}

A document’s machine-readable content—an anchor name, an image’s file name—is collected under the `meta` scope. A [QDoc](/formats/qdoc) file’s anchors and image file names land here, as does the content of an HTML `data` element.

Like the [inline elements](#inline-elements), `meta` is a sibling of `text`: an ordinary prose rule passes over it, and only a rule that asks for `meta` reaches it. Its text isn’t segmented, either—an identifier has no sentences.

Each piece carries its kind as a class, so a rule can target all of a document’s metadata or one kind of it:

```yaml
extends: existence
message: "Don't use spaces in a file name."
scope: meta.class.image
level: error
tokens:
  - " "
```

## [Prose units](#prose-units)

The parts above name elements of the document. These four name units of prose that Vale builds itself:

| Name        | Description                                                                                                                                                                                                                                                 |
| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `sentence`  | Each sentence, from every kind of prose—paragraphs, headings, list items, and table cells.                                                                                                                                                                  |
| `paragraph` | <p>Each body paragraph (a segment of text separated by two newlines).<br>Headings, list items, table cells, and blockquotes are not paragraphs.</p>                                                                                                         |
| `summary`   | <p>The document’s prose as one block, excluding headings, code spans, code blocks, and<br>table cells. This is what <a href="/checks/metric"><code>metric</code></a> and <a href="/checks/readability"><code>readability</code></a> measure by default.</p> |
| `raw`       | <p>The raw, unprocessed markup source as one block. This scope is useful for regex-based rules<br>that need to match against the original source text.</p>                                                                                                  |

A scope is a request as well as a selection. Sentences exist only because some rule declared `sentence`, and paragraphs only because some rule declared `paragraph`; Vale builds each unit when a rule asks for it and skips the work when none does. The same is true of the [selections](#selections) below.

## [Selections](#selections)

{% hint style="info" %}
Requires Vale v3.21.0 or later.
{% endhint %}

A `doc(...)` term selects elements of the document by CSS selector. Every markup format Vale reads is parsed into the same HTML, and the selector runs against that, so a selection means the same thing in Markdown, AsciiDoc, reStructuredText, Org, and HTML.

Before the selectors run, Vale wraps each heading and everything that follows it—up to the next heading of the same or a higher level—in a `section` element. Sections nest, so an `h3` after an `h2` sits inside the `h2`’s section. That is what lets a selector name a section of a document, which no format marks up on its own.

![A Markdown file with an h1, three h2 sections, and an h3 under Decision is wrapped into nested sections. The selector doc(section:has(a direct h2 child containing Decision)) marks the Decision section, so the paragraph and the h3 inside it carry the selection in their scopes while Context and Consequences do not, and the section itself is one doc block.](https://2533404145-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEMH7s0sC5N9L8xSMXGwW%2Fuploads%2Fgit-blob-75eac0309d99d8349f3f748ee7fbb88dd06d3f88%2Fselections.svg?alt=media)

A `doc(...)` term means one of three things, depending on where it stands:

**Alone, the element is the block.** Its text is gathered as one unit and linted once, so a rule can count or measure it:

```yaml
extends: occurrence
message: "The Decision section states no decision. Write it as 'We will ...'."
level: error
scope: 'doc(section:has(> h2:contains("Decision")))'
token: '(?i)\bwe will\b'
min: 1
```

```yaml
extends: metric
message: "The Summary runs %s words. The budget is 100."
level: error
scope: 'doc(section:has(> h2:contains("Summary")))'
formula: words
condition: "> 100"
```

An alert on a selection lands on the element’s first line, which is the heading of a section.

**Beside a scope, it narrows that scope to blocks inside the element.** Any existing rule can be pointed at one part of a document:

```yaml
extends: existence
message: "'%s' hedges. A recommendation states it."
level: error
scope: 'sentence & doc(section:has(> h2:contains("Recommendation")))'
tokens:
  - may want to
  - it is worth noting
```

```yaml
extends: existence
message: "Open on the point, not on '%s'."
level: warning
# The paragraph directly after any h2.
scope: 'text & doc(h2 + p)'
tokens:
  - in this section
```

**Negated, it excludes the element.** `~doc(...)` reaches everything outside the elements the selector matches.

A selection that matches nothing in a file still produces its block, empty, on line one. That is how a rule reports that a section is missing: an `occurrence` rule with `min: 1` and a token that matches any character finds zero occurrences and reports the shortfall.

```yaml
extends: occurrence
message: "An ADR has a Decision section."
level: error
scope: 'doc(section:has(> h2:contains("Decision")))'
token: '(?s).'
min: 1
```

Selectors follow the syntax of [Selectors Level 4](https://www.w3.org/TR/selectors-4/): the combinators `>`, `+`, and `~`; `:not()`, `:is()`, and `:where()`; `:has()` with a relative selector or a group of them, such as `:has(> h1, > h2)` for a section opened by either level or `:has(> h2 + p)` for a heading with a paragraph right after it; `:first-of-type`, `:last-of-type`, and the `nth` family; and attribute selectors. Three things to know:

* `:contains("…")` matches any part of the text, ignoring case, so `h2:contains("Decision")` also matches a heading that reads “Decision log.” Give it the whole heading.
* An element with no prose in it—a code block, an image—selects fine and then has nothing to lint. Selections are for elements that hold text.
* `:scope` and the `of S` form of `:nth-child()` are not supported. A relative selector in `:has()` is already anchored to the element, so `:has(:scope > h2)` is written `:has(> h2)`.

{% hint style="info" %}
Sibling combinators inside `:has()`, a group of relative selectors, and `:is()` and `:where()` require Vale v3.23.0 or later.
{% endhint %}

The elements a selector matches carry a `data-vale-doc` attribute in Vale’s internal HTML, and blocks inside them carry the selection in their scope as `in.<id>`. Neither is something a rule writes; the rule writes the selector.

## [Named scopes](#named-scopes)

{% hint style="info" %}
Requires Vale v3.23.0 or later.
{% endhint %}

A selector that several rules share can be written once and given a name. Names live in YAML files under `config/scopes/` on the StylesPath, one mapping of names to scope expressions per file:

```yaml
# styles/config/scopes/Sections.yml
methods: 'doc(section:has(> h2:contains("Methods")))'
discussion: 'doc(section:has(> h2:contains("Discussion")))'
lead: text & doc(h1 + p)
```

A rule uses the name wherever it would have written the expression: alone, negated, or in a chain.

```yaml
extends: occurrence
message: "The Methods section says nothing about blinding."
level: error
scope: methods
token: '(?i)\bblind'
min: 1
```

```yaml
extends: existence
message: "'%s' belongs in the Discussion."
level: warning
scope: sentence & ~discussion
tokens:
  - we speculate
```

Every file under the directory is read, so a package that ships its names beside its rules needs nothing added to a configuration. A name may hold a chain, as `lead` does above; a chained name joins the rule’s other terms, and can’t be negated. A name can’t be one Vale already uses for a scope, such as `heading` or `paragraph`, and the same name defined twice with different expressions is an error.

## [Combining selectors](#combining-selectors)

Rules may define multiple scopes by using a YAML array. An entry matches if **any** of them does:

```yaml
scope:
  # h1 OR h2
  - heading.h1
  - heading.h2
```

Any scope prefaced with `~` is negated:

```yaml
scope:
  # all scopes != h2
  - ~heading.h2
```

You can chain multiple scopes together using `&`, which requires **all** of them:

```yaml
scope:
  # any scope that is NOT a blockquote or a heading
  - ~blockquote & ~heading
```

{% hint style="info" %}
Chains that name `paragraph`, `sentence`, or an [inline element](#inline-elements) require Vale v3.18.0 or later. Earlier versions silently matched nothing for those chains.
{% endhint %}

The two combine: `&` is an AND within a single entry, and the array is an OR across entries. A `doc(...)` term is a term like any other, so it chains with prose units and negates the same way:

```yaml
scope:
  # (a heading that isn't an h1) OR (a list item in Markdown)
  - heading & ~heading.h1
  - list.md
  # sentences outside the Decision section
  - sentence & ~doc(section:has(> h2:contains("Decision")))
```

{% hint style="info" %}
Because matching is by parts rather than by prefix, a narrower-looking scope isn’t always narrower. `text.list` and `list` select the same blocks—the extra `text` adds nothing, since every list item’s scope already contains it.
{% endhint %}

## [Checks and scopes](#checks-and-scopes)

Every check runs on the blocks its scope names. Three of them have a default worth knowing:

* [`metric`](/checks/metric) and [`readability`](/checks/readability) measure the whole document unless the rule declares a scope. `text` also means the whole document here, since the document’s prose is what `summary` holds. Any other scope measures each block it names on its own—a paragraph, a heading, a list item, or a selection—and a selection counts the elements it holds, so `heading.h3` inside a section is the number of subheadings in that section.
* [`sequence`](/checks/sequence) reads part-of-speech tags, which are assigned a sentence at a time, so it always reads sentences. Its scope says which blocks the sentences come from: `list` is the sentences of list items, and a `doc(...)` term is the sentences inside the selection.

## [Code](#code)

There are two `code` scopes: `comment.line` and `comment.block`.

See the [Code](/formats/code) documentation for more information.


# URLs

Learn what Vale does with a URL, and how to change it.

A URL is never prose. Wherever one appears, Vale leaves it alone and lints the text around it. Link *text* is prose, and is linted.

## [In markup](#in-markup)

Every markup format Vale reads becomes the same structure:

```html
<a href="url">text</a>
```

The `url` is skipped and the `text` is linted. So, in Markdown:

```markdown
[our page on Ruby](https://example.com/ruby)
```

`our page on Ruby` is linted and `https://example.com/ruby` is not. The same holds for a link whose text is its URL, a URL in angle brackets, and a bare URL:

```markdown
[https://example.com](https://example.com)

<https://example.com>

https://example.com
```

None of these raise anything, because the only text they hold is the URL.

AsciiDoc and reStructuredText read the same way. `xref:ruby.adoc[our page on Ruby]` lints `our page on Ruby`; `` `our page <https://example.com>`_ `` lints `our page`; and a bare URL in either is skipped.

## [In code](#in-code)

Vale lints the comments in source code, and a URL in a comment is skipped like one in markup:

```go
// See https://example.com/setup for the details.
```

`See` and `for the details` are linted; the URL is not.

## [Rules about links](#rules-about-links)

A rule can ask for link text alone, or refuse it. The `link` scope reaches the text of every link and nothing else:

```yaml
extends: existence
message: "Don't use '%s' as link text."
scope: link
ignorecase: true
tokens:
  - here
  - click here
```

Negating it leaves link text out of a rule that runs everywhere else. A rule that enforces a product name's casing should not fire on a heading or a link, both of which follow title case:

```yaml
extends: substitution
message: "Use '%s' instead of '%s'."
scope: ~heading & ~link
swap:
  github: GitHub
```

`~strong`, `~emphasis`, and `~code` work the same way. See [Scopes](/topics/scopes) for the rest of the selector syntax.

## [Rules about URLs](#rules-about-urls)

A rule that wants the URL itself has to say so, since no ordinary scope reaches it. The `raw` scope runs a rule over the file as written, markup and all:

```yaml
extends: existence
message: "Use a fully qualified URL for '%s'."
scope: raw
raw:
  - 'https://admin\.example\.com/link/[^#\s"\)\]]+'
```

A `raw` rule sees every URL, including those in code blocks, so write its pattern to stop at the character that ends a URL rather than at whitespace alone.

## [When a URL still gets through](#when-a-url-still-gets-through)

Some converters render a bare URL as a link whose text is the URL, marked with a class. AsciiDoc does this with `bare`:

```html
<a href="https://example.com" class="bare">https://example.com</a>
```

Vale skips it already, since the text is the URL. If a converter marks a URL some other way, [`IgnoredClasses`](/keys/ignoredclasses) skips the content of any element with that class, and [`TokenIgnores`](/keys/tokenignores) skips anything a regular expression can describe.

If Vale reports text inside a URL with none of that involved, that is a bug: open an issue with the file and your `.vale.ini`.


# Actions

Learn how a rule carries its own fix, and where that fix goes.

## [A fix beside the finding](#a-fix-beside-the-finding)

A rule says what is wrong. An action says what to do about it: the matched text becomes this instead. It is the part of a rule that an editor can apply with one keystroke, and that an agent can act on without deciding anything.

```yaml
extends: existence
message: "Don't use end punctuation in headings."
scope: heading
nonword: true
action:
  name: edit
  params:
    - trim_right
    - '.?!'
tokens:
  - '\w+[.?!]$'
```

Vale resolves the action when the rule fires, not when someone asks for the fix. Every alert carries both the recipe, `Action`, and the answer, `Suggestions`:

```json
{
  "Action": { "Name": "edit", "Params": ["trim_right", ".?!"] },
  "Suggestions": ["Overview"],
  "Check": "House.HeadingPunctuation",
  "Message": "Don't use end punctuation in headings.",
  "Match": "Overview.",
  "Line": 3,
  "Span": [3, 11]
}
```

The one exception is spelling. Ranking a dictionary against a word is real work, so a spelling rule's suggestions are computed when an editor asks for them, and its `Suggestions` list is empty in lint output.

## [The message](#the-message)

A message with two placeholders gets the match first and the fix second:

```yaml
message: "'%s' should be '%s'."
```

A rule whose action yields several suggestions sees them joined: `'colour' should be 'color or colors'`. A rule with no action gets the match alone, and a second placeholder stays empty.

## [The actions](#the-actions)

| Name                        | Description                                                                                                              |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| [`replace`](/fixes/replace) | One or more replacements written into the rule. `substitution` and `capitalization` fill them in from their own entries. |
| [`remove`](/fixes/remove)   | Delete the match.                                                                                                        |
| [`edit`](/fixes/edit)       | Run the match through a pipeline of operations: rewriting, trimming, wrapping, and casing.                               |
| [`convert`](/fixes/convert) | Change the case of the match.                                                                                            |
| [`suggest`](/fixes/suggest) | Compute the replacements: spelling suggestions from the active dictionaries, or a Tengo script of your own.              |

An `edit` with several steps is written as a list of lists, one per operation, and the result of each is the input to the next:

```yaml
action:
  name: edit
  params:
    - [trim_right, '!']
    - [lower]
    - [wrap, '`']
```

An argument can also name what the rule matched: `$1` is the first capture group of the token that fired, and `$0` is the whole match. A `replace` written as `'$2, $1'` reorders a name without a pattern of its own. See [the token's groups](/fixes/edit#the-tokens-groups).

Every action is checked when the rule loads. An unknown name, a missing parameter, an `edit` operation Vale does not have, or a `suggest` script that is not on disk is an `E201` error pointing at the rule file, not a surprise after the rule fires.

A `replace` action respects [`matchcase`](/topics/styles#the-header): with it set, `A-OK` written into the rule still suggests `a-ok` for `a ok`. The other actions produce their text from the match itself, so they already carry its case.

## [Where a fix goes](#where-a-fix-goes)

**The CLI** shows it in the message, as above, and in JSON output as `Suggestions`.

**Editors** get the fix as a quick fix through [`vale-ls`](/guides/lsp): a replacement to choose from, or a deletion. A `remove` applied from an editor takes one neighboring space with the word, so a deletion does not leave two behind.


# Filters

Learn how to run a subset of the rules a configuration enables.

`--filter` takes an expression over the rules the configuration loaded, and runs only the ones it selects:

```sh
vale --filter='.Level == "error"' docs/
```

A filter narrows a run and never widens it: a rule the configuration did not enable is not there to select. Everything else about the run stays as configured, with one exception. When the expression mentions `.Level`, the minimum alert level drops to include what it selected, so `--filter='.Level == "suggestion"'` shows suggestions even under `MinAlertLevel = warning`.

![Using --filter to limit results to alerts whose scope is a heading.](https://2533404145-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEMH7s0sC5N9L8xSMXGwW%2Fuploads%2Fgit-blob-86f665ed682ed12230bcc10a68f080b0ea9241f5%2Ffilter.svg?alt=media)

## [The expression](#the-expression)

A filter is an [expression](https://expr-lang.org/docs/language-definition), evaluated once per rule against the rule's header. The fields are the ones every rule opens with, capitalized:

| Field          | Type       | Example                                  |
| -------------- | ---------- | ---------------------------------------- |
| `.Name`        | `string`   | `.Name == "Microsoft.Contractions"`      |
| `.Level`       | `string`   | `.Level in ["error", "warning"]`         |
| `.Scope`       | `string[]` | `"heading" in .Scope`                    |
| `.Extends`     | `string`   | `.Extends == "spelling"`                 |
| `.Message`     | `string`   | `.Message contains "instead"`            |
| `.Description` | `string`   | `.Description != ""`                     |
| `.Link`        | `string`   | `.Link startsWith "https://developers."` |

`.Scope` is a list because a rule may name several, so membership is the test. Expressions combine with `and`, `or`, and `not`, and the [operators](https://expr-lang.org/docs/language-definition#operators) include `matches` for a regular expression:

```sh
# Every rule from one style, at any level.
vale --filter='.Name startsWith "Google."' docs/

# Only spelling, and only in headings.
vale --filter='.Extends == "spelling" and "heading" in .Scope' docs/

# Everything except one rule.
vale --filter='.Name != "Vale.Spelling"' docs/
```

## [Saving a filter](#saving-a-filter)

A filter that is typed twice belongs in a file. `--filter` accepts a path, or the name of a file in the `StylesPath`'s `config/filters` directory:

```sh
vale --filter=headings.expr docs/
```

```
# config/filters/headings.expr
"heading" in .Scope
```

The value is tried as a path first, then as a name on the `StylesPath`, and read as an expression only when it is neither, so a file that is not where it was expected is reported as a bad expression rather than silently run as one.


# Templates

Learn how to write an output format of your own.

`--output` takes `CLI`, `line`, or `JSON`, and otherwise the name of a template: a [Go template](https://pkg.go.dev/text/template) that Vale renders with the run's alerts in place of a built-in format.

```sh
vale --output=sarif.tmpl docs/ > vale.sarif
```

The value is read as a path first. When no file is there, it is looked up by name on the `StylesPath`, where templates live in `config/templates`.

The exit code is the same as for any other format: `1` when an alert at the `error` level was reported, `0` otherwise, and `--no-exit` turns the first into the second.

## [The data](#the-data)

A template is executed once, with the whole run:

```go
type Data struct {
    Files       []ProcessedFile // every file that had at least one alert
    LintedTotal int             // every file the run read, alerts or not
}

type ProcessedFile struct {
    Path   string
    Alerts []core.Alert
}
```

A file with no alerts is left out of `Files` and counted in `LintedTotal`, which is how a summary line can say "in 12 files" while ranging over three. Each alert has the fields the [JSON output](/topics/cli#output) shows: `Check`, `Severity`, `Message`, `Description`, `Link`, `Match`, `Line`, `Span`, `Action`, and `Suggestions`. `Span` is a pair of one-based character columns, and both ends are inclusive.

## [The functions](#the-functions)

Every function in [Sprig](http://masterminds.github.io/sprig/) is available, which covers strings, lists, dictionaries, and arithmetic. Vale adds these:

| Name          | Argument(s)       | Description                                                                                        |
| ------------- | ----------------- | -------------------------------------------------------------------------------------------------- |
| `red`         | `string`          | The string in red.                                                                                 |
| `yellow`      | `string`          | The string in yellow.                                                                              |
| `blue`        | `string`          | The string in blue.                                                                                |
| `underline`   | `string`          | The string underlined.                                                                             |
| `newTable`    | `bool`            | A table with no borders that writes to standard output. The argument says whether cell text wraps. |
| `addRow`      | `Table, []string` | The table with the row appended. Returns the table, so it chains.                                  |
| `renderTable` | `Table`           | Print the table, with a blank line before and after, and empty it for reuse.                       |
| `jsonEscape`  | `string`          | The string escaped for use inside a JSON string literal, without the surrounding quotes.           |

The colors and the underline are terminal escapes, so a template meant to be redirected to a file should leave them out; `--no-color` disables them for a run.

## [Examples](#examples)

### [Customizing the default output](#customizing-the-default-output)

The following example re-implements Vale’s default output style using a template.

```go
{{- /* Keep track of our various counts */ -}}

{{- $e := 0 -}}
{{- $w := 0 -}}
{{- $s := 0 -}}
{{- $f := 0 -}}

{{- /* Range over the linted files */ -}}

{{- range .Files}}
{{$table := newTable true}}

{{- $f = add1 $f -}}
{{- .Path | underline | indent 1 -}}

{{- /* Range over the file's alerts */ -}}

{{- range .Alerts -}}

{{- $error := "" -}}
{{- if eq .Severity "error" -}}
    {{- $error = .Severity | red -}}
    {{- $e = add1 $e  -}}
{{- else if eq .Severity "warning" -}}
    {{- $error = .Severity | yellow -}}
    {{- $w = add1 $w -}}
{{- else -}}
    {{- $error = .Severity | blue -}}
    {{- $s = add1 $s -}}
{{- end}}

{{- $loc := printf "%d:%d" .Line (index .Span 0) -}}
{{- $row := list $loc $error .Message .Check | toStrings -}}

{{- $table = addRow $table $row -}}
{{end -}}

{{- $table = renderTable $table -}}
{{end}}
{{- $e}} {{"errors" | red}}, {{$w}} {{"warnings" | yellow}} and {{$s}} {{"suggestions" | blue}} in {{$f}} {{$f | int | plural "file" "files"}}.
```

### [Creating a RDJSONL template](#creating-a-rdjsonl-template)

The following example converts Vale’s output to [RDJSONL](https://github.com/reviewdog/reviewdog?tab=readme-ov-file#reviewdog-diagnostic-format-rdformat), which you can then pass to [Reviewdog](https://github.com/reviewdog/reviewdog) to display on pull request. This can be useful when the [Vale action](https://github.com/vale-cli/vale-action) is not suitable for your workflow.

```go
{{- /* Range over the linted files */ -}}

{{- range .Files}}

{{- $path := .Path -}}

{{- /* Range over the file's alerts */ -}}

{{- range .Alerts -}}

{{- $error := "" -}}
{{- if eq .Severity "error" -}}
    {{- $error = "ERROR" -}}
{{- else if eq .Severity "warning" -}}
    {{- $error = "WARNING" -}}
{{- else -}}
    {{- $error = "INFO" -}}
{{- end}}

{{- /* Variables setup */ -}}

{{- $line := printf "%d" .Line -}}
{{- $start := index .Span 0 -}}
{{- $end := add (index .Span 1) 1 -}}
{{- $check := printf "%s" .Check -}}
{{- $message := printf "%s" .Message -}}

{{- /* Output */ -}}

{"message": "{{ $message | jsonEscape }}", "location": {"path": "{{ $path }}", "range": {"start": {"line": {{ $line }}, "column": {{ $start }}}, "end": {"line": {{ $line }}, "column": {{ $end }}}}}, "severity": "{{ $error }}", "code": {"value": "{{ $check | jsonEscape }}"{{ if .Link }}, "url": "{{ .Link | jsonEscape }}"{{ end }}}}
{{end -}}
{{end -}}
```

Two things are worth knowing when adapting this. Reviewdog reads a range, so giving it the end of the span -- `Span` is inclusive, and Reviewdog's end is not -- is what underlines the match rather than pointing at its first character. And Reviewdog counts columns in UTF-8 bytes where Vale counts characters, so the two agree only until a line picks up its first multi-byte character; converting between them needs the source line, which a template can't read.

### [Creating a SARIF template](#creating-a-sarif-template)

The following example converts Vale's output to [SARIF](https://sarifweb.azurewebsites.net/), the format that [GitHub code scanning](https://docs.github.com/en/code-security/code-scanning/integrating-with-code-scanning/uploading-a-sarif-file-to-github), GitLab, and Azure DevOps read. Unlike a pull request comment, an alert reported this way persists: it has a history, and someone can dismiss it with a reason.

```bash
$ vale --output=sarif.tmpl . > vale.sarif
```

In a GitHub workflow, hand the file to `upload-sarif`:

```yaml
- name: Upload to code scanning
  uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: vale.sarif
```

Two details make the conversion straightforward. SARIF measures columns in characters, as Vale's `Span` does, so the positions carry over unchanged. And SARIF asks for each rule to be described once, which [`dict`](http://masterminds.github.io/sprig/dicts.html) and `set` collect in a pass over the alerts before any output.

```go
{{- /* Collect the rules that fired, so that each is described once. */ -}}
{{- $rules := dict -}}
{{- range .Files -}}
{{- range .Alerts -}}
{{- $_ := set $rules .Check (dict "link" .Link "text" .Description) -}}
{{- end -}}
{{- end -}}
{
  "$schema": "https://json.schemastore.org/sarif-2.1.0.json",
  "version": "2.1.0",
  "runs": [
    {
      "tool": {
        "driver": {
          "name": "Vale",
          "informationUri": "https://vale.sh",
          "rules": [
{{- $first := true -}}
{{- range $id, $rule := $rules }}
{{ if not $first }},{{ end }}            {
              "id": "{{ $id | jsonEscape }}",
              "shortDescription": {"text": "{{ if $rule.text }}{{ $rule.text | jsonEscape }}{{ else }}{{ $id | jsonEscape }}{{ end }}"}
              {{- if $rule.link }},
              "helpUri": "{{ $rule.link | jsonEscape }}"
              {{- end }}
            }
{{- $first = false -}}
{{- end }}
          ]
        }
      },
      "results": [
{{- $first = true -}}
{{- range .Files -}}
{{- $path := .Path -}}
{{- range .Alerts }}
{{ if not $first }},{{ end }}        {
          "ruleId": "{{ .Check | jsonEscape }}",
          "level": "{{ if eq .Severity "error" }}error{{ else if eq .Severity "warning" }}warning{{ else }}note{{ end }}",
          "message": {"text": "{{ .Message | jsonEscape }}"},
          "locations": [
            {
              "physicalLocation": {
                "artifactLocation": {"uri": "{{ $path | jsonEscape }}"},
                "region": {
                  "startLine": {{ .Line }},
                  "startColumn": {{ index .Span 0 }},
                  "endColumn": {{ add (index .Span 1) 1 }},
                  "snippet": {"text": "{{ .Match | jsonEscape }}"}
                }
              }
            }
          ]
        }
{{- $first = false -}}
{{- end -}}
{{- end }}
      ]
    }
  ]
}
```


# Views

Learn how to lint the prose inside a file that is not prose.

A markup file is a document, and Vale parses it into the blocks its rules see. A data file, a source file, or a plain-text file with a convention has prose in it too, in the descriptions of an API specification, the docstrings of a module, the body of a commit message, but nothing marks where. A View says where: a list of queries that pull named pieces out of the file, so that Vale lints those and passes over the rest, and a rule can be written for one of them by name.

```yaml
# <StylesPath>/config/views/OpenAPI.yml
engine: dasel
scopes:
  - name: title
    expr: info.title

  - name: description
    expr: search(has("description")).map(description)
    type: md
```

```ini
[*.{json,yml,yaml}]
BasedOnStyles = Vale, House
View = OpenAPI
```

A View is a YAML file in the `StylesPath`'s `config/views` directory, and the [`View`](/keys/view) key names it under a section, so the files it applies to are whichever that section matches. A name that is not there is an error when the configuration loads, and a query that fails is an error that points at the section.

## [The file](#the-file)

| Key        | Description                                                                                                                                 |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `engine`   | What runs the queries: `dasel` for a data file, `tree-sitter` for source code, or `textfsm` for plain text.                                 |
| `scopes`   | The queries, in order. Each has an `expr` the engine evaluates, an optional `name`, an optional `type`, and for `dasel` an optional `join`. |
| `template` | For `textfsm` only: the template the file is read through. Each scope's `expr` names one of its values.                                     |

A scope's `name` is how a rule reaches what the query found. It is appended to the scope of every block the value produces, so a rule with `scope: title` runs on the titles and nothing else, and a rule with the usual `scope: text` runs on everything the View extracted. A query without a name is linted, and is reachable only by the scopes the format gives it.

A scope's `type` is the format the extracted text is parsed as: `md`, `rst`, `html`, `org`, or `adoc`. Without one, the text is read as plain lines, which is right for a title and wrong for a description written in Markdown.

![A file goes to an engine, whose query yields values placed at their line and column, and each value becomes a block whose scope carries the query's name. Dasel reads a data file with a selector, tree-sitter reads source code with a query, and TextFSM reads plain text with a template's named value.](https://2533404145-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEMH7s0sC5N9L8xSMXGwW%2Fuploads%2Fgit-blob-56394aff5e154b991c57e440449af9e89ac1ffe7%2Fviews-engines.svg?alt=media)

## [Data](#data)

A `dasel` View applies to any file its section matches. The file is read as JSON, YAML, or TOML by its extension, and as YAML, which reads JSON too, when the extension is none of those. Each `expr` is a [Dasel](https://github.com/TomWright/dasel) selector over the parsed document. A selector may land on one string or many; every string it selects is one value, and anything that is not a string is dropped. A list of strings is dropped too, unless the scope has a `join`, the separator the list is joined with to make one value. A notebook keeps each Markdown cell's source as a list of lines that already end in a line break, so the cell is one value with `join: ""`, placed where its first line is:

```yaml
# <StylesPath>/config/views/Notebook.yml
engine: dasel
scopes:
  - name: cell
    expr: cells.all().filter(equal(cell_type,markdown)).source
    join: ""
    type: md
```

```ini
[*.ipynb]
BasedOnStyles = Vale
View = Notebook
```

```json
{
  "features": [
    { "title": "Views", "description": "Lint the prose inside a data file." },
    { "title": "Styles", "description": "Collect rules into a style." }
  ]
}
```

```yaml
engine: dasel
scopes:
  - name: feature
    expr: features.all().title

  - expr: features.all().description
    type: md
```

For JSON and YAML, each value is placed at the line and column the parser read it from, so an alert points into the source file, and a folded YAML block is read with its line breaks kept so positions stay line for line. TOML gives no positions, and a value is found by searching the text for it.

A YAML or TOML file's comments are linted as well, whatever the View selects: a comment is prose, and a selector cannot reach one. They carry the scopes a source file's comments do, `text.comment.line` and `text.comment.block`, so `IgnoredScopes = comment` leaves them out.

## [Code](#code)

Vale reads source code by its comments. A `tree-sitter` View replaces that with queries of your own, in tree-sitter's [query language](https://tree-sitter.github.io/tree-sitter/using-parsers/queries/index.html), run against the file's syntax tree. It applies to the languages Vale has a grammar for: C, C++, CSS, Elixir, Go, Haskell, Java, JavaScript, Julia, Kotlin, Lua, PHP, Protocol Buffers, Python, QML, R, Ruby, Rust, TOML, TypeScript, and YAML.

```python
def hello(name: str) -> str:
    """Greet someone by name."""
    return f"Hello, {name}!"
```

```yaml
engine: tree-sitter
scopes:
  - expr: (comment)+ @comment

  - name: docstring
    expr: |
      ((function_definition
        body: (block . (expression_statement (string) @docstring)))
      (#offset! @docstring 0 3 0 -3))
    type: md
```

Each capture in a query is one value. Its scope is `text.comment`, then the query's `name`, then `.line` or `.block` by whether the text spans lines, so the docstrings above answer to `scope: text.comment.docstring` and a plain `scope: comment` reaches both queries. Comment delimiters and per-line decoration are stripped before linting, and `#offset!` trims a capture by rows and columns from each end, which is how the docstring loses its quotes. A capture whose name starts with `_` is there for a predicate to test and is not linted, and one named `prose` is taken as bare content with nothing to strip.

A YAML file is otherwise read as data, whole or through a `dasel` View. Naming it in a `tree-sitter` View reads it as code instead, so a `(comment) @comment` query lints its comments and nothing else, which is how the comments in a Helm chart's `values.yaml` or a CI workflow are checked without the values.

## [Text](#text)

A `textfsm` View applies to any file the section matches, and reads it through a template in the form [TextFSM](https://github.com/google/textfsm/wiki/TextFSM) defined: named values, then a state machine of regular expressions in the same [dialect](/guides/regex) every rule uses. Each scope's `expr` names a value, and what the template captured for it becomes the scope's text, placed at the line and column it came from.

```yaml
engine: textfsm
template: |
  Value Subject (.+)
  Value List Body (.*)
  Value List Trailer ((?:BREAKING CHANGE|[A-Z][\w-]+): .+)

  Start
    ^${Subject} -> Body

  Body
    ^# -> Next
    ^${Trailer}
    ^${Body}
scopes:
  - name: subject
    expr: Subject

  - name: body
    expr: Body
    type: md

  - name: trailer
    expr: Trailer
```

```ini
[COMMIT_EDITMSG]
BasedOnStyles = Vale, House
View = Commit
```

The [Commits](/guides/commit-messages) package ships this View with the conventions and the hook; the [TextFSM guide](/guides/textfsm) walks through the template language.

A rule with `scope: subject` then runs on the first line of a commit message and nowhere else. Consecutive lines a `List` value captures at the same column are joined into one value, so a body reads as its paragraphs rather than one block per line. A value the template never fills is an empty scope at the top of the file, which is where an `occurrence` rule with `min` reports that it is missing. The [TextFSM guide](/guides/textfsm) walks through the template language, a transcript where only one side is linted, and how to see what a template captured.


# WebAssembly

{% hint style="info" %}
Requires Vale v3.26.0 or later.
{% endhint %}

Vale ships as a WebAssembly module for JavaScript hosts: a browser, a browser extension, or Node. It is the same engine as the CLI, built without CGo, so it lints Markdown, HTML, and plain text from memory with no server and no filesystem.

Each release carries `vale_<version>_wasm.tar.gz`, which holds two files:

| File           | What it is                                                                  |
| -------------- | --------------------------------------------------------------------------- |
| `vale.wasm`    | The module.                                                                 |
| `wasm_exec.js` | Go's loader, from the toolchain that built the module. Use the one shipped. |

## [Loading](#loading)

```html
<script src="wasm_exec.js"></script>
<script type="module">
  const go = new Go();
  const { instance } = await WebAssembly.instantiateStreaming(
    fetch("vale.wasm"),
    go.importObject,
  );
  go.run(instance); // installs the functions below on globalThis
</script>
```

`go.run` never resolves: the module stays resident, and its functions are called synchronously after it starts. Load it once, in a worker where one is available, since the first lint also loads the dictionary.

The module reads nothing from the host. `wasm_exec.js` answers every filesystem call with a stub, which is what the module expects; a Node host that installs its own `fs` global makes those calls asynchronous and the module stops. Leave `globalThis.fs` unset.

## [Functions](#functions)

Every function returns an object. A failure is `{ error: string }`; the shapes below are the success case.

| Function                                 | Returns               | What it does                                                                                                                                                       |
| ---------------------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `ValeLint(text, format, ini)`            | `{ result: string }`  | Lints `text` as a document named `input.<format>`, under the configuration in `ini`. `result` is the JSON the CLI prints with `--output=JSON`, keyed by that name. |
| `ValeSuggest(alertJSON, ini)`            | `{ suggestions: [] }` | Resolves one alert's [fix](/topics/actions): the replacement strings its action produces, or an empty list when it has none.                                       |
| `ValeLintBuilt(text, format, html, ini)` | `{ result: string }`  | Lints `text` from `html`, the host's own conversion of it; see [Converted formats](#converted-formats).                                                            |
| `ValeConverterOptions(format, ini)`      | object                | What to give the converter for `format` so its HTML is what Vale would have produced; see below.                                                                   |
| `ValeTransform(text, format, ini)`       | `{ result: string }`  | Applies the configuration's `BlockIgnores` and `TokenIgnores` to `text` and returns what is left to lint.                                                          |
| `ValeAddFile(path, contents)`            | `{ ok: true }`        | Writes one file into the in-memory filesystem. `contents` is a string or a `Uint8Array`.                                                                           |
| `ValeAddPackage(prefix, zipBytes)`       | `{ ok: true }`        | Extracts a ZIP archive under `prefix`, each entry at its own path.                                                                                                 |
| `ValeResetFS()`                          | `{ ok: true }`        | Empties the in-memory filesystem.                                                                                                                                  |
| `ValeListFS()`                           | `{ paths: [] }`       | Lists every path it holds.                                                                                                                                         |

`format` is an extension without the dot: `md`, `html`, `txt`. A `[formats]` association in the INI applies to it as it would to a file.

## [Assets](#assets)

The configuration is a string, and everything it names is resolved against an in-memory filesystem the host fills. Paths are absolute under a single root, so a `StylesPath` of `/styles` reads rules from `/styles/<Style>/<Rule>.yml`, vocabularies from `/styles/config/vocabularies/`, and dictionaries from `/styles/config/dictionaries/`, as it would on disk.

```js
ValeAddFile("/styles/House/Hedging.yml", ruleYAML);
ValeAddPackage("/styles", await (await fetch(packageURL)).arrayBuffer());

const ini = `
StylesPath = /styles
MinAlertLevel = suggestion

[*]
BasedOnStyles = Vale, House, Microsoft
`;
const { result, error } = ValeLint(text, "md", ini);
```

The built-in `Vale` style and its `en_US` dictionary are inside the module, so a configuration with no `StylesPath` lints with them alone. There is no user-level configuration: the INI passed is the whole configuration.

## [Formats](#formats)

| Format                                | In the browser            | How                                                                                           |
| ------------------------------------- | ------------------------- | --------------------------------------------------------------------------------------------- |
| Markdown, MDX, MyST, Quarto, Org-mode | Yes                       | Parsed in Go.                                                                                 |
| HTML                                  | Yes                       |                                                                                               |
| Plain text                            | Yes                       |                                                                                               |
| YAML, JSON, TOML                      | Yes                       | Through a [View](/topics/views).                                                              |
| AsciiDoc                              | With the host's converter | Asciidoctor.js; see below.                                                                    |
| XML                                   | With the host's converter | The browser's `XSLTProcessor` and the section's `Transform` stylesheet.                       |
| DITA, reStructuredText, Typst         | With the host's converter | When the host has one: DITA-OT and Docutils have no browser build; Typst does.                |
| Source code                           | No alerts                 | Comments are found with tree-sitter, which is CGo. The file is accepted and produces nothing. |

A document in an unsupported format is not an error: it lints as empty.

## [Converted formats](#converted-formats)

Natively, Vale reads AsciiDoc, reStructuredText, XML, DITA, and Typst by running a converter and linting its HTML. A browser can't run those programs, but it can run some of their JavaScript builds, so the module takes the HTML from the host instead: `ValeLintBuilt` is `ValeLint` with the conversion step already done. Everything after it is the same, including where each alert lands in the source.

The HTML has to be what Vale would have produced, which means the same arguments. `ValeConverterOptions` returns them, so the host and the CLI stay in step as they change:

| Format | Returns                                                                                                                                                                                                                                                                                      |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `adoc` | `attributes`, an array of Asciidoctor attributes including the `[asciidoctor]` section's; `standalone: false`; `safe: "secure"`; and `converter`, the source of a converter class to register, which renders a cross-reference the author gave no text as an empty link, as Vale's own does. |
| `rst`  | `args`, Docutils' command-line flags.                                                                                                                                                                                                                                                        |
| `xml`  | `params`, the stylesheet parameters, as name-value pairs.                                                                                                                                                                                                                                    |

With Asciidoctor.js 4:

```js
const asciidoctor = require("@asciidoctor/core");
const opts = ValeConverterOptions("adoc", ini);

const ValeHtml5Converter = new Function(`${opts.converter}; return ValeHtml5Converter;`)();
asciidoctor.ConverterFactory.register(new ValeHtml5Converter(asciidoctor), ["html5"]);

const html = await asciidoctor.convert(text, {
  standalone: opts.standalone,
  safe: opts.safe,
  attributes: opts.attributes,
});
const { result, error } = ValeLintBuilt(text, "adoc", String(html), ini);
```


# StylesPath

Learn where Vale keeps its resources, and how that directory is laid out.

{% hint style="info" %}
The `VALE_STYLES_PATH` environment variable replaces the default location described below.
{% endhint %}

`StylesPath` names the directory that holds everything Vale reads besides your content: the styles, and the `config/` directory of vocabularies, dictionaries, views, and other assets beside them. The value is a path, absolute or relative to the `.vale.ini` file that sets it, and it has to exist when the configuration loads. A path that isn't there is an error that points at the key.

```ini
# .vale.ini
# ci/
# └── vale/
#     └── styles/
StylesPath = ci/vale/styles

[*.md]
# House is a directory in ci/vale/styles.
BasedOnStyles = House
```

`vale sync` is the one command that creates the directory when it's missing, because installing a package is what fills it.

## [The default location](#the-default-location)

When no configuration file sets a `StylesPath`, Vale uses a directory in the operating system's data folder. `vale ls-dirs` prints the path on your system and whether it exists:

| OS      | Location                                        |
| ------- | ----------------------------------------------- |
| Windows | `%LOCALAPPDATA%\vale\styles`                    |
| macOS   | `$HOME/Library/Application Support/vale/styles` |
| Linux   | `$XDG_DATA_HOME/vale/styles`                    |

This directory is also read when a project does set its own path, as long as it exists. See [More than one path](#more-than-one-path).

## [Layout](#layout)

A `StylesPath` holds three kinds of entry: directories that are styles, one `config/` directory for everything that isn't a style, and a `.vale-config/` directory that `vale sync` writes.

```
styles/
├── House/                   a style you wrote
│   ├── Hedging.yml
│   ├── Terms.yml
│   └── dates/
│       └── TimeFormat.yml
├── Microsoft/               a style vale sync installed
│   ├── HeadingPunctuation.yml
│   └── ...
├── config/                  everything that isn't a style
│   ├── vocabularies/
│   │   └── Product/
│   │       ├── accept.txt
│   │       └── reject.txt
│   ├── dictionaries/
│   │   ├── en_medical.aff
│   │   └── en_medical.dic
│   ├── ignore/
│   │   └── jargon.txt
│   ├── actions/
│   ├── scripts/
│   ├── filters/
│   ├── templates/
│   └── views/
└── .vale-config/            written by vale sync
    └── 0-Microsoft.ini
```

Any directory that is not `config` or `.vale-config` is read as a style, so keep other files out of the top level.

### [Styles](#styles)

A style is a directory, and its name is the directory's name. Each `.yml` file in it is a rule, addressed as the style's name and the file's name joined with a dot: `Microsoft/HeadingPunctuation.yml` is `Microsoft.HeadingPunctuation` in configuration, in an in-text comment, in a filter, and in Vale's output.

Rules can be nested. A subdirectory joins the name with another dot, so `House/dates/TimeFormat.yml` is `House.dates.TimeFormat`, and any depth works the same way. Nesting is for organizing a large style; it doesn't change how a rule runs or what it inherits. The name is also how Vale finds a single rule that is switched on without its style: `House.dates.TimeFormat = YES` under a section opens `House/dates/TimeFormat.yml` directly.

Three things about what's inside a style:

* Only `.yml` files are rules. A README, a license, or a `.yaml` file is left alone.
* A directory whose name starts with `.` or `_` is skipped, along with everything under it. That is the place for drafts and retired rules.
* A rule's name can't contain `[` or `]`, because `Style.Rule[param]` is configuration syntax.

The [Styles](/topics/styles) page covers what goes in a rule.

### [The config directory](#the-config-directory)

`config/` holds what a rule or a configuration key refers to by name. Each subdirectory is reached from one place:

| Directory                                 | Holds                                                                                                                            | Reached by                                                                                                        |
| ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| [`vocabularies`](/keys/vocabularies)      | One directory per vocabulary, holding `accept.txt` and `reject.txt`. Subdirectories are walked, so a vocabulary can be split up. | `Vocab = Name` in `.vale.ini`.                                                                                    |
| [`dictionaries`](/checks/spelling)        | Hunspell pairs, `name.dic` beside `name.aff`, and the `.dict` models a `sequence` rule names.                                    | `dictionaries: [name]` in a `spelling` rule, or `model: name` in a `sequence` rule.                               |
| [`ignore`](/checks/spelling#ignore-files) | Plain-text word lists, one word per line, at any depth.                                                                          | `ignore: [file.txt]` in a `spelling` rule, relative to this directory.                                            |
| [`actions`](/fixes/suggest)               | Tengo scripts that compute a suggestion.                                                                                         | The script's name in a rule's `action`. A style's own directory is searched too, so a style can ship its scripts. |
| [`scripts`](/checks/script)               | Tengo scripts for `script` rules.                                                                                                | `script: Name.tengo` in the rule.                                                                                 |
| [`filters`](/topics/filters)              | Filter expressions, one per file.                                                                                                | The file's name on `--filter`.                                                                                    |
| [`templates`](/topics/templates)          | Output templates.                                                                                                                | The file's name on `--output`.                                                                                    |
| [`views`](/topics/views)                  | One View per `.yml` file.                                                                                                        | `View = Name` under a section.                                                                                    |

`vale sync` merges a package's `config/` into this one, directory by directory, so a package can carry a vocabulary or a view along with its style.

### [The pipeline directory](#the-pipeline-directory)

`.vale-config/` holds the `.vale.ini` files that packages carry, one per package, numbered in the order the [`Packages`](/keys/packages) key lists them. Vale reads them before your own configuration, so a package's settings are the base and yours are laid over them. The directory is deleted and rebuilt on every `vale sync`. Nothing in it is yours to edit.

## [More than one path](#more-than-one-path)

A run can have several paths on its search list, gathered in this order:

1. The default location, or `VALE_STYLES_PATH`, when the directory exists.
2. The `StylesPath` of the user-level `.vale.ini`, if that file sets one.
3. The `StylesPath` of the project's `.vale.ini`.

Each file sets one path, resolved against that file. `vale ls-config` prints the list under `Paths`, and `--no-global` drops the first two entries along with the user-level file.

A style, a vocabulary, a view, a script, an action, a template, or a filter is taken from the first path on the list that has it. So a style in the default location shadows one of the same name in the project, and a project can't override a shared style by copying it. Rename the copy instead.

The last path, the project's, is where `vale sync` installs packages and writes `.vale-config/`. It is also the only path a `spelling` rule reads `dictionaries` and `ignore` files from.

This is what lets a machine keep one copy of the shared styles for every project on it. Sync the packages into the default location once, from a configuration that lives there:

```ini
# ~/Library/Application Support/vale/.vale.ini
Packages = Microsoft, write-good
```

```console
$ vale sync --config "$HOME/Library/Application Support/vale/.vale.ini"
```

Then a project needs a path only for what is its own:

```ini
# .vale.ini
StylesPath = styles

[*.md]
BasedOnStyles = Microsoft, write-good, House
```

`Microsoft` and `write-good` are found in the default location, `House` in the project's `styles`, and `vale sync` in the project touches nothing shared.

## [Version control](#version-control)

Commit the styles you wrote and the `config/` directory. Everything `vale sync` writes, the installed styles and `.vale-config/`, is rebuilt from `Packages`, and most projects ignore it. The [Packages](/keys/packages#version-control) page shows a `.gitignore` that keeps the two apart.


# Packages

Learn how configuration is shared, installed, and kept up to date.

```ini
StylesPath = styles

Packages = Microsoft, write-good

[*.md]
BasedOnStyles = Microsoft, write-good
```

`Packages` lists what `vale sync` installs into the [`StylesPath`](/keys/stylespath). A package is a style, a configuration file, or both, published as an archive, and naming it here is all a project has to do to use it. Update the package, run `vale sync` again, and every project that names it has the new rules.

![One upstream package is inherited by several projects, so an upstream change reaches all of them. Within a project, configuration is layered: the first package is overridden by the second, and local configuration overrides both.](https://2533404145-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEMH7s0sC5N9L8xSMXGwW%2Fuploads%2Fgit-blob-85b00d5c4986cdb8561019246bb3b1b3374b1700%2Fpackage.svg?alt=media)

## [Naming a package](#naming-a-package)

Each entry in the list is one of four things:

* **A name from the library**, such as `Microsoft`. A name is looked up in the [package library](https://github.com/vale-cli/packages), which the [Package Explorer](https://vale.sh/explorer) is built from, and the URL it records is what gets installed.
* **A URL** to a `.zip` archive.
* **A path to a `.zip` archive** on disk.
* **A path to a directory** laid out like an unpacked archive.

A package's name is the archive's file name without `.zip`, or the directory's name, and the archive has to hold one folder with that name. `Google.zip` unpacks to `Google/`. A name that isn't in the library is tried as a path and then as a URL, so a misspelled name reports itself as a URL that couldn't be fetched.

Relative paths are resolved from the directory `vale sync` runs in, not from the configuration file.

## [What sync does](#what-sync-does)

`vale sync` reads `Packages` from one configuration file, the one the search finds or the one `--config` names, and installs each entry in order. In doing so it:

1. Creates the `StylesPath` if it doesn't exist, and deletes the `.vale-config/` directory inside it.
2. Fetches or unpacks each package, and copies what it holds into the `StylesPath`. A style already there with the same name is replaced whole, so an installed style is not the place to make edits.
3. Merges the package's `config/` into yours, one entry at a time: a vocabulary or dictionary the package carries lands beside the ones you have, and one with the same name replaces yours.
4. Copies the package's `.vale.ini`, if it has one, into `.vale-config/`, named for its position in the list. A package that lists packages of its own has those installed too.

Run it after adding a package, and in CI before linting. `--plain-progress` prints a line per package instead of a progress bar, for logs.

```console
$ vale sync --plain-progress
Synced Microsoft
Synced write-good
 SUCCESS  Synced 2 package(s) to '/home/me/docs/styles'.
```

## [Shapes](#shapes)

A package holds a style, a configuration file, or both.

### [A style](#a-style)

An archive of one style directory:

```console
$ unzip write-good.zip
Archive:  write-good.zip
   creating: write-good/
  inflating: write-good/README.md
  inflating: write-good/Cliches.yml
  inflating: write-good/ThereIs.yml
  inflating: write-good/Weasel.yml
  ...
  inflating: write-good/meta.json
```

The directory is copied into the `StylesPath`, and `BasedOnStyles = write-good` switches it on.

### [A configuration](#a-configuration)

An archive of a `.vale.ini` alone, which is how support for a markup convention or a site generator is shared:

```console
$ unzip Hugo.zip
Archive:  Hugo.zip
   creating: Hugo/
  inflating: Hugo/.vale.ini
```

The file lands in `.vale-config/` and is read before your own, so its settings are the base yours sit on. Nothing in it needs to be repeated locally.

### [Both](#both)

A `.vale.ini` beside a `styles/` directory that is a `StylesPath` of its own:

```
MyPackage/
├── .vale.ini
└── styles/
    ├── MyStyle/
    │   └── MyRule.yml
    └── config/
        ├── dictionaries/
        │   └── MyDic.dic
        ├── scripts/
        │   └── MyScript.tengo
        └── vocabularies/
            └── MyVocab/
                ├── accept.txt
                └── reject.txt
```

The directory has to be named `styles`, and it can hold anything a `StylesPath` can, styles and the `config/` directory alike. Its `.vale.ini` refers to it, switches its rules on, and can list further packages:

```ini
# MyPackage/.vale.ini
StylesPath = styles

Packages = proselint

[*.{md,adoc}]
BasedOnStyles = MyStyle
```

On sync, `styles/` is merged into the project's `StylesPath` and the `.vale.ini` goes to `.vale-config/`.

### [A vocabulary](#a-vocabulary)

{% hint style="info" %}
Requires Vale v3.25.0 or later.
{% endhint %}

A package can carry a [vocabulary](/keys/vocabularies) and turn it on for every file by naming it in `[*]`:

```
MyTerms/
├── .vale.ini
└── styles/
    └── config/
        └── vocabularies/
            └── MyTerms/
                └── accept.txt
```

```ini
# MyTerms/.vale.ini
[*]
Vocab = MyTerms
```

Its terms add to the project's own vocabularies, and its rules, `Vale.MyTerms.Terms` and `Vale.MyTerms.Avoid`, are on for every file. A project can switch them off by name, or drop the vocabulary with `Vocab = NONE` in its own `[*]`:

```ini
[*]
BasedOnStyles = Vale
# Accept the package's terms, but don't enforce their case:
Vale.MyTerms.Terms = NO
```

## [Pinning a version](#pinning-a-version)

Naming a package installs its latest release, so the rules it brings change as the package is updated. To hold a package at a known version, give the release URL in place of the name:

```ini
StylesPath = styles

Packages = https://github.com/vale-cli/Google/releases/download/v0.7.0/Google.zip

[*.md]
BasedOnStyles = Google
```

A style takes its name from the folder inside the archive, so `BasedOnStyles` reads the same either way. Only where the package comes from changes. Run `vale sync` again after editing the URL to move to a different version.

This is worth doing wherever a new rule arriving on its own would be disruptive, such as a repository several people write in, or a CI job that fails on new alerts.

## [Ordering](#ordering)

Configuration is read in this order: each package's `.vale.ini` in the order `Packages` lists them, then your own file.

```ini
Packages = pkg1, pkg2

[*.md]
BasedOnStyles = House
```

A list key such as `BasedOnStyles` gathers every value from every source, so the styles `pkg1` and `pkg2` switch on run alongside `House`. Any other key takes the last value read: `MinAlertLevel`, a rule's level, a parameter, and a switch all end up as your file has them, and `pkg2` has them as it does where your file is silent.

## [The version Vale supports](#the-version-vale-supports)

{% hint style="info" %}
Requires Vale v3.23.0 or later.
{% endhint %}

A package's `meta.json` names the Vale it needs, as `vale_version`. When the release sync downloads needs a newer Vale than the one running, sync installs the newest release this Vale does support instead, and says so:

```console
$ vale sync
 INFO  'Readability' needs Vale >=3.23.0; this is 3.22.0: installed v0.1.1 instead.
```

If no release supports the running Vale, sync installs the latest one and warns, which is what it always did; the metadata can only improve on that. A package pinned to a release URL is installed as asked, with the same warning when it needs a newer Vale.

This is what lets a package raise the Vale it needs without breaking anyone: an older Vale keeps the last release that worked for it. Sync finds the earlier releases one of two ways:

* **A manifest.** `meta.json` lists the releases, each with its version, the Vale it needs, and its archive URL. The URLs can point anywhere, so this works on any host, and sync downloads only the release it picks:

  ```json
  {
    "vale_version": ">=3.23.0",
    "releases": [
      {"version": "v0.3.0", "vale_version": ">=3.23.0", "url": "https://example.com/readability/v0.3.0/Readability.zip"},
      {"version": "v0.2.0", "vale_version": ">=2.13.0", "url": "https://example.com/readability/v0.2.0/Readability.zip"}
    ]
  }
  ```
* **The release feed.** Without a manifest, a package served from a GitHub `releases/latest/download` URL has its releases read from the repository's Atom feed, named in `meta.json` as `feed` or derived from the package's URL, and the ten most recent are tried newest first until one fits.

## [Publishing a package](#publishing-a-package)

A package is a release asset: a `.zip` whose single top-level folder is the package's name, attached to a release of a Git repository. The `releases/latest/download` URL then always points at the newest one. To make a package installable by name, add an entry for it to the [library](https://github.com/vale-cli/packages) in a pull request. The repository's README describes the entry.

## [Version control](#version-control)

Commit the styles you wrote and the `config/` directory. Everything `vale sync` writes, the installed styles and `.vale-config/`, is rebuilt from `Packages`, so most projects ignore it. Ignoring the whole `StylesPath` is simplest when nothing in it is yours. When some of it is, ignore selectively:

```gitignore
# Ignore the StylesPath except for our own vocabulary.

.github/styles/*
!.github/styles/config/

.github/styles/config/*
!.github/styles/config/vocabularies/

.github/styles/config/vocabularies/*
!.github/styles/config/vocabularies/Base
```

This ignores everything under `.github/styles/` except `.github/styles/config/vocabularies/Base`.


# Vocabularies

Learn about Vale's terminology management system.

Vocabularies allow you to maintain custom lists of terminology independent of your styles.

```ini
StylesPath = "..."

# Here's were we define the exceptions to use in *all*
# `BasedOnStyles`.
Vocab = Some-Name

[*]
# 'Vale' and 'MyStyle' automatically respect all
# custom exceptions.
#
# The built-in 'Vale' style is required for using
# `Vale.Terms`, `Vale.Avoid`, or `Vale.Spelling`.
BasedOnStyles = Vale, MyStyle
```

Each `Vocab` is a single folder (stored at `<StylesPath>/config/vocabularies/<name>/`) consisting of two plain-text files—`accept.txt` and `reject.txt`—that contain one regular expression per line.

The effects of using a custom `Vocab` are as follows:

* Entries in `accept.txt` are added to every exception list in all styles listed in `BasedOnStyles`—meaning that you now only need to update your project’s *vocabulary* to customize third-party styles.
* Entries in `accept.txt` are automatically added to a substitution rule (`Vale.Terms`), ensuring that any occurrences of these words or phrases exactly match their corresponding entry in `accept.txt`.
* Entries in `reject.txt` are automatically added to an existence rule (`Vale.Avoid`) that will flag all occurrences as errors.
* Entries in `accept.txt` and `reject.txt` should need little overlap, if any. For example, if you add `JavaScript` to `accept.txt`, then you do not need to add an overlapping regular expression entry of `[Jj]avascript` in `reject.txt`. Vale will enforce correct casing by virtue of the entry’s presence in `accept.txt`. See the section “Case sensitivity” for details.

This means that your exceptions can be developed independent of a style, allowing you to use the same exceptions with multiple styles or switch styles without having to re-implement them.

{% hint style="warning" %}
In versions of Vale prior to 3.0, vocabularies were stored in `<StylesPath>/Vocab`. When upgrading from an older version of Vale, you'll need to move your vocabularies to the new `<StylesPath>/config/vocabularies` location.
{% endhint %}

Vocabulary entries are stored in `<StylesPath>/config/vocabularies/<name>/` and are then referenced by `<name>` in `.vale.ini`. For example, consider the following folder structure:

```
$ tree styles
├───MyStyle
├───config
│   └───vocabularies
│       ├───Blog
│       │   ├───accept.txt
│       │   └───reject.txt
│       └───Marketing
│           ├───accept.txt
│           └───reject.txt
└───MyOtherStyle
```

Here, our `StylesPath` (`/styles`) contains two styles (`MyStyle` and `MyOtherStyle`) and two vocabularies (`Blog` and `Marketing`). You can then reference these entries by their folder name:

```ini
StylesPath = styles

Vocab = Blog

[*]
BasedOnStyles = Vale, MyStyle
```

## Vocabularies for a section

{% hint style="info" %}
Requires Vale v3.22.0 or later.
{% endhint %}

A section can name a vocabulary of its own, for the files it matches:

```ini
StylesPath = styles
Vocab = Product

[*]
BasedOnStyles = Vale

[api/*.md]
Vocab = API
```

Under `api/`, the accepted terms of `API` are added to those of `Product`; elsewhere only `Product` applies. Several matching sections add up. A section's vocabulary gets rules of its own, `Vale.API.Terms` and `Vale.API.Avoid`, which run only on that section's files and can be switched off by name like any other rule.

A vocabulary named in `[*]` applies to every file, with rules of its own that are on everywhere; this is how a [package](/keys/packages#a-vocabulary) turns on the vocabulary it carries.

`Vocab = NONE` clears the vocabularies named before it at the same level: at the top of the file, the top-level ones from earlier configuration layers, such as a package's; in a section, that section's.

## File format

Both `accept.txt` and `reject.txt` are plain-text files that take one entry per line:

```
first
[pP]y.*\b
third
```

The entries are evaluated as case-sensitive (except for rules extending `spelling`, as mentioned above) regular expressions, with two allowances for how terms are written. A period is text, not a wildcard, unless the entry uses some other regular-expression construct, so `Node.js` accepts `Node.js` and nothing else. And the space in a phrase matches a line break as well, so `mea culpa` is found where a paragraph wraps between the two words.

A line beginning with `#` and a space is a comment and is ignored; `#tag` on its own is an entry.

An alert from `Vale.Terms` offers the entry's spelling as a [fix](/topics/actions) when it is plain text, and the terms are candidates when a `spelling` rule [suggests](/fixes/suggest#spellings) a correction, ahead of the dictionary's own words.

## Case sensitivity

An important factor in successfully implementing a custom vocabulary is understanding how Vale handles case sensitivity.

While most spell-checking tools ignore case altogether, Vale’s vocabulary files are case-aware by default. This means that, for example, a vocabulary consisting of

```
MongoDB
```

will enforce the *exact* use of “MongoDB”: “mongoDB,” “MongoDb,” etc., will all result in errors. There are two ways around this.

First, you can indicate that a given entry should be case-insensitive by providing an appropriate regular expression:

```
(?i)MongoDB
[Oo]bservability
```

The first entry, `(?i)MongoDB`, marks the entire pattern as case-insensitive while the second, `[Oo]bservability`, provides two acceptable options.

You can also disable `Vale.Terms` and just use `Vale.Spelling`:

```ini
[*.md]
BasedOnStyles = Vale

Vale.Terms = NO
```

This will provide a more traditional spell-checking experience.

## Dictionaries and vocabularies

A [dictionary](/checks/spelling#dictionaries) and a vocabulary answer different questions:

|               | Dictionary                                                                      | Vocabulary                                              |
| ------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------- |
| Answers       | Is this a word?                                                                 | How do we write it here?                                |
| Case          | Permissive: a lower-case entry also accepts its capitalized and all-caps forms. | Exact: `Vale.Terms` reports any other case, with a fix. |
| Rejects terms | No                                                                              | Yes, with `Vale.Avoid`                                  |
| Read by       | `spelling` rules                                                                | Every rule in `BasedOnStyles`, as exceptions            |
| Maintained by | Vale, or the dictionary's publisher                                             | Your project                                            |

Put ordinary words, including technical ones like `failover` or `namespace`, in a dictionary or let the built-in one cover them. Put names and terms with a required spelling, like `PostgreSQL` or your product's name, in a vocabulary: it accepts them, enforces their case, and offers the fix.

## Relation to ignore files

The functionality of vocabularies is similar to the existing concept of [ignore files](/checks/spelling#ignore-files).

The major differences are that vocabularies apply to multiple extension points (rather than just `spelling`), support regular expressions, and have built-in rules associated with them (`Vale.Terms` and `Vale.Avoid`).

In general, this means that ignore files are for style *creators* while vocabularies are for style *users*:

* If you’re developing or maintaining a style, you may still want to include a custom `spelling` rule—`MyStyle.Spelling`—that packages its own ignore files.
* As a user of styles, vocabularies should be able to replace the use of ignore files completely.

## Rules targeting vocabulary entries

In cases where you want to write a rule that needs to match against an otherwise-ignored token, you can add `vocab: false` to the rule definition. For example,

```yaml
extends: existence
message: Did you mean '%s'?
vocab: false
tokens:
  # "MonoDB" can be in a vocab
  - MongoDB
```


# MinAlertLevel

Learn about how to set the minimum alert level for Vale.

```ini
StylesPath = styles
MinAlertLevel = suggestion

[*.md]
BasedOnStyles = Vale
```

The `MinAlertLevel` key allows you to set the minimum alert level that Vale will report. The supported levels are `suggestion` (default), `warning`, and `error`.

`error`-level alerts will result in a [non-zero exit code](/topics/cli#return-codes), while `warning`- and `suggestion`-level alerts will not. This is useful for controlling which rules will fail CI builds.

## [Overriding](#overriding)

The `MinAlertLevel` key can be overridden from the command line using the `--minAlertLevel` flag:

```bash
$ vale --minAlertLevel=warning README.md
```

This allows you to, for example, show all alerts in your editor while only running `error`-level alerts in CI.

## [Editing](#editing)

You can edit the severity of a rule by modifying its `level` in your local `.vale.ini` file:

```ini
[*.md]
BasedOnStyles = Vale

Vale.Spelling = warning
```


# IgnoredScopes

Learn about how to ignore inline-level HTML tags.

```ini
StylesPath = styles

IgnoredScopes = code, tt

[*.md]
BasedOnStyles = Vale
```

`IgnoredScopes` specifies inline-level HTML tags to ignore. In other words, these tags may occur in an active scope (unlike `SkippedScopes`, which are skipped entirely) but their content still won’t raise any alerts.

By default, Vale ignores `tt`, `code`, `kbd`, and `samp` tags. Setting this key **replaces** that list rather than adding to it, so include the defaults you still want. For example, considering the following Markdown file:

```markdown
This is a sentence that contains inline `code`.
```

Vale will not raise any alerts for the content within the backticks, such as `code` in the example above.

The key takes scope names as well as tags. In source code, `comment` skips every comment and `text.comment.doc` skips documentation comments alone; see [Code](/formats/code).

See [Markup](/topics/scopes) for more information.


# IgnoredClasses

Learn about how to ignore HTML classes.

```ini
StylesPath = styles

IgnoredClasses = my-class, another-class

[*.md]
BasedOnStyles = Vale
```

`IgnoredClasses` names HTML classes whose content Vale won't lint. The classes may appear on inline- or block-level elements.

By default, Vale ignores `problematic`, `pre`, and `code`.

{% hint style="info" %}
Unlike [`IgnoredScopes`](/keys/ignoredscopes) and [`SkippedScopes`](/keys/skippedscopes), which replace their defaults when you set them, `IgnoredClasses` **adds** to the list. The three defaults above stay ignored whatever you set.
{% endhint %}

This is most useful for content a documentation tool generates, where the markup carries classes the prose doesn't control:

```ini
# Sphinx marks unresolved references this way; there's no point
# spell-checking them.
IgnoredClasses = problematic, guilabel, menuselection
```

Because this key matches classes rather than tags, it's the one to reach for when what you want to skip isn't a distinct element—an inline `<span>` among other `<span>`s, say.


# SkippedScopes

Learn about how to ignore block-level HTML tags.

```ini
StylesPath = styles

SkippedScopes = script, style, pre

[*.md]
BasedOnStyles = Vale
```

`SkippedScopes` specifies block-level HTML tags to ignore. Any content in these scopes will be ignored.

By default, Vale ignores `script`, `style`, `pre`, `figure`, `noscript`, and `iframe` tags. Setting this key **replaces** that list rather than adding to it, so include the defaults you still want. For example, considering the following Markdown file:

````markdown
This is a sentence that contains normal text.

```python
# This is a code block.
print("Hello, world!")
```

Another normal sentence.
````

Vale will not raise any alerts for the content within the code block.

See [Markup](/topics/scopes) for more information.


# BasedOnStyles

Learn how to enable a style for a specific file type.

```ini
StylesPath = styles

[*.md]
BasedOnStyles = Vale, MyStyle
```

`BasedOnStyles` enables every rule in the named styles for the files a section matches.

It's a section-level setting, so it has to appear under a glob. Putting it above the first section is an error:

```
'BasedOnStyles' is a syntax-specific option
```

To apply styles to everything, use the catch-all section:

```ini
[*]
BasedOnStyles = Vale
```

## [More than one section](#more-than-one-section)

When several sections match a file, the last one written takes it: its `BasedOnStyles` **replaces** the others rather than adding to them.

```ini
[*]
BasedOnStyles = Vale

[*.md]
# Markdown files get Microsoft *instead of* Vale, not as well as.
BasedOnStyles = Microsoft
```

If you want the defaults plus something extra, name them all:

```ini
[*]
BasedOnStyles = Vale

[*.md]
BasedOnStyles = Vale, Microsoft
```

## [Individual rules](#individual-rules)

Rules can be switched on or off by name, and unlike `BasedOnStyles`, these settings **accumulate** across every section that matches:

```ini
[*]
BasedOnStyles = Vale

[*.md]
# Markdown keeps everything from Vale, and adds one rule
# from a style that isn't otherwise enabled.
Microsoft.Contractions = YES
```

That's the way to extend your defaults for one file type without repeating them.

Turning a rule off works the same way:

```ini
[*.md]
BasedOnStyles = Vale, MyStyle

Vale.Spelling = NO
```

A rule can also be enabled on its own, without its style being listed at all:

```ini
[*.md]
# Only this rule runs, not the rest of Style1.
Style1.Rule = YES
```

## [Starting over](#starting-over)

{% hint style="info" %}
Requires Vale v3.22.0 or later.
{% endhint %}

An empty `BasedOnStyles` clears everything a file inherited from earlier sections, the rule settings as well as the styles, so the section starts from nothing:

```ini
[*]
BasedOnStyles = write-good
write-good.Weasel = warning

[*.py]
# No styles, and no inherited Weasel setting either.
BasedOnStyles = NONE
```

`NONE` is the spelled-out form of an empty value, and requires Vale v3.24.0 or later; `BasedOnStyles =` with nothing after it does the same. A file left with no rule to run isn't read at all.

A rule named in that section still runs. To drop one inherited setting rather than all of them, set it to `UNSET`: the rule follows `BasedOnStyles` again, as if the earlier line had not been written.

```ini
[*]
write-good.Weasel = YES

[*.py]
write-good.Weasel = UNSET
```

`UNSET` works on a style name too, `write-good = UNSET`, and leaves the settings of individual rules in place.

## [Severity](#severity)

The same syntax sets a rule's level, which overrides whatever the rule file declares:

```ini
[*.md]
BasedOnStyles = Vale

Vale.Spelling = warning
```

{% hint style="info" %}
Setting a level for a whole style requires Vale v3.17.0 or later.
{% endhint %}

A bare style name sets the default for every rule in that style, and a rule naming itself still wins:

```ini
[*.md]
BasedOnStyles = proselint

# Everything in proselint is a suggestion ...
proselint = suggestion
# ... except this one.
proselint.Typography = warning
```

This is the concise way to keep one part of a style while turning the rest down—or off:

```ini
proselint = NO
proselint.Typography = YES
```

See [MinAlertLevel](/keys/minalertlevel) for how levels affect Vale's exit code.

## [Parameters](#parameters)

{% hint style="info" %}
Bracketed parameters require Vale v3.20.0 or later.
{% endhint %}

A bracketed key overrides one scalar on a rule you have enabled, keeping the rule's identity:

```ini
[*.md]
BasedOnStyles = Std

# The rule stays Std.SentenceLength; only its threshold moves.
Std.SentenceLength[max] = 30
```

Values are coerced to the field's type at compile time, and an unknown parameter fails the rule's compilation rather than being silently dropped.

Only scalars can be tuned this way. A structural field — `tokens`, `swap`, `message` — is refused with a pointer at [extending the rule](/topics/styles#extending-another-rule) in a style, which is what authoring is for. `[level]` is refused too: the classic `Style.Rule = warning` syntax already says that.


# BlockIgnores

Learn how to define custom block-level ignores in your Vale configuration.

{% hint style="info" %}
`BlockIgnores` are supported in AsciiDoc, HTML, Markdown, MDX, MyST, Org Mode, QDoc, Quarto, reStructuredText, and Typst. MyST, QDoc, Quarto, and Typst require Vale v3.18.0 or later; HTML requires v3.21.0 or later.

They work by wrapping each match in the format's block code delimiter, so they need a markup format to wrap it with. In a source code file they have no effect—but associating a markup format with your comments makes them available. See [Code](/formats/code#associations).
{% endhint %}

```ini
StylesPath = styles

[*.md]
BasedOnStyles = Vale

BlockIgnores = (?s) *({< file [^>]* >}.*?{</ ?file >})
```

`BlockIgnores` allow you to exclude certain block-level sections of text that don’t have an associated HTML tag that could be used with [`SkippedScopes`](/keys/skippedscopes).

The idea is to write a regular expression that captures the entire block in the first grouping. Several patterns are separated by commas, so a comma inside a pattern is written `\,`. See this [regex101 session](https://regex101.com/r/mFM0kZ/1/) for a more thorough explanation.

A section can be keyed on a path as well as an extension, which narrows the patterns to one part of the project:

```ini
[docs/api/*.md]
BlockIgnores = (?s)(<!-- generated -->.*?<!-- /generated -->)
```

The pattern matches the file as Vale was asked for it, so write it relative to where you run Vale. See [Globbing](/guides/globbing).


# TokenIgnores

Learn how to define custom inline-level ignores in your Vale configuration.

{% hint style="warning" %}
`TokenIgnores` are supported in AsciiDoc, HTML, Markdown, MDX, MyST, Org Mode, QDoc, Quarto, reStructuredText, and Typst. MyST, QDoc, Quarto, and Typst require Vale v3.18.0 or later; HTML requires v3.21.0 or later.

They work by wrapping each match in the format's inline code delimiter, so they need a markup format to wrap it with. In a source code file they have no effect—but associating a markup format with your comments makes them available. See [Code](/formats/code#associations).
{% endhint %}

```ini
StylesPath = styles

[*.rst]
BasedOnStyles = Vale

TokenIgnores = (:math:`.*`), (:ref:`.*`)
```

`TokenIgnores` allow you to exclude certain inline-level sections of text that don’t have an associated HTML tag that could be used with [`IgnoredScopes`](/keys/ignoredscopes).

The idea is to write a regular expression that captures the entire token in the first grouping. Several patterns are separated by commas, so a comma inside a pattern is written `\,`. See this [regex101 session](https://regex101.com/r/3Raecd/1) for a more thorough explanation.

A section can be keyed on a path as well as an extension, which narrows the patterns to one part of the project:

```ini
[docs/api/*.md]
TokenIgnores = (\{\{[^}]+\}\})
```

The pattern matches the file as Vale was asked for it, so write it relative to where you run Vale. See [Globbing](/guides/globbing).

{% hint style="info" %}
Dollar math needs no `TokenIgnores`: `$x^2$` and `$$…$$` are ignored in Markdown, Quarto, MyST, and MDX. See [Math](/formats/markdown#math).
{% endhint %}


# CommentDelimiters

Learn how to define custom comment delimiters.

`CommentDelimiters` allow you to override standard HTML comment delimiters (`<!-- foo -->`).

They are for a format whose tools do not accept an HTML comment, so that comment-based configuration can be written in a form it does. Before a file is parsed, every occurrence of the first delimiter becomes `<!--` and every occurrence of the second becomes `-->`, so the two must differ and neither should appear in the prose for any other reason.

```ini
[*.md]
BasedOnStyles = Vale

CommentDelimiters = {/*, */}
```

```markdown
{/* vale off */}

This is some text ACT test

{/* vale on */}

{/* vale Style.Redundancy = NO */}

This is some text ACT test

{/* vale Style.Redundancy = YES */}
```

The key applies to the formats that take [`BlockIgnores`](/keys/blockignores). The formats with a comment syntax of their own, MDX, AsciiDoc, reStructuredText, and Org among them, need none of this; see each [format](/formats/markdown) for the form it reads.


# Transform

Learn about how to convert XML to HTML for linting.

```ini
StylesPath = styles

[*.xml]
BasedOnStyles = Vale

Transform = docbook-xsl-snapshot/html/docbook.xsl
```

`Transform` names a version 1.0 XSL Transformation (XSLT) that converts the matched files to HTML. Vale lints the result, following the same rules it uses for [HTML](/formats/html).

It applies to the files Vale reads as XML, `.xml` and `.xsd`, and it is a section-level setting, so different parts of a project can use different stylesheets:

```ini
[manuals/*.xml]
Transform = docbook/html.xsl

[schemas/*.xsd]
Transform = xsd/html.xsl
```

DITA has a converter of its own and does not use this key; see [DITA](/formats/dita).

## [Paths](#paths)

A relative path is resolved against **the directory holding your `.vale.ini`**—not the working directory, and not your `StylesPath`. A leading `~` is expanded.

```ini
# Both of these are relative to .vale.ini:
Transform = xsl/docbook.xsl
Transform = ../shared/docbook.xsl

# Absolute and home-relative paths work too:
Transform = ~/xsl/docbook.xsl
```

## [Requirements](#requirements)

The conversion is performed by [`xsltproc`](http://xmlsoft.org/XSLT/xsltproc.html), which has to be installed and on your `$PATH`. See [XML](/formats/xml) for how to install it.


# WordTemplate

Learn about how to change what Vale considers a word boundary.

```ini
StylesPath = styles
MinAlertLevel = suggestion

WordTemplate = (?m)(?:%s)

[*.md]
BasedOnStyles = Vale
```

`WordTemplate` sets the pattern Vale wraps a rule's tokens in. The default is:

```
(?m)\b(?:%s)\b
```

`%s` is where the rule's tokens are inserted, and `\b` on either side is what stops `cat` from matching inside `concatenate`. Rules built from token lists—[`existence`](/checks/existence), [`substitution`](/checks/substitution), [`consistency`](/checks/consistency), and [`sequence`](/checks/sequence)—all use it.

## [When you need it](#when-you-need-it)

`\b` is defined as the boundary between a word character (`[0-9A-Za-z_]`) and anything else. Scripts written outside that set have no word characters at all, so the boundary never occurs and **the rule matches nothing**:

```yaml
extends: existence
message: "Found it."
level: error
tokens:
  - 世界
```

Given the text `你好世界朋友`, that rule reports nothing—and reports nothing for `hello 世界 there` too, where the term is surrounded by spaces. There is no error and no warning; the rule is simply silent.

Setting a template without `\b` fixes it:

```ini
WordTemplate = (?m)(?:%s)
```

```
 a.md
 1:3  error  Found it.  Test.Term
```

{% hint style="warning" %}
This is a global setting: it applies to every token-based rule in every style you load. Dropping `\b` means Latin-script tokens start matching inside longer words, so this is for projects whose content is genuinely in a script `\b` can't handle—not a per-rule adjustment.
{% endhint %}

## [Per-rule alternatives](#per-rule-alternatives)

If only some rules need different boundaries, leave `WordTemplate` alone and use the rule's own keys instead:

* `nonword: true` drops the boundaries for one rule.
* `raw` lets a rule supply its pattern verbatim, boundaries included.


# View

Learn about how to lint only part of a structured file.

```ini
StylesPath = styles

[*.json]
BasedOnStyles = Vale

View = MyView
```

`View` names a View to apply to the matched files. A View is a set of steps that pull named [scopes](/topics/scopes) out of a file Vale can’t otherwise parse—the descriptions in an OpenAPI spec, the docstrings in a source file, the subject and body of a commit message—so that Vale lints those and ignores the rest.

The named View is loaded from `<StylesPath>/config/views/<name>.yml`, and Vale reports an error at startup if it isn't there.

`View` is set per section, so different file types can be filtered differently.

See [Views](/topics/views) for how to write one.


# existence

Learn about the existence extension point.

| Name         | Type     | Description                                                                                                      |
| ------------ | -------- | ---------------------------------------------------------------------------------------------------------------- |
| `affix`      | `string` | Names an affix file, so a `word/FLAGS` token matches every form its flags name. See [Affix flags](#affix-flags). |
| `append`     | `bool`   | Adds `raw` to the end of `tokens`, assuming both are defined.                                                    |
| `ignorecase` | `bool`   | Makes all matches case-insensitive.                                                                              |
| `nonword`    | `bool`   | Removes the default word boundaries (`\b`).                                                                      |
| `action`     | `array`  | Options for correcting matches, see the [actions](/topics/actions) section.                                      |
| `raw`        | `array`  | A list of tokens to be concatenated into a pattern.                                                              |
| `tokens`     | `array`  | A list of tokens to be transformed into a non-capturing group.                                                   |
| `exceptions` | `array`  | An array of strings to be ignored.                                                                               |
| `vocab`      | `bool`   | If false, disables all active [vocabularies](/keys/vocabularies) for this rule (default: true).                  |

The most general extension point is existence. As its name implies, it looks for the “existence” of particular tokens.

```yaml
extends: existence
message: Consider removing '%s'
level: warning
ignorecase: true
tokens:
  - appears to be
  - arguably
```

> This is arguably the best option, and it appears to be stable.

```bash
test.md:1:9:style.Hedging:Consider removing 'arguably'
test.md:1:42:style.Hedging:Consider removing 'appears to be'
```

These tokens can be anything from simple phrases (as in the above example) to regular expressions—e.g., [the number of spaces between sentences](https://github.com/vale-cli/vale/blob/master/testdata/styles/demo/Spacing.yml) or [the position of punctuation after quotes](https://github.com/vale-cli/Google/blob/master/Google/Quotes.yml).

### [tokens](#tokens)

The most common entry point for this extension point is the `tokens` key, which is a list of strings or regular expressions to be transformed into a word-bounded, non-capturing group:

```yaml
tokens:
  - appears to be
  - arguably
```

Which, after compilation, becomes:

```regex
(?i)(?m)\b(?:appears\s+to\s+be|arguably)\b
```

This is a convenience feature to avoid having to write the same boilerplate for every token in a rule.

A space between words matches a line break too, so a phrase is found however the paragraph was wrapped, and the alert's message shows it on one line. A space inside a character class, after a backslash, or ahead of a quantifier such as `{2,}` keeps its meaning.

### [raw](#raw)

When you want more control over the regular expression, you can use the `raw` key instead:

```yaml
extends: existence
message: "Incorrect use of symbols in '%s'."
ignorecase: true
raw:
  - $[d]* ?(?:dollars|usd|us dollars)
```

This allows you to write more complex patterns without having to worry about any post-processing. Each entry in `raw` is concatenated with the previous entry, allowing for improved commenting and readability of complex patterns.

### [message](#message)

The `message` key is a string that will be used to generate the final message when a match is found. The (optional) `%s` placeholder will be replaced with the matched text. A rule with an [action](/topics/actions) may use a second `%s`, which is the fix.

## Affix flags

{% hint style="info" %}
Requires Vale v3.25.0 or later.
{% endhint %}

With `affix` set, a token written as `word/FLAGS` matches every form the Hunspell [affix flags](/guides/hunspell) name, instead of a hand-written pattern:

```yaml
extends: existence
message: Avoid '%s'.
affix: en_US-web
tokens:
  - leverage/DGS        # leverage, leveraged, leverages, leveraging
  - jump/DGS the gun    # jump the gun, jumped the gun, ...
```

In a phrase, each word with flags expands on its own, and the rest of the token is a pattern as usual. A `\b` on either side of a flagged word stays a word boundary.

`en_US-web` is the affix file of Vale's built-in dictionary. Any other name is looked up as a `.aff` file in `<StylesPath>/config/dictionaries/`, then in `$DICPATH`, as a spelling rule's [dictionaries](/checks/spelling) are.

In a rule that sets `affix`, an unescaped `/` in a space-separated word of a token separates the word from its flags, and the word must be plain text. Write a literal slash as `\/`: `and\/or`. A token without a slash is a pattern as usual.


# substitution

Learn about the substitution extension point.

| Name         | Type     | Description                                                                                                                     |
| ------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `affix`      | `string` | Names an affix file, so a `word/FLAGS` key matches every form its flags name. See [Affix flags](/checks/existence#affix-flags). |
| `ignorecase` | `bool`   | Makes all matches case-insensitive.                                                                                             |
| `matchcase`  | `bool`   | Adapts the replacement to the case of the matched text, so a rule written as `A-OK` still suggests `a-ok` for `a ok`.           |
| `nonword`    | `bool`   | Removes the default word boundaries (`\b`).                                                                                     |
| `swap`       | `map`    | A sequence of `observed: expected` pairs.                                                                                       |
| `exceptions` | `array`  | An array of strings to be ignored.                                                                                              |
| `vocab`      | `bool`   | If false, disables all active vocabularies for this rule (default: true).                                                       |
| `capitalize` | `bool`   | Matches the capitalization of the source token.                                                                                 |

`substitution` associates a string with a preferred form.

```yaml
extends: substitution
message: Consider using '%s' instead of '%s'
level: warning
ignorecase: false
# swap maps tokens in form of bad: good
swap:
  abundance: plenty
  accelerate: speed up
```

> There is an abundance of ways to accelerate this.

```bash
test.md:1:13:style.Preferred:Consider using 'plenty' instead of 'abundance'
test.md:1:34:style.Preferred:Consider using 'speed up' instead of 'accelerate'
```

If we want to suggest the use of “plenty” instead of “abundance,” for example, we’d write:

```yaml
swap:
  abundance: plenty
```

A space between words in a key matches a line break too, so a phrase is found however the paragraph was wrapped, and the alert's message shows it on one line.

## Regex keys

The keys may also be regular expressions:

```yaml
swap:
  '(?:give|gave) rise to': lead to
```

You can also reference capture groups for more dynamic substitutions:

```yaml
swap:
  'within the (\w+) directory': in the $1 directory
```

> The file is within the config directory.

```bash
test.md:1:13:style.Paths:Consider using 'in the config directory' instead of 'within the config directory'
```

## Affix flags

{% hint style="info" %}
Requires Vale v3.25.0 or later.
{% endhint %}

With [`affix`](/checks/existence#affix-flags) set, a key written as `word/FLAGS` matches every form its flags name. The value is the replacement for all of them, as written:

```yaml
extends: substitution
message: Use '%s' instead of '%s'.
affix: en_US-web
swap:
  utilize/DGS: use   # utilize, utilized, utilizes, utilizing
```

## Multiple suggestions

In some cases, you may want to suggest multiple alternatives for a single token. You can do this by separating them with a pipe ("|"):

```yaml
extends: substitution
# NOTE: We don't quote the first '%s':
message: Consider using %s instead of '%s.'
level: warning
# NOTE: The action is required.
action:
  name: replace
swap:
  # You can suggest multiple alternatives for a single token
  # by separating them with a pipe ("|").
  masterful: skilled|authoritative|commanding
```

In the CLI, this will render as a sentence with multiple suggestions:

![Vale reporting one warning for a single Markdown file, with a summary line counting errors, warnings and suggestions.](https://2533404145-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEMH7s0sC5N9L8xSMXGwW%2Fuploads%2Fgit-blob-21ea51c67f491c5e94c17c6ffc10806c85043e59%2Fpipe.svg?alt=media)

In LSP-based editors, the suggestions will be presented as a list of ‘Quick Fixes’. See the [LSP guide](/guides/lsp) for more information.

## message

`substitution` can have one or two `%s` format specifiers in its message. This allows us to do either of the following:

```yaml
message: "Consider using '%s' instead of '%s'."
# or
message: "Consider using '%s'."
```


# occurrence

Learn about the occurrence extension point.

| Name         | Type     | Description                                                         |
| ------------ | -------- | ------------------------------------------------------------------- |
| `max`        | `int`    | The maximum amount of times `token` may appear in a given scope.    |
| `min`        | `int`    | The minimum amount of times `token` has to appear in a given scope. |
| `token`      | `string` | The token of interest.                                              |
| `ignorecase` | `bool`   | Makes all matches case-insensitive.                                 |

`occurrence` enforces the maximum or minimum number of times a particular token can appear in a given scope.

```yaml
extends: occurrence
message: 'More than 3 commas!'
level: error
# Here, we're counting the number of times a comma appears
# in a sentence.
#
# If it occurs more than 3 times, we'll flag it.
scope: sentence
max: 3
token: ','
```

In the example above, we’re limiting the number of commas per sentence. The alert lands on the first match in the scope that went over:

> First, second, third, fourth, and fifth. Then, done.

```bash
test.md:1:6:style.Commas:More than 3 commas!
```

## [min](#min)

`min` makes absence a violation: a scope with fewer than `min` matches is flagged. The rule is evaluated once per scope, so a document that satisfies it in one paragraph and not the next is flagged at the paragraph that fell short.

```yaml
extends: occurrence
message: 'A paragraph here has no example (found %d).'
level: suggestion
scope: paragraph
min: 1
token: 'for example'
```

> Some tools, for example Vale, lint prose.
>
> Others lint code.

```bash
test.md:3:1:style.Examples:A paragraph here has no example (found 0).
```

When a scope has zero matches there is no occurrence to point at, so the alert is anchored to the scope's first word — one alert per scope that fell short, at that scope's own position.

{% hint style="info" %}
Per-scope positions for `min` shortfalls require Vale v3.20.0 or later. Earlier versions report once per file, at `1:1`.
{% endhint %}

## [message](#message)

The `message` key can contain an optional format specifier, `%s` or `%d`, which is populated with the number of occurrences:

```yaml
message: 'Titles should use fewer than 70 characters (found: %s).'
```


# repetition

Learn about the repetition extension point.

| Name         | Type    | Description                                                                                      |
| ------------ | ------- | ------------------------------------------------------------------------------------------------ |
| `ignorecase` | `bool`  | Makes all matches case-insensitive.                                                              |
| `alpha`      | `bool`  | Limits all matches to alphanumeric tokens.                                                       |
| `max`        | `int`   | The number of repeats allowed before a match is flagged. The default, 0, flags the first repeat. |
| `tokens`     | `array` | A list of tokens to be transformed into a non-capturing group.                                   |
| `exceptions` | `array` | An array of strings to be ignored.                                                               |
| `vocab`      | `bool`  | If false, disables all active vocabularies for this rule (default: true).                        |

`repetition` looks for repeated occurrences of its tokens.

```yaml
extends: repetition
message: "'%s' is repeated!"
level: error
alpha: true
tokens:
  - '[^\s.!?]+'
```

> This is is a test of the the rule.

```bash
test.md:1:6:style.Repetition:'is' is repeated!
test.md:1:22:style.Repetition:'the' is repeated!
```

## [Vale.Repetition](#valerepetition)

Vale includes a [built-in implementation](/topics/styles#the-built-in-style) of `repetition` that can be used to flag repeated words such as “the the” or “and and”. This rule will catch almost any instance of a repeated word, including across markup boundaries:

```markdown
See the Mermaid [Mermaid user guide][1].
```


# consistency

Learn about the consistency extension point.

| Name         | Type   | Description                                                       |
| ------------ | ------ | ----------------------------------------------------------------- |
| `nonword`    | `bool` | Removes the default word boundaries (`\b`).                       |
| `ignorecase` | `bool` | Makes all matches case-insensitive.                               |
| `either`     | `map`  | A map of `option 1: option 2` pairs of which only one may appear. |

`consistency` will ensure that a key and its value (e.g., “advisor” and “adviser”) don’t both occur in its scope.

```yaml
extends: consistency
message: "Inconsistent spelling of '%s'."
level: error
ignorecase: true

# We only want one of these to appear.
either:
  advisor: adviser
  centre: center
```

The first spelling to appear sets the expectation, and each later use of the other one is flagged:

> Ask your advisor. The centre has an adviser too.

```bash
test.md:1:37:style.Consistency:Inconsistent spelling of 'adviser'.
```


# conditional

Learn about the conditional extension point.

| Name         | Type     | Description                                                                                                                 |
| ------------ | -------- | --------------------------------------------------------------------------------------------------------------------------- |
| `ignorecase` | `bool`   | Makes all matches case-insensitive.                                                                                         |
| `first`      | `string` | The antecedent of the statement.                                                                                            |
| `second`     | `string` | The consequent of the statement.                                                                                            |
| `vocab`      | `bool`   | If false, disables all active vocabularies for this rule (default: true).                                                   |
| `exceptions` | `array`  | An array of strings to be ignored.                                                                                          |
| `in`         | `string` | The [View](/topics/views) scope `second` is looked for in. Without it, `second` is looked for in the same scope as `first`. |

```yaml
extends: conditional
message: "'%s' has no definition"
level: error
scope: text
ignorecase: false
# Ensures that the existence of 'first'
# implies the existence of 'second'.
first: '\b([A-Z]{3,5})\b'
second: '(?:\b[A-Z][a-z]+ )+\(([A-Z]{3,5})\)'
# ... with the exception of these:
exceptions:
  - ABC
  - ADD
```

For example, consider the following text:

> According to Wikipedia, the World Health Organization (WHO) is a specialized agency of the United Nations that is concerned with international public health. We can now use WHO because it has been defined, but we can’t use DAFB because people may not know what it represents. We can use `DAFB` when it’s presented as code, though.

Using the above text with our example rule yields the following:

```bash
test.md:1:224:style.UnexpandedAcronyms:'DAFB' has no definition
```

A definition has to come before the use: a match of `second` covers every `first` after it in the file, in any later paragraph, and a `first` before it is flagged. Every undefined use is flagged, not only the first one.

## [Several definition forms](#several-definition-forms)

Every capture group in `second` counts, so a pattern with an alternation accepts more than one way of defining a term. The rule below is satisfied by `WHO: World Health Organization` and by `World Health Organization (WHO)` alike:

```yaml
extends: conditional
message: "'%s' has no definition"
level: error
scope: text
first: '\b([A-Z]{3,5})\b'
second: '\b([A-Z]{3,5}): (?:[A-Z][a-z]+ ?)+|(?:\b[A-Z][a-z]+ )+\(([A-Z]{3,5})\)'
```

> WHO: World Health Organization. The Central Intelligence Agency (CIA) is next. But FBI is never defined, so FBI is flagged both times.

```bash
test.md:1:84:style.UnexpandedAcronyms:'FBI' has no definition
test.md:1:109:style.UnexpandedAcronyms:'FBI' has no definition
```

## [Exceptions and vocabulary](#exceptions-and-vocabulary)

A `first` match listed in `exceptions` is never flagged. The project's [vocabulary](/keys/vocabularies) is added to that list, so a term in `accept.txt` needs no definition either, and a multi-word entry covers the terms inside it: with `AWS CLI` accepted, `AWS` and `CLI` pass when they appear together and are flagged when they appear alone.

```yaml
extends: conditional
message: "'%s' has no definition"
level: error
scope: text
first: '\b([A-Z]{3,5})\b'
second: '(?:\b[A-Z][a-z]+ )+\(([A-Z]{3,5})\)'
exceptions:
  - ABC
  - ADD
```

> The API is fine. The AWS CLI is fine. AWS alone is not.

```bash
test.md:1:39:style.UnexpandedAcronyms:'AWS' has no definition
```

Set `vocab: false` to ignore the vocabulary and use only the rule's own `exceptions`.

## [Presence checks](#presence-checks)

When `second` has a capture group, a `first` match is allowed only if its captured value was also captured by a `second` match—the acronym-definition pattern above. When `second` has *no* capture group, the rule is a plain presence check: any `first` match requires `second` to appear somewhere in the same scope.

```yaml
extends: conditional
message: "A 'Section' requires a 'Summary:' line."
level: error
scope: raw
first: '\bSection\b'
second: 'Summary:'
```

## [Lookarounds](#lookarounds)

Regular expression lookarounds can be used to restrict the capture of the rule, allowing for more complex conditional statements. For example, the following rule will flag any MDX-style import that is not used:

```yaml
extends: conditional
message: "'%s' has been imported but not used."
level: error
scope: raw
first: '(?<=import )(\w+)(?= from)'
second: '(?<=<)(\w+)'
```

See the [regex guide](/guides/regex) for more information.

## [Across a View's scopes](#across-a-views-scopes)

When a [View](/topics/views) names the parts of a file, `in` looks for `second` in one part while `first` is matched in another. A commit whose subject is marked with `!` needs a `BREAKING CHANGE:` footer:

```yaml
extends: conditional
message: "A '!' in the subject needs a 'BREAKING CHANGE:' footer."
level: error
scope: subject
first: '^\w+(?:\([^)]*\))?!:'
second: '^BREAKING CHANGE: '
in: trailer
```

The alert lands on the subject's `!`. The reverse, a footer that needs the `!`, is the same rule with `scope` and `in` swapped. An `in` that no View defines is an error when the rule loads.


# capitalization

Learn about the capitalization extension point.

| Name         | Type     | Description                                                                                                          |
| ------------ | -------- | -------------------------------------------------------------------------------------------------------------------- |
| `match`      | `string` | `$title`, `$sentence`, `$lower`, `$upper`, or a pattern.                                                             |
| `style`      | `string` | AP or Chicago; only applies when match is set to `$title` (default: AP).                                             |
| `exceptions` | `array`  | An array of strings to be ignored.                                                                                   |
| `indicators` | `array`  | An array of suffixes that indicate the next token should be ignored.                                                 |
| `threshold`  | `float`  | The minimum proportion of words that must be (un)capitalized for a sentence to be considered correct (default: 0.8). |
| `prefix`     | `string` | A constant prefix to ignore during case conversion.                                                                  |
| `vocab`      | `bool`   | If false, disables all active vocabularies for this rule (default: true).                                            |

`capitalization` checks that the text in the specified scope matches the case of `match`.

```yaml
extends: capitalization
message: "'%s' should be in title case"
level: warning
scope: heading
# $title, $sentence, $lower, $upper, or a pattern.
match: $title
# AP or Chicago; only applies when match is set to
# $title.
style: AP
exceptions:
  - ABC
  - add
```

```markdown
## Getting started with the API
```

```bash
test.md:1:4:style.Headings:'Getting started with the API' should be in title case
```

## [styles](#styles)

The `capitalization` extension point supports two styles: “AP” and “Chicago.”

The “AP” style enforces the rules of the Associated Press Stylebook:

* Capitalize the first word and the last word of the title.
* Capitalize “to” in infinitives.
* Do not capitalize articles, conjunctions, and prepositions of three letters or fewer.

The “Chicago” style enforces the rules of The Chicago Manual of Style:

* Capitalize the first word and the last word of the title.
* Do not capitalize articles (a, an, the), coordinating conjunctions (and, but, or, for, nor), and prepositions, regardless of length.

## [prefix](#prefix)

The `prefix` option allows you to specify a constant prefix to ignore during case conversion. For example,

```yaml
extends: capitalization
message: "'%s' should be sentence-cased."
scope: heading
match: $sentence
# sentence-cased, but allows for a common prefix:
#
# E.g.,
#
# a. This is my heading
prefix: '^[a-z]\.\s'
```

In this example, `^[a-z]\.\s` is used to ignore the common prefix.

## [message](#message)

`capitalization` can have one or two `%s` format specifiers in its message. This allows us to do either of the following:

```yaml
message: "Found: '%s'; expected: '%s'."
# or
message: "'%s' should use title-style capitalization."
```


# metric

Learn about the metric extension point.

{% hint style="info" %}
When writing conditions, be sure to use floating-point numbers. For example, use `"== 8.0"` instead of `"== 8"`.
{% endhint %}

| Name        | Type     | Description                                                    |
| ----------- | -------- | -------------------------------------------------------------- |
| `formula`   | `string` | A formula of pre-defined variables to be evaluated.            |
| `condition` | `string` | A binary condition upon which `formula` will trigger an alert. |

`metric` enforces arbitrary formulas based on pre-defined, built-in variables.

```yaml
extends: metric
message: 'Try to keep the Flesch-Kincaid grade level (%s) below 8.'
link: |
  https://en.wikipedia.org/wiki/Flesch%E2%80%93Kincaid_readability_tests

formula: |
  (0.39 * (words / sentences)) + (11.8 * (syllables / words)) - 15.59

condition: '> 8.0'
```

> The implementation of the aforementioned methodology necessitates considerable computational infrastructure, notwithstanding the organization's preexisting technological capabilities.

```bash
test.md:1:1:style.Readability:Try to keep the Flesch-Kincaid grade level (34.16) below 8.
```

## [Variables](#variables)

The table below summarizes all available variables:

|       Variable       |                                                       Description                                                      |
| :------------------: | :--------------------------------------------------------------------------------------------------------------------: |
|     `blockquote`     |                                            The number of `blockquote` tags.                                            |
|     `characters`     |                                                The number of characters.                                               |
|    `complex_words`   |                    The number of polysyllabic words without common suffixes (`es`, `ed`, `ing`, …).                    |
|    `heading.h{n}`    |                       The number of headings at the specified level (for example, `heading.h1`).                       |
|        `list`        |                                            The number of `ol` and `ul` tags.                                           |
|     `long_words`     |                                    The number of words with more than 6 characters.                                    |
|     `paragraphs`     |                           The number of body paragraphs (what the `paragraph` scope matches).                          |
| `polysyllabic_words` |                                     The number of words with more than 2 syllables.                                    |
|         `pre`        |                                                The number of `pre` tags.                                               |
|      `sentences`     |                                                The number of sentences.                                                |
|      `syllables`     |                                                The number of syllables.                                                |
|     `quote_words`    |                           The number of words inside quotation marks (Vale v3.23.0 or later).                          |
| `sentence_length_sd` | The standard deviation of words per sentence, in words: how much the sentences vary in length (Vale v3.23.0 or later). |
|        `words`       |                                                  The number of words.                                                  |

The readability scores are variables too, computed from the counts above (Vale v3.23.0 or later):

|         Variable        |                                                Description                                                |
| :---------------------: | :-------------------------------------------------------------------------------------------------------: |
| `automated_readability` |       The [Automated Readability Index](https://en.wikipedia.org/wiki/Automated_readability_index).       |
|      `coleman_liau`     |            The [Coleman–Liau index](https://en.wikipedia.org/wiki/Coleman%E2%80%93Liau_index).            |
|       `dale_chall`      |       The [Dale–Chall score](https://en.wikipedia.org/wiki/Dale%E2%80%93Chall_readability_formula).       |
|     `flesch_kincaid`    | The [Flesch–Kincaid grade level](https://en.wikipedia.org/wiki/Flesch%E2%80%93Kincaid_readability_tests). |
|  `flesch_reading_ease`  |  The [Flesch reading-ease score](https://en.wikipedia.org/wiki/Flesch%E2%80%93Kincaid_readability_tests). |
|      `gunning_fog`      |                 The [Gunning fog index](https://en.wikipedia.org/wiki/Gunning_fog_index).                 |
|          `lix`          |                 The [LIX](https://en.wikipedia.org/wiki/Lix_\(readability_test\)) measure.                |
|          `smog`         |                           The [SMOG grade](https://en.wikipedia.org/wiki/SMOG).                           |

The example above is `flesch_kincaid`. With the score as a variable and `round`, a rule can report a grade the way an editor does:

```yaml
extends: metric
message: "Hard to read: grade %s. Split it, or trim it."
level: suggestion
scope: sentence
formula: round(automated_readability)
condition: ">= 10 && words >= 14"
```

A `metric` rule measures the whole document unless it declares a [scope](/topics/scopes#checks-and-scopes). With one, it measures each block the scope names on its own—a paragraph, a heading, a list item, or a `doc(...)` selection—and the alert lands on that block’s first line:

```yaml
extends: metric
message: "This section runs %s words. The budget is 400."
level: warning
scope: 'doc(section:has(> h2))'
formula: words
condition: "> 400"
```

A selection counts the elements it holds, so `heading.h3` inside a section is the number of subheadings in that section. Scoping a `metric` rule requires Vale v3.21.0 or later; `text` means the whole document, as an unset scope does.

{% hint style="info" %}
As of Vale v3.18.0, `paragraphs` counts body paragraphs alone. `words`, `sentences`, and the other prose-derived variables still take in every kind of prose—including list items and blockquotes, which are counted by `list` and `blockquote`.
{% endhint %}

## [Operators](#operators)

In addition to using the variables listed above, a `formula` may also use the following operators:

|    Operator    |               Description               |
| :------------: | :-------------------------------------: |
|       `+`      |                 Addition                |
|       `-`      |               Subtraction               |
|       `*`      |              Multiplication             |
|       `/`      |                 Division                |
| `math.sqrt(x)` |            Square root of `x`           |
|  `math.abs(x)` |          Absolute value of `x`          |
|   `round(x)`   | `x` rounded to the nearest whole number |
|  `round(x, n)` |    `x` rounded to `n` decimal places    |

A `condition` is a comparison against the formula's result: `>`, `<`, `==`, `!=`, `>=`, or `<=` and a number. Both the formula and the condition are evaluated as [Tengo](https://tengolang.com/) expressions, so a formula may also use its `math` module.

## [message](#message)

The result of a `formula` will be compared to its `condition` and inserted into its `message` format specifier (`%s`).


# readability

Learn about the readability extension point.

| Name      | Type    | Description                                                                                          |
| --------- | ------- | ---------------------------------------------------------------------------------------------------- |
| `metrics` | `array` | One or more of `Gunning Fog`, `Coleman-Liau`, `Flesch-Kincaid`, `SMOG`, and `Automated Readability`. |
| `grade`   | `float` | The highest acceptable grade level.                                                                  |

`readability` calculates the reading grade level of a document and flags it when the score is too high.

```yaml
extends: readability
message: "Grade level (%s) too high!"
level: warning
# Flag any document that reads above a US grade 7 level.
grade: 7
metrics:
  - Flesch-Kincaid
```

Given a document written in dense, abstract prose, the rule above reports:

```
 a.md
 1:1  warning  Grade level (33.70) too high!  demo.Reading
```

## [metrics](#metrics)

Each metric estimates the years of education a reader needs to understand the text on a first reading, expressed as a US grade level. They disagree with one another—they weigh sentence length, syllable count, and word length differently—so the number you get depends on which you choose.

When you list more than one, Vale averages them:

```yaml
extends: readability
message: "Grade level (%s) too high!"
level: warning
grade: 8
# The score is the average of the three, not the highest of them.
metrics:
  - Flesch-Kincaid
  - Gunning Fog
  - Coleman-Liau
```

Averaging several metrics is usually steadier than trusting one, since each has text it handles badly—`SMOG`, for example, is calibrated for health writing.

## [message](#message)

The `message` key can contain an optional format specifier `%s`, which is populated with the calculated grade level to two decimal places:

```yaml
message: "Grade level (%s) too high!"
```

## [Scope](#scope)

A `readability` rule scores the document’s prose as a whole unless it declares a [scope](/topics/scopes#checks-and-scopes), and reports the result at line 1. With a scope, it scores each block the scope names and reports on that block’s first line. A section is the natural unit:

```yaml
extends: readability
message: "The Summary reads at grade %s. Aim below 10."
level: warning
scope: 'doc(section:has(> h2:contains("Summary")))'
metrics:
  - Flesch-Kincaid
grade: 10
```

A grade level is calculated from whole sentences, so scoring anything smaller than a paragraph—a heading, a list item, a table cell—wouldn’t mean anything, even though the scope allows it. Scoping a `readability` rule requires Vale v3.21.0 or later.


# spelling

Learn about the spelling extension point.

| Name           | Type     | Description                                                                                            |
| -------------- | -------- | ------------------------------------------------------------------------------------------------------ |
| `custom`       | `bool`   | Turn off the default filters for acronyms, abbreviations, and numbers.                                 |
| `filters`      | `array`  | An array of patterns to ignore during spell checking.                                                  |
| `ignore`       | `array`  | Relative paths (from `<StylesPath>/config/ignore`) to files consisting of one word per line to ignore. |
| `exceptions`   | `array`  | An array of strings to be ignored.                                                                     |
| `dicpath`      | `string` | The location to look for `.dic` and `.aff` files. Can be absolute or relative to the `StylesPath`.     |
| `dictionaries` | `array`  | An array of dictionaries to load.                                                                      |
| `append`       | `bool`   | Adds the array of dictionaries after the default Vale dictionary, instead of replacing it.             |
| `aff`, `dic`   | `string` | One dictionary as a pair of files, found on the `StylesPath`, used instead of `dictionaries`.          |
| `split`        | `bool`   | Check the parts of an identifier rather than skipping it or checking it whole.                         |

`spelling` implements spell checking based on Hunspell-compatible dictionaries.

```yaml
# Uses the built-in dictionary and filters.
extends: spelling
message: "Did you really mean '%s'?"
level: error
```

> Teh build runs on evrey push.

```bash
test.md:1:1:style.Spelling:Did you really mean 'Teh'?
test.md:1:19:style.Spelling:Did you really mean 'evrey'?
```

By default, `spelling` includes a custom, open-source [dictionary for American English](https://github.com/vale-cli/en_US-web).

## [Dictionaries](#dictionaries)

You may instead use the `dictionaries` key to list multiple custom dictionaries:

```yaml
extends: spelling
message: "'%s' is a typo!"
dictionaries:
  - en_US
  - en_medical
```

The `spelling` extension point will look for `en_US.{dic,aff}` and `en_medical.{dic,aff}` files in `<StylesPath>/config/dictionaries`.

You can also use the `DICPATH` environment variable or the `dicpath` key.

## [Filters](#filters)

Vale comes with a set of built-in filters, as described in the table below:

| Filter                    | Description                                         |
| ------------------------- | --------------------------------------------------- |
| `[A-Z]{1}[a-z]+[A-Z]+\w+` | Mixed-cased words (such as “MongoDB”).              |
| `[^a-zA-Z_']`             | Words containing non-word tokens (such as numbers). |
| `[A-Z]+$`                 | Upper-cased words.                                  |

You can also choose define you own filters either with or without the built-in ones enabled:

```yaml
extends: spelling
message: "Did you really mean '%s'?"
level: error
# This disables the built-in filters. If you omit this
# key or set it to false, custom filters (see below) are
# added on top of the built-in ones.
custom: true
# A "filter" is a regular expression specifying words
# to ignore during spell checking.
filters:
  # Ignore all words starting with 'py'.
  #
  # e.g., 'PyYAML'.
  - '[pP]y.*\b'
```

## [Casing and code](#casing-and-code)

{% hint style="info" %}
Requires Vale v3.25.0 or later.
{% endhint %}

Two kinds of misspelling get a more specific message, and a fix:

* A word the dictionary spells with capitals inside it is reported as a casing mistake, with the dictionary's spelling as its fix: `github` is reported as `Use 'GitHub' instead of 'github'.`, and `json` as `Use 'JSON' instead of 'json'.` This applies to a word of three or more letters with at most one capital. A name the dictionary only capitalizes, such as `Kubernetes`, is reported as a misspelling, because the check can't tell it from a word in another language; to enforce its case, add it to a [vocabulary](/keys/vocabularies).
* Without `split`, a `snake_case` word, or a `camelCase` one that starts with two or more lower-case letters, is reported as looking like code, with the format's inline code as its fix. `apiVersion` and `created_at` are; `vSphere` and `iPhone` aren't.

| Format                                                  | Fix                       |
| ------------------------------------------------------- | ------------------------- |
| Markdown, MDX, MyST, Quarto, notebooks, AsciiDoc, Typst | `` `created_at` ``        |
| reStructuredText                                        | ` ``created_at`` `        |
| Org                                                     | `~created_at~`            |
| HTML                                                    | `<code>created_at</code>` |

In other formats, such as plain text, the word is reported as a misspelling. Both apply to every `spelling` rule, and replace its `message` for these alerts.

## [Identifiers](#identifiers)

{% hint style="info" %}
Requires Vale v3.22.0 or later.
{% endhint %}

An identifier is neither a word nor a typo. Without `split`, the built-in filters skip a `PascalCase` or hyphenated token and check a `camelCase` or `snake_case` one whole, so a typo inside `RecieveMessage` passes and a correct `receiveMessage` is reported as [code](#casing-and-code). With `split: true`, an identifier is taken apart at an underscore, a hyphen, a digit, and a change of case, and each part is checked and reported at its own position:

```yaml
extends: spelling
message: "Did you really mean '%s'?"
level: error
split: true
```

`getHTTPResponse_v2` is `get`, `HTTP`, `Response`, and `v`. A part in all capitals or under three letters is not checked, so `HTTP`, `Id`, and `v2` pass. A plain word is still reported whole, and a whole identifier that is in a dictionary or an ignore file passes without being split.

## [Ignore files](#ignore-files)

Ignore files are plain-text files that list words to be ignored during spell check (one case-insensitive entry per line). For example:

```
destructuring
transpiler
```

You can name these files anything you’d like and reference them relative to the active `<StylesPath>/config/ignore` directory.

```yaml
extends: spelling
message: "Did you really mean '%s'?"
level: error
ignore:
  - ignore1.txt
  - ignore2.txt
```

A word from an ignore file is also a candidate when the rule [suggests](/fixes/suggest#spellings) a spelling, placed ahead of a dictionary word it ties with.

See [Vocabularies](/keys/vocabularies) for information on rule-agnostic terminology lists.


# sequence

Learn about the sequence extension point.

| Name         | Type         | Description                                                                                                                                                                                                                                             |
| ------------ | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `tokens`     | `[]NLPToken` | A list of tokens with associated NLP metadata.                                                                                                                                                                                                          |
| `ignorecase` | `bool`       | Makes all matches case-insensitive.                                                                                                                                                                                                                     |
| `exceptions` | `[]string`   | Sentence regions, as regexes; a match beginning inside one is dropped.                                                                                                                                                                                  |
| `model`      | `string`     | A tagger lexicon, `<name>.dict` in `config/dictionaries`: one word per line, then its tags, tab-separated, most common first. Its entries decide a word's tag ahead of the built-in tagger, for rules written against another checker's idea of a noun. |

While most extension points focus on writing *style*, `sequence` aims to support grammar-focused rules.

```yaml
extends: sequence

# `%[4]s` is like `%s`, but specifically refers to the
# 4th token in our sequence.
message: |
  The infinitive '%[4]s' after 'be' requires 'to'.
  Did you mean '%[2]s %[3]s *to* %[4]s'?"
tokens:
  - tag: MD
  - pattern: be
  - tag: JJ
  # The `|` notation means that we'll accept `VB`
  # or `VBN` in position 4.
  - tag: VB|VBN
```

Every `sequence`-based rule is required to have at least one `pattern` (such as `pattern: be`, shown above). This becomes the “anchor” of the sequence: we find all instances of the first pattern and then check that the left- and right-hand sides of the sequence match.

Tokens judge the sentence one word at a time. When a rule needs a judgment about a *region* — for example, “the comma closing a fronted phrase isn’t a list comma” — hand that part to `exceptions`: each entry is a regular expression matched against the sentence, and a sequence match that begins inside one of its matches is dropped. Unlike other checks’ `exceptions`, these are regions rather than vocabulary terms, so the project’s accepted vocabulary is never merged in.

```yaml
# Skip matches that begin inside a fronted phrase.
exceptions:
  - '^(?i:(?:in|on|at|when|while|if)\b[^,]{0,60}),'
```

{% hint style="info" %}
`exceptions`, and the boundary behavior of `negate` described below, require Vale v3.20.0 or later.
{% endhint %}

Each entry in a sequence is known as an `NLPToken` and has the following structure:

```yaml
# [optional]: A regular expression (required
# if `tag` isn't given).
pattern: '...'

# [optional]: If true, indicates that we
# *shouldn't* match this token. A negated token at
# the start or end of a sequence is also satisfied
# by the sentence boundary itself: "not preceded by
# X" holds when nothing precedes the match at all.
negate: true # or false

# [optional]: A part-of-speech tag (required
# if `pattern` isn't given).
tag: '...'

# [optional]: An integer meaning that there may
# be up to `n` (3, in this case) tokens between
# this token and the next one.
skip: 3

# [optional]: How many times the token must occur --
# "at least two nouns", not just one. Each occurrence
# gets its own `skip` window, so `skip: 8, min: 2`
# reads "a noun within eight words, then another noun
# within eight words". The default is 1.
min: 2

# [optional]: A universal part-of-speech tag --
# NOUN, VERB, ADJ, and so on -- instead of a
# Penn Treebank `tag`. Universal tags are
# portable; Penn tags are more precise.
upos: '...'

# [optional]: If true, narrows the alert to this
# token alone. Without it, a match spans every
# token in the sequence -- marking one lets a rule
# require surrounding context while pointing at
# only the part the writer should change.
target: true # or false
```

`sequence`-based are [sentence-scoped](/topics/scopes). See [prose/tagging](https://github.com/jdkato/prose?tab=readme-ov-file#tagging) for a full list of supported part-of-speech tags.

{% hint style="info" %}
`min` requires Vale v3.19.0 or later.
{% endhint %}

`min` and `skip` combine to express "at least *n* occurrences within a window." For example, a pronoun is ambiguous when two or more nouns precede it:

```yaml
extends: sequence
message: "Avoid ambiguous pronouns."
level: warning
tokens:
  - tag: NN|NNP|NNPS|NNS
    skip: 8
    min: 2
  - pattern: \w+
    tag: PRP
    target: true
```

This matches "The dog chased the cat until **it** tired" — two nouns, then a pronoun — but not "The dog barked because it hungered." With `target` on the pronoun, the alert covers only `it`:

> The dog chased the cat until it tired. The dog barked because it hungered.

```bash
test.md:1:30:style.Pronouns:Avoid ambiguous pronouns.
```

Without `skip`, `min` means consecutive occurrences: `tag: JJ, min: 2` is two adjectives in a row.

{% hint style="info" %}
Reaching every block, and honoring a declared `scope`, requires Vale v3.17.0 or later. Earlier versions read sentences from paragraphs only.
{% endhint %}

By default, a `sequence` rule reads sentences from **every** block—headings, list items, and table cells as well as paragraphs. Much of a document's prose lives outside its paragraphs, and `sequence` is the only extension point that reads part-of-speech data, so it needs to reach all of it.

To narrow that, declare a `scope`:

```yaml
extends: sequence
message: "matched '%s'"
level: error
# Only take sentences from headings.
scope: heading
tokens:
  - pattern: quick
  - pattern: brown
```

The scope selects which blocks the sentences are drawn from; the rule still matches sentence by sentence within them. A [`doc(...)` selection](/topics/scopes#selections) works the same way: the rule reads the sentences inside the selected element.


# script

Learn about the script extension point.

{% hint style="warning" %}
When using `script`-based rules, you're limited to the standard Go [regex syntax](https://pkg.go.dev/regexp/syntax).
{% endhint %}

| Name     | Type     | Description                                            |
| -------- | -------- | ------------------------------------------------------ |
| `script` | `string` | The [Tengo](https://tengolang.com/) script to execute. |

`script` allows for the creation of arbitrary logic-based rules using [Tengo](https://tengolang.com/), a Go-like scripting language.

```yaml
extends: script
message: 'Consider inserting a new section heading at this point.'
link: https://tengolang.com
scope: raw
script: MyScript.tengo
```

Where `MyScript.tengo` is the Tengo script, stored in `<StylesPath>/config/scripts`. It is compiled once with the rule and run once per block of its scope.

````go
text := import("text")

matches := []
// at most 3 paragraphs per section
p_limit := 3

// Remove all instances of code blocks
// since we don't want to count inter-block
// newlines as a new paragraph.
document := text.re_replace("(?s) *(\n```.*?```\n)", scope, "")

count := 0
for line in text.split(document, "\n") {
    if text.has_prefix(line, "#") {
        count = 0 // New section; reset count
    } else if count > p_limit {
        start := text.index(scope, line)
        matches = append(matches, {begin: start, end: start + len(line)})
        count = 0
    } else if text.trim_space(line) == "" {
        count += 1
    }
}
````

{% stepper %}
{% step %}
Use Tengo’s [`text`](https://github.com/d5/tengo/blob/master/docs/stdlib-text.md) module, which provides a number of string- and regex-related utility functions.
{% endstep %}

{% step %}
Process the content in the `scope` variable. `scope` contains text based on the `scope: <scope>` setting for the rule. For more information, see [Scoping](/topics/scopes).
{% endstep %}

{% step %}
Populate the `matches` array with rule matches. Each match must be a map with the keys:

* `begin`: where the match begins in the content provided by the `scope` variable.
* `end`: where the match ends in the content provided by the `scope` variable.
* `message`, optionally: a message for this match, in place of the rule's own. Its `%s` is the matched text.
  {% endstep %}
  {% endstepper %}

Run on a section of five one-line paragraphs, the script flags the fourth one:

```markdown
# Intro

One.

Two.

Three.

Four.

Five.

# Next

Six.
```

```bash
test.md:9:1:style.Sections:Consider inserting a new section heading at this point.
```

## [Testing](#testing)

A script is easiest to change safely with cases beside it. List them under the rule's `tests` and run [`vale test`](https://github.com/vale-cli/vale.sh/tree/svelte/docs/topics/testing.md): each case lints its input with the real script, from `config/scripts` or inline, and compares the alerts. See [Script rules](https://github.com/vale-cli/vale.sh/tree/svelte/docs/topics/testing.md#script-rules).


# suggest

Learn how to compute a rule's suggestions.

```go
func suggest(match string) []string
```

`suggest` returns replacements that have to be worked out from the match: the closest words in a dictionary, or whatever a script of your own decides.

## [`spellings`](#spellings)

```yaml
action:
  name: suggest
  params:
    - spellings
```

The closest words to the match, up to six, by [Levenshtein distance](https://pkg.go.dev/github.com/adrg/strutil@v0.3.0/metrics#Levenshtein). The candidates are every dictionary the rule loads, the words in its ignore files, and the terms in the configuration's [vocabularies](/keys/vocabularies); a project's own word comes first among candidates it ties with, spelled as the project spells it, so `kubctl` suggests `kubectl` before `cubical`. A capitalized match gets capitalized suggestions.

Ranking a dictionary is real work, so these suggestions are not computed while linting. Lint output leaves the alert's `Suggestions` empty, and an editor asks for them per alert. On a rule that extends `spelling`, a bare `name: suggest` means the same thing.

## [A script](#a-script)

```yaml
action:
  name: suggest
  params:
    - CamelToSnake.tengo
```

Any other parameter names a [Tengo](https://github.com/d5/tengo) script. Vale looks for it in `<StylesPath>/config/actions` first, then in the rule's own style directory, so a style can ship its scripts beside its rules. A script that is in neither place is a load-time error.

The script receives the matched text as `match` and must leave its answer in `suggestions`, an array of strings; anything in the array that is not a string is dropped. The `text`, `fmt`, and `math` modules are available to import.

```go
text := import("text")

// `match` is the rule's matched text.
made := text.re_replace(`([A-Z]\w+)([A-Z]\w+)`, match, `$1-$2`)

made = text.replace(made, "-", "_", 1)
made = text.to_lower(made)

// `suggestions` is what Vale reads back.
suggestions := [made]
```

Saved as `CamelToSnake.tengo`, the script serves this rule:

```yaml
extends: existence
message: "'%s' should be in snake_case."
nonword: true
level: error
action:
  name: suggest
  params:
    - CamelToSnake.tengo
tokens:
  - '[A-Z]\w+[A-Z]\w+'
```

A script is compiled once per run and executed for each match.


# replace

Learn how to write a rule's replacements into the rule.

```go
func replace(match string) []string
```

`replace` returns the replacements listed in the rule.

```yaml
extends: existence
message: "Use '%s' for '%s'."
action:
  name: replace
  params:
    - color
tokens:
  - colour
```

A parameter can name the token's capture groups, `$1`, `$2`, and the whole match as `$0`; see [the token's groups](/fixes/edit#the-tokens-groups). Several parameters are several suggestions. The message joins them, `'color or hue'`, and an editor offers each as its own quick fix.

Rules that extend `substitution` or `capitalization` know their own replacement, so the parameters can be left out:

```yaml
extends: substitution
message: "Use '%s' instead of '%s'."
action:
  name: replace
swap:
  Javascript: JavaScript
```

A `substitution` entry whose value is written as alternatives, `color|hue`, yields one suggestion per alternative.

With [`matchcase`](/topics/styles#the-header) set on the rule, each replacement takes the case of the text it replaces, so a swap written as `a-ok` still suggests `A-OK` for `A OK`. The `capitalization` check ignores that setting: it decides the case, and changing its suggestion's case would undo the correction.


# remove

Learn how to delete a match.

```go
func remove(match string)
```

`remove` suggests an empty string in place of the matched text. It takes no parameters.

```yaml
extends: existence
message: "Don't use an ellipsis in documentation."
nonword: true
action:
  name: remove
tokens:
  - '...'
```

Applied to a whole word from an editor, the deletion takes one neighboring space with it, so removing `very` from `a very good one` leaves `a good one` rather than two spaces.


# edit

Learn how to transform a match into its fix.

```go
func edit(match string) string
```

`edit` produces one suggestion by running the matched text through one or more operations. Each operation is a name followed by its arguments, and the result of one is the input to the next. Whatever the last operation leaves is trimmed of surrounding whitespace before it is suggested.

One operation is a flat list:

```yaml
action:
  name: edit
  params:
    - trim_right
    - '!'
```

Several are a list of lists, one per step:

```yaml
action:
  name: edit
  params:
    - [trim_right, '!']
    - [lower]
    - [wrap, '`']
```

Because every operation takes a fixed number of arguments, the same pipeline written flat, `[trim_right, '!', lower, wrap, '`']\`, means the same thing; that is the form the alert carries.

![The match Wow! passes through a right trim, a lowercase, and a wrap step in turn, becoming Wow, then wow, then wow in backticks, which is the suggestion.](https://2533404145-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEMH7s0sC5N9L8xSMXGwW%2Fuploads%2Fgit-blob-853c1ee02a993311929c18cc33688474488847d1%2Fedit-pipeline.svg?alt=media)

## [The token's groups](#the-tokens-groups)

An argument can name what the rule matched. `$0` is the whole match, and `$1`, `$2`, and so on are the capture groups of the token that matched, in that token's own numbering. An `existence` rule can therefore reorder or reuse its match without a `regex` step that matches it again:

```yaml
extends: existence
message: "Put the family name first: '%s' should be '%s'."
nonword: true
action:
  name: replace
  params:
    - '$2, $1'
tokens:
  - '\b([A-Z][a-z]+) ([A-Z][a-z]+) \(author\)'
```

The same groups reach every `edit` step, so `[prefix, '$2 ']` or `[replace, '$1', '$2']` read as the author thinks.

Every argument is expanded this way except a `regex` step's own, where `$1` keeps meaning that pattern's first group. Write `$$` for a literal dollar sign. A rule whose tokens have no groups sees `$1` as empty, and the alert carries the groups as `Groups` so an editor resolving the suggestion later gets the same answer.

## [Rewriting](#rewriting)

### [`replace`](#replace)

Replace every occurrence of a literal string within the match.

```yaml
extends: sequence
message: Did you mean "%[1]ss" instead of "%[1]s's"?
tokens:
  - tag: NN
  - pattern: "'s"
  - pattern: are
action:
  name: edit
  params:
    - replace
    - "'"
    - ''
```

### [`regex`](#regex)

Replace every match of a pattern within the text with a replacement, which may refer to the pattern's groups as `$1`, `$2`, or `${name}`.

```yaml
extends: existence
message: "'%s' should be '%s'."
level: error
action:
  name: edit
  params:
    - regex
    - '(\w+)_(\w+)' # pattern
    - '$1-$2'       # replacement
tokens:
  - '\w+_\w+'
```

The pattern runs on the same engine as the rule's own tokens, so a lookaround that found the match can also rewrite it.

### [`remove`](#remove)

Delete every occurrence of any of the given characters from the match.

```yaml
extends: existence
message: "Don't use end punctuation in headings."
scope: heading
nonword: true
action:
  name: edit
  params:
    - remove
    - '.?!'
tokens:
  - '\w+[.?!]$'
```

## [Trimming](#trimming)

### [`trim_right`](#trim_right)

Remove any of the given characters from the end of the match.

```yaml
extends: existence
message: "Don't use exclamation points in text."
nonword: true
action:
  name: edit
  params:
    - trim_right
    - '!'
tokens:
  - '\w+!(?:\s|$)'
```

### [`trim_left`](#trim_left)

Remove any of the given characters from the start of the match.

```yaml
extends: existence
message: "'%s' too many spaces."
level: warning
nonword: true
action:
  name: edit
  params:
    - trim_left
    - ' '
tokens:
  - '(?<=[a-z][.!?] ) [A-Z]'
```

### [`trim`](#trim)

Remove any of the given characters from both ends of the match.

```yaml
action:
  name: edit
  params:
    - trim
    - '*_'
```

### [`truncate`](#truncate)

Keep what comes before the first occurrence of a separator. The built-in `Vale.Repetition` rule uses it to reduce `the the` to `the`:

```yaml
action:
  name: edit
  params:
    - truncate
    - ' '
```

### [`split`](#split)

Split the match on a separator and keep one piece, counted from zero.

```yaml
extends: existence
message: "Use the product name alone: '%s'."
action:
  name: edit
  params:
    - split
    - ' by '
    - '0'
tokens:
  - 'Acme Cloud by Acme'
```

### [`squeeze`](#squeeze)

Collapse every run of whitespace into one space.

```yaml
extends: existence
message: "'%s' has too many spaces."
nonword: true
action:
  name: edit
  params:
    - squeeze
tokens:
  - '\w+ {2,}\w+'
```

## [Wrapping](#wrapping)

### [`wrap`](#wrap)

Put the given string on both sides of the match.

```yaml
extends: existence
message: "Format '%s' as code: %s."
action:
  name: edit
  params:
    - wrap
    - '`'
tokens:
  - '\b[a-z]+_[a-z_]+\b'
```

### [`unwrap`](#unwrap)

Remove the given string from both sides of the match, where it is present.

```yaml
action:
  name: edit
  params:
    - unwrap
    - '*'
```

### [`prefix`](#prefix) and [`suffix`](#suffix)

Put the given string before, or after, the match. Together they wrap in a pair that differs on each side:

```yaml
action:
  name: edit
  params:
    - [prefix, '“']
    - [suffix, '”']
```

## [Casing](#casing)

Each takes no arguments.

| Name           | `foo Bar` becomes |
| -------------- | ----------------- |
| `lower`        | `foo bar`         |
| `upper`        | `FOO BAR`         |
| `capitalize`   | `Foo Bar`         |
| `uncapitalize` | `foo Bar`         |
| `title`        | `Foo Bar`         |
| `sentence`     | `Foo bar`         |
| `simple`       | `foo bar`         |
| `kebab`        | `foo-bar`         |
| `snake`        | `foo_bar`         |
| `camel`        | `fooBar`          |
| `pascal`       | `FooBar`          |

`capitalize` and `uncapitalize` touch only the first letter, which is what a rule about how a list item or a sentence starts wants.

`title` follows AP style, and `sentence` keeps only the first word capitalized; neither knows the rule's own vocabulary. For a fix that respects a `capitalization` rule's exceptions, use that rule's own `replace` action.

```yaml
extends: existence
message: "Headings are sentence case: '%s' should be '%s'."
scope: heading
action:
  name: edit
  params:
    - [trim_right, '.']
    - [sentence]
tokens:
  - '(?:[A-Z][a-z]+ ){2,}[A-Z][a-z]+\.?'
```

## [Typography](#typography)

Each takes no arguments.

| Name    | Result                                                                                                                                       |
| ------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `smart` | Typewriter punctuation to its typographic form: `---` and `--` to em and en dashes, `...` to an ellipsis, and straight quotes to curly ones. |
| `dumb`  | The reverse, back to ASCII punctuation.                                                                                                      |
| `ascii` | `dumb`, then accents stripped and letters with no plain form spelled out: `naïve` to `naive`, `Straße` to `Strasse`.                         |

A quote opens after whitespace, a bracket, or a dash, and closes anywhere else, so an apostrophe inside a word comes out as `’`.

```yaml
extends: existence
message: "Use curly quotes: '%s' should be '%s'."
nonword: true
action:
  name: edit
  params:
    - smart
tokens:
  - '"[^"]+"'
```

## [Numbers](#numbers)

Each takes no arguments.

| Name     | Result                                                                                                                 |
| -------- | ---------------------------------------------------------------------------------------------------------------------- |
| `words`  | An integer or ordinal spelled out: `21` to `twenty-one`, `1,205` to `one thousand two hundred five`, `3rd` to `third`. |
| `digits` | The reverse: `twenty-one` to `21`, `one hundred and five` to `105`, `third` to `3rd`.                                  |

Text that is not a number passes through unchanged, so a token that is broader than the number itself is fine.

```yaml
extends: existence
message: "Spell out numbers under ten: '%s' should be '%s'."
nonword: true
action:
  name: edit
  params:
    - words
tokens:
  - '\b[1-9]\b'
```


# convert

Learn how to change the case of a match.

```go
func convert(match string) string
```

`convert` suggests the matched text in another case. Its one parameter names the target:

| Name     | Result                                                 |
| -------- | ------------------------------------------------------ |
| `simple` | Lower case, with the words separated by single spaces. |

```yaml
extends: existence
message: "'%s' should be '%s'."
action:
  name: convert
  params:
    - simple
tokens:
  - '[A-Z]{2,}(?: [A-Z]{2,})+'
```

The same conversion is available as the `simple` step of [`edit`](/fixes/edit#casing), beside the other cases, which is where a fix that needs more than one step belongs.


# Front Matter

Learn how Vale handles front matter.

Front matter is linted in Markdown and its dialects (MDX, MyST, Quarto, R Markdown), AsciiDoc, reStructuredText, Org, Typst, and QDoc files.

There are 3 supported front matter types – YAML, TOML, and JSON. Each is recognized by the delimiters that open and close it:

{% tabs %}
{% tab title="YAML" %}
Opening and closing `---` lines:

```yaml
---
name: "frontmatter"
---
rest of the content
```

Or an opening `---yaml` line with a closing `---`:

```yaml
---yaml
name: "frontmatter"
---
rest of the content
```

{% endtab %}

{% tab title="TOML" %}
Opening and closing `+++` lines:

```toml
+++
name = "frontmatter"
+++
rest of the content
```

Or an opening `---toml` line with a closing `---`:

```toml
---toml
name = "frontmatter"
---
rest of the content
```

{% endtab %}

{% tab title="JSON" %}
Opening and closing `;;;` lines:

```json
;;;
{
    "name": "frontmatter"
}
;;;
rest of the content
```

Or an opening `---json` line with a closing `---`:

```json
---json
{
    "name": "frontmatter"
}
---
rest of the content
```

A bare JSON object followed by an empty line also works:

```json
{
    "name": "frontmatter"
}

rest of the content
```

{% endtab %}
{% endtabs %}

Each field is dynamically assigned its own scope, allowing you to write rules that target specific ones:

```yaml
---
title: 'My document'
description: "A short summary of the document's purpose."
author: 'John Doe'
---
```

Using the example above, the generated scopes would be `text.frontmatter.title`, `text.frontmatter.description`, and `text.frontmatter.author`.

A rule can then use these in its `scope:` field:

```yaml
extends: capitalization
message: "'%s' should be in title case"
level: warning
scope: text.frontmatter.title
```

This rule would then only be applied to the `title` field in the front matter.

Only string-valued fields are linted; a list or a nested map is left alone. A field can be excluded by naming its scope in [`IgnoredScopes`](/keys/ignoredscopes).


# Markdown

Learn how Vale handles Markdown content.

[GitHub-Flavored Markdown](https://github.github.com/gfm) support is built in. The supported extensions are `.md`, `.mdown`, `.markdown`, and `.markdn`. [R Markdown](/formats/rmarkdown) is read the same way.

By default, Vale ignores:

* Indented blocks: Blocks starting with four or more spaces.
* Fenced blocks: Blocks surrounded by three or more backticks.
* Code spans: Text surrounded by backticks.
* Math: `$$…$$` blocks and `$x^2$` spans. See [Math](#math).
* URLs: See [URL handling](https://github.com/vale-cli/vale/issues/320) for more information.

## [Math](#math)

Both `$$…$$` display math and `$x^2$` inline math are ignored. Inline math follows Pandoc's delimiter rules, which are what tell an equation from a price: the opening `$` needs a non-space character to its right, and the closing `$` needs one to its left and no digit after it. So `$g_i = g(p)_i$` is math, while `It costs $5 and $10` stays prose and is linted as such.

A span may wrap onto the next line, but not past the end of its paragraph. Write `\$` for a literal dollar sign that would otherwise open one.

## [Comments](#comments)

Vale supports comment-based configuration in Markdown files:

* Turn Vale off entirely:

```html
<!-- vale off -->

This text will be ignored.

<!-- vale on -->
```

* Turn off a specific rule:

```html
<!-- vale Style.Redundancy = NO -->

This is some text ACT test

<!-- vale Style.Redundancy = YES -->
```

* Turn off specific match(es) within a rule:

```html
<!-- vale Style.Redundancy["ACT test","OTHER"] = NO -->

This is some text ACT test

<!-- vale Style.Redundancy["ACT test","OTHER"] = YES -->
```

* Turn on or off specific styles:

```html
<!-- vale StyleName1 = YES -->
<!-- vale StyleName2 = NO -->
```

* Set styles (enabling them and switching off any other styles):

```html
<!-- vale style = StyleName1 -->
<!-- vale styles = StyleName1, StyleName2 -->
```


# MyST

Learn how Vale handles MyST content.

{% hint style="info" %}
Requires Vale v3.18.0 or later.
{% endhint %}

[MyST](https://myst-parser.readthedocs.io/) is CommonMark plus Sphinx-style constructs—directives, roles, targets, comments, and block breaks—that a plain Markdown parser reads as prose. Vale parses each one as the markup it is.

The supported extension is `.myst`. A Markdown file opts in through a [format association](/topics/.vale.ini#format-associations):

```ini
[formats]
md = myst

[*.{md,myst}]
BasedOnStyles = Vale
```

By default, Vale ignores:

* Targets: `(my-label)=` lines.
* Roles: the `{name}` of `` {term}`content` ``—the span that follows is a code span, ignored like any other.
* Comments: lines beginning with `%`.
* Block breaks: `+++` lines, including any metadata they carry.
* Directive options: `:key: value` lines and `---`-delimited YAML blocks under a directive opener.
* Substitutions: `{{ variable }}`.
* Attributes: `{.class}` lines and the `{.class}` of `[text]{.class}`—the text itself is still linted.
* Fenced blocks, code spans, and URLs, as in [Markdown](/formats/markdown).

## [Directives and roles](#directives-and-roles)

A directive's content is prose unless the directive is one whose content is code, math, or generated: `code-block`, `literalinclude`, `math`, `mermaid`, `toctree`, and the like. A role's content is code, since it is usually an identifier, except for the roles whose content is prose: `ref`, `doc`, `any`, `numref`, `term`, `guilabel`, `menuselection`, `abbr`, and `dfn`. A reference written with a title, `` {ref}`the setup guide <setup>` ``, is linted as its title; a bare target is code.

A project's extensions add to both lists in the same `[sphinx]` section a reStructuredText project uses; see [Sphinx](/formats/sphinx#directives-and-roles).

## [Directives](#directives)

A directive's content is Markdown, and Vale lints it—whether the directive is fenced with backticks or colons:

```
:::{note}
This prose is linted.
:::
```

The exception is a directive whose content is literal rather than prose: `code`, `code-block`, `code-cell`, `csv-table`, `eval-rst`, `highlight`, `include`, `literalinclude`, `math`, `mermaid`, `raw`, `sourcecode`, and `toctree` are ignored in full.

A directive's name also becomes a [class scope](/topics/scopes#class-scopes) for everything inside it, however deeply nested:

```yaml
extends: existence
message: "Don't use '%s' in an admonition."
scope: class.note
level: error
tokens:
  - obviously
```

## [Math and citations](#math-and-citations)

Math is ignored, both `$$…$$` display blocks and `$x^2$` inline spans. See [Math](/formats/markdown#math) for the delimiter rules, which are what keep `It costs $5 and $10` prose.

Citations (`[@ref]`) are read as prose, since a bare bracket is ordinary punctuation more often than not. To exclude them, use [`TokenIgnores`](/keys/tokenignores):

```ini
[*.{md,myst}]
TokenIgnores = (\[@[^\n\]]+\])
```


# Quarto

Learn how Vale handles Quarto content.

{% hint style="info" %}
Requires Vale v3.18.0 or later. Earlier versions can approximate support by assigning the format: `qmd = md` under `[formats]`.
{% endhint %}

[Quarto](https://quarto.org/) is Pandoc Markdown plus knitr- and Jupyter-style code cells. The cells are Markdown already—a cell fence is a fenced code block and an inline expression is a code span—so Vale parses the Pandoc layer on top: fenced divs, attributes, and shortcodes.

The supported extension is `.qmd`.

By default, Vale ignores:

* Code cells: ` ```{r} ` fences, including their `#|` options.
* Inline expressions: `` `r mean(x)` `` and `` `{python} 1 + 1 ``.
* Fenced-div lines: `::: {.callout-note}` and its closing `:::`—the content between them is linted.
* Shortcodes: `{{< video ... >}}`, inline or standing alone.
* Attributes: a heading's `{#sec-overview}`, and the `{.underline}` of `[text]{.underline}`—the text itself is still linted.
* Fenced blocks, code spans, and URLs, as in [Markdown](/formats/markdown).

## [Divs](#divs)

A fenced div's classes become [class scopes](/topics/scopes#class-scopes) for everything inside it, so a rule can target a callout, a margin note, or a column by name:

```yaml
extends: existence
message: "Don't use '%s' in a callout."
scope: class.callout-note
level: error
tokens:
  - obviously
```

Divs nest—by fence length (`::::` around `:::`) or same-length fences alike—and a block deep inside carries every enclosing class.

## [Math and citations](#math-and-citations)

Math is ignored, both `$$…$$` display blocks and `$x^2$` inline spans. See [Math](/formats/markdown#math) for the delimiter rules, which are what keep `It costs $5 and $10` prose.

Citations (`[@ref]`) and cross-references (`@sec-overview`) are read as prose, since a bare bracket or `@` is ordinary punctuation more often than not. To exclude them, use [`TokenIgnores`](/keys/tokenignores):

```ini
[*.qmd]
TokenIgnores = (\[?-?@[^\s\]]+\]?)
```


# R Markdown

Learn how Vale handles R Markdown content.

{% hint style="info" %}
Requires Vale v3.18.0 or later. Earlier versions can assign the format instead: `Rmd = md` under `[formats]`.
{% endhint %}

[R Markdown](https://rmarkdown.rstudio.com/) is Markdown with knitr code chunks and inline R expressions. Vale reads it as [Markdown](/formats/markdown), and the knitr syntax is code to it:

* Chunks are fenced blocks: the chunk options in the `{r ...}` info string and everything inside the fence are ignored.
* Inline expressions such as `` `r nrow(df)` `` are code spans, and are ignored by default.

The supported extensions are `.Rmd` and `.rmd`. A configuration section still has to match one of them:

```ini
[*.{md,Rmd}]
BasedOnStyles = Vale
```

Pandoc syntax that is not Markdown, citations for one, can be excluded with [`TokenIgnores`](/keys/tokenignores):

```ini
[*.{md,Rmd}]
TokenIgnores = (\[@[^\n\]]+\])
```

Front matter, math, and comment-based configuration work as they do in [Markdown](/formats/markdown).


# Jupyter

Learn how Vale handles Jupyter notebooks.

{% hint style="info" %}
Requires Vale v3.21.0 or later. Earlier versions can read the Markdown cells with a [View](/topics/views#data) instead.
{% endhint %}

A [Jupyter](https://jupyter.org/) notebook is a JSON file of cells. Vale reads it cell by cell, in order:

* A Markdown cell is linted as [Markdown](/formats/markdown).
* A code cell is linted as [code](/formats/code) in the kernel's language, so its comments are the prose. A cell that opens with the `%%markdown` (or `%%md`) magic is prose instead.
* Raw cells and cell outputs are skipped.

The supported extension is `.ipynb`:

```ini
[*.ipynb]
BasedOnStyles = Vale
```

An alert's line and column are those of the `.ipynb` file itself: the position of the text inside the JSON string that holds it.

## [Code cells](#code-cells)

The kernel's language comes from the notebook's `kernelspec`, then from its `language_info`. A cell that names its own language, as VS Code does in the cell's metadata, is linted as that language instead.

| Kernel language                | Linted as  |
| ------------------------------ | ---------- |
| `python`, `python3`, `ipython` | Python     |
| `r`                            | R          |
| `julia`                        | Julia      |
| `javascript`                   | JavaScript |
| `typescript`                   | TypeScript |
| `rust`                         | Rust       |
| `go`                           | Go         |
| `c`, `c++`, `cpp`              | C++        |
| `java`                         | Java       |
| `ruby`                         | Ruby       |
| `lua`                          | Lua        |
| `php`                          | PHP        |
| `haskell`                      | Haskell    |
| `elixir`                       | Elixir     |
| `clojure`                      | Clojure    |
| `perl`                         | Perl       |
| `csharp`, `c#`                 | C#         |
| `scala`                        | Scala      |
| `powershell`                   | PowerShell |

A comment carries the same scopes it would in a source file, `text.comment.line.py` for a one-line Python comment, so a rule can target notebook code by language. Code cells in a language that is not listed, a Bash kernel for one, are skipped.

## [Older notebooks](#older-notebooks)

Notebooks with `nbformat` 3 are read too: cells are found in each worksheet, a heading cell is read as a Markdown heading of its level, and a code cell's `language` field names its language.

## [Views](#views)

A [View](/topics/views) on the section takes precedence over the built-in reading, for anyone who wants a different one: only the Markdown cells, say, or the strings in a cell's outputs.


# AsciiDoc

Learn how Vale handles AsciiDoc content.

AsciiDoc is supported through the external program [Asciidoctor](https://asciidoctor.org/). See their [installation](https://docs.asciidoctor.org/asciidoctor/latest/install) instructions to get started. You’ll need to ensure that the `asciidoctor` executable is available in your `$PATH`.

The supported extensions are `.adoc`, `.asciidoc`, and `.asc`.

By default, Vale ignores:

* [Literals and source code](https://docs.asciidoctor.org/asciidoc/latest/syntax-quick-reference/#literals-and-source-code).
* URLs: See [URL handling](https://github.com/vale-cli/vale/issues/320) for more information.

## [Attributes](#attributes)

You can customize how `asciidoctor` is called by passing [document attributes](https://docs.asciidoctor.org/asciidoc/latest/attributes/document-attributes-ref/):

```ini
StylesPath = styles

[asciidoctor]
# attribute = value
#
# where 'YES' enables and 'NO' disables.

# enable
experimental = YES

# assign a specific value
attribute-missing = drop

[*.adoc]
BasedOnStyles = Vale
```

By default, Vale switches off the attributes that generate text of their own—section numbers (`sectnums`), the table of contents (`toc`), and caption labels such as the "Table 1." prefixed to a block title (`table-caption`, `figure-caption`, `example-caption`, `listing-caption`). Generated text appears nowhere in the source, so a rule matching it would report an unrelated line. It also sets `attribute-missing=drop`, so a reference to an undefined attribute disappears rather than being linted as text, and renders the document title. An attribute set in the `[asciidoctor]` section overrides these defaults.

A cross-reference without text of its own, `<<setup>>` or `xref:ch2.adoc#setup[]`, is treated the same way (Vale v3.25.0 or later). Asciidoctor would otherwise invent text for it: the target's title when it resolves, and `[setup]` or the file path when it doesn't, which is what every reference into another chapter of a book gets when a chapter is linted on its own. Vale renders the link empty, so only text the author wrote, `xref:setup[the setup steps]`, is linted. To lint a book, lint its chapter files; `include::` is not followed.

## [Comments](#comments)

Vale supports comment-based configuration in AsciiDoc files:

{% hint style="warning" %}
Make sure to surround the inline passthrough statements with newlines, as shown below.
{% endhint %}

* Turn Vale off entirely:

```adoc
pass:[<!-- vale off -->]

This text will be ignored.

pass:[<!-- vale on -->]
```

* Turn off a specific rule:

```adoc
pass:[<!-- vale Style.Redundancy = NO -->]

This is some text ACT test

pass:[<!-- vale Style.Redundancy = YES -->]
```

* Turn off specific match(es) within a rule:

```adoc
pass:[<!-- vale Style.Redundancy["ACT test","OTHER"\] = NO -->]

This is some text ACT test

pass:[<!-- vale Style.Redundancy["ACT test","OTHER"\] = YES -->]
```

The `]` that closes the match list is escaped as `\]`: a `pass:[]` passthrough ends at the first unescaped `]`, and without the escape the comment is cut short before Vale reads it.

* Turn on or off specific styles:

```adoc
pass:[<!-- vale StyleName1 = YES -->]
pass:[<!-- vale StyleName2 = NO -->]
```

* Set styles (enabling them and switching off any other styles):

```adoc
pass:[<!-- vale style = StyleName1 -->]
pass:[<!-- vale styles = StyleName1, StyleName2 -->]
```


# MDX

Learn how Vale handles MDX content.

{% hint style="info" %}
Vale v3.18.0 or later parses [MDX](https://mdxjs.com/) natively. Earlier versions require the external program [`mdx2vast`](https://github.com/jdkato/mdx2vast) (`npm install -g mdx2vast`) on your `$PATH`.
{% endhint %}

The supported extension is `.mdx`.

MDX is Markdown plus ESM statements, JSX elements, and JavaScript expressions. The JavaScript holds no prose, and Vale treats it as code and ignores it:

* JSX tags, their attributes, and self-closing elements (`<Chart data={population} />`).
* ESM `import` and `export` statements, including multiline bodies.
* JavaScript expressions—inline (`{Math.PI * 2}`) and standing on their own.
* Fenced blocks: Blocks surrounded by three or more backticks.
* Code spans: Text surrounded by backticks.

MDX removed indented code blocks from the grammar, so indentation is never code. A paragraph, a fence, a heading, or a list indented four or more spaces, the way `<Steps>` and `<Tabs>` children are often written, is read as what it is.

An expression is only a block of its own when it stands alone on its line. Followed by prose, `{/* note */}Some text.`, the line is a paragraph holding an inline expression, and the prose is linted.

Inside an expression, the text of a JSX element is read as text: an apostrophe or a bracket in `title={<>Don't (yet)</>}` means nothing. Inside a tag's attributes, an escaped quote (`'the cluster\'s version'`) is part of its string.

A tag or an expression left open to the end of the file, such as a `{` with no `}`, is invalid MDX, which the MDX compiler rejects. Vale reports it as an error at the line where the block opens (Vale v3.25.0 or later), since everything after it would otherwise be read as code and never checked. Other files in the run are still linted, and the run exits with code `2`; see [exit codes](/topics/cli#exit-codes).

## [JSX children](#jsx-children)

{% hint style="info" %}
Requires Vale v3.19.0 or later. Earlier versions skipped a JSX element entirely, children included.
{% endhint %}

A JSX element's *children* are Markdown, just as MDX itself reads them, so the prose inside `<Steps>...</Steps>` or `<Aside>...</Aside>` is linted:

```mdx
<Aside type="info">
  This text is linted. The `type` attribute is not.
</Aside>
```

The children carry the element's name as a [class scope](/topics/scopes#class-scopes), so a rule can target one component's content (`scope: text.class.Aside`), and a component whose content shouldn't be linted can be excluded by name:

```ini
IgnoredClasses = RawOutput
```

The same applies inline: in `<abbr>HTML</abbr> is a language`, the word "HTML" is linted as part of its sentence.

An element opened and closed on one line with text between its tags, `<Tip>This is a tip</Tip>`, is a paragraph holding an inline element, so its text is linted. One without text, `<video src="a.mp4"></video>`, is code.

## [The MDX package](#the-mdx-package)

{% hint style="info" %}
This package exists for versions before v3.18.0, whose parser threw on inline expressions that aren't valid JavaScript—ending the run rather than the file. The native parser reads them without complaint.
{% endhint %}

The [`MDX`](https://github.com/vale-cli/MDX) package carries the configuration for those cases:

```ini
Packages = MDX
```

See [`Packages`](/keys/packages) for more information.

## [Comments](#comments)

Vale supports comment-based configuration in MDX files:

* Turn Vale off entirely:

```mdx
{/* vale off */}

This text will be ignored.

{/* vale on */}
```

A comment works inline as well, `Some {/* vale off */}ignored{/* vale on */} text`.

* Turn off a specific rule:

```mdx
{/* vale Style.Redundancy = NO */}

This is some text ACT test

{/* vale Style.Redundancy = YES */}
```

* Turn off specific match(es) within a rule:

```mdx
{/* vale Style.Redundancy["ACT test","OTHER"] = NO */}

This is some text ACT test

{/* vale Style.Redundancy["ACT test","OTHER"] = YES */}
```

* Turn on or off specific styles:

```mdx
{/* vale StyleName1 = YES */}

{/* vale StyleName2 = NO */}
```

* Set styles (enabling them and switching off any other styles):

```mdx
{/* vale style = StyleName1 */}
{/* vale styles = StyleName1, StyleName2 */}
```


# HTML

Learn how Vale handles HTML content.

HTML5 support is built in. The supported extensions are `.html`, `.htm`, `.shtml`, and `.xhtml`.

By default, Vale skips the `script`, `style`, `pre`, `noscript`, and `iframe` elements, everything inside them included, and the inline `code`, `tt`, `kbd`, and `samp` elements. An element carrying the class `pre`, `code`, or `problematic` is skipped too. URLs are ignored as well (see [URL handling](https://github.com/vale-cli/vale/issues/320) for more information).

Two attributes are prose and are linted: an image's `alt` text, as `text.attr.alt`, and the page's description -- `<meta name="description">` or `og:description` -- as `text.attr.description`. Naming `alt` or `description` in `SkippedScopes` leaves one out.

The block list is replaced by [`SkippedScopes`](/keys/skippedscopes), the inline list by [`IgnoredScopes`](/keys/ignoredscopes), and the classes are added to by [`IgnoredClasses`](/keys/ignoredclasses).

## [Templates](#templates)

Text that has no element of its own can be excluded by pattern: a [`BlockIgnores`](/keys/blockignores) match is wrapped in `<pre>`, a [`TokenIgnores`](/keys/tokenignores) match in `<code>`. That masks the tags of a template language, so the prose around them is linted and the variables are not:

```ini
[*.html]
BasedOnStyles = Vale

BlockIgnores = (?s)({%\s*comment\s*%}.*?{%\s*endcomment\s*%})
TokenIgnores = ({{.*?}}), ({%.*?%}), ({#.*?#})
```

Text inside a tag, such as the string in `{% translate "..." %}`, is masked with it. A plain-text template, such as an email, has no markup to wrap a match in; read it as Markdown instead, which accepts the same patterns:

```ini
[formats]
txt = md
```

## [Comments](#comments)

Vale supports comment-based configuration in HTML files:

* Turn Vale off entirely:

```html
<!-- vale off -->

This text will be ignored.

<!-- vale on -->
```

* Turn off a specific rule:

```html
<!-- vale Style.Redundancy = NO -->

This is some text ACT test

<!-- vale Style.Redundancy = YES -->
```

* Turn off specific match(es) within a rule:

```html
<!-- vale Style.Redundancy["ACT test","OTHER"] = NO -->

This is some text ACT test

<!-- vale Style.Redundancy["ACT test","OTHER"] = YES -->
```

* Turn on or off specific styles:

```html
<!-- vale StyleName1 = YES -->
<!-- vale StyleName2 = NO -->
```

* Set styles (enabling them and switching off any other styles):

```html
<!-- vale style = StyleName1 -->
<!-- vale styles = StyleName1, StyleName2 -->
```


# reStructuredText

Learn how Vale handles reStructuredText content.

reStructuredText is supported through the external program [`rst2html`](http://docutils.sourceforge.net/docs/user/tools.html#rst2html-py). To get started, you’ll need to install the [`docutils`](https://pypi.org/project/docutils/) package:

```bash
$ pip install docutils
```

Vale looks for `rst2html`, `rst2html.py`, `rst2html-3`, or `rst2html-3.py` on your `$PATH`, which installing `docutils` normally provides. The script is asked which Python it runs under, so the interpreter `docutils` was installed into is the one used, even when it is not the `python3` on the path.

The supported extensions are `.rst` and `.rest`.

A [Sphinx](/formats/sphinx) project is read the same way; that page covers its directives and roles.

By default, Vale ignores:

* [Literal blocks](https://docutils.sourceforge.io/docs/user/rst/quickref.html#literal-blocks).
* [Inline literals](https://docutils.sourceforge.io/docs/user/rst/quickref.html#inline-markup).
* URLs: See [URL handling](https://github.com/vale-cli/vale/issues/320) for more information.

## [Directives and roles](#directives-and-roles)

Docutils defines the standard directives and roles. A Sphinx project uses Sphinx's as well, and whatever its extensions add, and on its own Docutils drops the body of a directive it doesn't know. Vale fills the gap before parsing: the body of an unknown directive is read as prose, and the text of an unknown role as code, which is what nearly every Sphinx directive and role amounts to. So `versionadded`, `seealso`, a `tab`, or a `grid` card is linted like any paragraph, and `:func:` or `:doc:` is left alone, without Sphinx being installed.

The exceptions Vale knows are Sphinx's own: `toctree`, `literalinclude`, `math`, the `auto*` family, and the doctest blocks are not prose; `ref`, `doc`, `term`, `guilabel`, and `menuselection` carry prose. A reference written with a title, `` :ref:`the setup guide <setup>` `` , is linted as its title; a bare target is code. A project names what its extensions add in a `[sphinx]` section:

```ini
[sphinx]
# Directives whose body is code, data, or a diagram: not linted.
CodeDirectives = mermaid, plantuml
# Roles whose text is prose rather than an identifier.
ProseRoles = kbd
```

Both keys add to the built-in lists. Requires Vale v3.23.0 or later.

## [Comments](#comments)

Vale supports comment-based configuration in reStructuredText files:

* Turn Vale off entirely:

```rst
.. vale off

This text will be ignored.

.. vale on
```

* Turn off a specific rule:

```rst
.. vale Style.Redundancy = NO

This is some text ACT test

.. vale Style.Redundancy = YES
```

* Turn off specific match(es) within a rule:

```rst
.. vale Style.Redundancy["ACT test","OTHER"] = NO

This is some text ACT test

.. vale Style.Redundancy["ACT test","OTHER"] = YES
```

* Turn on or off specific styles:

```rst
.. vale StyleName1 = YES
.. vale StyleName2 = NO
```

* Set styles (enabling them and switching off any other styles):

```rst
.. vale style = StyleName1
.. vale styles = StyleName1, StyleName2
```


# Sphinx

Learn how Vale handles a Sphinx project.

{% hint style="info" %}
Requires Vale v3.23.0 or later.
{% endhint %}

A [Sphinx](https://www.sphinx-doc.org/) project is reStructuredText, or MyST, plus the directives and roles Sphinx and its extensions define: `versionadded`, `seealso`, `toctree`, `:ref:`, `:func:`, and whatever `sphinx-tabs`, `sphinx-design`, or the project's own `conf.py` adds. Vale reads it through [Docutils](/formats/restructuredtext), the way it reads any reStructuredText, and does not need Sphinx installed.

## [Directives and roles](#directives-and-roles)

Docutils knows only its own directives and roles, so Vale decides the rest by a rule that fits nearly every Sphinx construct: the body of a directive Docutils doesn't define is prose, and the text of a role it doesn't define is code. A `versionadded`, a `seealso`, a `tab`, a `grid` card, or a `py:function` description is linted like any paragraph; `:func:`, `:class:`, and `:doc:` are left alone.

The exceptions Vale knows are Sphinx's own. `toctree`, `literalinclude`, `math`, the `auto*` family, and the doctest blocks are not prose. `ref`, `doc`, `any`, `numref`, `term`, `guilabel`, `menuselection`, `abbr`, and `dfn` carry prose: a reference written with a title, `` :ref:`the setup guide <setup>` `` , is linted as its title, and a bare target such as `` :doc:`guide/setup` `` is code.

An extension's additions that don't fit the rule are named in a `[sphinx]` section. Both keys add to the built-in lists:

```ini
StylesPath = styles

[sphinx]
# Directives whose body is code, data, or a diagram: not linted.
CodeDirectives = mermaid, plantuml
# Roles whose text is prose rather than an identifier.
ProseRoles = kbd, samp

[*.rst]
BasedOnStyles = Vale
```

## [What is and isn't read](#what-is-and-isnt-read)

Vale lints the source as written. Substitutions such as `|release|` are not expanded, `include` and `only` are not resolved, and content autodoc generates from docstrings is not in the file, so none of it is linted. Comments still carry Vale's [directives](/formats/restructuredtext#comments), so `.. vale off` works inside a Sphinx project as anywhere else.

A project written in MyST goes through Vale's own [MyST](/formats/myst) reader, which applies the same rule and the same `[sphinx]` section to the directive and role syntax in Markdown.

## [Setting up](#setting-up)

Install `docutils` into the environment the project builds with, so that `rst2html` is on your `$PATH`:

```bash
$ pip install docutils
```

Vale finds the interpreter from the script and keeps one Docutils process warm per run, so a project of a few hundred pages converts in a second or two. Nothing about the project's `conf.py`, theme, or extensions has to be installed for Vale to run.


# Typst

Learn how Vale handles Typst content.

{% hint style="info" %}
Requires Vale v3.18.0 or later.
{% endhint %}

[Typst](https://typst.app/) is supported through the external program [`typst2vast`](https://github.com/jdkato/typst2vast), which reads documents with `typst-syntax`—the Typst compiler's own parser—without ever evaluating them. To get started, install the CLI:

```console
$ cargo install typst2vast
```

You'll need to ensure that the `typst2vast` executable is available in your `$PATH` (this should happen automatically).

The supported extension is `.typ`.

Because nothing is compiled, a document that doesn't build still lints, no package is ever fetched, and no `#show` rule can move an alert off its source text.

By default, Vale ignores:

* Code mode: `#` expressions and `#let`, `#set`, `#show`, and `#import` statements.
* Raw text: inline `` `spans` `` and fenced blocks.
* Math: `$x^2$` and display equations.
* Comments: `//` and `/* ... */`, nesting included.
* Labels (`<my-label>`) and references (`@my-label`).

Everything else is prose in its scope: `=` headings are `heading`, `*strong*` and `_emphasis_` are `strong` and `emphasis`, raw spans are `code`, and list, numbered, and term items are `list`.

## [Content blocks](#content-blocks)

A `[content]` block anywhere inside code mode is prose, and Vale lints it—a figure's caption, a conditional's branches, a `#let`-bound body:

```typst
#figure(
  image("diagram.png", width: 70%),
  caption: [This caption is linted.],
)

#if release [This branch is linted.] else [So is this one.]
```


# XML

Learn how Vale handles XML content.

XML is supported through the external program [`xsltproc`](http://xmlsoft.org/XSLT/xsltproc.html). To install, see:

* [Chocolatey](https://community.chocolatey.org/packages/xsltproc) (Windows): `choco install xsltproc`.
* [Homebrew](https://formulae.brew.sh/formula/libxslt) (macOS): `brew install libxslt`.
* Debian/Ubuntu/apt-based systems: `apt-get install xsltproc`.

You’ll need to ensure that the `xsltproc` executable is available in your `$PATH`.

The supported extensions are `.xml` and `.xsd`.

You also need to provide a version 1.0 XSL Transformation (XSLT) for converting to HTML:

{% code title=".vale.ini" %}

```ini
[*.xml]
Transform = docbook-xsl-snapshot/html/docbook.xsl
```

{% endcode %}

A stylesheet is configuration that runs as a program, so Vale runs `xsltproc` with `--nonet`, `--nowrite`, and `--nomkdir`: the stylesheet can't fetch over the network, write a file, or create a directory, as a document under the AsciiDoc converter's safe mode can't include a file from outside the project. A stylesheet that tries is refused, and the run reports it as an error for that file.

Once converted, Vale will follow the same rules as it does for [HTML](/formats/html).


# Org

Learn how Vale handles Org content.

[Org](https://orgmode.org/) support is built in. The supported extension is `.org`.

By default, Vale ignores:

* [Code blocks](https://orgmode.org/org.html#Structure-of-Code-Blocks).
* [Literal examples](https://orgmode.org/org.html#Literal-Examples).
* [Code and verbatim strings](https://orgmode.org/org.html#Emphasis-and-Monospace-1).
* URLs: See [URL handling](https://github.com/vale-cli/vale/issues/320) for more information.
* Keywords such as `#+OPTIONS:` and `#+PROPERTY:`, and the tags at the end of a headline.

The `#+TITLE:`, `#+SUBTITLE:`, `#+AUTHOR:`, and `#+DESCRIPTION:` keywords hold prose and are linted: the title as a heading, the rest as paragraphs. A footnote's definition is linted where it is written, so an alert in one points at that line.

## [Comments](#comments)

Vale supports comment-based configuration in Org files:

* Turn Vale off entirely:

```org
# vale off

This text will be ignored.

# vale on
```

* Turn off a specific rule:

```org
# vale Style.Redundancy = NO

This is some text ACT test

# vale Style.Redundancy = YES
```

* Turn off specific match(es) within a rule:

```org
# vale Style.Redundancy["ACT test","OTHER"] = NO

This is some text ACT test

# vale Style.Redundancy["ACT test","OTHER"] = YES
```

* Turn on or off specific styles:

```org
# vale StyleName1 = YES
# vale StyleName2 = NO
```

* Set styles (enabling them and switching off any other styles):

```org
# vale style = StyleName1
# vale styles = StyleName1, StyleName2
```


# DITA

Learn how Vale handles DITA content.

{% hint style="warning" %}
The third-party `dita` command takes a second or two to start. A run converts every DITA file it lints in one call, so that cost is paid once per run rather than once per file.
{% endhint %}

DITA is supported through the [DITA Open Toolkit](https://www.dita-ot.org/). You’ll need to follow the [installation instructions](https://www.dita-ot.org/dev/topics/installing-client.html), including the optional step of adding the absolute path for the `bin` directory to the `PATH` system variable.

The supported extension is `.dita`.

Vale runs `dita` with the `html5` transformation and lints the result, so the [HTML](/formats/html) rules apply: `<codeblock>` becomes `<pre>` and is skipped, and `<codeph>` and `<tt>` become inline code.

[`TokenIgnores`](/keys/tokenignores) and [`BlockIgnores`](/keys/blockignores) apply to DITA. The toolkit reads its input from disk, so a file an ignore pattern changes is converted from a temporary copy written beside it, where its references still resolve, and the copy is removed afterward. Ignore patterns in DITA require Vale v3.22.0 or later.


# QDoc

Learn how Vale handles QDoc content.

{% hint style="info" %}
Requires Vale v3.18.0 or later.
{% endhint %}

[QDoc](https://doc.qt.io/qt-6/01-qdoc-manual.html) is Qt's documentation markup: LaTeX-style commands inside `/*! ... */` comment blocks. Support is built in—nothing to install.

The supported extensions are `.qdoc` and `.qdocinc`. An include file is the body of a comment without the `/*! ... */` around it, and is read that way. Doc comments in C++ and QML sources lint through a [format association](/topics/.vale.ini#format-associations):

```ini
[formats]
cpp = qdoc
qml = qdoc

[*.{qdoc,cpp,qml}]
BasedOnStyles = Vale
```

Alerts are mapped back to their positions in the source file. Only block comments (`/*! ... */`) are treated as documentation—`//` line comments are code.

By default, Vale ignores:

* Code blocks: `\code`, `\badcode`, `\qml`, and friends, through their `\end` commands.
* Omitted text: `\omit ... \endomit`.
* Topic and context commands: `\fn`, `\class`, `\page`, `\module`, `\since`, and the rest of their family—the whole line is markup.
* Quoting commands: `\snippet`, `\quotefile`, `\printline`, and so on.
* Links' targets: `\l {target} {text}` keeps its text and drops its target; `\sa` lines say nothing.
* Inline code: `\c` and `\a` arguments.

Everything else is prose in its scope: `\title` and `\section1` through `\section4` are headings, `\li` items are `list` entries or table cells, `\b` and `\e` are `strong` and `emphasis`, and `\caption` is a figure caption.

## [Classed paragraphs](#classed-paragraphs)

`\brief`, `\note`, `\warning`, and `\important` become [class scopes](/topics/scopes#class-scopes), so a rule can target them by name:

```yaml
extends: existence
message: "Don't use '%s' in a note."
scope: class.note
level: error
tokens:
  - obviously
```

An unknown command is masked with its text kept, so a QDoc extension Vale doesn't know about degrades to plain prose rather than to noise.


# Plain text

Learn how Vale handles files with no markup.

The supported extension is `.txt`. A file is read as prose: blank lines separate paragraphs, and the text is segmented into sentences, so a rule scoped to `paragraph` or `sentence` works as it does in a markup file. The `text` scope is the whole file as one block, so a pattern with `\s+` in it can match across a blank line there. Nothing is ignored, since nothing marks code or links.

## [Manuscripts](#manuscripts)

{% hint style="info" %}
Requires Vale v3.23.0 or later.
{% endhint %}

A plain-text file that marks its chapters is read as a document. A line of its own reading `Chapter One`, `Prologue`, `Epilogue`, `Interlude`, `Part II`, or a bare number such as `7` or `IV` is a heading, and everything after it up to the next such line is its section. A first short line followed by a chapter heading is the title. A row of asterisks, hashes, or dashes on its own line is a scene break.

Chapters and numbered headings are `heading.h2`; a part, book, or act is `heading.h1`, as is the title. With that structure, the rules a Markdown manuscript gets reach the `.txt` one: a rule scoped to `heading`, or to a selection such as `doc(section:has(> h2))`, sees the same chapters in either format. A file with no chapter line is read as it always was, one block of prose.

Any other extension Vale does not know is read as one block of text under the `text` scope, without paragraph or sentence segmentation. A [format association](/topics/.vale.ini#format-associations) reads such a file as one of the formats Vale knows:

```ini
[formats]
log = txt
```

Text that has structure by convention, a commit message, a transcript, a subtitle file, can be given scopes of its own through a [TextFSM View](/topics/views#text), so that a rule reaches the subject or one speaker's turns by name.

`--ignore-syntax` reads every file this way, line by line, whatever its extension.


# Code

Learn how Vale handles source code.

Vale lints the comments in source code, found with a [tree-sitter](https://tree-sitter.github.io/tree-sitter/) grammar for each language below. Clojure and PowerShell have no bundled grammar and are read with comment patterns instead. A one-line comment is scoped `text.comment.line` and a comment spanning lines `text.comment.block`, each followed by the file's extension.

| Language   | Extensions                                           | Scopes                                                                                                                                                                                                                                                                        |
| ---------- | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| C          | `.c`, `.h`                                           | <p><code>//</code> (<code>text.comment.line.ext</code>),<br><code>/</code><em><code>...</code></em><code>/</code> (<code>text.comment.line.ext</code>),<br><code>/\*</code> (<code>text.comment.block.ext</code>)</p>                                                         |
| Clojure    | `.clj`, `.cljs`, `.cljc`, `.cljd`                    | `;` (`text.comment.line.ext`)                                                                                                                                                                                                                                                 |
| C#         | `.cs`, `.csx`                                        | <p><code>//</code> (<code>text.comment.line.ext</code>),<br><code>/</code><em><code>...</code></em><code>/</code> (<code>text.comment.line.ext</code>),<br><code>/\*</code> (<code>text.comment.block.ext</code>)</p>                                                         |
| C++        | `.cpp`, `.cc`, `.cp`, `.cxx`, `.c++`, `.hpp`, `.h++` | <p><code>//</code> (<code>text.comment.line.ext</code>),<br><code>/</code><em><code>...</code></em><code>/</code> (<code>text.comment.line.ext</code>),<br><code>/\*</code> (<code>text.comment.block.ext</code>)</p>                                                         |
| CSS        | `.css`                                               | <p><code>/</code><em><code>...</code></em><code>/</code> (<code>text.comment.line.ext</code>),<br><code>/\*</code> (<code>text.comment.block.ext</code>)</p>                                                                                                                  |
| Elixir     | `.ex`, `.exs`                                        | <p><code>#</code> (<code>text.comment.line.ext</code>),<br><code>@doc</code> (<code>text.comment.doc.line.ext</code>),<br><code>@moduledoc</code> (<code>text.comment.doc.block.ext</code>)</p>                                                                               |
| Go         | `.go`                                                | <p><code>//</code> (<code>text.comment.line.ext</code>),<br><code>/</code><em><code>...</code></em><code>/</code> (<code>text.comment.line.ext</code>),<br><code>/\*</code> (<code>text.comment.block.ext</code>)</p>                                                         |
| Haskell    | `.hs`                                                | <p><code>--</code> (<code>text.comment.line.ext</code>),<br><code>{-</code> (<code>text.comment.block.ext</code>)</p>                                                                                                                                                         |
| Java       | `.java`, `.bsh`                                      | <p><code>//</code> (<code>text.comment.line.ext</code>),<br><code>/</code><em><code>...</code></em><code>/</code> (<code>text.comment.line.ext</code>),<br><code>/\*</code> (<code>text.comment.block.ext</code>)</p>                                                         |
| JavaScript | `.js`, `.jsx`, `.mjs`, `.cjs`                        | <p><code>//</code> (<code>text.comment.line.ext</code>),<br><code>/</code><em><code>...</code></em><code>/</code> (<code>text.comment.line.ext</code>),<br><code>/\*</code> (<code>text.comment.block.ext</code>)</p>                                                         |
| Julia      | `.jl`                                                | <p><code>#</code> (<code>text.comment.line.ext</code>),<br><code>#=</code> (<code>text.comment.block.ext</code>)</p>                                                                                                                                                          |
| Kotlin     | `.kt`, `.kts`                                        | <p><code>//</code> (<code>text.comment.line.ext</code>),<br><code>/</code><em><code>...</code></em><code>/</code> (<code>text.comment.line.ext</code>),<br><code>/\*</code> (<code>text.comment.block.ext</code>)</p>                                                         |
| LESS       | `.less`                                              | <p><code>//</code> (<code>text.comment.line.ext</code>),<br><code>/</code><em><code>...</code></em><code>/</code> (<code>text.comment.line.ext</code>),<br><code>/\*</code> (<code>text.comment.block.ext</code>)</p>                                                         |
| Lua        | `.lua`                                               | <p><code>--</code> (<code>text.comment.line.ext</code>),<br><code>--\[\[</code> (<code>text.comment.block.ext</code>)</p>                                                                                                                                                     |
| Perl       | `.pl`, `.pm`, `.pod`                                 | `#` (`text.comment.line.ext`)                                                                                                                                                                                                                                                 |
| PHP        | `.php`                                               | <p><code>//</code> (<code>text.comment.line.ext</code>),<br><code>#</code> (<code>text.comment.line.ext</code>),<br><code>/</code><em><code>...</code></em><code>/</code> (<code>text.comment.line.ext</code>),<br><code>/\*</code> (<code>text.comment.block.ext</code>)</p> |
| PowerShell | `.ps1`, `.psm1`, `.psd1`                             | <p><code>#</code> (<code>text.comment.line.ext</code>),<br><code><#...#></code> (<code>text.comment.line.ext</code>),<br><code><#</code> (<code>text.comment.block.ext</code>)</p>                                                                                            |
| Protobuf   | `.proto`                                             | <p><code>//</code> (<code>text.comment.line.ext</code>),<br><code>/</code><em><code>...</code></em><code>/</code> (<code>text.comment.line.ext</code>),<br><code>/\*</code> (<code>text.comment.block.ext</code>)</p>                                                         |
| Python     | `.py`, `.py3`, `.pyw`, `.rpy`, `.cpy`, `SConstruct`  | <p><code>#</code> (<code>text.comment.line.ext</code>),<br><code>"""</code> (<code>text.comment.block.ext</code>)</p>                                                                                                                                                         |
| QML        | `.qml`                                               | <p><code>//</code> (<code>text.comment.line.ext</code>),<br><code>/</code><em><code>...</code></em><code>/</code> (<code>text.comment.line.ext</code>),<br><code>/\*</code> (<code>text.comment.block.ext</code>)</p>                                                         |
| R          | `.r`, `.R`                                           | `#` (`text.comment.line.ext`)                                                                                                                                                                                                                                                 |
| Ruby       | `.rb`, `Gemfile`, `Rakefile`, `Brewfile`, `.gemspec` | <p><code>#</code> (<code>text.comment.line.ext</code>),<br><code>^=begin</code> (<code>text.comment.block.ext</code>)</p>                                                                                                                                                     |
| Rust       | `.rs`                                                | `//` (`text.comment.line.ext`)                                                                                                                                                                                                                                                |
| Sass       | `.sass`, `.scss`                                     | <p><code>//</code> (<code>text.comment.line.ext</code>),<br><code>/</code><em><code>...</code></em><code>/</code> (<code>text.comment.line.ext</code>),<br><code>/\*</code> (<code>text.comment.block.ext</code>)</p>                                                         |
| Scala      | `.scala`, `.sbt`                                     | <p><code>//</code> (<code>text.comment.line.ext</code>),<br><code>/</code><em><code>...</code></em><code>/</code> (<code>text.comment.line.ext</code>),<br><code>/\*</code> (<code>text.comment.block.ext</code>)</p>                                                         |
| Swift      | `.swift`                                             | <p><code>//</code> (<code>text.comment.line.ext</code>),<br><code>/</code><em><code>...</code></em><code>/</code> (<code>text.comment.line.ext</code>),<br><code>/\*</code> (<code>text.comment.block.ext</code>)</p>                                                         |
| TypeScript | `.ts`, `.tsx`, `.mts`, `.cts`                        | <p><code>//</code> (<code>text.comment.line.ext</code>),<br><code>/</code><em><code>...</code></em><code>/</code> (<code>text.comment.line.ext</code>),<br><code>/\*</code> (<code>text.comment.block.ext</code>)</p>                                                         |

A `.tsx` file is parsed with the TSX grammar, so a [View](/topics/views) can query its JSX nodes, such as `jsx_text`. Its comments are read as TypeScript's.

A URL in a comment is skipped, as one in markup is; see [URLs](/topics/urls).

## [Documentation attributes](#documentation-attributes)

{% hint style="info" %}
Requires Vale v3.19.0 or later.
{% endhint %}

Elixir has no documentation comment syntax: its published API documentation lives in module attributes holding a string or a heredoc, and that is what `mix docs` renders.

```elixir
defmodule Session do
  @moduledoc """
  A scheduled period of care delivery.
  """

  @doc "Books a session for a client."
  def book(client), do: ...
end
```

Both comments and attributes are extracted. `@moduledoc`, `@doc`, `@typedoc`, and `@shortdoc` carry a `doc` scope—`text.comment.doc.line` and `text.comment.doc.block`—so published documentation can be held to a different standard than an implementation note, or excluded on its own via [`IgnoredScopes`](/keys/ignoredscopes). `@doc false` and `@doc since: "1.0.0"` hold no prose, and neither is extracted.

## [Associations](#associations)

In many languages, it’s common for comments to contain *embedded markup* (e.g., Markdown, reStructuredText, etc.) within them. For example, consider the following Rust doc comment:

````rust
impl Person {
    /// Creates a person with the given name.
    ///
    /// # Examples
    ///
    /// ```
    /// // You can have rust code between fences
    /// // inside the comments If you pass --test
    /// // to `rustdoc`, it will even test it for
    /// // you!
    /// use doc::Person;
    /// let person = Person::new("name");
    /// ```
    pub fn new(name: &str) -> Person {
        Person {
            name: name.to_string(),
        }
    }
}
````

If the embedded markup is one of the supported formats, you can associate the `comment` scope with a `markup` type. This will allow you to lint the embedded markup as if it were a standalone file.

```ini
StylesPath = styles
MinAlertLevel = suggestion

[formats]
# Rust + Markdown
rs = md

[*.{rs,md}]
BasedOnStyles = Vale
```

![How embedded markup is linted: tree-sitter finds each comment in the source file, the per-line decoration is stripped, the remaining body is parsed as Markdown, and every alert is mapped back to its original line and column in the source.](https://2533404145-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEMH7s0sC5N9L8xSMXGwW%2Fuploads%2Fgit-blob-60a622f0d1581ca4ab80418b302590502034a14f%2Fembedded.svg?alt=media)

A [View](/topics/views) does the same per query: a scope's `type` names the markup for the comments that query finds, so a Python docstring can be read as reStructuredText while the file's `#` comments stay plain text.

Once a markup format has been assigned, you can make use of all the supported features of that format (such as ignore patterns and comment-based configuration) in your source code comments.

This includes [`TokenIgnores`](/keys/tokenignores) and [`BlockIgnores`](/keys/blockignores), which are otherwise unavailable in source code: they work by wrapping a match in the format's inline or block code delimiter, so they need a markup format to wrap it with. Associating one makes them available.

### [Block comment decoration](#block-comment-decoration)

{% hint style="info" %}
Requires Vale v3.17.0 or later. Earlier versions passed the leading asterisks through to the markup parser, which read a block comment as a single list.
{% endhint %}

Block comments in C-style languages conventionally decorate each line with a leading asterisk:

```javascript
/**
 * Reads the record and returns it.
 *
 * Pass `refresh` to bypass the cache:
 *
 * * `refresh: true` re-reads from disk.
 * * `refresh: false` uses the cache.
 */
```

That decoration is removed before the comment is handed to the markup parser, so the body above is read as a paragraph followed by a list—not as one long list, which is what the leading asterisks would otherwise make it.

Relative indentation is preserved, so indented code blocks inside a comment still work:

````javascript
/**
 * Formats the value for display.
 *
 * ```
 * const output = format(value);
 * ```
 */
````

The fenced block is treated as code and left alone, exactly as it would be in a standalone Markdown file.

{% hint style="info" %}
An asterisk is only treated as decoration when whitespace or the end of the line follows it. A line beginning `*emphasis*` or `**bold**` keeps its markup.
{% endhint %}

### [Documentation conventions](#documentation-conventions)

A documentation comment has a convention of its own on top of the language's comment syntax, and the parts of it that name code are not linted:

| Language   | Not linted                                                                                                                                            |
| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| Go         | The name a doc comment opens with (`// Println formats ...`, `// Package fmt ...`), and links in brackets (`[Name]`, `[pkg.Name]`).                   |
| Rust       | Intra-doc links (``[`Type`]``, `[module::item]`).                                                                                                     |
| Kotlin     | Links (`[name]`, `[text][name]`), and the symbol a block tag names (`@param name`, `@throws Type`).                                                   |
| Java       | Inline tags (`{@code ...}`, `{@link ...}`), and the symbol a block tag names (`@param name`, `@throws Type`).                                         |
| JavaScript | Inline tags (`{@link ...}`), a block tag with its type and name (`@param {string} name`, `@returns {Promise}`), and the whole of an `@example` block. |
| Python     | The field names of a docstring's field list (`:param name:`, `:raises Type:`, `:returns:`).                                                           |

The description after a tag is prose and is linted as usual.

### [Directives](#directives)

A comment addressed to a tool rather than a reader is not read at all: a Go build constraint or `//nolint` line, a Python `# noqa` or `# type:` comment, an `eslint` or `@ts-` comment in JavaScript, `NOPMD` and `CHECKSTYLE` in Java, `ktlint` in Kotlin, `NOLINT` and `clang-format` in C and C++, and `rubocop:` and `frozen_string_literal:` in Ruby.


# LSP

Get started with Vale's Language Server.

The Vale Language Server (`vale-ls`) implements the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/) around a local installation of Vale, giving any editor that speaks LSP autocomplete, diagnostics, hover popups, and quick fixes.

Most people don't run it directly—an editor plugin does. See [Editors](#editors) below.

## [Configuration](#configuration)

The server reads its settings from the `initializationOptions` your client sends when it connects:

```json
{
  "initializationOptions": {
    "installVale": true,
    "syncOnStartup": true,
    "filter": "",
    "configPath": ""
  }
}
```

| Option           | Type      | Default | Description                                                                                                                                                          |
| ---------------- | --------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `installVale`    | `boolean` | `false` | Install and update Vale into a `vale_bin` folder beside `vale-ls`. When false, `vale` must be on your `$PATH`.                                                       |
| `syncOnStartup`  | `boolean` | `false` | Run [`vale sync`](/topics/cli) when the server starts.                                                                                                               |
| `filter`         | `string`  | `""`    | An [output filter](/topics/cli) to apply, e.g. `.Level in ['warning', 'error']`.                                                                                     |
| `configPath`     | `string`  | `""`    | An absolute path to a `.vale.ini`. Usually best left empty so Vale's own [search process](/topics/.vale.ini) applies.                                                |
| `noGlobal`       | `boolean` | `false` | Leave out your user-level `.vale.ini`, which Vale otherwise merges into every project's configuration, `configPath` or not. Passes `--no-global` to every Vale call. |
| `valeBinaryPath` | `string`  | `""`    | An absolute path to the `vale` binary to use. Set this when you need your own installation rather than a managed or `$PATH` copy.                                    |
| `lintOnChange`   | `boolean` | `true`  | Report diagnostics as you type. When false, they're only updated when you save.                                                                                      |
| `debounceMs`     | `number`  | `300`   | How long typing has to settle before `lintOnChange` runs Vale.                                                                                                       |
| `showMetrics`    | `boolean` | `true`  | Show a [code lens](#code-lenses) with the document's word and sentence counts.                                                                                       |

{% hint style="info" %}
`installVale` and `syncOnStartup` are off unless your client asks for them. Editor plugins generally do—[LSP-vale-ls](https://github.com/vale-cli/LSP-vale-ls) turns both on by default—but if you're wiring the server up yourself, set them explicitly.
{% endhint %}

A configured `valeBinaryPath` is never substituted: if nothing is there, the server reports it rather than falling back to another copy of Vale. You can also pass it on the command line as `--vale-binary`, which the setting overrides.

Clients that push settings after connecting—`workspace/didChangeConfiguration`—can change any of these without a restart. The server accepts the settings object either as-is or scoped under a `vale` key.

In a workspace with more than one folder, each document is linted against the innermost folder containing it, so projects with different `.vale.ini` files can be open at the same time. Setting `configPath` overrides that for every document.

## [Quick fixes](#quick-fixes)

Where Vale can suggest a correction, the server offers it as a code action: a replacement for a [substitution](/topics/styles) rule, or a deletion for a repeated word.

Spelling alerts also offer to add the flagged word to a vocabulary, which writes it to that vocabulary's `accept.txt` and re-lints the file. A project with several active [vocabularies](/keys/vocabularies) gets one action per vocabulary, so you choose where the word lands.

## [Code lenses](#code-lenses)

Prose files carry a lens with the document's word and sentence counts. Selecting it reports the rest of Vale's metrics—characters, paragraphs, syllables, and the counts the readability formulas are built on. Turn it off with `showMetrics`.

## [Commands](#commands)

The server registers these for `workspace/executeCommand`:

| Command        | Arguments              | Description                                 |
| -------------- | ---------------------- | ------------------------------------------- |
| `cli.sync`     | none                   | Run [`vale sync`](/topics/cli).             |
| `cli.install`  | none                   | Install or update the managed copy of Vale. |
| `cli.compile`  | `[uri]`                | Compile a rule and open it on Regex101.     |
| `vocab.add`    | `[{uri, vocab, term}]` | Add a term to a vocabulary's `accept.txt`.  |
| `vocab.reject` | `[{uri, vocab, term}]` | Add a term to a vocabulary's `reject.txt`.  |
| `doc.metrics`  | `[{uri}]`              | Report a document's metrics.                |

## [Editors](#editors)

These connect to `vale-ls`:

* [Sublime Text](https://packagecontrol.io/packages/LSP-vale-ls)
* [VS Code](https://github.com/chrischinchilla/vale-vscode)
* [Neovim](https://github.com/neovim/nvim-lspconfig) (as `vale_ls`)
* [Zed](https://github.com/koozz/zed-vale)
* [Emacs](https://github.com/tpeacock19/flymake-vale)

Other editors integrate with the Vale CLI directly rather than through the server: [ALE](https://github.com/dense-analysis/ale), [JetBrains](https://plugins.jetbrains.com/plugin/19613-vale-cli/docs), [Obsidian](https://github.com/ChrisChinchilla/obsidian-vale), [Oxygen XML](https://www.oxygenxml.com/doc/versions/23.1/ug-editor/topics/vale-linter-addon.html), and [Qt Creator](https://wiki.qt.io/Setting_Up_Vale).

## [Running it yourself](#running-it-yourself)

Download a build from [releases](https://github.com/vale-cli/vale-ls/releases) and point your client at the binary. [LSP-vale-ls](https://github.com/vale-cli/LSP-vale-ls) is a small, readable example of a client configuration.

The binary takes no arguments beyond `--vale-binary`, `--version`, and `--help`; everything else comes from your client.


# Agents

Use Vale with an AI assistant: on what it writes, on what it reads, and as a tool it runs.

An agent meets Vale in three places. The prose it writes can be checked against a voice, so a style is enforced on every draft instead of remembered. The instructions it reads, `AGENTS.md`, `CLAUDE.md`, a skill, are prose files in a repository, and can be linted like any other. And Vale itself is a tool an agent can set up and run, with the same four steps as the [Quickstart](/topics/quickstart).

## [What an agent writes](#what-an-agent-writes)

A writing prompt has three parts: a persona, a set of constraints, and guardrails about where the constraints do not apply. The persona is the one thing a model does reliably. The constraints, no hedging, one slang term a sentence, sentences under 25 words, are a linter written as prose, and the guardrails, "none of this applies inside code or paths," are what Vale does by parsing the markup.

[Voices](https://github.com/jdkato/voices) is that conversion done: writing voices from the [output-style catalog](https://github.com/smixs/awesome-claude-output-styles), written as Vale rules. A rule costs nothing until a draft breaks it, where a prompt is paid for on every request, and an exit code is a fact where "I followed the style" is a claim. The [announcement](https://vale.sh/blog/voices) runs one draft through each voice and measures both.

```ini
Packages = https://github.com/jdkato/voices/releases/latest/download/Voices.zip

[*.md]
BasedOnStyles = Voices, Direct
```

`Voices` is the shared core, inflated words, throat-clearing, weasel attribution, recap endings, and is always on. `Direct`, `GenZ`, `Coach`, `Simple`, and `Claude` each add one voice beside it. `Claude` is derived from the formatting section of the published system prompts for Claude and is the demonstration of the argument: that section of a production prompt is already a linter. Voices requires Vale v3.20.0 or later and builds on [Std](https://github.com/vale-cli/Std).

With the edit-time hook below, the alerts reach the assistant in the turn it wrote the draft, and a rule that carries a fix is applied without a decision.

## [What an agent reads](#what-an-agent-reads)

An instruction file holds a model to a standard, and the same standard can be held to the file: say what to do, in the mood of a command, in a file short enough to be read on every turn. [Prompts](https://github.com/jdkato/Prompts) is a style for `AGENTS.md`, `CLAUDE.md`, `SKILL.md`, and system prompts, on any agent's file:

```ini
Packages = https://github.com/jdkato/Prompts/releases/latest/download/Prompts.zip

[{AGENTS,CLAUDE,SKILL}.md]
BasedOnStyles = Prompts
```

Its rules report advice where an instruction belongs (`you should`, `make sure to`), words that name no action (`handle`, `ensure`, `best practices`), more than ten absolutes in one file, two instructions that pull opposite ways, a skill `description` past the length its loader reads, a section past 300 words, and a file past 2,500. Each rule links to the vendor guidance it enforces. Name the files you mean in the section: a style for instructions has no business in the README. Prompts requires Vale v3.21.0 or later.

## [What an agent runs](#what-an-agent-runs)

An assistant can install Vale, write a configuration, and fix what it reports. What it needs beyond the Quickstart is the handful of facts that are easy to get wrong without reading further: only `error` sets a non-zero exit code, `--output=JSON` is the format to parse, and a term that trips spell check belongs in a vocabulary rather than in a switched-off rule.

### [AGENTS.md](#agentsmd)

[AGENTS.md](https://vale.sh/AGENTS.md) says all of that in the form assistants read. Save it in the root of your repository, where most of them find it on their own, and give it to one instead of the Quickstart.

### [Skills](#skills)

For task-shaped work there are [skills](https://vale.sh/skills) to copy in beside it, each a set of instructions an assistant follows when the task comes up:

| Skill    | What it does                                                                                                            |
| -------- | ----------------------------------------------------------------------------------------------------------------------- |
| `setup`  | Install Vale, write a `.vale.ini`, sync the styles, and get a first run in a repository that has none.                  |
| `fix`    | Run Vale, fix the error-level alerts, and open a pull request per file. Warnings and suggestions are left for a person. |
| `triage` | Turn a large first run into a decision per rule: fix, downgrade, or switch off.                                         |
| `vocab`  | Add a project's terms to a vocabulary so spell check accepts them.                                                      |
| `ci`     | Run Vale in GitHub Actions, a pre-commit hook, or another runner, failing the build on errors.                          |

The skills run the CLI you installed and need no account.

### [Claude Code](#claude-code)

In Claude Code, the skills, an edit-time hook, and the Vale CMS MCP server install together as one plugin:

```
/plugin marketplace add vale-cli/agent-tools
/plugin install vale@agent-tools
```

The hook lints each prose file as the assistant writes it and hands back the alerts, so a mistake is fixed in the same turn it was made. By default it hands back errors only, since a project's warnings and suggestions are advisory by its own choice; the plugin's `level` setting widens that to `warning` or `suggestion` for a style whose rules are deliberately advisory, which is what a voice is.

The MCP server is the one paid piece. It belongs to [Vale CMS](https://vale.sh/cms) and gives an assistant the engine itself to check a rule against, rather than the docs about it. See [MCP](/guides/mcp).


# Commits

Lint the log with the same tool that lints the docs: a convention for the subject, body, and trailers, spelling on the body, and a hook that stops a bad commit.

A commit message has a subject, a blank line, a body, and trailers, and every project has rules about each. [Commits](https://github.com/jdkato/commits) is a package of those rules: the conventions people follow, [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/), Angular's format, gitmoji, Emoji-Log, the seven rules, the Linux kernel's patch format, the Go project's, a Jira key, and the rule sets of the linters people run, commitlint, gitlint, committed, conform, and commitizen, each transcribed rule for rule. A [`textfsm` View](/guides/textfsm) reads the message into its parts, so a rule reaches the subject the way it reaches a heading.

{% hint style="info" %}
Requires Vale v3.21.0 or later.
{% endhint %}

## Install

The package is in the [library](https://vale.sh/explorer), so its name is enough:

```ini
StylesPath = styles
Packages = Commits

[COMMIT_EDITMSG]
BasedOnStyles = Commits, Conventional
```

```console
$ vale sync
```

The package brings the section, the View, and `Commits`, the core every convention shares: a blank line after the subject, the imperative mood, no whitespace at the edges, no `fix` or `wip` for a subject. You name the convention beside it.

| Style          | Enforces                                                                                       |
| -------------- | ---------------------------------------------------------------------------------------------- |
| `Conventional` | The [specification](https://www.conventionalcommits.org/en/v1.0.0/#specification), and only it |
| `Commitlint`   | commitlint's rules, with `config-conventional`'s on; goes with `Conventional`                  |
| `Angular`      | Angular's guidelines; goes with `Conventional`                                                 |
| `Gitmoji`      | One of the 75 gitmojis first, then a message                                                   |
| `EmojiLog`     | One of Emoji-Log's seven labels                                                                |
| `SevenRules`   | Chris Beams's seven rules: 50 characters, a capital, no period, the body wrapped at 72         |
| `Kernel`       | Subsystem prefix, 75 columns, `Signed-off-by:`, the `Fixes:` and `Closes:` tag forms           |
| `Go`           | Package prefix, no capital, `Fixes #123`                                                       |
| `Jira`         | An issue key in the subject                                                                    |
| `Gitlint`      | gitlint's built-in rules at their defaults                                                     |
| `Committed`    | committed's defaults                                                                           |
| `Conform`      | conform's commit policy                                                                        |
| `Commitizen`   | `cz check`'s schema; goes with `Conventional`                                                  |

Levels and toggles work as they do for any rule, and a limit is a parameter set with the bracket key:

```ini
[COMMIT_EDITMSG]
BasedOnStyles = Commits, Conventional, Commitlint
Commitlint.HeaderLength[max] = 72
Commits.Imperative = error
Conventional.Footer = NO
```

A rule a tool leaves off by default, commitlint's `scope-enum` or `references-empty`, is off here too; `Commitlint.References = YES` turns it on. Each rule file in the package's [styles](https://github.com/jdkato/commits/tree/main/Commits/styles) opens with what it reports and why, carries its level, and links the passage it enforces. A guide per tool, [commitlint](https://github.com/jdkato/commits/blob/main/docs/commitlint.md), [gitlint](https://github.com/jdkato/commits/blob/main/docs/gitlint.md), [committed](https://github.com/jdkato/commits/blob/main/docs/committed.md), [conform](https://github.com/jdkato/commits/blob/main/docs/conform.md), and [commitizen](https://github.com/jdkato/commits/blob/main/docs/commitizen.md), maps that tool's config to the rules here.

## The hook

A `commit-msg` hook receives the path of the message file. Vale reads it on stdin, and `--path` names the section that applies, since the text has no name of its own:

```sh
#!/bin/sh
# .git/hooks/commit-msg
exec vale --path=COMMIT_EDITMSG < "$1"
```

Vale exits non-zero on an error-level alert, so the commit stops with the alerts on screen. Lines Git adds as commentary, and the diff `commit -v` appends, are never seen by a rule. The package's [`script/commit-msg`](https://github.com/jdkato/commits/blob/main/script/commit-msg) is that hook with two additions: messages Git writes itself, merges, reverts, and the `fixup!` and `squash!` that autosquash reads, are let through, and on a fresh clone it runs `vale sync` once, when the `StylesPath` has no `Commits` in it.

Git keeps `.git/hooks` outside version control, so a hook copied there protects one clone. To ship it with the repository, put it in a tracked directory and point `core.hooksPath` at it, once per clone:

```console
$ git config core.hooksPath script
```

A `prepare` script in `package.json` or a `make setup` target runs that for every contributor. The hook runners do the same thing under the hood, with husky, lefthook, or the pre-commit framework:

```yaml
# lefthook.yml
commit-msg:
  commands:
    vale:
      run: vale --path=COMMIT_EDITMSG < {1}
```

```yaml
# .pre-commit-config.yaml
- repo: local
  hooks:
    - id: vale-commit-msg
      name: vale
      entry: sh -c 'vale --path=COMMIT_EDITMSG < "$1"' --
      language: system
      stages: [commit-msg]
```

## CI and pull requests

Because the message comes in on stdin, the same command works on what was pushed, on each commit of a pull request, and on the request's description:

```sh
git log -1 --format=%B | vale --path=COMMIT_EDITMSG

for sha in $(git rev-list "$BASE..HEAD"); do
  git log -1 --format=%B "$sha" | vale --path=COMMIT_EDITMSG || status=1
done

gh pr view --json body -q .body | vale --path=COMMIT_EDITMSG
```

Nothing on the server side can run a hook for a client, so the check in CI is the backstop for a clone that skipped the setup.

## Output

The report can take the shape of the tool a team used before. The package ships [templates](/topics/templates), picked with `--output` by name once synced:

```console
$ vale --output=commitlint.tmpl --path=COMMIT_EDITMSG < .git/COMMIT_EDITMSG
⧗   input: COMMIT_EDITMSG
✖   Start with a type, then a colon: 'fix: ...', 'feat(scope)!: ...'. 'Fixed' is not one. [Conventional.Type]
✖   A subject does not end with a period. [Commitlint.FullStop]
⚠   Write the subject as a command: 'Add', not 'Fixed'. [Commits.Imperative]

✖   found 2 problems, 1 warnings
ⓘ   Get help: https://www.conventionalcommits.org/en/v1.0.0/#specification
```

`gitlint.tmpl`, `committed.tmpl`, and `conform.tmpl` print those tools' forms, `github.tmpl` prints GitHub Actions annotations so an alert lands on the pull request at its line, and `hook.tmpl` is a short form for the terminal.

## Spelling and the rest of Vale

The body is read as Markdown, so `Vale.Spelling` skips an identifier in backticks and catches a misspelling in prose, and any style already in use on the docs runs on the log with one line of config:

```ini
[COMMIT_EDITMSG]
BasedOnStyles = Vale, Commits, Conventional, House
```

## Your own rules

A rule reaches a part of the message by its scope, `subject`, `gap`, `body`, or `trailer`, and a rule that spans two parts is a [`conditional`](/checks/conditional) whose `in` names the second:

```yaml
extends: conditional
message: "A '!' in the subject needs a 'BREAKING CHANGE:' trailer."
level: error
scope: subject
first: '^\w+(?:\([^)]*\))?!:'
second: '^BREAKING CHANGE: '
in: trailer
```

To change a list a rule holds, the types a project accepts, extend the rule in a style of your own with the pattern rewritten, and turn the original off. The [TextFSM guide](/guides/textfsm) is the View itself: how the template reads a message, and how the same approach reads a subtitle file or a transcript.


# MCP

Give an AI assistant the Vale engine, so it can check its own work.

Models write plausible Vale YAML. Whether it compiles, whether the regex matches anything, whether the config still loads — a model has no way to find out, so you find out later, in CI.

The Vale MCP server closes that gap. It exposes the engine as [Model Context Protocol](https://modelcontextprotocol.io) tools, so an assistant can scaffold a rule, compile it, run it over sample text, and see the alerts it produced — before writing a file.

{% hint style="info" %}
The hosted MCP server is part of [Vale CMS](https://vale.sh/cms), which is a paid product. Vale itself — the CLI, the engine, and the styles — stays free, open source, and MIT licensed.
{% endhint %}

## Connecting

The server speaks JSON-RPC 2.0 over HTTP. Point your client at it with a token from your Vale CMS account:

```json
{
  "mcpServers": {
    "vale-cms": {
      "type": "http",
      "url": "https://api.vale.sh/mcp"
    }
  }
}
```

Clients that support remote MCP servers — Claude Code, Claude Desktop, Cursor, VS Code, and others — need nothing installed locally.

## What the tools do

The tools fall into four groups.

### Author

Start from something that already compiles, rather than a blank file and a guess at the schema.

| Tool                  | Purpose                                                                        |
| --------------------- | ------------------------------------------------------------------------------ |
| `scaffold_rule`       | A valid starter rule for any of the twelve check types                         |
| `scaffold_vocab`      | A vocabulary, with its `accept.txt` and `reject.txt`                           |
| `scaffold_dictionary` | A Hunspell `.dic`/`.aff` pair                                                  |
| `scaffold_filter`     | A filter expression over your rules                                            |
| `scaffold_view`       | A view that extracts part of a document                                        |
| `scaffold_template`   | An output template                                                             |
| `assemble_style`      | A whole style package, with each rule checked against the guide's own examples |
| `fetch_guide`         | A published style guide, as text you can derive rules from                     |

### Verify

The half that generation can't do for itself.

| Tool            | Purpose                                                                               |
| --------------- | ------------------------------------------------------------------------------------- |
| `diagnose_rule` | Compile a rule and report the error, with its position                                |
| `test_rule`     | Run a rule over inputs and check each should or shouldn't match                       |
| `stress_rule`   | Generate near-miss inputs from the rule's own tokens and report what it wrongly flags |
| `check_config`  | Load a whole proposed project through the engine                                      |
| `lint_text`     | The alerts a config produces on sample prose                                          |
| `audit_style`   | Correctness and performance defects no compile step catches                           |
| `check_links`   | Resolve every rule's `link:` field, which Vale itself never validates                 |

### Understand

Answers about the engine, from the engine.

| Tool                | Purpose                                                          |
| ------------------- | ---------------------------------------------------------------- |
| `explain_check`     | What a check type detects, its fields, and an example            |
| `list_check_types`  | All twelve extension points                                      |
| `tag_text`          | Part-of-speech tags, as the `sequence` check sees them           |
| `show_blocks`       | How a format splits text into the blocks rules run over          |
| `trace_rule`        | What each slot of a sequence wanted, beside what the tagger said |
| `resolve_config`    | The config the engine actually resolved                          |
| `project_layout`    | Where every asset belongs, and how to enable it                  |
| `expand_dictionary` | The exact word forms a Hunspell entry accepts                    |

### Change safely

Editing a rule already in use is the risky edit.

| Tool              | Purpose                                                         |
| ----------------- | --------------------------------------------------------------- |
| `diff_rule`       | Which alerts an edit adds and removes, over a corpus you supply |
| `diff_style`      | The same, for a whole package                                   |
| `render_template` | What a template produces against real alerts                    |
| `put_files`       | Upload a project once and reference it by hash                  |

## Three questions it answers

These are the questions that cost hours by hand and one call here.

### A rule looks right and never fires

Sequence rules match on part-of-speech tags, and the tagger's reading of a word is invisible in the YAML. `trace_rule` shows what each slot wanted beside what the tagger actually produced — a slot expecting a proper noun sitting on a word tagged `JJ` is a rule that can never match.

The useful part is what it rules out: the rule may be correct and the tagger may simply disagree with you, which calls for a documented exception rather than a rewrite.

### A rule behaves differently in Markdown and plain text

`show_blocks` shows how each format splits a document into the blocks rules run over. A `.txt` file is one block, so its whole `text` scope is a single unit; the same content in Markdown is one block per paragraph.

That is how a pattern containing `\s+` quietly matches across a blank line and joins two paragraphs. The cause is the format, not the pattern.

### Linting got slower and no rule changed

`audit_style` prices a style before any text is read — what it costs to compile, which is paid on every run whether or not a rule ever fires. On a short document that is most of the wall clock.

A negated character class under `ignorecase` is the classic example: the regex engine computes the case orbit of every member, at roughly 3.6× the compile cost of the `\s` it replaced.

## See also

* [Vale CMS](https://vale.sh/cms) — the hosted editor the MCP server is part of
* [Styles](/topics/styles) — what a rule is, and the twelve check types
* [Scopes](/topics/scopes) — the scoping system `show_blocks` reports on


# Regex

Learn how to use regex in Vale.

Vale runs its rules on [`regexp2`](https://github.com/jdkato/regexp2), in its RE2 compatibility mode: the syntax of Go's standard [regexp](https://pkg.go.dev/regexp/syntax) package, plus the lookahead and lookbehind assertions that package leaves out.

This guide provides an overview of regex syntax supported by Vale, along with tips for writing regular expressions in [YAML](https://yaml.org/) files.

## [Syntax](#syntax)

For the base syntax, see the [Go docs](https://pkg.go.dev/regexp/syntax). For what `regexp2` adds, see its [README](https://github.com/dlclark/regexp2?tab=readme-ov-file#compare-regexp-and-regexp2).

The most commonly used assertion constructs are:

* Positive lookahead: `(?=re)`
* Negative lookahead: `(?!re)`
* Positive lookbehind: `(?<=re)`
* Negative lookbehind: `(?<!re)`

This extended syntax is supported everywhere in Vale, except for `script`-based rules (which are limited to the standard Go regex syntax).

## [YAML](#yaml)

Wrap all regex in single (`'`) or double (`"`) quotes to avoid YAML interpreting special characters:

* Single quotes (`'`): Prevent YAML from interpreting any characters except single quotes themselves.
* Double quotes (`"`): Allow YAML to interpret escape sequences like `\n` and `\t`, so you’ll need to escape backslashes.

In general, this means that you should **prefer single quotes** for most cases:

```yaml
extends: existence
message: Consider removing '%s'
level: warning
# A typical rule with single quotes:
tokens:
  - '([A-Z]\w+)([A-Z]\w+)'
```

If you need to *use* a single quote in your regex, you can escape it with another single quote:

```yaml
extends: existence
message: Consider removing '%s'
level: warning
# A rule with a single quote in the regex:
tokens:
  - '([A-Z]\w+)([A-Z]\w+)''s'
```

## [Common Issues](#common-issues)

<details>

<summary>Word Boundaries</summary>

In regex, `\b` is a word boundary assertion that matches the position between a word character and a non-word character.

For example, the regex `\bfoo\b` will only match the word “foo” and not “foobar” or “foo-bar”.

By default, [`existence`](/checks/existence) and [`substitution`](/checks/substitution) rules in Vale will automatically add word boundaries to the beginning and end of each token.

To disable this behavior, set `nonword` to `true`:

```yaml
extends: existence
message: Consider removing '%s'
nonword: true
tokens:
  - some token
```

`raw` is the other way out: it takes your pattern verbatim, boundaries and all.

Word boundaries need watching in non-Latin scripts. `\b` sits between a word character (`[0-9A-Za-z_]`) and anything else, so in a script with no word characters it never matches—and the rule finds nothing at all, with no error to say why. See [WordTemplate](/keys/wordtemplate) if that's your content.

</details>

<details>

<summary>Scoping</summary>

For markup-based rules, Vale converts each document to HTML and applies a [scoping](/topics/scopes) system before running any rules.

This means that if you’re writing a rule that targets markup syntax or needs to match across block boundaries, the results may be different from what you expect.

If you like to apply a rule to the entire, unprocessed document, you can use `scope: raw`:

```yaml
extends: existence
message: Consider removing '%s'
scope: raw
tokens:
  - some token
```

</details>


# TextFSM

Learn how to lint plain text that has structure but no markup.

A commit message is a subject, a blank line, a body, and trailers. A subtitle file is a run of cues, each an index, a timing, and a line or two of text. A transcript is a series of turns, each opened by a name. None of that is markup, so Vale reads such a file as lines, and a rule has no way to say “the subject” or “the model’s turn.”

A `textfsm` [View](/topics/views) fixes that. It reads the file through a template in the form Google’s [TextFSM](https://github.com/google/textfsm/wiki/TextFSM) defined for parsing the output of network devices: a list of named values, then a state machine whose rules are regular expressions. What the template captures becomes [scopes](/topics/scopes), each placed at the line and column it came from, so a rule reaches the subject the way it reaches a heading and its alert lands where the text is.

Vale runs the template itself. Nothing needs installing, and the patterns use the same [regex](/guides/regex) dialect as every rule.

{% hint style="info" %}
Requires Vale v3.21.0 or later.
{% endhint %}

## A first template

Take the commit message. Three parts of it deserve rules of their own: the subject, the body, and the trailers at the end.

```
fix: report the shortfall at the scope that fell short.

Zero matches leave no occurence to point at, but the scope has a
position of its own.

Signed-off-by: Joseph Kato <j@example.com>
```

The View lives at `<StylesPath>/config/views/Commit.yml`:

```yaml
engine: textfsm
template: |
  Value Subject (.+)
  Value List Body (.*)
  Value List Trailer ((?:BREAKING CHANGE|[A-Z][\w-]+): .+)

  Start
    ^${Subject} -> Body

  Body
    ^# -> Next
    ^${Trailer}
    ^${Body}
scopes:
  - name: subject
    expr: Subject

  - name: body
    expr: Body
    type: md

  - name: trailer
    expr: Trailer
```

Read the template from the top:

* Three `Value` lines declare what to capture: a name and the pattern that fills it. `Subject` keeps one line. `Body` and `Trailer` are `List` values, which keep every line they capture rather than the last.
* `Start` is the state reading begins in. Its one rule matches the first line, captures it as the subject, and moves to the `Body` state.
* In `Body`, each line is tried against the rules in order, and the first to match wins. A line starting with `#` is Git’s own commentary, and `-> Next` reads past it without capturing anything. A trailer looks like `Word: text`, or is the `BREAKING CHANGE:` line a conventional commit ends with, so it’s tried next; anything else is body.

`${Subject}` stands for the value’s pattern and captures what it matches. The `->` says what happens on a match; a rule without one reads the next line in the same state.

The `scopes` then name the values to lint. `subject` and `trailer` are linted as plain text. `body` has `type: md`, so it’s parsed as Markdown and a rule scoped to `body` sees paragraphs and inline code the way it does in a `.md` file.

Wire the View to the file in `.vale.ini`. Sections match by path, so a file without an extension is fine:

```ini
[COMMIT_EDITMSG]
BasedOnStyles = Vale, House

View = Commit
```

A rule reaches a scope by name:

```yaml
extends: existence
message: "A subject line doesn't end with '%s'."
level: error
scope: subject
raw:
  - '\.$'
```

And the alerts land in the file, not in the value:

```
 COMMIT_EDITMSG
 1:55  error  A subject line doesn't end with '.'.  House.Subject
 3:23  error  Did you really mean 'occurence'?      Vale.Spelling
```

In a `commit-msg` hook, the message arrives on stdin. `--path` tells Vale which section applies:

```sh
#!/bin/sh
vale --path=COMMIT_EDITMSG < "$1"
```

For commit messages, this is done: the [Commits](/guides/commit-messages) package ships the View, the conventions, and the hook.

## How a template is read

![The template as a state machine: Start moves to Body on a line matching Subject, capturing it; Body stays in Body on a Trailer or Body line, capturing one each; the record is emitted at the end of the file and each value becomes a scope.](https://2533404145-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEMH7s0sC5N9L8xSMXGwW%2Fuploads%2Fgit-blob-9db2d81fe7d58ee065cb50b0591749e30ea7cfa0%2Ftextfsm-states.svg?alt=media)

A template has two parts. The values come first, one per line, up to the first blank line. Each is `Value [options] Name (pattern)`, and a line starting with `#` is a comment. The states follow: a state is a name at the left margin, and its rules are the indented lines beneath it. Every template needs a `Start` state; `End` and `EOF` are reserved.

In the View’s YAML, the template is a block scalar (`|`), so backslashes in patterns need no escaping, and the indent under a state name is what marks a line as a rule.

Reading is one line at a time. The current state’s rules are tried in order, and the first to match decides what happens next. A line no rule matches is skipped: it captures nothing, the state stays the same, and the line lands in no scope.

A rule is `^pattern -> action`, where the action is any of the following, and `Line.Record` pairs are joined with a dot:

| Action            | Effect                                                                                   |
| ----------------- | ---------------------------------------------------------------------------------------- |
| `Next`            | Read the next line. The default.                                                         |
| `Continue`        | Keep trying the rules below against the same line. A `Continue` rule can’t change state. |
| `Record`          | Emit the captures in hand as a record and start a fresh one.                             |
| `NoRecord`        | Emit nothing. The default.                                                               |
| `Clear`           | Drop the captures in hand, except `Filldown` values.                                     |
| `Clearall`        | Drop every capture, `Filldown` values included.                                          |
| `Error "message"` | Stop reading. Vale reports the message as an error against the View and exits.           |
| A state name      | Enter that state. `End` stops reading.                                                   |

On a match, the line’s captures are assigned first, then the record action runs, then the state changes. So a rule that both captures and says `Record` puts that line’s captures into the record it emits, and `Continue.Record` emits before the rules below capture into the fresh one.

The options on a `Value` line change what it keeps:

| Option     | Effect                                                                     |
| ---------- | -------------------------------------------------------------------------- |
| `List`     | Every capture, in order, rather than the last one.                         |
| `Filldown` | The last capture carries into the next record until a new one replaces it. |
| `Required` | A record missing this value is dropped rather than emitted.                |

`Key` and `Fillup` are accepted for compatibility with TextFSM and change nothing.

A record is emitted for each `Record` action, and the captures in hand at the end of the file are emitted as one more, `End` included. So a template that never says `Record` yields one record per file. An `EOF` state replaces that last record: `EOF` with a `^.* -> Record` rule keeps it, and `EOF` with no rules discards it.

In a pattern, `${Name}` or `$Name` stands for the value’s pattern and captures what it matches. Everything outside it is matched but not captured, which is how `^user: ${User}` keeps the label out of the scope. Start each rule with `^`, as TextFSM does; a rule without it matches anywhere in the line.

## From captures to scopes

Each scope’s `expr` names one of the template’s values, and Vale reports an error at startup if it doesn’t. The scope’s `name` is what a rule’s `scope` refers to, and `type` says how to parse the captured text: `md`, `adoc`, `html`, `rst`, or `org`. Without a `type`, the text is linted as plain lines.

Consecutive lines a `List` value captures are joined into one block, so a body reads as the paragraphs it is rather than one block per line. A gap between the lines starts a new block, and so does a change of column: a block is placed by one line and one column, so every line in it has to start where its first line does. Two values captured after labels of different lengths, such as a `msgid` and a `msgstr`, never share a column and so never join, and a rule that needs both in one block matches nothing without saying why. Capture each whole line, label included, and they align at the first column.

The blank line matters here. `(.*)` matches a blank line and captures it as empty, so the block continues and the paragraph break survives. `(.+)` doesn’t match a blank line, so the block ends and the next captured line starts another. For a body with `type: md`, `(.*)` gives one document with paragraphs, and `(.+)` gives one document per paragraph. Rules that count across a document, such as `occurrence` and `repetition`, see the difference.

A value the template never fills is still a scope: one empty value at the top of the file. A rule that requires text, an `occurrence` with `min`, reports there that the value is missing, and every other rule sees no text and says nothing. That is how a commit without a trailer or a body is caught.

A fact about one line is a value for that line. Whether the body is separated from the subject by a blank line is a fact about line two, so a `Gap` value captures it, `^${Subject} -> Gap` then `^${Gap} -> Body`, and an `existence` rule on `\A\S` scoped to `gap` reports a body that starts too soon, on the line where it does. A fact that spans two values, a `!` in the subject needing a `BREAKING CHANGE:` footer, is a [`conditional`](/checks/conditional) whose `in` names the scope its consequent lives in.

A `textfsm` View takes the file over. A file the section matches is read by the template, whatever its extension, even when it’s `.md`. Only the values a scope names are linted; the rest of the file is never seen by a rule, unless the rule’s scope is `raw`, which still reads the whole file.

## One record per unit

A subtitle file is a run of cues, each an index, a timing line, and one or more lines of text, separated by blank lines. Only the text is prose:

```
1
00:00:01,000 --> 00:00:03,000
First cue, one line.

2
00:00:04,000 --> 00:00:06,000
Second cue,
which wraps.
```

The template walks a cue in three states, and the blank line that ends one emits a record and returns to `Start` for the next:

```yaml
engine: textfsm
template: |
  Value Index (\d+)
  Value Timing (\d\d:\d\d:\d\d,\d\d\d --> \d\d:\d\d:\d\d,\d\d\d.*)
  Value List Cue (.+)

  Start
    ^${Index}$ -> Timing

  Timing
    ^${Timing}$ -> Cue

  Cue
    ^$ -> Record Start
    ^${Cue}
scopes:
  - name: cue
    expr: Cue
```

`Index` and `Timing` are captured so that the template can tell where it is, but no scope names them, so no rule ever sees a timestamp. A rule scoped to `cue` runs over the text of each cue, and a two-line cue is one block at the line and column its first line starts on.

## One side of a conversation

A transcript alternates between a user and a model, and only one side is yours to lint:

```
user: Summarize the change in one line.
assistant: The linter now reports where a scope fell short.
It's worth noting that this only affects occurence rules.
user: Thanks, that's clear enuf.
```

A turn runs until the next label, so each state needs to know when the turn is over before it knows whose turn comes next:

```yaml
engine: textfsm
template: |
  Value List Assistant (.*)
  Value List User (.*)

  Start
    ^assistant: ${Assistant} -> Assistant
    ^user: ${User} -> User

  Assistant
    ^(?:user|assistant): -> Continue.Record
    ^assistant: ${Assistant}
    ^user: ${User} -> User
    ^${Assistant}

  User
    ^(?:user|assistant): -> Continue.Record
    ^user: ${User}
    ^assistant: ${Assistant} -> Assistant
    ^${User}
scopes:
  - name: assistant
    expr: Assistant
    type: md
```

The first rule in each state is the trick. It matches any label, captures nothing, emits the turn in hand as a record, and continues, so the rules below it capture the new turn into a fresh record. A line with no label is a continuation of whichever turn is open.

`User` is captured so the prompt’s lines have somewhere to go, but no scope names it, so the misspelling on the last line goes unreported:

```
 transcript.txt
 3:6   error  'worth noting' hedges, and this is the model's turn.  House.Assistant
 3:42  error  Did you really mean 'occurence'?                      Vale.Spelling
```

The column is the column of the capture, so an alert on the first line of a turn points past the label.

## Seeing what a template captured

A template can be right and still surprise you, because a line no rule matches vanishes without a word. The quickest way to see what a scope holds is a rule that reports every line of it:

```yaml
extends: existence
message: "captured '%s'"
level: suggestion
scope: cue
nonword: true
raw:
  - '(?m)^.+$'
```

Run with `--output=line` and each captured line is listed at its position:

```
sample.srt:3:1:Probe.Cue:captured 'First cue, one line.'
sample.srt:7:1:Probe.Cue:captured 'Second cue,'
sample.srt:8:1:Probe.Cue:captured 'which wraps.'
```

The `(?m)` matters: a block of joined lines is one value, and without the multiline flag `^` and `$` only match at its ends.

Delete the rule once the template does what you expect.


# Hunspell

Learn how to create and use Hunspell-compatible dictionaries in Vale.

[Hunspell](https://hunspell.github.io/) is a spell-checking engine known for its flexibility and support for complex morphological rules. It powers spell-checking in popular applications like LibreOffice, Mozilla Firefox, and Google Chrome.

Vale uses Hunspell-compatible dictionaries to power its [own spell-checking](/checks/spelling) features. This guide will discuss the basics of creating and using these dictionaries.

You can find more thorough documentation at the [official repository](https://github.com/hunspell/hunspell?tab=readme-ov-file#documentation). There’s also a well-documented Python port of the library called [spylls](https://github.com/zverok/spylls).

## How does spell-checking in Vale work?

Vale doesn’t use Hunspell directly and doesn’t require it to be installed on your system.

Instead, Vale uses a pure-Go package to parse Hunspell-compatible dictionaries and check the spelling of words. It is tested against Hunspell’s own test suite and passes nearly all of it: affixes, the compound rules and their checks, Hunspell’s case model, `FORBIDDENWORD`, `NEEDAFFIX`, `KEEPCASE`, `CIRCUMFIX`, `AF`, `SET`, `ICONV`, `IGNORE`, `CHECKSHARPS`, `BREAK`, and `REP`. Suggestions follow Hunspell’s order but do not yet read `MAP`, `KEY`, or `PHONE`. A directive Vale does not implement is read and ignored.

A Hunspell-compatible dictionary consists of two files:

1. Affix (`.aff`) file: This file defines the morphological rules, including prefixes, suffixes, and other language-specific grammar rules that govern how words are formed.
2. Dictionary (`.dic`) file: This file contains the list of root words and their associated affix codes to specify valid transformations.

You can name these files whatever you like, so long as the `.aff` and `.dic` files are named consistently – for example, `en_US.aff` and `en_US.dic`.

Here’s a minimal example of a dictionary:

```
1
software/M
```

“1” is the number of words in the dictionary and `software/M` is the root word “software” with the affix code `M`. This means that we accept the word “software” and the variations derived from the affix code `M`.

Our affix file would look like this:

```
SET UTF-8

SFX M Y 1
SFX M   0     's         .
```

* `SFX M Y 1`: This line defines a suffix rule (`SFX`) for the affix code `M`. The `Y` indicates that the rule is [cross-productible](https://github.com/hunspell/hunspell/blob/874abbbe65e228df525023afe176b42df34a7a4f/man/hunspell.5#L527) and the `1` indicates that there is one rule.
* `SFX M 0 's .`: This line defines the rule itself. It says that if a word has the affix code `M`, we can add `'s` to the end of the word. The `0` indicates that no part of the base word is removed when applying this suffix. The `.` indicates that there are no conditions for applying this rule.

The end result is that the dictionary will accept both “software” and “software’s”. Other variations like “softwares” or “softwaring” will be rejected.

## Using your dictionary

Put the two files in your `StylesPath`, under `config/dictionaries`:

```
styles/
└── config/
    └── dictionaries/
        ├── mini.aff
        └── mini.dic
```

Then name it from a [`spelling`](/checks/spelling) rule, without the extension:

{% code title="styles/MyStyle/Spelling.yml" %}

```yaml
extends: spelling
message: "Did you mean %s?"
level: error
# Use this dictionary instead of Vale's built-in one.
custom: true
dictionaries:
  - mini
```

{% endcode %}

Set `append: true` to check against your dictionary *and* Vale's built-in one; without it, yours replaces the default entirely. See [spelling](/checks/spelling) for the rest of the keys, including `dicpath` for keeping dictionaries somewhere else.

## Where can I find Hunspell dictionaries?

* [`wooorm/dictionaries`](https://github.com/wooorm/dictionaries?tab=readme-ov-file)
* [`LibreOffice/dictionaries`](https://github.com/LibreOffice/dictionaries)

[Firefox](https://addons.mozilla.org/en-US/firefox/language-tools) and [LibreOffice](https://extensions.libreoffice.org/en/extensions/?Tags%5B%5D=50) also provide Language Packs that include Hunspell dictionaries.


# Globbing

Learn how to use glob patterns in Vale.

[Glob](https://en.wikipedia.org/wiki/Glob_\(programming\)) patterns are used for matching file paths in a filesystem. They are commonly employed in command-line tools, scripting languages, and libraries to specify sets of filenames or directories.

This guide will cover the basics of using glob patterns in Vale.

## [Syntax](#syntax)

Vale supports the following glob syntax:

* `/` to separate path segments.
* `*` to match zero or more characters, including `/`.
* `?` to match one character other than `/`.
* `**` to match zero or more directories.
* `[]` to declare a range of characters to match.
* `{}` to declare a set of patterns to match.
* `[!...]` to negate a range of characters to match.

{% hint style="info" %}
`*` isn't limited to a single path segment: `docs/*.md` matches `docs/sub/nested.md` as well as `docs/page.md`. Use `?` where you need to stay within one segment.
{% endhint %}

Additionally, when using the `--glob` flag, you can use the `!` prefix to negate the *entire* pattern:

```sh
# Match all files except those with a `.md` or `.py` extension.
$ vale --glob='!**/*.{md,py}' path/to/files
```

## [Precedence](#precedence)

When evaluating glob patterns, the result of using the `--glob` flag is computed *first*, followed by any sections in the `.vale.ini` file.

For example, given the following `.vale.ini`:

```ini
StylesPath = styles

[*.md]
BasedOnStyles = Test
```

And this directory structure:

```
cases/test/
├── a.md
├── b
│   └── b.md
└── c.md
```

We can then run Vale with the following command:

```bash
$ vale --glob='!**/b/*' .
 cases/test/c.md
 8:37  warning  Found 'Here'.  Test.Test

 cases/test/a.md
 8:37  warning  Found 'Here'.  Test.Test
```

You’ll notice that the `b.md` file is not included in the output because the `--glob` flag takes precedence over the `.vale.ini` file.


# FAQ

Answers to questions Vale users ask most often.

{% hint style="info" %}
If none of these covers your case, ask in [Discord](https://discord.gg/tPeMs4A).
{% endhint %}

## Scopes and markup

### When should I use the `raw` scope?

Use it only when a rule needs to match markup *syntax* itself — an asterisk, a link target, a heading tag. Everything else should use a normal scope.

`raw` gives a rule the unprocessed file contents, which means no scope-related feature applies to it. That is the point of the scope, and also its cost.

### Why doesn't my `raw` rule skip code blocks?

Because `raw` bypasses the processing that skips them. Vale already ignores listing blocks and inline literals by default, so a rule like this fires inside code you meant to exclude:

```yaml
extends: existence
message: "Use uppercase letters for hexadecimal numbers"
scope: raw
level: error
raw: '\b0x[0-9a-f]*[a-f][0-9a-f]*\b'
```

Remove `scope: raw` and it behaves as intended:

```yaml
extends: existence
message: "Use uppercase letters for hexadecimal numbers"
level: error
raw: '\b0x[0-9a-f]*[a-f][0-9a-f]*\b'
```

### Why can't I disable a `raw` rule with a comment?

Comment processing happens after a document is converted to HTML, and `raw` rules don't run on the converted document. No markup-related feature — comments, ignore patterns — reaches them.

This is another reason to reach for `raw` only when targeting markup syntax.

### How do I turn a rule off for one paragraph?

Use [markup-based configuration](/topics/styles):

```markdown
<!-- vale Custom.spelling = NO -->

The three pillars of Developer Relations are **C**ommunity, **C**ontent, and **C**ode, also known as the 3Cs.

<!-- vale Custom.spelling = YES -->
```

### How do I check whether a word is italicized?

Target the markup with `raw`:

```yaml
extends: existence
message: "'%s' shouldn't be italicized."
scope: raw
nonword: true
tokens:
  - '\*(?:word1|word2)\*'
```

### How do I check that an image has alt text?

A `raw`-scoped rule can match the empty-alt form:

```yaml
extends: existence
message: "'%s' does not have an alt text."
level: warning
scope: raw
raw:
  - '!\[\]\(.+\)'
```

### How do I stop Vale spell-checking image alt text?

When AsciiDoc images have no explicit alt text, Asciidoctor derives one from the file path, and Vale then checks it. A [`TokenIgnores`](/keys/tokenignores) pattern skips them:

```ini
[*.adoc]
TokenIgnores = (image::.+\[\])
```

Providing real alt text is the better fix.

### Why doesn't `TokenIgnores` work on AsciiDoc cross-references?

Because the text Vale sees is the rendered link text, not the source. This cross-reference:

```html
<p>See <a href="#ecc_memsys_error_path">Subchapter</a> for more.</p>
```

lints "Subchapter". Without a label, it lints the anchor name instead:

```html
<p>See <a href="#ecc_memsys_error_path">[ecc_memsys_error_path]</a> for more.</p>
```

Give the reference a label suited to linting:

```asciidoc
See <<ecc_memsys_error_path,My Label>> for more.
```

### How do I ignore a reStructuredText directive?

With a [`BlockIgnores`](/keys/blockignores) pattern:

```ini
[*]
BasedOnStyles = Vale

BlockIgnores = (?s) *(\.\. math::)
```

### Why doesn't `occurrence` count across the whole document?

`occurrence` counts within each block that the scope matches. With `scope: heading`, this checks whether the token appears twice *in a single heading*:

```yaml
extends: occurrence
message: "Problem description is missing"
ignorecase: true
scope: heading
level: error
min: 1
max: 1
token: "problem description"
```

To count across the document, widen the scope and match the markup:

```yaml
extends: occurrence
message: "Problem description heading does not occur exactly once"
ignorecase: true
scope: raw
level: error
min: 1
max: 1
token: "<h[1-9]>problem description"
```

### How do I lint a `.txt` file as reStructuredText?

Use a [format association](/topics/.vale.ini#format-associations):

```ini
[formats]
txt = rst
```

The `--ext` flag also works, but only from the command line and only for single-file or all-`rst` input. A format association applies everywhere, including editor extensions and mixed-format runs.

### How do I get markup features in source-code comments?

Assign an embedded markup syntax to the format, then use the markup keys as normal:

```ini
[formats]
# Treat .cc comments as Markdown
cc = md

[*.cc]
# You then have access to all markup-related features,
# such as TokenIgnores and BlockIgnores.
TokenIgnores = (\\c \w+)
```

### How do I lint YAML?

Write a [view](/topics/views) that extracts the fields you care about:

```yaml
engine: dasel
scopes:
  - name: chapter.title
    expr: chapters.all().title

  - name: chapter.goal
    expr: chapters.all().goal
    # Long form content (`|-`) might contain markup?
    type: md

  - name: section.objective
    # objectives can be empty
    expr: chapters.all().topics.all().sections.all().objectives?.all()
```

Rules can then target those scopes individually:

```yaml
extends: existence
message: "'%s' should be capitalized"
scope: chapter.title
raw:
  - "^[a-z].+"
```

Your default `text`-scoped rules run on them too.

### Why don't `IgnoredScopes` and `TokenIgnores` work on my file?

They apply only to [supported formats](https://github.com/vale-cli/vale.sh/tree/svelte/docs/formats/README.md). For anything else, Vale has no markup to reason about, so there is nothing for those keys to select.

The other cause is a section that doesn't match the file. A section names files as they are on disk, so a [format association](/topics/.vale.ini#format-associations) doesn't make `[*.md]` reach a `.qmd`—that needs `[*.qmd]`. A section keyed on a path needs to be written relative to where you run Vale.

### Can Vale lint the URL inside a link?

No. There is no scope more specific than `raw` for this, and matching a link target reliably from raw text is difficult because the scope is so wide.

Checking link targets is closer to what a format-specific tool does than to prose linting.

### Can I enforce a reference style in AsciiDoc?

Yes, with `scope: raw`, since the rule needs to see the markup itself. There are AsciiDoc-specific examples in [rohennes/vale-asciidoc](https://github.com/rohennes/vale-asciidoc).

### How does Vale parse AsciiDoc?

It doesn't. Vale operates on the HTML that Asciidoctor produces.

### How do I set Asciidoctor up as the parser?

Make sure `asciidoctor` is on your `$PATH`. Any standard installation puts it there.

### Where does Vale look for a global config on Windows?

The user-level file is `%LOCALAPPDATA%\vale\.vale.ini`, and `vale ls-dirs` prints the exact path. A `.vale.ini` in the home directory, `%UserProfile%`, is also found, as the last step of the [search](/topics/.vale.ini#search) for a project file.

### How does Vale split text into tokens?

It depends on the rule's `extends` value and its `scope`. The matching process for `existence` is described on its [reference page](/checks/existence), and by default a rule sees text with markup syntax removed.

## Writing rules

### How do I disable a single rule?

Permanently, set it to `NO` in your `.vale.ini`:

```ini
Google.DateFormat = NO
```

For a one-off run, use a [filter](/topics/filters):

```console
$ vale --filter='.Name != "demo.Cap"'
```

### How do I disable rules for one sub-directory?

To skip a directory entirely, use `--glob`.

To change configuration for it, add a section to the root `.vale.ini` — you don't need a second config file:

```ini
StylesPath = vale-styles

Packages = Hugo, Microsoft

[*.md]
MinAlertLevel = suggestion
BasedOnStyles = Vale, Microsoft

[content/bad/*.md]
Microsoft.Contractions = NO
Microsoft.We = NO
Vale.Spelling = NO
```

### Can I use lookarounds in a pattern?

Yes, in `existence`-based rules, since v2.9.0.

### Why doesn't my `sequence` rule match?

Two mistakes are common. `pos` is not a supported key — the key is `tag` — and `^` matches the start of a *line*, not a sentence. A working version:

```yaml
extends: sequence
message: "Sentence should not start with a preposition."
tokens:
  - pattern: '[A-Z][a-z]+'
    tag: PRP
```

### How do I match a word only when it isn't followed by a noun?

Negate the final token:

```yaml
extends: sequence
message: "Don't use '%s'."
tokens:
  - pattern: following
  - tag: NN|NNS|NNP|NNPS
    negate: true
```

### Can a `conditional` rule accept plurals?

Yes — write the plural into both patterns:

```yaml
extends: conditional
message: "'%s' has no definition"
level: error
ignorecase: false
first: '\b([A-Z]{3,5})(?=s\b|\b)'
second: '(?:\b[A-Z][a-z]+ )+\(([A-Z]{3,5})s?\)'
```

### Can I flag a variable that's imported but never used?

It's better suited to a syntax linter, but it is possible:

```yaml
extends: conditional
message: "'%s' has been imported but not used."
level: error
scope: raw
first: "(?<=import )(.*)(?= from)"
second: '(?<=<)(\w+)'
```

### How do I stop a `substitution` rule firing on valid phrasing?

Exclude the valid forms with a lookbehind:

```yaml
swap:
  (?<!when |initial |a |default |root |your )login: log in
  you login: you log in
  log into: log in to
  logout (?:off|from): log out of
  logging into: logging in to
  logging out from: logging out of
```

### Why doesn't my pattern match next to punctuation?

Vale adds word boundaries (`\b`) automatically, which interferes with patterns that need to match punctuation. Set `nonword: true` to turn that off — see [`substitution`](/checks/substitution).

### Why doesn't `repetition` catch "text text."?

The trailing punctuation makes the two tokens different: `text` is not `text.`. Adjust the pattern to exclude it:

```yaml
extends: repetition
message: "'%s' is repeated!"
level: error
alpha: true
tokens:
  - '[^\s.!?,]+'
```

### How do I match a word only when it starts a sentence?

Combine a scope with a lookbehind:

```yaml
extends: existence
message: "'%s' should only be capitalized when starting a sentence."
level: error
nonword: true
scope:
  - heading
  - list
  - sentence
tokens:
  - (?<=\s)Internet(?! Service Provider| Protocol)
```

### Can I use the same `%s` twice in a message?

Yes, by index:

```yaml
message: "Avoid the use of '%s' on weekdays. Only use '%[1]s' on weekends."
```

### How do I replace a capture group with its uppercase form?

With an [`edit`](/fixes/edit) action: a step's argument can name the token's groups, and a casing step follows it.

```yaml
extends: existence
message: "'%s' should be '%s'."
action:
  name: edit
  params:
    - [replace, '$0', '$1']
    - [upper]
tokens:
  - '\bid: ([a-z]+)'
```

The first step replaces the whole match with its first group, and the second puts what is left in upper case.

### How strict should `capitalization` be?

`1.0` is the strictest setting. At that level, expect to maintain a [vocabulary](/keys/vocabularies) so brand and product names don't raise false positives.

## Vocabularies and spelling

### What's the difference between `Vale.Spelling` and `spelling`?

They are the same thing: `Vale.Spelling` is an implementation of the [`spelling`](/checks/spelling) check that uses the built-in dictionary. Write your own `spelling`-based rule only when you need a custom Hunspell dictionary — another language, or one of your own — and then use it *instead of* `Vale.Spelling`, not alongside it.

### How do I accept a multi-word phrase?

Spell check is a single-word operation, so a phrase needs two rules. First, stop the unusual word raising a spelling error:

```yaml
extends: spelling
message: "Did you really mean '%s'?"
level: error
filters:
  - '[Bb]ananaz'
```

Then enforce the phrase itself:

```yaml
extends: substitution
message: "Use '%s' instead of '%s'"
level: error
ignorecase: false
swap:
  # This will catch cases like "Blue Bananaz", "Yellow bananaz", etc.
  '(?:[^\s]*) ?[Bb]ananaz': Yellow Bananaz
```

### How do I accept a term without accepting it in handles and emails?

Make the vocabulary entry case-sensitive:

```
(?-i)Vaadin
```

`Vaadin` then passes `Vale.Spelling`, while `@vaadin` doesn't gain a case suggestion.

### Why doesn't my `accept.txt` entry work?

Entries are regular expressions, so a pattern that looks right may match nothing. `[dD]eserializer(s)` requires a literal `s`; you almost certainly want `[dD]eserializers?`.

### Why does adding a word to `accept.txt` stop my other rules firing on it?

A vocabulary entry is a global exception — every rule honours it. To except a word from one rule only, add it to that rule instead of the vocabulary.

### Can I change the wording of a spelling message?

Yes. Write your own rule that extends [`spelling`](/checks/spelling) with the message you want.

### Why are both "favour" and "favor" accepted?

Both spellings are in Vale's default dictionary. For a single-variant check, supply a [custom dictionary](/guides/hunspell).

### How do I ship vocabularies in a package?

`Vocab` is a global setting, and the vocabulary rules need the `Vale` style enabled:

```ini
StylesPath = vale/styles
MinAlertLevel = suggestion

Packages = https://github.com/spectrocloud/spectro-vale-pkg/releases/latest/download/spectrocloud-docs-internal.zip

# `Vocab` is a global setting
Vocab = spectrocloud-vocab

[*.md]
# The vocab rules, `Vale.Avoid` and `Vale.Terms`, require the `Vale` style to be enabled.
BasedOnStyles = Vale, spectrocloud-docs-internal
```

### How do I structure a package containing scripts and dictionaries?

From v3.0, every non-style resource lives under `<StylesPath>/config`:

```console
$ tree -a MyPackage
MyPackage
├── .vale.ini
└── styles
    ├── MyStyle
    │   └── MyRule.yml
    └── config
        ├── dictionaries
        │   └── MyDic.dic
        ├── scripts
        │   └── MyScript.tengo
        └── vocabularies
            └── MyVocab
                ├── accept.txt
                └── reject.txt
```

## Configuration and packages

### Why does `vale sync` fail with a `mkdir` error?

The directory named by [`StylesPath`](/keys/stylespath) has to exist before you sync:

```ini
StylesPath = a/path/to/an/existing/folder
```

### How do I override an entry in an inherited style?

Add the term to a [vocabulary](/keys/vocabularies); vocabulary entries take precedence over an inherited rule's list.

## CI and editors

### Can I report every alert but only fail on some files?

Not with a built-in option. Take Vale's JSON output and set the exit code from the file paths in it.

## Other

### How do I enforce one sentence per line?

```yaml
extends: occurrence
message: "Only use one sentence per line."
level: error
scope: paragraph
max: 1
token: '[.!?](?: |$)'
```

A [script](/checks/script) rule gives finer control.

### Does Vale know parts of speech?

Yes — the [`sequence`](/checks/sequence) check matches on part-of-speech tags. The [Package Explorer](https://vale.sh/explorer) has working examples.


# pre-commit

Use Vale with pre-commit, a Git Hooks framework.

[`pre-commit`](https://pre-commit.com/index.html) is a framework for managing and maintaining multi-language pre-commit hooks. It’s designed to be language-agnostic and can be used with any project.

To get started, here’s an example configuration that incorporates running `vale sync` prior to running Vale:

```yaml
repos:
  - repo: https://github.com/vale-cli/vale
    rev: v3.25.1
    hooks:
      - id: vale
        name: vale sync
        pass_filenames: false
        args: [sync]
      - id: vale
        args: [--output=line, --minAlertLevel=error]
```

Pin `rev` to a release tag rather than a commit. A commit pin is a version like any other and goes stale silently: the hook keeps installing whatever Vale was at that moment, so fixes released since then never reach it. A configuration pinned to an old revision can fail on features the docs describe — a `Vocab` that Vale reports as missing, for one — while the same setup works on a current release.


