index.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. 'use strict';
  2. const atRuleParamIndex = require('../../utils/atRuleParamIndex');
  3. const declarationValueIndex = require('../../utils/declarationValueIndex');
  4. const getUnitFromValueNode = require('../../utils/getUnitFromValueNode');
  5. const isStandardSyntaxAtRule = require('../../utils/isStandardSyntaxAtRule');
  6. const isStandardSyntaxDeclaration = require('../../utils/isStandardSyntaxDeclaration');
  7. const keywordSets = require('../../reference/keywordSets');
  8. const mediaParser = require('postcss-media-query-parser').default;
  9. const optionsMatches = require('../../utils/optionsMatches');
  10. const report = require('../../utils/report');
  11. const ruleMessages = require('../../utils/ruleMessages');
  12. const validateOptions = require('../../utils/validateOptions');
  13. const valueParser = require('postcss-value-parser');
  14. const vendor = require('../../utils/vendor');
  15. const { isRegExp, isString, assert } = require('../../utils/validateTypes');
  16. const ruleName = 'unit-no-unknown';
  17. const messages = ruleMessages(ruleName, {
  18. rejected: (unit) => `Unexpected unknown unit "${unit}"`,
  19. });
  20. const meta = {
  21. url: 'https://stylelint.io/user-guide/rules/list/unit-no-unknown',
  22. };
  23. /** @type {import('stylelint').Rule} */
  24. const rule = (primary, secondaryOptions) => {
  25. return (root, result) => {
  26. const validOptions = validateOptions(
  27. result,
  28. ruleName,
  29. { actual: primary },
  30. {
  31. actual: secondaryOptions,
  32. possible: {
  33. ignoreUnits: [isString, isRegExp],
  34. ignoreFunctions: [isString, isRegExp],
  35. },
  36. optional: true,
  37. },
  38. );
  39. if (!validOptions) {
  40. return;
  41. }
  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. const parsedValue = valueParser(value);
  54. parsedValue.walk((valueNode) => {
  55. // Ignore wrong units within `url` function
  56. // and within functions listed in the `ignoreFunctions` option
  57. if (
  58. valueNode.type === 'function' &&
  59. (valueNode.value.toLowerCase() === 'url' ||
  60. optionsMatches(secondaryOptions, 'ignoreFunctions', valueNode.value))
  61. ) {
  62. return false;
  63. }
  64. const unit = getUnitFromValueNode(valueNode);
  65. if (!unit) {
  66. return;
  67. }
  68. if (optionsMatches(secondaryOptions, 'ignoreUnits', unit)) {
  69. return;
  70. }
  71. if (keywordSets.units.has(unit.toLowerCase()) && unit.toLowerCase() !== 'x') {
  72. return;
  73. }
  74. if (unit.toLowerCase() === 'x') {
  75. if (
  76. node.type === 'atrule' &&
  77. node.name === 'media' &&
  78. node.params.toLowerCase().includes('resolution')
  79. ) {
  80. let ignoreUnit = false;
  81. mediaParser(node.params).walk((mediaNode, _i, mediaNodes) => {
  82. const lastMediaNode = mediaNodes[mediaNodes.length - 1];
  83. if (
  84. mediaNode.value.toLowerCase().includes('resolution') &&
  85. lastMediaNode.sourceIndex === valueNode.sourceIndex
  86. ) {
  87. ignoreUnit = true;
  88. return false;
  89. }
  90. });
  91. if (ignoreUnit) {
  92. return;
  93. }
  94. }
  95. if (node.type === 'decl') {
  96. if (node.prop.toLowerCase() === 'image-resolution') {
  97. return;
  98. }
  99. if (/^(?:-webkit-)?image-set[\s(]/i.test(value)) {
  100. const imageSet = parsedValue.nodes.find(
  101. (n) => vendor.unprefixed(n.value) === 'image-set',
  102. );
  103. assert(imageSet);
  104. assert('nodes' in imageSet);
  105. const imageSetLastNode = imageSet.nodes[imageSet.nodes.length - 1];
  106. const imageSetValueLastIndex = imageSetLastNode.sourceIndex;
  107. if (imageSetValueLastIndex >= valueNode.sourceIndex) {
  108. return;
  109. }
  110. }
  111. }
  112. }
  113. report({
  114. index: getIndex(node) + valueNode.sourceIndex,
  115. message: messages.rejected(unit),
  116. node,
  117. result,
  118. ruleName,
  119. });
  120. });
  121. }
  122. root.walkAtRules(/^media$/i, (atRule) => {
  123. if (!isStandardSyntaxAtRule(atRule)) {
  124. return;
  125. }
  126. check(atRule, atRule.params, atRuleParamIndex);
  127. });
  128. root.walkDecls((decl) => {
  129. if (!isStandardSyntaxDeclaration(decl)) {
  130. return;
  131. }
  132. check(decl, decl.value, declarationValueIndex);
  133. });
  134. };
  135. };
  136. rule.ruleName = ruleName;
  137. rule.messages = messages;
  138. rule.meta = meta;
  139. module.exports = rule;