@primer/stylelint-config 13.0.0-rc.78170fc → 13.0.0-rc.7a254e8

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,12 +1,12 @@
1
1
  import browsers from '@github/browserslist-config';
2
2
  import stylelint from 'stylelint';
3
- import anymatch from 'anymatch';
3
+ import declarationValueIndex from 'stylelint/lib/utils/declarationValueIndex.cjs';
4
4
  import valueParser from 'postcss-value-parser';
5
+ import { createRequire } from 'node:module';
6
+ import anymatch from 'anymatch';
5
7
  import TapMap from 'tap-map';
6
- import variables from '@primer/css/dist/variables.json' assert { type: 'json' };
7
- import declarationValueIndex from 'stylelint/lib/utils/declarationValueIndex.cjs';
8
+ import variables$2 from '@primer/css/dist/variables.json' with { type: 'json' };
8
9
  import matchAll from 'string.prototype.matchall';
9
- import { createRequire } from 'node:module';
10
10
 
11
11
  var propertyOrder = [
12
12
  'all',
@@ -181,6 +181,247 @@ 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
+ }
202
+
203
+ for (const file of files) {
204
+ // eslint-disable-next-line import/no-dynamic-require
205
+ const data = require$1(`@primer/primitives/dist/styleLint/${file}`);
206
+
207
+ for (const key of Object.keys(data)) {
208
+ const size = data[key];
209
+ const values = typeof size['value'] === 'string' ? [size['value']] : size['value'];
210
+
211
+ variables.push({
212
+ name: `--${size['name']}`,
213
+ values,
214
+ });
215
+ }
216
+ }
217
+
218
+ return variables
219
+ }
220
+
221
+ function walkGroups$1(root, validate) {
222
+ for (const node of root.nodes) {
223
+ if (node.type === 'function') {
224
+ walkGroups$1(node, validate);
225
+ } else {
226
+ validate(node);
227
+ }
228
+ }
229
+ return root
230
+ }
231
+
232
+ const {
233
+ createPlugin: createPlugin$2,
234
+ utils: {report: report$2, ruleMessages: ruleMessages$2, validateOptions: validateOptions$2},
235
+ } = stylelint;
236
+
237
+ const ruleName$4 = 'primer/borders';
238
+ const messages$4 = ruleMessages$2(ruleName$4, {
239
+ rejected: (value, replacement, propName) => {
240
+ if (propName && propName.includes('radius') && value.includes('borderWidth')) {
241
+ return `Border radius variables can not be used for border widths`
242
+ }
243
+
244
+ if ((propName && propName.includes('width')) || (borderShorthand(propName) && value.includes('borderRadius'))) {
245
+ return `Border width variables can not be used for border radii`
246
+ }
247
+
248
+ if (!replacement) {
249
+ 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`
250
+ }
251
+
252
+ return `Please replace '${value}' with a Primer border variable '${replacement['name']}'. https://primer.style/foundations/primitives/size#border`
253
+ },
254
+ });
255
+
256
+ const variables$1 = primitivesVariables('border');
257
+ const sizes$1 = [];
258
+ const radii = [];
259
+
260
+ // Props that we want to check
261
+ const propList$2 = ['border', 'border-width', 'border-radius'];
262
+ // Values that we want to ignore
263
+ const valueList$1 = ['${'];
264
+
265
+ const borderShorthand = prop =>
266
+ /^border(-(top|right|bottom|left|block-start|block-end|inline-start|inline-end))?$/.test(prop);
267
+
268
+ for (const variable of variables$1) {
269
+ const name = variable['name'];
270
+
271
+ if (name.includes('borderWidth')) {
272
+ const value = variable['values']
273
+ .pop()
274
+ .replace(/max|\(|\)/g, '')
275
+ .split(',')[0];
276
+ sizes$1.push({
277
+ name,
278
+ values: [value],
279
+ });
280
+ }
281
+
282
+ if (name.includes('borderRadius')) {
283
+ radii.push(variable);
284
+ }
285
+ }
286
+
287
+ /** @type {import('stylelint').Rule} */
288
+ const ruleFunction$2 = (primary, secondaryOptions, context) => {
289
+ return (root, result) => {
290
+ const validOptions = validateOptions$2(result, ruleName$4, {
291
+ actual: primary,
292
+ possible: [true],
293
+ });
294
+
295
+ if (!validOptions) return
296
+
297
+ root.walkDecls(declNode => {
298
+ const {prop, value} = declNode;
299
+
300
+ if (!propList$2.some(borderProp => prop.startsWith(borderProp))) return
301
+ if (/^border(-(top|right|bottom|left|block-start|block-end|inline-start|inline-end))?-color$/.test(prop)) return
302
+ if (valueList$1.some(valueToIgnore => value.includes(valueToIgnore))) return
303
+
304
+ const problems = [];
305
+
306
+ const parsedValue = walkGroups$1(valueParser(value), node => {
307
+ const checkForVariable = (vars, nodeValue) =>
308
+ vars.some(variable =>
309
+ new RegExp(`${variable['name'].replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`).test(nodeValue),
310
+ );
311
+
312
+ // Only check word types. https://github.com/TrySound/postcss-value-parser#word
313
+ if (node.type !== 'word') {
314
+ return
315
+ }
316
+
317
+ // Exact values to ignore.
318
+ if (
319
+ [
320
+ '*',
321
+ '+',
322
+ '-',
323
+ '/',
324
+ '0',
325
+ 'none',
326
+ 'inherit',
327
+ 'initial',
328
+ 'revert',
329
+ 'revert-layer',
330
+ 'unset',
331
+ 'solid',
332
+ 'dashed',
333
+ 'dotted',
334
+ 'transparent',
335
+ ].includes(node.value)
336
+ ) {
337
+ return
338
+ }
339
+
340
+ const valueUnit = valueParser.unit(node.value);
341
+
342
+ if (valueUnit && (valueUnit.unit === '' || !/^-?[0-9.]+$/.test(valueUnit.number))) {
343
+ return
344
+ }
345
+
346
+ // Skip if the value unit isn't a supported unit.
347
+ if (valueUnit && !['px', 'rem', 'em'].includes(valueUnit.unit)) {
348
+ return
349
+ }
350
+
351
+ // if we're looking at the border property that sets color in shorthand, don't bother checking the color
352
+ if (
353
+ // using border shorthand
354
+ borderShorthand(prop) &&
355
+ // includes a color as a third space-separated value
356
+ value.split(' ').length > 2 &&
357
+ // the color in the third space-separated value includes `node.value`
358
+ value
359
+ .split(' ')
360
+ .slice(2)
361
+ .some(color => color.includes(node.value))
362
+ ) {
363
+ return
364
+ }
365
+
366
+ // If the variable is found in the value, skip it.
367
+ if (prop.includes('width') || borderShorthand(prop)) {
368
+ if (checkForVariable(sizes$1, node.value)) {
369
+ return
370
+ }
371
+ }
372
+
373
+ if (prop.includes('radius')) {
374
+ if (checkForVariable(radii, node.value)) {
375
+ return
376
+ }
377
+ }
378
+
379
+ const replacement = (prop.includes('radius') ? radii : sizes$1).find(variable =>
380
+ variable.values.includes(node.value.replace('-', '')),
381
+ );
382
+ const fixable = replacement && valueUnit && !valueUnit.number.includes('-');
383
+
384
+ if (fixable && context.fix) {
385
+ node.value = node.value.replace(node.value, `var(${replacement['name']})`);
386
+ } else {
387
+ problems.push({
388
+ index: declarationValueIndex(declNode) + node.sourceIndex,
389
+ endIndex: declarationValueIndex(declNode) + node.sourceIndex + node.value.length,
390
+ message: messages$4.rejected(node.value, replacement, prop),
391
+ });
392
+ }
393
+
394
+ return
395
+ });
396
+
397
+ if (context.fix) {
398
+ declNode.value = parsedValue.toString();
399
+ }
400
+
401
+ if (problems.length) {
402
+ for (const err of problems) {
403
+ report$2({
404
+ index: err.index,
405
+ endIndex: err.endIndex,
406
+ message: err.message,
407
+ node: declNode,
408
+ result,
409
+ ruleName: ruleName$4,
410
+ });
411
+ }
412
+ }
413
+ });
414
+ }
415
+ };
416
+
417
+ ruleFunction$2.ruleName = ruleName$4;
418
+ ruleFunction$2.messages = messages$4;
419
+ ruleFunction$2.meta = {
420
+ fixable: true,
421
+ };
422
+
423
+ var borders = createPlugin$2(ruleName$4, ruleFunction$2);
424
+
184
425
  const SKIP_VALUE_NODE_TYPES = new Set(['space', 'div']);
185
426
  const SKIP_AT_RULE_NAMES = new Set(['each', 'for', 'function', 'mixin']);
186
427
 
@@ -419,24 +660,24 @@ function closest(node, test) {
419
660
  function createVariableRule(ruleName, rules, url) {
420
661
  const plugin = stylelint.createPlugin(ruleName, (enabled, options = {}, context) => {
421
662
  if (enabled === false) {
422
- return noop$4
663
+ return noop$2
423
664
  }
424
665
 
425
666
  let actualRules = rules;
426
667
  let overrides = options.rules;
427
668
  if (typeof rules === 'function') {
428
- actualRules = rules({variables, options, ruleName});
669
+ actualRules = rules({variables: variables$2, options, ruleName});
429
670
  } else {
430
671
  actualRules = Object.assign({}, rules);
431
672
  }
432
673
  if (typeof overrides === 'function') {
433
674
  delete options.rules;
434
- overrides = overrides({rules: actualRules, options, ruleName, variables});
675
+ overrides = overrides({rules: actualRules, options, ruleName, variables: variables$2});
435
676
  }
436
677
  if (overrides) {
437
678
  Object.assign(actualRules, overrides);
438
679
  }
439
- const validate = declarationValidator(actualRules, {variables});
680
+ const validate = declarationValidator(actualRules, {variables: variables$2});
440
681
 
441
682
  // The stylelint docs suggest respecting a "disableFix" rule option that
442
683
  // overrides the "global" context.fix (--fix) linting option.
@@ -496,70 +737,7 @@ function createVariableRule(ruleName, rules, url) {
496
737
  return plugin
497
738
  }
498
739
 
499
- function noop$4() {}
500
-
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
- );
740
+ function noop$2() {}
563
741
 
564
742
  var boxShadow = createVariableRule(
565
743
  'primer/box-shadow',
@@ -634,10 +812,10 @@ const messages$3 = stylelint.utils.ruleMessages(ruleName$3, {
634
812
 
635
813
  // 320px is the smallest viewport size that we support
636
814
 
637
- const walkGroups$1 = (root, validate) => {
815
+ const walkGroups = (root, validate) => {
638
816
  for (const node of root.nodes) {
639
817
  if (node.type === 'function') {
640
- walkGroups$1(node, validate);
818
+ walkGroups(node, validate);
641
819
  } else {
642
820
  validate(node);
643
821
  }
@@ -648,7 +826,7 @@ const walkGroups$1 = (root, validate) => {
648
826
  // eslint-disable-next-line no-unused-vars
649
827
  var responsiveWidths = stylelint.createPlugin(ruleName$3, (enabled, options = {}, context) => {
650
828
  if (!enabled) {
651
- return noop$3
829
+ return noop$1
652
830
  }
653
831
 
654
832
  const lintResult = (root, result) => {
@@ -659,12 +837,12 @@ var responsiveWidths = stylelint.createPlugin(ruleName$3, (enabled, options = {}
659
837
  }
660
838
 
661
839
  if (decl.type !== 'decl' || !decl.prop.match(/^(min-width|width)/)) {
662
- return noop$3
840
+ return noop$1
663
841
  }
664
842
 
665
843
  const problems = [];
666
844
 
667
- walkGroups$1(valueParser(decl.value), node => {
845
+ walkGroups(valueParser(decl.value), node => {
668
846
  // Only check word types. https://github.com/TrySound/postcss-value-parser#word
669
847
  if (node.type !== 'word') {
670
848
  return
@@ -714,70 +892,60 @@ var responsiveWidths = stylelint.createPlugin(ruleName$3, (enabled, options = {}
714
892
  return lintResult
715
893
  });
716
894
 
717
- function noop$3() {}
718
-
719
- // TODO: Pull this in from primer/primitives
720
- const spacerValues = {
721
- '$spacer-1': '4px',
722
- '$spacer-2': '8px',
723
- '$spacer-3': '16px',
724
- '$spacer-4': '24px',
725
- '$spacer-5': '32px',
726
- '$spacer-6': '40px',
727
- '$spacer-7': '48px',
728
- '$spacer-8': '64px',
729
- '$spacer-9': '80px',
730
- '$spacer-10': '96px',
731
- '$spacer-11': '112px',
732
- '$spacer-12': '128px',
733
- '$em-spacer-1': '0.0625em',
734
- '$em-spacer-2': '0.125em',
735
- '$em-spacer-3': '0.25em',
736
- '$em-spacer-4': '0.375em',
737
- '$em-spacer-5': '0.5em',
738
- '$em-spacer-6': '0.75em',
739
- };
895
+ function noop$1() {}
896
+
897
+ const {
898
+ createPlugin: createPlugin$1,
899
+ utils: {report: report$1, ruleMessages: ruleMessages$1, validateOptions: validateOptions$1},
900
+ } = stylelint;
740
901
 
741
902
  const ruleName$2 = 'primer/spacing';
742
- const messages$2 = stylelint.utils.ruleMessages(ruleName$2, {
903
+ const messages$2 = ruleMessages$1(ruleName$2, {
743
904
  rejected: (value, replacement) => {
744
- if (replacement === null) {
745
- return `Please use a primer spacer variable instead of '${value}'. Consult the primer docs for a suitable replacement. https://primer.style/css/storybook/?path=/docs/support-spacing--docs`
905
+ if (!replacement) {
906
+ return `Please use a primer size variable instead of '${value}'. Consult the primer docs for a suitable replacement. https://primer.style/foundations/primitives/size`
746
907
  }
747
908
 
748
- return `Please replace ${value} with spacing variable '${replacement}'.`
909
+ return `Please replace '${value}' with size variable '${replacement['name']}'. https://primer.style/foundations/primitives/size`
749
910
  },
750
911
  });
751
912
 
752
- const walkGroups = (root, validate) => {
753
- for (const node of root.nodes) {
754
- if (node.type === 'function') {
755
- walkGroups(node, validate);
756
- } else {
757
- validate(node);
758
- }
759
- }
760
- return root
761
- };
913
+ // Props that we want to check
914
+ const propList$1 = ['padding', 'margin', 'top', 'right', 'bottom', 'left'];
915
+ // Values that we want to ignore
916
+ const valueList = ['${'];
762
917
 
763
- // eslint-disable-next-line no-unused-vars
764
- var spacing = stylelint.createPlugin(ruleName$2, (enabled, options = {}, context) => {
765
- if (!enabled) {
766
- return noop$2
918
+ const sizes = primitivesVariables('spacing');
919
+
920
+ // Add +-1px to each value
921
+ for (const size of sizes) {
922
+ const values = size['values'];
923
+ const px = parseInt(values.find(value => value.includes('px')));
924
+ if (![2, 6].includes(px)) {
925
+ values.push(`${px + 1}px`);
926
+ values.push(`${px - 1}px`);
767
927
  }
928
+ }
768
929
 
769
- const lintResult = (root, result) => {
770
- root.walk(decl => {
771
- if (decl.type !== 'decl' || !decl.prop.match(/^(padding|margin)/)) {
772
- return noop$2
773
- }
930
+ /** @type {import('stylelint').Rule} */
931
+ const ruleFunction$1 = (primary, secondaryOptions, context) => {
932
+ return (root, result) => {
933
+ const validOptions = validateOptions$1(result, ruleName$2, {
934
+ actual: primary,
935
+ possible: [true],
936
+ });
774
937
 
775
- const problems = [];
938
+ if (!validOptions) return
939
+
940
+ root.walkDecls(declNode => {
941
+ const {prop, value} = declNode;
776
942
 
777
- const parsedValue = walkGroups(valueParser(decl.value), node => {
778
- // Remove leading negative sign, if any.
779
- const cleanValue = node.value.replace(/^-/g, '');
943
+ if (!propList$1.some(spacingProp => prop.startsWith(spacingProp))) return
944
+ if (valueList.some(valueToIgnore => value.includes(valueToIgnore))) return
945
+
946
+ const problems = [];
780
947
 
948
+ const parsedValue = walkGroups$1(valueParser(value), node => {
781
949
  // Only check word types. https://github.com/TrySound/postcss-value-parser#word
782
950
  if (node.type !== 'word') {
783
951
  return
@@ -788,30 +956,36 @@ var spacing = stylelint.createPlugin(ruleName$2, (enabled, options = {}, context
788
956
  return
789
957
  }
790
958
 
791
- const valueUnit = valueParser.unit(cleanValue);
959
+ const valueUnit = valueParser.unit(node.value);
960
+
961
+ if (valueUnit && (valueUnit.unit === '' || !/^-?[0-9.]+$/.test(valueUnit.number))) {
962
+ return
963
+ }
792
964
 
793
- if (valueUnit && (valueUnit.unit === '' || !/^[0-9.]+$/.test(valueUnit.number))) {
965
+ // Skip if the value unit isn't a supported unit.
966
+ if (valueUnit && !['px', 'rem', 'em'].includes(valueUnit.unit)) {
794
967
  return
795
968
  }
796
969
 
797
- // If the a variable is found in the value, skip it.
970
+ // If the variable is found in the value, skip it.
798
971
  if (
799
- Object.keys(spacerValues).some(variable =>
800
- new RegExp(`${variable.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`).test(cleanValue),
972
+ sizes.some(variable =>
973
+ new RegExp(`${variable['name'].replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`).test(node.value),
801
974
  )
802
975
  ) {
803
976
  return
804
977
  }
805
978
 
806
- const replacement = Object.keys(spacerValues).find(spacer => spacerValues[spacer] === cleanValue) || null;
807
- const valueMatch = replacement ? spacerValues[replacement] : node.value;
979
+ const replacement = sizes.find(variable => variable.values.includes(node.value.replace('-', '')));
980
+ const fixable = replacement && valueUnit && !valueUnit.number.includes('-');
808
981
 
809
- if (replacement && context.fix) {
810
- node.value = node.value.replace(valueMatch, replacement);
982
+ if (fixable && context.fix) {
983
+ node.value = node.value.replace(node.value, `var(${replacement['name']})`);
811
984
  } else {
812
985
  problems.push({
813
- index: declarationValueIndex(decl) + node.sourceIndex,
814
- message: messages$2.rejected(valueMatch, replacement),
986
+ index: declarationValueIndex(declNode) + node.sourceIndex,
987
+ endIndex: declarationValueIndex(declNode) + node.sourceIndex + node.value.length,
988
+ message: messages$2.rejected(node.value, replacement),
815
989
  });
816
990
  }
817
991
 
@@ -819,627 +993,215 @@ var spacing = stylelint.createPlugin(ruleName$2, (enabled, options = {}, context
819
993
  });
820
994
 
821
995
  if (context.fix) {
822
- decl.value = parsedValue.toString();
996
+ declNode.value = parsedValue.toString();
823
997
  }
824
998
 
825
999
  if (problems.length) {
826
1000
  for (const err of problems) {
827
- stylelint.utils.report({
1001
+ report$1({
828
1002
  index: err.index,
1003
+ endIndex: err.endIndex,
829
1004
  message: err.message,
830
- node: decl,
1005
+ node: declNode,
831
1006
  result,
832
1007
  ruleName: ruleName$2,
833
1008
  });
834
1009
  }
835
1010
  }
836
1011
  });
837
- };
838
-
839
- return lintResult
840
- });
1012
+ }
1013
+ };
841
1014
 
842
- function noop$2() {}
1015
+ ruleFunction$1.ruleName = ruleName$2;
1016
+ ruleFunction$1.messages = messages$2;
1017
+ ruleFunction$1.meta = {
1018
+ fixable: true,
1019
+ };
843
1020
 
844
- var typography = createVariableRule(
845
- 'primer/typography',
846
- {
847
- 'font-size': {
848
- expects: 'a font-size variable',
849
- values: ['$body-font-size', '$h{000,00,0,1,2,3,4,5,6}-size', '$font-size-*', '1', '1em', 'inherit'],
850
- },
851
- 'font-weight': {
852
- props: 'font-weight',
853
- values: ['$font-weight-*', 'inherit'],
854
- replacements: {
855
- bold: '$font-weight-bold',
856
- normal: '$font-weight-normal',
857
- },
858
- },
859
- 'line-height': {
860
- props: 'line-height',
861
- values: ['$body-line-height', '$lh-*', '0', '1', '1em', 'inherit'],
862
- },
863
- },
864
- 'https://primer.style/css/utilities/typography',
865
- );
1021
+ var spacing = createPlugin$1(ruleName$2, ruleFunction$1);
866
1022
 
867
- // Meant as temp until we can move to primitives or css
868
- const colorTypes = ['accent', 'success', 'attention', 'severe', 'danger', 'open', 'closed', 'done', 'sponsors'];
1023
+ const {
1024
+ createPlugin,
1025
+ utils: {report, ruleMessages, validateOptions},
1026
+ } = stylelint;
869
1027
 
870
- var utilities$1 = {
871
- color: [
872
- {
873
- value: 'var(--color-fg-default)',
874
- utilityClass: 'color-fg-default',
875
- },
876
- {
877
- value: 'var(--color-fg-muted)',
878
- utilityClass: 'color-fg-muted',
879
- },
880
- {
881
- value: 'var(--color-fg-subtle)',
882
- utilityClass: 'color-fg-subtle',
883
- },
884
- ].concat(
885
- colorTypes.map(type => {
886
- return {
887
- value: `var(--color-${type}-fg)`,
888
- utilityClass: `color-fg-${type}`,
889
- }
890
- }),
891
- ),
892
- 'background-color': [
893
- {
894
- value: 'var(--color-canvas-default)',
895
- utilityClass: 'color-bg-default',
896
- },
897
- {
898
- value: 'var(--color-canvas-overlay)',
899
- utilityClass: 'color-bg-overlay',
900
- },
901
- {
902
- value: 'var(--color-canvas-inset)',
903
- utilityClass: 'color-bg-inset',
904
- },
905
- {
906
- value: 'var(--color-canvas-subtle)',
907
- utilityClass: 'color-bg-subtle',
908
- },
909
- {
910
- value: 'transparent',
911
- utilityClass: 'color-bg-transparent',
912
- },
913
- ]
914
- .concat(
915
- colorTypes.map(type => {
916
- return {
917
- value: `var(--color-${type}-subtle)`,
918
- utilityClass: `color-bg-${type}`,
919
- }
920
- }),
921
- )
922
- .concat(
923
- colorTypes.map(type => {
924
- return {
925
- value: `var(--color-${type}-emphasis)`,
926
- utilityClass: `color-bg-${type}-emphasis`,
927
- }
928
- }),
929
- ),
930
- 'border-color': [
931
- {
932
- value: 'var(--color-border-default',
933
- utilityClass: 'color-border-default',
934
- },
935
- {
936
- value: 'var(--color-border-muted',
937
- utilityClass: 'color-border-muted',
938
- },
939
- {
940
- value: 'var(--color-border-subtle',
941
- utilityClass: 'color-border-subtle',
942
- },
943
- ]
944
- .concat(
945
- colorTypes.map(type => {
946
- return {
947
- value: `var(--color-${type}-muted)`,
948
- utilityClass: `color-border-${type}`,
949
- }
950
- }),
951
- )
952
- .concat(
953
- colorTypes.map(type => {
954
- return {
955
- value: `var(--color-${type}-emphasis)`,
956
- utilityClass: `color-border-${type}-emphasis`,
957
- }
958
- }),
959
- ),
960
- margin: Array.from(new Array(6)).map((_, i) => {
961
- return {
962
- value: `$spacer-${i + 1}`,
963
- utilityClass: `m-${i + 1}`,
964
- }
965
- }),
966
- 'margin-top': Array.from(new Array(6)).map((_, i) => {
967
- return {
968
- value: `$spacer-${i + 1}`,
969
- utilityClass: `mt-${i + 1}`,
970
- }
971
- }),
972
- 'margin-right': Array.from(new Array(6)).map((_, i) => {
973
- return {
974
- value: `$spacer-${i + 1}`,
975
- utilityClass: `mr-${i + 1}`,
976
- }
977
- }),
978
- 'margin-bottom': Array.from(new Array(6)).map((_, i) => {
979
- return {
980
- value: `$spacer-${i + 1}`,
981
- utilityClass: `mb-${i + 1}`,
982
- }
983
- }),
984
- 'margin-left': Array.from(new Array(6)).map((_, i) => {
985
- return {
986
- value: `$spacer-${i + 1}`,
987
- utilityClass: `ml-${i + 1}`,
988
- }
989
- }),
990
- padding: Array.from(new Array(6)).map((_, i) => {
991
- return {
992
- value: `$spacer-${i + 1}`,
993
- utilityClass: `p-${i + 1}`,
994
- }
995
- }),
996
- 'padding-top': Array.from(new Array(6)).map((_, i) => {
997
- return {
998
- value: `$spacer-${i + 1}`,
999
- utilityClass: `pt-${i + 1}`,
1000
- }
1001
- }),
1002
- 'padding-right': Array.from(new Array(6)).map((_, i) => {
1003
- return {
1004
- value: `$spacer-${i + 1}`,
1005
- utilityClass: `pr-${i + 1}`,
1006
- }
1007
- }),
1008
- 'padding-bottom': Array.from(new Array(6)).map((_, i) => {
1009
- return {
1010
- value: `$spacer-${i + 1}`,
1011
- utilityClass: `pb-${i + 1}`,
1012
- }
1013
- }),
1014
- 'padding-left': Array.from(new Array(6)).map((_, i) => {
1015
- return {
1016
- value: `$spacer-${i + 1}`,
1017
- utilityClass: `pl-${i + 1}`,
1028
+ const ruleName$1 = 'primer/typography';
1029
+ const messages$1 = ruleMessages(ruleName$1, {
1030
+ rejected: (value, replacement) => {
1031
+ // no possible replacement
1032
+ if (!replacement) {
1033
+ return `Please use a Primer typography variable instead of '${value}'. Consult the primer docs for a suitable replacement. https://primer.style/foundations/primitives/typography`
1018
1034
  }
1019
- }),
1020
- 'line-height': [
1021
- {
1022
- value: '$lh-condensed-ultra',
1023
- utilityClass: 'lh-condensed-ultra',
1024
- },
1025
- {
1026
- value: '$lh-condensed',
1027
- utilityClass: 'lh-condensed',
1028
- },
1029
- {
1030
- value: '$lh-default',
1031
- utilityClass: 'lh-default',
1032
- },
1033
- {
1034
- value: '0',
1035
- utilityClass: 'lh-0',
1036
- },
1037
- ],
1038
- 'text-align': [
1039
- {
1040
- value: 'left',
1041
- utilityClass: 'text-left',
1042
- },
1043
- {
1044
- value: 'right',
1045
- utilityClass: 'text-right',
1046
- },
1047
- {
1048
- value: 'center',
1049
- utilityClass: 'text-center',
1050
- },
1051
- ],
1052
- 'font-style': [
1053
- {
1054
- value: 'italic',
1055
- utilityClass: 'text-italic',
1056
- },
1057
- ],
1058
- 'text-transform': [
1059
- {
1060
- value: 'uppercase',
1061
- utilityClass: 'text-uppercase',
1062
- },
1063
- ],
1064
- 'text-decoration': [
1065
- {
1066
- value: 'underline',
1067
- utilityClass: 'text-underline',
1068
- },
1069
- {
1070
- value: 'none',
1071
- utilityClass: 'no-underline',
1072
- },
1073
- ],
1074
- 'white-space': [
1075
- {
1076
- value: 'nowrap',
1077
- utilityClass: 'no-wrap',
1078
- },
1079
- {
1080
- value: 'normal',
1081
- utilityClass: 'ws-normal',
1082
- },
1083
- ],
1084
- 'word-break': [
1085
- {
1086
- value: 'break-all',
1087
- utilityClass: 'wb-break-all',
1088
- },
1089
- ],
1090
- width: [
1091
- {
1092
- value: '100%',
1093
- utilityClass: 'width-full',
1094
- },
1095
- {
1096
- value: 'auto%',
1097
- utilityClass: 'width-auto',
1098
- },
1099
- ],
1100
- overflow: [
1101
- {
1102
- value: 'visible',
1103
- utilityClass: 'overflow-visible',
1104
- },
1105
- {
1106
- value: 'hidden',
1107
- utilityClass: 'overflow-hidden',
1108
- },
1109
- {
1110
- value: 'auto',
1111
- utilityClass: 'overflow-auto',
1112
- },
1113
- {
1114
- value: 'scroll',
1115
- utilityClass: 'overflow-scroll',
1116
- },
1117
- ],
1118
- 'overflow-x': [
1119
- {
1120
- value: 'visible',
1121
- utilityClass: 'overflow-x-visible',
1122
- },
1123
- {
1124
- value: 'hidden',
1125
- utilityClass: 'overflow-x-hidden',
1126
- },
1127
- {
1128
- value: 'auto',
1129
- utilityClass: 'overflow-x-auto',
1130
- },
1131
- {
1132
- value: 'scroll',
1133
- utilityClass: 'overflow-x-scroll',
1134
- },
1135
- ],
1136
- 'overflow-y': [
1137
- {
1138
- value: 'visible',
1139
- utilityClass: 'overflow-y-visible',
1140
- },
1141
- {
1142
- value: 'hidden',
1143
- utilityClass: 'overflow-y-hidden',
1144
- },
1145
- {
1146
- value: 'auto',
1147
- utilityClass: 'overflow-y-auto',
1148
- },
1149
- {
1150
- value: 'scroll',
1151
- utilityClass: 'overflow-y-scroll',
1152
- },
1153
- ],
1154
- height: [
1155
- {
1156
- value: '100%',
1157
- utilityClass: 'height-full',
1158
- },
1159
- ],
1160
- 'max-width': [
1161
- {
1162
- value: '100%',
1163
- utilityClass: 'width-fit',
1164
- },
1165
- ],
1166
- 'max-height': [
1167
- {
1168
- value: '100%',
1169
- utilityClass: 'height-fit',
1170
- },
1171
- ],
1172
- 'min-width': [
1173
- {
1174
- value: '0',
1175
- utilityClass: 'min-width-0',
1176
- },
1177
- ],
1178
- float: [
1179
- {
1180
- value: 'left',
1181
- utilityClass: 'float-left',
1182
- },
1183
- {
1184
- value: 'right',
1185
- utilityClass: 'float-right',
1186
- },
1187
- {
1188
- value: 'none',
1189
- utilityClass: 'float-none',
1190
- },
1191
- ],
1192
- 'list-style': [
1193
- {
1194
- value: 'none',
1195
- utilityClass: 'list-style-none',
1196
- },
1197
- ],
1198
- 'user-select': [
1199
- {
1200
- value: 'none',
1201
- utilityClass: 'user-select-none',
1202
- },
1203
- ],
1204
- visibility: [
1205
- {
1206
- value: 'hidden',
1207
- utilityClass: 'v-hidden',
1208
- },
1209
- {
1210
- value: 'visible',
1211
- utilityClass: 'v-visible',
1212
- },
1213
- ],
1214
- 'vertical-align': [
1215
- {
1216
- value: 'middle',
1217
- utilityClass: 'v-align-middle',
1218
- },
1219
- {
1220
- value: 'top',
1221
- utilityClass: 'v-align-top',
1222
- },
1223
- {
1224
- value: 'bottom',
1225
- utilityClass: 'v-align-bottom',
1226
- },
1227
- {
1228
- value: 'text-top',
1229
- utilityClass: 'v-align-text-top',
1230
- },
1231
- {
1232
- value: 'text-bottom',
1233
- utilityClass: 'v-align-text-bottom',
1234
- },
1235
- {
1236
- value: 'text-baseline',
1237
- utilityClass: 'v-align-baseline',
1238
- },
1239
- ],
1240
- 'font-weight': [
1241
- {
1242
- value: '$font-weight-normal',
1243
- utilityClass: 'text-normal',
1244
- },
1245
- {
1246
- value: '$font-weight-bold',
1247
- utilityClass: 'text-bold',
1248
- },
1249
- {
1250
- value: '$font-weight-semibold',
1251
- utilityClass: 'text-semibold',
1252
- },
1253
- {
1254
- value: '$font-weight-light',
1255
- utilityClass: 'text-light',
1256
- },
1257
- ],
1258
- top: [
1259
- {
1260
- value: '0',
1261
- utilityClass: 'top-0',
1262
- },
1263
- {
1264
- value: 'auto',
1265
- utilityClass: 'top-auto',
1266
- },
1267
- ],
1268
- right: [
1269
- {
1270
- value: '0',
1271
- utilityClass: 'right-0',
1272
- },
1273
- {
1274
- value: 'auto',
1275
- utilityClass: 'right-auto',
1276
- },
1277
- ],
1278
- bottom: [
1279
- {
1280
- value: '0',
1281
- utilityClass: 'bottom-0',
1282
- },
1283
- {
1284
- value: 'auto',
1285
- utilityClass: 'bottom-auto',
1286
- },
1287
- ],
1288
- left: [
1289
- {
1290
- value: '0',
1291
- utilityClass: 'left-0',
1292
- },
1293
- {
1294
- value: 'auto',
1295
- utilityClass: 'left-auto',
1296
- },
1297
- ],
1298
- position: [
1299
- {
1300
- value: 'static',
1301
- utilityClass: 'position-static',
1302
- },
1303
- {
1304
- value: 'relative',
1305
- utilityClass: 'position-relative',
1306
- },
1307
- {
1308
- value: 'absolute',
1309
- utilityClass: 'position-absolute',
1310
- },
1311
- {
1312
- value: 'fixed',
1313
- utilityClass: 'position-fixed',
1314
- },
1315
- {
1316
- value: 'sticky',
1317
- utilityClass: 'position-sticky',
1318
- },
1319
- ],
1320
- 'box-shadow': [
1321
- {
1322
- value: 'none',
1323
- utilityClass: 'box-shadow-none',
1324
- },
1325
- {
1326
- value: 'var(--color-shadow-small)',
1327
- utilityClass: 'box-shadow-small',
1328
- },
1329
- {
1330
- value: 'var(--color-shadow-medium)',
1331
- utilityClass: 'box-shadow-medium',
1332
- },
1333
- {
1334
- value: 'var(--color-shadow-large)',
1335
- utilityClass: 'box-shadow-large',
1336
- },
1337
- {
1338
- value: 'var(--color-shadow-extra-large)',
1339
- utilityClass: 'box-shadow-extra-large',
1340
- },
1341
- ],
1342
- border: [
1343
- {
1344
- value: '$border',
1345
- utilityClass: 'border',
1346
- },
1347
- {
1348
- value: '0',
1349
- utilityClass: 'border-0',
1350
- },
1351
- ],
1352
- 'border-top': [
1353
- {
1354
- value: '$border',
1355
- utilityClass: 'border-top',
1356
- },
1357
- {
1358
- value: '0',
1359
- utilityClass: 'border-top-0',
1360
- },
1361
- ],
1362
- 'border-right': [
1363
- {
1364
- value: '$border',
1365
- utilityClass: 'border-right',
1366
- },
1367
- {
1368
- value: '0',
1369
- utilityClass: 'border-right-0',
1370
- },
1371
- ],
1372
- 'border-bottom': [
1373
- {
1374
- value: '$border',
1375
- utilityClass: 'border-bottom',
1376
- },
1377
- {
1378
- value: '0',
1379
- utilityClass: 'border-bottom-0',
1380
- },
1381
- ],
1382
- 'border-left': [
1383
- {
1384
- value: '$border',
1385
- utilityClass: 'border-left',
1386
- },
1387
- {
1388
- value: '0',
1389
- utilityClass: 'border-left-0',
1390
- },
1391
- ],
1392
- };
1393
1035
 
1394
- const ruleName$1 = 'primer/utilities';
1036
+ // multiple possible replacements
1037
+ if (replacement.length) {
1038
+ 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`
1039
+ }
1395
1040
 
1396
- const messages$1 = stylelint.utils.ruleMessages(ruleName$1, {
1397
- rejected: (selector, utilityClass) => {
1398
- return `Consider using the Primer utility '.${utilityClass}' instead of the selector '${selector}' in your html. https://primer.style/css/utilities`
1041
+ // one possible replacement
1042
+ return `Please replace '${value}' with Primer typography variable '${replacement['name']}'. https://primer.style/foundations/primitives/typography`
1399
1043
  },
1400
1044
  });
1401
1045
 
1402
- // eslint-disable-next-line no-unused-vars
1403
- var utilities = stylelint.createPlugin(ruleName$1, (enabled, options = {}, context) => {
1404
- if (!enabled) {
1405
- return noop$1
1046
+ const fontWeightKeywordMap = {
1047
+ normal: 400,
1048
+ bold: 600,
1049
+ bolder: 600,
1050
+ lighter: 300,
1051
+ };
1052
+ const getClosestFontWeight = (goalWeightNumber, fontWeightsTokens) => {
1053
+ return fontWeightsTokens.reduce((prev, curr) =>
1054
+ Math.abs(curr.values - goalWeightNumber) < Math.abs(prev.values - goalWeightNumber) ? curr : prev,
1055
+ ).values
1056
+ };
1057
+
1058
+ const variables = primitivesVariables('typography');
1059
+ const fontSizes = [];
1060
+ const fontWeights = [];
1061
+ const lineHeights = [];
1062
+ const fontStacks = [];
1063
+ const fontShorthands = [];
1064
+
1065
+ // Props that we want to check for typography variables
1066
+ const propList = ['font-size', 'font-weight', 'line-height', 'font-family', 'font'];
1067
+
1068
+ for (const variable of variables) {
1069
+ const name = variable['name'];
1070
+
1071
+ if (name.includes('size')) {
1072
+ fontSizes.push(variable);
1406
1073
  }
1407
1074
 
1408
- const utilityReplacement = (declaration, value) => {
1409
- const declarationUtilities = utilities$1[declaration];
1410
- if (declarationUtilities) {
1411
- return declarationUtilities.find(utility => {
1412
- return utility.value === value
1413
- })
1414
- }
1415
- };
1075
+ if (name.includes('weight')) {
1076
+ fontWeights.push(variable);
1077
+ }
1416
1078
 
1417
- const lintResult = (root, result) => {
1418
- root.walkRules(rule => {
1419
- if (!/^\.[\w\-_]+$/.exec(rule.selector)) {
1079
+ if (name.includes('lineHeight')) {
1080
+ lineHeights.push(variable);
1081
+ }
1082
+
1083
+ if (name.includes('fontStack')) {
1084
+ fontStacks.push(variable);
1085
+ }
1086
+
1087
+ if (name.includes('shorthand')) {
1088
+ fontShorthands.push(variable);
1089
+ }
1090
+ }
1091
+
1092
+ /** @type {import('stylelint').Rule} */
1093
+ const ruleFunction = (primary, secondaryOptions, context) => {
1094
+ return (root, result) => {
1095
+ const validOptions = validateOptions(result, ruleName$1, {
1096
+ actual: primary,
1097
+ possible: [true],
1098
+ });
1099
+ let validValues = [];
1100
+
1101
+ if (!validOptions) return
1102
+
1103
+ root.walkDecls(declNode => {
1104
+ const {prop, value} = declNode;
1105
+
1106
+ if (!propList.some(typographyProp => prop.startsWith(typographyProp))) return
1107
+
1108
+ const problems = [];
1109
+
1110
+ const checkForVariable = (vars, nodeValue) =>
1111
+ vars.some(variable =>
1112
+ new RegExp(`${variable['name'].replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`).test(nodeValue),
1113
+ );
1114
+
1115
+ // Exact values to ignore.
1116
+ if (value === 'inherit') {
1420
1117
  return
1421
1118
  }
1422
- const decls = rule.nodes.filter(decl => decl.type === 'decl');
1423
1119
 
1424
- if (decls.length === 1) {
1425
- const replacement = utilityReplacement(decls[0].prop, decls[0].value);
1426
- if (replacement) {
1427
- stylelint.utils.report({
1428
- index: rule.sourceIndex,
1429
- message: messages$1.rejected(rule.selector, replacement.utilityClass),
1430
- node: rule,
1120
+ switch (prop) {
1121
+ case 'font-size':
1122
+ validValues = fontSizes;
1123
+ break
1124
+ case 'font-weight':
1125
+ validValues = fontWeights;
1126
+ break
1127
+ case 'line-height':
1128
+ validValues = lineHeights;
1129
+ break
1130
+ case 'font-family':
1131
+ validValues = fontStacks;
1132
+ break
1133
+ case 'font':
1134
+ validValues = fontShorthands;
1135
+ break
1136
+ default:
1137
+ validValues = [];
1138
+ }
1139
+
1140
+ if (checkForVariable(validValues, value)) {
1141
+ return
1142
+ }
1143
+
1144
+ const getReplacements = () => {
1145
+ const replacementTokens = validValues.filter(variable => {
1146
+ if (!(variable.values instanceof Array)) {
1147
+ let nodeValue = value;
1148
+
1149
+ if (prop === 'font-weight') {
1150
+ nodeValue = getClosestFontWeight(fontWeightKeywordMap[value] || value, fontWeights);
1151
+ }
1152
+
1153
+ return variable.values.toString() === nodeValue.toString()
1154
+ }
1155
+
1156
+ return variable.values.includes(value.replace('-', ''))
1157
+ });
1158
+
1159
+ if (!replacementTokens.length) {
1160
+ return
1161
+ }
1162
+
1163
+ if (replacementTokens.length > 1) {
1164
+ return replacementTokens
1165
+ }
1166
+
1167
+ return replacementTokens[0]
1168
+ };
1169
+ const replacement = getReplacements();
1170
+ const fixable = replacement && !replacement.length;
1171
+
1172
+ if (fixable && context.fix) {
1173
+ declNode.value = value.replace(value, `var(${replacement['name']})`);
1174
+ } else {
1175
+ problems.push({
1176
+ index: declarationValueIndex(declNode),
1177
+ endIndex: declarationValueIndex(declNode) + value.length,
1178
+ message: messages$1.rejected(value, replacement, prop),
1179
+ });
1180
+ }
1181
+
1182
+ if (problems.length) {
1183
+ for (const err of problems) {
1184
+ report({
1185
+ index: err.index,
1186
+ endIndex: err.endIndex,
1187
+ message: err.message,
1188
+ node: declNode,
1431
1189
  result,
1432
1190
  ruleName: ruleName$1,
1433
1191
  });
1434
1192
  }
1435
1193
  }
1436
1194
  });
1437
- };
1195
+ }
1196
+ };
1438
1197
 
1439
- return lintResult
1440
- });
1198
+ ruleFunction.ruleName = ruleName$1;
1199
+ ruleFunction.messages = messages$1;
1200
+ ruleFunction.meta = {
1201
+ fixable: true,
1202
+ };
1441
1203
 
1442
- function noop$1() {}
1204
+ var typography = createPlugin(ruleName$1, ruleFunction);
1443
1205
 
1444
1206
  const ruleName = 'primer/no-display-colors';
1445
1207
  const messages = stylelint.utils.ruleMessages(ruleName, {
@@ -1506,7 +1268,6 @@ var index = {
1506
1268
  responsiveWidths,
1507
1269
  spacing,
1508
1270
  typography,
1509
- utilities,
1510
1271
  noDisplayColors,
1511
1272
  ],
1512
1273
  rules: {
@@ -1574,7 +1335,6 @@ var index = {
1574
1335
  'primer/responsive-widths': true,
1575
1336
  'primer/spacing': true,
1576
1337
  'primer/typography': true,
1577
- 'primer/utilities': null,
1578
1338
  'primer/no-display-colors': true,
1579
1339
  'property-no-unknown': [
1580
1340
  true,
@@ -1625,12 +1385,8 @@ var index = {
1625
1385
  'comment-empty-line-before': null,
1626
1386
  'length-zero-no-unit': null,
1627
1387
  'selector-max-type': null,
1628
- 'primer/spacing': null,
1629
1388
  'primer/colors': null,
1630
- 'primer/borders': null,
1631
- 'primer/typography': null,
1632
1389
  'primer/box-shadow': null,
1633
- 'primer/utilities': null,
1634
1390
  },
1635
1391
  },
1636
1392
  {
@@ -1655,7 +1411,6 @@ var index = {
1655
1411
  },
1656
1412
  {
1657
1413
  files: ['**/*.module.css'],
1658
- plugins: ['stylelint-css-modules-no-global-scoped-selector'],
1659
1414
  rules: {
1660
1415
  'property-no-unknown': [
1661
1416
  true,
@@ -1680,13 +1435,8 @@ var index = {
1680
1435
  ignoreFunctions: ['global'],
1681
1436
  },
1682
1437
  ],
1683
- 'css-modules/no-global-scoped-selector': true,
1684
1438
  // temporarily disabiling Primer plugins while we work on upgrades https://github.com/github/primer/issues/3165
1685
- 'primer/spacing': null,
1686
- 'primer/borders': null,
1687
- 'primer/typography': null,
1688
1439
  'primer/box-shadow': null,
1689
- 'primer/utilities': null,
1690
1440
  },
1691
1441
  },
1692
1442
  ],