index.cjs 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  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 parser = require('postcss-selector-parser');
  5. const typeGuards = require('../../utils/typeGuards.cjs');
  6. const validateTypes = require('../../utils/validateTypes.cjs');
  7. const hasBlock = require('../../utils/hasBlock.cjs');
  8. const isStandardSyntaxRule = require('../../utils/isStandardSyntaxRule.cjs');
  9. const optionsMatches = require('../../utils/optionsMatches.cjs');
  10. const report = require('../../utils/report.cjs');
  11. const ruleMessages = require('../../utils/ruleMessages.cjs');
  12. const validateOptions = require('../../utils/validateOptions.cjs');
  13. const ruleName = 'max-nesting-depth';
  14. const messages = ruleMessages(ruleName, {
  15. expected: (depth) => `Expected nesting depth to be no more than ${depth}`,
  16. });
  17. const meta = {
  18. url: 'https://stylelint.io/user-guide/rules/max-nesting-depth',
  19. };
  20. /** @type {import('stylelint').CoreRules[ruleName]} */
  21. const rule = (primary, secondaryOptions) => {
  22. /**
  23. * @param {import('postcss').Node} node
  24. */
  25. const isIgnoreAtRule = (node) =>
  26. typeGuards.isAtRule(node) && optionsMatches(secondaryOptions, 'ignoreAtRules', node.name);
  27. /**
  28. * @param {import('postcss').Node} node
  29. */
  30. const isIgnoreRule = (node) => {
  31. return typeGuards.isRule(node) && optionsMatches(secondaryOptions, 'ignoreRules', node.selector);
  32. };
  33. return (root, result) => {
  34. const validOptions = validateOptions(
  35. result,
  36. ruleName,
  37. {
  38. actual: primary,
  39. possible: [validateTypes.isNumber],
  40. },
  41. {
  42. optional: true,
  43. actual: secondaryOptions,
  44. possible: {
  45. ignore: ['blockless-at-rules', 'pseudo-classes'],
  46. ignoreAtRules: [validateTypes.isString, validateTypes.isRegExp],
  47. ignoreRules: [validateTypes.isString, validateTypes.isRegExp],
  48. ignorePseudoClasses: [validateTypes.isString, validateTypes.isRegExp],
  49. },
  50. },
  51. );
  52. if (!validOptions) return;
  53. root.walkRules(checkStatement);
  54. root.walkAtRules(checkStatement);
  55. /**
  56. * @param {import('postcss').Rule | import('postcss').AtRule} statement
  57. */
  58. function checkStatement(statement) {
  59. if (isIgnoreAtRule(statement)) {
  60. return;
  61. }
  62. if (isIgnoreRule(statement)) {
  63. return;
  64. }
  65. if (!hasBlock(statement)) {
  66. return;
  67. }
  68. if (typeGuards.isRule(statement) && !isStandardSyntaxRule(statement)) {
  69. return;
  70. }
  71. const depth = nestingDepth(statement, 0);
  72. if (depth > primary) {
  73. report({
  74. ruleName,
  75. result,
  76. node: statement,
  77. message: messages.expected,
  78. messageArgs: [primary],
  79. });
  80. }
  81. }
  82. };
  83. /**
  84. * @param {import('postcss').Node} node
  85. * @param {number} level
  86. * @returns {number}
  87. */
  88. function nestingDepth(node, level) {
  89. const parent = node.parent;
  90. if (!parent) {
  91. return 0;
  92. }
  93. if (isIgnoreAtRule(parent)) {
  94. return 0;
  95. }
  96. // The nesting depth level's computation has finished
  97. // when this function, recursively called, receives
  98. // a node that is not nested -- a direct child of the
  99. // root node
  100. if (typeGuards.isRoot(parent) || (typeGuards.isAtRule(parent) && parent.parent && typeGuards.isRoot(parent.parent))) {
  101. return level;
  102. }
  103. /**
  104. * @param {string} selector
  105. */
  106. function containsPseudoClassesOnly(selector) {
  107. const normalized = parser().processSync(selector, { lossless: false });
  108. const selectors = normalized.split(',');
  109. const filteredSelectors = selectors.filter(
  110. (sel) => !optionsMatches(secondaryOptions, 'ignoreRules', sel),
  111. );
  112. return filteredSelectors.every((sel) => extractPseudoRule(sel));
  113. }
  114. /**
  115. * @param {string[]} selectors
  116. * @returns {boolean}
  117. */
  118. function containsIgnoredPseudoClassesOrRulesOnly(selectors) {
  119. if (
  120. !(
  121. secondaryOptions &&
  122. (secondaryOptions.ignorePseudoClasses || secondaryOptions.ignoreRules)
  123. )
  124. )
  125. return false;
  126. return selectors.every((selector) => {
  127. if (
  128. secondaryOptions.ignoreRules &&
  129. optionsMatches(secondaryOptions, 'ignoreRules', selector)
  130. )
  131. return true;
  132. if (!secondaryOptions.ignorePseudoClasses) return false;
  133. const pseudoRule = extractPseudoRule(selector);
  134. if (!pseudoRule) return false;
  135. return optionsMatches(secondaryOptions, 'ignorePseudoClasses', pseudoRule);
  136. });
  137. }
  138. if (
  139. (optionsMatches(secondaryOptions, 'ignore', 'blockless-at-rules') &&
  140. typeGuards.isAtRule(node) &&
  141. node.every((child) => !typeGuards.isDeclaration(child))) ||
  142. (optionsMatches(secondaryOptions, 'ignore', 'pseudo-classes') &&
  143. typeGuards.isRule(node) &&
  144. containsPseudoClassesOnly(node.selector)) ||
  145. (typeGuards.isRule(node) && containsIgnoredPseudoClassesOrRulesOnly(node.selectors))
  146. ) {
  147. return nestingDepth(parent, level);
  148. }
  149. // Unless any of the conditions above apply, we want to
  150. // add 1 to the nesting depth level and then check the parent,
  151. // continuing to add and move up the hierarchy
  152. // until we hit the root node
  153. return nestingDepth(parent, level + 1);
  154. }
  155. };
  156. /**
  157. * @param {string} selector
  158. * @returns {string | undefined}
  159. */
  160. function extractPseudoRule(selector) {
  161. return selector.startsWith('&:') && selector[2] !== ':' ? selector.slice(2) : undefined;
  162. }
  163. rule.ruleName = ruleName;
  164. rule.messages = messages;
  165. rule.meta = meta;
  166. module.exports = rule;