index.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  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(unfixed, fixed),
  52. node: decl,
  53. index: valueIndex + hue.sourceIndex,
  54. endIndex: valueIndex + hue.sourceEndIndex,
  55. result,
  56. ruleName,
  57. });
  58. });
  59. if (needsFix) {
  60. setDeclarationValue(decl, parsedValue.toString());
  61. }
  62. });
  63. };
  64. };
  65. /**
  66. * @param {string} value
  67. */
  68. function asDegree(value) {
  69. return `${value}deg`;
  70. }
  71. /**
  72. * @param {string} value
  73. */
  74. function asNumber(value) {
  75. const dimension = valueParser.unit(value);
  76. if (dimension) return dimension.number;
  77. throw new TypeError(`The "${value}" value must have a unit`);
  78. }
  79. /**
  80. * @param {import('postcss-value-parser').FunctionNode} node
  81. */
  82. function findHue(node) {
  83. const args = node.nodes.filter(({ type }) => type === 'word' || type === 'function');
  84. const value = node.value.toLowerCase();
  85. if (HUE_FIRST_ARG_FUNCS.includes(value)) {
  86. return args[0];
  87. }
  88. if (HUE_THIRD_ARG_FUNCS.includes(value)) {
  89. return args[2];
  90. }
  91. return undefined;
  92. }
  93. /**
  94. * @param {string} value
  95. */
  96. function isDegree(value) {
  97. const dimension = valueParser.unit(value);
  98. return dimension && dimension.unit.toLowerCase() === 'deg';
  99. }
  100. /**
  101. * @param {string} value
  102. */
  103. function isNumber(value) {
  104. const dimension = valueParser.unit(value);
  105. return dimension && dimension.unit === '';
  106. }
  107. rule.ruleName = ruleName;
  108. rule.messages = messages;
  109. rule.meta = meta;
  110. module.exports = rule;