index.mjs 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. import valueParser from 'postcss-value-parser';
  2. import { atRuleRegexes } from '../../utils/regexes.mjs';
  3. import { declarationValueIndex } from '../../utils/nodeFieldIndices.mjs';
  4. import isVarFunction from '../../utils/isVarFunction.mjs';
  5. import report from '../../utils/report.mjs';
  6. import ruleMessages from '../../utils/ruleMessages.mjs';
  7. import validateOptions from '../../utils/validateOptions.mjs';
  8. const ruleName = 'custom-property-no-missing-var-function';
  9. const messages = ruleMessages(ruleName, {
  10. rejected: (customProperty) => `Unexpected missing var function for "${customProperty}"`,
  11. });
  12. const meta = {
  13. url: 'https://stylelint.io/user-guide/rules/custom-property-no-missing-var-function',
  14. };
  15. /** @typedef {import('postcss-value-parser').Node} Node */
  16. // Properties that can receive a custom-ident
  17. const IGNORED_PROPERTIES = new Set([
  18. 'animation',
  19. 'animation-name',
  20. 'container-name',
  21. 'counter-increment',
  22. 'counter-reset',
  23. 'counter-set',
  24. 'grid-column',
  25. 'grid-column-end',
  26. 'grid-column-start',
  27. 'grid-row',
  28. 'grid-row-end',
  29. 'grid-row-start',
  30. 'list-style',
  31. 'list-style-type',
  32. 'transition',
  33. 'transition-property',
  34. 'view-transition-name',
  35. 'will-change',
  36. ]);
  37. /** @type {import('stylelint').CoreRules[ruleName]} */
  38. const rule = (primary) => {
  39. return (root, result) => {
  40. const validOptions = validateOptions(result, ruleName, { actual: primary });
  41. if (!validOptions) return;
  42. /** @type {Set<string>} */
  43. const knownCustomProperties = new Set();
  44. root.walkAtRules(atRuleRegexes.propertyName, ({ params }) => {
  45. knownCustomProperties.add(params);
  46. });
  47. root.walkDecls(/^--/, ({ prop }) => {
  48. knownCustomProperties.add(prop);
  49. });
  50. root.walkDecls((decl) => {
  51. const { prop, value } = decl;
  52. if (!value.includes('--')) return;
  53. if (IGNORED_PROPERTIES.has(prop.toLowerCase())) return;
  54. valueParser(value).nodes.forEach((childNode) => {
  55. check(childNode, decl);
  56. });
  57. });
  58. /**
  59. * @param {Node} node
  60. * @param {import('postcss').Declaration} decl
  61. */
  62. function check(node, decl) {
  63. if (node.type === 'function') {
  64. const name = node.value.toLowerCase();
  65. let args = node.nodes;
  66. if (name === 'var') args = node.nodes.slice(1);
  67. else if (name === 'running') {
  68. const [child] = node.nodes;
  69. const mustDrill = child && isVarFunction(child);
  70. if (mustDrill) args = child.nodes.slice(1);
  71. else return;
  72. }
  73. args.forEach((arg) => check(arg, decl));
  74. return;
  75. }
  76. if (!isDashedIdent(node)) return;
  77. if (!knownCustomProperties.has(node.value)) return;
  78. const index = declarationValueIndex(decl) + node.sourceIndex;
  79. const endIndex = index + node.value.length;
  80. report({
  81. message: messages.rejected,
  82. messageArgs: [node.value],
  83. node: decl,
  84. index,
  85. endIndex,
  86. result,
  87. ruleName,
  88. });
  89. }
  90. };
  91. };
  92. /**
  93. * @param {Node} node
  94. */
  95. function isDashedIdent({ type, value }) {
  96. return type === 'word' && value.startsWith('--');
  97. }
  98. rule.ruleName = ruleName;
  99. rule.messages = messages;
  100. rule.meta = meta;
  101. export default rule;