Creating Custom Rules
You can create a custom rule by referring to the API documentation, but the easiest way is to use the scaffolding command:
npx @markuplint/create-rule
Answer the interactive questions:
? What purpose do you create the rule for? …
❯ Add the rule to this project
Create the rule and publish it as a package
Choose either "Add the rule to this project" or "Create the rule and publish it as a package."
Adding to your project
The command will ask for a directory name and a rule name. Then choose between TypeScript or JavaScript, and whether to include tests.
The following files are created:
The test code is written in Vitest format. Rewrite it as needed for your test runner.
Then specify it in your configuration to apply it:
{
"plugins": ["./[dir-name]/index.js"], // Need transpile if the source is TypeScript
"rules": {
"[dir-name]/[rule-name]": true
}
}
By default, the plugin name is the directory name ([dir-name] in the examples above). You can change it in the plugin definition:
import { createPlugin } from '@markuplint/ml-core';
import { ruleName } from './rules/ruleName';
export default createPlugin({
name: '[dir-name]', // 👈 Change here if you want
create(setting) {
return {
rules: {
ruleName: ruleName(setting),
},
};
},
});
Creating a plugin as an npm package
The command will ask for a plugin name and a rule name. Then choose between TypeScript or JavaScript, and whether to include tests.
The following files are created:
Basic evaluation
Extract target nodes from the document object, evaluate them, and pass violations to the report function. The document object provides both Markuplint-specific methods (like walkOn) and native DOM APIs (like querySelector):
createRule({
async verify({ document, report }) {
// Walking style
await document.walkOn('Element', el => {
if (el.localName === 'div') {
report({
scope: el,
message: 'The div element is found',
});
}
});
// DOM API traversing style
const el = document.querySelector('div');
if (el) {
report({
scope: el,
message: 'The div element is found',
});
}
},
});
There are two ways to report a violation: pass a node, or pass the line, column, and raw string:
report({
scope: node, // Specify a node (Element, Attribute, or TextNode, etc.)
message: 'Warning message',
});
report({
line: 20,
col: 10,
raw: 'string in range',
message: 'Warning message',
});
Messages i18n
The translate function (aliased as t) translates messages:
createRule({
async verify({ document, report, translate, t }) {
const noTitle = !document.querySelector('title');
if (noTitle) {
report({
line: 1,
col: 1,
raw: '',
message: translate('missing {0}', t('the "{0*}" {1}', 'title', 'element')),
});
}
},
});
Missing the "title" element
「title」要素がありません
See the @markuplint/i18n API for details.
Currently only Japanese is available besides English in the dictionaries. Contributions for other languages are welcome.
Next steps
- API Documentation — Full API reference for rule development
- Applying Rules — How to apply custom rules in your configuration