245 lines
8.4 KiB
Markdown
245 lines
8.4 KiB
Markdown
# Working on rules
|
|
|
|
Please help us create, enhance, and debug our rules!
|
|
|
|
## Add a rule
|
|
|
|
You should:
|
|
|
|
1. Get yourself ready to [contribute code](../../CONTRIBUTING.md#code-contributions).
|
|
2. Familiarize yourself with the [conventions and patterns](../user-guide/rules/about.md) for rules.
|
|
|
|
### Write the rule
|
|
|
|
When writing the rule, you should:
|
|
|
|
- make the rule strict by default
|
|
- add secondary `ignore` options to make the rule more permissive
|
|
- not include code specific to language extensions, e.g. SCSS
|
|
|
|
You should make use of the:
|
|
|
|
- PostCSS API
|
|
- construct-specific parsers
|
|
- utility functions
|
|
|
|
#### PostCSS API
|
|
|
|
Use 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.
|
|
|
|
When 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:
|
|
|
|
```js
|
|
const hasProperty = nodes.find(
|
|
({ type, prop }) => type === "decl" && prop === propertyName
|
|
);
|
|
```
|
|
|
|
Use `node.raws` instead of `node.raw()` when accessing raw strings from the [PostCSS AST](https://astexplorer.net/#/gist/ef718daf3e03f1d200b03dc5a550ec60/c8cbe9c6809a85894cebf3fb66de46215c377f1a).
|
|
|
|
#### Construct-specific parsers
|
|
|
|
Depending on the rule, we also recommend using:
|
|
|
|
- [postcss-value-parser](https://github.com/TrySound/postcss-value-parser)
|
|
- [postcss-selector-parser](https://github.com/postcss/postcss-selector-parser)
|
|
|
|
There are significant benefits to using these parsers instead of regular expressions or `indexOf` searches (even if they aren't always the most performant method).
|
|
|
|
#### Utility functions
|
|
|
|
stylelint 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!).
|
|
|
|
Use the:
|
|
|
|
- `validateOptions()` utility to warn users about invalid options
|
|
- `isStandardSyntax*` utilities to ignore non-standard syntax
|
|
|
|
### Add options
|
|
|
|
Only add an option to a rule if it addresses a _requested_ use case to avoid polluting the tool with unused features.
|
|
|
|
If 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:
|
|
|
|
```js
|
|
function rule(primary, secondary) {
|
|
return (root, result) => {
|
|
/* .. */
|
|
};
|
|
}
|
|
|
|
rule.primaryOptionArray = true;
|
|
|
|
module.exports = rule;
|
|
```
|
|
|
|
There 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.
|
|
|
|
### Add autofix
|
|
|
|
Depending 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/).
|
|
|
|
Add `context` variable to rule parameters:
|
|
|
|
```js
|
|
function rule(primary, secondary, context) {
|
|
return (root, result) => {
|
|
/* .. */
|
|
};
|
|
}
|
|
```
|
|
|
|
`context` is an object which could have two properties:
|
|
|
|
- `fix`(boolean): If `true`, your rule can apply autofixes.
|
|
- `newline`(string): Line-ending used in current linted file.
|
|
|
|
If `context.fix` is `true`, then change `root` using PostCSS API and return early before `report()` is called.
|
|
|
|
```js
|
|
if (context.fix) {
|
|
// Apply fixes using PostCSS API
|
|
return; // Return and don't report a problem
|
|
}
|
|
|
|
report(/* .. */);
|
|
```
|
|
|
|
### Write tests
|
|
|
|
Each rule must have tests that cover all patterns that:
|
|
|
|
- are considered violations
|
|
- should _not_ be considered violations
|
|
|
|
Write as many as you can stand to.
|
|
|
|
You should:
|
|
|
|
- test errors in multiple positions, not the same place every time
|
|
- use realistic (if simple) CSS, and avoid the use of ellipses
|
|
- use standard CSS syntax by default, and only swap parsers when testing a specific piece of non-standard syntax
|
|
|
|
#### Commonly overlooked edge-cases
|
|
|
|
You should ask yourself how does your rule handle:
|
|
|
|
- variables (`$sass`, `@less` or `var(--custom-property)`)?
|
|
- CSS strings (e.g. `content: "anything goes";`)?
|
|
- CSS comments (e.g. `/* anything goes */`)?
|
|
- `url()` functions, including data URIs (e.g. `url(anything/goes.jpg)`)?
|
|
- vendor prefixes (e.g. `@-webkit-keyframes name {}`)?
|
|
- case sensitivity (e.g. `@KEYFRAMES name {}`)?
|
|
- a pseudo-class _combined_ with a pseudo-element (e.g. `a:hover::before`)?
|
|
- nesting (e.g. do you resolve `& a {}`, or check it as is?)?
|
|
- whitespace and punctuation (e.g. comparing `rgb(0,0,0)` with `rgb(0, 0, 0)`)?
|
|
|
|
### Write the README
|
|
|
|
You should:
|
|
|
|
- only use standard CSS syntax in example code and options
|
|
- use `<!-- prettier-ignore -->` before `css` code fences
|
|
- use "this rule" to refer to the rule, e.g. "This rule ignores ..."
|
|
- align the arrows within the prototypical code example with the beginning of the highlighted construct
|
|
- align the text within the prototypical code example as far to the left as possible
|
|
|
|
For example:
|
|
|
|
<!-- prettier-ignore -->
|
|
```css
|
|
@media screen and (min-width: 768px) {}
|
|
/** ↑ ↑
|
|
* These names and values */
|
|
```
|
|
|
|
When writing examples, you should use:
|
|
|
|
- complete CSS patterns i.e. avoid ellipses (`...`)
|
|
- the minimum amount of code possible to communicate the pattern, e.g. if the rule targets selectors then use an empty rule, e.g. `{}`
|
|
- `{}`, rather than `{ }` for empty rules
|
|
- the `a` type selector by default
|
|
- the `@media` at-rules by default
|
|
- the `color` property by default
|
|
- _foo_, _bar_ and _baz_ for names, e.g. `.foo`, `#bar`, `--baz`
|
|
|
|
Look at the READMEs of other rules to glean more conventional patterns.
|
|
|
|
### Wire up the rule
|
|
|
|
The final step is to add references to the new rule in the following places:
|
|
|
|
- [The rules `index.js` file](../../lib/rules/index.js)
|
|
- [The list of rules](../user-guide/rules/list.md)
|
|
|
|
## Add an option to a rule
|
|
|
|
You should:
|
|
|
|
1. Get ready to [contribute code](../../CONTRIBUTING.md#code-contributions).
|
|
2. Change the rule's validation to allow for the new option.
|
|
3. Add new unit tests to test the option.
|
|
4. Add (as little as possible) logic to the rule to make the tests pass.
|
|
5. Add documentation about the new option.
|
|
|
|
## Fix a bug in a rule
|
|
|
|
You should:
|
|
|
|
1. Get ready to [contribute code](../../CONTRIBUTING.md#code-contributions).
|
|
2. Write failing unit tests that exemplify the bug.
|
|
3. Fiddle with the rule until those new tests pass.
|
|
|
|
## Deprecate a rule
|
|
|
|
Deprecating rules doesn't happen very often. When you do, you must:
|
|
|
|
1. Point the `stylelintReference` link to the specific version of the rule README on the GitHub website, so that it is always accessible.
|
|
2. Add the appropriate meta data to mark the rule as deprecated.
|
|
|
|
## Improve the performance of a rule
|
|
|
|
You can run a benchmarks on any given rule with any valid config using:
|
|
|
|
```shell
|
|
npm run benchmark-rule -- ruleName ruleOptions [ruleContext]
|
|
```
|
|
|
|
If the `ruleOptions` argument is anything other than a string or a boolean, it must be valid JSON wrapped in quotation marks.
|
|
|
|
```shell
|
|
npm run benchmark-rule -- selector-combinator-space-after never
|
|
```
|
|
|
|
```shell
|
|
npm run benchmark-rule -- selector-combinator-space-after always
|
|
```
|
|
|
|
```shell
|
|
npm run benchmark-rule -- block-opening-brace-space-before "[\"always\", {\"ignoreAtRules\": [\"else\"]}]"
|
|
```
|
|
|
|
If the `ruleContext` argument is specified, the sames procedure would apply:
|
|
|
|
```shell
|
|
npm run benchmark-rule -- block-opening-brace-space-before "[\"always\", {\"ignoreAtRules\": [\"else\"]}]" "{\"fix\": \"true\"}"
|
|
```
|
|
|
|
The script loads Bootstrap's CSS (from its CDN) and runs it through the configured rule.
|
|
|
|
It will end up printing some simple stats like this:
|
|
|
|
```shell
|
|
Warnings: 1441
|
|
Mean: 74.17598357142856 ms
|
|
Deviation: 16.63969674310928 ms
|
|
```
|
|
|
|
When writing new rules or refactoring existing rules, use these measurements to determine the efficiency of your code.
|
|
|
|
A 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!
|
|
|
|
**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.
|
|
|
|
Make sure you include benchmark measurements in your pull request!
|