index.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. 'use strict';
  2. const valueParser = require('postcss-value-parser');
  3. const atRuleParamIndex = require('../../utils/atRuleParamIndex');
  4. const declarationValueIndex = require('../../utils/declarationValueIndex');
  5. const getAtRuleParams = require('../../utils/getAtRuleParams');
  6. const getDeclarationValue = require('../../utils/getDeclarationValue');
  7. const isCustomProperty = require('../../utils/isCustomProperty');
  8. const isMathFunction = require('../../utils/isMathFunction');
  9. const isStandardSyntaxAtRule = require('../../utils/isStandardSyntaxAtRule');
  10. const keywordSets = require('../../reference/keywordSets');
  11. const optionsMatches = require('../../utils/optionsMatches');
  12. const report = require('../../utils/report');
  13. const ruleMessages = require('../../utils/ruleMessages');
  14. const setAtRuleParams = require('../../utils/setAtRuleParams');
  15. const setDeclarationValue = require('../../utils/setDeclarationValue');
  16. const validateOptions = require('../../utils/validateOptions');
  17. const { isRegExp, isString } = require('../../utils/validateTypes');
  18. const ruleName = 'length-zero-no-unit';
  19. const messages = ruleMessages(ruleName, {
  20. rejected: 'Unexpected unit',
  21. });
  22. const meta = {
  23. url: 'https://stylelint.io/user-guide/rules/list/length-zero-no-unit',
  24. };
  25. /** @type {import('stylelint').Rule} */
  26. const rule = (primary, secondaryOptions, context) => {
  27. return (root, result) => {
  28. const validOptions = validateOptions(
  29. result,
  30. ruleName,
  31. {
  32. actual: primary,
  33. },
  34. {
  35. actual: secondaryOptions,
  36. possible: {
  37. ignore: ['custom-properties'],
  38. ignoreFunctions: [isString, isRegExp],
  39. },
  40. optional: true,
  41. },
  42. );
  43. if (!validOptions) return;
  44. let needsFix;
  45. /**
  46. * @param {import('postcss').Node} node
  47. * @param {number} nodeIndex
  48. * @param {import('postcss-value-parser').Node} valueNode
  49. */
  50. function check(node, nodeIndex, valueNode) {
  51. const { value, sourceIndex } = valueNode;
  52. if (isMathFunction(valueNode)) return false;
  53. if (isFunction(valueNode) && optionsMatches(secondaryOptions, 'ignoreFunctions', value))
  54. return false;
  55. if (!isWord(valueNode)) return;
  56. const numberUnit = valueParser.unit(value);
  57. if (numberUnit === false) return;
  58. const { number, unit } = numberUnit;
  59. if (unit === '') return;
  60. if (!isLength(unit)) return;
  61. if (isFraction(unit)) return;
  62. if (!isZero(number)) return;
  63. if (context.fix) {
  64. valueNode.value = number;
  65. needsFix = true;
  66. return;
  67. }
  68. report({
  69. index: nodeIndex + sourceIndex + number.length,
  70. message: messages.rejected,
  71. node,
  72. result,
  73. ruleName,
  74. });
  75. }
  76. /**
  77. * @param {import('postcss').AtRule} node
  78. */
  79. function checkAtRule(node) {
  80. if (!isStandardSyntaxAtRule(node)) return;
  81. needsFix = false;
  82. const index = atRuleParamIndex(node);
  83. const parsedValue = valueParser(getAtRuleParams(node));
  84. parsedValue.walk((valueNode) => check(node, index, valueNode));
  85. if (needsFix) {
  86. setAtRuleParams(node, parsedValue.toString());
  87. }
  88. }
  89. /**
  90. * @param {import('postcss').Declaration} node
  91. */
  92. function checkDecl(node) {
  93. needsFix = false;
  94. const { prop } = node;
  95. if (isLineHeight(prop)) return;
  96. if (isFlex(prop)) return;
  97. if (optionsMatches(secondaryOptions, 'ignore', 'custom-properties') && isCustomProperty(prop))
  98. return;
  99. const index = declarationValueIndex(node);
  100. const parsedValue = valueParser(getDeclarationValue(node));
  101. parsedValue.walk((valueNode, valueNodeIndex, valueNodes) => {
  102. if (isLineHeightValue(node, valueNodes, valueNodeIndex)) return;
  103. return check(node, index, valueNode);
  104. });
  105. if (needsFix) {
  106. setDeclarationValue(node, parsedValue.toString());
  107. }
  108. }
  109. root.walkAtRules(checkAtRule);
  110. root.walkDecls(checkDecl);
  111. };
  112. };
  113. /**
  114. * @param {import('postcss').Declaration} decl
  115. * @param {import('postcss-value-parser').Node[]} nodes
  116. * @param {number} index
  117. */
  118. function isLineHeightValue({ prop }, nodes, index) {
  119. return (
  120. prop.toLowerCase() === 'font' &&
  121. index > 0 &&
  122. nodes[index - 1].type === 'div' &&
  123. nodes[index - 1].value === '/'
  124. );
  125. }
  126. /**
  127. * @param {string} prop
  128. */
  129. function isLineHeight(prop) {
  130. return prop.toLowerCase() === 'line-height';
  131. }
  132. /**
  133. * @param {string} prop
  134. */
  135. function isFlex(prop) {
  136. return prop.toLowerCase() === 'flex';
  137. }
  138. /**
  139. * @param {import('postcss-value-parser').Node} node
  140. */
  141. function isWord({ type }) {
  142. return type === 'word';
  143. }
  144. /**
  145. * @param {string} unit
  146. */
  147. function isLength(unit) {
  148. return keywordSets.lengthUnits.has(unit.toLowerCase());
  149. }
  150. /**
  151. * @param {import('postcss-value-parser').Node} node
  152. */
  153. function isFunction({ type }) {
  154. return type === 'function';
  155. }
  156. /**
  157. * @param {string} unit
  158. */
  159. function isFraction(unit) {
  160. return unit.toLowerCase() === 'fr';
  161. }
  162. /**
  163. * @param {string} number
  164. */
  165. function isZero(number) {
  166. return Number.parseFloat(number) === 0;
  167. }
  168. rule.ruleName = ruleName;
  169. rule.messages = messages;
  170. rule.meta = meta;
  171. module.exports = rule;