index.mjs 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. import valueParser from 'postcss-value-parser';
  2. import { atRuleParamIndex, declarationValueIndex } from '../../utils/nodeFieldIndices.mjs';
  3. import { isRegExp, isString } from '../../utils/validateTypes.mjs';
  4. import { atRuleRegexes } from '../../utils/regexes.mjs';
  5. import isCustomProperty from '../../utils/isCustomProperty.mjs';
  6. import isStandardSyntaxProperty from '../../utils/isStandardSyntaxProperty.mjs';
  7. import isVarFunction from '../../utils/isVarFunction.mjs';
  8. import report from '../../utils/report.mjs';
  9. import ruleMessages from '../../utils/ruleMessages.mjs';
  10. import validateOptions from '../../utils/validateOptions.mjs';
  11. const ruleName = 'custom-property-pattern';
  12. const messages = ruleMessages(ruleName, {
  13. expected: (property, pattern) => `Expected "${property}" to match pattern "${pattern}"`,
  14. });
  15. const meta = {
  16. url: 'https://stylelint.io/user-guide/rules/custom-property-pattern',
  17. };
  18. const VAR_FUNC_REGEX = /var\(/i;
  19. /** @type {import('stylelint').CoreRules[ruleName]} */
  20. const rule = (primary) => {
  21. return (root, result) => {
  22. const validOptions = validateOptions(result, ruleName, {
  23. actual: primary,
  24. possible: [isRegExp, isString],
  25. });
  26. if (!validOptions) return;
  27. const regexpPattern = isString(primary) ? new RegExp(primary) : primary;
  28. /**
  29. * @param {string} property
  30. * @returns {boolean}
  31. */
  32. function check(property) {
  33. return (
  34. !isCustomProperty(property) ||
  35. !isStandardSyntaxProperty(property) ||
  36. regexpPattern.test(property.slice(2))
  37. );
  38. }
  39. root.walkDecls((decl) => {
  40. const { prop, value } = decl;
  41. if (VAR_FUNC_REGEX.test(value)) {
  42. const parsedValue = valueParser(value);
  43. parsedValue.walk((node) => {
  44. if (!isVarFunction(node)) return;
  45. const { nodes } = node;
  46. const firstNode = nodes[0];
  47. if (!firstNode || check(firstNode.value)) return;
  48. complain(declarationValueIndex(decl) + firstNode.sourceIndex, firstNode.value, decl);
  49. });
  50. }
  51. if (check(prop)) return;
  52. complain(0, prop, decl);
  53. });
  54. root.walkAtRules(atRuleRegexes.propertyName, (atRule) => {
  55. const { params: propName } = atRule;
  56. if (check(propName)) return;
  57. complain(atRuleParamIndex(atRule), propName, atRule);
  58. });
  59. /**
  60. * @param {number} index
  61. * @param {string} propName
  62. * @param {import('postcss').Declaration|import('postcss').AtRule} node
  63. */
  64. function complain(index, propName, node) {
  65. report({
  66. result,
  67. ruleName,
  68. message: messages.expected,
  69. messageArgs: [propName, primary],
  70. node,
  71. index,
  72. endIndex: index + propName.length,
  73. });
  74. }
  75. };
  76. };
  77. rule.ruleName = ruleName;
  78. rule.messages = messages;
  79. rule.meta = meta;
  80. export default rule;