🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

postcss-minify-selectors

Package Overview
Dependencies
Maintainers
8
Versions
75
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

postcss-minify-selectors - npm Package Compare versions

Comparing version
7.1.0
to
7.1.1
+225
src/lib/foldToIsHelpers.js
'use strict';
/**
* @typedef {object} Token
* @property {'compound'|'combinator'} kind
* @property {string} str
* @property {import('postcss-selector-parser').Node[]} [nodes]
*/
/** @typedef {[number, number, number]} Specificity */
/**
* @param {import('postcss-selector-parser').Selector} selector
* @return {Token[]}
*/
function tokenize(selector) {
/** @type {Token[]} */
const tokens = [];
/** @type {import('postcss-selector-parser').Node[]} */
let bucket = [];
const flush = () => {
if (bucket.length) {
tokens.push({
kind: 'compound',
str: bucket.map((n) => String(n)).join(''),
nodes: bucket,
});
bucket = [];
}
};
for (const node of selector.nodes) {
if (node.type === 'combinator') {
flush();
tokens.push({ kind: 'combinator', str: String(node) });
} else {
bucket.push(node);
}
}
flush();
return tokens;
}
/**
* @param {Token} token
* @return {boolean}
*/
function hasPseudoElementOrNesting(token) {
if (token.kind !== 'compound' || !token.nodes) {
return false;
}
for (const n of token.nodes) {
if (n.type === 'nesting') {
return true;
}
if (n.type === 'pseudo') {
const v = n.value;
if (v.startsWith('::')) {
return true;
}
if (
v === ':before' ||
v === ':after' ||
v === ':first-letter' ||
v === ':first-line'
) {
return true;
}
}
}
return false;
}
/**
* @param {Token} token
* @return {boolean}
*/
function hasNthChildOfClause(token) {
if (token.kind !== 'compound' || !token.nodes) {
return false;
}
for (const n of token.nodes) {
if (n.type !== 'pseudo') {
continue;
}
if (n.value !== ':nth-child' && n.value !== ':nth-last-child') {
continue;
}
for (const child of n.nodes) {
for (const inner of child.nodes) {
if (inner.type === 'tag' && inner.value === 'of') {
return true;
}
}
}
}
return false;
}
/**
* @param {import('postcss-selector-parser').Node[]} nodes
* @return {Specificity}
*/
function specificityOf(nodes) {
let id = 0;
let cls = 0;
let type = 0;
for (const n of nodes) {
if (n.type === 'id') {
id++;
} else if (n.type === 'class' || n.type === 'attribute') {
cls++;
} else if (n.type === 'pseudo') {
const v = n.value;
if (v.startsWith('::')) {
type++;
continue;
}
if (v === ':where') {
continue;
}
if (v === ':is' || v === ':matches' || v === ':not' || v === ':has') {
const s = maxChildSpecificity(n);
id += s[0];
cls += s[1];
type += s[2];
continue;
}
if (
v === ':before' ||
v === ':after' ||
v === ':first-letter' ||
v === ':first-line'
) {
type++;
} else {
cls++;
}
} else if (n.type === 'tag') {
type++;
}
}
return [id, cls, type];
}
/**
* @param {import('postcss-selector-parser').Pseudo} pseudo
* @return {Specificity}
*/
function maxChildSpecificity(pseudo) {
/** @type {Specificity} */
let best = [0, 0, 0];
for (const child of pseudo.nodes) {
if (child.type !== 'selector') {
continue;
}
const s = specificityOf(child.nodes);
if (compareSpecificity(s, best) > 0) {
best = s;
}
}
return best;
}
/**
* Sums the specificity of compound tokens in a fold middle — the divergent
* portion of a selector list, between the shared prefix and shared suffix.
*
* @param {Token[]} middle
* @return {Specificity}
*/
function specificityOfMiddle(middle) {
let id = 0;
let cls = 0;
let type = 0;
for (const token of middle) {
if (token.kind !== 'compound' || !token.nodes) {
continue;
}
const s = specificityOf(token.nodes);
id += s[0];
cls += s[1];
type += s[2];
}
return [id, cls, type];
}
/**
* @param {Specificity} a
* @param {Specificity} b
* @return {number}
*/
function compareSpecificity(a, b) {
return a[0] - b[0] || a[1] - b[1] || a[2] - b[2];
}
/**
* @param {Specificity} a
* @param {Specificity} b
* @return {boolean}
*/
function equalSpecificity(a, b) {
return a[0] === b[0] && a[1] === b[1] && a[2] === b[2];
}
/**
* @param {Token[]} tokens
* @return {string}
*/
function joinTokens(tokens) {
return tokens.map((t) => t.str).join('');
}
module.exports = {
tokenize,
hasPseudoElementOrNesting,
hasNthChildOfClause,
specificityOf,
specificityOfMiddle,
maxChildSpecificity,
compareSpecificity,
equalSpecificity,
joinTokens,
};
export type Token = {
kind: "compound" | "combinator";
str: string;
nodes?: import("postcss-selector-parser").Node[] | undefined;
};
export type Specificity = [number, number, number];
/**
* @typedef {object} Token
* @property {'compound'|'combinator'} kind
* @property {string} str
* @property {import('postcss-selector-parser').Node[]} [nodes]
*/
/** @typedef {[number, number, number]} Specificity */
/**
* @param {import('postcss-selector-parser').Selector} selector
* @return {Token[]}
*/
export function tokenize(selector: import("postcss-selector-parser").Selector): Token[];
/**
* @param {Token} token
* @return {boolean}
*/
export function hasPseudoElementOrNesting(token: Token): boolean;
/**
* @param {Token} token
* @return {boolean}
*/
export function hasNthChildOfClause(token: Token): boolean;
/**
* @param {import('postcss-selector-parser').Node[]} nodes
* @return {Specificity}
*/
export function specificityOf(nodes: import("postcss-selector-parser").Node[]): Specificity;
/**
* Sums the specificity of compound tokens in a fold middle — the divergent
* portion of a selector list, between the shared prefix and shared suffix.
*
* @param {Token[]} middle
* @return {Specificity}
*/
export function specificityOfMiddle(middle: Token[]): Specificity;
/**
* @param {import('postcss-selector-parser').Pseudo} pseudo
* @return {Specificity}
*/
export function maxChildSpecificity(pseudo: import("postcss-selector-parser").Pseudo): Specificity;
/**
* @param {Specificity} a
* @param {Specificity} b
* @return {number}
*/
export function compareSpecificity(a: Specificity, b: Specificity): number;
/**
* @param {Specificity} a
* @param {Specificity} b
* @return {boolean}
*/
export function equalSpecificity(a: Specificity, b: Specificity): boolean;
/**
* @param {Token[]} tokens
* @return {string}
*/
export function joinTokens(tokens: Token[]): string;
//# sourceMappingURL=foldToIsHelpers.d.ts.map
{"version":3,"file":"foldToIsHelpers.d.ts","sourceRoot":"","sources":["../../src/lib/foldToIsHelpers.js"],"names":[],"mappings":";UAIc,UAAU,GAAC,YAAY;SACvB,MAAM;;;0BAIN,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;AAPtC;;;;;GAKG;AAEH,sDAAsD;AAEtD;;;GAGG;AACH,mCAHW,OAAO,yBAAyB,EAAE,QAAQ,GACzC,KAAK,EAAE,CA6BlB;AAED;;;GAGG;AACH,iDAHW,KAAK,GACJ,OAAO,CA0BlB;AAED;;;GAGG;AACH,2CAHW,KAAK,GACJ,OAAO,CAsBlB;AAED;;;GAGG;AACH,qCAHW,OAAO,yBAAyB,EAAE,IAAI,EAAE,GACvC,WAAW,CA0CtB;AAqBD;;;;;;GAMG;AACH,4CAHW,KAAK,EAAE,GACN,WAAW,CAgBtB;AAxCD;;;GAGG;AACH,4CAHW,OAAO,yBAAyB,EAAE,MAAM,GACvC,WAAW,CAetB;AAyBD;;;;GAIG;AACH,sCAJW,WAAW,KACX,WAAW,GACV,MAAM,CAIjB;AAED;;;;GAIG;AACH,oCAJW,WAAW,KACX,WAAW,GACV,OAAO,CAIlB;AAED;;;GAGG;AACH,mCAHW,KAAK,EAAE,GACN,MAAM,CAIjB"}
+3
-3
{
"name": "postcss-minify-selectors",
"version": "7.1.0",
"version": "7.1.1",
"description": "Minify selectors with PostCSS.",

@@ -43,7 +43,7 @@ "main": "src/index.js",

"@types/cssesc": "^3.0.2",
"postcss": "^8.5.10"
"postcss": "^8.5.13"
},
"peerDependencies": {
"postcss": "^8.5.10"
"postcss": "^8.5.13"
}
}
'use strict';
const {
tokenize,
hasPseudoElementOrNesting,
hasNthChildOfClause,
specificityOfMiddle,
equalSpecificity,
joinTokens,
} = require('./foldToIsHelpers.js');
// Folds sibling selectors in a comma-separated list by factoring out a common
// prefix and/or suffix into a shared `:is()` group.
//
// The specificity guard below is what keeps the rewrite safe w.r.t. the
// cascade: `:is(a, b)` takes the max specificity of its arguments, so folding
// selectors whose divergent parts have different specificities would silently
// change which rule wins at match time.
/**
* @typedef {object} Token
* @property {'compound'|'combinator'} kind
* @property {string} str
* @property {import('postcss-selector-parser').Node[]} [nodes]
*/
/**
* @param {import('postcss-selector-parser').Selector} selector
* @return {Token[]}
*/
function tokenize(selector) {
/** @type {Token[]} */
const tokens = [];
/** @type {import('postcss-selector-parser').Node[]} */
let bucket = [];
const flush = () => {
if (bucket.length) {
tokens.push({
kind: 'compound',
str: bucket.map((n) => String(n)).join(''),
nodes: bucket,
});
bucket = [];
}
};
for (const node of selector.nodes) {
if (node.type === 'combinator') {
flush();
tokens.push({ kind: 'combinator', str: String(node) });
} else {
bucket.push(node);
}
}
flush();
return tokens;
}
/**
* Pseudo-elements cannot live inside `:is()`; nesting `&` would change meaning.
*
* @param {Token} token
* @return {boolean}
*/
function hasPseudoElementOrNesting(token) {
if (token.kind !== 'compound' || !token.nodes) {
return false;
}
for (const n of token.nodes) {
if (n.type === 'nesting') {
return true;
}
if (n.type === 'pseudo') {
const v = n.value;
if (v.startsWith('::')) {
return true;
}
if (
v === ':before' ||
v === ':after' ||
v === ':first-letter' ||
v === ':first-line'
) {
return true;
}
}
}
return false;
}
/** @typedef {[number, number, number]} Specificity */
/**
* Specificity contribution of a list of simple-selector nodes, following the
* nested-specificity rules for `:is()`, `:not()`, `:has()`, and `:where()`.
*
* @param {import('postcss-selector-parser').Node[]} nodes
* @return {Specificity}
*/
function specificityOf(nodes) {
let id = 0;
let cls = 0;
let type = 0;
for (const n of nodes) {
if (n.type === 'id') {
id++;
} else if (n.type === 'class' || n.type === 'attribute') {
cls++;
} else if (n.type === 'pseudo') {
const v = n.value;
if (v.startsWith('::')) {
type++;
continue;
}
if (v === ':where') {
continue;
}
if (v === ':is' || v === ':matches' || v === ':not' || v === ':has') {
const s = maxChildSpecificity(n);
id += s[0];
cls += s[1];
type += s[2];
continue;
}
// Legacy single-colon pseudo-elements count as type.
if (
v === ':before' ||
v === ':after' ||
v === ':first-letter' ||
v === ':first-line'
) {
type++;
} else {
cls++;
}
} else if (n.type === 'tag') {
type++;
}
}
return [id, cls, type];
}
/**
* @param {import('postcss-selector-parser').Pseudo} pseudo
* @return {Specificity}
*/
function maxChildSpecificity(pseudo) {
/** @type {Specificity} */
let best = [0, 0, 0];
for (const child of pseudo.nodes) {
if (child.type !== 'selector') {
continue;
}
const s = specificityOf(child.nodes);
if (compareSpecificity(s, best) > 0) {
best = s;
}
}
return best;
}
/**
* @param {Token[]} middle
* @return {Specificity}
*/
function specificityOfMiddle(middle) {
let id = 0;
let cls = 0;
let type = 0;
for (const token of middle) {
if (token.kind !== 'compound' || !token.nodes) {
continue;
}
const s = specificityOf(token.nodes);
id += s[0];
cls += s[1];
type += s[2];
}
return [id, cls, type];
}
/**
* @param {Specificity} a
* @param {Specificity} b
* @return {number}
*/
function compareSpecificity(a, b) {
return a[0] - b[0] || a[1] - b[1] || a[2] - b[2];
}
/**
* @param {Specificity} a
* @param {Specificity} b
* @return {boolean}
*/
function sameSpecificity(a, b) {
return a[0] === b[0] && a[1] === b[1] && a[2] === b[2];
}
/**
* @param {Token[]} tokens
* @return {string}
*/
function joinTokens(tokens) {
return tokens.map((t) => t.str).join('');
}
/**
* Returns the folded selector list string if beneficial, otherwise null.
*
* @param {import('postcss-selector-parser').Root} root

@@ -247,5 +56,2 @@ * @return {string | null}

// Each prefix/suffix boundary must land on a combinator — otherwise
// splicing `:is(...)` in would place it adjacent to a type/class token
// in the prefix/suffix and silently change what the selector matches.
const firstTokens = tokenLists[0];

@@ -274,2 +80,5 @@ while (prefix > 0 && firstTokens[prefix - 1].kind !== 'combinator') {

}
if (hasNthChildOfClause(token)) {
return null;
}
}

@@ -280,3 +89,3 @@ }

for (let i = 1; i < middles.length; i++) {
if (!sameSpecificity(firstSpec, specificityOfMiddle(middles[i]))) {
if (!equalSpecificity(firstSpec, specificityOfMiddle(middles[i]))) {
return null;

@@ -283,0 +92,0 @@ }

export = tryFold;
/**
* Returns the folded selector list string if beneficial, otherwise null.
*
* @param {import('postcss-selector-parser').Root} root

@@ -9,11 +7,2 @@ * @return {string | null}

declare function tryFold(root: import("postcss-selector-parser").Root): string | null;
declare namespace tryFold {
export { Token, Specificity };
}
type Token = {
kind: "compound" | "combinator";
str: string;
nodes?: import("postcss-selector-parser").Node[] | undefined;
};
type Specificity = [number, number, number];
//# sourceMappingURL=foldToIs.d.ts.map

@@ -1,1 +0,1 @@

{"version":3,"file":"foldToIs.d.ts","sourceRoot":"","sources":["../../src/lib/foldToIs.js"],"names":[],"mappings":";AAuMA;;;;;GAKG;AACH,+BAHW,OAAO,yBAAyB,EAAE,IAAI,GACrC,MAAM,GAAG,IAAI,CAsGxB;;;;;UArSa,UAAU,GAAC,YAAY;SACvB,MAAM;;;mBAqEN,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC"}
{"version":3,"file":"foldToIs.d.ts","sourceRoot":"","sources":["../../src/lib/foldToIs.js"],"names":[],"mappings":";AAUA;;;GAGG;AACH,+BAHW,OAAO,yBAAyB,EAAE,IAAI,GACrC,MAAM,GAAG,IAAI,CAsGxB"}