index.cjs 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  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 validateTypes = require('../../utils/validateTypes.cjs');
  5. const keywords = require('../../reference/keywords.cjs');
  6. const regexes = require('../../utils/regexes.cjs');
  7. const getRuleSelector = require('../../utils/getRuleSelector.cjs');
  8. const parseSelector = require('../../utils/parseSelector.cjs');
  9. const parser = require('postcss-selector-parser');
  10. const report = require('../../utils/report.cjs');
  11. const ruleMessages = require('../../utils/ruleMessages.cjs');
  12. const validateOptions = require('../../utils/validateOptions.cjs');
  13. const ruleName = 'keyframe-selector-notation';
  14. const messages = ruleMessages(ruleName, {
  15. expected: (selector, fixedSelector) => `Expected "${selector}" to be "${fixedSelector}"`,
  16. });
  17. const meta = {
  18. url: 'https://stylelint.io/user-guide/rules/keyframe-selector-notation',
  19. fixable: true,
  20. };
  21. const PERCENTAGE_SELECTORS = new Set(['0%', '100%']);
  22. const PERCENTAGE_TO_KEYWORD = new Map([
  23. ['0%', 'from'],
  24. ['100%', 'to'],
  25. ]);
  26. const KEYWORD_TO_PERCENTAGE = new Map([
  27. ['from', '0%'],
  28. ['to', '100%'],
  29. ]);
  30. /** @type {import('stylelint').CoreRules[ruleName]} */
  31. const rule = (primary) => {
  32. return (root, result) => {
  33. const validOptions = validateOptions(result, ruleName, {
  34. actual: primary,
  35. possible: ['keyword', 'percentage', 'percentage-unless-within-keyword-only-block'],
  36. });
  37. if (!validOptions) return;
  38. /**
  39. * @typedef {{
  40. * expFunc: (selector: string, selectorsInBlock: string[]) => boolean,
  41. * fixFunc: (selector: string) => string,
  42. * }} OptionFuncs
  43. *
  44. * @type {Record<typeof primary, OptionFuncs>}
  45. */
  46. const optionFuncs = {
  47. keyword: {
  48. expFunc: (selector) => keywords.keyframeSelectorKeywords.has(selector),
  49. fixFunc: (selector) => getFromMap(PERCENTAGE_TO_KEYWORD, selector),
  50. },
  51. percentage: {
  52. expFunc: (selector) => PERCENTAGE_SELECTORS.has(selector),
  53. fixFunc: (selector) => getFromMap(KEYWORD_TO_PERCENTAGE, selector),
  54. },
  55. 'percentage-unless-within-keyword-only-block': {
  56. expFunc: (selector, selectorsInBlock) => {
  57. if (selectorsInBlock.every((s) => keywords.keyframeSelectorKeywords.has(s))) return true;
  58. return PERCENTAGE_SELECTORS.has(selector);
  59. },
  60. fixFunc: (selector) => getFromMap(KEYWORD_TO_PERCENTAGE, selector),
  61. },
  62. };
  63. const { expFunc, fixFunc } = optionFuncs[primary];
  64. root.walkAtRules(regexes.atRuleRegexes.keyframesName, (atRuleKeyframes) => {
  65. const selectorsInBlock =
  66. primary === 'percentage-unless-within-keyword-only-block'
  67. ? getSelectorsInBlock(atRuleKeyframes)
  68. : [];
  69. atRuleKeyframes.walkRules((keyframeRule) => {
  70. const selectors = parseSelector(getRuleSelector(keyframeRule), result, keyframeRule);
  71. if (!selectors) return;
  72. selectors.each((selector) => {
  73. const parsed = parseKeyframeSelector(selector);
  74. if (!parsed) return;
  75. const [keyframeSelector, normalizedSelector] = parsed;
  76. if (expFunc(normalizedSelector, selectorsInBlock)) return;
  77. const fixedSelector = fixFunc(normalizedSelector);
  78. const fix = () => {
  79. keyframeSelector.value = fixedSelector;
  80. keyframeRule.selector = selectors.toString();
  81. };
  82. report({
  83. message: messages.expected,
  84. messageArgs: [keyframeSelector.value, fixedSelector],
  85. node: keyframeRule,
  86. result,
  87. ruleName,
  88. index: keyframeSelector.sourceIndex,
  89. endIndex: keyframeSelector.sourceIndex + normalizedSelector.length,
  90. fix: {
  91. apply: fix,
  92. node: keyframeRule,
  93. },
  94. });
  95. });
  96. });
  97. });
  98. };
  99. };
  100. /** @import { Selector, Tag } from 'postcss-selector-parser' */
  101. /**
  102. * Full syntax is:
  103. * `<keyframe-selector> = from | to | <percentage [0,100]> | <timeline-range-name> <percentage>`
  104. *
  105. * Only parses `from | to | <percentage [0,100]>` and returns `undefined` in any other case.
  106. *
  107. * @param {Selector} selector
  108. * @returns {[Tag,string]|undefined}
  109. */
  110. function parseKeyframeSelector(selector) {
  111. const relevantNodes = selector.nodes.filter((node) => {
  112. if (parser.isComment(node)) return false;
  113. if (parser.isCombinator(node) && node.value.trim() === '') return false;
  114. return true;
  115. });
  116. if (relevantNodes.length !== 1) return;
  117. if (!relevantNodes.every(parser.isTag)) return;
  118. const keyframeSelector = relevantNodes[0];
  119. if (!keyframeSelector) return;
  120. const normalizedSelector = keyframeSelector.value.toLowerCase();
  121. if (
  122. !keywords.keyframeSelectorKeywords.has(normalizedSelector) &&
  123. !PERCENTAGE_SELECTORS.has(normalizedSelector)
  124. ) {
  125. return;
  126. }
  127. return [keyframeSelector, normalizedSelector];
  128. }
  129. /**
  130. * @param {Map<string, string>} map
  131. * @param {string} key
  132. * @returns {string}
  133. */
  134. function getFromMap(map, key) {
  135. const value = map.get(key);
  136. validateTypes.assertString(value);
  137. return value;
  138. }
  139. /**
  140. * @param {import('postcss').AtRule} atRule
  141. * @returns {string[]}
  142. */
  143. function getSelectorsInBlock(atRule) {
  144. /** @type {string[]} */
  145. const selectors = [];
  146. atRule.walkRules((r) => {
  147. selectors.push(...r.selectors.map((selector) => selector.toLowerCase()));
  148. });
  149. return selectors;
  150. }
  151. rule.ruleName = ruleName;
  152. rule.messages = messages;
  153. rule.meta = meta;
  154. module.exports = rule;