@primer/stylelint-config 13.0.0-rc.fd47ce2 → 13.0.0

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.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$3 from '@primer/css/dist/variables.json' with { type: 'json' };
9
9
  import matchAll from 'string.prototype.matchall';
10
10
 
11
11
  var propertyOrder = [
@@ -181,6 +181,346 @@ 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
+ case 'typography':
198
+ files.push('base/typography/typography.json');
199
+ files.push('functional/typography/typography.json');
200
+ break
201
+ case 'box-shadow':
202
+ files.push('functional/themes/light.json');
203
+ files.push('functional/size/border.json');
204
+ break
205
+ }
206
+
207
+ for (const file of files) {
208
+ // eslint-disable-next-line import/no-dynamic-require
209
+ const data = require$1(`@primer/primitives/dist/styleLint/${file}`);
210
+
211
+ for (const key of Object.keys(data)) {
212
+ const size = data[key];
213
+ const values = typeof size['value'] === 'string' ? [size['value']] : size['value'];
214
+
215
+ variables.push({
216
+ name: `--${size['name']}`,
217
+ values,
218
+ });
219
+ }
220
+ }
221
+
222
+ return variables
223
+ }
224
+
225
+ function walkGroups$1(root, validate) {
226
+ for (const node of root.nodes) {
227
+ if (node.type === 'function') {
228
+ walkGroups$1(node, validate);
229
+ } else {
230
+ validate(node);
231
+ }
232
+ }
233
+ return root
234
+ }
235
+
236
+ const {
237
+ createPlugin: createPlugin$3,
238
+ utils: {report: report$3, ruleMessages: ruleMessages$3, validateOptions: validateOptions$3},
239
+ } = stylelint;
240
+
241
+ const ruleName$5 = 'primer/borders';
242
+ const messages$5 = ruleMessages$3(ruleName$5, {
243
+ rejected: (value, replacement, propName) => {
244
+ if (propName && propName.includes('radius') && value.includes('borderWidth')) {
245
+ return `Border radius variables can not be used for border widths`
246
+ }
247
+
248
+ if ((propName && propName.includes('width')) || (borderShorthand(propName) && value.includes('borderRadius'))) {
249
+ return `Border width variables can not be used for border radii`
250
+ }
251
+
252
+ if (!replacement) {
253
+ 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`
254
+ }
255
+
256
+ return `Please replace '${value}' with a Primer border variable '${replacement['name']}'. https://primer.style/foundations/primitives/size#border`
257
+ },
258
+ });
259
+
260
+ const variables$2 = primitivesVariables('border');
261
+ const sizes$1 = [];
262
+ const radii = [];
263
+
264
+ // Props that we want to check
265
+ const propList$2 = ['border', 'border-width', 'border-radius'];
266
+ // Values that we want to ignore
267
+ const valueList$1 = ['${'];
268
+
269
+ const borderShorthand = prop =>
270
+ /^border(-(top|right|bottom|left|block-start|block-end|inline-start|inline-end))?$/.test(prop);
271
+
272
+ for (const variable of variables$2) {
273
+ const name = variable['name'];
274
+
275
+ if (name.includes('borderWidth')) {
276
+ const value = variable['values']
277
+ .pop()
278
+ .replace(/max|\(|\)/g, '')
279
+ .split(',')[0];
280
+ sizes$1.push({
281
+ name,
282
+ values: [value],
283
+ });
284
+ }
285
+
286
+ if (name.includes('borderRadius')) {
287
+ radii.push(variable);
288
+ }
289
+ }
290
+
291
+ /** @type {import('stylelint').Rule} */
292
+ const ruleFunction$3 = (primary, secondaryOptions, context) => {
293
+ return (root, result) => {
294
+ const validOptions = validateOptions$3(result, ruleName$5, {
295
+ actual: primary,
296
+ possible: [true],
297
+ });
298
+
299
+ if (!validOptions) return
300
+
301
+ root.walkDecls(declNode => {
302
+ const {prop, value} = declNode;
303
+
304
+ if (!propList$2.some(borderProp => prop.startsWith(borderProp))) return
305
+ if (/^border(-(top|right|bottom|left|block-start|block-end|inline-start|inline-end))?-color$/.test(prop)) return
306
+ if (valueList$1.some(valueToIgnore => value.includes(valueToIgnore))) return
307
+
308
+ const problems = [];
309
+
310
+ const parsedValue = walkGroups$1(valueParser(value), node => {
311
+ const checkForVariable = (vars, nodeValue) =>
312
+ vars.some(variable =>
313
+ new RegExp(`${variable['name'].replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`).test(nodeValue),
314
+ );
315
+
316
+ // Only check word types. https://github.com/TrySound/postcss-value-parser#word
317
+ if (node.type !== 'word') {
318
+ return
319
+ }
320
+
321
+ // Exact values to ignore.
322
+ if (
323
+ [
324
+ '*',
325
+ '+',
326
+ '-',
327
+ '/',
328
+ '0',
329
+ 'none',
330
+ 'inherit',
331
+ 'initial',
332
+ 'revert',
333
+ 'revert-layer',
334
+ 'unset',
335
+ 'solid',
336
+ 'dashed',
337
+ 'dotted',
338
+ 'transparent',
339
+ ].includes(node.value)
340
+ ) {
341
+ return
342
+ }
343
+
344
+ const valueUnit = valueParser.unit(node.value);
345
+
346
+ if (valueUnit && (valueUnit.unit === '' || !/^-?[0-9.]+$/.test(valueUnit.number))) {
347
+ return
348
+ }
349
+
350
+ // Skip if the value unit isn't a supported unit.
351
+ if (valueUnit && !['px', 'rem', 'em'].includes(valueUnit.unit)) {
352
+ return
353
+ }
354
+
355
+ // if we're looking at the border property that sets color in shorthand, don't bother checking the color
356
+ if (
357
+ // using border shorthand
358
+ borderShorthand(prop) &&
359
+ // includes a color as a third space-separated value
360
+ value.split(' ').length > 2 &&
361
+ // the color in the third space-separated value includes `node.value`
362
+ value
363
+ .split(' ')
364
+ .slice(2)
365
+ .some(color => color.includes(node.value))
366
+ ) {
367
+ return
368
+ }
369
+
370
+ // If the variable is found in the value, skip it.
371
+ if (prop.includes('width') || borderShorthand(prop)) {
372
+ if (checkForVariable(sizes$1, node.value)) {
373
+ return
374
+ }
375
+ }
376
+
377
+ if (prop.includes('radius')) {
378
+ if (checkForVariable(radii, node.value)) {
379
+ return
380
+ }
381
+ }
382
+
383
+ const replacement = (prop.includes('radius') ? radii : sizes$1).find(variable =>
384
+ variable.values.includes(node.value.replace('-', '')),
385
+ );
386
+ const fixable = replacement && valueUnit && !valueUnit.number.includes('-');
387
+
388
+ if (fixable && context.fix) {
389
+ node.value = node.value.replace(node.value, `var(${replacement['name']})`);
390
+ } else {
391
+ problems.push({
392
+ index: declarationValueIndex(declNode) + node.sourceIndex,
393
+ endIndex: declarationValueIndex(declNode) + node.sourceIndex + node.value.length,
394
+ message: messages$5.rejected(node.value, replacement, prop),
395
+ });
396
+ }
397
+
398
+ return
399
+ });
400
+
401
+ if (context.fix) {
402
+ declNode.value = parsedValue.toString();
403
+ }
404
+
405
+ if (problems.length) {
406
+ for (const err of problems) {
407
+ report$3({
408
+ index: err.index,
409
+ endIndex: err.endIndex,
410
+ message: err.message,
411
+ node: declNode,
412
+ result,
413
+ ruleName: ruleName$5,
414
+ });
415
+ }
416
+ }
417
+ });
418
+ }
419
+ };
420
+
421
+ ruleFunction$3.ruleName = ruleName$5;
422
+ ruleFunction$3.messages = messages$5;
423
+ ruleFunction$3.meta = {
424
+ fixable: true,
425
+ };
426
+
427
+ var borders = createPlugin$3(ruleName$5, ruleFunction$3);
428
+
429
+ const {
430
+ createPlugin: createPlugin$2,
431
+ utils: {report: report$2, ruleMessages: ruleMessages$2, validateOptions: validateOptions$2},
432
+ } = stylelint;
433
+
434
+ const ruleName$4 = 'primer/box-shadow';
435
+ const messages$4 = ruleMessages$2(ruleName$4, {
436
+ rejected: (value, replacement) => {
437
+ if (!replacement) {
438
+ return `Please use a Primer box-shadow variable instead of '${value}'. Consult the primer docs for a suitable replacement. https://primer.style/foundations/primitives/color#shadow or https://primer.style/foundations/primitives/size#border-size`
439
+ }
440
+
441
+ return `Please replace '${value}' with a Primer box-shadow variable '${replacement['name']}'. https://primer.style/foundations/primitives/color#shadow or https://primer.style/foundations/primitives/size#border-size`
442
+ },
443
+ });
444
+
445
+ const variables$1 = primitivesVariables('box-shadow');
446
+ const shadows = [];
447
+
448
+ for (const variable of variables$1) {
449
+ const name = variable['name'];
450
+
451
+ // TODO: Decide if this is safe. Someday we might have variables that
452
+ // have 'shadow' in the name but aren't full box-shadows.
453
+ if (name.includes('shadow') || name.includes('boxShadow')) {
454
+ shadows.push(variable);
455
+ }
456
+ }
457
+
458
+ /** @type {import('stylelint').Rule} */
459
+ const ruleFunction$2 = (primary, secondaryOptions, context) => {
460
+ return (root, result) => {
461
+ const validOptions = validateOptions$2(result, ruleName$4, {
462
+ actual: primary,
463
+ possible: [true],
464
+ });
465
+ const validValues = shadows;
466
+
467
+ if (!validOptions) return
468
+
469
+ root.walkDecls(declNode => {
470
+ const {prop, value} = declNode;
471
+
472
+ if (prop !== 'box-shadow') return
473
+
474
+ if (value === 'none') return
475
+
476
+ const problems = [];
477
+
478
+ const checkForVariable = (vars, nodeValue) => {
479
+ return vars.some(variable =>
480
+ new RegExp(`${variable['name'].replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`).test(nodeValue),
481
+ )
482
+ };
483
+
484
+ if (checkForVariable(validValues, value)) {
485
+ return
486
+ }
487
+
488
+ const replacement = validValues.find(variable => variable.values.includes(value));
489
+
490
+ if (replacement && context.fix) {
491
+ declNode.value = value.replace(value, `var(${replacement['name']})`);
492
+ } else {
493
+ problems.push({
494
+ index: declarationValueIndex(declNode),
495
+ endIndex: declarationValueIndex(declNode) + value.length,
496
+ message: messages$4.rejected(value, replacement),
497
+ });
498
+ }
499
+
500
+ if (problems.length) {
501
+ for (const err of problems) {
502
+ report$2({
503
+ index: err.index,
504
+ endIndex: err.endIndex,
505
+ message: err.message,
506
+ node: declNode,
507
+ result,
508
+ ruleName: ruleName$4,
509
+ });
510
+ }
511
+ }
512
+ });
513
+ }
514
+ };
515
+
516
+ ruleFunction$2.ruleName = ruleName$4;
517
+ ruleFunction$2.messages = messages$4;
518
+ ruleFunction$2.meta = {
519
+ fixable: true,
520
+ };
521
+
522
+ var boxShadow = createPlugin$2(ruleName$4, ruleFunction$2);
523
+
184
524
  const SKIP_VALUE_NODE_TYPES = new Set(['space', 'div']);
185
525
  const SKIP_AT_RULE_NAMES = new Set(['each', 'for', 'function', 'mixin']);
186
526
 
@@ -425,18 +765,18 @@ function createVariableRule(ruleName, rules, url) {
425
765
  let actualRules = rules;
426
766
  let overrides = options.rules;
427
767
  if (typeof rules === 'function') {
428
- actualRules = rules({variables, options, ruleName});
768
+ actualRules = rules({variables: variables$3, options, ruleName});
429
769
  } else {
430
770
  actualRules = Object.assign({}, rules);
431
771
  }
432
772
  if (typeof overrides === 'function') {
433
773
  delete options.rules;
434
- overrides = overrides({rules: actualRules, options, ruleName, variables});
774
+ overrides = overrides({rules: actualRules, options, ruleName, variables: variables$3});
435
775
  }
436
776
  if (overrides) {
437
777
  Object.assign(actualRules, overrides);
438
778
  }
439
- const validate = declarationValidator(actualRules, {variables});
779
+ const validate = declarationValidator(actualRules, {variables: variables$3});
440
780
 
441
781
  // The stylelint docs suggest respecting a "disableFix" rule option that
442
782
  // overrides the "global" context.fix (--fix) linting option.
@@ -470,10 +810,9 @@ function createVariableRule(ruleName, rules, url) {
470
810
  const message = stylelint.utils
471
811
  .ruleMessages(ruleName, {
472
812
  rejected: m => {
473
- if (url) {
813
+ {
474
814
  return `${m}. See ${url}.`
475
815
  }
476
- return `${m}.`
477
816
  },
478
817
  })
479
818
  .rejected(error);
@@ -498,90 +837,6 @@ function createVariableRule(ruleName, rules, url) {
498
837
 
499
838
  function noop$2() {}
500
839
 
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
- var boxShadow = createVariableRule(
565
- 'primer/box-shadow',
566
- {
567
- 'box shadow': {
568
- expects: 'a box-shadow variable',
569
- props: 'box-shadow',
570
- values: [
571
- '$box-shadow*',
572
- '$*-shadow',
573
- 'none',
574
- // Match variables in any of the following formats: --color-shadow-*, --color-*-shadow-*, --color-*-shadow, --shadow-*, *shadow*
575
- /var\(--color-(.+-)*shadow(-.+)*\)/,
576
- /var\(--shadow(-.+)*\)/,
577
- /var\((.+-)*shadow(-.+)*\)/,
578
- ],
579
- singular: true,
580
- },
581
- },
582
- 'https://primer.style/css/utilities/box-shadow',
583
- );
584
-
585
840
  const bgVars = [
586
841
  '$bg-*',
587
842
  '$tooltip-background-color',
@@ -624,9 +879,9 @@ var colors = createVariableRule(
624
879
  'https://primer.style/primitives/colors',
625
880
  );
626
881
 
627
- const ruleName$2 = 'primer/responsive-widths';
882
+ const ruleName$3 = 'primer/responsive-widths';
628
883
 
629
- const messages$2 = stylelint.utils.ruleMessages(ruleName$2, {
884
+ const messages$3 = stylelint.utils.ruleMessages(ruleName$3, {
630
885
  rejected: value => {
631
886
  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`
632
887
  },
@@ -634,10 +889,10 @@ const messages$2 = stylelint.utils.ruleMessages(ruleName$2, {
634
889
 
635
890
  // 320px is the smallest viewport size that we support
636
891
 
637
- const walkGroups$1 = (root, validate) => {
892
+ const walkGroups = (root, validate) => {
638
893
  for (const node of root.nodes) {
639
894
  if (node.type === 'function') {
640
- walkGroups$1(node, validate);
895
+ walkGroups(node, validate);
641
896
  } else {
642
897
  validate(node);
643
898
  }
@@ -646,7 +901,7 @@ const walkGroups$1 = (root, validate) => {
646
901
  };
647
902
 
648
903
  // eslint-disable-next-line no-unused-vars
649
- var responsiveWidths = stylelint.createPlugin(ruleName$2, (enabled, options = {}, context) => {
904
+ var responsiveWidths = stylelint.createPlugin(ruleName$3, (enabled, options = {}, context) => {
650
905
  if (!enabled) {
651
906
  return noop$1
652
907
  }
@@ -664,7 +919,7 @@ var responsiveWidths = stylelint.createPlugin(ruleName$2, (enabled, options = {}
664
919
 
665
920
  const problems = [];
666
921
 
667
- walkGroups$1(valueParser(decl.value), node => {
922
+ walkGroups(valueParser(decl.value), node => {
668
923
  // Only check word types. https://github.com/TrySound/postcss-value-parser#word
669
924
  if (node.type !== 'word') {
670
925
  return
@@ -682,7 +937,7 @@ var responsiveWidths = stylelint.createPlugin(ruleName$2, (enabled, options = {}
682
937
  if (parseInt(valueUnit.number) > 320) {
683
938
  problems.push({
684
939
  index: declarationValueIndex(decl) + node.sourceIndex,
685
- message: messages$2.rejected(node.value),
940
+ message: messages$3.rejected(node.value),
686
941
  });
687
942
  }
688
943
  break
@@ -690,7 +945,7 @@ var responsiveWidths = stylelint.createPlugin(ruleName$2, (enabled, options = {}
690
945
  if (parseInt(valueUnit.number) > 100) {
691
946
  problems.push({
692
947
  index: declarationValueIndex(decl) + node.sourceIndex,
693
- message: messages$2.rejected(node.value),
948
+ message: messages$3.rejected(node.value),
694
949
  });
695
950
  }
696
951
  break
@@ -704,7 +959,7 @@ var responsiveWidths = stylelint.createPlugin(ruleName$2, (enabled, options = {}
704
959
  message: err.message,
705
960
  node: decl,
706
961
  result,
707
- ruleName: ruleName$2,
962
+ ruleName: ruleName$3,
708
963
  });
709
964
  }
710
965
  }
@@ -716,59 +971,13 @@ var responsiveWidths = stylelint.createPlugin(ruleName$2, (enabled, options = {}
716
971
 
717
972
  function noop$1() {}
718
973
 
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
974
  const {
755
- createPlugin,
756
- utils: {report, ruleMessages, validateOptions},
975
+ createPlugin: createPlugin$1,
976
+ utils: {report: report$1, ruleMessages: ruleMessages$1, validateOptions: validateOptions$1},
757
977
  } = stylelint;
758
978
 
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
- const ruleName$1 = 'primer/spacing';
771
- const messages$1 = ruleMessages(ruleName$1, {
979
+ const ruleName$2 = 'primer/spacing';
980
+ const messages$2 = ruleMessages$1(ruleName$2, {
772
981
  rejected: (value, replacement) => {
773
982
  if (!replacement) {
774
983
  return `Please use a primer size variable instead of '${value}'. Consult the primer docs for a suitable replacement. https://primer.style/foundations/primitives/size`
@@ -778,21 +987,27 @@ const messages$1 = ruleMessages(ruleName$1, {
778
987
  },
779
988
  });
780
989
 
781
- const meta = {
782
- fixable: true,
783
- };
990
+ // Props that we want to check
991
+ const propList$1 = ['padding', 'margin', 'top', 'right', 'bottom', 'left'];
992
+ // Values that we want to ignore
993
+ const valueList = ['${'];
784
994
 
785
- /** @type {import('stylelint').Rule} */
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 = ['${'];
995
+ const sizes = primitivesVariables('spacing');
792
996
 
793
- const sizes = await primitivesVariables('size');
997
+ // Add +-1px to each value
998
+ for (const size of sizes) {
999
+ const values = size['values'];
1000
+ const px = parseInt(values.find(value => value.includes('px')));
1001
+ if (![2, 6].includes(px)) {
1002
+ values.push(`${px + 1}px`);
1003
+ values.push(`${px - 1}px`);
1004
+ }
1005
+ }
794
1006
 
795
- const validOptions = validateOptions(result, ruleName$1, {
1007
+ /** @type {import('stylelint').Rule} */
1008
+ const ruleFunction$1 = (primary, secondaryOptions, context) => {
1009
+ return (root, result) => {
1010
+ const validOptions = validateOptions$1(result, ruleName$2, {
796
1011
  actual: primary,
797
1012
  possible: [true],
798
1013
  });
@@ -802,12 +1017,12 @@ const ruleFunction = (primary, secondaryOptions, context) => {
802
1017
  root.walkDecls(declNode => {
803
1018
  const {prop, value} = declNode;
804
1019
 
805
- if (!propList.some(spacingProp => prop.startsWith(spacingProp))) return
1020
+ if (!propList$1.some(spacingProp => prop.startsWith(spacingProp))) return
806
1021
  if (valueList.some(valueToIgnore => value.includes(valueToIgnore))) return
807
1022
 
808
1023
  const problems = [];
809
1024
 
810
- const parsedValue = walkGroups(valueParser(value), node => {
1025
+ const parsedValue = walkGroups$1(valueParser(value), node => {
811
1026
  // Only check word types. https://github.com/TrySound/postcss-value-parser#word
812
1027
  if (node.type !== 'word') {
813
1028
  return
@@ -847,7 +1062,7 @@ const ruleFunction = (primary, secondaryOptions, context) => {
847
1062
  problems.push({
848
1063
  index: declarationValueIndex(declNode) + node.sourceIndex,
849
1064
  endIndex: declarationValueIndex(declNode) + node.sourceIndex + node.value.length,
850
- message: messages$1.rejected(node.value, replacement),
1065
+ message: messages$2.rejected(node.value, replacement),
851
1066
  });
852
1067
  }
853
1068
 
@@ -858,6 +1073,189 @@ const ruleFunction = (primary, secondaryOptions, context) => {
858
1073
  declNode.value = parsedValue.toString();
859
1074
  }
860
1075
 
1076
+ if (problems.length) {
1077
+ for (const err of problems) {
1078
+ report$1({
1079
+ index: err.index,
1080
+ endIndex: err.endIndex,
1081
+ message: err.message,
1082
+ node: declNode,
1083
+ result,
1084
+ ruleName: ruleName$2,
1085
+ });
1086
+ }
1087
+ }
1088
+ });
1089
+ }
1090
+ };
1091
+
1092
+ ruleFunction$1.ruleName = ruleName$2;
1093
+ ruleFunction$1.messages = messages$2;
1094
+ ruleFunction$1.meta = {
1095
+ fixable: true,
1096
+ };
1097
+
1098
+ var spacing = createPlugin$1(ruleName$2, ruleFunction$1);
1099
+
1100
+ const {
1101
+ createPlugin,
1102
+ utils: {report, ruleMessages, validateOptions},
1103
+ } = stylelint;
1104
+
1105
+ const ruleName$1 = 'primer/typography';
1106
+ const messages$1 = ruleMessages(ruleName$1, {
1107
+ rejected: (value, replacement) => {
1108
+ // no possible replacement
1109
+ if (!replacement) {
1110
+ return `Please use a Primer typography variable instead of '${value}'. Consult the primer docs for a suitable replacement. https://primer.style/foundations/primitives/typography`
1111
+ }
1112
+
1113
+ // multiple possible replacements
1114
+ if (replacement.length) {
1115
+ return `Please use one of the following Primer typography variables instead of '${value}': ${replacement.map(replacementObj => `'${replacementObj.name}'`).join(', ')}. https://primer.style/foundations/primitives/typography`
1116
+ }
1117
+
1118
+ // one possible replacement
1119
+ return `Please replace '${value}' with Primer typography variable '${replacement['name']}'. https://primer.style/foundations/primitives/typography`
1120
+ },
1121
+ });
1122
+
1123
+ const fontWeightKeywordMap = {
1124
+ normal: 400,
1125
+ bold: 600,
1126
+ bolder: 600,
1127
+ lighter: 300,
1128
+ };
1129
+ const getClosestFontWeight = (goalWeightNumber, fontWeightsTokens) => {
1130
+ return fontWeightsTokens.reduce((prev, curr) =>
1131
+ Math.abs(curr.values - goalWeightNumber) < Math.abs(prev.values - goalWeightNumber) ? curr : prev,
1132
+ ).values
1133
+ };
1134
+
1135
+ const variables = primitivesVariables('typography');
1136
+ const fontSizes = [];
1137
+ const fontWeights = [];
1138
+ const lineHeights = [];
1139
+ const fontStacks = [];
1140
+ const fontShorthands = [];
1141
+
1142
+ // Props that we want to check for typography variables
1143
+ const propList = ['font-size', 'font-weight', 'line-height', 'font-family', 'font'];
1144
+
1145
+ for (const variable of variables) {
1146
+ const name = variable['name'];
1147
+
1148
+ if (name.includes('size')) {
1149
+ fontSizes.push(variable);
1150
+ }
1151
+
1152
+ if (name.includes('weight')) {
1153
+ fontWeights.push(variable);
1154
+ }
1155
+
1156
+ if (name.includes('lineHeight')) {
1157
+ lineHeights.push(variable);
1158
+ }
1159
+
1160
+ if (name.includes('fontStack')) {
1161
+ fontStacks.push(variable);
1162
+ }
1163
+
1164
+ if (name.includes('shorthand')) {
1165
+ fontShorthands.push(variable);
1166
+ }
1167
+ }
1168
+
1169
+ /** @type {import('stylelint').Rule} */
1170
+ const ruleFunction = (primary, secondaryOptions, context) => {
1171
+ return (root, result) => {
1172
+ const validOptions = validateOptions(result, ruleName$1, {
1173
+ actual: primary,
1174
+ possible: [true],
1175
+ });
1176
+ let validValues = [];
1177
+
1178
+ if (!validOptions) return
1179
+
1180
+ root.walkDecls(declNode => {
1181
+ const {prop, value} = declNode;
1182
+
1183
+ if (!propList.some(typographyProp => prop.startsWith(typographyProp))) return
1184
+
1185
+ const problems = [];
1186
+
1187
+ const checkForVariable = (vars, nodeValue) =>
1188
+ vars.some(variable =>
1189
+ new RegExp(`${variable['name'].replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`).test(nodeValue),
1190
+ );
1191
+
1192
+ // Exact values to ignore.
1193
+ if (value === 'inherit') {
1194
+ return
1195
+ }
1196
+
1197
+ switch (prop) {
1198
+ case 'font-size':
1199
+ validValues = fontSizes;
1200
+ break
1201
+ case 'font-weight':
1202
+ validValues = fontWeights;
1203
+ break
1204
+ case 'line-height':
1205
+ validValues = lineHeights;
1206
+ break
1207
+ case 'font-family':
1208
+ validValues = fontStacks;
1209
+ break
1210
+ case 'font':
1211
+ validValues = fontShorthands;
1212
+ break
1213
+ default:
1214
+ validValues = [];
1215
+ }
1216
+
1217
+ if (checkForVariable(validValues, value)) {
1218
+ return
1219
+ }
1220
+
1221
+ const getReplacements = () => {
1222
+ const replacementTokens = validValues.filter(variable => {
1223
+ if (!(variable.values instanceof Array)) {
1224
+ let nodeValue = value;
1225
+
1226
+ if (prop === 'font-weight') {
1227
+ nodeValue = getClosestFontWeight(fontWeightKeywordMap[value] || value, fontWeights);
1228
+ }
1229
+
1230
+ return variable.values.toString() === nodeValue.toString()
1231
+ }
1232
+
1233
+ return variable.values.includes(value.replace('-', ''))
1234
+ });
1235
+
1236
+ if (!replacementTokens.length) {
1237
+ return
1238
+ }
1239
+
1240
+ if (replacementTokens.length > 1) {
1241
+ return replacementTokens
1242
+ }
1243
+
1244
+ return replacementTokens[0]
1245
+ };
1246
+ const replacement = getReplacements();
1247
+ const fixable = replacement && !replacement.length;
1248
+
1249
+ if (fixable && context.fix) {
1250
+ declNode.value = value.replace(value, `var(${replacement['name']})`);
1251
+ } else {
1252
+ problems.push({
1253
+ index: declarationValueIndex(declNode),
1254
+ endIndex: declarationValueIndex(declNode) + value.length,
1255
+ message: messages$1.rejected(value, replacement, prop),
1256
+ });
1257
+ }
1258
+
861
1259
  if (problems.length) {
862
1260
  for (const err of problems) {
863
1261
  report({
@@ -876,32 +1274,11 @@ const ruleFunction = (primary, secondaryOptions, context) => {
876
1274
 
877
1275
  ruleFunction.ruleName = ruleName$1;
878
1276
  ruleFunction.messages = messages$1;
879
- ruleFunction.meta = meta;
880
-
881
- var spacing = createPlugin(ruleName$1, ruleFunction);
1277
+ ruleFunction.meta = {
1278
+ fixable: true,
1279
+ };
882
1280
 
883
- var typography = createVariableRule(
884
- 'primer/typography',
885
- {
886
- 'font-size': {
887
- expects: 'a font-size variable',
888
- values: ['$body-font-size', '$h{000,00,0,1,2,3,4,5,6}-size', '$font-size-*', '1', '1em', 'inherit'],
889
- },
890
- 'font-weight': {
891
- props: 'font-weight',
892
- values: ['$font-weight-*', 'inherit'],
893
- replacements: {
894
- bold: '$font-weight-bold',
895
- normal: '$font-weight-normal',
896
- },
897
- },
898
- 'line-height': {
899
- props: 'line-height',
900
- values: ['$body-line-height', '$lh-*', '0', '1', '1em', 'inherit'],
901
- },
902
- },
903
- 'https://primer.style/css/utilities/typography',
904
- );
1281
+ var typography = createPlugin(ruleName$1, ruleFunction);
905
1282
 
906
1283
  const ruleName = 'primer/no-display-colors';
907
1284
  const messages = stylelint.utils.ruleMessages(ruleName, {
@@ -1086,9 +1463,6 @@ var index = {
1086
1463
  'length-zero-no-unit': null,
1087
1464
  'selector-max-type': null,
1088
1465
  'primer/colors': null,
1089
- 'primer/borders': null,
1090
- 'primer/typography': null,
1091
- 'primer/box-shadow': null,
1092
1466
  },
1093
1467
  },
1094
1468
  {
@@ -1113,7 +1487,6 @@ var index = {
1113
1487
  },
1114
1488
  {
1115
1489
  files: ['**/*.module.css'],
1116
- plugins: ['stylelint-css-modules-no-global-scoped-selector'],
1117
1490
  rules: {
1118
1491
  'property-no-unknown': [
1119
1492
  true,
@@ -1138,11 +1511,6 @@ var index = {
1138
1511
  ignoreFunctions: ['global'],
1139
1512
  },
1140
1513
  ],
1141
- 'css-modules/no-global-scoped-selector': true,
1142
- // temporarily disabiling Primer plugins while we work on upgrades https://github.com/github/primer/issues/3165
1143
- 'primer/borders': null,
1144
- 'primer/typography': null,
1145
- 'primer/box-shadow': null,
1146
1514
  },
1147
1515
  },
1148
1516
  ],