Skip to content

Commit

Permalink
move to helper
Browse files Browse the repository at this point in the history
  • Loading branch information
romainmenke committed Jan 25, 2024
1 parent 586ef15 commit 5c8fda0
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 182 deletions.
95 changes: 4 additions & 91 deletions lib/rules/selector-max-specificity/index.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,14 @@
'use strict';

const selectorSpecificity = require('@csstools/selector-specificity');
const selectorResolveNested = require('@csstools/selector-resolve-nested');
const selectorParser = require('postcss-selector-parser');
const validateTypes = require('../../utils/validateTypes.cjs');
const typeGuards = require('../../utils/typeGuards.cjs');
const getRuleSelector = require('../../utils/getRuleSelector.cjs');
const isStandardSyntaxRule = require('../../utils/isStandardSyntaxRule.cjs');
const isStandardSyntaxSelector = require('../../utils/isStandardSyntaxSelector.cjs');
const optionsMatches = require('../../utils/optionsMatches.cjs');
const report = require('../../utils/report.cjs');
const resolveNestedSelectorsForRule = require('../../utils/resolveNestedSelectorsForRule.cjs');
const ruleMessages = require('../../utils/ruleMessages.cjs');
const selectorAST = require('../../utils/selectorAST.cjs');
const validateOptions = require('../../utils/validateOptions.cjs');

const ruleName = 'selector-max-specificity';
Expand Down Expand Up @@ -70,92 +67,6 @@ function removeIgnoredNodes(selector, isIgnored) {
return selector;
}

/**
* @typedef {import('postcss-selector-parser').Selector} Selector
* @typedef {import('postcss-selector-parser').Root} SelectorRoot
* @param {import('postcss').Rule} rule
* @param {import('stylelint').PostcssResult} result
* @param {(selector: string) => boolean} isIgnored
* @returns {Array<{selector: Selector, resolvedSelectors: Array<Selector>}> | undefined}
*/
function gatherSelectorsForRule(rule, result, isIgnored) {
/** @typedef {import('postcss').Document} Document */
/** @typedef {import('postcss').Root} Root */
/** @typedef {import('postcss').Container} Container */

const ownAST = selectorAST(getRuleSelector(rule), result, rule);

if (!ownAST) return;

/** @type {import('postcss-selector-parser').Root | undefined} */
let ast = undefined;

/** @type {Array<Document|Root|Container>} */
let ancestors = [];

{
/** @type {Document|Root|Container|undefined} */
let parent = rule.parent;

while (parent) {
ancestors.push(parent);
parent = parent.parent;
}
}

ancestors.reverse();

for (const child of ancestors) {
if (typeGuards.isRule(child)) {
const childAST = selectorAST(getRuleSelector(child), result, child);

if (!childAST) return;

if (ast) {
ast = selectorResolveNested.resolveNestedSelector(childAST, ast);
} else {
ast = childAST;
}

continue;
}

if (typeGuards.isAtRule(child) && ast) {
// `.foo, #bar { @media screen { color: red; } }`
// equivalent to
// `@media screen { .foo, #bar { & { color: red; } } }`
// `@media screen { :is(.foo, #bar) { color: red; } }`
const childAST = selectorAST('&', result, child);

if (!childAST) return;

ast = selectorResolveNested.resolveNestedSelector(childAST, ast);
}
}

return ownAST.map((selector) => {
if (!ast) {
return {
selector,
resolvedSelectors: [removeIgnoredNodes(selector.clone(), isIgnored)],
};
}

return {
selector,
resolvedSelectors: selectorResolveNested.resolveNestedSelector(
selectorParser.root({
nodes: [selector],
value: '',
}),
ast,
).nodes.map((x) => {
return removeIgnoredNodes(x, isIgnored);
}),
};
});
}

/** @type {import('stylelint').Rule<string>} */
const rule = (primary, secondaryOptions) => {
return (root, result) => {
Expand Down Expand Up @@ -200,14 +111,16 @@ const rule = (primary, secondaryOptions) => {
return;
}

const selectorList = gatherSelectorsForRule(ruleNode, result, isSelectorIgnored);
const selectorList = resolveNestedSelectorsForRule(ruleNode, result);

if (!selectorList) return;

selectorList.forEach(({ selector, resolvedSelectors }) => {
const selectorStr = selector.toString();

resolvedSelectors.forEach((resolvedSelector) => {
resolvedSelector = removeIgnoredNodes(resolvedSelector, isSelectorIgnored);

if (!isStandardSyntaxSelector(resolvedSelector.toString())) return;

// Check if the selector specificity exceeds the allowed maximum
Expand Down
95 changes: 4 additions & 91 deletions lib/rules/selector-max-specificity/index.mjs
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import { compare, selectorSpecificity } from '@csstools/selector-specificity';
import { resolveNestedSelector } from '@csstools/selector-resolve-nested';
import selectorParser from 'postcss-selector-parser';

import { assertNumber, isRegExp, isString } from '../../utils/validateTypes.mjs';
import { isAtRule, isRule } from '../../utils/typeGuards.mjs';

import getRuleSelector from '../../utils/getRuleSelector.mjs';
import isStandardSyntaxRule from '../../utils/isStandardSyntaxRule.mjs';
import isStandardSyntaxSelector from '../../utils/isStandardSyntaxSelector.mjs';
import optionsMatches from '../../utils/optionsMatches.mjs';
import report from '../../utils/report.mjs';
import resolveNestedSelectorsForRule from '../../utils/resolveNestedSelectorsForRule.mjs';
import ruleMessages from '../../utils/ruleMessages.mjs';
import selectorAST from '../../utils/selectorAST.mjs';
import validateOptions from '../../utils/validateOptions.mjs';

const ruleName = 'selector-max-specificity';
Expand Down Expand Up @@ -68,92 +65,6 @@ function removeIgnoredNodes(selector, isIgnored) {
return selector;
}

/**
* @typedef {import('postcss-selector-parser').Selector} Selector
* @typedef {import('postcss-selector-parser').Root} SelectorRoot
* @param {import('postcss').Rule} rule
* @param {import('stylelint').PostcssResult} result
* @param {(selector: string) => boolean} isIgnored
* @returns {Array<{selector: Selector, resolvedSelectors: Array<Selector>}> | undefined}
*/
function gatherSelectorsForRule(rule, result, isIgnored) {
/** @typedef {import('postcss').Document} Document */
/** @typedef {import('postcss').Root} Root */
/** @typedef {import('postcss').Container} Container */

const ownAST = selectorAST(getRuleSelector(rule), result, rule);

if (!ownAST) return;

/** @type {import('postcss-selector-parser').Root | undefined} */
let ast = undefined;

/** @type {Array<Document|Root|Container>} */
let ancestors = [];

{
/** @type {Document|Root|Container|undefined} */
let parent = rule.parent;

while (parent) {
ancestors.push(parent);
parent = parent.parent;
}
}

ancestors.reverse();

for (const child of ancestors) {
if (isRule(child)) {
const childAST = selectorAST(getRuleSelector(child), result, child);

if (!childAST) return;

if (ast) {
ast = resolveNestedSelector(childAST, ast);
} else {
ast = childAST;
}

continue;
}

if (isAtRule(child) && ast) {
// `.foo, #bar { @media screen { color: red; } }`
// equivalent to
// `@media screen { .foo, #bar { & { color: red; } } }`
// `@media screen { :is(.foo, #bar) { color: red; } }`
const childAST = selectorAST('&', result, child);

if (!childAST) return;

ast = resolveNestedSelector(childAST, ast);
}
}

return ownAST.map((selector) => {
if (!ast) {
return {
selector,
resolvedSelectors: [removeIgnoredNodes(selector.clone(), isIgnored)],
};
}

return {
selector,
resolvedSelectors: resolveNestedSelector(
selectorParser.root({
nodes: [selector],
value: '',
}),
ast,
).nodes.map((x) => {
return removeIgnoredNodes(x, isIgnored);
}),
};
});
}

/** @type {import('stylelint').Rule<string>} */
const rule = (primary, secondaryOptions) => {
return (root, result) => {
Expand Down Expand Up @@ -198,14 +109,16 @@ const rule = (primary, secondaryOptions) => {
return;
}

const selectorList = gatherSelectorsForRule(ruleNode, result, isSelectorIgnored);
const selectorList = resolveNestedSelectorsForRule(ruleNode, result);

if (!selectorList) return;

selectorList.forEach(({ selector, resolvedSelectors }) => {
const selectorStr = selector.toString();

resolvedSelectors.forEach((resolvedSelector) => {
resolvedSelector = removeIgnoredNodes(resolvedSelector, isSelectorIgnored);

if (!isStandardSyntaxSelector(resolvedSelector.toString())) return;

// Check if the selector specificity exceeds the allowed maximum
Expand Down
94 changes: 94 additions & 0 deletions lib/utils/resolveNestedSelectorsForRule.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// 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 selectorResolveNested = require('@csstools/selector-resolve-nested');
const typeGuards = require('./typeGuards.cjs');
const getRuleSelector = require('./getRuleSelector.cjs');
const selectorAST = require('./selectorAST.cjs');
const selectorParser = require('postcss-selector-parser');

/**
* @typedef {import('postcss-selector-parser').Selector} Selector
* @typedef {import('postcss-selector-parser').Root} SelectorRoot
* @param {import('postcss').Rule} rule
* @param {import('stylelint').PostcssResult} result
* @returns {Array<{selector: Selector, resolvedSelectors: Array<Selector>}> | undefined}
*/
function resolveNestedSelectorsForRule(rule, result) {
/** @typedef {import('postcss').Document} Document */
/** @typedef {import('postcss').Root} Root */
/** @typedef {import('postcss').Container} Container */

const ownAST = selectorAST(getRuleSelector(rule), result, rule);

if (!ownAST) return;

/** @type {import('postcss-selector-parser').Root | undefined} */
let ast = undefined;

/** @type {Array<Document|Root|Container>} */
let ancestors = [];

{
/** @type {Document|Root|Container|undefined} */
let parent = rule.parent;

while (parent) {
ancestors.push(parent);
parent = parent.parent;
}
}

ancestors.reverse();

for (const child of ancestors) {
if (typeGuards.isRule(child)) {
const childAST = selectorAST(getRuleSelector(child), result, child);

if (!childAST) return;

if (ast) {
ast = selectorResolveNested.resolveNestedSelector(childAST, ast);
} else {
ast = childAST;
}

continue;
}

if (typeGuards.isAtRule(child) && ast) {
// `.foo, #bar { @media screen { color: red; } }`
// equivalent to
// `@media screen { .foo, #bar { & { color: red; } } }`
// `@media screen { :is(.foo, #bar) { color: red; } }`
const childAST = selectorAST('&', result, child);

if (!childAST) return;

ast = selectorResolveNested.resolveNestedSelector(childAST, ast);
}
}

return ownAST.map((selector) => {
if (!ast) {
return {
selector,
resolvedSelectors: [selector.clone()],
};
}

return {
selector,
resolvedSelectors: selectorResolveNested.resolveNestedSelector(
selectorParser.root({
nodes: [selector],
value: '',
}),
ast,
).nodes,
};
});
}

module.exports = resolveNestedSelectorsForRule;

0 comments on commit 5c8fda0

Please sign in to comment.