UNPKG

8.31 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. Use `node.raws` instead of `node.raw()` when accessing raw strings from the [PostCSS AST](https://astexplorer.net/#/gist/ef718daf3e03f1d200b03dc5a550ec60/c8cbe9c6809a85894cebf3fb66de46215c377f1a).
29
30#### Construct-specific parsers
31
32Depending on the rule, we also recommend using:
33
34- [postcss-value-parser](https://github.com/TrySound/postcss-value-parser)
35- [postcss-selector-parser](https://github.com/postcss/postcss-selector-parser)
36
37There are significant benefits to using these parsers instead of regular expressions or `indexOf` searches (even if they aren't always the most performant method).
38
39#### Utility functions
40
41stylelint 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!).
42
43Use the:
44
45- `validateOptions()` utility to warn users about invalid options
46- `isStandardSyntax*` utilities to ignore non-standard syntax
47
48### Add options
49
50Only add an option to a rule if it addresses a _requested_ use case to avoid polluting the tool with unused features.
51
52If 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:
53
54```js
55function rule(primary, secondary) {
56 return (root, result) => {
57 /* .. */
58 };
59}
60
61rule.primaryOptionArray = true;
62
63module.exports = rule;
64```
65
66There 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.
67
68### Add autofix
69
70Depending 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/).
71
72Add `context` variable to rule parameters:
73
74```js
75function rule(primary, secondary, context) {
76 return (root, result) => {
77 /* .. */
78 };
79}
80```
81
82`context` is an object which could have two properties:
83
84- `fix`(boolean): If `true`, your rule can apply autofixes.
85- `newline`(string): Line-ending used in current linted file.
86
87If `context.fix` is `true`, then change `root` using PostCSS API and return early before `report()` is called.
88
89```js
90if (context.fix) {
91 // Apply fixes using PostCSS API
92 return; // Return and don't report a problem
93}
94
95report(/* .. */);
96```
97
98### Write tests
99
100Each rule must have tests that cover all patterns that:
101
102- are considered violations
103- should _not_ be considered violations
104
105Write as many as you can stand to.
106
107You should:
108
109- test errors in multiple positions, not the same place every time
110- use realistic (if simple) CSS, and avoid the use of ellipses
111- use standard CSS syntax by default, and only swap parsers when testing a specific piece of non-standard syntax
112
113#### Commonly overlooked edge-cases
114
115You should ask yourself how does your rule handle:
116
117- variables (`$sass`, `@less` or `var(--custom-property)`)?
118- CSS strings (e.g. `content: "anything goes";`)?
119- CSS comments (e.g. `/* anything goes */`)?
120- `url()` functions, including data URIs (e.g. `url(anything/goes.jpg)`)?
121- vendor prefixes (e.g. `@-webkit-keyframes name {}`)?
122- case sensitivity (e.g. `@KEYFRAMES name {}`)?
123- a pseudo-class _combined_ with a pseudo-element (e.g. `a:hover::before`)?
124- nesting (e.g. do you resolve `& a {}`, or check it as is?)?
125- whitespace and punctuation (e.g. comparing `rgb(0,0,0)` with `rgb(0, 0, 0)`)?
126
127### Write the README
128
129You should:
130
131- only use standard CSS syntax in example code and options
132- use `<!-- prettier-ignore -->` before `css` code fences
133- use "this rule" to refer to the rule, e.g. "This rule ignores ..."
134- align the arrows within the prototypical code example with the beginning of the highlighted construct
135- align the text within the prototypical code example as far to the left as possible
136
137For example:
138
139<!-- prettier-ignore -->
140```css
141 @media screen and (min-width: 768px) {}
142/** ↑ ↑
143 * These names and values */
144```
145
146When writing examples, you should use:
147
148- complete CSS patterns i.e. avoid ellipses (`...`)
149- the minimum amount of code possible to communicate the pattern, e.g. if the rule targets selectors then use an empty rule, e.g. `{}`
150- `{}`, rather than `{ }` for empty rules
151- the `a` type selector by default
152- the `@media` at-rules by default
153- the `color` property by default
154- _foo_, _bar_ and _baz_ for names, e.g. `.foo`, `#bar`, `--baz`
155
156Look at the READMEs of other rules to glean more conventional patterns.
157
158### Wire up the rule
159
160The final step is to add references to the new rule in the following places:
161
162- [The rules `index.js` file](../../lib/rules/index.js)
163- [The list of rules](../user-guide/rules/list.md)
164
165## Add an option to a rule
166
167You should:
168
1691. Get ready to [contribute code](../../CONTRIBUTING.md#code-contributions).
1702. Change the rule's validation to allow for the new option.
1713. Add new unit tests to test the option.
1724. Add (as little as possible) logic to the rule to make the tests pass.
1735. Add documentation about the new option.
174
175## Fix a bug in a rule
176
177You should:
178
1791. Get ready to [contribute code](../../CONTRIBUTING.md#code-contributions).
1802. Write failing unit tests that exemplify the bug.
1813. Fiddle with the rule until those new tests pass.
182
183## Deprecate a rule
184
185Deprecating rules doesn't happen very often. When you do, you must:
186
1871. Point the `stylelintReference` link to the specific version of the rule README on the GitHub website, so that it is always accessible.
1882. Add the appropriate meta data to mark the rule as deprecated.
189
190## Improve the performance of a rule
191
192You can run a benchmarks on any given rule with any valid config using:
193
194```shell
195npm run benchmark-rule -- ruleName ruleOptions [ruleContext]
196```
197
198If the `ruleOptions` argument is anything other than a string or a boolean, it must be valid JSON wrapped in quotation marks.
199
200```shell
201npm run benchmark-rule -- selector-combinator-space-after never
202```
203
204```shell
205npm run benchmark-rule -- selector-combinator-space-after always
206```
207
208```shell
209npm run benchmark-rule -- block-opening-brace-space-before "[\"always\", {\"ignoreAtRules\": [\"else\"]}]"
210```
211
212If the `ruleContext` argument is specified, the sames procedure would apply:
213
214```shell
215npm run benchmark-rule -- block-opening-brace-space-before "[\"always\", {\"ignoreAtRules\": [\"else\"]}]" "{\"fix\": \"true\"}"
216```
217
218The script loads Bootstrap's CSS (from its CDN) and runs it through the configured rule.
219
220It will end up printing some simple stats like this:
221
222```shell
223Warnings: 1441
224Mean: 74.17598357142856 ms
225Deviation: 16.63969674310928 ms
226```
227
228When writing new rules or refactoring existing rules, use these measurements to determine the efficiency of your code.
229
230A 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!
231
232**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.
233
234Make sure you include benchmark measurements in your pull request!