Skip to content

Commit

Permalink
Refactor types for flattenNestedSelectorsForRule and `getSelectorSo…
Browse files Browse the repository at this point in the history
…urceIndex` (#7704)

* Refactor to add `selectorSourceIndex` utility

* update postcss-selector-parser

* add some basic tests

* cleanup

* rename

* Refactor types for `flattenNestedSelectorsForRule` and `getSelectorSourceIndex`

* cleanup

* Update lib/rules/selector-max-attribute/index.mjs

Co-authored-by: Masafumi Koba <[email protected]>

* apply suggestion from code review

* Update lib/rules/selector-max-attribute/index.mjs

Co-authored-by: Masafumi Koba <[email protected]>

* apply suggestions from code review

* fix

* Update lib/rules/selector-max-type/index.mjs

Co-authored-by: Masafumi Koba <[email protected]>

* Update lib/rules/selector-max-id/index.mjs

Co-authored-by: Masafumi Koba <[email protected]>

* apply suggestions from code review

---------

Co-authored-by: Masafumi Koba <[email protected]>
  • Loading branch information
romainmenke and ybiquitous committed May 23, 2024
1 parent e0d8fdd commit c4ea9d4
Show file tree
Hide file tree
Showing 26 changed files with 236 additions and 198 deletions.
2 changes: 1 addition & 1 deletion lib/rules/no-descending-specificity/index.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ const rule = (primary, secondaryOptions) => {

// Resolve any nested selectors before checking
flattenNestedSelectorsForRule(ruleNode, result).forEach(({ selector, resolvedSelectors }) => {
resolvedSelectors.forEach((resolvedSelector) => {
resolvedSelectors.each((resolvedSelector) => {
checkSelector(resolvedSelector, selector, ruleNode, comparisonContext);
});
});
Expand Down
2 changes: 1 addition & 1 deletion lib/rules/no-descending-specificity/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ const rule = (primary, secondaryOptions) => {

// Resolve any nested selectors before checking
flattenNestedSelectorsForRule(ruleNode, result).forEach(({ selector, resolvedSelectors }) => {
resolvedSelectors.forEach((resolvedSelector) => {
resolvedSelectors.each((resolvedSelector) => {
checkSelector(resolvedSelector, selector, ruleNode, comparisonContext);
});
});
Expand Down
25 changes: 15 additions & 10 deletions lib/rules/selector-max-attribute/index.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// please instead edit the ESM counterpart and rebuild with Rollup (npm run build).
'use strict';

const selectorParser = require('postcss-selector-parser');
const validateTypes = require('../../utils/validateTypes.cjs');
const flattenNestedSelectorsForRule = require('../../utils/flattenNestedSelectorsForRule.cjs');
const getSelectorSourceIndex = require('../../utils/getSelectorSourceIndex.cjs');
Expand All @@ -13,6 +14,8 @@ const report = require('../../utils/report.cjs');
const ruleMessages = require('../../utils/ruleMessages.cjs');
const validateOptions = require('../../utils/validateOptions.cjs');

const { isRoot, isSelector } = selectorParser;

const ruleName = 'selector-max-attribute';

const messages = ruleMessages(ruleName, {
Expand Down Expand Up @@ -50,17 +53,12 @@ const rule = (primary, secondaryOptions) => {
}

/**
* @param {import('postcss-selector-parser').Container<string | undefined>} resolvedSelectorNode
* @param {import('postcss-selector-parser').Container<string | undefined>} selectorNode
* @param {import('postcss-selector-parser').Selector} resolvedSelectorNode
* @param {import('postcss-selector-parser').Selector} selectorNode
* @param {import('postcss').Rule} ruleNode
*/
function checkSelector(resolvedSelectorNode, selectorNode, ruleNode) {
const count = resolvedSelectorNode.reduce((total, childNode) => {
// Only traverse inside actual selectors and context functional pseudo-classes
if (childNode.type === 'selector' || isContextFunctionalPseudoClass(childNode)) {
checkSelector(childNode, selectorNode, ruleNode);
}

if (childNode.type !== 'attribute') {
// Not an attribute node -> ignore
return total;
Expand All @@ -76,7 +74,7 @@ const rule = (primary, secondaryOptions) => {
return total;
}, 0);

if (selectorNode.type !== 'root' && selectorNode.type !== 'pseudo' && count > primary) {
if (count > primary) {
const index = getSelectorSourceIndex(selectorNode);
const selectorStr = selectorNode.toString().trim();

Expand All @@ -96,8 +94,15 @@ const rule = (primary, secondaryOptions) => {
if (!isStandardSyntaxRule(ruleNode)) return;

flattenNestedSelectorsForRule(ruleNode, result).forEach(({ selector, resolvedSelectors }) => {
resolvedSelectors.forEach((resolvedSelector) => {
checkSelector(resolvedSelector, selector, ruleNode);
resolvedSelectors.walk((childSelector) => {
if (!isSelector(childSelector)) return;

if (
isRoot(childSelector.parent) ||
isContextFunctionalPseudoClass(childSelector.parent)
) {
checkSelector(childSelector, selector, ruleNode);
}
});
});
});
Expand Down
25 changes: 15 additions & 10 deletions lib/rules/selector-max-attribute/index.mjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import selectorParser from 'postcss-selector-parser';
const { isRoot, isSelector } = selectorParser;

import { isRegExp, isString } from '../../utils/validateTypes.mjs';
import flattenNestedSelectorsForRule from '../../utils/flattenNestedSelectorsForRule.mjs';
import getSelectorSourceIndex from '../../utils/getSelectorSourceIndex.mjs';
Expand Down Expand Up @@ -46,17 +49,12 @@ const rule = (primary, secondaryOptions) => {
}

/**
* @param {import('postcss-selector-parser').Container<string | undefined>} resolvedSelectorNode
* @param {import('postcss-selector-parser').Container<string | undefined>} selectorNode
* @param {import('postcss-selector-parser').Selector} resolvedSelectorNode
* @param {import('postcss-selector-parser').Selector} selectorNode
* @param {import('postcss').Rule} ruleNode
*/
function checkSelector(resolvedSelectorNode, selectorNode, ruleNode) {
const count = resolvedSelectorNode.reduce((total, childNode) => {
// Only traverse inside actual selectors and context functional pseudo-classes
if (childNode.type === 'selector' || isContextFunctionalPseudoClass(childNode)) {
checkSelector(childNode, selectorNode, ruleNode);
}

if (childNode.type !== 'attribute') {
// Not an attribute node -> ignore
return total;
Expand All @@ -72,7 +70,7 @@ const rule = (primary, secondaryOptions) => {
return total;
}, 0);

if (selectorNode.type !== 'root' && selectorNode.type !== 'pseudo' && count > primary) {
if (count > primary) {
const index = getSelectorSourceIndex(selectorNode);
const selectorStr = selectorNode.toString().trim();

Expand All @@ -92,8 +90,15 @@ const rule = (primary, secondaryOptions) => {
if (!isStandardSyntaxRule(ruleNode)) return;

flattenNestedSelectorsForRule(ruleNode, result).forEach(({ selector, resolvedSelectors }) => {
resolvedSelectors.forEach((resolvedSelector) => {
checkSelector(resolvedSelector, selector, ruleNode);
resolvedSelectors.walk((childSelector) => {
if (!isSelector(childSelector)) return;

if (
isRoot(childSelector.parent) ||
isContextFunctionalPseudoClass(childSelector.parent)
) {
checkSelector(childSelector, selector, ruleNode);
}
});
});
});
Expand Down
25 changes: 15 additions & 10 deletions lib/rules/selector-max-class/index.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// please instead edit the ESM counterpart and rebuild with Rollup (npm run build).
'use strict';

const selectorParser = require('postcss-selector-parser');
const flattenNestedSelectorsForRule = require('../../utils/flattenNestedSelectorsForRule.cjs');
const getSelectorSourceIndex = require('../../utils/getSelectorSourceIndex.cjs');
const isContextFunctionalPseudoClass = require('../../utils/isContextFunctionalPseudoClass.cjs');
Expand All @@ -11,6 +12,8 @@ const report = require('../../utils/report.cjs');
const ruleMessages = require('../../utils/ruleMessages.cjs');
const validateOptions = require('../../utils/validateOptions.cjs');

const { isRoot, isSelector } = selectorParser;

const ruleName = 'selector-max-class';

const messages = ruleMessages(ruleName, {
Expand All @@ -35,23 +38,18 @@ const rule = (primary) => {
}

/**
* @param {import('postcss-selector-parser').Container<string | undefined>} resolvedSelectorNode
* @param {import('postcss-selector-parser').Container<string | undefined>} selectorNode
* @param {import('postcss-selector-parser').Selector} resolvedSelectorNode
* @param {import('postcss-selector-parser').Selector} selectorNode
* @param {import('postcss').Rule} ruleNode
*/
function checkSelector(resolvedSelectorNode, selectorNode, ruleNode) {
const count = resolvedSelectorNode.reduce((total, childNode) => {
// Only traverse inside actual selectors and context functional pseudo-classes
if (childNode.type === 'selector' || isContextFunctionalPseudoClass(childNode)) {
checkSelector(childNode, selectorNode, ruleNode);
}

if (childNode.type === 'class') total += 1;

return total;
}, 0);

if (selectorNode.type !== 'root' && selectorNode.type !== 'pseudo' && count > primary) {
if (count > primary) {
const index = getSelectorSourceIndex(selectorNode);
const selectorStr = selectorNode.toString().trim();

Expand All @@ -71,8 +69,15 @@ const rule = (primary) => {
if (!isStandardSyntaxRule(ruleNode)) return;

flattenNestedSelectorsForRule(ruleNode, result).forEach(({ selector, resolvedSelectors }) => {
resolvedSelectors.forEach((resolvedSelector) => {
checkSelector(resolvedSelector, selector, ruleNode);
resolvedSelectors.walk((childSelector) => {
if (!isSelector(childSelector)) return;

if (
isRoot(childSelector.parent) ||
isContextFunctionalPseudoClass(childSelector.parent)
) {
checkSelector(childSelector, selector, ruleNode);
}
});
});
});
Expand Down
25 changes: 15 additions & 10 deletions lib/rules/selector-max-class/index.mjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import selectorParser from 'postcss-selector-parser';
const { isRoot, isSelector } = selectorParser;

import flattenNestedSelectorsForRule from '../../utils/flattenNestedSelectorsForRule.mjs';
import getSelectorSourceIndex from '../../utils/getSelectorSourceIndex.mjs';
import isContextFunctionalPseudoClass from '../../utils/isContextFunctionalPseudoClass.mjs';
Expand Down Expand Up @@ -31,23 +34,18 @@ const rule = (primary) => {
}

/**
* @param {import('postcss-selector-parser').Container<string | undefined>} resolvedSelectorNode
* @param {import('postcss-selector-parser').Container<string | undefined>} selectorNode
* @param {import('postcss-selector-parser').Selector} resolvedSelectorNode
* @param {import('postcss-selector-parser').Selector} selectorNode
* @param {import('postcss').Rule} ruleNode
*/
function checkSelector(resolvedSelectorNode, selectorNode, ruleNode) {
const count = resolvedSelectorNode.reduce((total, childNode) => {
// Only traverse inside actual selectors and context functional pseudo-classes
if (childNode.type === 'selector' || isContextFunctionalPseudoClass(childNode)) {
checkSelector(childNode, selectorNode, ruleNode);
}

if (childNode.type === 'class') total += 1;

return total;
}, 0);

if (selectorNode.type !== 'root' && selectorNode.type !== 'pseudo' && count > primary) {
if (count > primary) {
const index = getSelectorSourceIndex(selectorNode);
const selectorStr = selectorNode.toString().trim();

Expand All @@ -67,8 +65,15 @@ const rule = (primary) => {
if (!isStandardSyntaxRule(ruleNode)) return;

flattenNestedSelectorsForRule(ruleNode, result).forEach(({ selector, resolvedSelectors }) => {
resolvedSelectors.forEach((resolvedSelector) => {
checkSelector(resolvedSelector, selector, ruleNode);
resolvedSelectors.walk((childSelector) => {
if (!isSelector(childSelector)) return;

if (
isRoot(childSelector.parent) ||
isContextFunctionalPseudoClass(childSelector.parent)
) {
checkSelector(childSelector, selector, ruleNode);
}
});
});
});
Expand Down
20 changes: 10 additions & 10 deletions lib/rules/selector-max-combinators/index.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// please instead edit the ESM counterpart and rebuild with Rollup (npm run build).
'use strict';

const selectorParser = require('postcss-selector-parser');
const flattenNestedSelectorsForRule = require('../../utils/flattenNestedSelectorsForRule.cjs');
const getSelectorSourceIndex = require('../../utils/getSelectorSourceIndex.cjs');
const isNonNegativeInteger = require('../../utils/isNonNegativeInteger.cjs');
Expand All @@ -10,6 +11,8 @@ const report = require('../../utils/report.cjs');
const ruleMessages = require('../../utils/ruleMessages.cjs');
const validateOptions = require('../../utils/validateOptions.cjs');

const { isSelector } = selectorParser;

const ruleName = 'selector-max-combinators';

const messages = ruleMessages(ruleName, {
Expand All @@ -36,23 +39,18 @@ const rule = (primary) => {
}

/**
* @param {import('postcss-selector-parser').Container<string | undefined>} resolvedSelectorNode
* @param {import('postcss-selector-parser').Container<string | undefined>} selectorNode
* @param {import('postcss-selector-parser').Selector} resolvedSelectorNode
* @param {import('postcss-selector-parser').Selector} selectorNode
* @param {import('postcss').Rule} ruleNode
*/
function checkSelector(resolvedSelectorNode, selectorNode, ruleNode) {
const count = resolvedSelectorNode.reduce((total, childNode) => {
// Only traverse inside actual selectors
if (childNode.type === 'selector') {
checkSelector(childNode, selectorNode, ruleNode);
}

if (childNode.type === 'combinator') total += 1;

return total;
}, 0);

if (selectorNode.type !== 'root' && selectorNode.type !== 'pseudo' && count > primary) {
if (count > primary) {
const index = getSelectorSourceIndex(selectorNode);
const selectorStr = selectorNode.toString().trim();

Expand All @@ -72,8 +70,10 @@ const rule = (primary) => {
if (!isStandardSyntaxRule(ruleNode)) return;

flattenNestedSelectorsForRule(ruleNode, result).forEach(({ selector, resolvedSelectors }) => {
resolvedSelectors.forEach((resolvedSelector) => {
checkSelector(resolvedSelector, selector, ruleNode);
resolvedSelectors.walk((childSelector) => {
if (!isSelector(childSelector)) return;

checkSelector(childSelector, selector, ruleNode);
});
});
});
Expand Down
20 changes: 10 additions & 10 deletions lib/rules/selector-max-combinators/index.mjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import selectorParser from 'postcss-selector-parser';
const { isSelector } = selectorParser;

import flattenNestedSelectorsForRule from '../../utils/flattenNestedSelectorsForRule.mjs';
import getSelectorSourceIndex from '../../utils/getSelectorSourceIndex.mjs';
import isNonNegativeInteger from '../../utils/isNonNegativeInteger.mjs';
Expand Down Expand Up @@ -32,23 +35,18 @@ const rule = (primary) => {
}

/**
* @param {import('postcss-selector-parser').Container<string | undefined>} resolvedSelectorNode
* @param {import('postcss-selector-parser').Container<string | undefined>} selectorNode
* @param {import('postcss-selector-parser').Selector} resolvedSelectorNode
* @param {import('postcss-selector-parser').Selector} selectorNode
* @param {import('postcss').Rule} ruleNode
*/
function checkSelector(resolvedSelectorNode, selectorNode, ruleNode) {
const count = resolvedSelectorNode.reduce((total, childNode) => {
// Only traverse inside actual selectors
if (childNode.type === 'selector') {
checkSelector(childNode, selectorNode, ruleNode);
}

if (childNode.type === 'combinator') total += 1;

return total;
}, 0);

if (selectorNode.type !== 'root' && selectorNode.type !== 'pseudo' && count > primary) {
if (count > primary) {
const index = getSelectorSourceIndex(selectorNode);
const selectorStr = selectorNode.toString().trim();

Expand All @@ -68,8 +66,10 @@ const rule = (primary) => {
if (!isStandardSyntaxRule(ruleNode)) return;

flattenNestedSelectorsForRule(ruleNode, result).forEach(({ selector, resolvedSelectors }) => {
resolvedSelectors.forEach((resolvedSelector) => {
checkSelector(resolvedSelector, selector, ruleNode);
resolvedSelectors.walk((childSelector) => {
if (!isSelector(childSelector)) return;

checkSelector(childSelector, selector, ruleNode);
});
});
});
Expand Down
20 changes: 11 additions & 9 deletions lib/rules/selector-max-compound-selectors/index.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -62,20 +62,15 @@ const rule = (primary, secondaryOptions) => {
}

/**
* @param {import('postcss-selector-parser').Container<string | undefined>} resolvedSelectorNode
* @param {import('postcss-selector-parser').Container<string | undefined>} selectorNode
* @param {import('postcss-selector-parser').Selector} resolvedSelectorNode
* @param {import('postcss-selector-parser').Selector} selectorNode
* @param {import('postcss').Rule} ruleNode
*/
function checkSelector(resolvedSelectorNode, selectorNode, ruleNode) {
/** @type {import('postcss-selector-parser').Node[]} */
const filteredChildNodes = [];

resolvedSelectorNode.each((childNode) => {
// Only traverse inside actual selectors and context functional pseudo-classes
if (isSelector(childNode) || isContextFunctionalPseudoClass(childNode)) {
checkSelector(childNode, selectorNode, ruleNode);
}

if (!isSelectorIgnored(childNode)) {
filteredChildNodes.push(childNode);
}
Expand Down Expand Up @@ -119,8 +114,15 @@ const rule = (primary, secondaryOptions) => {
if (!isStandardSyntaxRule(ruleNode)) return;

flattenNestedSelectorsForRule(ruleNode, result).forEach(({ selector, resolvedSelectors }) => {
resolvedSelectors.forEach((resolvedSelector) => {
checkSelector(resolvedSelector, selector, ruleNode);
resolvedSelectors.walk((childSelector) => {
if (!isSelector(childSelector)) return;

if (
isRoot(childSelector.parent) ||
isContextFunctionalPseudoClass(childSelector.parent)
) {
checkSelector(childSelector, selector, ruleNode);
}
});
});
});
Expand Down
Loading

0 comments on commit c4ea9d4

Please sign in to comment.