Skip to content

Commit 9e64b86

Browse files
committed
Change to archive old arbitrary plugin guide
1 parent 349fdb6 commit 9e64b86

File tree

3 files changed

+20
-352
lines changed

3 files changed

+20
-352
lines changed

doc/learn/create-a-plugin.md

Lines changed: 9 additions & 349 deletions
Original file line numberDiff line numberDiff line change
@@ -1,360 +1,20 @@
11
---
2+
archive: true
23
authorGithub: wooorm
34
authorTwitter: wooorm
45
author: Titus Wormer
5-
description: Guide that shows how to create a (retext) plugin
6+
description: Guide that shows how to create a plugin
67
group: guide
7-
modified: 2024-08-06
8+
modified: 2024-08-13
89
published: 2017-05-03
910
tags:
1011
- plugin
11-
- retext
1212
title: Create a plugin
1313
---
1414

15-
## Creating a plugin with unified
16-
17-
This guide shows how to create a plugin for retext that checks the amount of
18-
spaces between sentences.
19-
The concepts here apply to the other syntaxes of unified as well.
20-
21-
> Stuck?
22-
> Have an idea for another guide?
23-
> See [`support.md`][support].
24-
25-
### Contents
26-
27-
* [Plugin basics](#plugin-basics)
28-
* [Case](#case)
29-
* [Setting up](#setting-up)
30-
* [Plugin](#plugin)
31-
* [Further exercises](#further-exercises)
32-
33-
### Plugin basics
34-
35-
A unified plugin changes the way the applied-on processor works, in several
36-
ways.
37-
In this guide we’ll review how to inspect syntax trees.
38-
39-
Plugins can contain two parts: an **attacher**, which is a function that is
40-
called when someone calls `.use`, and a **transformer**, which is an optional
41-
function called each time a file is processed with a syntax tree and a virtual
42-
file.
43-
44-
In this case, we want to check the syntax tree of each processed file, so we do
45-
specify a transformer.
46-
47-
Now you know the basics of plugins in unified.
48-
On to our case!
49-
50-
### Case
51-
52-
Before we start, let’s first outline what we want to make.
53-
Say we have the following text file:
54-
55-
```markdown
56-
One sentence. Two sentences.
57-
58-
One sentence. Two sentences.
59-
```
60-
61-
We want to get a warning for the second paragraph, saying that one space instead
62-
of two spaces should be used.
63-
64-
In the next step we’ll write the code to use our plugin.
65-
66-
### Setting up
67-
68-
Let’s set up a project.
69-
Create a folder, `example`, enter it, and initialize a new project:
70-
71-
```sh
72-
mkdir example
73-
cd example
74-
npm init -y
75-
```
76-
77-
Then make sure the project is a module, so that `import` and `export` work,
78-
by changing `package.json`:
79-
80-
```diff
81-
--- a/package.json
82-
+++ b/package.json
83-
@@ -1,6 +1,7 @@
84-
{
85-
"name": "example",
86-
"version": "1.0.0",
87-
+ "type": "module",
88-
"main": "index.js",
89-
"scripts": {
90-
"test": "echo \"Error: no test specified\" && exit 1"
91-
```
92-
93-
Make sure `example.md` exists with:
94-
95-
```markdown
96-
One sentence. Two sentences.
97-
98-
One sentence. Two sentences.
99-
```
100-
101-
Now, let’s create an `example.js` file that will process our text file and
102-
report any found problems.
103-
104-
```js twoslash
105-
// @filename: plugin.d.ts
106-
import type {Root} from 'nlcst'
107-
import type {VFile} from 'vfile'
108-
export default function retextSentenceSpacing(): (tree: Root, file: VFile) => undefined;
109-
// @filename: example.js
110-
/// <reference types="node" />
111-
// ---cut---
112-
import fs from 'node:fs/promises'
113-
import {retext} from 'retext'
114-
import {reporter} from 'vfile-reporter'
115-
import retextSentenceSpacing from './plugin.js'
116-
117-
const document = await fs.readFile('example.md', 'utf8')
118-
119-
const file = await retext()
120-
.use(retextSentenceSpacing)
121-
.process(document)
122-
123-
console.error(reporter(file))
124-
```
125-
126-
> Don’t forget to `npm install retext vfile-reporter`!
127-
128-
If you read the guide on [using unified][use], you’ll see some familiar
129-
statements.
130-
First, we load dependencies, then we read the file in.
131-
We process that file with the plugin we’ll create in a second, and finally we
132-
report either a fatal error or any found linting messages.
133-
134-
Note that we directly depend on retext.
135-
This is a package that exposes a unified processor, and comes with the parser
136-
and compiler attached.
137-
138-
When running our example (it doesn’t work yet though) we want to see a message
139-
for the second paragraph, saying that one space instead of two spaces should be
140-
used.
141-
142-
Now we’ve got everything set up except for the plugin itself.
143-
We’ll do that in the next section.
144-
145-
### Plugin
146-
147-
As we read in Plugin Basics, we’ll need a plugin, and for our case also a
148-
transformer.
149-
Let’s create them in our plugin file `plugin.js`:
150-
151-
```js twoslash
152-
/**
153-
* @import {Root} from 'nlcst'
154-
* @import {VFile} from 'vfile'
155-
*/
156-
157-
export default function retextSentenceSpacing() {
158-
/**
159-
* @param {Root} tree
160-
* @param {VFile} file
161-
* @return {undefined}
162-
*/
163-
return function (tree, file) {
164-
}
165-
}
166-
```
167-
168-
First things first, we need to check `tree` for a pattern.
169-
We can use a utility to help us to recursively walk our tree, namely
170-
[`unist-util-visit`][visit].
171-
Let’s add that.
172-
173-
```diff
174-
--- a/plugin.js
175-
+++ b/plugin.js
176-
@@ -3,6 +3,8 @@
177-
* @import {VFile} from 'vfile'
178-
*/
179-
180-
+import {visit} from 'unist-util-visit'
181-
+
182-
export default function retextSentenceSpacing() {
183-
/**
184-
* @param {Root} tree
185-
@@ -10,5 +12,8 @@ export default function retextSentenceSpacing() {
186-
* @return {undefined}
187-
*/
188-
return function (tree, file) {
189-
+ visit(tree, 'ParagraphNode', function (node) {
190-
+ console.log(node)
191-
+ })
192-
}
193-
}
194-
```
195-
196-
> Don’t forget to `npm install unist-util-visit`.
197-
198-
If we now run our example with Node.js, as follows, we’ll see that visitor is
199-
called with both paragraphs in our example:
200-
201-
```sh
202-
node example.js
203-
```
204-
205-
```txt
206-
{
207-
type: 'ParagraphNode',
208-
children: [
209-
{ type: 'SentenceNode', children: [Array], position: [Object] },
210-
{ type: 'WhiteSpaceNode', value: ' ', position: [Object] },
211-
{ type: 'SentenceNode', children: [Array], position: [Object] }
212-
],
213-
position: {
214-
start: { line: 1, column: 1, offset: 0 },
215-
end: { line: 1, column: 29, offset: 28 }
216-
}
217-
}
218-
{
219-
type: 'ParagraphNode',
220-
children: [
221-
{ type: 'SentenceNode', children: [Array], position: [Object] },
222-
{ type: 'WhiteSpaceNode', value: ' ', position: [Object] },
223-
{ type: 'SentenceNode', children: [Array], position: [Object] }
224-
],
225-
position: {
226-
start: { line: 3, column: 1, offset: 30 },
227-
end: { line: 3, column: 30, offset: 59 }
228-
}
229-
}
230-
no issues found
231-
```
232-
233-
This output already shows that paragraphs contain two types of nodes:
234-
`SentenceNode` and `WhiteSpaceNode`.
235-
The latter is what we want to check, but the former is important because we only
236-
warn about whitespace between sentences in this plugin (that could be another
237-
plugin though).
238-
239-
Let’s now loop through the children of each paragraph.
240-
Only checking whitespace between sentences.
241-
We use a small utility for checking node types: [`unist-util-is`][is].
242-
243-
```diff
244-
--- a/plugin.js
245-
+++ b/plugin.js
246-
@@ -13,7 +13,23 @@ export default function retextSentenceSpacing() {
247-
*/
248-
return function (tree, file) {
249-
visit(tree, 'ParagraphNode', function (node) {
250-
- console.log(node)
251-
+ let index = -1
252-
+
253-
+ while (++index < node.children.length) {
254-
+ const previous = node.children[index - 1]
255-
+ const child = node.children[index]
256-
+ const next = node.children[index + 1]
257-
+
258-
+ if (
259-
+ previous &&
260-
+ next &&
261-
+ previous.type === 'SentenceNode' &&
262-
+ child.type === 'WhiteSpaceNode' &&
263-
+ next.type === 'SentenceNode'
264-
+ ) {
265-
+ console.log(child)
266-
+ }
267-
+ }
268-
})
269-
}
270-
}
271-
```
272-
273-
If we now run our example with Node, as follows, we’ll see that only whitespace
274-
between sentences is logged.
275-
276-
```sh
277-
node example.js
278-
```
279-
280-
```txt
281-
{
282-
type: 'WhiteSpaceNode',
283-
value: ' ',
284-
position: {
285-
start: { line: 1, column: 14, offset: 13 },
286-
end: { line: 1, column: 15, offset: 14 }
287-
}
288-
}
289-
{
290-
type: 'WhiteSpaceNode',
291-
value: ' ',
292-
position: {
293-
start: { line: 3, column: 14, offset: 43 },
294-
end: { line: 3, column: 16, offset: 45 }
295-
}
296-
}
297-
no issues found
298-
```
299-
300-
Finally, let’s add a warning for the second whitespace, as it has more
301-
characters than needed.
302-
We can use [`file.message()`][message] to associate a message with the file.
303-
304-
```diff
305-
--- a/plugin.js
306-
+++ b/plugin.js
307-
@@ -25,9 +25,15 @@ export default function retextSentenceSpacing() {
308-
next &&
309-
previous.type === 'SentenceNode' &&
310-
child.type === 'WhiteSpaceNode' &&
311-
- next.type === 'SentenceNode'
312-
+ next.type === 'SentenceNode' &&
313-
+ child.value.length !== 1
314-
) {
315-
- console.log(child)
316-
+ file.message(
317-
+ 'Unexpected `' +
318-
+ child.value.length +
319-
+ '` spaces between sentences, expected `1` space',
320-
+ child
321-
+ )
322-
}
323-
}
324-
})
325-
```
326-
327-
If we now run our example one final time, we’ll see a message for our problem!
328-
329-
```sh
330-
$ node example.js
331-
3:14-3:16 warning Unexpected `2` spaces between sentences, expected `1` space
332-
333-
⚠ 1 warning
334-
```
335-
336-
### Further exercises
337-
338-
One space between sentences isn’t for everyone.
339-
This plugin could receive the preferred amount of spaces instead of a hard-coded
340-
`1`.
341-
342-
If you want to warn for tabs or newlines between sentences, maybe create a
343-
plugin for that too?
344-
345-
If you haven’t already, check out the other articles in the
346-
[learn section][learn]!
347-
348-
<!--Definitions-->
349-
350-
[support]: https://github.com/unifiedjs/.github/blob/main/support.md
351-
352-
[visit]: https://github.com/syntax-tree/unist-util-visit
353-
354-
[is]: https://github.com/syntax-tree/unist-util-is
355-
356-
[message]: https://github.com/vfile/vfile#vfilemessagereason-position-origin
357-
358-
[learn]: /learn/
359-
360-
[use]: /learn/guide/using-unified/
15+
This guide is archived.
16+
See [“Create a retext plugin”](/learn/guide/create-a-retext-plugin/) for the
17+
current version.
18+
See also [“Create a rehype plugin”](/learn/guide/create-a-rehype-plugin/) and
19+
[“Create a remark plugin”](/learn/guide/create-a-remark-plugin/)
20+
for the other similar guides.

0 commit comments

Comments
 (0)