Skip to content

Commit

Permalink
Fix no-descending-specificity performance (#7026)
Browse files Browse the repository at this point in the history
* Fix `no-descending-specificity` performance

* Create thirty-swans-complain.md

---------

Co-authored-by: Richard Hallows <jeddy3@users.noreply.github.com>
  • Loading branch information
romainmenke and jeddy3 committed Jul 3, 2023
1 parent b919a0b commit cf73360
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 17 deletions.
5 changes: 5 additions & 0 deletions .changeset/thirty-swans-complain.md
@@ -0,0 +1,5 @@
---
"stylelint": patch
---

Fixed: `no-descending-specificity` performance
52 changes: 52 additions & 0 deletions lib/rules/no-descending-specificity/__tests__/index.mjs
Expand Up @@ -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,
},
],
},
],
});

Expand Down
36 changes: 19 additions & 17 deletions lib/rules/no-descending-specificity/index.js
Expand Up @@ -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';

Expand Down Expand Up @@ -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;
}

Expand All @@ -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<string, Entry[]>} 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;
Expand All @@ -126,6 +125,8 @@ const rule = (primary, secondaryOptions) => {
messageArgs: [selector, priorEntry.selector],
word: selector,
});

break;
}
}

Expand All @@ -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 (
Expand Down

0 comments on commit cf73360

Please sign in to comment.