index.js 3.4 KB

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