UNPKG

8.62 kBMarkdownView Raw
1# Working on rules
2
3Please help us create, enhance, and debug our rules!
4
5## Add a rule
6
7You should:
8
91. Get yourself ready to [contribute code](../../CONTRIBUTING.md#code-contributions).
102. Familiarize yourself with the [conventions and patterns](../user-guide/rules/about.md) for rules.
11
12### Write the rule
13
14When writing the rule, you should:
15
16- make the rule strict by default
17- add secondary `ignore` options to make the rule more permissive
18- not include code specific to language extensions, e.g. SCSS
19
20You should make use of the:
21
22- PostCSS API
23- construct-specific parsers
24- utility functions
25
26#### PostCSS API
27
28Use the [PostCSS API](https://api.postcss.org/) to navigate and analyze the CSS syntax tree. We recommend using the `walk` iterators (e.g. `walkDecls`), rather than using `forEach` to loop through the nodes.
29
30When using array methods on nodes, e.g. `find`, `some`, `filter` etc, you should explicitly check the `type` property of the node before attempting to access other properties. For example:
31
32```js
33const hasProperty = nodes.find(
34 ({ type, prop }) => type === "decl" && prop === propertyName
35);
36```
37
38Use `node.raws` instead of `node.raw()` when accessing raw strings from the [PostCSS AST](https://astexplorer.net/#/gist/ef718daf3e03f1d200b03dc5a550ec60/c8cbe9c6809a85894cebf3fb66de46215c377f1a).
39
40#### Construct-specific parsers
41
42Depending on the rule, we also recommend using:
43
44- [postcss-value-parser](https://github.com/TrySound/postcss-value-parser)
45- [postcss-selector-parser](https://github.com/postcss/postcss-selector-parser)
46
47There are significant benefits to using these parsers instead of regular expressions or `indexOf` searches (even if they aren't always the most performant method).
48
49#### Utility functions
50
51stylelint has [utility functions](https://github.com/stylelint/stylelint/tree/master/lib/utils) that are used in existing rules and might prove useful to you, as well. Please look through those so that you know what's available. (And if you have a new function that you think might prove generally helpful, let's add it to the list!).
52
53Use the:
54
55- `validateOptions()` utility to warn users about invalid options
56- `isStandardSyntax*` utilities to ignore non-standard syntax
57
58### Add options
59
60Only add an option to a rule if it addresses a _requested_ use case to avoid polluting the tool with unused features.
61
62If your rule can accept an array as its primary option, you must designate this by setting the property `primaryOptionArray = true` on your rule function. For example:
63
64```js
65function rule(primary, secondary) {
66 return (root, result) => {
67 /* .. */
68 };
69}
70
71rule.primaryOptionArray = true;
72
73module.exports = rule;
74```
75
76There is one caveat here: If your rule accepts a primary option array, it cannot also accept a primary option object. Whenever possible, if you want your rule to accept a primary option array, you should make an array the only possibility, instead of allowing for various data structures.
77
78### Add autofix
79
80Depending on the rule, it might be possible to automatically fix the rule's violations by mutating the PostCSS AST (Abstract Syntax Tree) using the [PostCSS API](http://api.postcss.org/).
81
82Add `context` variable to rule parameters:
83
84```js
85function rule(primary, secondary, context) {
86 return (root, result) => {
87 /* .. */
88 };
89}
90```
91
92`context` is an object which could have two properties:
93
94- `fix`(boolean): If `true`, your rule can apply autofixes.
95- `newline`(string): Line-ending used in current linted file.
96
97If `context.fix` is `true`, then change `root` using PostCSS API and return early before `report()` is called.
98
99```js
100if (context.fix) {
101 // Apply fixes using PostCSS API
102 return; // Return and don't report a problem
103}
104
105report(/* .. */);
106```
107
108### Write tests
109
110Each rule must have tests that cover all patterns that:
111
112- are considered violations
113- should _not_ be considered violations
114
115Write as many as you can stand to.
116
117You should:
118
119- test errors in multiple positions, not the same place every time
120- use realistic (if simple) CSS, and avoid the use of ellipses
121- use standard CSS syntax by default, and only swap parsers when testing a specific piece of non-standard syntax
122
123#### Commonly overlooked edge-cases
124
125You should ask yourself how does your rule handle:
126
127- variables (`$sass`, `@less` or `var(--custom-property)`)?
128- CSS strings (e.g. `content: "anything goes";`)?
129- CSS comments (e.g. `/* anything goes */`)?
130- `url()` functions, including data URIs (e.g. `url(anything/goes.jpg)`)?
131- vendor prefixes (e.g. `@-webkit-keyframes name {}`)?
132- case sensitivity (e.g. `@KEYFRAMES name {}`)?
133- a pseudo-class _combined_ with a pseudo-element (e.g. `a:hover::before`)?
134- nesting (e.g. do you resolve `& a {}`, or check it as is?)?
135- whitespace and punctuation (e.g. comparing `rgb(0,0,0)` with `rgb(0, 0, 0)`)?
136
137### Write the README
138
139You should:
140
141- only use standard CSS syntax in example code and options
142- use `<!-- prettier-ignore -->` before `css` code fences
143- use "this rule" to refer to the rule, e.g. "This rule ignores ..."
144- align the arrows within the prototypical code example with the beginning of the highlighted construct
145- align the text within the prototypical code example as far to the left as possible
146
147For example:
148
149<!-- prettier-ignore -->
150```css
151 @media screen and (min-width: 768px) {}
152/** ↑ ↑
153 * These names and values */
154```
155
156When writing examples, you should use:
157
158- complete CSS patterns i.e. avoid ellipses (`...`)
159- the minimum amount of code possible to communicate the pattern, e.g. if the rule targets selectors then use an empty rule, e.g. `{}`
160- `{}`, rather than `{ }` for empty rules
161- the `a` type selector by default
162- the `@media` at-rules by default
163- the `color` property by default
164- _foo_, _bar_ and _baz_ for names, e.g. `.foo`, `#bar`, `--baz`
165
166Look at the READMEs of other rules to glean more conventional patterns.
167
168### Wire up the rule
169
170The final step is to add references to the new rule in the following places:
171
172- [The rules `index.js` file](../../lib/rules/index.js)
173- [The list of rules](../user-guide/rules/list.md)
174
175## Add an option to a rule
176
177You should:
178
1791. Get ready to [contribute code](../../CONTRIBUTING.md#code-contributions).
1802. Change the rule's validation to allow for the new option.
1813. Add new unit tests to test the option.
1824. Add (as little as possible) logic to the rule to make the tests pass.
1835. Add documentation about the new option.
184
185## Fix a bug in a rule
186
187You should:
188
1891. Get ready to [contribute code](../../CONTRIBUTING.md#code-contributions).
1902. Write failing unit tests that exemplify the bug.
1913. Fiddle with the rule until those new tests pass.
192
193## Deprecate a rule
194
195Deprecating rules doesn't happen very often. When you do, you must:
196
1971. Point the `stylelintReference` link to the specific version of the rule README on the GitHub website, so that it is always accessible.
1982. Add the appropriate meta data to mark the rule as deprecated.
199
200## Improve the performance of a rule
201
202You can run a benchmarks on any given rule with any valid config using:
203
204```shell
205npm run benchmark-rule -- ruleName ruleOptions [ruleContext]
206```
207
208If the `ruleOptions` argument is anything other than a string or a boolean, it must be valid JSON wrapped in quotation marks.
209
210```shell
211npm run benchmark-rule -- selector-combinator-space-after never
212```
213
214```shell
215npm run benchmark-rule -- selector-combinator-space-after always
216```
217
218```shell
219npm run benchmark-rule -- block-opening-brace-space-before "[\"always\", {\"ignoreAtRules\": [\"else\"]}]"
220```
221
222If the `ruleContext` argument is specified, the sames procedure would apply:
223
224```shell
225npm run benchmark-rule -- block-opening-brace-space-before "[\"always\", {\"ignoreAtRules\": [\"else\"]}]" "{\"fix\": \"true\"}"
226```
227
228The script loads Bootstrap's CSS (from its CDN) and runs it through the configured rule.
229
230It will end up printing some simple stats like this:
231
232```shell
233Warnings: 1441
234Mean: 74.17598357142856 ms
235Deviation: 16.63969674310928 ms
236```
237
238When writing new rules or refactoring existing rules, use these measurements to determine the efficiency of your code.
239
240A stylelint rule can repeat its core logic many, many times (e.g. checking every value node of every declaration in a vast CSS codebase). So it's worth paying attention to performance and doing what we can to improve it!
241
242**Improving the performance of a rule is a great way to contribute if you want a quick little project.** Try picking a rule and seeing if there's anything you can do to speed it up.
243
244Make sure you include benchmark measurements in your pull request!