9 KiB
Custom Rules
In addition to its built-in rules, markdownlint lets you enhance the linting
experience by passing an array of custom rules using the options.customRules
property. Custom rules can do everything the built-in
rules can and are defined inline or imported from another package (keyword
markdownlint-rule on npm). When defined by a file or
package, the export can be a single rule object (see below) or an array of them.
Custom rules can be disabled, enabled, and customized using the same syntax as
built-in rules.
Implementing Simple Rules
For simple requirements like disallowing certain characters or patterns, the community-developed markdownlint-rule-search-replace plug-in can be used. This plug-in allows anyone to create a set of simple text-replacement rules without needing to write code.
Authoring
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.
Custom rules can (should) operate on a structured set of tokens based on the
micromark parser (this is preferred). Alternatively, custom
rules can operate on a structured set of tokens based on the
markdown-it parser (legacy support). Finally, custom rules
can operate directly on text with the none parser.
A simple rule implementation using the micromark parser to report a violation
for any use of blockquotes might look like:
/** @type import("markdownlint").Rule */
module.exports = {
"names": [ "any-blockquote-micromark" ],
"description": "Rule that reports an error for any blockquote",
"information": new URL("https://example.com/rules/any-blockquote"),
"tags": [ "test" ],
"parser": "micromark",
"function": (params, onError) => {
const blockquotes = params.parsers.micromark.tokens
.filter((token) => token.type === "blockQuote");
for (const blockquote of blockquotes) {
const lines = blockquote.endLine - blockquote.startLine + 1;
onError({
"lineNumber": blockquote.startLine,
"detail": "Blockquote spans " + lines + " line(s).",
"context": params.lines[blockquote.startLine - 1]
});
}
}
}
That same rule implemented using the markdown-it parser might look like:
/** @type import("markdownlint").Rule */
module.exports = {
"names": [ "any-blockquote-markdown-it" ],
"description": "Rule that reports an error for any blockquote",
"information": new URL("https://example.com/rules/any-blockquote"),
"tags": [ "test" ],
"parser": "markdownit",
"function": (params, onError) => {
const blockquotes = params.parsers.markdownit.tokens
.filter((token) => token.type === "blockquote_open");
for (const blockquote of blockquotes) {
const [ startIndex, endIndex ] = blockquote.map;
const lines = endIndex - startIndex;
onError({
"lineNumber": blockquote.lineNumber,
"detail": "Blockquote spans " + lines + " line(s).",
"context": blockquote.line
});
}
}
}
A rule is implemented as an Object:
namesis a requiredArrayofStringvalues that identify the rule in output messages and config.descriptionis a requiredStringvalue that describes the rule in output messages.informationis an optional (absolute)URLof a link to more information about the rule.tagsis a requiredArrayofStringvalues that groups related rules for easier customization.parseris a requiredStringvalue"markdownit" | "micromark" | "none"that specifies the parser data used viaparams.parsers(see below).asynchronousis an optionalBooleanvalue that indicates whether the rule returns aPromiseand runs asynchronously.functionis a requiredFunctionthat implements the rule and is passed two parameters:paramsis anObjectwith properties that describe the content being analyzed:nameis aStringthat identifies the input file/string.parsersis anObjectwith properties corresponding to the value ofparserin the rule definition (see above).markdownitis anObjectthat provides access to output from themarkdown-itparser.tokensis anArrayofmarkdown-itTokens with addedlineandlineNumberproperties. (This property was previously on theparamsobject.)
micromarkis anObjectthat provides access to output from themicromarkparser.tokensis anArrayofMicromarkTokenobjects.
- Samples for both
tokensare available via test snapshots.
linesis anArrayofStringvalues corresponding to the lines of the input file/string.frontMatterLinesis anArrayofStringvalues corresponding to any front matter (not present inlines).configis anObjectcorresponding to the rule's entry inoptions.config(if present).versionis aStringthat corresponds to the version ofmarkdownlint
onErroris a function that takes a singleObjectparameter with one required and four optional properties:lineNumberis a requiredNumberspecifying the 1-based line number of the error.detailis an optionalStringwith information about what caused the error.contextis an optionalStringwith relevant text surrounding the error location.informationis an optional (absolute)URLof a link to override the same-named value provided by the rule definition. (Uncommon)rangeis an optionalArraywith twoNumbervalues identifying the 1-based column and length of the error.fixInfois an optionalObjectwith information about how to fix the error (all properties are optional, but at least one ofdeleteCountandinsertTextshould be present; when applying a fix, the delete should be performed before the insert):lineNumberis an optionalNumberspecifying the 1-based line number of the edit.editColumnis an optionalNumberspecifying the 1-based column number of the edit.deleteCountis an optionalNumberspecifying the number of characters to delete (the value-1is used to delete the line).insertTextis an optionalStringspecifying the text to insert.\nis the platform-independent way to add a line break; line breaks should be added at the beginning of a line instead of at the end.
The collection of helper functions shared by the built-in rules is available for use by custom rules in the markdownlint-rule-helpers package.
Asynchronous Rules
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.
Note: Asynchronous rules cannot be referenced in a synchronous calling
context (i.e., markdownlint.sync(...)). Attempting to do so throws an
exception.
Examples
- Simple rules used by the project's test cases
- Code for all
markdownlintbuilt-in rules - Complete example rule including npm configuration
- Custom rules from the github/docs repository
- Custom rules from the electron/lint-roller repository
- Custom rules from the webhintio/hint repository