index.cjs 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. // NOTICE: This file is generated by Rollup. To modify it,
  2. // please instead edit the ESM counterpart and rebuild with Rollup (npm run build).
  3. 'use strict';
  4. const valueParser = require('postcss-value-parser');
  5. const nodeFieldIndices = require('../../utils/nodeFieldIndices.cjs');
  6. const getDeclarationValue = require('../../utils/getDeclarationValue.cjs');
  7. const isStandardSyntaxValue = require('../../utils/isStandardSyntaxValue.cjs');
  8. const report = require('../../utils/report.cjs');
  9. const ruleMessages = require('../../utils/ruleMessages.cjs');
  10. const setDeclarationValue = require('../../utils/setDeclarationValue.cjs');
  11. const validateOptions = require('../../utils/validateOptions.cjs');
  12. const ruleName = 'lightness-notation';
  13. const messages = ruleMessages(ruleName, {
  14. expected: (unfixed, fixed) => `Expected "${unfixed}" to be "${fixed}"`,
  15. });
  16. const meta = {
  17. url: 'https://stylelint.io/user-guide/rules/lightness-notation',
  18. fixable: true,
  19. };
  20. const LIGHTNESS_ZERO_TO_ONE_RANGE_FUNCS = new Set(['oklch', 'oklab']);
  21. const LIGHTNESS_ZERO_TO_HUNDRED_RANGE_FUNCS = new Set(['lch', 'lab']);
  22. const LIGHTNESS_FUNCS = new Set([
  23. ...LIGHTNESS_ZERO_TO_ONE_RANGE_FUNCS,
  24. ...LIGHTNESS_ZERO_TO_HUNDRED_RANGE_FUNCS,
  25. ]);
  26. const HAS_LIGHTNESS_COLOR_FUNC = new RegExp(`\\b(?:${[...LIGHTNESS_FUNCS].join('|')})\\(`, 'i');
  27. /** @type {import('stylelint').CoreRules[ruleName]} */
  28. const rule = (primary) => {
  29. return (root, result) => {
  30. const validOptions = validateOptions(result, ruleName, {
  31. actual: primary,
  32. possible: ['percentage', 'number'],
  33. });
  34. if (!validOptions) return;
  35. root.walkDecls((decl) => {
  36. if (!HAS_LIGHTNESS_COLOR_FUNC.test(decl.value)) return;
  37. const parsedValue = valueParser(getDeclarationValue(decl));
  38. parsedValue.walk((node) => {
  39. if (node.type !== 'function') return;
  40. const functionName = node.value.toLowerCase();
  41. if (!LIGHTNESS_FUNCS.has(functionName)) return;
  42. const lightness = findLightness(node);
  43. if (!lightness) return;
  44. const { value: unfixedValue } = lightness;
  45. if (!isStandardSyntaxValue(unfixedValue)) return;
  46. const dimension = valueParser.unit(unfixedValue);
  47. if (!dimension) return;
  48. const isPercentage = dimension.unit === `%`;
  49. const isNumber = dimension.unit === '';
  50. if (!isPercentage && !isNumber) return;
  51. if (primary === 'percentage' && isPercentage) return;
  52. if (primary === 'number' && isNumber) return;
  53. const fixedValue =
  54. primary === 'percentage'
  55. ? asPercentage(unfixedValue, functionName)
  56. : asNumber(unfixedValue, functionName);
  57. const valueIndex = nodeFieldIndices.declarationValueIndex(decl);
  58. const fix = () => {
  59. lightness.value = fixedValue;
  60. setDeclarationValue(decl, parsedValue.toString());
  61. };
  62. report({
  63. message: messages.expected,
  64. messageArgs: [unfixedValue, fixedValue],
  65. node: decl,
  66. index: valueIndex + lightness.sourceIndex,
  67. endIndex: valueIndex + lightness.sourceEndIndex,
  68. result,
  69. ruleName,
  70. fix: {
  71. apply: fix,
  72. node: decl,
  73. },
  74. });
  75. });
  76. });
  77. };
  78. };
  79. /**
  80. * @param {string} value
  81. * @param {import('postcss-value-parser').FunctionNode['value']} func
  82. */
  83. function asPercentage(value, func) {
  84. let num = Number.parseFloat(value);
  85. if (LIGHTNESS_ZERO_TO_HUNDRED_RANGE_FUNCS.has(func)) {
  86. return `${num}%`;
  87. }
  88. if (LIGHTNESS_ZERO_TO_ONE_RANGE_FUNCS.has(func)) {
  89. num *= 100;
  90. }
  91. if (Number.isInteger(num)) {
  92. return `${num}%`;
  93. }
  94. return `${roundToNumberOfDigits(num, value)}%`;
  95. }
  96. /**
  97. * @param {string} value
  98. * @param {import('postcss-value-parser').FunctionNode['value']} func
  99. */
  100. function asNumber(value, func) {
  101. let num = Number.parseFloat(value);
  102. if (LIGHTNESS_ZERO_TO_ONE_RANGE_FUNCS.has(func)) {
  103. num /= 100;
  104. return `${roundToNumberOfDigits(num, value)}`;
  105. }
  106. return `${num}`;
  107. }
  108. /**
  109. * @param {number} num
  110. * @param {string} value
  111. * @returns {string}
  112. */
  113. function roundToNumberOfDigits(num, value) {
  114. if (num === 0) return '0';
  115. const precision = value.replaceAll('%', '').replaceAll('.', '').length;
  116. if (precision === 0) return `${num}`;
  117. return num.toPrecision(precision).replace(/0+$/, ''); // trim trailing zeros
  118. }
  119. /**
  120. * @param {import('postcss-value-parser').FunctionNode} node
  121. */
  122. function findLightness({ nodes }) {
  123. return nodes.find(({ type }) => type === 'word' || type === 'function');
  124. }
  125. rule.ruleName = ruleName;
  126. rule.messages = messages;
  127. rule.meta = meta;
  128. module.exports = rule;