index.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. 'use strict';
  2. const valueParser = require('postcss-value-parser');
  3. const declarationValueIndex = require('../../utils/declarationValueIndex');
  4. const getDeclarationValue = require('../../utils/getDeclarationValue');
  5. const isStandardSyntaxValue = require('../../utils/isStandardSyntaxValue');
  6. const report = require('../../utils/report');
  7. const ruleMessages = require('../../utils/ruleMessages');
  8. const setDeclarationValue = require('../../utils/setDeclarationValue');
  9. const validateOptions = require('../../utils/validateOptions');
  10. const ruleName = 'hue-degree-notation';
  11. const messages = ruleMessages(ruleName, {
  12. expected: (unfixed, fixed) => `Expected "${unfixed}" to be "${fixed}"`,
  13. });
  14. const meta = {
  15. url: 'https://stylelint.io/user-guide/rules/hue-degree-notation',
  16. fixable: true,
  17. };
  18. const HUE_FIRST_ARG_FUNCS = ['hsl', 'hsla', 'hwb'];
  19. const HUE_THIRD_ARG_FUNCS = ['lch'];
  20. const HUE_FUNCS = new Set([...HUE_FIRST_ARG_FUNCS, ...HUE_THIRD_ARG_FUNCS]);
  21. /** @type {import('stylelint').Rule} */
  22. const rule = (primary, _secondaryOptions, context) => {
  23. return (root, result) => {
  24. const validOptions = validateOptions(result, ruleName, {
  25. actual: primary,
  26. possible: ['angle', 'number'],
  27. });
  28. if (!validOptions) return;
  29. root.walkDecls((decl) => {
  30. let needsFix = false;
  31. const parsedValue = valueParser(getDeclarationValue(decl));
  32. parsedValue.walk((node) => {
  33. if (node.type !== 'function') return;
  34. if (!HUE_FUNCS.has(node.value.toLowerCase())) return;
  35. const hue = findHue(node);
  36. if (!hue) return;
  37. const { value } = hue;
  38. if (!isStandardSyntaxValue(value)) return;
  39. if (!isDegree(value) && !isNumber(value)) return;
  40. if (primary === 'angle' && isDegree(value)) return;
  41. if (primary === 'number' && isNumber(value)) return;
  42. const fixed = primary === 'angle' ? asDegree(value) : asNumber(value);
  43. const unfixed = value;
  44. if (context.fix) {
  45. hue.value = fixed;
  46. needsFix = true;
  47. return;
  48. }
  49. const valueIndex = declarationValueIndex(decl);
  50. report({
  51. message: messages.expected,
  52. messageArgs: [unfixed, fixed],
  53. node: decl,
  54. index: valueIndex + hue.sourceIndex,
  55. endIndex: valueIndex + hue.sourceEndIndex,
  56. result,
  57. ruleName,
  58. });
  59. });
  60. if (needsFix) {
  61. setDeclarationValue(decl, parsedValue.toString());
  62. }
  63. });
  64. };
  65. };
  66. /**
  67. * @param {string} value
  68. */
  69. function asDegree(value) {
  70. return `${value}deg`;
  71. }
  72. /**
  73. * @param {string} value
  74. */
  75. function asNumber(value) {
  76. const dimension = valueParser.unit(value);
  77. if (dimension) return dimension.number;
  78. throw new TypeError(`The "${value}" value must have a unit`);
  79. }
  80. /**
  81. * @param {import('postcss-value-parser').FunctionNode} node
  82. */
  83. function findHue(node) {
  84. const args = node.nodes.filter(({ type }) => type === 'word' || type === 'function');
  85. const value = node.value.toLowerCase();
  86. if (HUE_FIRST_ARG_FUNCS.includes(value)) {
  87. return args[0];
  88. }
  89. if (HUE_THIRD_ARG_FUNCS.includes(value)) {
  90. return args[2];
  91. }
  92. return undefined;
  93. }
  94. /**
  95. * @param {string} value
  96. */
  97. function isDegree(value) {
  98. const dimension = valueParser.unit(value);
  99. return dimension && dimension.unit.toLowerCase() === 'deg';
  100. }
  101. /**
  102. * @param {string} value
  103. */
  104. function isNumber(value) {
  105. const dimension = valueParser.unit(value);
  106. return dimension && dimension.unit === '';
  107. }
  108. rule.ruleName = ruleName;
  109. rule.messages = messages;
  110. rule.meta = meta;
  111. module.exports = rule;