index.cjs 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  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 selectorParser = require('postcss-selector-parser');
  5. const validateTypes = require('../../utils/validateTypes.cjs');
  6. const getRuleSelector = require('../../utils/getRuleSelector.cjs');
  7. const isStandardSyntaxRule = require('../../utils/isStandardSyntaxRule.cjs');
  8. const parseSelector = require('../../utils/parseSelector.cjs');
  9. const report = require('../../utils/report.cjs');
  10. const ruleMessages = require('../../utils/ruleMessages.cjs');
  11. const validateOptions = require('../../utils/validateOptions.cjs');
  12. const { isAttribute, isClassName, isIdentifier, isPseudoClass, isTag, isUniversal } =
  13. selectorParser;
  14. const ruleName = 'selector-not-notation';
  15. const messages = ruleMessages(ruleName, {
  16. expected: (type) => `Expected ${type} :not() pseudo-class notation`,
  17. });
  18. const meta = {
  19. url: 'https://stylelint.io/user-guide/rules/selector-not-notation',
  20. fixable: true,
  21. };
  22. /** @typedef {import('postcss-selector-parser').Node} Node */
  23. /** @typedef {import('postcss-selector-parser').Selector} Selector */
  24. /** @typedef {import('postcss-selector-parser').Pseudo} Pseudo */
  25. /** @typedef {import('postcss-selector-parser').Root} Root */
  26. /** @typedef {import('postcss').Rule} Rule */
  27. /** @typedef {import('stylelint').Range} Range */
  28. /**
  29. * @param {Node} node
  30. * @returns {boolean}
  31. */
  32. const isSimpleSelector = (node) =>
  33. isPseudoClass(node) ||
  34. isAttribute(node) ||
  35. isClassName(node) ||
  36. isUniversal(node) ||
  37. isIdentifier(node) ||
  38. isTag(node);
  39. /**
  40. * @param {Node | undefined} node
  41. * @returns {node is Pseudo}
  42. */
  43. const isNot = (node) =>
  44. isPseudoClass(node) && node.value !== undefined && node.value.toLowerCase() === ':not';
  45. /**
  46. * @param {Selector[]} list
  47. * @returns {boolean}
  48. */
  49. const isSimple = (list) => {
  50. if (list.length > 1) return false;
  51. validateTypes.assert(list[0], 'list is never empty');
  52. const [first, second] = list[0].nodes;
  53. if (!first) return true;
  54. if (second) return false;
  55. return isSimpleSelector(first) && !isNot(first);
  56. };
  57. /** @type {import('stylelint').CoreRules[ruleName]} */
  58. const rule = (primary) => {
  59. return (root, result) => {
  60. const validOptions = validateOptions(result, ruleName, {
  61. actual: primary,
  62. possible: ['simple', 'complex'],
  63. });
  64. if (!validOptions) return;
  65. root.walkRules((ruleNode) => {
  66. if (!isStandardSyntaxRule(ruleNode)) return;
  67. const selector = ruleNode.selector;
  68. if (!selector.includes(':not(')) return;
  69. const selectorRoot = parseSelector(getRuleSelector(ruleNode), result, ruleNode);
  70. if (!selectorRoot) return;
  71. selectorRoot.walkPseudos((pseudo) => {
  72. if (!isNot(pseudo)) return;
  73. const index = pseudo.sourceIndex;
  74. const endIndex = index + pseudo.toString().trim().length;
  75. let fix;
  76. if (primary === 'complex') {
  77. const prev = pseudo.prev();
  78. const hasConsecutiveNot = prev && isNot(prev);
  79. if (!hasConsecutiveNot) return;
  80. fix = () => fixComplex(prev, ruleNode, selectorRoot);
  81. } else {
  82. const selectors = pseudo.nodes;
  83. if (isSimple(selectors)) return;
  84. const second = selectors.length > 1 && selectors[1];
  85. const isFixable =
  86. second && (!second.nodes.length || selectors.every(({ nodes }) => nodes.length === 1));
  87. if (isFixable) fix = () => fixSimple(pseudo, ruleNode, selectorRoot);
  88. }
  89. report({
  90. message: messages.expected,
  91. messageArgs: [primary],
  92. node: ruleNode,
  93. result,
  94. ruleName,
  95. index,
  96. endIndex,
  97. fix: {
  98. apply: fix,
  99. node: ruleNode,
  100. },
  101. });
  102. });
  103. });
  104. };
  105. };
  106. /**
  107. * @param {Pseudo} not
  108. * @returns {Node}
  109. */
  110. const getLastConsecutiveNot = ({ parent, sourceIndex }) => {
  111. validateTypes.assert(parent);
  112. const nodes = parent.nodes;
  113. const index = nodes.findIndex((node) => node.sourceIndex >= sourceIndex && !isNot(node));
  114. const node = index === -1 ? nodes[nodes.length - 1] : nodes[index - 1];
  115. validateTypes.assert(node);
  116. return node;
  117. };
  118. /**
  119. * @param {Pseudo} not
  120. * @param {Rule} ruleNode
  121. * @param {Root} selectorRoot
  122. */
  123. function fixSimple(not, ruleNode, selectorRoot) {
  124. const simpleSelectors = not.nodes
  125. .filter(({ nodes }) => nodes[0] && isSimpleSelector(nodes[0]))
  126. .map((s) => {
  127. validateTypes.assert(s.nodes[0]);
  128. s.nodes[0].rawSpaceBefore = '';
  129. s.nodes[0].rawSpaceAfter = '';
  130. return s;
  131. });
  132. const firstSelector = simpleSelectors.shift();
  133. validateTypes.assert(firstSelector);
  134. validateTypes.assert(not.parent);
  135. not.empty();
  136. not.nodes.push(firstSelector);
  137. for (const s of simpleSelectors) {
  138. const last = getLastConsecutiveNot(not);
  139. const clone = last.clone({ nodes: [s] });
  140. clone.rawSpaceBefore = '';
  141. not.parent.insertAfter(last, clone);
  142. }
  143. ruleNode.selector = selectorRoot.toString();
  144. }
  145. /**
  146. * @param {Pseudo} previousNot
  147. * @param {Rule} ruleNode
  148. * @param {Root} selectorRoot
  149. */
  150. function fixComplex(previousNot, ruleNode, selectorRoot) {
  151. const indentAndTrimRight = (/** @type {Selector[]} */ selectors) => {
  152. for (const s of selectors) {
  153. validateTypes.assert(s.nodes[0]);
  154. s.nodes[0].rawSpaceBefore = ' ';
  155. s.nodes[0].rawSpaceAfter = '';
  156. }
  157. };
  158. const [head, ...tail] = previousNot.nodes;
  159. let node = previousNot.next();
  160. if (head == null || head.nodes.length === 0) return;
  161. validateTypes.assert(head.nodes[0]);
  162. head.nodes[0].rawSpaceBefore = '';
  163. head.nodes[0].rawSpaceAfter = '';
  164. indentAndTrimRight(tail);
  165. while (isNot(node)) {
  166. const selectors = node.nodes;
  167. const prev = node;
  168. indentAndTrimRight(selectors);
  169. previousNot.nodes = previousNot.nodes.concat(selectors);
  170. node = node.next();
  171. prev.remove();
  172. }
  173. ruleNode.selector = selectorRoot.toString();
  174. }
  175. rule.ruleName = ruleName;
  176. rule.messages = messages;
  177. rule.meta = meta;
  178. module.exports = rule;