index.js 3.3 KB

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