@primer/stylelint-config 13.0.0-rc.f33e046 → 13.0.0-rc.f49bf6d

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