Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add documentation for creating custom rules. Closes #261 #262

Merged
merged 49 commits into from
Apr 7, 2021
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
6ab9a44
chore: add documentation for custom rules
floroz Apr 3, 2021
c3702f0
extend example to include config from start to finish
floroz Apr 4, 2021
f40b561
add table of contents
floroz Apr 4, 2021
ce41e5a
add table of contents
floroz Apr 4, 2021
fc90590
toc bug
floroz Apr 4, 2021
dbb31d2
readme update
floroz Apr 4, 2021
596916b
updated custom rule
Apr 7, 2021
e8f7f62
fix bash commands
Apr 7, 2021
e1f69db
fix bash commands
Apr 7, 2021
eac095c
fix broken space
Apr 7, 2021
2c1e630
add bash command
Apr 7, 2021
377a279
update intro
Apr 7, 2021
03caf5c
address PR comments
Apr 7, 2021
ae9f17b
comment capital letter
Apr 7, 2021
402e8ec
lint fix
Apr 7, 2021
7c737c2
Update doc/create-a-custom-rule.md
floroz Apr 7, 2021
f9f4d6e
Update doc/create-a-custom-rule.md
floroz Apr 7, 2021
269ba88
Update doc/create-a-custom-rule.md
floroz Apr 7, 2021
1667d7a
Update doc/create-a-custom-rule.md
floroz Apr 7, 2021
0c6ef92
Update doc/create-a-custom-rule.md
floroz Apr 7, 2021
8f8b241
Update doc/create-a-custom-rule.md
floroz Apr 7, 2021
2215da2
Update doc/create-a-custom-rule.md
floroz Apr 7, 2021
4bef7e1
Update doc/create-a-custom-rule.md
floroz Apr 7, 2021
726ebaf
Update doc/create-a-custom-rule.md
floroz Apr 7, 2021
c05c4f4
Update doc/create-a-custom-rule.md
floroz Apr 7, 2021
e448790
Update doc/create-a-custom-rule.md
floroz Apr 7, 2021
d81eddb
Update doc/create-a-custom-rule.md
floroz Apr 7, 2021
fc0e816
Update doc/create-a-custom-rule.md
floroz Apr 7, 2021
65ae963
Update doc/create-a-custom-rule.md
floroz Apr 7, 2021
e6c7817
Update doc/create-a-custom-rule.md
floroz Apr 7, 2021
dab9d17
Update doc/create-a-custom-rule.md
floroz Apr 7, 2021
8ccbe59
Update doc/create-a-custom-rule.md
floroz Apr 7, 2021
b72285e
Update doc/create-a-custom-rule.md
floroz Apr 7, 2021
7146126
Update doc/create-a-custom-rule.md
floroz Apr 7, 2021
f494afa
Update doc/create-a-custom-rule.md
floroz Apr 7, 2021
e6692a5
Update doc/create-a-custom-rule.md
floroz Apr 7, 2021
c3e60c6
Update doc/create-a-custom-rule.md
floroz Apr 7, 2021
f34617d
Update doc/create-a-custom-rule.md
floroz Apr 7, 2021
0cc9da6
Update doc/create-a-custom-rule.md
floroz Apr 7, 2021
8b5c21b
Update doc/create-a-custom-rule.md
floroz Apr 7, 2021
88cbd99
Update doc/create-a-custom-rule.md
floroz Apr 7, 2021
885173c
Update doc/create-a-custom-rule.md
floroz Apr 7, 2021
20e9b24
Update doc/create-a-custom-rule.md
floroz Apr 7, 2021
2e495fe
Update doc/create-a-custom-rule.md
floroz Apr 7, 2021
09e0769
Update doc/create-a-custom-rule.md
floroz Apr 7, 2021
77241da
Update doc/create-a-custom-rule.md
floroz Apr 7, 2021
b2a05fd
Update doc/create-a-custom-rule.md
floroz Apr 7, 2021
015ca30
Update doc/create-a-custom-rule.md
floroz Apr 7, 2021
e5c31de
Update doc/create-a-custom-rule.md
floroz Apr 7, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
242 changes: 242 additions & 0 deletions doc/create-a-custom-rule.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
# Create a custom `remark-lint` rule

This guide is part of [a step-by-step tutorial](https://dev.to/floroz/how-to-create-a-custom-lint-rule-for-markdown-and-mdx-using-remark-and-eslint-2jim), and will help you getting started to create your first linting plugin for `remark`.

## Contents

* [Set up the project](#set-up-the-project)
* [Set up remark](#set-up-remark)
* [The `no-invalid-gif` rule](#the-no-invalid-gif-rule)
* [Create the custom rule](#create-the-custom-rule)
* [Rule arguments](#rule-arguments)
* [Rule implementation](#rule-implementation)
* [Import the rule in your remark config](#import-the-rule-in-your-remark-config)
* [Apply the rule on the Markdown file](#apply-the-rule-on-the-markdown-file)

## Set up the project

Create a new folder and enter it from your terminal. For this example I will be using Unix commands (macOS and Linux compatible).
Now we can generate our `package.json`

```sh
mkdir my-custom-rule

cd my-custom-rule

npm init -y
```

Now we can start installing our dependencies.

```sh
npm install remark-lint remark-cli
```

* [`remark-lint`](https://github.com/remarkjs/remark-lint): Core lint plugin
* [`remark-cli`](https://github.com/remarkjs/remark/tree/main/packages/remark-cli): Command-line interface

We will also need some utilities:

```sh
npm install unified-lint-rule unist-util-generated unist-util-visit
```

These will help us creating and managing our custom rules.

[Back to Top](#contents)

## Set up remark

With everything installed, we can now create a `.remarkrc.js` that will contain the plugins we’ll use.

For more info on configuration, see [Configuring `remark-lint`](https://github.com/remarkjs/remark-lint#configuring-remark-lint).

```sh
touch .remarkrc.js
```

```js
// .remarkrc.js

module.exports = {
plugins: [],
};
```

Then, in our `package.json`, let's add the following script, which will process all the markdown file within our project:

```json
"scripts": {
"lint": "remark ."
}
```

Let's create a `doc.md`, the markdown file we want to lint:

```sh
touch doc.md
```

...and copy/paste the following:

```markdown
## 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:

```sh
doc.md: no issues found
```

All good, the file has been processed, and because we haven't specified any plugins nor lint rule, no issues are found.

[Back to Top](#contents)

## The `no-invalid-gif` rule

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:

```markdown
![a funny cat](funny-cat.gif)
```

Because the file extension `.gif` in the image tag violates our rule.

[Back to Top](#contents)

## Create the custom 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`.

```sh
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.

```js
// rules/no-gif-allowed.js

var rule = require("unified-lint-rule");
function noGifAllowed(tree, file, options) {
// rule implementation
}
module.exports = rule("remark-lint:no-gif-allowed", noGifAllowed);
```

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:

```js
module.exports = rule("my-project-name:no-gif-allowed", noGifAllowed);
// or
module.exports = rule("my-npm-published-package:no-gif-allowed", noGifAllowed);
```

This can help you when wanting to create a group of rules under the same *namespace*.

[Back to Top](#contents)

## Rule arguments

Your rule function will receive three arguments.

```js
function noGifAllowed(tree, file, options) {}
```

* `tree` (*required*): [mdast](https://github.com/syntax-tree/mdast)
* `file` (*required*): [virtual file](https://github.com/vfile/vfile)
* `options` (*optional*): additional information passed to the rule by users

[Back to Top](#contents)

## Rule implementation

Because we will be inspecting [mdast](https://github.com/syntax-tree/mdast), which is a markdown abstract syntax tree built upon [unist](https://github.com/syntax-tree/unist), we can take advantage of the many existing [unist utilities](https://github.com/syntax-tree/unist#utilities) to inspect our tree’s nodes.

For this example, we will use [`unist-util-visit`](https://github.com/syntax-tree/unist-util-visit) to recursively inspect all the image nodes, and [`unist-util-generated`](https://github.com/syntax-tree/unist-util-generated) to ensure we are not inspecting nodes that we have generated ourselves and do not belong to the `doc.md`.

```js
const rule = require("unified-lint-rule");
const visit = require("unist-visit-util");
const generated = require("unist-util-generated");

function isValidNode(node) {
// Here we check whether the given node violates our rule.
// Implementation details are not relevant to the scope of this example.
// This is an overly simplified solution for demonstration purposes
if (node.url && typeof node.url === "string") {
return !node.url.endsWith(".gif");
}
}
function noGifAllowed(tree, file, options) {
visit(tree, "image", visitor);
function visitor(node) {
if (!generated(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 = isValidNode(node);
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 extentions. Please do not use gifs`,
node
);
}
}
}
}
module.exports = rule("remark-lint:no-gif-allowed", noGifAllowed);
```

[Back to Top](#contents)

## Import the rule in your remark config

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:

```js
// .remarkrc.js
const noGifAllowed = require("./rules/no-gif-allowed.js");

module.exports = {
plugins: [noGifAllowed],
};
```

[Back to Top](#contents)

## Apply the rule on the Markdown file

If you run `npm lint`, you should see the following message in the terminal:

```sh
5:1-5:30 warning Invalid image file extentions. Please do not use gifs no-gif-allowed remark-lint
```

**Congratulations! The rule works!**

[Back to Top](#contents)
5 changes: 5 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ powered by [plugins][remark-plugins] (such as these).
* [Rules](#rules)
* [List of presets](#list-of-presets)
* [List of external rules](#list-of-external-rules)
* [Create a custom rule](#create-a-custom-rule)
* [Security](#security)
* [Related](#related)
* [Contribute](#contribute)
Expand Down Expand Up @@ -404,6 +405,10 @@ This list is ordered based on the name without prefix, so excluding
* [`remark-lint-double-link`](https://github.com/Scrum/remark-lint-double-link)
— Ensure the same URL is not linked multiple times.

## Create a custom rule

Follow this comprehensive [tutorial](https://github.com/remarkjs/remark-lint/blob/main/doc/create-a-custom-rule.md) on how to create your own custom rule for `remark`.

## Security

Use of `remark-lint` does not mutate the tree so there are no openings for
Expand Down