index.cjs 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  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 cssTree = require('css-tree');
  5. const validateTypes = require('../../utils/validateTypes.cjs');
  6. const nodeFieldIndices = require('../../utils/nodeFieldIndices.cjs');
  7. const regexes = require('../../utils/regexes.cjs');
  8. const getAtRuleParams = require('../../utils/getAtRuleParams.cjs');
  9. const getRuleSelector = require('../../utils/getRuleSelector.cjs');
  10. const isStandardSyntaxAtRule = require('../../utils/isStandardSyntaxAtRule.cjs');
  11. const isStandardSyntaxRule = require('../../utils/isStandardSyntaxRule.cjs');
  12. const optionsMatches = require('../../utils/optionsMatches.cjs');
  13. const report = require('../../utils/report.cjs');
  14. const ruleMessages = require('../../utils/ruleMessages.cjs');
  15. const validateOptions = require('../../utils/validateOptions.cjs');
  16. const ruleName = 'nesting-selector-no-missing-scoping-root';
  17. const messages = ruleMessages(ruleName, {
  18. rejected: 'Unexpected missing scoping root',
  19. });
  20. const meta = {
  21. url: 'https://stylelint.io/user-guide/rules/nesting-selector-no-missing-scoping-root',
  22. };
  23. /** @type {import('stylelint').CoreRules[ruleName]} */
  24. const rule = (primary, secondaryOptions) => {
  25. return (root, result) => {
  26. const validOptions = validateOptions(
  27. result,
  28. ruleName,
  29. { actual: primary, possible: [true] },
  30. {
  31. actual: secondaryOptions,
  32. possible: {
  33. ignoreAtRules: [validateTypes.isString, validateTypes.isRegExp],
  34. },
  35. optional: true,
  36. },
  37. );
  38. if (!validOptions) return;
  39. root.walkRules(/&/, (ruleNode) => {
  40. if (!isStandardSyntaxRule(ruleNode)) return;
  41. // Check if the rule is nested within a scoping root
  42. if (hasValidScopingRoot(ruleNode, secondaryOptions)) return;
  43. let ast;
  44. try {
  45. ast = cssTree.parse(getRuleSelector(ruleNode), { context: 'selectorList', positions: true });
  46. } catch {
  47. // Cannot parse selector list, skip checking
  48. return;
  49. }
  50. check(ruleNode, ast);
  51. });
  52. // Check @scope at-rules for nesting selectors in parameters
  53. root.walkAtRules(regexes.atRuleRegexes.scopeName, (atRule) => {
  54. if (!isStandardSyntaxAtRule(atRule)) return;
  55. // Cheap check for nesting selector
  56. if (!atRule.params.includes('&')) return;
  57. // Only check @scope at-rules that don't have a parent scoping context
  58. if (hasValidScopingRoot(atRule, secondaryOptions)) return;
  59. let ast;
  60. try {
  61. ast = cssTree.parse(getAtRuleParams(atRule), {
  62. atrule: 'scope',
  63. context: 'atrulePrelude',
  64. positions: true,
  65. });
  66. } catch {
  67. // Cannot parse @scope at-rule, skip checking
  68. return;
  69. }
  70. check(atRule, ast, nodeFieldIndices.atRuleParamIndex(atRule));
  71. });
  72. /**
  73. * @param {import('postcss').Rule | import('postcss').AtRule} node
  74. * @param {import('css-tree').CssNode} ast
  75. * @param {number} [offset=0]
  76. */
  77. function check(node, ast, offset = 0) {
  78. cssTree.walk(ast, {
  79. /**
  80. * @param {import('css-tree').CssNode} cssNode
  81. */
  82. enter(cssNode) {
  83. if (cssNode.type !== 'NestingSelector') return;
  84. if (!cssNode.loc) return;
  85. const index = offset + cssNode.loc.start.offset;
  86. const endIndex = index + 1;
  87. report({
  88. message: messages.rejected,
  89. node,
  90. result,
  91. ruleName,
  92. index,
  93. endIndex,
  94. });
  95. },
  96. });
  97. }
  98. };
  99. };
  100. /**
  101. * Check if a node has a valid scoping root
  102. * @param {import('postcss').Rule | import('postcss').AtRule} node
  103. * @param {object} secondaryOptions
  104. * @returns {boolean}
  105. */
  106. function hasValidScopingRoot(node, secondaryOptions) {
  107. let current = node.parent;
  108. while (current) {
  109. // If we find a rule in the parent chain, it provides a scoping root
  110. if (current.type === 'rule') {
  111. return true;
  112. }
  113. // If we find an @scope at-rule, it provides a scoping root
  114. if (current.type === 'atrule' && current.name === 'scope') {
  115. return true;
  116. }
  117. // If we find an ignored at-rule, it provides a scoping root
  118. if (
  119. current.type === 'atrule' &&
  120. optionsMatches(secondaryOptions, 'ignoreAtRules', current.name)
  121. ) {
  122. return true;
  123. }
  124. // If we reach the root without finding a scoping root
  125. if (current.type === 'root') {
  126. return false;
  127. }
  128. current = current.parent;
  129. }
  130. return false;
  131. }
  132. rule.ruleName = ruleName;
  133. rule.messages = messages;
  134. rule.meta = meta;
  135. module.exports = rule;