This guide is part of a step-by-step tutorial, and will help you
getting started to create your first linting plugin for remark
.
- Set up the project
- Set up remark
- The
no-invalid-gif
rule - Create the custom rule
- Rule arguments
- Rule implementation
- Import the rule in your remark config
- Apply the rule on the Markdown file
Create a new folder and enter it from your terminal.
For this example I will be using Unix commands (macOS and Linux compatible).
Then generate a package.json
:
mkdir my-custom-rule
cd my-custom-rule
npm init -y
Now we can start installing our dependencies:
npm install remark-lint remark-cli
remark-lint
— core lint pluginremark-cli
— command line interface
We will also use some utilities:
npm install unified-lint-rule unist-util-visit
These will help us creating and managing our custom rules.
With everything installed, we can now create a .remarkrc.js
that will contain
the plugins we’ll use.
For more info on configuration, see Examples in remark-lint
.
touch .remarkrc.js
// .remarkrc.js
/**
* @import {Preset} from 'unified'
*/
/** @type {Preset} */
const preset = {plugins: []}
export default preset
Then, in our package.json
, add the following script to process all the
markdown files in our project:
"scripts": {
"lint": "remark ."
}
Let’s create a doc.md
, the markdown file we want to lint:
touch doc.md
…and copy/paste the following:
## Best pets! <3
Some funny images of our favorite pets
![a funny cat](funny-cat.gif)
![a lovely dog](lovely-dog.png)
At this point, we have a working remark
configuration and a markdown file in
the project.
If we run npm run lint
we should expect to see in our terminal:
doc.md: no issues found
All good, the file has been processed, and because we haven’t specified any plugins nor lint rules, no issues are found.
Let’s imagine we want to write a rule that checks whether a .gif
file is used
as an image.
Given the content of our doc.md
file declared above, we would expect an
error or warning pointing to:
![a funny cat](funny-cat.gif)
Because the file extension .gif
in the image violates our rule.
Let’s create a new folder rules
under the root directory, where we will place
all of our custom rules, and create a new file in it named no-gif-allowed.js
.
mkdir rules
cd rules
touch no-gif-allowed.js
cd .. # return to project root
Note: the name of folders and files, and where to place them within your project, is up to you.
In ./rules/no-gif-allowed.js
, let’s import unified-lint-rule
.
We then export the result of calling rule
by providing the namespace and rule
name (remark-lint:no-gif-allowed
) as the first argument, and our
implementation of the rule (noGifAllowed
) as the second argument.
// rules/no-gif-allowed.js
import {lintRule} from 'unified-lint-rule'
const remarkLintNoGifAllowed = lintRule(
'remark-lint:no-gif-allowed',
function (tree, file, options) {
// Rule implementation
}
)
export default remarkLintNoGifAllowed
Let’s say you want all your custom rules to be defined as part of your project
namespace.
If your project was named my-project
, then you can export your rule as:
const remarkLintNoGifAllowed = lintRule(
'my-project-name:no-gif-allowed',
function () {}
)
// Or:
const remarkLintNoGifAllowed = lintRule(
'my-npm-published-package:no-gif-allowed',
function () {}
)
This can help you when wanting to create a group of rules under the same namespace.
Your rule function will receive three arguments:
function rule(tree, file, options) {}
tree
(required): mdastfile
(required): virtual fileoptions
(optional): additional info passed to the rule by users
Because we will be inspecting mdast, which is a markdown abstract syntax tree built upon unist, we can take advantage of the many existing unist utilities to inspect our tree’s nodes.
For this example, we will use unist-util-visit
to
recursively inspect all the image nodes, and
unist-util-generated
to ensure we are not inspecting
nodes that we have generated ourselves and do not belong to the doc.md
.
/**
* @import {Root} from 'mdast'
*/
import {lintRule} from 'unified-lint-rule'
import {visit} from 'unist-util-visit'
const remarkLintNoGifAllowed = lintRule(
'remark-lint:no-gif-allowed',
/**
* @param {Root} tree
* Tree.
* @returns {undefined}
* Nothing.
*/
function (tree, file, options) {
visit(tree, 'image', function (node) {
// This is an extremely simplified example of how to structure
// the logic to check whether a node violates your rule.
// You have complete freedom over how to visit/inspect the tree,
// and on how to implement the validation logic for your node.
const isValid = !node.url.endsWith('.gif')
if (!isValid) {
// Remember to provide the node as second argument to the message,
// in order to obtain the position and column where the violation occurred.
file.message(
'Invalid image file extensions. Please do not use gifs',
node
)
}
})
}
)
export default remarkLintNoGifAllowed
Now that our custom rule is defined and ready to be used we need to add it to
our remark
configuration.
You can do that by importing your rule and adding it in plugins
array:
// .remarkrc.js
/**
* @import {Preset} from 'unified'
*/
import remarkLintNoGifAllowed from './rules/no-gif-allowed.js'
/** @type {Preset} */
const preset = {plugins: [remarkLintNoGifAllowed]}
export default preset
If you run npm run lint
, you should see the following message in the terminal:
5:1-5:30 warning Invalid image file extensions. Please do not use gifs no-gif-allowed remark-lint
Congratulations! The rule works!