@primer/stylelint-config 13.0.0-rc.35215da → 13.0.0-rc.7df9f82
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -4
- package/package.json +11 -6
- package/plugins/README.md +1 -131
- package/plugins/new-color-vars-have-fallback.js +0 -35
- package/plugins/no-deprecated-colors.js +0 -97
- package/plugins/no-override.js +0 -98
- package/plugins/no-scale-colors.js +0 -51
- package/plugins/no-undefined-vars.js +0 -118
- package/plugins/no-unused-vars.js +0 -96
package/README.md
CHANGED
|
@@ -30,10 +30,6 @@ Primer Stylelint Config extends the [stylelint-config-standard](https://github.c
|
|
|
30
30
|
- [stylelint-order](https://github.com/hudochenkov/stylelint-order): Order-related linting rules for stylelint. Properties must be [sorted according to this list](https://github.com/primer/stylelint-config/blob/main/property-order.js).
|
|
31
31
|
- [stylelint-scss](https://github.com/kristerkari/stylelint-scss): A collection of SCSS specific linting rules for stylelint
|
|
32
32
|
- [scss/selector-no-redundant-nesting-selector](https://github.com/kristerkari/stylelint-scss/blob/master/src/rules/selector-no-redundant-nesting-selector/README.md): Disallow redundant nesting selectors (`&`).
|
|
33
|
-
- [primer/no-override](./plugins/#primerno-override): Prohibits custom styles that target Primer CSS selectors.
|
|
34
|
-
- [primer/no-unused-vars](./plugins/#primerno-unused-vars): Warns about SCSS variables that are declared by not used in your local files.
|
|
35
|
-
- [primer/no-undefined-vars](./plugins/#primerno-undefined-vars): Prohibits usage of undefined CSS variables.
|
|
36
|
-
- [primer/no-scale-colors](./plugins/#primerno-scale-colors): Prohibits the use of [non-functional scale CSS variables](https://primer.style/css/support/color-system#color-palette)
|
|
37
33
|
- [primer/colors](./plugins/#primercolors): Enforces the use of certain color variables.
|
|
38
34
|
- [primer/spacing](./plugins/#primerspacing): Enforces the use of spacing variables for margin and padding.
|
|
39
35
|
- [primer/typography](./plugins/#primertypography): Enforces the use of typography variables for certain CSS properties.
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@primer/stylelint-config",
|
|
3
|
-
"version": "13.0.0-rc.
|
|
3
|
+
"version": "13.0.0-rc.7df9f82",
|
|
4
4
|
"description": "Sharable stylelint config used by GitHub's CSS",
|
|
5
5
|
"author": "GitHub, Inc.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"type": "module",
|
|
8
|
+
"main": "dist/index.cjs",
|
|
8
9
|
"exports": {
|
|
9
10
|
".": {
|
|
10
|
-
"import": "./dist/index.
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
11
12
|
"require": "./dist/index.cjs"
|
|
12
13
|
}
|
|
13
14
|
},
|
|
@@ -32,10 +33,12 @@
|
|
|
32
33
|
"node": ">16.0.0"
|
|
33
34
|
},
|
|
34
35
|
"scripts": {
|
|
36
|
+
"pretest": "npm run build",
|
|
35
37
|
"build": "rollup -c",
|
|
36
38
|
"clean": "rimraf dist",
|
|
37
|
-
"test": "
|
|
38
|
-
"test:
|
|
39
|
+
"test": "npm run test:jest && npm run test:stylelint",
|
|
40
|
+
"test:jest": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest --coverage false",
|
|
41
|
+
"test:stylelint": "stylelint __tests__/__fixtures__/good/",
|
|
39
42
|
"lint": "eslint .",
|
|
40
43
|
"release": "changeset publish"
|
|
41
44
|
},
|
|
@@ -44,8 +47,8 @@
|
|
|
44
47
|
"@primer/css": "^21.0.8",
|
|
45
48
|
"@primer/primitives": "^7.16.0",
|
|
46
49
|
"anymatch": "^3.1.1",
|
|
47
|
-
"globby": "^11.0.1",
|
|
48
50
|
"postcss-scss": "^4.0.2",
|
|
51
|
+
"postcss-styled-syntax": "^0.6.4",
|
|
49
52
|
"postcss-value-parser": "^4.0.2",
|
|
50
53
|
"string.prototype.matchall": "^4.0.2",
|
|
51
54
|
"stylelint": "^16.3.1",
|
|
@@ -53,6 +56,7 @@
|
|
|
53
56
|
"stylelint-no-unsupported-browser-features": "^8.0.0",
|
|
54
57
|
"stylelint-order": "^6.0.4",
|
|
55
58
|
"stylelint-scss": "^6.2.0",
|
|
59
|
+
"stylelint-value-no-unknown-custom-properties": "^6.0.1",
|
|
56
60
|
"tap-map": "^1.0.0"
|
|
57
61
|
},
|
|
58
62
|
"prettier": "@github/prettier-config",
|
|
@@ -88,7 +92,8 @@
|
|
|
88
92
|
],
|
|
89
93
|
"testPathIgnorePatterns": [
|
|
90
94
|
"/node_modules/",
|
|
91
|
-
"__tests__/utils"
|
|
95
|
+
"__tests__/utils",
|
|
96
|
+
"__tests__/__fixtures__"
|
|
92
97
|
]
|
|
93
98
|
}
|
|
94
99
|
}
|
package/plugins/README.md
CHANGED
|
@@ -7,11 +7,6 @@ This directory contains all of our custom stylelint plugins, each of which provi
|
|
|
7
7
|
- [Primer stylelint plugins](#primer-stylelint-plugins)
|
|
8
8
|
- [Rules](#rules)
|
|
9
9
|
- [Usage](#usage)
|
|
10
|
-
- [`primer/no-override`](#primerno-override)
|
|
11
|
-
- [`primer/no-unused-vars`](#primerno-unused-vars)
|
|
12
|
-
- [`primer/no-deprecated-colors`](#primerno-deprecated-colors)
|
|
13
|
-
- [`primer/no-undefined-vars`](#primerno-undefined-vars)
|
|
14
|
-
- [`primer/no-scale-colors`](#primerno-scale-colors)
|
|
15
10
|
- [`primer/colors`](#primercolors)
|
|
16
11
|
- [`primer/spacing`](#primerspacing)
|
|
17
12
|
- [`primer/typography`](#primertypography)
|
|
@@ -33,131 +28,7 @@ If you're _not_ using or extending `@primer/stylelint-config`, you can still ref
|
|
|
33
28
|
```js
|
|
34
29
|
// stylelint.config.js
|
|
35
30
|
module.exports = {
|
|
36
|
-
plugins: ['@primer/stylelint-config/plugins/
|
|
37
|
-
}
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
## `primer/no-override`
|
|
41
|
-
|
|
42
|
-
This rule prohibits "overriding" class selectors defined in [Primer CSS]. By default, it will only fail selectors that target utility classes:
|
|
43
|
-
|
|
44
|
-
```scss
|
|
45
|
-
// FAIL
|
|
46
|
-
.mt-0 {
|
|
47
|
-
/* literally anything */
|
|
48
|
-
}
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
You can further constrain overrides to exclude _any_ class selector in Primer by providing additional names in the `bundles` option:
|
|
52
|
-
|
|
53
|
-
```js
|
|
54
|
-
// stylelint.config.js
|
|
55
|
-
module.exports = {
|
|
56
|
-
// ...
|
|
57
|
-
rules: {
|
|
58
|
-
'primer/no-override': [
|
|
59
|
-
true,
|
|
60
|
-
{
|
|
61
|
-
bundles: ['utilities', core', 'product', 'marketing']
|
|
62
|
-
}
|
|
63
|
-
]
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
## `primer/no-unused-vars`
|
|
69
|
-
|
|
70
|
-
This rule helps you find SCSS variables that _may_ not be used, and can be safely deleted. It works by scanning all of the SCSS files in your project and looking for anything that appears to be either a Sass variable declaration or reference:
|
|
71
|
-
|
|
72
|
-
```scss
|
|
73
|
-
$name: value;
|
|
74
|
-
/** ↑
|
|
75
|
-
* The colon is what makes this a declaration */
|
|
76
|
-
|
|
77
|
-
/* Anything starting with a $ and followed by word chars or hyphens
|
|
78
|
-
* and _not_ followed by a colon is considered a reference: */
|
|
79
|
-
|
|
80
|
-
margin: $value;
|
|
81
|
-
/** ↑
|
|
82
|
-
* Not a colon */
|
|
83
|
-
padding: $value 1px;
|
|
84
|
-
/** ↑
|
|
85
|
-
* Not a colon */
|
|
86
|
-
|
|
87
|
-
@media screen and (max-width: $break-lg) {
|
|
88
|
-
/** ↑
|
|
89
|
-
* Also not a colon */
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
Equipped with a list of all the variable declarations and references, the linting rule walks all of the declarations in the
|
|
93
|
-
file being linted, finds any that look like declarations (using the same pattern as the project-wide scan), and generates a warning for any that have zero references in the files it's scanned.
|
|
94
|
-
|
|
95
|
-
Because there isn't any good way for a stylelint plugin to know all of the files being linted, it needs to be told where to find all of the declarations and references in its options:
|
|
96
|
-
|
|
97
|
-
- `files` is a single path, glob, or array of paths and globs, that tells the plugin which files to scan relative to the current working directory. The default is `['**/*.scss', '!node_modules']`, which tells [globby] to find all the `.scss` files recursively and ignore the `node_modules` directory.
|
|
98
|
-
|
|
99
|
-
- `variablePattern` is a regular expression that matches a single variable in either a source file string or the `prop` of a postcss Declaration node (`{type: 'decl'}`). The default matches Sass/SCSS variables: `/\$[-\w]/g`. Note that the `g` ("global") flag is _required_ to match multiple variable references on a single line.
|
|
100
|
-
|
|
101
|
-
- `verbose` is a boolean that enables chatty `console.warn()` messages telling you what the plugin found, which can aid in debugging more complicated project layouts.
|
|
102
|
-
|
|
103
|
-
## `primer/no-deprecated-colors`
|
|
104
|
-
|
|
105
|
-
This rule identifies deprecated color variables from [primer/primitives]](https://github.com/primer/primitives) deprecated.json file and suggests replacements.
|
|
106
|
-
|
|
107
|
-
```scss
|
|
108
|
-
body {
|
|
109
|
-
color: var(--color-fg-default);
|
|
110
|
-
}
|
|
111
|
-
/** ↑
|
|
112
|
-
* OK: --color-text-primary is defined */
|
|
113
|
-
|
|
114
|
-
body {
|
|
115
|
-
color: var(--color-text-primary);
|
|
116
|
-
}
|
|
117
|
-
/** ↑
|
|
118
|
-
* FAIL: --color-text-primary is deprecated. */
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
## `primer/no-undefined-vars`
|
|
122
|
-
|
|
123
|
-
This rule prohibits any usages of undefined CSS variables.
|
|
124
|
-
|
|
125
|
-
```scss
|
|
126
|
-
:root {
|
|
127
|
-
--color-text-primary: #000;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
body {
|
|
131
|
-
color: var(--color-text-primary);
|
|
132
|
-
}
|
|
133
|
-
/** ↑
|
|
134
|
-
* OK: --color-text-primary is defined */
|
|
135
|
-
|
|
136
|
-
body {
|
|
137
|
-
color: var(--color-foo);
|
|
138
|
-
}
|
|
139
|
-
/** ↑
|
|
140
|
-
* FAIL: --color-foo is not defined */
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
For the purposes of this rule, a CSS variable declaration is any text starting with `--` and immediately followed by a colon.
|
|
144
|
-
|
|
145
|
-
Because there isn't a good way for a stylelint plugin to know what CSS variables are defined, it needs to be told where to look for declarations in its options:
|
|
146
|
-
|
|
147
|
-
- `files` is a single path, glob, or array of paths and globs, that tells the plugin which files (relative to the current working directory) to scan for CSS variable declarations. The default is `['**/*.scss', '!node_modules']`, which tells [globby] to find all the `.scss` files recursively and ignore the `node_modules` directory.
|
|
148
|
-
- `verbose` is a boolean that enables chatty `console.warn()` messages telling you what the plugin found, which can aid in debugging more complicated project layouts.
|
|
149
|
-
|
|
150
|
-
## `primer/no-scale-colors`
|
|
151
|
-
|
|
152
|
-
This rule prohibits the use of [non-functional scale CSS variables](https://primer.style/css/support/color-system#color-palette) like `var(--color-scale-blue-1)` in all cases except the `color-variables` mixin.
|
|
153
|
-
|
|
154
|
-
```scss
|
|
155
|
-
// Okay; using scale colors while defining new variables
|
|
156
|
-
@include color-scale-var('new-var-name', var(--color-scale-blue-1), var(--color-scale-blue-2))
|
|
157
|
-
|
|
158
|
-
// Fail; using scale colors directly as a property value
|
|
159
|
-
.selector {
|
|
160
|
-
color: var(--color-scale-blue-1)
|
|
31
|
+
plugins: ['@primer/stylelint-config/plugins/colors']
|
|
161
32
|
}
|
|
162
33
|
```
|
|
163
34
|
|
|
@@ -377,5 +248,4 @@ module.exports = {
|
|
|
377
248
|
- `disableFix` is a boolean that can disable auto-fixing of this rule when running `stylelint --fix` to auto-fix other rules.
|
|
378
249
|
|
|
379
250
|
[primer css]: https://primer.style/css
|
|
380
|
-
[globby]: https://www.npmjs.com/package/globby
|
|
381
251
|
[glob patterns]: http://tldp.org/LDP/GNU-Linux-Tools-Summary/html/x11655.htm
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import stylelint from 'stylelint'
|
|
2
|
-
import variables from './lib/new-color-css-vars-map.json' with {type: 'json'}
|
|
3
|
-
|
|
4
|
-
export const ruleName = 'primer/new-color-vars-have-fallback'
|
|
5
|
-
export const messages = stylelint.utils.ruleMessages(ruleName, {
|
|
6
|
-
expectedFallback: variable =>
|
|
7
|
-
`Expected a fallback value for CSS variable ${variable}. New color variables fallbacks, check primer.style/primitives to find the correct value`,
|
|
8
|
-
})
|
|
9
|
-
|
|
10
|
-
export default stylelint.createPlugin(ruleName, enabled => {
|
|
11
|
-
if (!enabled) {
|
|
12
|
-
return noop
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
return (root, result) => {
|
|
16
|
-
root.walkDecls(node => {
|
|
17
|
-
for (const variable of variables) {
|
|
18
|
-
if (node.value.includes(`var(${variable})`)) {
|
|
19
|
-
// Check if the declaration uses a CSS variable from the JSON
|
|
20
|
-
const match = node.value.match(new RegExp(`var\\(${variable},(.*)\\)`))
|
|
21
|
-
if (!match || match[1].trim() === '') {
|
|
22
|
-
stylelint.utils.report({
|
|
23
|
-
ruleName,
|
|
24
|
-
result,
|
|
25
|
-
node,
|
|
26
|
-
message: messages.expectedFallback(variable),
|
|
27
|
-
})
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
})
|
|
32
|
-
}
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
function noop() {}
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import stylelint from 'stylelint'
|
|
2
|
-
import matchAll from 'string.prototype.matchall'
|
|
3
|
-
import variableChecks from './lib/primitives-v8.json' with {type: 'json'}
|
|
4
|
-
export const ruleName = 'primer/no-deprecated-colors'
|
|
5
|
-
export const messages = stylelint.utils.ruleMessages(ruleName, {
|
|
6
|
-
rejected: (varName, replacement, property) => {
|
|
7
|
-
if (replacement === null) {
|
|
8
|
-
return `Variable ${varName} is deprecated for property ${property}. Please consult the primer color docs for a replacement. https://primer.style/primitives/storybook/?path=/story/migration-tables`
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
return `Variable ${varName} is deprecated for property ${property}. Please use the replacement ${replacement}.`
|
|
12
|
-
},
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
// Match CSS variable references (e.g var(--color-text-primary))
|
|
16
|
-
// eslint-disable-next-line no-useless-escape
|
|
17
|
-
const variableReferenceRegex = /var\(([^\),]+)(,.*)?\)/g
|
|
18
|
-
|
|
19
|
-
const replacedVars = {}
|
|
20
|
-
const newVars = {}
|
|
21
|
-
|
|
22
|
-
export default stylelint.createPlugin(ruleName, (enabled, options = {}, context) => {
|
|
23
|
-
const {inlineFallback = false} = options
|
|
24
|
-
|
|
25
|
-
if (!enabled) {
|
|
26
|
-
return noop
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const {verbose = false} = options
|
|
30
|
-
// eslint-disable-next-line no-console
|
|
31
|
-
const log = verbose ? (...args) => console.warn(...args) : noop
|
|
32
|
-
|
|
33
|
-
// Keep track of declarations we've already seen
|
|
34
|
-
const seen = new WeakMap()
|
|
35
|
-
|
|
36
|
-
const lintResult = (root, result) => {
|
|
37
|
-
// Walk all declarations
|
|
38
|
-
root.walk(node => {
|
|
39
|
-
if (seen.has(node)) {
|
|
40
|
-
return
|
|
41
|
-
} else {
|
|
42
|
-
seen.set(node, true)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// walk these nodes
|
|
46
|
-
if (node.type !== 'decl') {
|
|
47
|
-
return
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
for (const [, variableName] of matchAll(node.value, variableReferenceRegex)) {
|
|
51
|
-
if (variableName in variableChecks) {
|
|
52
|
-
let replacement = variableChecks[variableName]
|
|
53
|
-
if (typeof replacement === 'object') {
|
|
54
|
-
if (node.prop) {
|
|
55
|
-
for (const prop of replacement) {
|
|
56
|
-
// Check if node.prop starts with one of the props array elements
|
|
57
|
-
if (prop['props'].some(p => node.prop.startsWith(p))) {
|
|
58
|
-
replacement = prop['replacement']
|
|
59
|
-
break
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
if (typeof replacement === 'object') {
|
|
64
|
-
replacement = null
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (context.fix && replacement !== null) {
|
|
69
|
-
replacement = `${replacement}${inlineFallback ? `, var(${variableName})` : ''}`
|
|
70
|
-
replacedVars[variableName] = true
|
|
71
|
-
newVars[replacement] = true
|
|
72
|
-
if (node.type === 'atrule') {
|
|
73
|
-
node.params = node.params.replace(variableName, replacement)
|
|
74
|
-
} else {
|
|
75
|
-
node.value = node.value.replace(variableName, replacement)
|
|
76
|
-
}
|
|
77
|
-
continue
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
stylelint.utils.report({
|
|
81
|
-
message: messages.rejected(variableName, replacement, node.prop),
|
|
82
|
-
node,
|
|
83
|
-
ruleName,
|
|
84
|
-
result,
|
|
85
|
-
})
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
})
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
log(
|
|
92
|
-
`${Object.keys(replacedVars).length} deprecated variables replaced with ${Object.keys(newVars).length} variables.`,
|
|
93
|
-
)
|
|
94
|
-
return lintResult
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
function noop() {}
|
package/plugins/no-override.js
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import stylelint from 'stylelint'
|
|
2
|
-
import primerJson from '@primer/css/dist/stats/primer.json' with {type: 'json'}
|
|
3
|
-
|
|
4
|
-
const ruleName = 'primer/no-override'
|
|
5
|
-
const CLASS_PATTERN = /(\.[-\w]+)/
|
|
6
|
-
const CLASS_PATTERN_ALL = new RegExp(CLASS_PATTERN, 'g')
|
|
7
|
-
const CLASS_PATTERN_ONLY = /^\.[-\w]+(:{1,2}[-\w]+)?$/
|
|
8
|
-
|
|
9
|
-
export default stylelint.createPlugin(ruleName, (enabled, options = {}) => {
|
|
10
|
-
if (!enabled) {
|
|
11
|
-
return noop
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const {ignoreSelectors = []} = options
|
|
15
|
-
|
|
16
|
-
const isSelectorIgnored =
|
|
17
|
-
typeof ignoreSelectors === 'function'
|
|
18
|
-
? ignoreSelectors
|
|
19
|
-
: selector => {
|
|
20
|
-
return ignoreSelectors.some(pattern => {
|
|
21
|
-
return pattern instanceof RegExp ? pattern.test(selector) : selector.includes(pattern)
|
|
22
|
-
})
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// These map selectors to the bundle in which they're defined.
|
|
26
|
-
// If there's no entry for a given selector, it means that it's not defined
|
|
27
|
-
// in one of the *specified* bundles, since we're iterating over the list of
|
|
28
|
-
// bundle names in the options.
|
|
29
|
-
const immutableSelectors = new Set()
|
|
30
|
-
const immutableClassSelectors = new Set()
|
|
31
|
-
|
|
32
|
-
const selectors = primerJson.selectors.values
|
|
33
|
-
for (const selector of selectors) {
|
|
34
|
-
immutableSelectors.add(selector)
|
|
35
|
-
for (const classSelector of getClassSelectors(selector)) {
|
|
36
|
-
immutableClassSelectors.add(classSelector)
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const messages = stylelint.utils.ruleMessages(ruleName, {
|
|
41
|
-
rejected: ({rule, selector}) => {
|
|
42
|
-
const ruleSelector = collapseWhitespace(rule.selector)
|
|
43
|
-
const context = selector === rule.selector ? '' : ` in "${ruleSelector}"`
|
|
44
|
-
return `Primer CSS class "${collapseWhitespace(selector)}" should not be overridden${context}.`
|
|
45
|
-
},
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
return (root, result) => {
|
|
49
|
-
const report = subject =>
|
|
50
|
-
stylelint.utils.report({
|
|
51
|
-
message: messages.rejected(subject),
|
|
52
|
-
node: subject.rule,
|
|
53
|
-
result,
|
|
54
|
-
ruleName,
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
root.walkRules(rule => {
|
|
58
|
-
const {selector} = rule
|
|
59
|
-
if (immutableSelectors.has(selector)) {
|
|
60
|
-
if (isClassSelector(selector)) {
|
|
61
|
-
if (!isSelectorIgnored(selector)) {
|
|
62
|
-
return report({
|
|
63
|
-
rule,
|
|
64
|
-
selector,
|
|
65
|
-
})
|
|
66
|
-
}
|
|
67
|
-
} else {
|
|
68
|
-
// console.log(`not a class selector: "${selector}"`)
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
for (const classSelector of getClassSelectors(selector)) {
|
|
72
|
-
if (immutableClassSelectors.has(classSelector)) {
|
|
73
|
-
if (!isSelectorIgnored(classSelector)) {
|
|
74
|
-
return report({
|
|
75
|
-
rule,
|
|
76
|
-
selector: classSelector,
|
|
77
|
-
})
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
})
|
|
82
|
-
}
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
function getClassSelectors(selector) {
|
|
86
|
-
const match = selector.match(CLASS_PATTERN_ALL)
|
|
87
|
-
return match ? [...match] : []
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function isClassSelector(selector) {
|
|
91
|
-
return CLASS_PATTERN_ONLY.test(selector)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function collapseWhitespace(str) {
|
|
95
|
-
return str.trim().replace(/\s+/g, ' ')
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function noop() {}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import stylelint from 'stylelint'
|
|
2
|
-
import matchAll from 'string.prototype.matchall'
|
|
3
|
-
|
|
4
|
-
export const ruleName = 'primer/no-scale-colors'
|
|
5
|
-
export const messages = stylelint.utils.ruleMessages(ruleName, {
|
|
6
|
-
rejected: varName =>
|
|
7
|
-
`${varName} is a non-functional scale color and cannot be used without being wrapped in the color-variables mixin`,
|
|
8
|
-
})
|
|
9
|
-
|
|
10
|
-
// Match CSS variable references (e.g var(--color-text-primary))
|
|
11
|
-
// eslint-disable-next-line no-useless-escape
|
|
12
|
-
const variableReferenceRegex = /var\(([^\),]+)(,.*)?\)/g
|
|
13
|
-
|
|
14
|
-
export default stylelint.createPlugin(ruleName, (enabled, options = {}) => {
|
|
15
|
-
if (!enabled) {
|
|
16
|
-
return noop
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const {verbose = false} = options
|
|
20
|
-
// eslint-disable-next-line no-console
|
|
21
|
-
const log = verbose ? (...args) => console.warn(...args) : noop
|
|
22
|
-
|
|
23
|
-
// Keep track of declarations we've already seen
|
|
24
|
-
const seen = new WeakMap()
|
|
25
|
-
|
|
26
|
-
return (root, result) => {
|
|
27
|
-
root.walkRules(rule => {
|
|
28
|
-
rule.walkDecls(decl => {
|
|
29
|
-
if (seen.has(decl)) {
|
|
30
|
-
return
|
|
31
|
-
} else {
|
|
32
|
-
seen.set(decl, true)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
for (const [, variableName] of matchAll(decl.value, variableReferenceRegex)) {
|
|
36
|
-
log(`Found variable reference ${variableName}`)
|
|
37
|
-
if (variableName.match(/^--color-(scale|auto)-/)) {
|
|
38
|
-
stylelint.utils.report({
|
|
39
|
-
message: messages.rejected(variableName),
|
|
40
|
-
node: decl,
|
|
41
|
-
result,
|
|
42
|
-
ruleName,
|
|
43
|
-
})
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
})
|
|
47
|
-
})
|
|
48
|
-
}
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
function noop() {}
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
import fs from 'fs'
|
|
2
|
-
import stylelint from 'stylelint'
|
|
3
|
-
import matchAll from 'string.prototype.matchall'
|
|
4
|
-
import globby from 'globby'
|
|
5
|
-
import TapMap from 'tap-map'
|
|
6
|
-
|
|
7
|
-
export const ruleName = 'primer/no-undefined-vars'
|
|
8
|
-
export const messages = stylelint.utils.ruleMessages(ruleName, {
|
|
9
|
-
rejected: varName => `${varName} is not defined`,
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
// Match CSS variable definitions (e.g. --color-text-primary:)
|
|
13
|
-
const variableDefinitionRegex = /^\s*(--[\w|-]+):/gm
|
|
14
|
-
|
|
15
|
-
// Match CSS variables defined with the color-variables mixin
|
|
16
|
-
const colorModeVariableDefinitionRegex = /^[^/\n]*\(["']?([^'"\s,]+)["']?,\s*\(light|dark:/gm
|
|
17
|
-
|
|
18
|
-
// Match CSS variable references (e.g var(--color-text-primary))
|
|
19
|
-
// eslint-disable-next-line no-useless-escape
|
|
20
|
-
const variableReferenceRegex = /var\(([^\),]+)(,.*)?\)/g
|
|
21
|
-
|
|
22
|
-
export default stylelint.createPlugin(ruleName, (enabled, options = {}) => {
|
|
23
|
-
if (!enabled) {
|
|
24
|
-
return noop
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const {files = ['**/*.scss', '!node_modules'], verbose = false} = options
|
|
28
|
-
// eslint-disable-next-line no-console
|
|
29
|
-
const log = verbose ? (...args) => console.warn(...args) : noop
|
|
30
|
-
const globalDefinedVariables = getDefinedVariables(files, log)
|
|
31
|
-
// Keep track of declarations we've already seen
|
|
32
|
-
const seen = new WeakMap()
|
|
33
|
-
|
|
34
|
-
return (root, result) => {
|
|
35
|
-
const fileDefinedVariables = new Set()
|
|
36
|
-
|
|
37
|
-
function checkVariable(variableName, node, allowed) {
|
|
38
|
-
if (!allowed.has(variableName)) {
|
|
39
|
-
stylelint.utils.report({
|
|
40
|
-
message: messages.rejected(variableName),
|
|
41
|
-
node,
|
|
42
|
-
result,
|
|
43
|
-
ruleName,
|
|
44
|
-
})
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
root.walkAtRules(rule => {
|
|
49
|
-
if (rule.name === 'include' && rule.params.startsWith('color-variables')) {
|
|
50
|
-
const innerMatch = [...matchAll(rule.params, variableReferenceRegex)]
|
|
51
|
-
if (!innerMatch.length) {
|
|
52
|
-
return
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
for (const [, variableName] of innerMatch) {
|
|
56
|
-
checkVariable(variableName, rule, new Set([...globalDefinedVariables, ...fileDefinedVariables]))
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
root.walkRules(rule => {
|
|
62
|
-
const scopeDefinedVaribles = new Set()
|
|
63
|
-
|
|
64
|
-
rule.walkDecls(decl => {
|
|
65
|
-
// Add CSS variable declarations within the source text to the list of allowed variables
|
|
66
|
-
if (decl.prop.startsWith('--')) {
|
|
67
|
-
scopeDefinedVaribles.add(decl.prop)
|
|
68
|
-
if (decl.parent.selector === ':root' || decl.parent.selector === ':host') {
|
|
69
|
-
fileDefinedVariables.add(decl.prop)
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (seen.has(decl)) {
|
|
74
|
-
return
|
|
75
|
-
} else {
|
|
76
|
-
seen.set(decl, true)
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
for (const [, variableName] of matchAll(decl.value, variableReferenceRegex)) {
|
|
80
|
-
checkVariable(
|
|
81
|
-
variableName,
|
|
82
|
-
decl,
|
|
83
|
-
new Set([...globalDefinedVariables, ...fileDefinedVariables, ...scopeDefinedVaribles]),
|
|
84
|
-
)
|
|
85
|
-
}
|
|
86
|
-
})
|
|
87
|
-
})
|
|
88
|
-
}
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
const cwd = process.cwd()
|
|
92
|
-
const cache = new TapMap()
|
|
93
|
-
|
|
94
|
-
function getDefinedVariables(globs, log) {
|
|
95
|
-
const cacheKey = JSON.stringify({globs, cwd})
|
|
96
|
-
return cache.tap(cacheKey, () => {
|
|
97
|
-
const definedVariables = new Set()
|
|
98
|
-
|
|
99
|
-
const files = globby.sync(globs)
|
|
100
|
-
log(`Scanning ${files.length} SCSS files for CSS variables`)
|
|
101
|
-
for (const file of files) {
|
|
102
|
-
log(`==========\nLooking for CSS variable definitions in ${file}`)
|
|
103
|
-
const css = fs.readFileSync(file, 'utf-8')
|
|
104
|
-
for (const [, variableName] of matchAll(css, variableDefinitionRegex)) {
|
|
105
|
-
log(`${variableName} defined in ${file}`)
|
|
106
|
-
definedVariables.add(variableName)
|
|
107
|
-
}
|
|
108
|
-
for (const [, variableName] of matchAll(css, colorModeVariableDefinitionRegex)) {
|
|
109
|
-
log(`--color-${variableName} defined via color-variables in ${file}`)
|
|
110
|
-
definedVariables.add(`--color-${variableName}`)
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return definedVariables
|
|
115
|
-
})
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function noop() {}
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import TapMap from 'tap-map'
|
|
2
|
-
import globby from 'globby'
|
|
3
|
-
import matchAll from 'string.prototype.matchall'
|
|
4
|
-
import stylelint from 'stylelint'
|
|
5
|
-
import {readFileSync} from 'fs'
|
|
6
|
-
|
|
7
|
-
export const ruleName = 'primer/no-unused-vars'
|
|
8
|
-
|
|
9
|
-
const cwd = process.cwd()
|
|
10
|
-
const COLON = ':'
|
|
11
|
-
const SCSS_VARIABLE_PATTERN = /(\$[-\w]+)/g
|
|
12
|
-
|
|
13
|
-
export const messages = stylelint.utils.ruleMessages(ruleName, {
|
|
14
|
-
rejected: name => `The variable "${name}" is not referenced.`,
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
const cache = new TapMap()
|
|
18
|
-
|
|
19
|
-
export default stylelint.createPlugin(ruleName, (enabled, options = {}) => {
|
|
20
|
-
if (!enabled) {
|
|
21
|
-
return noop
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const {files = ['**/*.scss', '!node_modules'], variablePattern = SCSS_VARIABLE_PATTERN, verbose = false} = options
|
|
25
|
-
// eslint-disable-next-line no-console
|
|
26
|
-
const log = verbose ? (...args) => console.warn(...args) : noop
|
|
27
|
-
const cacheOptions = {files, variablePattern, cwd}
|
|
28
|
-
const {refs} = getCachedVariables(cacheOptions, log)
|
|
29
|
-
|
|
30
|
-
return (root, result) => {
|
|
31
|
-
root.walkDecls(decl => {
|
|
32
|
-
for (const [name] of matchAll(decl.prop, variablePattern)) {
|
|
33
|
-
if (!refs.has(name)) {
|
|
34
|
-
stylelint.utils.report({
|
|
35
|
-
message: messages.rejected(name),
|
|
36
|
-
node: decl,
|
|
37
|
-
result,
|
|
38
|
-
ruleName,
|
|
39
|
-
})
|
|
40
|
-
} else {
|
|
41
|
-
const path = stripCwd(decl.source.input.file)
|
|
42
|
-
log(`${name} declared in ${path} ref'd in ${pluralize(refs.get(name).size, 'file')}`)
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
})
|
|
46
|
-
}
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
function getCachedVariables(options, log) {
|
|
50
|
-
const key = JSON.stringify(options)
|
|
51
|
-
return cache.tap(key, () => {
|
|
52
|
-
const {files, variablePattern} = options
|
|
53
|
-
const decls = new TapMap()
|
|
54
|
-
const refs = new TapMap()
|
|
55
|
-
|
|
56
|
-
log(`Looking for variables in ${files} ...`)
|
|
57
|
-
for (const file of globby.sync(files)) {
|
|
58
|
-
const css = readFileSync(file, 'utf8')
|
|
59
|
-
for (const match of matchAll(css, variablePattern)) {
|
|
60
|
-
const after = css.substr(match.index + match[0].length)
|
|
61
|
-
const name = match[0]
|
|
62
|
-
if (after.startsWith(COLON)) {
|
|
63
|
-
decls.tap(name, set).add(file)
|
|
64
|
-
} else {
|
|
65
|
-
refs.tap(name, set).add(file)
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
log(`Found ${decls.size} declarations, ${pluralize(refs.size, 'reference')}.`)
|
|
70
|
-
|
|
71
|
-
for (const [name, filesList] of decls.entries()) {
|
|
72
|
-
const fileRefs = refs.get(name)
|
|
73
|
-
if (fileRefs) {
|
|
74
|
-
log(`variable "${name}" declared in ${pluralize(filesList.size, 'file')}, ref'd in ${fileRefs.size}`)
|
|
75
|
-
} else {
|
|
76
|
-
log(`[!] variable "${name}" declared in ${Array.from(filesList)[0]} is not referenced`)
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return {decls, refs}
|
|
81
|
-
})
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function noop() {}
|
|
85
|
-
|
|
86
|
-
function set() {
|
|
87
|
-
return new Set()
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function stripCwd(path) {
|
|
91
|
-
return path.startsWith(cwd) ? path.substr(cwd.length + 1) : path
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function pluralize(num, str, plural = `${str}s`) {
|
|
95
|
-
return num === 1 ? `${num} ${str}` : `${num} ${plural}`
|
|
96
|
-
}
|