The built-in rules:
- apply to standard CSS syntax only
- are generally useful; not tied to idiosyncratic patterns
- have a clear and unambiguous finished state
- have a singular purpose
- are standalone, and don't rely on another rule
- do not contain functionality that overlaps with another rule
In contrast, a plugin is a community rule that doesn't adhere to all these criteria. It might support a particular methodology or toolset, or apply to non-standard constructs and features, or be for specific use cases.
Each rule accepts a primary and an optional secondary option.
Every rule must have a primary option. For example, in:
"color-hex-case": "upper"
, the primary option is"upper"
"indentation": [2, { "except": ["block"] }]
, the primary option is2
Some rules require extra flexibility to address edge cases. These can use an optional secondary options object. For example, in:
"color-hex-case": "upper"
there is no secondary options object"indentation": [2, { "except": ["block"] }]
, the secondary options object is{ "except": ["block"] }
The most typical secondary options are "ignore": []
and "except": []
.
The "ignore"
and "except"
options accept an array of predefined keyword options, e.g. ["relative", "first-nested", "descendant"]
:
"ignore"
skips-over a particular pattern"except"
inverts the primary option for a particular pattern
Some rules accept a user-defined list of things to ignore. This takes the form of "ignore<Things>": []
, e.g. "ignoreAtRules": []
.
The ignore*
options let users ignore non-standard syntax at the configuration level. For example, the:
:global
and:local
pseudo-classes introduced in CSS Modules@debug
and@extend
at-rules introduced in SCSS
Methodologies and language extensions come and go quickly, and this approach ensures our codebase does not become littered with code for obsolete things.
Rule are consistently named, they are:
- made up of lowercase words separated by hyphens
- split into two parts
The first part describes what thing the rule applies to. The second part describes what the rule is checking.
For example:
"number-leading-zero"
// ↑ ↑
// the thing what the rule is checking
There is no first part when the rule applies to the whole stylesheet.
For example:
"no-eol-whitespace"
"indentation"
// ↑
// what the rules are checking
Rules are named to encourage explicit, rather than implicit, options. For example, color-hex-case: "upper"|"lower"
rather than color-hex-uppercase: "always"|"never"
. As color-hex-uppercase: "never"
implies always lowercase, whereas color-hex-case: "lower"
makes it explicit.
Most rules require or disallow something.
For example, whether numbers must or must not have a leading zero:
number-leading-zero
:string - "always"|"never"
"always"
- there must always be a leading zero"never"
- there must never be a leading zero
a { line-height: 0.5; }
/** ↑
* This leading zero */
However, some rules just disallow something. These rules include *-no-*
in their name.
For example, to disallow empty blocks:
block-no-empty
- blocks must not be empty
a { }
/** ↑
* Blocks like this */
Notice how it does not make sense to have an option to enforce the opposite, i.e. that every block must be empty.
*-max-*
and *-min-*
rules set a limit to something.
For example, specifying the maximum number of digits after the "." in a number:
number-max-precision
:int
a { font-size: 1.333em; }
/** ↑
* The maximum number of digits after this "." */
Whitespace rules allow you to enforce an empty line, a single space, a newline or no space in some specific part of the stylesheet.
The whitespace rules combine two sets of keywords:
before
,after
andinside
to specify where the whitespace (if any) is expectedempty-line
,space
andnewline
to specify whether a single empty line, a single space, a single newline or no space is expected there
For example, specifying if a single empty line or no space must come before all the comments in a stylesheet:
comment-empty-line-before
:string
-"always"|"never"
a {}
←
/* comment */ ↑
↑
/** ↑
* This empty line */
Additionally, some whitespace rules use an additional set of keywords:
comma
,colon
,semicolon
,opening-brace
,closing-brace
,opening-parenthesis
,closing-parenthesis
,operator
orrange-operator
are used if a specific piece of punctuation in the thing is being targeted
For example, specifying if a single space or no space must follow a comma in a function:
function-comma-space-after
:string
-"always"|"never"
a { transform: translate(1, 1) }
/** ↑
* The space after this commas */
The plural of the punctuation is used for inside
rules. For example, specifying if a single space or no space must be inside the parentheses of a function:
function-parentheses-space-inside
:string
-"always"|"never"
a { transform: translate( 1, 1 ); }
/** ↑ ↑
* The space inside these two parentheses */
Each rule is accompanied by a README in the following format:
- Rule name.
- Single-line description.
- Prototypical code example.
- Expanded description (if necessary).
- Options.
- Example patterns that are considered violations (for each option value).
- Example patterns that are not considered violations (for each option value).
- Optional options (if applicable).
The single-line description is in the form of:
- "Disallow ..." for
no
rules - "Limit ..." for
max
rules - "Require ..." for rules that accept
"always"
and"never"
options - "Specify ..." for everything else
Each rule produces violation messages in these forms:
- "Expected [something] [in some context]"
- "Unexpected [something] [in some context]"