@primer/stylelint-config 13.0.0-rc.c3d4a78 → 13.0.0-rc.c67f7df

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