| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165 |
- // 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;
|