Automated documentation checks
What the automated checks on your documentation pull request look for, how to run them yourself, and how to change them.
Every pull request to the home-assistant.io repository runs a set of automated checks. They catch the small things, like a code block without a language or a misspelled product name, so reviewers can focus on the content.
The checks run in the test.yml GitHub Actions workflow. Two of them are about your writing:
- Lint Markdown checks the structure and formatting of your Markdown, with remark.
- Lint Text checks spelling, terminology, and wording, with textlint.
The Markdown job also checks that integration pages in source/_integrations/ use the .markdown file extension. A .md file there fails the check.
A third job, Build Astro site, builds the part of the site in the astro folder. It does not look at your writing, but it has to pass too.
You need Node.js. The lint jobs in the CI use version 20. The repository's .nvmrc file and the devcontainer use version 26.
-
In your clone of the repository, install the dependencies:
Shellnpm install -
Run the Markdown checks:
Shellnpm run markdown:lint -
Run the text checks:
Shellnpm run textlint
These are the same commands the CI runs. They are defined in package.json.
To check a single page while you work, run the tools on that file directly:
npx remark --quiet --frail source/_integrations/my_integration.markdown
npx textlint source/_integrations/my_integration.markdownThe repository recommends the textlint and markdownlint extensions for Visual Studio Code. With those installed, you see most problems in your editor while you type.
The markdown:lint script runs remark --quiet --frail . on the whole repository. The --frail flag turns warnings into failures, so every finding blocks the check.
The rules live in .remarkrc.js. They check:
- Code blocks: every fenced code block names its language. Shell examples do not start each line with
$, because that breaks copy and paste. A$is fine when you need to tell a command apart from its output. - Headings: headings use the
#style, and only go down one level at a time. No jumping from##to####. - Lists: unordered lists use
-. Ordered lists use a period after the number, like1., and number their items consistently. Counting up,1.,2.,3., is the convention. - Spelling of names: a list of prohibited strings, like
Githubinstead ofGitHub,websocketorWebsocketinstead ofWebSocket, andSpeech-To-Textinstead ofSpeech-to-text. - Links to the site itself: a full
https://www.home-assistant.io/link should be a relative path, like/integrations/hue/.
Some files are skipped on purpose, like the blog posts and changelogs. They are listed in .remarkignore.
-
Install the package of the rule, which updates
package.jsonandpackage-lock.json:Shellnpm install --save-dev remark-lint-some-rule -
Enable and configure the rule in
.remarkrc.js. -
Run
npm run markdown:lint. -
Fix the existing violations, or add a justified exclusion to
.remarkignore.
The textlint script checks these folders:
source/_docssource/_faqsource/_integrationssource/_dashboardssource/cloudsource/getting-startedsource/hassiosource/dashboards
Other folders, like the blog, are not checked by the CI. To check everything under source, run npm run textlint:all.
The rules live in .textlintrc.json:
common-misspellingscatches common spelling mistakes.terminologyenforces product names, preferred terms, and wording. It skips text inside blockquotes.- The
commentsfilter lets you turn textlint off for a part of a page. - The
allowlistfilter ignores text that would otherwise give false positives, like YAML keys such asmqtt:orhomeassistant:.
Terminology lives in the terms list of the terminology rule, in .textlintrc.json. An entry takes one of two forms.
A plain string is a term that must keep exactly this spelling and capitalization:
"Home Assistant",
"Z-Wave",
"GitHub"A pair flags one spelling and suggests another. The first value is the pattern to match, the second is the suggestion shown to the contributor:
["addon", "add-on"],
["colour", "color"],
["repo\\b", "repository"]The pattern is a regular expression inside a JSON string. Escape characters that have a special meaning in either, like the backslash in \\b above.
Put new entries near related ones. Then run npm run textlint to see what the change catches.
Sometimes textlint flags something that is correct. In that case, wrap the text in HTML comments:
<!-- textlint-disable -->
Text that textlint should skip.
<!-- textlint-enable -->To turn off only one rule, add its name:
<!-- textlint-disable terminology -->
Text that the terminology rule should skip.
<!-- textlint-enable terminology -->Some guidelines:
- Keep the disabled part as small as possible.
- Put a blank line before and after each comment, unless that breaks the surrounding Markdown.
- Do not put these comments inside a text box.
- Add the package of the rule to
package.json, and updatepackage-lock.json. - Enable and configure the rule in
.textlintrc.json. - Run
npm run textlint. - Fix the existing violations, or add narrow exclusions to the
allowlistin.textlintrc.json.
The repository also has a .markdownlint.json file. It configures markdownlint for editors:
- The first heading of a page is level 2 (
MD002), because the title comes from the front matter. - There is no line length limit (
MD013), so you can write flowing text. - Headings with the same text are allowed, as long as they are not siblings under the same parent (
MD024). - Inline HTML is allowed (
MD033).
The markdownlint extension is in the recommended extensions of .vscode/extensions.json, so you may see its warnings in Visual Studio Code.
Markdownlint does not run in the CI, and there is no package.json script for it. Its warnings are hints, not blockers. If a markdownlint rule must be enforced on pull requests, it also needs a script and a step in the workflow.