| // NOTICE: This file is generated by Rollup. To modify it, |
| // please instead edit the ESM counterpart and rebuild with Rollup (npm run build). |
| 'use strict'; |
| |
| const cssTree = require('css-tree'); |
| const validateTypes = require('../../utils/validateTypes.cjs'); |
| const nodeFieldIndices = require('../../utils/nodeFieldIndices.cjs'); |
| const regexes = require('../../utils/regexes.cjs'); |
| const getAtRuleParams = require('../../utils/getAtRuleParams.cjs'); |
| const getRuleSelector = require('../../utils/getRuleSelector.cjs'); |
| const isStandardSyntaxAtRule = require('../../utils/isStandardSyntaxAtRule.cjs'); |
| const isStandardSyntaxRule = require('../../utils/isStandardSyntaxRule.cjs'); |
| const optionsMatches = require('../../utils/optionsMatches.cjs'); |
| const report = require('../../utils/report.cjs'); |
| const ruleMessages = require('../../utils/ruleMessages.cjs'); |
| const validateOptions = require('../../utils/validateOptions.cjs'); |
| |
| const ruleName = 'nesting-selector-no-missing-scoping-root'; |
| |
| const messages = ruleMessages(ruleName, { |
| rejected: 'Unexpected missing scoping root', |
| }); |
| |
| const meta = { |
| url: 'https://stylelint.io/user-guide/rules/nesting-selector-no-missing-scoping-root', |
| }; |
| |
| /** @type {import('stylelint').CoreRules[ruleName]} */ |
| const rule = (primary, secondaryOptions) => { |
| return (root, result) => { |
| const validOptions = validateOptions( |
| result, |
| ruleName, |
| { actual: primary, possible: [true] }, |
| { |
| actual: secondaryOptions, |
| possible: { |
| ignoreAtRules: [validateTypes.isString, validateTypes.isRegExp], |
| }, |
| optional: true, |
| }, |
| ); |
| |
| if (!validOptions) return; |
| |
| root.walkRules(/&/, (ruleNode) => { |
| if (!isStandardSyntaxRule(ruleNode)) return; |
| |
| // Check if the rule is nested within a scoping root |
| if (hasValidScopingRoot(ruleNode, secondaryOptions)) return; |
| |
| let ast; |
| |
| try { |
| ast = cssTree.parse(getRuleSelector(ruleNode), { context: 'selectorList', positions: true }); |
| } catch { |
| // Cannot parse selector list, skip checking |
| return; |
| } |
| |
| check(ruleNode, ast); |
| }); |
| |
| // Check @scope at-rules for nesting selectors in parameters |
| root.walkAtRules(regexes.atRuleRegexes.scopeName, (atRule) => { |
| if (!isStandardSyntaxAtRule(atRule)) return; |
| |
| // Cheap check for nesting selector |
| if (!atRule.params.includes('&')) return; |
| |
| // Only check @scope at-rules that don't have a parent scoping context |
| if (hasValidScopingRoot(atRule, secondaryOptions)) return; |
| |
| let ast; |
| |
| try { |
| ast = cssTree.parse(getAtRuleParams(atRule), { |
| atrule: 'scope', |
| context: 'atrulePrelude', |
| positions: true, |
| }); |
| } catch { |
| // Cannot parse @scope at-rule, skip checking |
| return; |
| } |
| |
| check(atRule, ast, nodeFieldIndices.atRuleParamIndex(atRule)); |
| }); |
| |
| /** |
| * @param {import('postcss').Rule | import('postcss').AtRule} node |
| * @param {import('css-tree').CssNode} ast |
| * @param {number} [offset=0] |
| */ |
| function check(node, ast, offset = 0) { |
| cssTree.walk(ast, { |
| /** |
| * @param {import('css-tree').CssNode} cssNode |
| */ |
| enter(cssNode) { |
| if (cssNode.type !== 'NestingSelector') return; |
| |
| if (!cssNode.loc) return; |
| |
| const index = offset + cssNode.loc.start.offset; |
| const endIndex = index + 1; |
| |
| report({ |
| message: messages.rejected, |
| node, |
| result, |
| ruleName, |
| index, |
| endIndex, |
| }); |
| }, |
| }); |
| } |
| }; |
| }; |
| |
| /** |
| * Check if a node has a valid scoping root |
| * @param {import('postcss').Rule | import('postcss').AtRule} node |
| * @param {object} secondaryOptions |
| * @returns {boolean} |
| */ |
| function hasValidScopingRoot(node, secondaryOptions) { |
| let current = node.parent; |
| |
| while (current) { |
| // If we find a rule in the parent chain, it provides a scoping root |
| if (current.type === 'rule') { |
| return true; |
| } |
| |
| // If we find an @scope at-rule, it provides a scoping root |
| if (current.type === 'atrule' && current.name === 'scope') { |
| return true; |
| } |
| |
| // If we find an ignored at-rule, it provides a scoping root |
| if ( |
| current.type === 'atrule' && |
| optionsMatches(secondaryOptions, 'ignoreAtRules', current.name) |
| ) { |
| return true; |
| } |
| |
| // If we reach the root without finding a scoping root |
| if (current.type === 'root') { |
| return false; |
| } |
| |
| current = current.parent; |
| } |
| |
| return false; |
| } |
| |
| rule.ruleName = ruleName; |
| rule.messages = messages; |
| rule.meta = meta; |
| |
| module.exports = rule; |