@primer/stylelint-config 13.0.0-rc.864dd2a → 13.0.0-rc.99be5f2

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
@@ -7,8 +7,8 @@ var valueParser = require('postcss-value-parser');
7
7
  var TapMap = require('tap-map');
8
8
  var variables = require('@primer/css/dist/variables.json');
9
9
  var declarationValueIndex = require('stylelint/lib/utils/declarationValueIndex.cjs');
10
- var matchAll = require('string.prototype.matchall');
11
10
  var node_module = require('node:module');
11
+ var matchAll = require('string.prototype.matchall');
12
12
 
13
13
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
14
14
  var propertyOrder = [
@@ -422,7 +422,7 @@ function closest(node, test) {
422
422
  function createVariableRule(ruleName, rules, url) {
423
423
  const plugin = stylelint.createPlugin(ruleName, (enabled, options = {}, context) => {
424
424
  if (enabled === false) {
425
- return noop$4
425
+ return noop$3
426
426
  }
427
427
 
428
428
  let actualRules = rules;
@@ -499,7 +499,7 @@ function createVariableRule(ruleName, rules, url) {
499
499
  return plugin
500
500
  }
501
501
 
502
- function noop$4() {}
502
+ function noop$3() {}
503
503
 
504
504
  var borders = createVariableRule(
505
505
  'primer/borders',
@@ -651,7 +651,7 @@ const walkGroups$1 = (root, validate) => {
651
651
  // eslint-disable-next-line no-unused-vars
652
652
  var responsiveWidths = stylelint.createPlugin(ruleName$3, (enabled, options = {}, context) => {
653
653
  if (!enabled) {
654
- return noop$3
654
+ return noop$2
655
655
  }
656
656
 
657
657
  const lintResult = (root, result) => {
@@ -662,7 +662,7 @@ var responsiveWidths = stylelint.createPlugin(ruleName$3, (enabled, options = {}
662
662
  }
663
663
 
664
664
  if (decl.type !== 'decl' || !decl.prop.match(/^(min-width|width)/)) {
665
- return noop$3
665
+ return noop$2
666
666
  }
667
667
 
668
668
  const problems = [];
@@ -717,40 +717,44 @@ var responsiveWidths = stylelint.createPlugin(ruleName$3, (enabled, options = {}
717
717
  return lintResult
718
718
  });
719
719
 
720
- function noop$3() {}
720
+ function noop$2() {}
721
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
- };
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)));
743
723
 
744
- const ruleName$2 = 'primer/spacing';
745
- const messages$2 = stylelint.utils.ruleMessages(ruleName$2, {
746
- 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`
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
+ values.push(`${parseInt(size['original']['value']) + 1}px`);
742
+ values.push(`${parseInt(size['original']['value']) - 1}px`);
743
+
744
+ variables.push({
745
+ name: `--${size['name']}`,
746
+ values,
747
+ });
749
748
  }
749
+ }
750
750
 
751
- return `Please replace ${value} with spacing variable '${replacement}'.`
752
- },
753
- });
751
+ return variables
752
+ }
753
+
754
+ const {
755
+ createPlugin,
756
+ utils: {report, ruleMessages, validateOptions},
757
+ } = stylelint;
754
758
 
755
759
  const walkGroups = (root, validate) => {
756
760
  for (const node of root.nodes) {
@@ -763,24 +767,47 @@ const walkGroups = (root, validate) => {
763
767
  return root
764
768
  };
765
769
 
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
770
- }
770
+ const ruleName$2 = 'primer/spacing';
771
+ const messages$2 = ruleMessages(ruleName$2, {
772
+ rejected: (value, replacement) => {
773
+ if (!replacement) {
774
+ return `Please use a primer size variable instead of '${value}'. Consult the primer docs for a suitable replacement. https://primer.style/foundations/primitives/size`
775
+ }
771
776
 
772
- const lintResult = (root, result) => {
773
- root.walk(decl => {
774
- if (decl.type !== 'decl' || !decl.prop.match(/^(padding|margin)/)) {
775
- return noop$2
776
- }
777
+ return `Please replace '${value}' with size variable '${replacement['name']}'. https://primer.style/foundations/primitives/size`
778
+ },
779
+ });
777
780
 
778
- const problems = [];
781
+ const meta = {
782
+ fixable: true,
783
+ };
784
+
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 = ['${'];
792
+
793
+ const sizes = await primitivesVariables('size');
794
+
795
+ const validOptions = validateOptions(result, ruleName$2, {
796
+ actual: primary,
797
+ possible: [true],
798
+ });
799
+
800
+ if (!validOptions) return
801
+
802
+ root.walkDecls(declNode => {
803
+ const {prop, value} = declNode;
779
804
 
780
- const parsedValue = walkGroups(valueParser(decl.value), node => {
781
- // Remove leading negative sign, if any.
782
- const cleanValue = node.value.replace(/^-/g, '');
805
+ if (!propList.some(spacingProp => prop.startsWith(spacingProp))) return
806
+ if (valueList.some(valueToIgnore => value.includes(valueToIgnore))) return
783
807
 
808
+ const problems = [];
809
+
810
+ const parsedValue = walkGroups(valueParser(value), node => {
784
811
  // Only check word types. https://github.com/TrySound/postcss-value-parser#word
785
812
  if (node.type !== 'word') {
786
813
  return
@@ -791,30 +818,36 @@ var spacing = stylelint.createPlugin(ruleName$2, (enabled, options = {}, context
791
818
  return
792
819
  }
793
820
 
794
- const valueUnit = valueParser.unit(cleanValue);
821
+ const valueUnit = valueParser.unit(node.value);
795
822
 
796
- if (valueUnit && (valueUnit.unit === '' || !/^[0-9.]+$/.test(valueUnit.number))) {
823
+ if (valueUnit && (valueUnit.unit === '' || !/^-?[0-9.]+$/.test(valueUnit.number))) {
797
824
  return
798
825
  }
799
826
 
800
- // If the a variable is found in the value, skip it.
827
+ // Skip if the value unit isn't a supported unit.
828
+ if (valueUnit && !['px', 'rem', 'em'].includes(valueUnit.unit)) {
829
+ return
830
+ }
831
+
832
+ // If the variable is found in the value, skip it.
801
833
  if (
802
- Object.keys(spacerValues).some(variable =>
803
- new RegExp(`${variable.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`).test(cleanValue),
834
+ sizes.some(variable =>
835
+ new RegExp(`${variable['name'].replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`).test(node.value),
804
836
  )
805
837
  ) {
806
838
  return
807
839
  }
808
840
 
809
- const replacement = Object.keys(spacerValues).find(spacer => spacerValues[spacer] === cleanValue) || null;
810
- const valueMatch = replacement ? spacerValues[replacement] : node.value;
841
+ const replacement = sizes.find(variable => variable.values.includes(node.value.replace('-', '')));
842
+ const fixable = replacement && valueUnit && !valueUnit.number.includes('-');
811
843
 
812
- if (replacement && context.fix) {
813
- node.value = node.value.replace(valueMatch, replacement);
844
+ if (fixable && context.fix) {
845
+ node.value = node.value.replace(node.value, `var(${replacement['name']})`);
814
846
  } else {
815
847
  problems.push({
816
- index: declarationValueIndex(decl) + node.sourceIndex,
817
- message: messages$2.rejected(valueMatch, replacement),
848
+ index: declarationValueIndex(declNode) + node.sourceIndex,
849
+ endIndex: declarationValueIndex(declNode) + node.sourceIndex + node.value.length,
850
+ message: messages$2.rejected(node.value, replacement),
818
851
  });
819
852
  }
820
853
 
@@ -822,27 +855,30 @@ var spacing = stylelint.createPlugin(ruleName$2, (enabled, options = {}, context
822
855
  });
823
856
 
824
857
  if (context.fix) {
825
- decl.value = parsedValue.toString();
858
+ declNode.value = parsedValue.toString();
826
859
  }
827
860
 
828
861
  if (problems.length) {
829
862
  for (const err of problems) {
830
- stylelint.utils.report({
863
+ report({
831
864
  index: err.index,
865
+ endIndex: err.endIndex,
832
866
  message: err.message,
833
- node: decl,
867
+ node: declNode,
834
868
  result,
835
869
  ruleName: ruleName$2,
836
870
  });
837
871
  }
838
872
  }
839
873
  });
840
- };
874
+ }
875
+ };
841
876
 
842
- return lintResult
843
- });
877
+ ruleFunction.ruleName = ruleName$2;
878
+ ruleFunction.messages = messages$2;
879
+ ruleFunction.meta = meta;
844
880
 
845
- function noop$2() {}
881
+ var spacing = createPlugin(ruleName$2, ruleFunction);
846
882
 
847
883
  var typography = createVariableRule(
848
884
  'primer/typography',
@@ -1628,7 +1664,6 @@ var index = {
1628
1664
  'comment-empty-line-before': null,
1629
1665
  'length-zero-no-unit': null,
1630
1666
  'selector-max-type': null,
1631
- 'primer/spacing': null,
1632
1667
  'primer/colors': null,
1633
1668
  'primer/borders': null,
1634
1669
  'primer/typography': null,
@@ -1658,6 +1693,7 @@ var index = {
1658
1693
  },
1659
1694
  {
1660
1695
  files: ['**/*.module.css'],
1696
+ plugins: ['stylelint-css-modules-no-global-scoped-selector'],
1661
1697
  rules: {
1662
1698
  'property-no-unknown': [
1663
1699
  true,
@@ -1682,8 +1718,8 @@ var index = {
1682
1718
  ignoreFunctions: ['global'],
1683
1719
  },
1684
1720
  ],
1721
+ 'css-modules/no-global-scoped-selector': true,
1685
1722
  // temporarily disabiling Primer plugins while we work on upgrades https://github.com/github/primer/issues/3165
1686
- 'primer/spacing': null,
1687
1723
  'primer/borders': null,
1688
1724
  'primer/typography': null,
1689
1725
  'primer/box-shadow': null,
package/dist/index.mjs CHANGED
@@ -5,8 +5,8 @@ import valueParser from 'postcss-value-parser';
5
5
  import TapMap from 'tap-map';
6
6
  import variables from '@primer/css/dist/variables.json' assert { type: 'json' };
7
7
  import declarationValueIndex from 'stylelint/lib/utils/declarationValueIndex.cjs';
8
- import matchAll from 'string.prototype.matchall';
9
8
  import { createRequire } from 'node:module';
9
+ import matchAll from 'string.prototype.matchall';
10
10
 
11
11
  var propertyOrder = [
12
12
  'all',
@@ -419,7 +419,7 @@ function closest(node, test) {
419
419
  function createVariableRule(ruleName, rules, url) {
420
420
  const plugin = stylelint.createPlugin(ruleName, (enabled, options = {}, context) => {
421
421
  if (enabled === false) {
422
- return noop$4
422
+ return noop$3
423
423
  }
424
424
 
425
425
  let actualRules = rules;
@@ -496,7 +496,7 @@ function createVariableRule(ruleName, rules, url) {
496
496
  return plugin
497
497
  }
498
498
 
499
- function noop$4() {}
499
+ function noop$3() {}
500
500
 
501
501
  var borders = createVariableRule(
502
502
  'primer/borders',
@@ -648,7 +648,7 @@ const walkGroups$1 = (root, validate) => {
648
648
  // eslint-disable-next-line no-unused-vars
649
649
  var responsiveWidths = stylelint.createPlugin(ruleName$3, (enabled, options = {}, context) => {
650
650
  if (!enabled) {
651
- return noop$3
651
+ return noop$2
652
652
  }
653
653
 
654
654
  const lintResult = (root, result) => {
@@ -659,7 +659,7 @@ var responsiveWidths = stylelint.createPlugin(ruleName$3, (enabled, options = {}
659
659
  }
660
660
 
661
661
  if (decl.type !== 'decl' || !decl.prop.match(/^(min-width|width)/)) {
662
- return noop$3
662
+ return noop$2
663
663
  }
664
664
 
665
665
  const problems = [];
@@ -714,40 +714,44 @@ var responsiveWidths = stylelint.createPlugin(ruleName$3, (enabled, options = {}
714
714
  return lintResult
715
715
  });
716
716
 
717
- function noop$3() {}
717
+ function noop$2() {}
718
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
- };
719
+ const require$1 = createRequire(import.meta.url);
740
720
 
741
- const ruleName$2 = 'primer/spacing';
742
- const messages$2 = stylelint.utils.ruleMessages(ruleName$2, {
743
- 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`
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
+ values.push(`${parseInt(size['original']['value']) + 1}px`);
739
+ values.push(`${parseInt(size['original']['value']) - 1}px`);
740
+
741
+ variables.push({
742
+ name: `--${size['name']}`,
743
+ values,
744
+ });
746
745
  }
746
+ }
747
747
 
748
- return `Please replace ${value} with spacing variable '${replacement}'.`
749
- },
750
- });
748
+ return variables
749
+ }
750
+
751
+ const {
752
+ createPlugin,
753
+ utils: {report, ruleMessages, validateOptions},
754
+ } = stylelint;
751
755
 
752
756
  const walkGroups = (root, validate) => {
753
757
  for (const node of root.nodes) {
@@ -760,24 +764,47 @@ const walkGroups = (root, validate) => {
760
764
  return root
761
765
  };
762
766
 
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
767
- }
767
+ const ruleName$2 = 'primer/spacing';
768
+ const messages$2 = ruleMessages(ruleName$2, {
769
+ rejected: (value, replacement) => {
770
+ if (!replacement) {
771
+ return `Please use a primer size variable instead of '${value}'. Consult the primer docs for a suitable replacement. https://primer.style/foundations/primitives/size`
772
+ }
768
773
 
769
- const lintResult = (root, result) => {
770
- root.walk(decl => {
771
- if (decl.type !== 'decl' || !decl.prop.match(/^(padding|margin)/)) {
772
- return noop$2
773
- }
774
+ return `Please replace '${value}' with size variable '${replacement['name']}'. https://primer.style/foundations/primitives/size`
775
+ },
776
+ });
774
777
 
775
- const problems = [];
778
+ const meta = {
779
+ fixable: true,
780
+ };
781
+
782
+ /** @type {import('stylelint').Rule} */
783
+ const ruleFunction = (primary, secondaryOptions, context) => {
784
+ return async (root, result) => {
785
+ // Props that we want to check
786
+ const propList = ['padding', 'margin', 'top', 'right', 'bottom', 'left'];
787
+ // Values that we want to ignore
788
+ const valueList = ['${'];
789
+
790
+ const sizes = await primitivesVariables('size');
791
+
792
+ const validOptions = validateOptions(result, ruleName$2, {
793
+ actual: primary,
794
+ possible: [true],
795
+ });
796
+
797
+ if (!validOptions) return
798
+
799
+ root.walkDecls(declNode => {
800
+ const {prop, value} = declNode;
776
801
 
777
- const parsedValue = walkGroups(valueParser(decl.value), node => {
778
- // Remove leading negative sign, if any.
779
- const cleanValue = node.value.replace(/^-/g, '');
802
+ if (!propList.some(spacingProp => prop.startsWith(spacingProp))) return
803
+ if (valueList.some(valueToIgnore => value.includes(valueToIgnore))) return
780
804
 
805
+ const problems = [];
806
+
807
+ const parsedValue = walkGroups(valueParser(value), node => {
781
808
  // Only check word types. https://github.com/TrySound/postcss-value-parser#word
782
809
  if (node.type !== 'word') {
783
810
  return
@@ -788,30 +815,36 @@ var spacing = stylelint.createPlugin(ruleName$2, (enabled, options = {}, context
788
815
  return
789
816
  }
790
817
 
791
- const valueUnit = valueParser.unit(cleanValue);
818
+ const valueUnit = valueParser.unit(node.value);
792
819
 
793
- if (valueUnit && (valueUnit.unit === '' || !/^[0-9.]+$/.test(valueUnit.number))) {
820
+ if (valueUnit && (valueUnit.unit === '' || !/^-?[0-9.]+$/.test(valueUnit.number))) {
794
821
  return
795
822
  }
796
823
 
797
- // If the a variable is found in the value, skip it.
824
+ // Skip if the value unit isn't a supported unit.
825
+ if (valueUnit && !['px', 'rem', 'em'].includes(valueUnit.unit)) {
826
+ return
827
+ }
828
+
829
+ // If the variable is found in the value, skip it.
798
830
  if (
799
- Object.keys(spacerValues).some(variable =>
800
- new RegExp(`${variable.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`).test(cleanValue),
831
+ sizes.some(variable =>
832
+ new RegExp(`${variable['name'].replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`).test(node.value),
801
833
  )
802
834
  ) {
803
835
  return
804
836
  }
805
837
 
806
- const replacement = Object.keys(spacerValues).find(spacer => spacerValues[spacer] === cleanValue) || null;
807
- const valueMatch = replacement ? spacerValues[replacement] : node.value;
838
+ const replacement = sizes.find(variable => variable.values.includes(node.value.replace('-', '')));
839
+ const fixable = replacement && valueUnit && !valueUnit.number.includes('-');
808
840
 
809
- if (replacement && context.fix) {
810
- node.value = node.value.replace(valueMatch, replacement);
841
+ if (fixable && context.fix) {
842
+ node.value = node.value.replace(node.value, `var(${replacement['name']})`);
811
843
  } else {
812
844
  problems.push({
813
- index: declarationValueIndex(decl) + node.sourceIndex,
814
- message: messages$2.rejected(valueMatch, replacement),
845
+ index: declarationValueIndex(declNode) + node.sourceIndex,
846
+ endIndex: declarationValueIndex(declNode) + node.sourceIndex + node.value.length,
847
+ message: messages$2.rejected(node.value, replacement),
815
848
  });
816
849
  }
817
850
 
@@ -819,27 +852,30 @@ var spacing = stylelint.createPlugin(ruleName$2, (enabled, options = {}, context
819
852
  });
820
853
 
821
854
  if (context.fix) {
822
- decl.value = parsedValue.toString();
855
+ declNode.value = parsedValue.toString();
823
856
  }
824
857
 
825
858
  if (problems.length) {
826
859
  for (const err of problems) {
827
- stylelint.utils.report({
860
+ report({
828
861
  index: err.index,
862
+ endIndex: err.endIndex,
829
863
  message: err.message,
830
- node: decl,
864
+ node: declNode,
831
865
  result,
832
866
  ruleName: ruleName$2,
833
867
  });
834
868
  }
835
869
  }
836
870
  });
837
- };
871
+ }
872
+ };
838
873
 
839
- return lintResult
840
- });
874
+ ruleFunction.ruleName = ruleName$2;
875
+ ruleFunction.messages = messages$2;
876
+ ruleFunction.meta = meta;
841
877
 
842
- function noop$2() {}
878
+ var spacing = createPlugin(ruleName$2, ruleFunction);
843
879
 
844
880
  var typography = createVariableRule(
845
881
  'primer/typography',
@@ -1625,7 +1661,6 @@ var index = {
1625
1661
  'comment-empty-line-before': null,
1626
1662
  'length-zero-no-unit': null,
1627
1663
  'selector-max-type': null,
1628
- 'primer/spacing': null,
1629
1664
  'primer/colors': null,
1630
1665
  'primer/borders': null,
1631
1666
  'primer/typography': null,
@@ -1655,6 +1690,7 @@ var index = {
1655
1690
  },
1656
1691
  {
1657
1692
  files: ['**/*.module.css'],
1693
+ plugins: ['stylelint-css-modules-no-global-scoped-selector'],
1658
1694
  rules: {
1659
1695
  'property-no-unknown': [
1660
1696
  true,
@@ -1679,8 +1715,8 @@ var index = {
1679
1715
  ignoreFunctions: ['global'],
1680
1716
  },
1681
1717
  ],
1718
+ 'css-modules/no-global-scoped-selector': true,
1682
1719
  // temporarily disabiling Primer plugins while we work on upgrades https://github.com/github/primer/issues/3165
1683
- 'primer/spacing': null,
1684
1720
  'primer/borders': null,
1685
1721
  'primer/typography': null,
1686
1722
  'primer/box-shadow': null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primer/stylelint-config",
3
- "version": "13.0.0-rc.864dd2a",
3
+ "version": "13.0.0-rc.99be5f2",
4
4
  "description": "Sharable stylelint config used by GitHub's CSS",
5
5
  "author": "GitHub, Inc.",
6
6
  "license": "MIT",
@@ -45,7 +45,7 @@
45
45
  "dependencies": {
46
46
  "@github/browserslist-config": "^1.0.0",
47
47
  "@primer/css": "^21.0.8",
48
- "@primer/primitives": "^7.16.0",
48
+ "@primer/primitives": "^7.17.1",
49
49
  "anymatch": "^3.1.1",
50
50
  "postcss-scss": "^4.0.2",
51
51
  "postcss-styled-syntax": "^0.6.4",
@@ -53,16 +53,22 @@
53
53
  "string.prototype.matchall": "^4.0.2",
54
54
  "stylelint": "^16.3.1",
55
55
  "stylelint-config-standard": "^36.0.0",
56
+ "stylelint-css-modules-no-global-scoped-selector": "^1.0.2",
56
57
  "stylelint-no-unsupported-browser-features": "^8.0.0",
57
58
  "stylelint-order": "^6.0.4",
58
59
  "stylelint-scss": "^6.2.0",
59
60
  "stylelint-value-no-unknown-custom-properties": "^6.0.1",
60
61
  "tap-map": "^1.0.0"
61
62
  },
63
+ "overrides": {
64
+ "stylelint-css-modules-no-global-scoped-selector": {
65
+ "stylelint": "$stylelint"
66
+ }
67
+ },
62
68
  "prettier": "@github/prettier-config",
63
69
  "devDependencies": {
64
70
  "@changesets/changelog-github": "^0.5.0",
65
- "@changesets/cli": "2.26.1",
71
+ "@changesets/cli": "2.27.1",
66
72
  "@github/prettier-config": "^0.0.6",
67
73
  "@rollup/plugin-commonjs": "^25.0.7",
68
74
  "@rollup/plugin-json": "^6.1.0",
@@ -0,0 +1,33 @@
1
+ import {createRequire} from 'node:module'
2
+
3
+ const require = createRequire(import.meta.url)
4
+
5
+ export async function primitivesVariables(type) {
6
+ const variables = []
7
+
8
+ const files = []
9
+ switch (type) {
10
+ case 'size':
11
+ files.push('base/size/size.json')
12
+ break
13
+ }
14
+
15
+ for (const file of files) {
16
+ // eslint-disable-next-line import/no-dynamic-require
17
+ const data = require(`@primer/primitives/dist/styleLint/${file}`)
18
+
19
+ for (const key of Object.keys(data)) {
20
+ const size = data[key]
21
+ const values = size['value']
22
+ values.push(`${parseInt(size['original']['value']) + 1}px`)
23
+ values.push(`${parseInt(size['original']['value']) - 1}px`)
24
+
25
+ variables.push({
26
+ name: `--${size['name']}`,
27
+ values,
28
+ })
29
+ }
30
+ }
31
+
32
+ return variables
33
+ }
@@ -1,39 +1,12 @@
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
5
 
5
- // TODO: Pull this in from primer/primitives
6
- const spacerValues = {
7
- '$spacer-1': '4px',
8
- '$spacer-2': '8px',
9
- '$spacer-3': '16px',
10
- '$spacer-4': '24px',
11
- '$spacer-5': '32px',
12
- '$spacer-6': '40px',
13
- '$spacer-7': '48px',
14
- '$spacer-8': '64px',
15
- '$spacer-9': '80px',
16
- '$spacer-10': '96px',
17
- '$spacer-11': '112px',
18
- '$spacer-12': '128px',
19
- '$em-spacer-1': '0.0625em',
20
- '$em-spacer-2': '0.125em',
21
- '$em-spacer-3': '0.25em',
22
- '$em-spacer-4': '0.375em',
23
- '$em-spacer-5': '0.5em',
24
- '$em-spacer-6': '0.75em',
25
- }
26
-
27
- export const ruleName = 'primer/spacing'
28
- export const messages = stylelint.utils.ruleMessages(ruleName, {
29
- rejected: (value, replacement) => {
30
- if (replacement === null) {
31
- 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`
32
- }
33
-
34
- return `Please replace ${value} with spacing variable '${replacement}'.`
35
- },
36
- })
6
+ const {
7
+ createPlugin,
8
+ utils: {report, ruleMessages, validateOptions},
9
+ } = stylelint
37
10
 
38
11
  const walkGroups = (root, validate) => {
39
12
  for (const node of root.nodes) {
@@ -46,24 +19,47 @@ const walkGroups = (root, validate) => {
46
19
  return root
47
20
  }
48
21
 
49
- // eslint-disable-next-line no-unused-vars
50
- export default stylelint.createPlugin(ruleName, (enabled, options = {}, context) => {
51
- if (!enabled) {
52
- return noop
53
- }
22
+ export const ruleName = 'primer/spacing'
23
+ export const messages = ruleMessages(ruleName, {
24
+ rejected: (value, replacement) => {
25
+ if (!replacement) {
26
+ return `Please use a primer size variable instead of '${value}'. Consult the primer docs for a suitable replacement. https://primer.style/foundations/primitives/size`
27
+ }
54
28
 
55
- const lintResult = (root, result) => {
56
- root.walk(decl => {
57
- if (decl.type !== 'decl' || !decl.prop.match(/^(padding|margin)/)) {
58
- return noop
59
- }
29
+ return `Please replace '${value}' with size variable '${replacement['name']}'. https://primer.style/foundations/primitives/size`
30
+ },
31
+ })
60
32
 
61
- const problems = []
33
+ const meta = {
34
+ fixable: true,
35
+ }
36
+
37
+ /** @type {import('stylelint').Rule} */
38
+ 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
+
47
+ const validOptions = validateOptions(result, ruleName, {
48
+ actual: primary,
49
+ possible: [true],
50
+ })
51
+
52
+ if (!validOptions) return
53
+
54
+ root.walkDecls(declNode => {
55
+ const {prop, value} = declNode
62
56
 
63
- const parsedValue = walkGroups(valueParser(decl.value), node => {
64
- // Remove leading negative sign, if any.
65
- const cleanValue = node.value.replace(/^-/g, '')
57
+ if (!propList.some(spacingProp => prop.startsWith(spacingProp))) return
58
+ if (valueList.some(valueToIgnore => value.includes(valueToIgnore))) return
66
59
 
60
+ const problems = []
61
+
62
+ const parsedValue = walkGroups(valueParser(value), node => {
67
63
  // Only check word types. https://github.com/TrySound/postcss-value-parser#word
68
64
  if (node.type !== 'word') {
69
65
  return
@@ -74,30 +70,36 @@ export default stylelint.createPlugin(ruleName, (enabled, options = {}, context)
74
70
  return
75
71
  }
76
72
 
77
- const valueUnit = valueParser.unit(cleanValue)
73
+ const valueUnit = valueParser.unit(node.value)
78
74
 
79
- if (valueUnit && (valueUnit.unit === '' || !/^[0-9.]+$/.test(valueUnit.number))) {
75
+ if (valueUnit && (valueUnit.unit === '' || !/^-?[0-9.]+$/.test(valueUnit.number))) {
80
76
  return
81
77
  }
82
78
 
83
- // If the a variable is found in the value, skip it.
79
+ // Skip if the value unit isn't a supported unit.
80
+ if (valueUnit && !['px', 'rem', 'em'].includes(valueUnit.unit)) {
81
+ return
82
+ }
83
+
84
+ // If the variable is found in the value, skip it.
84
85
  if (
85
- Object.keys(spacerValues).some(variable =>
86
- new RegExp(`${variable.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`).test(cleanValue),
86
+ sizes.some(variable =>
87
+ new RegExp(`${variable['name'].replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`).test(node.value),
87
88
  )
88
89
  ) {
89
90
  return
90
91
  }
91
92
 
92
- const replacement = Object.keys(spacerValues).find(spacer => spacerValues[spacer] === cleanValue) || null
93
- const valueMatch = replacement ? spacerValues[replacement] : node.value
93
+ const replacement = sizes.find(variable => variable.values.includes(node.value.replace('-', '')))
94
+ const fixable = replacement && valueUnit && !valueUnit.number.includes('-')
94
95
 
95
- if (replacement && context.fix) {
96
- node.value = node.value.replace(valueMatch, replacement)
96
+ if (fixable && context.fix) {
97
+ node.value = node.value.replace(node.value, `var(${replacement['name']})`)
97
98
  } else {
98
99
  problems.push({
99
- index: declarationValueIndex(decl) + node.sourceIndex,
100
- message: messages.rejected(valueMatch, replacement),
100
+ index: declarationValueIndex(declNode) + node.sourceIndex,
101
+ endIndex: declarationValueIndex(declNode) + node.sourceIndex + node.value.length,
102
+ message: messages.rejected(node.value, replacement),
101
103
  })
102
104
  }
103
105
 
@@ -105,15 +107,16 @@ export default stylelint.createPlugin(ruleName, (enabled, options = {}, context)
105
107
  })
106
108
 
107
109
  if (context.fix) {
108
- decl.value = parsedValue.toString()
110
+ declNode.value = parsedValue.toString()
109
111
  }
110
112
 
111
113
  if (problems.length) {
112
114
  for (const err of problems) {
113
- stylelint.utils.report({
115
+ report({
114
116
  index: err.index,
117
+ endIndex: err.endIndex,
115
118
  message: err.message,
116
- node: decl,
119
+ node: declNode,
117
120
  result,
118
121
  ruleName,
119
122
  })
@@ -121,8 +124,10 @@ export default stylelint.createPlugin(ruleName, (enabled, options = {}, context)
121
124
  }
122
125
  })
123
126
  }
127
+ }
124
128
 
125
- return lintResult
126
- })
129
+ ruleFunction.ruleName = ruleName
130
+ ruleFunction.messages = messages
131
+ ruleFunction.meta = meta
127
132
 
128
- function noop() {}
133
+ export default createPlugin(ruleName, ruleFunction)