@primer/stylelint-config 12.4.2-rc.dce29d6 → 12.5.0-rc.59c3145

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 12.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#262](https://github.com/primer/stylelint-config/pull/262) [`28a4086`](https://github.com/primer/stylelint-config/commit/28a4086e8c781f76494c7e77b9437046a6f686a6) Thanks [@jonrohan](https://github.com/jonrohan)! - Writing a primer/utilities plugin to look for code that duplicates utilities
8
+
3
9
  ## 12.4.2
4
10
 
5
11
  ### Patch Changes
package/README.md CHANGED
@@ -40,6 +40,7 @@ Primer Stylelint Config extends the [stylelint-config-standard](https://github.c
40
40
  - [primer/borders](./plugins/#primerborders): Enforces the use of certain variables for border properties.
41
41
  - [primer/box-shadow](./plugins/#primerbox-shadow): Enforces the use of certain variables for `box-shadow`.
42
42
  - [primer/responsive-widths](./plugins/#primerresponsive-widths): Errors on `width` and `min-width` that is larger than the minimum browser size supported. `320px`
43
+ - [primer/utilities](./plugins/#primerutilities): Errors when someone writes custom CSS for a declaration that has an existing primer/css/utility.
43
44
 
44
45
  ## License
45
46
 
package/index.js CHANGED
@@ -9,17 +9,18 @@ module.exports = {
9
9
  'stylelint-no-unsupported-browser-features',
10
10
  'stylelint-order',
11
11
  'stylelint-scss',
12
- './plugins/no-override',
13
- './plugins/no-deprecated-colors',
14
- './plugins/no-unused-vars',
15
- './plugins/no-undefined-vars',
16
- './plugins/no-scale-colors',
17
12
  './plugins/borders',
18
13
  './plugins/box-shadow',
19
14
  './plugins/colors',
15
+ './plugins/no-deprecated-colors',
16
+ './plugins/no-override',
17
+ './plugins/no-scale-colors',
18
+ './plugins/no-undefined-vars',
19
+ './plugins/no-unused-vars',
20
20
  './plugins/responsive-widths',
21
21
  './plugins/spacing',
22
- './plugins/typography'
22
+ './plugins/typography',
23
+ './plugins/utilities'
23
24
  ],
24
25
  rules: {
25
26
  'alpha-value-notation': 'number',
@@ -69,6 +70,7 @@ module.exports = {
69
70
  'primer/responsive-widths': true,
70
71
  'primer/spacing': true,
71
72
  'primer/typography': true,
73
+ 'primer/utilities': null,
72
74
  'scss/at-extend-no-missing-placeholder': true,
73
75
  'scss/at-rule-no-unknown': true,
74
76
  'scss/declaration-nested-properties-no-divided-groups': true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primer/stylelint-config",
3
- "version": "12.4.2-rc.dce29d6",
3
+ "version": "12.5.0-rc.59c3145",
4
4
  "description": "Sharable stylelint config used by GitHub's CSS",
5
5
  "homepage": "http://primer.style/css/tools/linting",
6
6
  "author": "GitHub, Inc.",
@@ -39,19 +39,19 @@
39
39
  "tap-map": "^1.0.0"
40
40
  },
41
41
  "devDependencies": {
42
- "@changesets/changelog-github": "0.4.3",
42
+ "@changesets/changelog-github": "0.4.4",
43
43
  "@changesets/cli": "2.22.0",
44
44
  "@github/prettier-config": "0.0.4",
45
- "@primer/css": "^19.0.0",
45
+ "@primer/css": "^20.0.0",
46
46
  "@primer/primitives": "^7.0.1",
47
47
  "dedent": "0.7.0",
48
- "eslint": "8.11.0",
48
+ "eslint": "8.14.0",
49
49
  "eslint-plugin-github": "4.3.5",
50
- "eslint-plugin-jest": "26.1.3",
50
+ "eslint-plugin-jest": "26.1.5",
51
51
  "eslint-plugin-prettier": "4.0.0",
52
52
  "jest": "27.5.1",
53
53
  "jest-preset-stylelint": "5.0.1",
54
- "prettier": "2.6.0"
54
+ "prettier": "2.6.2"
55
55
  },
56
56
  "jest": {
57
57
  "preset": "jest-preset-stylelint",
package/plugins/README.md CHANGED
@@ -18,6 +18,7 @@ This directory contains all of our custom stylelint plugins, each of which provi
18
18
  - [`primer/borders`](#primerborders)
19
19
  - [`primer/box-shadow`](#primerbox-shadow)
20
20
  - [`primer/responsive-widths`](#primerresponsive-widths)
21
+ - [`primer/utilities`](#primerutilities)
21
22
  - [Variable rules](#variable-rules)
22
23
  - [Variable rule options](#variable-rule-options)
23
24
 
@@ -212,6 +213,31 @@ This [variable rule](#variable-rules) enforces the use of `$box-shadow*` variabl
212
213
 
213
214
  This plugin checks for `width` and `min-width` declarations that use a value less than the minimum browser size. `320px`
214
215
 
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
+
215
241
  ## Variable rules
216
242
 
217
243
  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:
@@ -0,0 +1,526 @@
1
+ // Meant as temp until we can move to primitives or css
2
+ const colorTypes = ['accent', 'success', 'attention', 'severe', 'danger', 'open', 'closed', 'done', 'sponsors']
3
+
4
+ module.exports = {
5
+ color: [
6
+ {
7
+ value: 'var(--color-fg-default)',
8
+ utilityClass: 'color-fg-default'
9
+ },
10
+ {
11
+ value: 'var(--color-fg-muted)',
12
+ utilityClass: 'color-fg-muted'
13
+ },
14
+ {
15
+ value: 'var(--color-fg-subtle)',
16
+ utilityClass: 'color-fg-subtle'
17
+ }
18
+ ].concat(
19
+ colorTypes.map(type => {
20
+ return {
21
+ value: `var(--color-${type}-fg)`,
22
+ utilityClass: `color-fg-${type}`
23
+ }
24
+ })
25
+ ),
26
+ 'background-color': [
27
+ {
28
+ value: 'var(--color-canvas-default)',
29
+ utilityClass: 'color-bg-default'
30
+ },
31
+ {
32
+ value: 'var(--color-canvas-overlay)',
33
+ utilityClass: 'color-bg-overlay'
34
+ },
35
+ {
36
+ value: 'var(--color-canvas-inset)',
37
+ utilityClass: 'color-bg-inset'
38
+ },
39
+ {
40
+ value: 'var(--color-canvas-subtle)',
41
+ utilityClass: 'color-bg-subtle'
42
+ },
43
+ {
44
+ value: 'transparent',
45
+ utilityClass: 'color-bg-transparent'
46
+ }
47
+ ]
48
+ .concat(
49
+ colorTypes.map(type => {
50
+ return {
51
+ value: `var(--color-${type}-subtle)`,
52
+ utilityClass: `color-bg-${type}`
53
+ }
54
+ })
55
+ )
56
+ .concat(
57
+ colorTypes.map(type => {
58
+ return {
59
+ value: `var(--color-${type}-emphasis)`,
60
+ utilityClass: `color-bg-${type}-emphasis`
61
+ }
62
+ })
63
+ ),
64
+ 'border-color': [
65
+ {
66
+ value: 'var(--color-border-default',
67
+ utilityClass: 'color-border-default'
68
+ },
69
+ {
70
+ value: 'var(--color-border-muted',
71
+ utilityClass: 'color-border-muted'
72
+ },
73
+ {
74
+ value: 'var(--color-border-subtle',
75
+ utilityClass: 'color-border-subtle'
76
+ }
77
+ ]
78
+ .concat(
79
+ colorTypes.map(type => {
80
+ return {
81
+ value: `var(--color-${type}-muted)`,
82
+ utilityClass: `color-border-${type}`
83
+ }
84
+ })
85
+ )
86
+ .concat(
87
+ colorTypes.map(type => {
88
+ return {
89
+ value: `var(--color-${type}-emphasis)`,
90
+ utilityClass: `color-border-${type}-emphasis`
91
+ }
92
+ })
93
+ ),
94
+ margin: Array.from(new Array(6)).map((_, i) => {
95
+ return {
96
+ value: `$spacer-${i + 1}`,
97
+ utilityClass: `m-${i + 1}`
98
+ }
99
+ }),
100
+ 'margin-top': Array.from(new Array(6)).map((_, i) => {
101
+ return {
102
+ value: `$spacer-${i + 1}`,
103
+ utilityClass: `mt-${i + 1}`
104
+ }
105
+ }),
106
+ 'margin-right': Array.from(new Array(6)).map((_, i) => {
107
+ return {
108
+ value: `$spacer-${i + 1}`,
109
+ utilityClass: `mr-${i + 1}`
110
+ }
111
+ }),
112
+ 'margin-bottom': Array.from(new Array(6)).map((_, i) => {
113
+ return {
114
+ value: `$spacer-${i + 1}`,
115
+ utilityClass: `mb-${i + 1}`
116
+ }
117
+ }),
118
+ 'margin-left': Array.from(new Array(6)).map((_, i) => {
119
+ return {
120
+ value: `$spacer-${i + 1}`,
121
+ utilityClass: `ml-${i + 1}`
122
+ }
123
+ }),
124
+ padding: Array.from(new Array(6)).map((_, i) => {
125
+ return {
126
+ value: `$spacer-${i + 1}`,
127
+ utilityClass: `p-${i + 1}`
128
+ }
129
+ }),
130
+ 'padding-top': Array.from(new Array(6)).map((_, i) => {
131
+ return {
132
+ value: `$spacer-${i + 1}`,
133
+ utilityClass: `pt-${i + 1}`
134
+ }
135
+ }),
136
+ 'padding-right': Array.from(new Array(6)).map((_, i) => {
137
+ return {
138
+ value: `$spacer-${i + 1}`,
139
+ utilityClass: `pr-${i + 1}`
140
+ }
141
+ }),
142
+ 'padding-bottom': Array.from(new Array(6)).map((_, i) => {
143
+ return {
144
+ value: `$spacer-${i + 1}`,
145
+ utilityClass: `pb-${i + 1}`
146
+ }
147
+ }),
148
+ 'padding-left': Array.from(new Array(6)).map((_, i) => {
149
+ return {
150
+ value: `$spacer-${i + 1}`,
151
+ utilityClass: `pl-${i + 1}`
152
+ }
153
+ }),
154
+ 'line-height': [
155
+ {
156
+ value: '$lh-condensed-ultra',
157
+ utilityClass: 'lh-condensed-ultra'
158
+ },
159
+ {
160
+ value: '$lh-condensed',
161
+ utilityClass: 'lh-condensed'
162
+ },
163
+ {
164
+ value: '$lh-default',
165
+ utilityClass: 'lh-default'
166
+ },
167
+ {
168
+ value: '0',
169
+ utilityClass: 'lh-0'
170
+ }
171
+ ],
172
+ 'text-align': [
173
+ {
174
+ value: 'left',
175
+ utilityClass: 'text-left'
176
+ },
177
+ {
178
+ value: 'right',
179
+ utilityClass: 'text-right'
180
+ },
181
+ {
182
+ value: 'center',
183
+ utilityClass: 'text-center'
184
+ }
185
+ ],
186
+ 'font-style': [
187
+ {
188
+ value: 'italic',
189
+ utilityClass: 'text-italic'
190
+ }
191
+ ],
192
+ 'text-transform': [
193
+ {
194
+ value: 'uppercase',
195
+ utilityClass: 'text-uppercase'
196
+ }
197
+ ],
198
+ 'text-decoration': [
199
+ {
200
+ value: 'underline',
201
+ utilityClass: 'text-underline'
202
+ },
203
+ {
204
+ value: 'none',
205
+ utilityClass: 'no-underline'
206
+ }
207
+ ],
208
+ 'white-space': [
209
+ {
210
+ value: 'nowrap',
211
+ utilityClass: 'no-wrap'
212
+ },
213
+ {
214
+ value: 'normal',
215
+ utilityClass: 'ws-normal'
216
+ }
217
+ ],
218
+ 'word-break': [
219
+ {
220
+ value: 'break-all',
221
+ utilityClass: 'wb-break-all'
222
+ }
223
+ ],
224
+ width: [
225
+ {
226
+ value: '100%',
227
+ utilityClass: 'width-full'
228
+ },
229
+ {
230
+ value: 'auto%',
231
+ utilityClass: 'width-auto'
232
+ }
233
+ ],
234
+ overflow: [
235
+ {
236
+ value: 'visible',
237
+ utilityClass: 'overflow-visible'
238
+ },
239
+ {
240
+ value: 'hidden',
241
+ utilityClass: 'overflow-hidden'
242
+ },
243
+ {
244
+ value: 'auto',
245
+ utilityClass: 'overflow-auto'
246
+ },
247
+ {
248
+ value: 'scroll',
249
+ utilityClass: 'overflow-scroll'
250
+ }
251
+ ],
252
+ 'overflow-x': [
253
+ {
254
+ value: 'visible',
255
+ utilityClass: 'overflow-x-visible'
256
+ },
257
+ {
258
+ value: 'hidden',
259
+ utilityClass: 'overflow-x-hidden'
260
+ },
261
+ {
262
+ value: 'auto',
263
+ utilityClass: 'overflow-x-auto'
264
+ },
265
+ {
266
+ value: 'scroll',
267
+ utilityClass: 'overflow-x-scroll'
268
+ }
269
+ ],
270
+ 'overflow-y': [
271
+ {
272
+ value: 'visible',
273
+ utilityClass: 'overflow-y-visible'
274
+ },
275
+ {
276
+ value: 'hidden',
277
+ utilityClass: 'overflow-y-hidden'
278
+ },
279
+ {
280
+ value: 'auto',
281
+ utilityClass: 'overflow-y-auto'
282
+ },
283
+ {
284
+ value: 'scroll',
285
+ utilityClass: 'overflow-y-scroll'
286
+ }
287
+ ],
288
+ height: [
289
+ {
290
+ value: '100%',
291
+ utilityClass: 'height-full'
292
+ }
293
+ ],
294
+ 'max-width': [
295
+ {
296
+ value: '100%',
297
+ utilityClass: 'width-fit'
298
+ }
299
+ ],
300
+ 'max-height': [
301
+ {
302
+ value: '100%',
303
+ utilityClass: 'height-fit'
304
+ }
305
+ ],
306
+ 'min-width': [
307
+ {
308
+ value: '0',
309
+ utilityClass: 'min-width-0'
310
+ }
311
+ ],
312
+ float: [
313
+ {
314
+ value: 'left',
315
+ utilityClass: 'float-left'
316
+ },
317
+ {
318
+ value: 'right',
319
+ utilityClass: 'float-right'
320
+ },
321
+ {
322
+ value: 'none',
323
+ utilityClass: 'float-none'
324
+ }
325
+ ],
326
+ 'list-style': [
327
+ {
328
+ value: 'none',
329
+ utilityClass: 'list-style-none'
330
+ }
331
+ ],
332
+ 'user-select': [
333
+ {
334
+ value: 'none',
335
+ utilityClass: 'user-select-none'
336
+ }
337
+ ],
338
+ visibility: [
339
+ {
340
+ value: 'hidden',
341
+ utilityClass: 'v-hidden'
342
+ },
343
+ {
344
+ value: 'visible',
345
+ utilityClass: 'v-visible'
346
+ }
347
+ ],
348
+ 'vertical-align': [
349
+ {
350
+ value: 'middle',
351
+ utilityClass: 'v-align-middle'
352
+ },
353
+ {
354
+ value: 'top',
355
+ utilityClass: 'v-align-top'
356
+ },
357
+ {
358
+ value: 'bottom',
359
+ utilityClass: 'v-align-bottom'
360
+ },
361
+ {
362
+ value: 'text-top',
363
+ utilityClass: 'v-align-text-top'
364
+ },
365
+ {
366
+ value: 'text-bottom',
367
+ utilityClass: 'v-align-text-bottom'
368
+ },
369
+ {
370
+ value: 'text-baseline',
371
+ utilityClass: 'v-align-baseline'
372
+ }
373
+ ],
374
+ 'font-weight': [
375
+ {
376
+ value: '$font-weight-normal',
377
+ utilityClass: 'text-normal'
378
+ },
379
+ {
380
+ value: '$font-weight-bold',
381
+ utilityClass: 'text-bold'
382
+ },
383
+ {
384
+ value: '$font-weight-semibold',
385
+ utilityClass: 'text-semibold'
386
+ },
387
+ {
388
+ value: '$font-weight-light',
389
+ utilityClass: 'text-light'
390
+ }
391
+ ],
392
+ top: [
393
+ {
394
+ value: '0',
395
+ utilityClass: 'top-0'
396
+ },
397
+ {
398
+ value: 'auto',
399
+ utilityClass: 'top-auto'
400
+ }
401
+ ],
402
+ right: [
403
+ {
404
+ value: '0',
405
+ utilityClass: 'right-0'
406
+ },
407
+ {
408
+ value: 'auto',
409
+ utilityClass: 'right-auto'
410
+ }
411
+ ],
412
+ bottom: [
413
+ {
414
+ value: '0',
415
+ utilityClass: 'bottom-0'
416
+ },
417
+ {
418
+ value: 'auto',
419
+ utilityClass: 'bottom-auto'
420
+ }
421
+ ],
422
+ left: [
423
+ {
424
+ value: '0',
425
+ utilityClass: 'left-0'
426
+ },
427
+ {
428
+ value: 'auto',
429
+ utilityClass: 'left-auto'
430
+ }
431
+ ],
432
+ position: [
433
+ {
434
+ value: 'static',
435
+ utilityClass: 'position-static'
436
+ },
437
+ {
438
+ value: 'relative',
439
+ utilityClass: 'position-relative'
440
+ },
441
+ {
442
+ value: 'absolute',
443
+ utilityClass: 'position-absolute'
444
+ },
445
+ {
446
+ value: 'fixed',
447
+ utilityClass: 'position-fixed'
448
+ },
449
+ {
450
+ value: 'sticky',
451
+ utilityClass: 'position-sticky'
452
+ }
453
+ ],
454
+ 'box-shadow': [
455
+ {
456
+ value: 'none',
457
+ utilityClass: 'box-shadow-none'
458
+ },
459
+ {
460
+ value: 'var(--color-shadow-small)',
461
+ utilityClass: 'box-shadow-small'
462
+ },
463
+ {
464
+ value: 'var(--color-shadow-medium)',
465
+ utilityClass: 'box-shadow-medium'
466
+ },
467
+ {
468
+ value: 'var(--color-shadow-large)',
469
+ utilityClass: 'box-shadow-large'
470
+ },
471
+ {
472
+ value: 'var(--color-shadow-extra-large)',
473
+ utilityClass: 'box-shadow-extra-large'
474
+ }
475
+ ],
476
+ border: [
477
+ {
478
+ value: '$border',
479
+ utilityClass: 'border'
480
+ },
481
+ {
482
+ value: '0',
483
+ utilityClass: 'border-0'
484
+ }
485
+ ],
486
+ 'border-top': [
487
+ {
488
+ value: '$border',
489
+ utilityClass: 'border-top'
490
+ },
491
+ {
492
+ value: '0',
493
+ utilityClass: 'border-top-0'
494
+ }
495
+ ],
496
+ 'border-right': [
497
+ {
498
+ value: '$border',
499
+ utilityClass: 'border-right'
500
+ },
501
+ {
502
+ value: '0',
503
+ utilityClass: 'border-right-0'
504
+ }
505
+ ],
506
+ 'border-bottom': [
507
+ {
508
+ value: '$border',
509
+ utilityClass: 'border-bottom'
510
+ },
511
+ {
512
+ value: '0',
513
+ utilityClass: 'border-bottom-0'
514
+ }
515
+ ],
516
+ 'border-left': [
517
+ {
518
+ value: '$border',
519
+ utilityClass: 'border-left'
520
+ },
521
+ {
522
+ value: '0',
523
+ utilityClass: 'border-left-0'
524
+ }
525
+ ]
526
+ }
@@ -0,0 +1,55 @@
1
+ const stylelint = require('stylelint')
2
+ const utilities = require('./lib/primer-utilities')
3
+
4
+ const ruleName = 'primer/utilities'
5
+
6
+ const messages = stylelint.utils.ruleMessages(ruleName, {
7
+ rejected: (selector, utilityClass) => {
8
+ return `Consider using the Primer utility '.${utilityClass}' instead of the selector '${selector}' in your html. https://primer.style/css/utilities`
9
+ }
10
+ })
11
+
12
+ // eslint-disable-next-line no-unused-vars
13
+ module.exports = stylelint.createPlugin(ruleName, (enabled, options = {}, context) => {
14
+ if (!enabled) {
15
+ return noop
16
+ }
17
+
18
+ const utilityReplacement = (declaration, value) => {
19
+ const declarationUtilities = utilities[declaration]
20
+ if (declarationUtilities) {
21
+ return declarationUtilities.find(utility => {
22
+ return utility.value === value
23
+ })
24
+ }
25
+ }
26
+
27
+ const lintResult = (root, result) => {
28
+ root.walkRules(rule => {
29
+ if (!/^\.[\w\-_]+$/.exec(rule.selector)) {
30
+ return false
31
+ }
32
+ const decls = rule.nodes.filter(decl => decl.type === 'decl')
33
+
34
+ if (decls.length === 1) {
35
+ const replacement = utilityReplacement(decls[0].prop, decls[0].value)
36
+ if (replacement) {
37
+ stylelint.utils.report({
38
+ index: rule.sourceIndex,
39
+ message: messages.rejected(rule.selector, replacement.utilityClass),
40
+ node: rule,
41
+ result,
42
+ ruleName
43
+ })
44
+ }
45
+ }
46
+ })
47
+ }
48
+
49
+ return lintResult
50
+ })
51
+
52
+ function noop() {}
53
+
54
+ module.exports.ruleName = ruleName
55
+ module.exports.messages = messages