You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

180 lines
3.6 KiB

3 months ago
  1. 'use strict';
  2. const valueParser = require('postcss-value-parser');
  3. const mappings = require('./lib/map');
  4. /**
  5. * @param {unknown} item
  6. * @param {number} index
  7. * @return {boolean}
  8. */
  9. function evenValues(item, index) {
  10. return index % 2 === 0;
  11. }
  12. const repeatKeywords = new Set(mappings.values());
  13. /**
  14. * @param {valueParser.Node} node
  15. * @return {boolean}
  16. */
  17. function isCommaNode(node) {
  18. return node.type === 'div' && node.value === ',';
  19. }
  20. const variableFunctions = new Set(['var', 'env', 'constant']);
  21. /**
  22. * @param {valueParser.Node} node
  23. * @return {boolean}
  24. */
  25. function isVariableFunctionNode(node) {
  26. if (node.type !== 'function') {
  27. return false;
  28. }
  29. return variableFunctions.has(node.value.toLowerCase());
  30. }
  31. /**
  32. * @param {string} value
  33. * @return {string}
  34. */
  35. function transform(value) {
  36. const parsed = valueParser(value);
  37. if (parsed.nodes.length === 1) {
  38. return value;
  39. }
  40. /** @type {{start: number?, end: number?}[]} */
  41. const ranges = [];
  42. let rangeIndex = 0;
  43. let shouldContinue = true;
  44. parsed.nodes.forEach((node, index) => {
  45. // After comma (`,`) follows next background
  46. if (isCommaNode(node)) {
  47. rangeIndex += 1;
  48. shouldContinue = true;
  49. return;
  50. }
  51. if (!shouldContinue) {
  52. return;
  53. }
  54. // After separator (`/`) follows `background-size` values
  55. // Avoid them
  56. if (node.type === 'div' && node.value === '/') {
  57. shouldContinue = false;
  58. return;
  59. }
  60. if (!ranges[rangeIndex]) {
  61. ranges[rangeIndex] = {
  62. start: null,
  63. end: null,
  64. };
  65. }
  66. // Do not try to be processed `var and `env` function inside background
  67. if (isVariableFunctionNode(node)) {
  68. shouldContinue = false;
  69. ranges[rangeIndex].start = null;
  70. ranges[rangeIndex].end = null;
  71. return;
  72. }
  73. const isRepeatKeyword =
  74. node.type === 'word' && repeatKeywords.has(node.value.toLowerCase());
  75. if (ranges[rangeIndex].start === null && isRepeatKeyword) {
  76. ranges[rangeIndex].start = index;
  77. ranges[rangeIndex].end = index;
  78. return;
  79. }
  80. if (ranges[rangeIndex].start !== null) {
  81. if (node.type === 'space') {
  82. return;
  83. } else if (isRepeatKeyword) {
  84. ranges[rangeIndex].end = index;
  85. return;
  86. }
  87. return;
  88. }
  89. });
  90. ranges.forEach((range) => {
  91. if (range.start === null) {
  92. return;
  93. }
  94. const nodes = parsed.nodes.slice(
  95. range.start,
  96. /** @type {number} */ (range.end) + 1
  97. );
  98. if (nodes.length !== 3) {
  99. return;
  100. }
  101. const key = nodes
  102. .filter(evenValues)
  103. .map((n) => n.value.toLowerCase())
  104. .toString();
  105. const match = mappings.get(key);
  106. if (match) {
  107. nodes[0].value = match;
  108. nodes[1].value = nodes[2].value = '';
  109. }
  110. });
  111. return parsed.toString();
  112. }
  113. /**
  114. * @type {import('postcss').PluginCreator<void>}
  115. * @return {import('postcss').Plugin}
  116. */
  117. function pluginCreator() {
  118. return {
  119. postcssPlugin: 'postcss-normalize-repeat-style',
  120. prepare() {
  121. const cache = new Map();
  122. return {
  123. OnceExit(css) {
  124. css.walkDecls(
  125. /^(background(-repeat)?|(-\w+-)?mask-repeat)$/i,
  126. (decl) => {
  127. const value = decl.value;
  128. if (!value) {
  129. return;
  130. }
  131. if (cache.has(value)) {
  132. decl.value = cache.get(value);
  133. return;
  134. }
  135. const result = transform(value);
  136. decl.value = result;
  137. cache.set(value, result);
  138. }
  139. );
  140. },
  141. };
  142. },
  143. };
  144. }
  145. pluginCreator.postcss = true;
  146. module.exports = pluginCreator;