2018-03-03 22:15:49 -08:00
|
|
|
# Custom Rules
|
|
|
|
|
2022-11-05 17:34:37 -07:00
|
|
|
In addition to its built-in rules, `markdownlint` lets you enhance the linting
|
|
|
|
experience by passing a list of custom rules using the [`options.customRules`
|
|
|
|
property][options-custom-rules]. Custom rules can do everything the built-in
|
|
|
|
rules can and are defined inline or imported from another package ([keyword
|
|
|
|
`markdownlint-rule` on npm][markdownlint-rule]). Custom rules can be disabled,
|
|
|
|
enabled, and customized using the same syntax as built-in rules.
|
2018-03-03 22:15:49 -08:00
|
|
|
|
2022-12-10 08:04:45 +05:30
|
|
|
## Implementing Simple Rules
|
|
|
|
|
|
|
|
For simple requirements like disallowing certain characters or patterns,
|
|
|
|
the community-developed
|
|
|
|
[markdownlint-rule-search-replace][markdownlint-rule-search-replace]
|
|
|
|
plug-in can be used.
|
|
|
|
This plug-in allows anyone to create a set of simple text-replacement rules in
|
|
|
|
JSON without needing to write any code.
|
|
|
|
|
|
|
|
[markdownlint-rule-search-replace]: https://www.npmjs.com/package/markdownlint-rule-search-replace
|
|
|
|
|
2018-03-03 22:15:49 -08:00
|
|
|
## Authoring
|
|
|
|
|
2022-11-05 17:34:37 -07:00
|
|
|
Rules are defined by a name (or multiple names), a description, an optional link
|
|
|
|
to more information, one or more tags, and a function that implements the rule's
|
|
|
|
behavior. That function is called once for each file/string input and is passed
|
|
|
|
the parsed input and a function to log any violations.
|
2018-03-03 22:15:49 -08:00
|
|
|
|
|
|
|
A simple rule implementation looks like:
|
|
|
|
|
2022-11-13 20:53:10 -08:00
|
|
|
```javascript
|
2024-03-06 21:18:51 -08:00
|
|
|
/** @type import("markdownlint").Rule */
|
2018-03-03 22:15:49 -08:00
|
|
|
module.exports = {
|
|
|
|
"names": [ "any-blockquote" ],
|
|
|
|
"description": "Rule that reports an error for any blockquote",
|
2019-01-15 21:56:38 -08:00
|
|
|
"information": new URL("https://example.com/rules/any-blockquote"),
|
2018-03-03 22:15:49 -08:00
|
|
|
"tags": [ "test" ],
|
2024-03-09 16:17:50 -08:00
|
|
|
"parser": "markdownit",
|
2018-03-03 22:15:49 -08:00
|
|
|
"function": function rule(params, onError) {
|
2023-02-18 21:41:07 -08:00
|
|
|
params.parsers.markdownit.tokens.filter(function filterToken(token) {
|
2018-03-03 22:15:49 -08:00
|
|
|
return token.type === "blockquote_open";
|
|
|
|
}).forEach(function forToken(blockquote) {
|
|
|
|
var lines = blockquote.map[1] - blockquote.map[0];
|
|
|
|
onError({
|
|
|
|
"lineNumber": blockquote.lineNumber,
|
|
|
|
"detail": "Blockquote spans " + lines + " line(s).",
|
|
|
|
"context": blockquote.line.substr(0, 7)
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
```
|
|
|
|
|
2024-03-09 16:17:50 -08:00
|
|
|
A rule is implemented as an `Object`:
|
2018-03-03 22:15:49 -08:00
|
|
|
|
2022-11-05 17:34:37 -07:00
|
|
|
- `names` is a required `Array` of `String` values that identify the rule in
|
|
|
|
output messages and config.
|
|
|
|
- `description` is a required `String` value that describes the rule in output
|
|
|
|
messages.
|
|
|
|
- `information` is an optional (absolute) `URL` of a link to more information
|
|
|
|
about the rule.
|
|
|
|
- `tags` is a required `Array` of `String` values that groups related rules for
|
|
|
|
easier customization.
|
2024-03-09 16:17:50 -08:00
|
|
|
- `parser` is a required `String` value `"markdownit" | "none"` that specifies
|
|
|
|
the parser data used via `params.parsers` (see below).
|
|
|
|
- Note: The value `"micromark"` is valid but is NOT currently supported.
|
2022-11-05 17:34:37 -07:00
|
|
|
- `asynchronous` is an optional `Boolean` value that indicates whether the rule
|
|
|
|
returns a `Promise` and runs asynchronously.
|
|
|
|
- `function` is a required `Function` that implements the rule and is passed two
|
|
|
|
parameters:
|
|
|
|
- `params` is an `Object` with properties that describe the content being
|
|
|
|
analyzed:
|
2018-05-25 17:28:56 -07:00
|
|
|
- `name` is a `String` that identifies the input file/string.
|
2024-03-09 16:17:50 -08:00
|
|
|
- `parsers` is an `Object` with properties corresponding to the value of
|
|
|
|
`parser` in the rule definition (see above).
|
|
|
|
- `markdownit` is an `Object` that provides access to output from the
|
|
|
|
[`markdown-it`][markdown-it] parser.
|
|
|
|
- `tokens` is an `Array` of [`markdown-it` `Token`s][markdown-it-token]
|
|
|
|
with added `line` and `lineNumber` properties. (This property was
|
|
|
|
previously on the `params` object.)
|
2022-11-05 17:34:37 -07:00
|
|
|
- `lines` is an `Array` of `String` values corresponding to the lines of the
|
|
|
|
input file/string.
|
|
|
|
- `frontMatterLines` is an `Array` of `String` values corresponding to any
|
|
|
|
front matter (not present in `lines`).
|
|
|
|
- `config` is an `Object` corresponding to the rule's entry in
|
|
|
|
`options.config` (if present).
|
|
|
|
- `onError` is a function that takes a single `Object` parameter with one
|
|
|
|
required and four optional properties:
|
|
|
|
- `lineNumber` is a required `Number` specifying the 1-based line number of
|
|
|
|
the error.
|
|
|
|
- `detail` is an optional `String` with information about what caused the
|
|
|
|
error.
|
|
|
|
- `context` is an optional `String` with relevant text surrounding the error
|
|
|
|
location.
|
2023-07-11 21:44:45 -07:00
|
|
|
- `information` is an optional (absolute) `URL` of a link to override the
|
|
|
|
same-named value provided by the rule definition. (Uncommon)
|
2022-11-05 17:34:37 -07:00
|
|
|
- `range` is an optional `Array` with two `Number` values identifying the
|
|
|
|
1-based column and length of the error.
|
|
|
|
- `fixInfo` is an optional `Object` with information about how to fix the
|
|
|
|
error (all properties are optional, but at least one of `deleteCount` and
|
|
|
|
`insertText` should be present; when applying a fix, the delete should be
|
2019-09-19 21:49:42 -07:00
|
|
|
performed before the insert):
|
2022-11-05 17:34:37 -07:00
|
|
|
- `lineNumber` is an optional `Number` specifying the 1-based line number
|
|
|
|
of the edit.
|
|
|
|
- `editColumn` is an optional `Number` specifying the 1-based column
|
|
|
|
number of the edit.
|
|
|
|
- `deleteCount` is an optional `Number` specifying the number of
|
|
|
|
characters to delete (the value `-1` is used to delete the line).
|
|
|
|
- `insertText` is an optional `String` specifying the text to insert. `\n`
|
|
|
|
is the platform-independent way to add a line break; line breaks should
|
2022-12-10 08:04:45 +05:30
|
|
|
be added at the beginning of a line instead of at the end.
|
2018-03-03 22:15:49 -08:00
|
|
|
|
2022-11-05 17:34:37 -07:00
|
|
|
The collection of helper functions shared by the built-in rules is available for
|
|
|
|
use by custom rules in the [markdownlint-rule-helpers package][rule-helpers].
|
2019-04-13 11:18:57 -07:00
|
|
|
|
2021-12-11 21:44:25 -08:00
|
|
|
### Asynchronous Rules
|
|
|
|
|
2022-11-05 17:34:37 -07:00
|
|
|
If a rule needs to perform asynchronous operations (such as fetching a network
|
|
|
|
resource), it can specify the value `true` for its `asynchronous` property.
|
|
|
|
Asynchronous rules should return a `Promise` from their `function`
|
|
|
|
implementation that is resolved when the rule completes. (The value passed to
|
|
|
|
`resolve(...)` is ignored.) Linting violations from asynchronous rules are
|
|
|
|
reported via the `onError` function just like for synchronous rules.
|
2021-12-11 21:44:25 -08:00
|
|
|
|
2022-11-05 17:34:37 -07:00
|
|
|
**Note**: Asynchronous rules cannot be referenced in a synchronous calling
|
|
|
|
context (i.e., `markdownlint.sync(...)`). Attempting to do so throws an
|
|
|
|
exception.
|
2021-12-11 21:44:25 -08:00
|
|
|
|
2018-03-03 22:15:49 -08:00
|
|
|
## Examples
|
|
|
|
|
2022-11-05 17:34:37 -07:00
|
|
|
- [Simple rules used by the project's test cases][test-rules]
|
|
|
|
- [Code for all `markdownlint` built-in rules][lib]
|
|
|
|
- [Package configuration for publishing to npm][test-rules-npm]
|
2019-03-03 21:35:20 -08:00
|
|
|
- Packages should export a single rule object or an `Array` of rule objects
|
2022-11-05 17:34:37 -07:00
|
|
|
- [Custom rules from the webhintio/hint repository][hint]
|
2018-03-03 22:15:49 -08:00
|
|
|
|
|
|
|
## References
|
|
|
|
|
2022-11-05 17:34:37 -07:00
|
|
|
- [CommonMark documentation and specification][commonmark]
|
|
|
|
- [`markdown-it` Markdown parser project page][markdown-it]
|
2018-03-03 22:15:49 -08:00
|
|
|
|
|
|
|
## Params
|
|
|
|
|
2018-07-20 22:31:41 -07:00
|
|
|
The Markdown document:
|
2018-03-03 22:15:49 -08:00
|
|
|
|
2018-03-03 22:22:02 -08:00
|
|
|
```markdown
|
2018-03-03 22:15:49 -08:00
|
|
|
# Title
|
|
|
|
|
|
|
|
Text *text* text.
|
|
|
|
```
|
|
|
|
|
2018-07-20 22:31:41 -07:00
|
|
|
Yields the `params` object:
|
2018-03-03 22:15:49 -08:00
|
|
|
|
|
|
|
```json
|
|
|
|
{
|
2018-05-25 17:28:56 -07:00
|
|
|
"name": "doc/example.md",
|
2024-03-09 16:17:50 -08:00
|
|
|
"parsers.markdownit.tokens": [
|
2018-03-03 22:15:49 -08:00
|
|
|
{
|
|
|
|
"type": "heading_open",
|
|
|
|
"tag": "h1",
|
|
|
|
"attrs": null,
|
|
|
|
"map": [ 0, 1 ],
|
|
|
|
"nesting": 1,
|
|
|
|
"level": 0,
|
|
|
|
"children": null,
|
|
|
|
"content": "",
|
|
|
|
"markup": "#",
|
|
|
|
"info": "",
|
|
|
|
"meta": null,
|
|
|
|
"block": true,
|
|
|
|
"hidden": false,
|
|
|
|
"line": "# Title",
|
|
|
|
"lineNumber": 1
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"type": "inline",
|
|
|
|
"tag": "",
|
|
|
|
"attrs": null,
|
|
|
|
"map": [ 0, 1 ],
|
|
|
|
"nesting": 0,
|
|
|
|
"level": 1,
|
|
|
|
"children": [
|
|
|
|
{
|
|
|
|
"type": "text",
|
|
|
|
"tag": "",
|
|
|
|
"attrs": null,
|
|
|
|
"map": null,
|
|
|
|
"nesting": 0,
|
|
|
|
"level": 0,
|
|
|
|
"children": null,
|
|
|
|
"content": "Title",
|
|
|
|
"markup": "",
|
|
|
|
"info": "",
|
|
|
|
"meta": null,
|
|
|
|
"block": false,
|
|
|
|
"hidden": false,
|
|
|
|
"lineNumber": 1,
|
|
|
|
"line": "# Title"
|
|
|
|
}
|
|
|
|
],
|
|
|
|
"content": "Title",
|
|
|
|
"markup": "",
|
|
|
|
"info": "",
|
|
|
|
"meta": null,
|
|
|
|
"block": true,
|
|
|
|
"hidden": false,
|
|
|
|
"line": "# Title",
|
|
|
|
"lineNumber": 1
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"type": "heading_close",
|
|
|
|
"tag": "h1",
|
|
|
|
"attrs": null,
|
|
|
|
"map": null,
|
|
|
|
"nesting": -1,
|
|
|
|
"level": 0,
|
|
|
|
"children": null,
|
|
|
|
"content": "",
|
|
|
|
"markup": "#",
|
|
|
|
"info": "",
|
|
|
|
"meta": null,
|
|
|
|
"block": true,
|
|
|
|
"hidden": false
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"type": "paragraph_open",
|
|
|
|
"tag": "p",
|
|
|
|
"attrs": null,
|
|
|
|
"map": [ 2, 3 ],
|
|
|
|
"nesting": 1,
|
|
|
|
"level": 0,
|
|
|
|
"children": null,
|
|
|
|
"content": "",
|
|
|
|
"markup": "",
|
|
|
|
"info": "",
|
|
|
|
"meta": null,
|
|
|
|
"block": true,
|
|
|
|
"hidden": false,
|
|
|
|
"line": "Text *text* text.",
|
|
|
|
"lineNumber": 3
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"type": "inline",
|
|
|
|
"tag": "",
|
|
|
|
"attrs": null,
|
|
|
|
"map": [ 2, 3 ],
|
|
|
|
"nesting": 0,
|
|
|
|
"level": 1,
|
|
|
|
"children": [
|
|
|
|
{
|
|
|
|
"type": "text",
|
|
|
|
"tag": "",
|
|
|
|
"attrs": null,
|
|
|
|
"map": null,
|
|
|
|
"nesting": 0,
|
|
|
|
"level": 0,
|
|
|
|
"children": null,
|
|
|
|
"content": "Text ",
|
|
|
|
"markup": "",
|
|
|
|
"info": "",
|
|
|
|
"meta": null,
|
|
|
|
"block": false,
|
|
|
|
"hidden": false,
|
|
|
|
"lineNumber": 3,
|
|
|
|
"line": "Text *text* text."
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"type": "em_open",
|
|
|
|
"tag": "em",
|
|
|
|
"attrs": null,
|
|
|
|
"map": null,
|
|
|
|
"nesting": 1,
|
|
|
|
"level": 1,
|
|
|
|
"children": null,
|
|
|
|
"content": "",
|
|
|
|
"markup": "*",
|
|
|
|
"info": "",
|
|
|
|
"meta": null,
|
|
|
|
"block": false,
|
|
|
|
"hidden": false,
|
|
|
|
"lineNumber": 3,
|
|
|
|
"line": "Text *text* text."
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"type": "text",
|
|
|
|
"tag": "",
|
|
|
|
"attrs": null,
|
|
|
|
"map": null,
|
|
|
|
"nesting": 0,
|
|
|
|
"level": 1,
|
|
|
|
"children": null,
|
|
|
|
"content": "text",
|
|
|
|
"markup": "",
|
|
|
|
"info": "",
|
|
|
|
"meta": null,
|
|
|
|
"block": false,
|
|
|
|
"hidden": false,
|
|
|
|
"lineNumber": 3,
|
|
|
|
"line": "Text *text* text."
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"type": "em_close",
|
|
|
|
"tag": "em",
|
|
|
|
"attrs": null,
|
|
|
|
"map": null,
|
|
|
|
"nesting": -1,
|
|
|
|
"level": 0,
|
|
|
|
"children": null,
|
|
|
|
"content": "",
|
|
|
|
"markup": "*",
|
|
|
|
"info": "",
|
|
|
|
"meta": null,
|
|
|
|
"block": false,
|
|
|
|
"hidden": false,
|
|
|
|
"lineNumber": 3,
|
|
|
|
"line": "Text *text* text."
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"type": "text",
|
|
|
|
"tag": "",
|
|
|
|
"attrs": null,
|
|
|
|
"map": null,
|
|
|
|
"nesting": 0,
|
|
|
|
"level": 0,
|
|
|
|
"children": null,
|
|
|
|
"content": " text.",
|
|
|
|
"markup": "",
|
|
|
|
"info": "",
|
|
|
|
"meta": null,
|
|
|
|
"block": false,
|
|
|
|
"hidden": false,
|
|
|
|
"lineNumber": 3,
|
|
|
|
"line": "Text *text* text."
|
|
|
|
}
|
|
|
|
],
|
|
|
|
"content": "Text *text* text.",
|
|
|
|
"markup": "",
|
|
|
|
"info": "",
|
|
|
|
"meta": null,
|
|
|
|
"block": true,
|
|
|
|
"hidden": false,
|
|
|
|
"line": "Text *text* text.",
|
|
|
|
"lineNumber": 3
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"type": "paragraph_close",
|
|
|
|
"tag": "p",
|
|
|
|
"attrs": null,
|
|
|
|
"map": null,
|
|
|
|
"nesting": -1,
|
|
|
|
"level": 0,
|
|
|
|
"children": null,
|
|
|
|
"content": "",
|
|
|
|
"markup": "",
|
|
|
|
"info": "",
|
|
|
|
"meta": null,
|
|
|
|
"block": true,
|
|
|
|
"hidden": false
|
|
|
|
}
|
|
|
|
],
|
|
|
|
"lines": [
|
|
|
|
"# Title",
|
|
|
|
"",
|
|
|
|
"Text *text* text.",
|
|
|
|
""
|
|
|
|
],
|
|
|
|
"frontMatterLines": [],
|
|
|
|
"config": {
|
|
|
|
"customValue1": "abc",
|
|
|
|
"customValue2": 123
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
2022-11-05 17:34:37 -07:00
|
|
|
|
|
|
|
[commonmark]: https://commonmark.org/
|
|
|
|
[hint]: https://github.com/webhintio/hint/blob/main/scripts/lint-markdown.js
|
|
|
|
[lib]: ../lib
|
|
|
|
[markdown-it]: https://github.com/markdown-it/markdown-it
|
|
|
|
[markdown-it-token]: https://markdown-it.github.io/markdown-it/#Token
|
|
|
|
[markdownlint-rule]: https://www.npmjs.com/search?q=keywords:markdownlint-rule
|
|
|
|
[rule-helpers]: https://www.npmjs.com/package/markdownlint-rule-helpers
|
|
|
|
[options-custom-rules]: ../README.md#optionscustomrules
|
|
|
|
[test-rules]: ../test/rules
|
|
|
|
[test-rules-npm]: ../test/rules/npm
|