diff --git a/.changeset/thirty-swans-complain.md b/.changeset/thirty-swans-complain.md new file mode 100644 index 0000000000..aaf466e6ce --- /dev/null +++ b/.changeset/thirty-swans-complain.md @@ -0,0 +1,5 @@ +--- +"stylelint": patch +--- + +Fixed: `no-descending-specificity` performance diff --git a/lib/rules/no-descending-specificity/__tests__/index.mjs b/lib/rules/no-descending-specificity/__tests__/index.mjs index 224d417037..4659832c2b 100644 --- a/lib/rules/no-descending-specificity/__tests__/index.mjs +++ b/lib/rules/no-descending-specificity/__tests__/index.mjs @@ -189,6 +189,58 @@ testRule({ endLine: 1, endColumn: 29, }, + { + code: '.a .b .c {} .b .c {} .c {}', + warnings: [ + { + message: messages.rejected('.b .c', '.a .b .c'), + line: 1, + column: 13, + endLine: 1, + endColumn: 18, + }, + { + message: messages.rejected('.c', '.a .b .c'), + line: 1, + column: 22, + endLine: 1, + endColumn: 24, + }, + ], + }, + { + code: '.a .b .c {} .b .c {} .c {} .d .b .c {} .b .c {} .c {}', + warnings: [ + { + message: messages.rejected('.b .c', '.a .b .c'), + line: 1, + column: 13, + endLine: 1, + endColumn: 18, + }, + { + message: messages.rejected('.c', '.a .b .c'), + line: 1, + column: 22, + endLine: 1, + endColumn: 24, + }, + { + message: messages.rejected('.b .c', '.a .b .c'), + line: 1, + column: 40, + endLine: 1, + endColumn: 45, + }, + { + message: messages.rejected('.c', '.a .b .c'), + line: 1, + column: 49, + endLine: 1, + endColumn: 51, + }, + ], + }, ], }); diff --git a/lib/rules/no-descending-specificity/index.js b/lib/rules/no-descending-specificity/index.js index ac4a639913..4bbe50b0bc 100644 --- a/lib/rules/no-descending-specificity/index.js +++ b/lib/rules/no-descending-specificity/index.js @@ -13,7 +13,6 @@ const parseSelector = require('../../utils/parseSelector'); const report = require('../../utils/report'); const ruleMessages = require('../../utils/ruleMessages'); const validateOptions = require('../../utils/validateOptions'); -const { assert } = require('../../utils/validateTypes'); const ruleName = 'no-descending-specificity'; @@ -63,8 +62,10 @@ const rule = (primary, secondaryOptions) => { return; } + const selectors = ruleNode.selectors; + // Ignores selectors within list of selectors - if (ignoreSelectorsWithinList && ruleNode.selectors.length > 1) { + if (ignoreSelectorsWithinList && selectors.length > 1) { return; } @@ -74,43 +75,41 @@ const rule = (primary, secondaryOptions) => { findAtRuleContext(ruleNode), ); - for (const selector of ruleNode.selectors) { - const trimSelector = selector.trim(); - + for (const selector of selectors) { // Ignore `.selector, { }` - if (trimSelector === '') { + if (selector.trim() === '') { continue; } // Resolve any nested selectors before checking for (const resolvedSelector of resolvedNestedSelector(selector, ruleNode)) { - parseSelector(resolvedSelector, result, ruleNode, (s) => { - if (!isStandardSyntaxSelector(resolvedSelector)) { - return; - } + if (!isStandardSyntaxSelector(resolvedSelector)) { + continue; + } - checkSelector(s, ruleNode, comparisonContext); + parseSelector(resolvedSelector, result, ruleNode, (s) => { + checkSelector(resolvedSelector, s, ruleNode, comparisonContext); }); } } }); /** + * @param {string} selector * @param {import('postcss-selector-parser').Root} selectorNode * @param {import('postcss').Rule} ruleNode * @param {Map} comparisonContext */ - function checkSelector(selectorNode, ruleNode, comparisonContext) { - const selector = selectorNode.toString(); + function checkSelector(selector, selectorNode, ruleNode, comparisonContext) { const referenceSelector = lastCompoundSelectorWithoutPseudoClasses(selectorNode); - if (referenceSelector === undefined) return; + if (!referenceSelector) return; const selectorSpecificity = calculate(selectorNode); const entry = { selector, specificity: selectorSpecificity }; const priorComparableSelectors = comparisonContext.get(referenceSelector); - if (priorComparableSelectors === undefined) { + if (!priorComparableSelectors) { comparisonContext.set(referenceSelector, [entry]); return; @@ -126,6 +125,8 @@ const rule = (primary, secondaryOptions) => { messageArgs: [selector, priorEntry.selector], word: selector, }); + + break; } } @@ -141,11 +142,12 @@ const rule = (primary, secondaryOptions) => { function lastCompoundSelectorWithoutPseudoClasses(selectorNode) { const firstChild = selectorNode.nodes[0]; - assert(firstChild); + if (!firstChild) return undefined; + const nodesByCombinator = firstChild.split((node) => node.type === 'combinator'); const nodesAfterLastCombinator = nodesByCombinator[nodesByCombinator.length - 1]; - assert(nodesAfterLastCombinator); + if (!nodesAfterLastCombinator) return undefined; const nodesWithoutPseudoClasses = nodesAfterLastCombinator.filter((node) => { return (