index.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. 'use strict';
  2. const atRuleParamIndex = require('../../utils/atRuleParamIndex');
  3. const declarationValueIndex = require('../../utils/declarationValueIndex');
  4. const getDimension = require('../../utils/getDimension');
  5. const report = require('../../utils/report');
  6. const ruleMessages = require('../../utils/ruleMessages');
  7. const validateOptions = require('../../utils/validateOptions');
  8. const valueParser = require('postcss-value-parser');
  9. const ruleName = 'unit-case';
  10. const messages = ruleMessages(ruleName, {
  11. expected: (actual, expected) => `Expected "${actual}" to be "${expected}"`,
  12. });
  13. const meta = {
  14. url: 'https://stylelint.io/user-guide/rules/unit-case',
  15. fixable: true,
  16. };
  17. /** @type {import('stylelint').Rule} */
  18. const rule = (primary, _secondaryOptions, context) => {
  19. return (root, result) => {
  20. const validOptions = validateOptions(result, ruleName, {
  21. actual: primary,
  22. possible: ['lower', 'upper'],
  23. });
  24. if (!validOptions) {
  25. return;
  26. }
  27. /**
  28. * @template {import('postcss').AtRule | import('postcss').Declaration} T
  29. * @param {T} node
  30. * @param {string} checkedValue
  31. * @param {(node: T) => number} getIndex
  32. * @returns {void}
  33. */
  34. function check(node, checkedValue, getIndex) {
  35. /** @type {Array<{ index: number, endIndex: number, message: string }>} */
  36. const problems = [];
  37. /**
  38. * @param {import('postcss-value-parser').Node} valueNode
  39. * @returns {boolean}
  40. */
  41. function processValue(valueNode) {
  42. const { number, unit } = getDimension(valueNode);
  43. if (!number || !unit) return false;
  44. const expectedUnit = primary === 'lower' ? unit.toLowerCase() : unit.toUpperCase();
  45. if (unit === expectedUnit) {
  46. return false;
  47. }
  48. const index = getIndex(node);
  49. problems.push({
  50. index: index + valueNode.sourceIndex + number.length,
  51. endIndex: index + valueNode.sourceEndIndex,
  52. message: messages.expected(unit, expectedUnit),
  53. });
  54. return true;
  55. }
  56. const parsedValue = valueParser(checkedValue).walk((valueNode) => {
  57. // Ignore wrong units within `url` function
  58. let needFix = false;
  59. const value = valueNode.value;
  60. if (valueNode.type === 'function' && value.toLowerCase() === 'url') {
  61. return false;
  62. }
  63. if (value.includes('*')) {
  64. value.split('*').some((val) => {
  65. return processValue({
  66. ...valueNode,
  67. sourceIndex: value.indexOf(val) + val.length + 1,
  68. value: val,
  69. });
  70. });
  71. }
  72. needFix = processValue(valueNode);
  73. if (needFix && context.fix) {
  74. valueNode.value = primary === 'lower' ? value.toLowerCase() : value.toUpperCase();
  75. }
  76. });
  77. if (problems.length) {
  78. if (context.fix) {
  79. if ('name' in node && node.name === 'media') {
  80. node.params = parsedValue.toString();
  81. } else if ('value' in node) {
  82. node.value = parsedValue.toString();
  83. }
  84. } else {
  85. for (const err of problems) {
  86. report({
  87. index: err.index,
  88. endIndex: err.endIndex,
  89. message: err.message,
  90. node,
  91. result,
  92. ruleName,
  93. });
  94. }
  95. }
  96. }
  97. }
  98. root.walkAtRules((atRule) => {
  99. if (!/^media$/i.test(atRule.name) && !('variable' in atRule)) {
  100. return;
  101. }
  102. check(atRule, atRule.params, atRuleParamIndex);
  103. });
  104. root.walkDecls((decl) => check(decl, decl.value, declarationValueIndex));
  105. };
  106. };
  107. rule.ruleName = ruleName;
  108. rule.messages = messages;
  109. rule.meta = meta;
  110. module.exports = rule;