index.mjs 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. import valueParser from 'postcss-value-parser';
  2. import { isRegExp, isString } from '../../utils/validateTypes.mjs';
  3. import { declarationValueIndex } from '../../utils/nodeFieldIndices.mjs';
  4. import isCustomProperty from '../../utils/isCustomProperty.mjs';
  5. import isStandardSyntaxFunction from '../../utils/isStandardSyntaxFunction.mjs';
  6. import matchesStringOrRegExp from '../../utils/matchesStringOrRegExp.mjs';
  7. import report from '../../utils/report.mjs';
  8. import ruleMessages from '../../utils/ruleMessages.mjs';
  9. import validateOptions from '../../utils/validateOptions.mjs';
  10. import vendor from '../../utils/vendor.mjs';
  11. const ruleName = 'function-allowed-list';
  12. const messages = ruleMessages(ruleName, {
  13. rejected: (name) => `Unexpected function "${name}"`,
  14. });
  15. const meta = {
  16. url: 'https://stylelint.io/user-guide/rules/function-allowed-list',
  17. };
  18. /** @type {import('stylelint').CoreRules[ruleName]} */
  19. const rule = (primary, secondaryOptions) => {
  20. return (root, result) => {
  21. const validOptions = validateOptions(
  22. result,
  23. ruleName,
  24. {
  25. actual: primary,
  26. possible: [isString, isRegExp],
  27. },
  28. {
  29. actual: secondaryOptions,
  30. possible: {
  31. exceptWithoutPropertyFallback: [isString, isRegExp],
  32. },
  33. optional: true,
  34. },
  35. );
  36. if (!validOptions) {
  37. return;
  38. }
  39. const exceptWithoutPropertyFallback = secondaryOptions?.exceptWithoutPropertyFallback ?? [];
  40. root.walkDecls((decl) => {
  41. valueParser(decl.value).walk((node) => {
  42. if (node.type !== 'function') {
  43. return;
  44. }
  45. if (!isStandardSyntaxFunction(node)) {
  46. return;
  47. }
  48. const unprefixedName = vendor.unprefixed(node.value);
  49. if (matchesStringOrRegExp(unprefixedName, primary)) {
  50. // Check if function requires fallback
  51. if (!matchesStringOrRegExp(unprefixedName, exceptWithoutPropertyFallback)) {
  52. return;
  53. }
  54. if (hasPrevPropertyDeclaration(decl)) return;
  55. }
  56. const index = declarationValueIndex(decl) + node.sourceIndex;
  57. const endIndex = index + node.value.length;
  58. report({
  59. message: messages.rejected,
  60. messageArgs: [node.value],
  61. node: decl,
  62. index,
  63. endIndex,
  64. result,
  65. ruleName,
  66. });
  67. });
  68. });
  69. };
  70. };
  71. /**
  72. * @param {import('postcss').Declaration} decl
  73. * @returns {boolean}
  74. */
  75. function hasPrevPropertyDeclaration(decl) {
  76. const prop = decl.prop.toLowerCase();
  77. if (isCustomProperty(prop)) return false;
  78. let prev = decl.prev();
  79. while (prev) {
  80. if (prev.type === 'decl' && prev.prop.toLowerCase() === prop) {
  81. return true;
  82. }
  83. prev = prev.prev();
  84. }
  85. return false;
  86. }
  87. rule.primaryOptionArray = true;
  88. rule.ruleName = ruleName;
  89. rule.messages = messages;
  90. rule.meta = meta;
  91. export default rule;