@primer/stylelint-config 13.0.0-rc.20fd242 → 13.0.0-rc.21ca0f0
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 -5
- package/dist/index.cjs +1288 -0
- package/dist/index.mjs +1285 -0
- package/package.json +14 -8
- package/plugins/README.md +1 -157
- package/plugins/borders.js +196 -63
- package/plugins/lib/utils.js +45 -0
- package/plugins/spacing.js +66 -64
- package/plugins/lib/primer-utilities.js +0 -526
- 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/plugins/utilities.js +0 -52
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1285 @@
|
|
|
1
|
+
import browsers from '@github/browserslist-config';
|
|
2
|
+
import stylelint from 'stylelint';
|
|
3
|
+
import declarationValueIndex from 'stylelint/lib/utils/declarationValueIndex.cjs';
|
|
4
|
+
import valueParser from 'postcss-value-parser';
|
|
5
|
+
import { createRequire } from 'node:module';
|
|
6
|
+
import anymatch from 'anymatch';
|
|
7
|
+
import TapMap from 'tap-map';
|
|
8
|
+
import variables$1 from '@primer/css/dist/variables.json' assert { type: 'json' };
|
|
9
|
+
import matchAll from 'string.prototype.matchall';
|
|
10
|
+
|
|
11
|
+
var propertyOrder = [
|
|
12
|
+
'all',
|
|
13
|
+
'position',
|
|
14
|
+
'top',
|
|
15
|
+
'right',
|
|
16
|
+
'bottom',
|
|
17
|
+
'left',
|
|
18
|
+
'z-index',
|
|
19
|
+
'display',
|
|
20
|
+
'float',
|
|
21
|
+
'width',
|
|
22
|
+
'min-width',
|
|
23
|
+
'max-width',
|
|
24
|
+
'height',
|
|
25
|
+
'min-height',
|
|
26
|
+
'max-height',
|
|
27
|
+
'box-sizing',
|
|
28
|
+
'padding',
|
|
29
|
+
'padding-top',
|
|
30
|
+
'padding-right',
|
|
31
|
+
'padding-bottom',
|
|
32
|
+
'padding-left',
|
|
33
|
+
'margin',
|
|
34
|
+
'margin-top',
|
|
35
|
+
'margin-right',
|
|
36
|
+
'margin-bottom',
|
|
37
|
+
'margin-left',
|
|
38
|
+
'overflow',
|
|
39
|
+
'overflow-x',
|
|
40
|
+
'overflow-y',
|
|
41
|
+
'clip',
|
|
42
|
+
'clear',
|
|
43
|
+
'font',
|
|
44
|
+
'font-family',
|
|
45
|
+
'font-size',
|
|
46
|
+
'font-style',
|
|
47
|
+
'font-weight',
|
|
48
|
+
'font-variant',
|
|
49
|
+
'font-size-adjust',
|
|
50
|
+
'font-stretch',
|
|
51
|
+
'font-effect',
|
|
52
|
+
'font-emphasize',
|
|
53
|
+
'font-emphasize-position',
|
|
54
|
+
'font-emphasize-style',
|
|
55
|
+
'font-smooth',
|
|
56
|
+
'hyphens',
|
|
57
|
+
'line-height',
|
|
58
|
+
'color',
|
|
59
|
+
'text-align',
|
|
60
|
+
'text-align-last',
|
|
61
|
+
'text-emphasis',
|
|
62
|
+
'text-emphasis-color',
|
|
63
|
+
'text-emphasis-style',
|
|
64
|
+
'text-emphasis-position',
|
|
65
|
+
'text-decoration',
|
|
66
|
+
'text-indent',
|
|
67
|
+
'text-justify',
|
|
68
|
+
'text-outline',
|
|
69
|
+
'text-overflow',
|
|
70
|
+
'text-overflow-ellipsis',
|
|
71
|
+
'text-overflow-mode',
|
|
72
|
+
'text-shadow',
|
|
73
|
+
'text-transform',
|
|
74
|
+
'text-wrap',
|
|
75
|
+
'letter-spacing',
|
|
76
|
+
'word-break',
|
|
77
|
+
'word-spacing',
|
|
78
|
+
'word-wrap',
|
|
79
|
+
'tab-size',
|
|
80
|
+
'white-space',
|
|
81
|
+
'vertical-align',
|
|
82
|
+
'list-style',
|
|
83
|
+
'list-style-position',
|
|
84
|
+
'list-style-type',
|
|
85
|
+
'list-style-image',
|
|
86
|
+
'pointer-events',
|
|
87
|
+
'fill',
|
|
88
|
+
'fill-opacity',
|
|
89
|
+
'stroke',
|
|
90
|
+
'stroke-opacity',
|
|
91
|
+
'stroke-width',
|
|
92
|
+
'shape-rendering',
|
|
93
|
+
'cursor',
|
|
94
|
+
'visibility',
|
|
95
|
+
'zoom',
|
|
96
|
+
'flex-direction',
|
|
97
|
+
'flex-order',
|
|
98
|
+
'flex-pack',
|
|
99
|
+
'flex-align',
|
|
100
|
+
'table-layout',
|
|
101
|
+
'empty-cells',
|
|
102
|
+
'caption-side',
|
|
103
|
+
'border-spacing',
|
|
104
|
+
'border-collapse',
|
|
105
|
+
'content',
|
|
106
|
+
'quotes',
|
|
107
|
+
'counter-reset',
|
|
108
|
+
'counter-increment',
|
|
109
|
+
'resize',
|
|
110
|
+
'user-select',
|
|
111
|
+
'nav-index',
|
|
112
|
+
'nav-up',
|
|
113
|
+
'nav-right',
|
|
114
|
+
'nav-down',
|
|
115
|
+
'nav-left',
|
|
116
|
+
'background',
|
|
117
|
+
'background-color',
|
|
118
|
+
'background-image',
|
|
119
|
+
'filter',
|
|
120
|
+
'background-repeat',
|
|
121
|
+
'background-attachment',
|
|
122
|
+
'background-position',
|
|
123
|
+
'background-position-x',
|
|
124
|
+
'background-position-y',
|
|
125
|
+
'background-clip',
|
|
126
|
+
'background-origin',
|
|
127
|
+
'background-size',
|
|
128
|
+
'border',
|
|
129
|
+
'border-color',
|
|
130
|
+
'border-style',
|
|
131
|
+
'border-width',
|
|
132
|
+
'border-top',
|
|
133
|
+
'border-top-color',
|
|
134
|
+
'border-top-style',
|
|
135
|
+
'border-top-width',
|
|
136
|
+
'border-right',
|
|
137
|
+
'border-right-color',
|
|
138
|
+
'border-right-style',
|
|
139
|
+
'border-right-width',
|
|
140
|
+
'border-bottom',
|
|
141
|
+
'border-bottom-color',
|
|
142
|
+
'border-bottom-style',
|
|
143
|
+
'border-bottom-width',
|
|
144
|
+
'border-left',
|
|
145
|
+
'border-left-color',
|
|
146
|
+
'border-left-style',
|
|
147
|
+
'border-left-width',
|
|
148
|
+
'border-radius',
|
|
149
|
+
'border-top-left-radius',
|
|
150
|
+
'border-top-right-radius',
|
|
151
|
+
'border-bottom-right-radius',
|
|
152
|
+
'border-bottom-left-radius',
|
|
153
|
+
'border-image',
|
|
154
|
+
'border-image-source',
|
|
155
|
+
'border-image-slice',
|
|
156
|
+
'border-image-width',
|
|
157
|
+
'border-image-outset',
|
|
158
|
+
'border-image-repeat',
|
|
159
|
+
'outline',
|
|
160
|
+
'outline-width',
|
|
161
|
+
'outline-style',
|
|
162
|
+
'outline-color',
|
|
163
|
+
'outline-offset',
|
|
164
|
+
'box-shadow',
|
|
165
|
+
'opacity',
|
|
166
|
+
'transition',
|
|
167
|
+
'transition-delay',
|
|
168
|
+
'transition-timing-function',
|
|
169
|
+
'transition-duration',
|
|
170
|
+
'transition-property',
|
|
171
|
+
'transform',
|
|
172
|
+
'transform-origin',
|
|
173
|
+
'animation',
|
|
174
|
+
'animation-name',
|
|
175
|
+
'animation-duration',
|
|
176
|
+
'animation-fill-mode',
|
|
177
|
+
'animation-play-state',
|
|
178
|
+
'animation-timing-function',
|
|
179
|
+
'animation-delay',
|
|
180
|
+
'animation-iteration-count',
|
|
181
|
+
'animation-direction',
|
|
182
|
+
];
|
|
183
|
+
|
|
184
|
+
const require$1 = createRequire(import.meta.url);
|
|
185
|
+
|
|
186
|
+
function primitivesVariables(type) {
|
|
187
|
+
const variables = [];
|
|
188
|
+
|
|
189
|
+
const files = [];
|
|
190
|
+
switch (type) {
|
|
191
|
+
case 'spacing':
|
|
192
|
+
files.push('base/size/size.json');
|
|
193
|
+
break
|
|
194
|
+
case 'border':
|
|
195
|
+
files.push('functional/size/border.json');
|
|
196
|
+
break
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
for (const file of files) {
|
|
200
|
+
// eslint-disable-next-line import/no-dynamic-require
|
|
201
|
+
const data = require$1(`@primer/primitives/dist/styleLint/${file}`);
|
|
202
|
+
|
|
203
|
+
for (const key of Object.keys(data)) {
|
|
204
|
+
const size = data[key];
|
|
205
|
+
const values = typeof size['value'] === 'string' ? [size['value']] : size['value'];
|
|
206
|
+
|
|
207
|
+
variables.push({
|
|
208
|
+
name: `--${size['name']}`,
|
|
209
|
+
values,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return variables
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function walkGroups$1(root, validate) {
|
|
218
|
+
for (const node of root.nodes) {
|
|
219
|
+
if (node.type === 'function') {
|
|
220
|
+
walkGroups$1(node, validate);
|
|
221
|
+
} else {
|
|
222
|
+
validate(node);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return root
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const {
|
|
229
|
+
createPlugin: createPlugin$1,
|
|
230
|
+
utils: {report: report$1, ruleMessages: ruleMessages$1, validateOptions: validateOptions$1},
|
|
231
|
+
} = stylelint;
|
|
232
|
+
|
|
233
|
+
const ruleName$3 = 'primer/borders';
|
|
234
|
+
const messages$3 = ruleMessages$1(ruleName$3, {
|
|
235
|
+
rejected: (value, replacement, propName) => {
|
|
236
|
+
if (propName && propName.includes('radius') && value.includes('borderWidth')) {
|
|
237
|
+
return `Border radius variables can not be used for border widths`
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if ((propName && propName.includes('width')) || (borderShorthand(propName) && value.includes('borderRadius'))) {
|
|
241
|
+
return `Border width variables can not be used for border radii`
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (!replacement) {
|
|
245
|
+
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`
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return `Please replace '${value}' with a Primer border variable '${replacement['name']}'. https://primer.style/foundations/primitives/size#border`
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
const variables = primitivesVariables('border');
|
|
253
|
+
const sizes$1 = [];
|
|
254
|
+
const radii = [];
|
|
255
|
+
|
|
256
|
+
// Props that we want to check
|
|
257
|
+
const propList$1 = ['border', 'border-width', 'border-radius'];
|
|
258
|
+
// Values that we want to ignore
|
|
259
|
+
const valueList$1 = ['${'];
|
|
260
|
+
|
|
261
|
+
const borderShorthand = prop =>
|
|
262
|
+
/^border(-(top|right|bottom|left|block-start|block-end|inline-start|inline-end))?$/.test(prop);
|
|
263
|
+
|
|
264
|
+
for (const variable of variables) {
|
|
265
|
+
const name = variable['name'];
|
|
266
|
+
|
|
267
|
+
if (name.includes('borderWidth')) {
|
|
268
|
+
const value = variable['values']
|
|
269
|
+
.pop()
|
|
270
|
+
.replace(/max|\(|\)/g, '')
|
|
271
|
+
.split(',')[0];
|
|
272
|
+
sizes$1.push({
|
|
273
|
+
name,
|
|
274
|
+
values: [value],
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (name.includes('borderRadius')) {
|
|
279
|
+
radii.push(variable);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** @type {import('stylelint').Rule} */
|
|
284
|
+
const ruleFunction$1 = (primary, secondaryOptions, context) => {
|
|
285
|
+
return (root, result) => {
|
|
286
|
+
const validOptions = validateOptions$1(result, ruleName$3, {
|
|
287
|
+
actual: primary,
|
|
288
|
+
possible: [true],
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
if (!validOptions) return
|
|
292
|
+
|
|
293
|
+
root.walkDecls(declNode => {
|
|
294
|
+
const {prop, value} = declNode;
|
|
295
|
+
|
|
296
|
+
if (!propList$1.some(borderProp => prop.startsWith(borderProp))) return
|
|
297
|
+
if (/^border(-(top|right|bottom|left|block-start|block-end|inline-start|inline-end))?-color$/.test(prop)) return
|
|
298
|
+
if (valueList$1.some(valueToIgnore => value.includes(valueToIgnore))) return
|
|
299
|
+
|
|
300
|
+
const problems = [];
|
|
301
|
+
|
|
302
|
+
const parsedValue = walkGroups$1(valueParser(value), node => {
|
|
303
|
+
const checkForVariable = (vars, nodeValue) =>
|
|
304
|
+
vars.some(variable =>
|
|
305
|
+
new RegExp(`${variable['name'].replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`).test(nodeValue),
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
// Only check word types. https://github.com/TrySound/postcss-value-parser#word
|
|
309
|
+
if (node.type !== 'word') {
|
|
310
|
+
return
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Exact values to ignore.
|
|
314
|
+
if (
|
|
315
|
+
[
|
|
316
|
+
'*',
|
|
317
|
+
'+',
|
|
318
|
+
'-',
|
|
319
|
+
'/',
|
|
320
|
+
'0',
|
|
321
|
+
'none',
|
|
322
|
+
'inherit',
|
|
323
|
+
'initial',
|
|
324
|
+
'revert',
|
|
325
|
+
'revert-layer',
|
|
326
|
+
'unset',
|
|
327
|
+
'solid',
|
|
328
|
+
'dashed',
|
|
329
|
+
'dotted',
|
|
330
|
+
'transparent',
|
|
331
|
+
].includes(node.value)
|
|
332
|
+
) {
|
|
333
|
+
return
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const valueUnit = valueParser.unit(node.value);
|
|
337
|
+
|
|
338
|
+
if (valueUnit && (valueUnit.unit === '' || !/^-?[0-9.]+$/.test(valueUnit.number))) {
|
|
339
|
+
return
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Skip if the value unit isn't a supported unit.
|
|
343
|
+
if (valueUnit && !['px', 'rem', 'em'].includes(valueUnit.unit)) {
|
|
344
|
+
return
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// if we're looking at the border property that sets color in shorthand, don't bother checking the color
|
|
348
|
+
if (
|
|
349
|
+
// using border shorthand
|
|
350
|
+
borderShorthand(prop) &&
|
|
351
|
+
// includes a color as a third space-separated value
|
|
352
|
+
value.split(' ').length > 2 &&
|
|
353
|
+
// the color in the third space-separated value includes `node.value`
|
|
354
|
+
value
|
|
355
|
+
.split(' ')
|
|
356
|
+
.slice(2)
|
|
357
|
+
.some(color => color.includes(node.value))
|
|
358
|
+
) {
|
|
359
|
+
return
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// If the variable is found in the value, skip it.
|
|
363
|
+
if (prop.includes('width') || borderShorthand(prop)) {
|
|
364
|
+
if (checkForVariable(sizes$1, node.value)) {
|
|
365
|
+
return
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (prop.includes('radius')) {
|
|
370
|
+
if (checkForVariable(radii, node.value)) {
|
|
371
|
+
return
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const replacement = (prop.includes('radius') ? radii : sizes$1).find(variable =>
|
|
376
|
+
variable.values.includes(node.value.replace('-', '')),
|
|
377
|
+
);
|
|
378
|
+
const fixable = replacement && valueUnit && !valueUnit.number.includes('-');
|
|
379
|
+
|
|
380
|
+
if (fixable && context.fix) {
|
|
381
|
+
node.value = node.value.replace(node.value, `var(${replacement['name']})`);
|
|
382
|
+
} else {
|
|
383
|
+
problems.push({
|
|
384
|
+
index: declarationValueIndex(declNode) + node.sourceIndex,
|
|
385
|
+
endIndex: declarationValueIndex(declNode) + node.sourceIndex + node.value.length,
|
|
386
|
+
message: messages$3.rejected(node.value, replacement, prop),
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
if (context.fix) {
|
|
394
|
+
declNode.value = parsedValue.toString();
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (problems.length) {
|
|
398
|
+
for (const err of problems) {
|
|
399
|
+
report$1({
|
|
400
|
+
index: err.index,
|
|
401
|
+
endIndex: err.endIndex,
|
|
402
|
+
message: err.message,
|
|
403
|
+
node: declNode,
|
|
404
|
+
result,
|
|
405
|
+
ruleName: ruleName$3,
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
ruleFunction$1.ruleName = ruleName$3;
|
|
414
|
+
ruleFunction$1.messages = messages$3;
|
|
415
|
+
ruleFunction$1.meta = {
|
|
416
|
+
fixable: true,
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
var borders = createPlugin$1(ruleName$3, ruleFunction$1);
|
|
420
|
+
|
|
421
|
+
const SKIP_VALUE_NODE_TYPES = new Set(['space', 'div']);
|
|
422
|
+
const SKIP_AT_RULE_NAMES = new Set(['each', 'for', 'function', 'mixin']);
|
|
423
|
+
|
|
424
|
+
function declarationValidator(rules, options = {}) {
|
|
425
|
+
const {formatMessage = defaultMessageFormatter, variables, verbose = false} = options;
|
|
426
|
+
const variableReplacements = new TapMap();
|
|
427
|
+
if (variables) {
|
|
428
|
+
for (const [name, {values}] of Object.entries(variables)) {
|
|
429
|
+
for (const value of values) {
|
|
430
|
+
variableReplacements.tap(value, () => []).push(name);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const validators = Object.entries(rules)
|
|
436
|
+
.map(([key, rule]) => {
|
|
437
|
+
if (rule === false) {
|
|
438
|
+
return false
|
|
439
|
+
}
|
|
440
|
+
const {name = key, props = name, expects = `a ${name} value`} = rule;
|
|
441
|
+
const replacements = Object.assign({}, rule.replacements, getVariableReplacements(rule.values));
|
|
442
|
+
// console.warn(`replacements for "${key}": ${JSON.stringify(replacements)}`)
|
|
443
|
+
Object.assign(rule, {name, props, expects, replacements});
|
|
444
|
+
return {
|
|
445
|
+
rule,
|
|
446
|
+
matchesProp: anymatch(props),
|
|
447
|
+
validate: Array.isArray(rule.components) ? componentValidator(rule) : valueValidator(rule),
|
|
448
|
+
}
|
|
449
|
+
})
|
|
450
|
+
.filter(Boolean);
|
|
451
|
+
|
|
452
|
+
const validatorsByProp = new TapMap();
|
|
453
|
+
const validatorsByReplacementValue = new Map();
|
|
454
|
+
for (const validator of validators) {
|
|
455
|
+
for (const value of Object.keys(validator.rule.replacements)) {
|
|
456
|
+
validatorsByReplacementValue.set(value, validator);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
return decl => {
|
|
461
|
+
if (closest(decl, isSkippableAtRule)) {
|
|
462
|
+
if (verbose) {
|
|
463
|
+
// eslint-disable-next-line no-console
|
|
464
|
+
console.warn(`skipping declaration: ${decl.parent.toString()}`);
|
|
465
|
+
}
|
|
466
|
+
// As a general rule, any rule nested in an at-rule is ignored, since
|
|
467
|
+
// @for, @each, @mixin, and @function blocks can use whatever variables
|
|
468
|
+
// they want
|
|
469
|
+
return {valid: true}
|
|
470
|
+
}
|
|
471
|
+
const validator = getPropValidator(decl.prop);
|
|
472
|
+
if (validator) {
|
|
473
|
+
const result = validator.validate(decl);
|
|
474
|
+
result.errors = result.errors.map(formatMessage);
|
|
475
|
+
return result
|
|
476
|
+
} else {
|
|
477
|
+
return {valid: true}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function getVariableReplacements(values) {
|
|
482
|
+
const replacements = {};
|
|
483
|
+
const varValues = (Array.isArray(values) ? values : [values]).filter(v => typeof v === 'string' && v.includes('$'));
|
|
484
|
+
const matches = anymatch(varValues);
|
|
485
|
+
for (const [value, aliases] of variableReplacements.entries()) {
|
|
486
|
+
for (const alias of aliases) {
|
|
487
|
+
if (matches(alias)) {
|
|
488
|
+
replacements[value] = alias;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
return replacements
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function getPropValidator(prop) {
|
|
496
|
+
return validatorsByProp.tap(prop, () => validators.find(v => v.matchesProp(prop)))
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function valueValidator({expects, values, replacements, singular = false}) {
|
|
500
|
+
const matches = anymatch(values);
|
|
501
|
+
return function validate({prop, value}, nested) {
|
|
502
|
+
if (matches(value)) {
|
|
503
|
+
return {
|
|
504
|
+
valid: true,
|
|
505
|
+
errors: [],
|
|
506
|
+
fixable: false,
|
|
507
|
+
replacement: undefined,
|
|
508
|
+
}
|
|
509
|
+
} else if (replacements[value]) {
|
|
510
|
+
let replacement = value;
|
|
511
|
+
do {
|
|
512
|
+
replacement = replacements[replacement];
|
|
513
|
+
} while (replacements[replacement])
|
|
514
|
+
return {
|
|
515
|
+
valid: false,
|
|
516
|
+
errors: [{expects, prop, value, replacement}],
|
|
517
|
+
fixable: true,
|
|
518
|
+
replacement,
|
|
519
|
+
}
|
|
520
|
+
} else {
|
|
521
|
+
if (nested || singular) {
|
|
522
|
+
return {
|
|
523
|
+
valid: false,
|
|
524
|
+
errors: [{expects, prop, value}],
|
|
525
|
+
fixable: false,
|
|
526
|
+
replacement: undefined,
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const parsed = valueParser(value);
|
|
531
|
+
const validations = parsed.nodes
|
|
532
|
+
.map((node, index) => Object.assign(node, {index}))
|
|
533
|
+
.filter(node => !SKIP_VALUE_NODE_TYPES.has(node.type))
|
|
534
|
+
.map(node => {
|
|
535
|
+
const validation = validate({prop, value: valueParser.stringify(node)}, true);
|
|
536
|
+
validation.index = node.index;
|
|
537
|
+
return validation
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
const valid = validations.every(v => v.valid);
|
|
541
|
+
if (valid) {
|
|
542
|
+
return {valid, errors: [], fixable: false, replacement: undefined}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
const fixable = validations.some(v => v.fixable);
|
|
546
|
+
const errors = validations.reduce((list, v) => list.concat(v.errors), []);
|
|
547
|
+
|
|
548
|
+
let replacement = undefined;
|
|
549
|
+
for (const validation of validations) {
|
|
550
|
+
if (fixable && validation.replacement) {
|
|
551
|
+
parsed.nodes[validation.index] = {type: 'word', value: validation.replacement};
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
if (fixable) {
|
|
556
|
+
replacement = valueParser.stringify(parsed);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
return {
|
|
560
|
+
valid,
|
|
561
|
+
fixable,
|
|
562
|
+
errors,
|
|
563
|
+
replacement,
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function componentValidator({expects, components, values, replacements}) {
|
|
570
|
+
const matchesCompoundValue = anymatch(values);
|
|
571
|
+
return decl => {
|
|
572
|
+
const {prop, value: compoundValue} = decl;
|
|
573
|
+
const parsed = valueParser(compoundValue);
|
|
574
|
+
if (parsed.nodes.length === 1 && matchesCompoundValue(compoundValue)) {
|
|
575
|
+
return {valid: true, errors: []}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
const errors = [];
|
|
579
|
+
|
|
580
|
+
let fixable = false;
|
|
581
|
+
let componentIndex = 0;
|
|
582
|
+
for (const [index, node] of Object.entries(parsed.nodes)) {
|
|
583
|
+
if (SKIP_VALUE_NODE_TYPES.has(node.type)) {
|
|
584
|
+
continue
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
const value = valueParser.stringify(node);
|
|
588
|
+
|
|
589
|
+
let componentProp = components[componentIndex++];
|
|
590
|
+
let validator = getPropValidator(componentProp);
|
|
591
|
+
if (validatorsByReplacementValue.has(value)) {
|
|
592
|
+
validator = validatorsByReplacementValue.get(value);
|
|
593
|
+
componentProp = validator.rule.name;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
const nestedProp = `${componentProp} (in ${prop})`;
|
|
597
|
+
if (validator) {
|
|
598
|
+
const result = validator.validate({prop: nestedProp, value}, true);
|
|
599
|
+
if (result.replacement) {
|
|
600
|
+
parsed.nodes[index] = {
|
|
601
|
+
type: 'word',
|
|
602
|
+
value: result.replacement,
|
|
603
|
+
};
|
|
604
|
+
fixable = true;
|
|
605
|
+
}
|
|
606
|
+
for (const error of result.errors) {
|
|
607
|
+
errors.push(error);
|
|
608
|
+
}
|
|
609
|
+
} else {
|
|
610
|
+
errors.push({expects, prop: nestedProp, value});
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
let replacement = fixable ? valueParser.stringify(parsed) : undefined;
|
|
615
|
+
|
|
616
|
+
// if a compound replacement exists, suggest *that* instead
|
|
617
|
+
if (replacement && replacements[replacement]) {
|
|
618
|
+
do {
|
|
619
|
+
replacement = replacements[replacement];
|
|
620
|
+
} while (replacements[replacement])
|
|
621
|
+
return {
|
|
622
|
+
valid: false,
|
|
623
|
+
errors: [{expects, prop, value: compoundValue, replacement}],
|
|
624
|
+
fixable: true,
|
|
625
|
+
replacement,
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
return {
|
|
630
|
+
valid: errors.length === 0,
|
|
631
|
+
errors,
|
|
632
|
+
fixable,
|
|
633
|
+
replacement,
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
function isSkippableAtRule(node) {
|
|
639
|
+
return node.type === 'atrule' && SKIP_AT_RULE_NAMES.has(node.name)
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function defaultMessageFormatter(error) {
|
|
644
|
+
const {expects, value, replacement} = error;
|
|
645
|
+
const expected = replacement ? `"${replacement}"` : expects;
|
|
646
|
+
return `Please use ${expected} instead of "${value}"`
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
function closest(node, test) {
|
|
650
|
+
let ancestor = node;
|
|
651
|
+
do {
|
|
652
|
+
if (test(ancestor)) return ancestor
|
|
653
|
+
} while ((ancestor = ancestor.parent))
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
function createVariableRule(ruleName, rules, url) {
|
|
657
|
+
const plugin = stylelint.createPlugin(ruleName, (enabled, options = {}, context) => {
|
|
658
|
+
if (enabled === false) {
|
|
659
|
+
return noop$2
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
let actualRules = rules;
|
|
663
|
+
let overrides = options.rules;
|
|
664
|
+
if (typeof rules === 'function') {
|
|
665
|
+
actualRules = rules({variables: variables$1, options, ruleName});
|
|
666
|
+
} else {
|
|
667
|
+
actualRules = Object.assign({}, rules);
|
|
668
|
+
}
|
|
669
|
+
if (typeof overrides === 'function') {
|
|
670
|
+
delete options.rules;
|
|
671
|
+
overrides = overrides({rules: actualRules, options, ruleName, variables: variables$1});
|
|
672
|
+
}
|
|
673
|
+
if (overrides) {
|
|
674
|
+
Object.assign(actualRules, overrides);
|
|
675
|
+
}
|
|
676
|
+
const validate = declarationValidator(actualRules, {variables: variables$1});
|
|
677
|
+
|
|
678
|
+
// The stylelint docs suggest respecting a "disableFix" rule option that
|
|
679
|
+
// overrides the "global" context.fix (--fix) linting option.
|
|
680
|
+
const {verbose = false, disableFix} = options;
|
|
681
|
+
const fixEnabled = context && context.fix && !disableFix;
|
|
682
|
+
const seen = new WeakMap();
|
|
683
|
+
|
|
684
|
+
return (root, result) => {
|
|
685
|
+
root.walkRules(rule => {
|
|
686
|
+
rule.walkDecls(decl => {
|
|
687
|
+
if (seen.has(decl)) {
|
|
688
|
+
return
|
|
689
|
+
} else {
|
|
690
|
+
seen.set(decl, true);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
const validated = validate(decl);
|
|
694
|
+
const {valid, fixable, replacement, errors} = validated;
|
|
695
|
+
if (valid) {
|
|
696
|
+
// eslint-disable-next-line no-console
|
|
697
|
+
if (verbose) console.warn(`valid: "${decl.toString()}" in: "${rule.selector}"`);
|
|
698
|
+
return
|
|
699
|
+
} else if (fixEnabled && fixable) {
|
|
700
|
+
// eslint-disable-next-line no-console
|
|
701
|
+
if (verbose) console.warn(` fixed: ${replacement}`);
|
|
702
|
+
decl.value = replacement;
|
|
703
|
+
} else {
|
|
704
|
+
// eslint-disable-next-line no-console
|
|
705
|
+
if (verbose) console.warn(` ${errors.length} error(s)`);
|
|
706
|
+
for (const error of errors) {
|
|
707
|
+
const message = stylelint.utils
|
|
708
|
+
.ruleMessages(ruleName, {
|
|
709
|
+
rejected: m => {
|
|
710
|
+
if (url) {
|
|
711
|
+
return `${m}. See ${url}.`
|
|
712
|
+
}
|
|
713
|
+
return `${m}.`
|
|
714
|
+
},
|
|
715
|
+
})
|
|
716
|
+
.rejected(error);
|
|
717
|
+
|
|
718
|
+
stylelint.utils.report({
|
|
719
|
+
message,
|
|
720
|
+
node: decl,
|
|
721
|
+
result,
|
|
722
|
+
ruleName,
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
});
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
Object.assign(plugin, {rules});
|
|
732
|
+
|
|
733
|
+
return plugin
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
function noop$2() {}
|
|
737
|
+
|
|
738
|
+
var boxShadow = createVariableRule(
|
|
739
|
+
'primer/box-shadow',
|
|
740
|
+
{
|
|
741
|
+
'box shadow': {
|
|
742
|
+
expects: 'a box-shadow variable',
|
|
743
|
+
props: 'box-shadow',
|
|
744
|
+
values: [
|
|
745
|
+
'$box-shadow*',
|
|
746
|
+
'$*-shadow',
|
|
747
|
+
'none',
|
|
748
|
+
// Match variables in any of the following formats: --color-shadow-*, --color-*-shadow-*, --color-*-shadow, --shadow-*, *shadow*
|
|
749
|
+
/var\(--color-(.+-)*shadow(-.+)*\)/,
|
|
750
|
+
/var\(--shadow(-.+)*\)/,
|
|
751
|
+
/var\((.+-)*shadow(-.+)*\)/,
|
|
752
|
+
],
|
|
753
|
+
singular: true,
|
|
754
|
+
},
|
|
755
|
+
},
|
|
756
|
+
'https://primer.style/css/utilities/box-shadow',
|
|
757
|
+
);
|
|
758
|
+
|
|
759
|
+
const bgVars = [
|
|
760
|
+
'$bg-*',
|
|
761
|
+
'$tooltip-background-color',
|
|
762
|
+
// Match variables in any of the following formats: --color-bg-*, --color-*-bg-*, --color-*-bg, *bgColor*, *fgColor*, *borderColor*, *iconColor*
|
|
763
|
+
/var\(--color-(.+-)*bg(-.+)*\)/,
|
|
764
|
+
/var\(--color-[^)]+\)/,
|
|
765
|
+
/var\((.+-)*bgColor(-.+)*\)/,
|
|
766
|
+
/var\((.+-)*fgColor(-.+)*\)/,
|
|
767
|
+
/var\((.+-)*borderColor(-.+)*\)/,
|
|
768
|
+
/var\((.+-)*iconColor(-.+)*\)/,
|
|
769
|
+
];
|
|
770
|
+
|
|
771
|
+
var colors = createVariableRule(
|
|
772
|
+
'primer/colors',
|
|
773
|
+
{
|
|
774
|
+
'background-color': {
|
|
775
|
+
expects: 'a background color variable',
|
|
776
|
+
values: bgVars.concat('none', 'transparent'),
|
|
777
|
+
},
|
|
778
|
+
background: {
|
|
779
|
+
expects: 'a background color variable',
|
|
780
|
+
values: bgVars.concat('none', 'transparent', 'top', 'right', 'bottom', 'left', 'center', '*px', 'url(*)'),
|
|
781
|
+
},
|
|
782
|
+
'text color': {
|
|
783
|
+
expects: 'a text color variable',
|
|
784
|
+
props: 'color',
|
|
785
|
+
values: [
|
|
786
|
+
'$text-*',
|
|
787
|
+
'$tooltip-text-color',
|
|
788
|
+
'inherit',
|
|
789
|
+
// Match variables in any of the following formats: --color-text-*, --color-*-text-*, --color-*-text, *fgColor*, *iconColor*
|
|
790
|
+
/var\(--color-(.+-)*text(-.+)*\)/,
|
|
791
|
+
/var\(--color-(.+-)*fg(-.+)*\)/,
|
|
792
|
+
/var\(--color-[^)]+\)/,
|
|
793
|
+
/var\((.+-)*fgColor(-.+)*\)/,
|
|
794
|
+
/var\((.+-)*iconColor(-.+)*\)/,
|
|
795
|
+
],
|
|
796
|
+
},
|
|
797
|
+
},
|
|
798
|
+
'https://primer.style/primitives/colors',
|
|
799
|
+
);
|
|
800
|
+
|
|
801
|
+
const ruleName$2 = 'primer/responsive-widths';
|
|
802
|
+
|
|
803
|
+
const messages$2 = stylelint.utils.ruleMessages(ruleName$2, {
|
|
804
|
+
rejected: value => {
|
|
805
|
+
return `A value larger than the smallest viewport could break responsive pages. Use a width value smaller than ${value}. https://primer.style/css/support/breakpoints`
|
|
806
|
+
},
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
// 320px is the smallest viewport size that we support
|
|
810
|
+
|
|
811
|
+
const walkGroups = (root, validate) => {
|
|
812
|
+
for (const node of root.nodes) {
|
|
813
|
+
if (node.type === 'function') {
|
|
814
|
+
walkGroups(node, validate);
|
|
815
|
+
} else {
|
|
816
|
+
validate(node);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
return root
|
|
820
|
+
};
|
|
821
|
+
|
|
822
|
+
// eslint-disable-next-line no-unused-vars
|
|
823
|
+
var responsiveWidths = stylelint.createPlugin(ruleName$2, (enabled, options = {}, context) => {
|
|
824
|
+
if (!enabled) {
|
|
825
|
+
return noop$1
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
const lintResult = (root, result) => {
|
|
829
|
+
root.walk(decl => {
|
|
830
|
+
// Ignore things inside of breakpoints
|
|
831
|
+
if (decl.type === 'atrule' && decl.name === 'include' && decl.params.includes('breakpoint')) {
|
|
832
|
+
return false
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
if (decl.type !== 'decl' || !decl.prop.match(/^(min-width|width)/)) {
|
|
836
|
+
return noop$1
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
const problems = [];
|
|
840
|
+
|
|
841
|
+
walkGroups(valueParser(decl.value), node => {
|
|
842
|
+
// Only check word types. https://github.com/TrySound/postcss-value-parser#word
|
|
843
|
+
if (node.type !== 'word') {
|
|
844
|
+
return
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// Exact values to ignore.
|
|
848
|
+
if (['*', '+', '-', '/', '0', 'auto', 'inherit', 'initial'].includes(node.value)) {
|
|
849
|
+
return
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
const valueUnit = valueParser.unit(node.value);
|
|
853
|
+
|
|
854
|
+
switch (valueUnit.unit) {
|
|
855
|
+
case 'px':
|
|
856
|
+
if (parseInt(valueUnit.number) > 320) {
|
|
857
|
+
problems.push({
|
|
858
|
+
index: declarationValueIndex(decl) + node.sourceIndex,
|
|
859
|
+
message: messages$2.rejected(node.value),
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
break
|
|
863
|
+
case 'vw':
|
|
864
|
+
if (parseInt(valueUnit.number) > 100) {
|
|
865
|
+
problems.push({
|
|
866
|
+
index: declarationValueIndex(decl) + node.sourceIndex,
|
|
867
|
+
message: messages$2.rejected(node.value),
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
break
|
|
871
|
+
}
|
|
872
|
+
});
|
|
873
|
+
|
|
874
|
+
if (problems.length) {
|
|
875
|
+
for (const err of problems) {
|
|
876
|
+
stylelint.utils.report({
|
|
877
|
+
index: err.index,
|
|
878
|
+
message: err.message,
|
|
879
|
+
node: decl,
|
|
880
|
+
result,
|
|
881
|
+
ruleName: ruleName$2,
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
});
|
|
886
|
+
};
|
|
887
|
+
|
|
888
|
+
return lintResult
|
|
889
|
+
});
|
|
890
|
+
|
|
891
|
+
function noop$1() {}
|
|
892
|
+
|
|
893
|
+
const {
|
|
894
|
+
createPlugin,
|
|
895
|
+
utils: {report, ruleMessages, validateOptions},
|
|
896
|
+
} = stylelint;
|
|
897
|
+
|
|
898
|
+
const ruleName$1 = 'primer/spacing';
|
|
899
|
+
const messages$1 = ruleMessages(ruleName$1, {
|
|
900
|
+
rejected: (value, replacement) => {
|
|
901
|
+
if (!replacement) {
|
|
902
|
+
return `Please use a primer size variable instead of '${value}'. Consult the primer docs for a suitable replacement. https://primer.style/foundations/primitives/size`
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
return `Please replace '${value}' with size variable '${replacement['name']}'. https://primer.style/foundations/primitives/size`
|
|
906
|
+
},
|
|
907
|
+
});
|
|
908
|
+
|
|
909
|
+
// Props that we want to check
|
|
910
|
+
const propList = ['padding', 'margin', 'top', 'right', 'bottom', 'left'];
|
|
911
|
+
// Values that we want to ignore
|
|
912
|
+
const valueList = ['${'];
|
|
913
|
+
|
|
914
|
+
const sizes = primitivesVariables('spacing');
|
|
915
|
+
|
|
916
|
+
// Add +-1px to each value
|
|
917
|
+
for (const size of sizes) {
|
|
918
|
+
const values = size['values'];
|
|
919
|
+
const px = parseInt(values.find(value => value.includes('px')));
|
|
920
|
+
if (![2, 6].includes(px)) {
|
|
921
|
+
values.push(`${px + 1}px`);
|
|
922
|
+
values.push(`${px - 1}px`);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
/** @type {import('stylelint').Rule} */
|
|
927
|
+
const ruleFunction = (primary, secondaryOptions, context) => {
|
|
928
|
+
return (root, result) => {
|
|
929
|
+
const validOptions = validateOptions(result, ruleName$1, {
|
|
930
|
+
actual: primary,
|
|
931
|
+
possible: [true],
|
|
932
|
+
});
|
|
933
|
+
|
|
934
|
+
if (!validOptions) return
|
|
935
|
+
|
|
936
|
+
root.walkDecls(declNode => {
|
|
937
|
+
const {prop, value} = declNode;
|
|
938
|
+
|
|
939
|
+
if (!propList.some(spacingProp => prop.startsWith(spacingProp))) return
|
|
940
|
+
if (valueList.some(valueToIgnore => value.includes(valueToIgnore))) return
|
|
941
|
+
|
|
942
|
+
const problems = [];
|
|
943
|
+
|
|
944
|
+
const parsedValue = walkGroups$1(valueParser(value), node => {
|
|
945
|
+
// Only check word types. https://github.com/TrySound/postcss-value-parser#word
|
|
946
|
+
if (node.type !== 'word') {
|
|
947
|
+
return
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// Exact values to ignore.
|
|
951
|
+
if (['*', '+', '-', '/', '0', 'auto', 'inherit', 'initial'].includes(node.value)) {
|
|
952
|
+
return
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
const valueUnit = valueParser.unit(node.value);
|
|
956
|
+
|
|
957
|
+
if (valueUnit && (valueUnit.unit === '' || !/^-?[0-9.]+$/.test(valueUnit.number))) {
|
|
958
|
+
return
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// Skip if the value unit isn't a supported unit.
|
|
962
|
+
if (valueUnit && !['px', 'rem', 'em'].includes(valueUnit.unit)) {
|
|
963
|
+
return
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
// If the variable is found in the value, skip it.
|
|
967
|
+
if (
|
|
968
|
+
sizes.some(variable =>
|
|
969
|
+
new RegExp(`${variable['name'].replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`).test(node.value),
|
|
970
|
+
)
|
|
971
|
+
) {
|
|
972
|
+
return
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
const replacement = sizes.find(variable => variable.values.includes(node.value.replace('-', '')));
|
|
976
|
+
const fixable = replacement && valueUnit && !valueUnit.number.includes('-');
|
|
977
|
+
|
|
978
|
+
if (fixable && context.fix) {
|
|
979
|
+
node.value = node.value.replace(node.value, `var(${replacement['name']})`);
|
|
980
|
+
} else {
|
|
981
|
+
problems.push({
|
|
982
|
+
index: declarationValueIndex(declNode) + node.sourceIndex,
|
|
983
|
+
endIndex: declarationValueIndex(declNode) + node.sourceIndex + node.value.length,
|
|
984
|
+
message: messages$1.rejected(node.value, replacement),
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
return
|
|
989
|
+
});
|
|
990
|
+
|
|
991
|
+
if (context.fix) {
|
|
992
|
+
declNode.value = parsedValue.toString();
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
if (problems.length) {
|
|
996
|
+
for (const err of problems) {
|
|
997
|
+
report({
|
|
998
|
+
index: err.index,
|
|
999
|
+
endIndex: err.endIndex,
|
|
1000
|
+
message: err.message,
|
|
1001
|
+
node: declNode,
|
|
1002
|
+
result,
|
|
1003
|
+
ruleName: ruleName$1,
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
});
|
|
1008
|
+
}
|
|
1009
|
+
};
|
|
1010
|
+
|
|
1011
|
+
ruleFunction.ruleName = ruleName$1;
|
|
1012
|
+
ruleFunction.messages = messages$1;
|
|
1013
|
+
ruleFunction.meta = {
|
|
1014
|
+
fixable: true,
|
|
1015
|
+
};
|
|
1016
|
+
|
|
1017
|
+
var spacing = createPlugin(ruleName$1, ruleFunction);
|
|
1018
|
+
|
|
1019
|
+
var typography = createVariableRule(
|
|
1020
|
+
'primer/typography',
|
|
1021
|
+
{
|
|
1022
|
+
'font-size': {
|
|
1023
|
+
expects: 'a font-size variable',
|
|
1024
|
+
values: ['$body-font-size', '$h{000,00,0,1,2,3,4,5,6}-size', '$font-size-*', '1', '1em', 'inherit'],
|
|
1025
|
+
},
|
|
1026
|
+
'font-weight': {
|
|
1027
|
+
props: 'font-weight',
|
|
1028
|
+
values: ['$font-weight-*', 'inherit'],
|
|
1029
|
+
replacements: {
|
|
1030
|
+
bold: '$font-weight-bold',
|
|
1031
|
+
normal: '$font-weight-normal',
|
|
1032
|
+
},
|
|
1033
|
+
},
|
|
1034
|
+
'line-height': {
|
|
1035
|
+
props: 'line-height',
|
|
1036
|
+
values: ['$body-line-height', '$lh-*', '0', '1', '1em', 'inherit'],
|
|
1037
|
+
},
|
|
1038
|
+
},
|
|
1039
|
+
'https://primer.style/css/utilities/typography',
|
|
1040
|
+
);
|
|
1041
|
+
|
|
1042
|
+
const ruleName = 'primer/no-display-colors';
|
|
1043
|
+
const messages = stylelint.utils.ruleMessages(ruleName, {
|
|
1044
|
+
rejected: varName => `${varName} is in alpha and should be used with caution with approval from the Primer team`,
|
|
1045
|
+
});
|
|
1046
|
+
|
|
1047
|
+
// Match CSS variable references (e.g var(--display-blue-fgColor))
|
|
1048
|
+
// eslint-disable-next-line no-useless-escape
|
|
1049
|
+
const variableReferenceRegex = /var\(([^\),]+)(,.*)?\)/g;
|
|
1050
|
+
|
|
1051
|
+
var noDisplayColors = stylelint.createPlugin(ruleName, (enabled, options = {}) => {
|
|
1052
|
+
if (!enabled) {
|
|
1053
|
+
return noop
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
const {verbose = false} = options;
|
|
1057
|
+
// eslint-disable-next-line no-console
|
|
1058
|
+
const log = verbose ? (...args) => console.warn(...args) : noop;
|
|
1059
|
+
|
|
1060
|
+
// Keep track of declarations we've already seen
|
|
1061
|
+
const seen = new WeakMap();
|
|
1062
|
+
|
|
1063
|
+
return (root, result) => {
|
|
1064
|
+
root.walkRules(rule => {
|
|
1065
|
+
rule.walkDecls(decl => {
|
|
1066
|
+
if (seen.has(decl)) {
|
|
1067
|
+
return
|
|
1068
|
+
} else {
|
|
1069
|
+
seen.set(decl, true);
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
for (const [, variableName] of matchAll(decl.value, variableReferenceRegex)) {
|
|
1073
|
+
log(`Found variable reference ${variableName}`);
|
|
1074
|
+
if (variableName.match(/^--display-.*/)) {
|
|
1075
|
+
stylelint.utils.report({
|
|
1076
|
+
message: messages.rejected(variableName),
|
|
1077
|
+
node: decl,
|
|
1078
|
+
result,
|
|
1079
|
+
ruleName,
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
});
|
|
1084
|
+
});
|
|
1085
|
+
}
|
|
1086
|
+
});
|
|
1087
|
+
|
|
1088
|
+
function noop() {}
|
|
1089
|
+
|
|
1090
|
+
const require = createRequire(import.meta.url);
|
|
1091
|
+
|
|
1092
|
+
/** @type {import('stylelint').Config} */
|
|
1093
|
+
var index = {
|
|
1094
|
+
extends: ['stylelint-config-standard'],
|
|
1095
|
+
ignoreFiles: ['**/*.js', '**/*.cjs', '**/*.ts', '**/*.mjs'],
|
|
1096
|
+
reportNeedlessDisables: true,
|
|
1097
|
+
plugins: [
|
|
1098
|
+
'stylelint-value-no-unknown-custom-properties',
|
|
1099
|
+
'stylelint-no-unsupported-browser-features',
|
|
1100
|
+
'stylelint-order',
|
|
1101
|
+
borders,
|
|
1102
|
+
boxShadow,
|
|
1103
|
+
colors,
|
|
1104
|
+
responsiveWidths,
|
|
1105
|
+
spacing,
|
|
1106
|
+
typography,
|
|
1107
|
+
noDisplayColors,
|
|
1108
|
+
],
|
|
1109
|
+
rules: {
|
|
1110
|
+
'alpha-value-notation': 'number',
|
|
1111
|
+
'at-rule-disallowed-list': ['extend'],
|
|
1112
|
+
'at-rule-no-unknown': null,
|
|
1113
|
+
'block-no-empty': true,
|
|
1114
|
+
'color-function-notation': null,
|
|
1115
|
+
'color-named': 'never',
|
|
1116
|
+
'color-no-invalid-hex': true,
|
|
1117
|
+
'comment-no-empty': null,
|
|
1118
|
+
'csstools/value-no-unknown-custom-properties': [
|
|
1119
|
+
true,
|
|
1120
|
+
{
|
|
1121
|
+
severity: 'warning',
|
|
1122
|
+
importFrom: [
|
|
1123
|
+
'@primer/primitives/dist/css/functional/size/size-coarse.css',
|
|
1124
|
+
'@primer/primitives/dist/css/functional/size/border.css',
|
|
1125
|
+
'@primer/primitives/dist/css/functional/size/size.css',
|
|
1126
|
+
'@primer/primitives/dist/css/functional/size/size-fine.css',
|
|
1127
|
+
'@primer/primitives/dist/css/functional/size/breakpoints.css',
|
|
1128
|
+
'@primer/primitives/dist/css/functional/size/viewport.css',
|
|
1129
|
+
'@primer/primitives/dist/css/functional/motion/motion.css',
|
|
1130
|
+
'@primer/primitives/dist/css/functional/themes/light.css',
|
|
1131
|
+
'@primer/primitives/dist/css/functional/typography/typography.css',
|
|
1132
|
+
'@primer/primitives/dist/css/base/size/size.css',
|
|
1133
|
+
'@primer/primitives/dist/css/base/typography/typography.css',
|
|
1134
|
+
].map(path => require.resolve(path)),
|
|
1135
|
+
},
|
|
1136
|
+
],
|
|
1137
|
+
'custom-property-pattern': null,
|
|
1138
|
+
'declaration-block-no-duplicate-properties': [true, {ignore: ['consecutive-duplicates']}],
|
|
1139
|
+
'declaration-block-no-redundant-longhand-properties': null,
|
|
1140
|
+
'declaration-block-no-shorthand-property-overrides': true,
|
|
1141
|
+
'declaration-property-value-disallowed-list': {
|
|
1142
|
+
'/^transition/': ['/all/'],
|
|
1143
|
+
'/^background/': ['http:', 'https:'],
|
|
1144
|
+
'/^border/': ['none'],
|
|
1145
|
+
'/.+/': ['initial'],
|
|
1146
|
+
},
|
|
1147
|
+
'function-calc-no-unspaced-operator': true,
|
|
1148
|
+
'function-linear-gradient-no-nonstandard-direction': true,
|
|
1149
|
+
'function-no-unknown': null,
|
|
1150
|
+
'keyframes-name-pattern': null,
|
|
1151
|
+
'max-nesting-depth': 3,
|
|
1152
|
+
'media-feature-name-no-unknown': null,
|
|
1153
|
+
'media-feature-name-no-vendor-prefix': null,
|
|
1154
|
+
'no-descending-specificity': null,
|
|
1155
|
+
'no-duplicate-selectors': true,
|
|
1156
|
+
'no-invalid-position-at-import-rule': [true, {ignoreAtRules: ['use']}],
|
|
1157
|
+
'number-max-precision': null,
|
|
1158
|
+
'order/properties-order': propertyOrder,
|
|
1159
|
+
'plugin/no-unsupported-browser-features': [
|
|
1160
|
+
true,
|
|
1161
|
+
{
|
|
1162
|
+
severity: 'warning',
|
|
1163
|
+
ignore: ['css-nesting'],
|
|
1164
|
+
ignorePartialSupport: true,
|
|
1165
|
+
browsers,
|
|
1166
|
+
},
|
|
1167
|
+
],
|
|
1168
|
+
'primer/borders': true,
|
|
1169
|
+
'primer/box-shadow': true,
|
|
1170
|
+
'primer/colors': true,
|
|
1171
|
+
'primer/responsive-widths': true,
|
|
1172
|
+
'primer/spacing': true,
|
|
1173
|
+
'primer/typography': true,
|
|
1174
|
+
'primer/no-display-colors': true,
|
|
1175
|
+
'property-no-unknown': [
|
|
1176
|
+
true,
|
|
1177
|
+
{
|
|
1178
|
+
ignoreProperties: ['@container', 'container-type'],
|
|
1179
|
+
},
|
|
1180
|
+
],
|
|
1181
|
+
'selector-class-pattern': null,
|
|
1182
|
+
'selector-max-compound-selectors': 3,
|
|
1183
|
+
'selector-max-id': 0,
|
|
1184
|
+
'selector-max-specificity': '0,4,0',
|
|
1185
|
+
'selector-max-type': 0,
|
|
1186
|
+
'selector-no-qualifying-type': true,
|
|
1187
|
+
'selector-pseudo-element-no-unknown': true,
|
|
1188
|
+
'string-no-newline': true,
|
|
1189
|
+
'unit-no-unknown': true,
|
|
1190
|
+
'value-keyword-case': null,
|
|
1191
|
+
'selector-not-notation': null,
|
|
1192
|
+
'import-notation': ['string'],
|
|
1193
|
+
'annotation-no-unknown': null,
|
|
1194
|
+
'keyframe-selector-notation': ['percentage-unless-within-keyword-only-block'],
|
|
1195
|
+
'media-query-no-invalid': null,
|
|
1196
|
+
'media-feature-range-notation': ['prefix'],
|
|
1197
|
+
},
|
|
1198
|
+
overrides: [
|
|
1199
|
+
{
|
|
1200
|
+
files: ['**/*.scss'],
|
|
1201
|
+
customSyntax: 'postcss-scss',
|
|
1202
|
+
plugins: ['stylelint-scss'],
|
|
1203
|
+
rules: {
|
|
1204
|
+
'scss/at-extend-no-missing-placeholder': true,
|
|
1205
|
+
'scss/at-rule-no-unknown': true,
|
|
1206
|
+
'scss/declaration-nested-properties-no-divided-groups': true,
|
|
1207
|
+
'scss/dollar-variable-no-missing-interpolation': true,
|
|
1208
|
+
'scss/function-quote-no-quoted-strings-inside': true,
|
|
1209
|
+
'scss/function-unquote-no-unquoted-strings-inside': true,
|
|
1210
|
+
'scss/no-duplicate-mixins': true,
|
|
1211
|
+
'scss/selector-no-redundant-nesting-selector': true,
|
|
1212
|
+
},
|
|
1213
|
+
},
|
|
1214
|
+
{
|
|
1215
|
+
files: ['**/*.tsx'],
|
|
1216
|
+
customSyntax: 'postcss-styled-syntax',
|
|
1217
|
+
rules: {
|
|
1218
|
+
'order/properties-order': null,
|
|
1219
|
+
'rule-empty-line-before': null,
|
|
1220
|
+
'declaration-empty-line-before': null,
|
|
1221
|
+
'comment-empty-line-before': null,
|
|
1222
|
+
'length-zero-no-unit': null,
|
|
1223
|
+
'selector-max-type': null,
|
|
1224
|
+
'primer/colors': null,
|
|
1225
|
+
'primer/typography': null,
|
|
1226
|
+
'primer/box-shadow': null,
|
|
1227
|
+
},
|
|
1228
|
+
},
|
|
1229
|
+
{
|
|
1230
|
+
files: ['**/*.pcss'],
|
|
1231
|
+
rules: {
|
|
1232
|
+
'media-feature-range-notation': null,
|
|
1233
|
+
'import-notation': null,
|
|
1234
|
+
'custom-property-pattern': null,
|
|
1235
|
+
'selector-class-pattern': null,
|
|
1236
|
+
'keyframes-name-pattern': null,
|
|
1237
|
+
'no-descending-specificity': null,
|
|
1238
|
+
'declaration-block-no-redundant-longhand-properties': null,
|
|
1239
|
+
'color-function-notation': 'legacy',
|
|
1240
|
+
'selector-nested-pattern': '^&\\s?\\W',
|
|
1241
|
+
'at-rule-no-unknown': [
|
|
1242
|
+
true,
|
|
1243
|
+
{
|
|
1244
|
+
ignoreAtRules: ['mixin', 'define-mixin'],
|
|
1245
|
+
},
|
|
1246
|
+
],
|
|
1247
|
+
},
|
|
1248
|
+
},
|
|
1249
|
+
{
|
|
1250
|
+
files: ['**/*.module.css'],
|
|
1251
|
+
plugins: ['stylelint-css-modules-no-global-scoped-selector'],
|
|
1252
|
+
rules: {
|
|
1253
|
+
'property-no-unknown': [
|
|
1254
|
+
true,
|
|
1255
|
+
{
|
|
1256
|
+
ignoreProperties: ['composes', 'compose-with'],
|
|
1257
|
+
ignoreSelectors: [':export', /^:import/],
|
|
1258
|
+
},
|
|
1259
|
+
],
|
|
1260
|
+
'selector-pseudo-class-no-unknown': [
|
|
1261
|
+
true,
|
|
1262
|
+
{ignorePseudoClasses: ['export', 'import', 'global', 'local', 'external']},
|
|
1263
|
+
],
|
|
1264
|
+
'selector-type-no-unknown': [
|
|
1265
|
+
true,
|
|
1266
|
+
{
|
|
1267
|
+
ignoreTypes: ['from'],
|
|
1268
|
+
},
|
|
1269
|
+
],
|
|
1270
|
+
'function-no-unknown': [
|
|
1271
|
+
true,
|
|
1272
|
+
{
|
|
1273
|
+
ignoreFunctions: ['global'],
|
|
1274
|
+
},
|
|
1275
|
+
],
|
|
1276
|
+
'css-modules/no-global-scoped-selector': true,
|
|
1277
|
+
// temporarily disabiling Primer plugins while we work on upgrades https://github.com/github/primer/issues/3165
|
|
1278
|
+
'primer/typography': null,
|
|
1279
|
+
'primer/box-shadow': null,
|
|
1280
|
+
},
|
|
1281
|
+
},
|
|
1282
|
+
],
|
|
1283
|
+
};
|
|
1284
|
+
|
|
1285
|
+
export { index as default };
|