@primer/stylelint-config 13.0.0-rc.20fd242 → 13.0.0-rc.27facf3

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/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@primer/stylelint-config",
3
- "version": "13.0.0-rc.20fd242",
3
+ "version": "13.0.0-rc.27facf3",
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
+ "main": "dist/index.cjs",
9
9
  "exports": {
10
10
  ".": {
11
- "import": "./dist/index.js",
11
+ "import": "./dist/index.mjs",
12
12
  "require": "./dist/index.cjs"
13
13
  }
14
14
  },
@@ -45,30 +45,36 @@
45
45
  "dependencies": {
46
46
  "@github/browserslist-config": "^1.0.0",
47
47
  "@primer/css": "^21.0.8",
48
- "@primer/primitives": "^7.16.0",
48
+ "@primer/primitives": "^8.2.0",
49
49
  "anymatch": "^3.1.1",
50
- "globby": "^11.0.1",
51
50
  "postcss-scss": "^4.0.2",
52
51
  "postcss-styled-syntax": "^0.6.4",
53
52
  "postcss-value-parser": "^4.0.2",
54
53
  "string.prototype.matchall": "^4.0.2",
55
54
  "stylelint": "^16.3.1",
56
55
  "stylelint-config-standard": "^36.0.0",
56
+ "stylelint-css-modules-no-global-scoped-selector": "^1.0.2",
57
57
  "stylelint-no-unsupported-browser-features": "^8.0.0",
58
58
  "stylelint-order": "^6.0.4",
59
59
  "stylelint-scss": "^6.2.0",
60
+ "stylelint-value-no-unknown-custom-properties": "^6.0.1",
60
61
  "tap-map": "^1.0.0"
61
62
  },
63
+ "overrides": {
64
+ "stylelint-css-modules-no-global-scoped-selector": {
65
+ "stylelint": "$stylelint"
66
+ }
67
+ },
62
68
  "prettier": "@github/prettier-config",
63
69
  "devDependencies": {
64
- "@changesets/changelog-github": "0.4.7",
65
- "@changesets/cli": "2.26.1",
70
+ "@changesets/changelog-github": "^0.5.0",
71
+ "@changesets/cli": "2.27.1",
66
72
  "@github/prettier-config": "^0.0.6",
67
73
  "@rollup/plugin-commonjs": "^25.0.7",
68
74
  "@rollup/plugin-json": "^6.1.0",
69
75
  "@rollup/plugin-node-resolve": "^15.2.3",
70
76
  "@typescript-eslint/parser": "^7.7.0",
71
- "dedent": "1.5.1",
77
+ "dedent": "^1.5.3",
72
78
  "eslint": "^8.0.1",
73
79
  "eslint-plugin-github": "^4.10.2",
74
80
  "eslint-plugin-jest": "^28.2.0",
package/plugins/README.md CHANGED
@@ -7,18 +7,12 @@ 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)
18
13
  - [`primer/borders`](#primerborders)
19
14
  - [`primer/box-shadow`](#primerbox-shadow)
20
15
  - [`primer/responsive-widths`](#primerresponsive-widths)
21
- - [`primer/utilities`](#primerutilities)
22
16
  - [Variable rules](#variable-rules)
23
17
  - [Variable rule options](#variable-rule-options)
24
18
 
@@ -33,131 +27,7 @@ If you're _not_ using or extending `@primer/stylelint-config`, you can still ref
33
27
  ```js
34
28
  // stylelint.config.js
35
29
  module.exports = {
36
- plugins: ['@primer/stylelint-config/plugins/no-override', '@primer/stylelint-config/plugins/no-unused-vars']
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)
30
+ plugins: ['@primer/stylelint-config/plugins/colors']
161
31
  }
162
32
  ```
163
33
 
@@ -213,31 +83,6 @@ This [variable rule](#variable-rules) enforces the use of `$box-shadow*` variabl
213
83
 
214
84
  This plugin checks for `width` and `min-width` declarations that use a value less than the minimum browser size. `320px`
215
85
 
216
- ## `primer/utilities`
217
-
218
- Checks for selectors with single declarations that can be replaced with [primer/css utilities](https://primer.style/css/utilities/).
219
-
220
- ```scss
221
- .foo {
222
- color: var(--color-fg-default);
223
- }
224
- /** ↑
225
- * FAIL: --color-fg-default can be replaced with .color-fg-default */
226
-
227
- .foo {
228
- color: #custom;
229
- }
230
- /** ↑
231
- * OK: Color value doesn't match a utility. */
232
-
233
- .foo {
234
- color: var(--color-fg-default);
235
- padding: 0;
236
- }
237
- /** ↑
238
- * OK: Has more than one declaration, not considered */
239
- ```
240
-
241
86
  ## Variable rules
242
87
 
243
88
  Variable rules are created using a general-purpose helper that can validate constraints for matching CSS properties and values. In general, the Primer CSS variable rules enforce two basic principles for custom CSS:
@@ -377,5 +222,4 @@ module.exports = {
377
222
  - `disableFix` is a boolean that can disable auto-fixing of this rule when running `stylelint --fix` to auto-fix other rules.
378
223
 
379
224
  [primer css]: https://primer.style/css
380
- [globby]: https://www.npmjs.com/package/globby
381
225
  [glob patterns]: http://tldp.org/LDP/GNU-Linux-Tools-Summary/html/x11655.htm
@@ -1,64 +1,197 @@
1
- import {createVariableRule} from './lib/variable-rules.js'
2
-
3
- export default createVariableRule(
4
- 'primer/borders',
5
- {
6
- border: {
7
- expects: 'a border variable',
8
- props: 'border{,-top,-right,-bottom,-left}',
9
- values: ['$border', 'none', '0'],
10
- components: ['border-width', 'border-style', 'border-color'],
11
- replacements: {
12
- // because shorthand border properties ¯\_(ツ)_/¯
13
- '$border-width $border-style $border-gray': '$border',
14
- '$border-width $border-gray $border-style': '$border',
15
- '$border-style $border-width $border-gray': '$border',
16
- '$border-style $border-gray $border-width': '$border',
17
- '$border-gray $border-width $border-style': '$border',
18
- '$border-gray $border-style $border-width': '$border',
19
- '$border-width $border-style $border-color': '$border',
20
- '$border-width $border-color $border-style': '$border',
21
- '$border-style $border-width $border-color': '$border',
22
- '$border-style $border-color $border-width': '$border',
23
- '$border-color $border-width $border-style': '$border',
24
- '$border-color $border-style $border-width': '$border',
25
- },
26
- },
27
- 'border color': {
28
- expects: 'a border color variable',
29
- props: 'border{,-top,-right,-bottom,-left}-color',
30
- values: [
31
- '$border-*',
32
- 'transparent',
33
- 'currentColor',
34
- // Match variables in any of the following formats: --color-border-*, --color-*-border-*, --color-*-border, --borderColor-, *borderColor*
35
- /var\(--color-(.+-)*border(-.+)*\)/,
36
- /var\(--color-[^)]+\)/,
37
- /var\(--borderColor-[^)]+\)/,
38
- /var\((.+-)*borderColor(-.+)*\)/,
39
- ],
40
- replacements: {
41
- '$border-gray': '$border-color',
42
- },
43
- },
44
- 'border style': {
45
- expects: 'a border style variable',
46
- props: 'border{,-top,-right,-bottom,-left}-style',
47
- values: ['$border-style', 'none'],
48
- },
49
- 'border width': {
50
- expects: 'a border width variable',
51
- props: 'border{,-top,-right,-bottom,-left}-width',
52
- values: ['$border-width*', '0'],
53
- },
54
- 'border radius': {
55
- expects: 'a border radius variable',
56
- props: 'border{,-{top,bottom}-{left,right}}-radius',
57
- values: ['$border-radius', '0', '50%', 'inherit'],
58
- replacements: {
59
- '100%': '50%',
60
- },
61
- },
1
+ import stylelint from 'stylelint'
2
+ import declarationValueIndex from 'stylelint/lib/utils/declarationValueIndex.cjs'
3
+ import valueParser from 'postcss-value-parser'
4
+ import {walkGroups, primitivesVariables} from './lib/utils.js'
5
+
6
+ const {
7
+ createPlugin,
8
+ utils: {report, ruleMessages, validateOptions},
9
+ } = stylelint
10
+
11
+ export const ruleName = 'primer/borders'
12
+ export const messages = ruleMessages(ruleName, {
13
+ rejected: (value, replacement, propName) => {
14
+ if (propName && propName.includes('radius') && value.includes('borderWidth')) {
15
+ return `Border radius variables can not be used for border widths`
16
+ }
17
+
18
+ if ((propName && propName.includes('width')) || (borderShorthand(propName) && value.includes('borderRadius'))) {
19
+ return `Border width variables can not be used for border radii`
20
+ }
21
+
22
+ if (!replacement) {
23
+ return `Please use a Primer border variable instead of '${value}'. Consult the primer docs for a suitable replacement. https://primer.style/foundations/primitives/size#border`
24
+ }
25
+
26
+ return `Please replace '${value}' with a Primer border variable '${replacement['name']}'. https://primer.style/foundations/primitives/size#border`
62
27
  },
63
- 'https://primer.style/css/utilities/borders',
64
- )
28
+ })
29
+
30
+ const variables = primitivesVariables('border')
31
+ const sizes = []
32
+ const radii = []
33
+
34
+ // Props that we want to check
35
+ const propList = ['border', 'border-width', 'border-radius']
36
+ // Values that we want to ignore
37
+ const valueList = ['${']
38
+
39
+ const borderShorthand = prop =>
40
+ /^border(-(top|right|bottom|left|block-start|block-end|inline-start|inline-end))?$/.test(prop)
41
+
42
+ for (const variable of variables) {
43
+ const name = variable['name']
44
+
45
+ if (name.includes('borderWidth')) {
46
+ const value = variable['values']
47
+ .pop()
48
+ .replace(/max|\(|\)/g, '')
49
+ .split(',')[0]
50
+ sizes.push({
51
+ name,
52
+ values: [value],
53
+ })
54
+ }
55
+
56
+ if (name.includes('borderRadius')) {
57
+ radii.push(variable)
58
+ }
59
+ }
60
+
61
+ /** @type {import('stylelint').Rule} */
62
+ const ruleFunction = (primary, secondaryOptions, context) => {
63
+ return (root, result) => {
64
+ const validOptions = validateOptions(result, ruleName, {
65
+ actual: primary,
66
+ possible: [true],
67
+ })
68
+
69
+ if (!validOptions) return
70
+
71
+ root.walkDecls(declNode => {
72
+ const {prop, value} = declNode
73
+
74
+ if (!propList.some(borderProp => prop.startsWith(borderProp))) return
75
+ if (/^border(-(top|right|bottom|left|block-start|block-end|inline-start|inline-end))?-color$/.test(prop)) return
76
+ if (valueList.some(valueToIgnore => value.includes(valueToIgnore))) return
77
+
78
+ const problems = []
79
+
80
+ const parsedValue = walkGroups(valueParser(value), node => {
81
+ const checkForVariable = (vars, nodeValue) =>
82
+ vars.some(variable =>
83
+ new RegExp(`${variable['name'].replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`).test(nodeValue),
84
+ )
85
+
86
+ // Only check word types. https://github.com/TrySound/postcss-value-parser#word
87
+ if (node.type !== 'word') {
88
+ return
89
+ }
90
+
91
+ // Exact values to ignore.
92
+ if (
93
+ [
94
+ '*',
95
+ '+',
96
+ '-',
97
+ '/',
98
+ '0',
99
+ 'none',
100
+ 'inherit',
101
+ 'initial',
102
+ 'revert',
103
+ 'revert-layer',
104
+ 'unset',
105
+ 'solid',
106
+ 'dashed',
107
+ 'dotted',
108
+ 'transparent',
109
+ ].includes(node.value)
110
+ ) {
111
+ return
112
+ }
113
+
114
+ const valueUnit = valueParser.unit(node.value)
115
+
116
+ if (valueUnit && (valueUnit.unit === '' || !/^-?[0-9.]+$/.test(valueUnit.number))) {
117
+ return
118
+ }
119
+
120
+ // Skip if the value unit isn't a supported unit.
121
+ if (valueUnit && !['px', 'rem', 'em'].includes(valueUnit.unit)) {
122
+ return
123
+ }
124
+
125
+ // if we're looking at the border property that sets color in shorthand, don't bother checking the color
126
+ if (
127
+ // using border shorthand
128
+ borderShorthand(prop) &&
129
+ // includes a color as a third space-separated value
130
+ value.split(' ').length > 2 &&
131
+ // the color in the third space-separated value includes `node.value`
132
+ value
133
+ .split(' ')
134
+ .slice(2)
135
+ .some(color => color.includes(node.value))
136
+ ) {
137
+ return
138
+ }
139
+
140
+ // If the variable is found in the value, skip it.
141
+ if (prop.includes('width') || borderShorthand(prop)) {
142
+ if (checkForVariable(sizes, node.value)) {
143
+ return
144
+ }
145
+ }
146
+
147
+ if (prop.includes('radius')) {
148
+ if (checkForVariable(radii, node.value)) {
149
+ return
150
+ }
151
+ }
152
+
153
+ const replacement = (prop.includes('radius') ? radii : sizes).find(variable =>
154
+ variable.values.includes(node.value.replace('-', '')),
155
+ )
156
+ const fixable = replacement && valueUnit && !valueUnit.number.includes('-')
157
+
158
+ if (fixable && context.fix) {
159
+ node.value = node.value.replace(node.value, `var(${replacement['name']})`)
160
+ } else {
161
+ problems.push({
162
+ index: declarationValueIndex(declNode) + node.sourceIndex,
163
+ endIndex: declarationValueIndex(declNode) + node.sourceIndex + node.value.length,
164
+ message: messages.rejected(node.value, replacement, prop),
165
+ })
166
+ }
167
+
168
+ return
169
+ })
170
+
171
+ if (context.fix) {
172
+ declNode.value = parsedValue.toString()
173
+ }
174
+
175
+ if (problems.length) {
176
+ for (const err of problems) {
177
+ report({
178
+ index: err.index,
179
+ endIndex: err.endIndex,
180
+ message: err.message,
181
+ node: declNode,
182
+ result,
183
+ ruleName,
184
+ })
185
+ }
186
+ }
187
+ })
188
+ }
189
+ }
190
+
191
+ ruleFunction.ruleName = ruleName
192
+ ruleFunction.messages = messages
193
+ ruleFunction.meta = {
194
+ fixable: true,
195
+ }
196
+
197
+ export default createPlugin(ruleName, ruleFunction)
@@ -0,0 +1,45 @@
1
+ import {createRequire} from 'node:module'
2
+
3
+ const require = createRequire(import.meta.url)
4
+
5
+ export function primitivesVariables(type) {
6
+ const variables = []
7
+
8
+ const files = []
9
+ switch (type) {
10
+ case 'spacing':
11
+ files.push('base/size/size.json')
12
+ break
13
+ case 'border':
14
+ files.push('functional/size/border.json')
15
+ break
16
+ }
17
+
18
+ for (const file of files) {
19
+ // eslint-disable-next-line import/no-dynamic-require
20
+ const data = require(`@primer/primitives/dist/styleLint/${file}`)
21
+
22
+ for (const key of Object.keys(data)) {
23
+ const size = data[key]
24
+ const values = typeof size['value'] === 'string' ? [size['value']] : size['value']
25
+
26
+ variables.push({
27
+ name: `--${size['name']}`,
28
+ values,
29
+ })
30
+ }
31
+ }
32
+
33
+ return variables
34
+ }
35
+
36
+ export function walkGroups(root, validate) {
37
+ for (const node of root.nodes) {
38
+ if (node.type === 'function') {
39
+ walkGroups(node, validate)
40
+ } else {
41
+ validate(node)
42
+ }
43
+ }
44
+ return root
45
+ }