@primer/stylelint-config 13.0.0-rc.c3d4a78 → 13.0.0-rc.c67f7df
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/dist/index.cjs +268 -134
- package/dist/index.mjs +268 -134
- package/package.json +1 -1
- package/plugins/borders.js +196 -63
- package/plugins/lib/{primitives.js → utils.js} +17 -8
- package/plugins/spacing.js +20 -23
package/dist/index.cjs
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
var browsers = require('@github/browserslist-config');
|
|
4
4
|
var stylelint = require('stylelint');
|
|
5
|
-
var anymatch = require('anymatch');
|
|
6
|
-
var valueParser = require('postcss-value-parser');
|
|
7
|
-
var TapMap = require('tap-map');
|
|
8
|
-
var variables = require('@primer/css/dist/variables.json');
|
|
9
5
|
var declarationValueIndex = require('stylelint/lib/utils/declarationValueIndex.cjs');
|
|
6
|
+
var valueParser = require('postcss-value-parser');
|
|
10
7
|
var node_module = require('node:module');
|
|
8
|
+
var anymatch = require('anymatch');
|
|
9
|
+
var TapMap = require('tap-map');
|
|
10
|
+
var variables$1 = require('@primer/css/dist/variables.json');
|
|
11
11
|
var matchAll = require('string.prototype.matchall');
|
|
12
12
|
|
|
13
13
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
@@ -184,6 +184,243 @@ var propertyOrder = [
|
|
|
184
184
|
'animation-direction',
|
|
185
185
|
];
|
|
186
186
|
|
|
187
|
+
const require$2 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
|
|
188
|
+
|
|
189
|
+
function primitivesVariables(type) {
|
|
190
|
+
const variables = [];
|
|
191
|
+
|
|
192
|
+
const files = [];
|
|
193
|
+
switch (type) {
|
|
194
|
+
case 'spacing':
|
|
195
|
+
files.push('base/size/size.json');
|
|
196
|
+
break
|
|
197
|
+
case 'border':
|
|
198
|
+
files.push('functional/size/border.json');
|
|
199
|
+
break
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
for (const file of files) {
|
|
203
|
+
// eslint-disable-next-line import/no-dynamic-require
|
|
204
|
+
const data = require$2(`@primer/primitives/dist/styleLint/${file}`);
|
|
205
|
+
|
|
206
|
+
for (const key of Object.keys(data)) {
|
|
207
|
+
const size = data[key];
|
|
208
|
+
const values = typeof size['value'] === 'string' ? [size['value']] : size['value'];
|
|
209
|
+
|
|
210
|
+
variables.push({
|
|
211
|
+
name: `--${size['name']}`,
|
|
212
|
+
values,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return variables
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function walkGroups$1(root, validate) {
|
|
221
|
+
for (const node of root.nodes) {
|
|
222
|
+
if (node.type === 'function') {
|
|
223
|
+
walkGroups$1(node, validate);
|
|
224
|
+
} else {
|
|
225
|
+
validate(node);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return root
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const {
|
|
232
|
+
createPlugin: createPlugin$1,
|
|
233
|
+
utils: {report: report$1, ruleMessages: ruleMessages$1, validateOptions: validateOptions$1},
|
|
234
|
+
} = stylelint;
|
|
235
|
+
|
|
236
|
+
const ruleName$3 = 'primer/borders';
|
|
237
|
+
const messages$3 = ruleMessages$1(ruleName$3, {
|
|
238
|
+
rejected: (value, replacement, propName) => {
|
|
239
|
+
if (propName && propName.includes('radius') && value.includes('borderWidth')) {
|
|
240
|
+
return `Border radius variables can not be used for border widths`
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if ((propName && propName.includes('width')) || (borderShorthand(propName) && value.includes('borderRadius'))) {
|
|
244
|
+
return `Border width variables can not be used for border radii`
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (!replacement) {
|
|
248
|
+
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`
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return `Please replace '${value}' with a Primer border variable '${replacement['name']}'. https://primer.style/foundations/primitives/size#border`
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
const variables = primitivesVariables('border');
|
|
256
|
+
const sizes$1 = [];
|
|
257
|
+
const radii = [];
|
|
258
|
+
|
|
259
|
+
// Props that we want to check
|
|
260
|
+
const propList$1 = ['border', 'border-width', 'border-radius'];
|
|
261
|
+
// Values that we want to ignore
|
|
262
|
+
const valueList$1 = ['${'];
|
|
263
|
+
|
|
264
|
+
const borderShorthand = prop =>
|
|
265
|
+
/^border(-(top|right|bottom|left|block-start|block-end|inline-start|inline-end))?$/.test(prop);
|
|
266
|
+
|
|
267
|
+
for (const variable of variables) {
|
|
268
|
+
const name = variable['name'];
|
|
269
|
+
|
|
270
|
+
if (name.includes('borderWidth')) {
|
|
271
|
+
const value = variable['values']
|
|
272
|
+
.pop()
|
|
273
|
+
.replace(/max|\(|\)/g, '')
|
|
274
|
+
.split(',')[0];
|
|
275
|
+
sizes$1.push({
|
|
276
|
+
name,
|
|
277
|
+
values: [value],
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (name.includes('borderRadius')) {
|
|
282
|
+
radii.push(variable);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** @type {import('stylelint').Rule} */
|
|
287
|
+
const ruleFunction$1 = (primary, secondaryOptions, context) => {
|
|
288
|
+
return (root, result) => {
|
|
289
|
+
const validOptions = validateOptions$1(result, ruleName$3, {
|
|
290
|
+
actual: primary,
|
|
291
|
+
possible: [true],
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
if (!validOptions) return
|
|
295
|
+
|
|
296
|
+
root.walkDecls(declNode => {
|
|
297
|
+
const {prop, value} = declNode;
|
|
298
|
+
|
|
299
|
+
if (!propList$1.some(borderProp => prop.startsWith(borderProp))) return
|
|
300
|
+
if (/^border(-(top|right|bottom|left|block-start|block-end|inline-start|inline-end))?-color$/.test(prop)) return
|
|
301
|
+
if (valueList$1.some(valueToIgnore => value.includes(valueToIgnore))) return
|
|
302
|
+
|
|
303
|
+
const problems = [];
|
|
304
|
+
|
|
305
|
+
const parsedValue = walkGroups$1(valueParser(value), node => {
|
|
306
|
+
const checkForVariable = (vars, nodeValue) =>
|
|
307
|
+
vars.some(variable =>
|
|
308
|
+
new RegExp(`${variable['name'].replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`).test(nodeValue),
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
// Only check word types. https://github.com/TrySound/postcss-value-parser#word
|
|
312
|
+
if (node.type !== 'word') {
|
|
313
|
+
return
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Exact values to ignore.
|
|
317
|
+
if (
|
|
318
|
+
[
|
|
319
|
+
'*',
|
|
320
|
+
'+',
|
|
321
|
+
'-',
|
|
322
|
+
'/',
|
|
323
|
+
'0',
|
|
324
|
+
'none',
|
|
325
|
+
'inherit',
|
|
326
|
+
'initial',
|
|
327
|
+
'revert',
|
|
328
|
+
'revert-layer',
|
|
329
|
+
'unset',
|
|
330
|
+
'solid',
|
|
331
|
+
'dashed',
|
|
332
|
+
'dotted',
|
|
333
|
+
'transparent',
|
|
334
|
+
].includes(node.value)
|
|
335
|
+
) {
|
|
336
|
+
return
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const valueUnit = valueParser.unit(node.value);
|
|
340
|
+
|
|
341
|
+
if (valueUnit && (valueUnit.unit === '' || !/^-?[0-9.]+$/.test(valueUnit.number))) {
|
|
342
|
+
return
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Skip if the value unit isn't a supported unit.
|
|
346
|
+
if (valueUnit && !['px', 'rem', 'em'].includes(valueUnit.unit)) {
|
|
347
|
+
return
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// if we're looking at the border property that sets color in shorthand, don't bother checking the color
|
|
351
|
+
if (
|
|
352
|
+
// using border shorthand
|
|
353
|
+
borderShorthand(prop) &&
|
|
354
|
+
// includes a color as a third space-separated value
|
|
355
|
+
value.split(' ').length > 2 &&
|
|
356
|
+
// the color in the third space-separated value includes `node.value`
|
|
357
|
+
value
|
|
358
|
+
.split(' ')
|
|
359
|
+
.slice(2)
|
|
360
|
+
.some(color => color.includes(node.value))
|
|
361
|
+
) {
|
|
362
|
+
return
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// If the variable is found in the value, skip it.
|
|
366
|
+
if (prop.includes('width') || borderShorthand(prop)) {
|
|
367
|
+
if (checkForVariable(sizes$1, node.value)) {
|
|
368
|
+
return
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (prop.includes('radius')) {
|
|
373
|
+
if (checkForVariable(radii, node.value)) {
|
|
374
|
+
return
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const replacement = (prop.includes('radius') ? radii : sizes$1).find(variable =>
|
|
379
|
+
variable.values.includes(node.value.replace('-', '')),
|
|
380
|
+
);
|
|
381
|
+
const fixable = replacement && valueUnit && !valueUnit.number.includes('-');
|
|
382
|
+
|
|
383
|
+
if (fixable && context.fix) {
|
|
384
|
+
node.value = node.value.replace(node.value, `var(${replacement['name']})`);
|
|
385
|
+
} else {
|
|
386
|
+
problems.push({
|
|
387
|
+
index: declarationValueIndex(declNode) + node.sourceIndex,
|
|
388
|
+
endIndex: declarationValueIndex(declNode) + node.sourceIndex + node.value.length,
|
|
389
|
+
message: messages$3.rejected(node.value, replacement, prop),
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
if (context.fix) {
|
|
397
|
+
declNode.value = parsedValue.toString();
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (problems.length) {
|
|
401
|
+
for (const err of problems) {
|
|
402
|
+
report$1({
|
|
403
|
+
index: err.index,
|
|
404
|
+
endIndex: err.endIndex,
|
|
405
|
+
message: err.message,
|
|
406
|
+
node: declNode,
|
|
407
|
+
result,
|
|
408
|
+
ruleName: ruleName$3,
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
ruleFunction$1.ruleName = ruleName$3;
|
|
417
|
+
ruleFunction$1.messages = messages$3;
|
|
418
|
+
ruleFunction$1.meta = {
|
|
419
|
+
fixable: true,
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
var borders = createPlugin$1(ruleName$3, ruleFunction$1);
|
|
423
|
+
|
|
187
424
|
const SKIP_VALUE_NODE_TYPES = new Set(['space', 'div']);
|
|
188
425
|
const SKIP_AT_RULE_NAMES = new Set(['each', 'for', 'function', 'mixin']);
|
|
189
426
|
|
|
@@ -428,18 +665,18 @@ function createVariableRule(ruleName, rules, url) {
|
|
|
428
665
|
let actualRules = rules;
|
|
429
666
|
let overrides = options.rules;
|
|
430
667
|
if (typeof rules === 'function') {
|
|
431
|
-
actualRules = rules({variables, options, ruleName});
|
|
668
|
+
actualRules = rules({variables: variables$1, options, ruleName});
|
|
432
669
|
} else {
|
|
433
670
|
actualRules = Object.assign({}, rules);
|
|
434
671
|
}
|
|
435
672
|
if (typeof overrides === 'function') {
|
|
436
673
|
delete options.rules;
|
|
437
|
-
overrides = overrides({rules: actualRules, options, ruleName, variables});
|
|
674
|
+
overrides = overrides({rules: actualRules, options, ruleName, variables: variables$1});
|
|
438
675
|
}
|
|
439
676
|
if (overrides) {
|
|
440
677
|
Object.assign(actualRules, overrides);
|
|
441
678
|
}
|
|
442
|
-
const validate = declarationValidator(actualRules, {variables});
|
|
679
|
+
const validate = declarationValidator(actualRules, {variables: variables$1});
|
|
443
680
|
|
|
444
681
|
// The stylelint docs suggest respecting a "disableFix" rule option that
|
|
445
682
|
// overrides the "global" context.fix (--fix) linting option.
|
|
@@ -501,69 +738,6 @@ function createVariableRule(ruleName, rules, url) {
|
|
|
501
738
|
|
|
502
739
|
function noop$2() {}
|
|
503
740
|
|
|
504
|
-
var borders = createVariableRule(
|
|
505
|
-
'primer/borders',
|
|
506
|
-
{
|
|
507
|
-
border: {
|
|
508
|
-
expects: 'a border variable',
|
|
509
|
-
props: 'border{,-top,-right,-bottom,-left}',
|
|
510
|
-
values: ['$border', 'none', '0'],
|
|
511
|
-
components: ['border-width', 'border-style', 'border-color'],
|
|
512
|
-
replacements: {
|
|
513
|
-
// because shorthand border properties ¯\_(ツ)_/¯
|
|
514
|
-
'$border-width $border-style $border-gray': '$border',
|
|
515
|
-
'$border-width $border-gray $border-style': '$border',
|
|
516
|
-
'$border-style $border-width $border-gray': '$border',
|
|
517
|
-
'$border-style $border-gray $border-width': '$border',
|
|
518
|
-
'$border-gray $border-width $border-style': '$border',
|
|
519
|
-
'$border-gray $border-style $border-width': '$border',
|
|
520
|
-
'$border-width $border-style $border-color': '$border',
|
|
521
|
-
'$border-width $border-color $border-style': '$border',
|
|
522
|
-
'$border-style $border-width $border-color': '$border',
|
|
523
|
-
'$border-style $border-color $border-width': '$border',
|
|
524
|
-
'$border-color $border-width $border-style': '$border',
|
|
525
|
-
'$border-color $border-style $border-width': '$border',
|
|
526
|
-
},
|
|
527
|
-
},
|
|
528
|
-
'border color': {
|
|
529
|
-
expects: 'a border color variable',
|
|
530
|
-
props: 'border{,-top,-right,-bottom,-left}-color',
|
|
531
|
-
values: [
|
|
532
|
-
'$border-*',
|
|
533
|
-
'transparent',
|
|
534
|
-
'currentColor',
|
|
535
|
-
// Match variables in any of the following formats: --color-border-*, --color-*-border-*, --color-*-border, --borderColor-, *borderColor*
|
|
536
|
-
/var\(--color-(.+-)*border(-.+)*\)/,
|
|
537
|
-
/var\(--color-[^)]+\)/,
|
|
538
|
-
/var\(--borderColor-[^)]+\)/,
|
|
539
|
-
/var\((.+-)*borderColor(-.+)*\)/,
|
|
540
|
-
],
|
|
541
|
-
replacements: {
|
|
542
|
-
'$border-gray': '$border-color',
|
|
543
|
-
},
|
|
544
|
-
},
|
|
545
|
-
'border style': {
|
|
546
|
-
expects: 'a border style variable',
|
|
547
|
-
props: 'border{,-top,-right,-bottom,-left}-style',
|
|
548
|
-
values: ['$border-style', 'none'],
|
|
549
|
-
},
|
|
550
|
-
'border width': {
|
|
551
|
-
expects: 'a border width variable',
|
|
552
|
-
props: 'border{,-top,-right,-bottom,-left}-width',
|
|
553
|
-
values: ['$border-width*', '0'],
|
|
554
|
-
},
|
|
555
|
-
'border radius': {
|
|
556
|
-
expects: 'a border radius variable',
|
|
557
|
-
props: 'border{,-{top,bottom}-{left,right}}-radius',
|
|
558
|
-
values: ['$border-radius', '0', '50%', 'inherit'],
|
|
559
|
-
replacements: {
|
|
560
|
-
'100%': '50%',
|
|
561
|
-
},
|
|
562
|
-
},
|
|
563
|
-
},
|
|
564
|
-
'https://primer.style/css/utilities/borders',
|
|
565
|
-
);
|
|
566
|
-
|
|
567
741
|
var boxShadow = createVariableRule(
|
|
568
742
|
'primer/box-shadow',
|
|
569
743
|
{
|
|
@@ -637,10 +811,10 @@ const messages$2 = stylelint.utils.ruleMessages(ruleName$2, {
|
|
|
637
811
|
|
|
638
812
|
// 320px is the smallest viewport size that we support
|
|
639
813
|
|
|
640
|
-
const walkGroups
|
|
814
|
+
const walkGroups = (root, validate) => {
|
|
641
815
|
for (const node of root.nodes) {
|
|
642
816
|
if (node.type === 'function') {
|
|
643
|
-
walkGroups
|
|
817
|
+
walkGroups(node, validate);
|
|
644
818
|
} else {
|
|
645
819
|
validate(node);
|
|
646
820
|
}
|
|
@@ -667,7 +841,7 @@ var responsiveWidths = stylelint.createPlugin(ruleName$2, (enabled, options = {}
|
|
|
667
841
|
|
|
668
842
|
const problems = [];
|
|
669
843
|
|
|
670
|
-
walkGroups
|
|
844
|
+
walkGroups(valueParser(decl.value), node => {
|
|
671
845
|
// Only check word types. https://github.com/TrySound/postcss-value-parser#word
|
|
672
846
|
if (node.type !== 'word') {
|
|
673
847
|
return
|
|
@@ -719,57 +893,11 @@ var responsiveWidths = stylelint.createPlugin(ruleName$2, (enabled, options = {}
|
|
|
719
893
|
|
|
720
894
|
function noop$1() {}
|
|
721
895
|
|
|
722
|
-
const require$2 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
|
|
723
|
-
|
|
724
|
-
async function primitivesVariables(type) {
|
|
725
|
-
const variables = [];
|
|
726
|
-
|
|
727
|
-
const files = [];
|
|
728
|
-
switch (type) {
|
|
729
|
-
case 'size':
|
|
730
|
-
files.push('base/size/size.json');
|
|
731
|
-
break
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
for (const file of files) {
|
|
735
|
-
// eslint-disable-next-line import/no-dynamic-require
|
|
736
|
-
const data = require$2(`@primer/primitives/dist/styleLint/${file}`);
|
|
737
|
-
|
|
738
|
-
for (const key of Object.keys(data)) {
|
|
739
|
-
const size = data[key];
|
|
740
|
-
const values = size['value'];
|
|
741
|
-
const intValue = parseInt(size['original']['value']);
|
|
742
|
-
if (![2, 6].includes(intValue)) {
|
|
743
|
-
values.push(`${intValue + 1}px`);
|
|
744
|
-
values.push(`${intValue - 1}px`);
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
variables.push({
|
|
748
|
-
name: `--${size['name']}`,
|
|
749
|
-
values,
|
|
750
|
-
});
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
return variables
|
|
755
|
-
}
|
|
756
|
-
|
|
757
896
|
const {
|
|
758
897
|
createPlugin,
|
|
759
898
|
utils: {report, ruleMessages, validateOptions},
|
|
760
899
|
} = stylelint;
|
|
761
900
|
|
|
762
|
-
const walkGroups = (root, validate) => {
|
|
763
|
-
for (const node of root.nodes) {
|
|
764
|
-
if (node.type === 'function') {
|
|
765
|
-
walkGroups(node, validate);
|
|
766
|
-
} else {
|
|
767
|
-
validate(node);
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
return root
|
|
771
|
-
};
|
|
772
|
-
|
|
773
901
|
const ruleName$1 = 'primer/spacing';
|
|
774
902
|
const messages$1 = ruleMessages(ruleName$1, {
|
|
775
903
|
rejected: (value, replacement) => {
|
|
@@ -781,20 +909,26 @@ const messages$1 = ruleMessages(ruleName$1, {
|
|
|
781
909
|
},
|
|
782
910
|
});
|
|
783
911
|
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
912
|
+
// Props that we want to check
|
|
913
|
+
const propList = ['padding', 'margin', 'top', 'right', 'bottom', 'left'];
|
|
914
|
+
// Values that we want to ignore
|
|
915
|
+
const valueList = ['${'];
|
|
787
916
|
|
|
788
|
-
|
|
789
|
-
const ruleFunction = (primary, secondaryOptions, context) => {
|
|
790
|
-
return async (root, result) => {
|
|
791
|
-
// Props that we want to check
|
|
792
|
-
const propList = ['padding', 'margin', 'top', 'right', 'bottom', 'left'];
|
|
793
|
-
// Values that we want to ignore
|
|
794
|
-
const valueList = ['${'];
|
|
917
|
+
const sizes = primitivesVariables('spacing');
|
|
795
918
|
|
|
796
|
-
|
|
919
|
+
// Add +-1px to each value
|
|
920
|
+
for (const size of sizes) {
|
|
921
|
+
const values = size['values'];
|
|
922
|
+
const px = parseInt(values.find(value => value.includes('px')));
|
|
923
|
+
if (![2, 6].includes(px)) {
|
|
924
|
+
values.push(`${px + 1}px`);
|
|
925
|
+
values.push(`${px - 1}px`);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
797
928
|
|
|
929
|
+
/** @type {import('stylelint').Rule} */
|
|
930
|
+
const ruleFunction = (primary, secondaryOptions, context) => {
|
|
931
|
+
return (root, result) => {
|
|
798
932
|
const validOptions = validateOptions(result, ruleName$1, {
|
|
799
933
|
actual: primary,
|
|
800
934
|
possible: [true],
|
|
@@ -810,7 +944,7 @@ const ruleFunction = (primary, secondaryOptions, context) => {
|
|
|
810
944
|
|
|
811
945
|
const problems = [];
|
|
812
946
|
|
|
813
|
-
const parsedValue = walkGroups(valueParser(value), node => {
|
|
947
|
+
const parsedValue = walkGroups$1(valueParser(value), node => {
|
|
814
948
|
// Only check word types. https://github.com/TrySound/postcss-value-parser#word
|
|
815
949
|
if (node.type !== 'word') {
|
|
816
950
|
return
|
|
@@ -879,7 +1013,9 @@ const ruleFunction = (primary, secondaryOptions, context) => {
|
|
|
879
1013
|
|
|
880
1014
|
ruleFunction.ruleName = ruleName$1;
|
|
881
1015
|
ruleFunction.messages = messages$1;
|
|
882
|
-
ruleFunction.meta =
|
|
1016
|
+
ruleFunction.meta = {
|
|
1017
|
+
fixable: true,
|
|
1018
|
+
};
|
|
883
1019
|
|
|
884
1020
|
var spacing = createPlugin(ruleName$1, ruleFunction);
|
|
885
1021
|
|
|
@@ -1089,7 +1225,6 @@ var index = {
|
|
|
1089
1225
|
'length-zero-no-unit': null,
|
|
1090
1226
|
'selector-max-type': null,
|
|
1091
1227
|
'primer/colors': null,
|
|
1092
|
-
'primer/borders': null,
|
|
1093
1228
|
'primer/typography': null,
|
|
1094
1229
|
'primer/box-shadow': null,
|
|
1095
1230
|
},
|
|
@@ -1143,7 +1278,6 @@ var index = {
|
|
|
1143
1278
|
],
|
|
1144
1279
|
'css-modules/no-global-scoped-selector': true,
|
|
1145
1280
|
// temporarily disabiling Primer plugins while we work on upgrades https://github.com/github/primer/issues/3165
|
|
1146
|
-
'primer/borders': null,
|
|
1147
1281
|
'primer/typography': null,
|
|
1148
1282
|
'primer/box-shadow': null,
|
|
1149
1283
|
},
|
package/dist/index.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import browsers from '@github/browserslist-config';
|
|
2
2
|
import stylelint from 'stylelint';
|
|
3
|
-
import anymatch from 'anymatch';
|
|
4
|
-
import valueParser from 'postcss-value-parser';
|
|
5
|
-
import TapMap from 'tap-map';
|
|
6
|
-
import variables from '@primer/css/dist/variables.json' assert { type: 'json' };
|
|
7
3
|
import declarationValueIndex from 'stylelint/lib/utils/declarationValueIndex.cjs';
|
|
4
|
+
import valueParser from 'postcss-value-parser';
|
|
8
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
9
|
import matchAll from 'string.prototype.matchall';
|
|
10
10
|
|
|
11
11
|
var propertyOrder = [
|
|
@@ -181,6 +181,243 @@ var propertyOrder = [
|
|
|
181
181
|
'animation-direction',
|
|
182
182
|
];
|
|
183
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
|
+
|
|
184
421
|
const SKIP_VALUE_NODE_TYPES = new Set(['space', 'div']);
|
|
185
422
|
const SKIP_AT_RULE_NAMES = new Set(['each', 'for', 'function', 'mixin']);
|
|
186
423
|
|
|
@@ -425,18 +662,18 @@ function createVariableRule(ruleName, rules, url) {
|
|
|
425
662
|
let actualRules = rules;
|
|
426
663
|
let overrides = options.rules;
|
|
427
664
|
if (typeof rules === 'function') {
|
|
428
|
-
actualRules = rules({variables, options, ruleName});
|
|
665
|
+
actualRules = rules({variables: variables$1, options, ruleName});
|
|
429
666
|
} else {
|
|
430
667
|
actualRules = Object.assign({}, rules);
|
|
431
668
|
}
|
|
432
669
|
if (typeof overrides === 'function') {
|
|
433
670
|
delete options.rules;
|
|
434
|
-
overrides = overrides({rules: actualRules, options, ruleName, variables});
|
|
671
|
+
overrides = overrides({rules: actualRules, options, ruleName, variables: variables$1});
|
|
435
672
|
}
|
|
436
673
|
if (overrides) {
|
|
437
674
|
Object.assign(actualRules, overrides);
|
|
438
675
|
}
|
|
439
|
-
const validate = declarationValidator(actualRules, {variables});
|
|
676
|
+
const validate = declarationValidator(actualRules, {variables: variables$1});
|
|
440
677
|
|
|
441
678
|
// The stylelint docs suggest respecting a "disableFix" rule option that
|
|
442
679
|
// overrides the "global" context.fix (--fix) linting option.
|
|
@@ -498,69 +735,6 @@ function createVariableRule(ruleName, rules, url) {
|
|
|
498
735
|
|
|
499
736
|
function noop$2() {}
|
|
500
737
|
|
|
501
|
-
var borders = createVariableRule(
|
|
502
|
-
'primer/borders',
|
|
503
|
-
{
|
|
504
|
-
border: {
|
|
505
|
-
expects: 'a border variable',
|
|
506
|
-
props: 'border{,-top,-right,-bottom,-left}',
|
|
507
|
-
values: ['$border', 'none', '0'],
|
|
508
|
-
components: ['border-width', 'border-style', 'border-color'],
|
|
509
|
-
replacements: {
|
|
510
|
-
// because shorthand border properties ¯\_(ツ)_/¯
|
|
511
|
-
'$border-width $border-style $border-gray': '$border',
|
|
512
|
-
'$border-width $border-gray $border-style': '$border',
|
|
513
|
-
'$border-style $border-width $border-gray': '$border',
|
|
514
|
-
'$border-style $border-gray $border-width': '$border',
|
|
515
|
-
'$border-gray $border-width $border-style': '$border',
|
|
516
|
-
'$border-gray $border-style $border-width': '$border',
|
|
517
|
-
'$border-width $border-style $border-color': '$border',
|
|
518
|
-
'$border-width $border-color $border-style': '$border',
|
|
519
|
-
'$border-style $border-width $border-color': '$border',
|
|
520
|
-
'$border-style $border-color $border-width': '$border',
|
|
521
|
-
'$border-color $border-width $border-style': '$border',
|
|
522
|
-
'$border-color $border-style $border-width': '$border',
|
|
523
|
-
},
|
|
524
|
-
},
|
|
525
|
-
'border color': {
|
|
526
|
-
expects: 'a border color variable',
|
|
527
|
-
props: 'border{,-top,-right,-bottom,-left}-color',
|
|
528
|
-
values: [
|
|
529
|
-
'$border-*',
|
|
530
|
-
'transparent',
|
|
531
|
-
'currentColor',
|
|
532
|
-
// Match variables in any of the following formats: --color-border-*, --color-*-border-*, --color-*-border, --borderColor-, *borderColor*
|
|
533
|
-
/var\(--color-(.+-)*border(-.+)*\)/,
|
|
534
|
-
/var\(--color-[^)]+\)/,
|
|
535
|
-
/var\(--borderColor-[^)]+\)/,
|
|
536
|
-
/var\((.+-)*borderColor(-.+)*\)/,
|
|
537
|
-
],
|
|
538
|
-
replacements: {
|
|
539
|
-
'$border-gray': '$border-color',
|
|
540
|
-
},
|
|
541
|
-
},
|
|
542
|
-
'border style': {
|
|
543
|
-
expects: 'a border style variable',
|
|
544
|
-
props: 'border{,-top,-right,-bottom,-left}-style',
|
|
545
|
-
values: ['$border-style', 'none'],
|
|
546
|
-
},
|
|
547
|
-
'border width': {
|
|
548
|
-
expects: 'a border width variable',
|
|
549
|
-
props: 'border{,-top,-right,-bottom,-left}-width',
|
|
550
|
-
values: ['$border-width*', '0'],
|
|
551
|
-
},
|
|
552
|
-
'border radius': {
|
|
553
|
-
expects: 'a border radius variable',
|
|
554
|
-
props: 'border{,-{top,bottom}-{left,right}}-radius',
|
|
555
|
-
values: ['$border-radius', '0', '50%', 'inherit'],
|
|
556
|
-
replacements: {
|
|
557
|
-
'100%': '50%',
|
|
558
|
-
},
|
|
559
|
-
},
|
|
560
|
-
},
|
|
561
|
-
'https://primer.style/css/utilities/borders',
|
|
562
|
-
);
|
|
563
|
-
|
|
564
738
|
var boxShadow = createVariableRule(
|
|
565
739
|
'primer/box-shadow',
|
|
566
740
|
{
|
|
@@ -634,10 +808,10 @@ const messages$2 = stylelint.utils.ruleMessages(ruleName$2, {
|
|
|
634
808
|
|
|
635
809
|
// 320px is the smallest viewport size that we support
|
|
636
810
|
|
|
637
|
-
const walkGroups
|
|
811
|
+
const walkGroups = (root, validate) => {
|
|
638
812
|
for (const node of root.nodes) {
|
|
639
813
|
if (node.type === 'function') {
|
|
640
|
-
walkGroups
|
|
814
|
+
walkGroups(node, validate);
|
|
641
815
|
} else {
|
|
642
816
|
validate(node);
|
|
643
817
|
}
|
|
@@ -664,7 +838,7 @@ var responsiveWidths = stylelint.createPlugin(ruleName$2, (enabled, options = {}
|
|
|
664
838
|
|
|
665
839
|
const problems = [];
|
|
666
840
|
|
|
667
|
-
walkGroups
|
|
841
|
+
walkGroups(valueParser(decl.value), node => {
|
|
668
842
|
// Only check word types. https://github.com/TrySound/postcss-value-parser#word
|
|
669
843
|
if (node.type !== 'word') {
|
|
670
844
|
return
|
|
@@ -716,57 +890,11 @@ var responsiveWidths = stylelint.createPlugin(ruleName$2, (enabled, options = {}
|
|
|
716
890
|
|
|
717
891
|
function noop$1() {}
|
|
718
892
|
|
|
719
|
-
const require$1 = createRequire(import.meta.url);
|
|
720
|
-
|
|
721
|
-
async function primitivesVariables(type) {
|
|
722
|
-
const variables = [];
|
|
723
|
-
|
|
724
|
-
const files = [];
|
|
725
|
-
switch (type) {
|
|
726
|
-
case 'size':
|
|
727
|
-
files.push('base/size/size.json');
|
|
728
|
-
break
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
for (const file of files) {
|
|
732
|
-
// eslint-disable-next-line import/no-dynamic-require
|
|
733
|
-
const data = require$1(`@primer/primitives/dist/styleLint/${file}`);
|
|
734
|
-
|
|
735
|
-
for (const key of Object.keys(data)) {
|
|
736
|
-
const size = data[key];
|
|
737
|
-
const values = size['value'];
|
|
738
|
-
const intValue = parseInt(size['original']['value']);
|
|
739
|
-
if (![2, 6].includes(intValue)) {
|
|
740
|
-
values.push(`${intValue + 1}px`);
|
|
741
|
-
values.push(`${intValue - 1}px`);
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
variables.push({
|
|
745
|
-
name: `--${size['name']}`,
|
|
746
|
-
values,
|
|
747
|
-
});
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
return variables
|
|
752
|
-
}
|
|
753
|
-
|
|
754
893
|
const {
|
|
755
894
|
createPlugin,
|
|
756
895
|
utils: {report, ruleMessages, validateOptions},
|
|
757
896
|
} = stylelint;
|
|
758
897
|
|
|
759
|
-
const walkGroups = (root, validate) => {
|
|
760
|
-
for (const node of root.nodes) {
|
|
761
|
-
if (node.type === 'function') {
|
|
762
|
-
walkGroups(node, validate);
|
|
763
|
-
} else {
|
|
764
|
-
validate(node);
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
return root
|
|
768
|
-
};
|
|
769
|
-
|
|
770
898
|
const ruleName$1 = 'primer/spacing';
|
|
771
899
|
const messages$1 = ruleMessages(ruleName$1, {
|
|
772
900
|
rejected: (value, replacement) => {
|
|
@@ -778,20 +906,26 @@ const messages$1 = ruleMessages(ruleName$1, {
|
|
|
778
906
|
},
|
|
779
907
|
});
|
|
780
908
|
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
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 = ['${'];
|
|
784
913
|
|
|
785
|
-
|
|
786
|
-
const ruleFunction = (primary, secondaryOptions, context) => {
|
|
787
|
-
return async (root, result) => {
|
|
788
|
-
// Props that we want to check
|
|
789
|
-
const propList = ['padding', 'margin', 'top', 'right', 'bottom', 'left'];
|
|
790
|
-
// Values that we want to ignore
|
|
791
|
-
const valueList = ['${'];
|
|
914
|
+
const sizes = primitivesVariables('spacing');
|
|
792
915
|
|
|
793
|
-
|
|
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
|
+
}
|
|
794
925
|
|
|
926
|
+
/** @type {import('stylelint').Rule} */
|
|
927
|
+
const ruleFunction = (primary, secondaryOptions, context) => {
|
|
928
|
+
return (root, result) => {
|
|
795
929
|
const validOptions = validateOptions(result, ruleName$1, {
|
|
796
930
|
actual: primary,
|
|
797
931
|
possible: [true],
|
|
@@ -807,7 +941,7 @@ const ruleFunction = (primary, secondaryOptions, context) => {
|
|
|
807
941
|
|
|
808
942
|
const problems = [];
|
|
809
943
|
|
|
810
|
-
const parsedValue = walkGroups(valueParser(value), node => {
|
|
944
|
+
const parsedValue = walkGroups$1(valueParser(value), node => {
|
|
811
945
|
// Only check word types. https://github.com/TrySound/postcss-value-parser#word
|
|
812
946
|
if (node.type !== 'word') {
|
|
813
947
|
return
|
|
@@ -876,7 +1010,9 @@ const ruleFunction = (primary, secondaryOptions, context) => {
|
|
|
876
1010
|
|
|
877
1011
|
ruleFunction.ruleName = ruleName$1;
|
|
878
1012
|
ruleFunction.messages = messages$1;
|
|
879
|
-
ruleFunction.meta =
|
|
1013
|
+
ruleFunction.meta = {
|
|
1014
|
+
fixable: true,
|
|
1015
|
+
};
|
|
880
1016
|
|
|
881
1017
|
var spacing = createPlugin(ruleName$1, ruleFunction);
|
|
882
1018
|
|
|
@@ -1086,7 +1222,6 @@ var index = {
|
|
|
1086
1222
|
'length-zero-no-unit': null,
|
|
1087
1223
|
'selector-max-type': null,
|
|
1088
1224
|
'primer/colors': null,
|
|
1089
|
-
'primer/borders': null,
|
|
1090
1225
|
'primer/typography': null,
|
|
1091
1226
|
'primer/box-shadow': null,
|
|
1092
1227
|
},
|
|
@@ -1140,7 +1275,6 @@ var index = {
|
|
|
1140
1275
|
],
|
|
1141
1276
|
'css-modules/no-global-scoped-selector': true,
|
|
1142
1277
|
// temporarily disabiling Primer plugins while we work on upgrades https://github.com/github/primer/issues/3165
|
|
1143
|
-
'primer/borders': null,
|
|
1144
1278
|
'primer/typography': null,
|
|
1145
1279
|
'primer/box-shadow': null,
|
|
1146
1280
|
},
|
package/package.json
CHANGED
package/plugins/borders.js
CHANGED
|
@@ -1,64 +1,197 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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)
|
|
@@ -2,14 +2,17 @@ import {createRequire} from 'node:module'
|
|
|
2
2
|
|
|
3
3
|
const require = createRequire(import.meta.url)
|
|
4
4
|
|
|
5
|
-
export
|
|
5
|
+
export function primitivesVariables(type) {
|
|
6
6
|
const variables = []
|
|
7
7
|
|
|
8
8
|
const files = []
|
|
9
9
|
switch (type) {
|
|
10
|
-
case '
|
|
10
|
+
case 'spacing':
|
|
11
11
|
files.push('base/size/size.json')
|
|
12
12
|
break
|
|
13
|
+
case 'border':
|
|
14
|
+
files.push('functional/size/border.json')
|
|
15
|
+
break
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
for (const file of files) {
|
|
@@ -18,12 +21,7 @@ export async function primitivesVariables(type) {
|
|
|
18
21
|
|
|
19
22
|
for (const key of Object.keys(data)) {
|
|
20
23
|
const size = data[key]
|
|
21
|
-
const values = size['value']
|
|
22
|
-
const intValue = parseInt(size['original']['value'])
|
|
23
|
-
if (![2, 6].includes(intValue)) {
|
|
24
|
-
values.push(`${intValue + 1}px`)
|
|
25
|
-
values.push(`${intValue - 1}px`)
|
|
26
|
-
}
|
|
24
|
+
const values = typeof size['value'] === 'string' ? [size['value']] : size['value']
|
|
27
25
|
|
|
28
26
|
variables.push({
|
|
29
27
|
name: `--${size['name']}`,
|
|
@@ -34,3 +32,14 @@ export async function primitivesVariables(type) {
|
|
|
34
32
|
|
|
35
33
|
return variables
|
|
36
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
|
+
}
|
package/plugins/spacing.js
CHANGED
|
@@ -1,24 +1,13 @@
|
|
|
1
1
|
import stylelint from 'stylelint'
|
|
2
2
|
import declarationValueIndex from 'stylelint/lib/utils/declarationValueIndex.cjs'
|
|
3
3
|
import valueParser from 'postcss-value-parser'
|
|
4
|
-
import {primitivesVariables} from './lib/
|
|
4
|
+
import {primitivesVariables, walkGroups} from './lib/utils.js'
|
|
5
5
|
|
|
6
6
|
const {
|
|
7
7
|
createPlugin,
|
|
8
8
|
utils: {report, ruleMessages, validateOptions},
|
|
9
9
|
} = stylelint
|
|
10
10
|
|
|
11
|
-
const walkGroups = (root, validate) => {
|
|
12
|
-
for (const node of root.nodes) {
|
|
13
|
-
if (node.type === 'function') {
|
|
14
|
-
walkGroups(node, validate)
|
|
15
|
-
} else {
|
|
16
|
-
validate(node)
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
return root
|
|
20
|
-
}
|
|
21
|
-
|
|
22
11
|
export const ruleName = 'primer/spacing'
|
|
23
12
|
export const messages = ruleMessages(ruleName, {
|
|
24
13
|
rejected: (value, replacement) => {
|
|
@@ -30,20 +19,26 @@ export const messages = ruleMessages(ruleName, {
|
|
|
30
19
|
},
|
|
31
20
|
})
|
|
32
21
|
|
|
33
|
-
|
|
34
|
-
|
|
22
|
+
// Props that we want to check
|
|
23
|
+
const propList = ['padding', 'margin', 'top', 'right', 'bottom', 'left']
|
|
24
|
+
// Values that we want to ignore
|
|
25
|
+
const valueList = ['${']
|
|
26
|
+
|
|
27
|
+
const sizes = primitivesVariables('spacing')
|
|
28
|
+
|
|
29
|
+
// Add +-1px to each value
|
|
30
|
+
for (const size of sizes) {
|
|
31
|
+
const values = size['values']
|
|
32
|
+
const px = parseInt(values.find(value => value.includes('px')))
|
|
33
|
+
if (![2, 6].includes(px)) {
|
|
34
|
+
values.push(`${px + 1}px`)
|
|
35
|
+
values.push(`${px - 1}px`)
|
|
36
|
+
}
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
/** @type {import('stylelint').Rule} */
|
|
38
40
|
const ruleFunction = (primary, secondaryOptions, context) => {
|
|
39
|
-
return
|
|
40
|
-
// Props that we want to check
|
|
41
|
-
const propList = ['padding', 'margin', 'top', 'right', 'bottom', 'left']
|
|
42
|
-
// Values that we want to ignore
|
|
43
|
-
const valueList = ['${']
|
|
44
|
-
|
|
45
|
-
const sizes = await primitivesVariables('size')
|
|
46
|
-
|
|
41
|
+
return (root, result) => {
|
|
47
42
|
const validOptions = validateOptions(result, ruleName, {
|
|
48
43
|
actual: primary,
|
|
49
44
|
possible: [true],
|
|
@@ -128,6 +123,8 @@ const ruleFunction = (primary, secondaryOptions, context) => {
|
|
|
128
123
|
|
|
129
124
|
ruleFunction.ruleName = ruleName
|
|
130
125
|
ruleFunction.messages = messages
|
|
131
|
-
ruleFunction.meta =
|
|
126
|
+
ruleFunction.meta = {
|
|
127
|
+
fixable: true,
|
|
128
|
+
}
|
|
132
129
|
|
|
133
130
|
export default createPlugin(ruleName, ruleFunction)
|