UNPKG

10.2 kBMarkdownView Raw
1# Writing plugins
2
3Plugins are rules and sets of rules built by the community.
4
5We recommend your plugin adheres to [stylelint's conventions](rules.md) for:
6
7- names
8- options
9- messages
10- tests
11- docs
12
13## The anatomy of a plugin
14
15```js
16// Abbreviated example
17const stylelint = require("stylelint");
18
19const ruleName = "plugin/foo-bar";
20const messages = stylelint.utils.ruleMessages(ruleName, {
21 expected: "Expected ..."
22});
23
24module.exports = stylelint.createPlugin(ruleName, function (
25 primaryOption,
26 secondaryOptionObject
27) {
28 return function (postcssRoot, postcssResult) {
29 const validOptions = stylelint.utils.validateOptions(
30 postcssResult,
31 ruleName,
32 {
33 /* .. */
34 }
35 );
36
37 if (!validOptions) {
38 return;
39 }
40
41 // ... some logic ...
42 stylelint.utils.report({
43 /* .. */
44 });
45 };
46});
47
48module.exports.ruleName = ruleName;
49module.exports.messages = messages;
50```
51
52Your plugin's rule name must be namespaced, e.g. `your-namespace/your-rule-name`, to ensure it never clashes with the built-in rules. If your plugin provides only a single rule or you can't think of a good namespace, you can use `plugin/my-rule`. _You should document your plugin's rule name (and namespace) because users need to use them in their config._
53
54Use `stylelint.createPlugin(ruleName, ruleFunction)` to ensure that your plugin is set up properly alongside other rules.
55
56For your plugin rule to work with the [standard configuration format](../user-guide/configure.md#rules), `ruleFunction` should accept 2 arguments:
57
58- the primary option
59- optionally, a secondary options object
60
61If your plugin rule supports [autofixing](rules.md#add-autofix), then `ruleFunction` should also accept a third argument: `context`. You should try to support the `disableFix` option in your secondary options object. Within the rule, don't perform autofixing if the user has passed a `disableFix` option for your rule.
62
63`ruleFunction` should return a function that is essentially a little [PostCSS plugin](https://github.com/postcss/postcss/blob/master/docs/writing-a-plugin.md). It takes 2 arguments:
64
65- the PostCSS Root (the parsed AST)
66- the PostCSS LazyResult
67
68You'll have to [learn about the PostCSS API](https://api.postcss.org/).
69
70### Asynchronous rules
71
72You can return a `Promise` instance from your plugin function to create an asynchronous rule.
73
74```js
75// Abbreviated asynchronous example
76const stylelint = require("stylelint");
77
78const ruleName = "plugin/foo-bar-async";
79const messages = stylelint.utils.ruleMessages(ruleName, {
80 expected: "Expected ..."
81});
82
83module.exports = stylelint.createPlugin(ruleName, function (
84 primaryOption,
85 secondaryOptionObject
86) {
87 return function (postcssRoot, postcssResult) {
88 const validOptions = stylelint.utils.validateOptions(
89 postcssResult,
90 ruleName,
91 {
92 /* .. */
93 }
94 );
95
96 if (!validOptions) {
97 return;
98 }
99
100 return new Promise(function (resolve) {
101 // some async operation
102 setTimeout(function () {
103 // ... some logic ...
104 stylelint.utils.report({
105 /* .. */
106 });
107 resolve();
108 }, 1);
109 });
110 };
111});
112
113module.exports.ruleName = ruleName;
114module.exports.messages = messages;
115```
116
117## Testing
118
119You should use [`jest-preset-stylelint`](https://github.com/stylelint/jest-preset-stylelint) to test your plugin. The preset exposes a global `testRule` function that you can use to efficiently test your plugin using a schema.
120
121For example:
122
123```js
124// index.test.js
125const { messages, ruleName } = require(".");
126
127testRule({
128 plugins: ["./index.js"],
129 ruleName,
130 config: true,
131 fix: true,
132
133 accept: [
134 {
135 code: ".class {}"
136 },
137 {
138 code: ".my-class {}"
139 }
140 ],
141
142 reject: [
143 {
144 code: ".myClass {}",
145 fixed: ".my-class {}",
146 message: messages.expected(),
147 line: 1,
148 column: 1
149 }
150 ]
151});
152```
153
154However, if your plugin involves more than just checking syntax you can use stylelint directly.
155
156For example:
157
158```js
159// index.test.js
160const { lint } = require("stylelint");
161
162const config = {
163 plugins: ["./index.js"],
164 rules: {
165 "plugin/at-import-no-unresolveable": [true]
166 }
167};
168
169it("warns for unresolveable import", async () => {
170 const {
171 results: [{ warnings, parseErrors }]
172 } = await lint({
173 files: "fixtures/contains-unresolveable-import.css",
174 config
175 });
176
177 expect(parseErrors).toHaveLength(0);
178 expect(warnings).toHaveLength(1);
179
180 const [{ line, column, text }] = warnings;
181
182 expect(text).toBe(
183 "Unexpected unresolveable import (plugin/at-import-no-unresolveable)"
184 );
185 expect(line).toBe(1);
186 expect(column).toBe(1);
187});
188
189it("doesn't warn for fileless sources", async () => {
190 const {
191 results: [{ warnings, parseErrors }]
192 } = await lint({
193 code: "@import url(unknown.css)",
194 config
195 });
196 expect(parseErrors).toHaveLength(0);
197 expect(warnings).toHaveLength(0);
198});
199```
200
201Alternatively, if you don't want to use Jest you'll find more tools in [awesome stylelint](https://github.com/stylelint/awesome-stylelint#tools).
202
203## `stylelint.utils`
204
205stylelint exposes some useful utilities.
206
207### `stylelint.utils.report`
208
209Adds violations from your plugin to the list of violations that stylelint will report to the user.
210
211Use `stylelint.utils.report` to ensure your plugin respects disabled ranges and other possible future features of stylelint. _Do not use PostCSS's `node.warn()` method directly._
212
213### `stylelint.utils.ruleMessages`
214
215Tailors your messages to the format of standard stylelint rules.
216
217### `stylelint.utils.validateOptions`
218
219Validates the options for your rule.
220
221### `stylelint.utils.checkAgainstRule`
222
223Checks CSS against a standard stylelint rule _within your own rule_. This function provides power and flexibility for plugins authors who wish to modify, constrain, or extend the functionality of existing stylelint rules.
224
225It accepts an options object and a callback that is invoked with warnings from the specified rule. The options are:
226
227- `ruleName`: the name of the rule you are invoking
228- `ruleSettings`: settings for the rule you are invoking
229- `root`: the root node to run this rule against
230
231Use the warning to create a _new_ warning _from your plugin rule_ that you report with `stylelint.utils.report`.
232
233For example, imagine you want to create a plugin that runs `at-rule-no-unknown` with a built-in list of exceptions for at-rules provided by your preprocessor-of-choice:
234
235```js
236const allowableAtRules = [
237 /* .. */
238];
239
240function myPluginRule(primaryOption, secondaryOptionObject) {
241 return function (postcssRoot, postcssResult) {
242 const defaultedOptions = Object.assign({}, secondaryOptionObject, {
243 ignoreAtRules: allowableAtRules.concat(options.ignoreAtRules || [])
244 });
245
246 stylelint.utils.checkAgainstRule(
247 {
248 ruleName: "at-rule-no-unknown",
249 ruleSettings: [primaryOption, defaultedOptions],
250 root: postcssRoot
251 },
252 (warning) => {
253 stylelint.utils.report({
254 message: myMessage,
255 ruleName: myRuleName,
256 result: postcssResult,
257 node: warning.node,
258 line: warning.line,
259 column: warning.column
260 });
261 }
262 );
263 };
264}
265```
266
267## `stylelint.rules`
268
269All of the rule functions are available at `stylelint.rules`. This allows you to build on top of existing rules for your particular needs.
270
271A typical use-case is to build in more complex conditionals that the rule's options allow for. For example, maybe your codebase uses special comment directives to customize rule options for specific stylesheets. You could build a plugin that checks those directives and then runs the appropriate rules with the right options (or doesn't run them at all).
272
273All rules share a common signature. They are a function that accepts two arguments: a primary option and a secondary options object. And that functions returns a function that has the signature of a PostCSS plugin, expecting a PostCSS root and result as its arguments.
274
275Here's an example of a plugin that runs `color-hex-case` only if there is a special directive `@@check-color-hex-case` somewhere in the stylesheet:
276
277```js
278module.exports = stylelint.createPlugin(ruleName, function (expectation) {
279 const runColorHexCase = stylelint.rules["color-hex-case"](expectation);
280
281 return (root, result) => {
282 if (root.toString().indexOf("@@check-color-hex-case") === -1) {
283 return;
284 }
285
286 runColorHexCase(root, result);
287 };
288});
289```
290
291## Allow primary option arrays
292
293If your plugin can accept an array as its primary option, you must designate this by setting the property `primaryOptionArray = true` on your rule function. For more information, check out the ["Working on rules"](rules.md) doc.
294
295## External helper modules
296
297In addition to the standard parsers mentioned in the ["Working on rules"](rules.md) doc, there are other external modules used within stylelint that we recommend using. These include:
298
299- [normalize-selector](https://github.com/getify/normalize-selector): normalize CSS selectors.
300- [postcss-resolve-nested-selector](https://github.com/davidtheclark/postcss-resolve-nested-selector): given a (nested) selector in a PostCSS AST, return an array of resolved selectors.
301
302Have a look through [stylelint's internal utils](https://github.com/stylelint/stylelint/tree/master/lib/utils) and if you come across one that you need in your plugin, then please consider helping us extract it out into an external module.
303
304## Peer dependencies
305
306You should express, within the `peerDependencies` key (and **not** within the `dependencies` key) of your plugin's `package.json`, what version(s) of stylelint your plugin can be used with. This is to ensure that different versions of stylelint are not unexpectedly installed.
307
308For example, to express that your plugin can be used with stylelint versions 7 and 8:
309
310```json
311{
312 "peerDependencies": {
313 "stylelint": "^7.0.0 || ^8.0.0"
314 }
315}
316```
317
318## Plugin packs
319
320To make a single module provide multiple rules, export an array of plugin objects (rather than a single object).
321
322## Sharing plugins and plugin packs
323
324Use the `stylelint-plugin` keyword within your `package.json`.