index.js 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. 'use strict';
  2. const atRuleParamIndex = require('../../utils/atRuleParamIndex');
  3. const declarationValueIndex = require('../../utils/declarationValueIndex');
  4. const getUnitFromValueNode = require('../../utils/getUnitFromValueNode');
  5. const optionsMatches = require('../../utils/optionsMatches');
  6. const report = require('../../utils/report');
  7. const ruleMessages = require('../../utils/ruleMessages');
  8. const validateObjectWithArrayProps = require('../../utils/validateObjectWithArrayProps');
  9. const validateOptions = require('../../utils/validateOptions');
  10. const valueParser = require('postcss-value-parser');
  11. const { isRegExp, isString } = require('../../utils/validateTypes');
  12. const ruleName = 'unit-allowed-list';
  13. const messages = ruleMessages(ruleName, {
  14. rejected: (unit) => `Unexpected unit "${unit}"`,
  15. });
  16. const meta = {
  17. url: 'https://stylelint.io/user-guide/rules/list/unit-allowed-list',
  18. };
  19. /** @type {import('stylelint').Rule<string | string[]>} */
  20. const rule = (primary, secondaryOptions) => {
  21. return (root, result) => {
  22. const validOptions = validateOptions(
  23. result,
  24. ruleName,
  25. {
  26. actual: primary,
  27. possible: [isString],
  28. },
  29. {
  30. optional: true,
  31. actual: secondaryOptions,
  32. possible: {
  33. ignoreFunctions: [isString, isRegExp],
  34. ignoreProperties: [validateObjectWithArrayProps(isString, isRegExp)],
  35. },
  36. },
  37. );
  38. if (!validOptions) {
  39. return;
  40. }
  41. const primaryValues = [primary].flat();
  42. /**
  43. * @template {import('postcss').AtRule | import('postcss').Declaration} T
  44. * @param {T} node
  45. * @param {string} value
  46. * @param {(node: T) => number} getIndex
  47. * @returns {void}
  48. */
  49. function check(node, value, getIndex) {
  50. // make sure multiplication operations (*) are divided - not handled
  51. // by postcss-value-parser
  52. value = value.replace(/\*/g, ',');
  53. valueParser(value).walk((valueNode) => {
  54. if (valueNode.type === 'function') {
  55. const valueLowerCase = valueNode.value.toLowerCase();
  56. // Ignore wrong units within `url` function
  57. if (valueLowerCase === 'url') {
  58. return false;
  59. }
  60. if (optionsMatches(secondaryOptions, 'ignoreFunctions', valueLowerCase)) {
  61. return false;
  62. }
  63. }
  64. const unit = getUnitFromValueNode(valueNode);
  65. if (!unit || (unit && primaryValues.includes(unit.toLowerCase()))) {
  66. return;
  67. }
  68. if (
  69. 'prop' in node &&
  70. secondaryOptions &&
  71. optionsMatches(secondaryOptions.ignoreProperties, unit.toLowerCase(), node.prop)
  72. ) {
  73. return;
  74. }
  75. report({
  76. index: getIndex(node) + valueNode.sourceIndex,
  77. message: messages.rejected(unit),
  78. node,
  79. result,
  80. ruleName,
  81. });
  82. });
  83. }
  84. root.walkAtRules(/^media$/i, (atRule) => check(atRule, atRule.params, atRuleParamIndex));
  85. root.walkDecls((decl) => check(decl, decl.value, declarationValueIndex));
  86. };
  87. };
  88. rule.primaryOptionArray = true;
  89. rule.ruleName = ruleName;
  90. rule.messages = messages;
  91. rule.meta = meta;
  92. module.exports = rule;