From 74f109c553d23a7d87c38767804241467f804776 Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Tue, 10 Jun 2025 11:05:49 +0200 Subject: [PATCH 1/2] fix #1625 --- plugins/postcss-is-pseudo-class/CHANGELOG.md | 4 + .../postcss-is-pseudo-class/dist/index.cjs | 2 +- .../postcss-is-pseudo-class/dist/index.mjs | 2 +- .../src/split-selectors/complex.ts | 4 +- .../split-selectors/complex/is-in-compound.ts | 4 + .../complex/is-pseudo-in-first-compound.ts | 25 +++-- .../complex/same-preceding-element.ts | 94 +++++++++++++++++++ .../postcss-is-pseudo-class/test/_tape.mjs | 2 +- .../postcss-is-pseudo-class/test/basic.css | 4 + .../test/basic.does-not-exist.expect.css | 4 + .../test/basic.expect.css | 4 + .../basic.oncomplex.no-warning.expect.css | 4 + .../test/basic.oncomplex.warning.expect.css | 4 + .../test/basic.preserve.expect.css | 4 + .../basic.with-cloned-declarations.expect.css | 4 + .../postcss-is-pseudo-class/test/complex.css | 45 +++++++++ .../test/complex.expect.css | 45 +++++++++ .../test/compound-after-complex-is.css | 4 +- .../test/compound-after-complex-is.expect.css | 4 +- 19 files changed, 249 insertions(+), 14 deletions(-) create mode 100644 plugins/postcss-is-pseudo-class/src/split-selectors/complex/same-preceding-element.ts diff --git a/plugins/postcss-is-pseudo-class/CHANGELOG.md b/plugins/postcss-is-pseudo-class/CHANGELOG.md index 5c926a129d..5243c901d1 100644 --- a/plugins/postcss-is-pseudo-class/CHANGELOG.md +++ b/plugins/postcss-is-pseudo-class/CHANGELOG.md @@ -1,5 +1,9 @@ # Changes to PostCSS Is Pseudo Class +### Unreleased (patch) + +- Add support for more complex selector patterns. `.a > :is(.b > .c)` -> `.a.b > .c` + ### 5.0.1 _October 23, 2024_ diff --git a/plugins/postcss-is-pseudo-class/dist/index.cjs b/plugins/postcss-is-pseudo-class/dist/index.cjs index d16df987ab..1fb58864a5 100644 --- a/plugins/postcss-is-pseudo-class/dist/index.cjs +++ b/plugins/postcss-is-pseudo-class/dist/index.cjs @@ -1 +1 @@ -"use strict";var e=require("postcss-selector-parser"),s=require("@csstools/selector-specificity");function alwaysValidSelector(s){const o=e().astSync(s);let t=!0;return o.walk((e=>{if("class"!==e.type&&"comment"!==e.type&&"id"!==e.type&&"root"!==e.type&&"selector"!==e.type&&"string"!==e.type&&"tag"!==e.type&&"universal"!==e.type&&("attribute"!==e.type||e.insensitive)&&("combinator"!==e.type||"+"!==e.value&&">"!==e.value&&"~"!==e.value&&" "!==e.value)&&("pseudo"!==e.type||e.nodes?.length||":hover"!==e.value.toLowerCase()&&":focus"!==e.value.toLowerCase())){if("pseudo"===e.type&&1===e.nodes?.length&&":not"===e.value.toLowerCase()){let s=!0;if(e.nodes[0].walkCombinators((()=>{s=!1})),s)return}return t=!1,!1}})),t}function sortCompoundSelectorsInsideComplexSelector(s){if(!s||!s.nodes||1===s.nodes.length)return;const o=[];let t=[];for(let n=0;n"selector"===e.type&&"selector"===s.type&&e.nodes.length&&s.nodes.length?selectorTypeOrder(e.nodes[0],e.nodes[0].type)-selectorTypeOrder(s.nodes[0],s.nodes[0].type):"selector"===e.type&&e.nodes.length?selectorTypeOrder(e.nodes[0],e.nodes[0].type)-selectorTypeOrder(s,s.type):"selector"===s.type&&s.nodes.length?selectorTypeOrder(e,e.type)-selectorTypeOrder(s.nodes[0],s.nodes[0].type):selectorTypeOrder(e,e.type)-selectorTypeOrder(s,s.type)));const t=new Set(s.map((e=>e.type))),r=t.has("universal")&&(t.has("tag")||t.has("attribute")||t.has("class")||t.has("id")||t.has("pseudo"));for(let e=0;e=0;o--){const t=n[o-1];if(n[o].remove(),t&&"tag"===t.type&&"tag"===n[o].type){const t=e.pseudo({value:":is",nodes:[e.selector({value:"",nodes:[n[o]]})]});t.parent=s,s.nodes.unshift(t)}else n[o].parent=s,s.nodes.unshift(n[o])}}function selectorTypeOrder(s,t){return e.isPseudoElement(s)?o.pseudoElement:o[t]}const o={universal:0,tag:1,pseudoElement:2,id:3,class:4,attribute:5,pseudo:6,selector:7,string:8,root:9,comment:10};function childAdjacentChild(e){return!(!e||!e.nodes)&&("selector"===e.type&&(3===e.nodes.length&&(!(!e.nodes[0]||"pseudo"!==e.nodes[0].type||":-csstools-matches"!==e.nodes[0].value)&&(!(!e.nodes[1]||"combinator"!==e.nodes[1].type||"+"!==e.nodes[1].value&&"~"!==e.nodes[1].value)&&(!(!e.nodes[2]||"pseudo"!==e.nodes[2].type||":-csstools-matches"!==e.nodes[2].value)&&(!(!e.nodes[0].nodes||1!==e.nodes[0].nodes.length)&&("selector"===e.nodes[0].nodes[0].type&&(!(!e.nodes[0].nodes[0].nodes||3!==e.nodes[0].nodes[0].nodes.length)&&(!(!e.nodes[0].nodes[0].nodes||"combinator"!==e.nodes[0].nodes[0].nodes[1].type||">"!==e.nodes[0].nodes[0].nodes[1].value)&&(!(!e.nodes[2].nodes||1!==e.nodes[2].nodes.length)&&("selector"===e.nodes[2].nodes[0].type&&(!(!e.nodes[2].nodes[0].nodes||3!==e.nodes[2].nodes[0].nodes.length)&&(!(!e.nodes[2].nodes[0].nodes||"combinator"!==e.nodes[2].nodes[0].nodes[1].type||">"!==e.nodes[2].nodes[0].nodes[1].value)&&(e.nodes[0].nodes[0].insertAfter(e.nodes[0].nodes[0].nodes[0],e.nodes[2].nodes[0].nodes[0].clone()),e.nodes[2].nodes[0].nodes[1].remove(),e.nodes[2].nodes[0].nodes[0].remove(),e.nodes[0].replaceWith(e.nodes[0].nodes[0]),e.nodes[2].replaceWith(e.nodes[2].nodes[0]),!0))))))))))))))}function isInCompoundWithOneOtherElement(s){if(!s||!s.nodes)return!1;if(!e.isSelector(s))return!1;if(2!==s.nodes.length)return!1;let o=-1,t=-1;s.nodes[0]&&e.isPseudoClass(s.nodes[0])&&":-csstools-matches"===s.nodes[0].value?(o=0,t=1):s.nodes[1]&&e.isPseudoClass(s.nodes[1])&&":-csstools-matches"===s.nodes[1].value&&(o=1,t=0);const n=s.nodes[o];if(!n||!e.isPseudoClass(n)||1!==n.nodes.length)return!1;const r=s.nodes[t];return!!r&&(!e.isCombinator(r)&&(n.nodes[0].append(r.clone()),n.replaceWith(...n.nodes[0].nodes),r.remove(),!0))}function isPseudoInFirstCompound(s){if(!s||!s.nodes)return!1;if(!e.isSelector(s))return!1;let o=-1;for(let t=0;t{t.nodes[0].append(e.clone())})),r.forEach((e=>{t.nodes[0].append(e.clone())})),t.replaceWith(...t.nodes),n.forEach((e=>{e.remove()})),r.forEach((e=>{e.remove()})),!0}function complexSelectors(s,o,t,n){return s.flatMap((s=>{if(-1===s.indexOf(":-csstools-matches")&&-1===s.toLowerCase().indexOf(":is"))return s;const r=e().astSync(s);return r.walkPseudos((s=>{if(":is"===s.value.toLowerCase()&&s.nodes&&s.nodes.length&&"selector"===s.nodes[0].type&&0===s.nodes[0].nodes.length)return s.value=":not",void s.nodes[0].append(e.universal());if(":-csstools-matches"===s.value)if(!s.nodes||s.nodes.length){if(s.walkPseudos((s=>{if(e.isPseudoElement(s)){let e=s.value;if(e.startsWith("::-csstools-invalid-"))return;for(;e.startsWith(":");)e=e.slice(1);s.value=`::-csstools-invalid-${e}`,n()}})),1===s.nodes.length&&"selector"===s.nodes[0].type){if(1===s.nodes[0].nodes.length)return void s.replaceWith(s.nodes[0].nodes[0]);if(!s.nodes[0].some((e=>"combinator"===e.type)))return void s.replaceWith(...s.nodes[0].nodes)}1!==r.nodes.length||"selector"!==r.nodes[0].type||1!==r.nodes[0].nodes.length||r.nodes[0].nodes[0]!==s?childAdjacentChild(s.parent)||isInCompoundWithOneOtherElement(s.parent)||isPseudoInFirstCompound(s.parent)||("warning"===o.onComplexSelector&&t(),s.value=":is"):s.replaceWith(...s.nodes[0].nodes)}else s.remove()})),r.walk((e=>{"selector"===e.type&&"nodes"in e&&1===e.nodes.length&&"selector"===e.nodes[0].type&&e.replaceWith(e.nodes[0])})),r.walk((e=>{"nodes"in e&&sortCompoundSelectorsInsideComplexSelector(e)})),r.toString()})).filter((e=>!!e))}function splitSelectors(o,t,n=0){const r=":not(#"+t.specificityMatchingName+")",d=":not(."+t.specificityMatchingName+")",l=":not("+t.specificityMatchingName+")";return o.flatMap((o=>{if(-1===o.toLowerCase().indexOf(":is"))return o;let i=!1;const a=[];if(e().astSync(o).walkPseudos((e=>{if(":is"!==e.value.toLowerCase()||!e.nodes||!e.nodes.length)return;if("selector"===e.nodes[0].type&&0===e.nodes[0].nodes.length)return;if("pseudo"===e.parent?.parent?.type&&":not"===e.parent?.parent?.value?.toLowerCase())return void a.push([{start:e.parent.parent.sourceIndex,end:e.parent.parent.sourceIndex+e.parent.parent.toString().length,option:`:not(${e.nodes.toString()})`}]);if("pseudo"===e.parent?.parent?.type&&":has"===e.parent?.parent?.value?.toLowerCase())return void(e.value=":-csstools-matches");let o=e.parent;for(;o;){if(o.value&&":is"===o.value.toLowerCase()&&"pseudo"===o.type)return void(i=!0);o=o.parent}const t=s.selectorSpecificity(e),n=e.sourceIndex,c=n+e.toString().length,p=[];e.nodes.forEach((e=>{const o={start:n,end:c,option:""},i=s.selectorSpecificity(e);let a=e.toString().trim();const u=Math.max(0,t.a-i.a),h=Math.max(0,t.b-i.b),f=Math.max(0,t.c-i.c);for(let e=0;e{let s="";for(let t=0;t!!e))}function cartesianProduct(...e){const s=[],o=e.length-1;return function helper(t,n){for(let r=0,d=e[n].length;r{const s={specificityMatchingName:"does-not-exist",...e||{}};return{postcssPlugin:"postcss-is-pseudo-class",prepare(){const e=new WeakSet;return{postcssPlugin:"postcss-is-pseudo-class",Rule(o,{result:n}){if(!o.selector)return;if(!t.test(o.selector))return;if(e.has(o))return;let r=!1;const warnOnComplexSelector=()=>{"warning"===s.onComplexSelector&&(r||(r=!0,o.warn(n,`Complex selectors in '${o.selector}' can not be transformed to an equivalent selector without ':is()'.`)))};let d=!1;const warnOnPseudoElements=()=>{"warning"===s.onPseudoElement&&(d||(d=!0,o.warn(n,`Pseudo elements are not allowed in ':is()', unable to transform '${o.selector}'`)))};try{let t=!1;const n=[],r=complexSelectors(splitSelectors(o.selectors,{specificityMatchingName:s.specificityMatchingName}),{onComplexSelector:s.onComplexSelector},warnOnComplexSelector,warnOnPseudoElements);if(Array.from(new Set(r)).forEach((s=>{if(o.selectors.indexOf(s)>-1)n.push(s);else{if(alwaysValidSelector(s))return n.push(s),void(t=!0);e.add(o),o.cloneBefore({selector:s}),t=!0}})),n.length&&t&&(e.add(o),o.cloneBefore({selectors:n})),!s.preserve){if(!t)return;o.remove()}}catch(e){if(!(e instanceof Error))throw e;if(e.message.indexOf("call stack size exceeded")>-1)throw e;o.warn(n,`Failed to parse selector "${o.selector}" with error: ${e.message}`)}}}}}};creator.postcss=!0,module.exports=creator; +"use strict";var e=require("postcss-selector-parser"),s=require("@csstools/selector-specificity");function alwaysValidSelector(s){const o=e().astSync(s);let n=!0;return o.walk((e=>{if("class"!==e.type&&"comment"!==e.type&&"id"!==e.type&&"root"!==e.type&&"selector"!==e.type&&"string"!==e.type&&"tag"!==e.type&&"universal"!==e.type&&("attribute"!==e.type||e.insensitive)&&("combinator"!==e.type||"+"!==e.value&&">"!==e.value&&"~"!==e.value&&" "!==e.value)&&("pseudo"!==e.type||e.nodes?.length||":hover"!==e.value.toLowerCase()&&":focus"!==e.value.toLowerCase())){if("pseudo"===e.type&&1===e.nodes?.length&&":not"===e.value.toLowerCase()){let s=!0;if(e.nodes[0].walkCombinators((()=>{s=!1})),s)return}return n=!1,!1}})),n}function sortCompoundSelectorsInsideComplexSelector(s){if(!s||!s.nodes||1===s.nodes.length)return;const o=[];let n=[];for(let t=0;t"selector"===e.type&&"selector"===s.type&&e.nodes.length&&s.nodes.length?selectorTypeOrder(e.nodes[0],e.nodes[0].type)-selectorTypeOrder(s.nodes[0],s.nodes[0].type):"selector"===e.type&&e.nodes.length?selectorTypeOrder(e.nodes[0],e.nodes[0].type)-selectorTypeOrder(s,s.type):"selector"===s.type&&s.nodes.length?selectorTypeOrder(e,e.type)-selectorTypeOrder(s.nodes[0],s.nodes[0].type):selectorTypeOrder(e,e.type)-selectorTypeOrder(s,s.type)));const n=new Set(s.map((e=>e.type))),r=n.has("universal")&&(n.has("tag")||n.has("attribute")||n.has("class")||n.has("id")||n.has("pseudo"));for(let e=0;e=0;o--){const n=t[o-1];if(t[o].remove(),n&&"tag"===n.type&&"tag"===t[o].type){const n=e.pseudo({value:":is",nodes:[e.selector({value:"",nodes:[t[o]]})]});n.parent=s,s.nodes.unshift(n)}else t[o].parent=s,s.nodes.unshift(t[o])}}function selectorTypeOrder(s,n){return e.isPseudoElement(s)?o.pseudoElement:o[n]}const o={universal:0,tag:1,pseudoElement:2,id:3,class:4,attribute:5,pseudo:6,selector:7,string:8,root:9,comment:10};function childAdjacentChild(e){return!(!e||!e.nodes)&&("selector"===e.type&&(3===e.nodes.length&&(!(!e.nodes[0]||"pseudo"!==e.nodes[0].type||":-csstools-matches"!==e.nodes[0].value)&&(!(!e.nodes[1]||"combinator"!==e.nodes[1].type||"+"!==e.nodes[1].value&&"~"!==e.nodes[1].value)&&(!(!e.nodes[2]||"pseudo"!==e.nodes[2].type||":-csstools-matches"!==e.nodes[2].value)&&(!(!e.nodes[0].nodes||1!==e.nodes[0].nodes.length)&&("selector"===e.nodes[0].nodes[0].type&&(!(!e.nodes[0].nodes[0].nodes||3!==e.nodes[0].nodes[0].nodes.length)&&(!(!e.nodes[0].nodes[0].nodes||"combinator"!==e.nodes[0].nodes[0].nodes[1].type||">"!==e.nodes[0].nodes[0].nodes[1].value)&&(!(!e.nodes[2].nodes||1!==e.nodes[2].nodes.length)&&("selector"===e.nodes[2].nodes[0].type&&(!(!e.nodes[2].nodes[0].nodes||3!==e.nodes[2].nodes[0].nodes.length)&&(!(!e.nodes[2].nodes[0].nodes||"combinator"!==e.nodes[2].nodes[0].nodes[1].type||">"!==e.nodes[2].nodes[0].nodes[1].value)&&(e.nodes[0].nodes[0].insertAfter(e.nodes[0].nodes[0].nodes[0],e.nodes[2].nodes[0].nodes[0].clone()),e.nodes[2].nodes[0].nodes[1].remove(),e.nodes[2].nodes[0].nodes[0].remove(),e.nodes[0].replaceWith(e.nodes[0].nodes[0]),e.nodes[2].replaceWith(e.nodes[2].nodes[0]),!0))))))))))))))}function isInCompoundWithOneOtherElement(s){if(!s||!s.nodes)return!1;if(!e.isSelector(s))return!1;if(2!==s.nodes.length)return!1;let o=-1,n=-1;s.nodes[0]&&e.isPseudoClass(s.nodes[0])&&":-csstools-matches"===s.nodes[0].value?(o=0,n=1):s.nodes[1]&&e.isPseudoClass(s.nodes[1])&&":-csstools-matches"===s.nodes[1].value&&(o=1,n=0);const t=s.nodes[o];if(!t||!e.isPseudoClass(t)||1!==t.nodes.length)return!1;const r=s.nodes[n];return!!r&&(!e.isCombinator(r)&&(!e.isPseudoElement(r)&&(t.nodes[0].append(r.clone()),t.replaceWith(...t.nodes[0].nodes),r.remove(),!0)))}function isPseudoInFirstCompound(s){if(!s||!s.nodes)return!1;if(!e.isSelector(s))return!1;let o=-1;for(let n=0;n{n.nodes[0].append(e.clone())})),r.forEach((e=>{n.nodes[0].append(e.clone())})),n.replaceWith(...n.nodes),t.forEach((e=>{e.remove()})),r.forEach((e=>{e.remove()})),!0}function samePrecedingElement(s){if(!s||!s.nodes)return!1;if("selector"!==s.type)return!1;let o=-1;for(let n=0;n"!==s.nodes[o].value&&"+"!==s.nodes[o].value)return!1;const t=s.nodes[o].value;if(!s.nodes[n]||"pseudo"!==s.nodes[n].type||":-csstools-matches"!==s.nodes[n].value)return!1;if(!s.nodes[n].nodes||1!==s.nodes[n].nodes.length)return!1;if("selector"!==s.nodes[n].nodes[0].type)return!1;if(!s.nodes[n].nodes[0].nodes||3!==s.nodes[n].nodes[0].nodes.length)return!1;if(!s.nodes[n].nodes[0].nodes||"combinator"!==s.nodes[n].nodes[0].nodes[1].type||s.nodes[n].nodes[0].nodes[1].value!==t)return!1;const r=s.nodes[n];if(!r||!e.isPseudoClass(r))return!1;const d=s.nodes.slice(0,o);return s.each((e=>{e.remove()})),d.forEach((e=>{s.append(e)})),r.nodes[0].nodes.forEach((e=>{s.append(e)})),!0}function complexSelectors(s,o,n,t){return s.flatMap((s=>{if(-1===s.indexOf(":-csstools-matches")&&-1===s.toLowerCase().indexOf(":is"))return s;const r=e().astSync(s);return r.walkPseudos((s=>{if(":is"===s.value.toLowerCase()&&s.nodes&&s.nodes.length&&"selector"===s.nodes[0].type&&0===s.nodes[0].nodes.length)return s.value=":not",void s.nodes[0].append(e.universal());if(":-csstools-matches"===s.value)if(!s.nodes||s.nodes.length){if(s.walkPseudos((s=>{if(e.isPseudoElement(s)){let e=s.value;if(e.startsWith("::-csstools-invalid-"))return;for(;e.startsWith(":");)e=e.slice(1);s.value=`::-csstools-invalid-${e}`,t()}})),1===s.nodes.length&&"selector"===s.nodes[0].type){if(1===s.nodes[0].nodes.length)return void s.replaceWith(s.nodes[0].nodes[0]);if(!s.nodes[0].some((e=>"combinator"===e.type)))return void s.replaceWith(...s.nodes[0].nodes)}1!==r.nodes.length||"selector"!==r.nodes[0].type||1!==r.nodes[0].nodes.length||r.nodes[0].nodes[0]!==s?childAdjacentChild(s.parent)||isInCompoundWithOneOtherElement(s.parent)||isPseudoInFirstCompound(s.parent)||samePrecedingElement(s.parent)||("warning"===o.onComplexSelector&&n(),s.value=":is"):s.replaceWith(...s.nodes[0].nodes)}else s.remove()})),r.walk((e=>{"selector"===e.type&&"nodes"in e&&1===e.nodes.length&&"selector"===e.nodes[0].type&&e.replaceWith(e.nodes[0])})),r.walk((e=>{"nodes"in e&&sortCompoundSelectorsInsideComplexSelector(e)})),r.toString()})).filter((e=>!!e))}function splitSelectors(o,n,t=0){const r=":not(#"+n.specificityMatchingName+")",d=":not(."+n.specificityMatchingName+")",l=":not("+n.specificityMatchingName+")";return o.flatMap((o=>{if(-1===o.toLowerCase().indexOf(":is"))return o;let i=!1;const a=[];if(e().astSync(o).walkPseudos((e=>{if(":is"!==e.value.toLowerCase()||!e.nodes||!e.nodes.length)return;if("selector"===e.nodes[0].type&&0===e.nodes[0].nodes.length)return;if("pseudo"===e.parent?.parent?.type&&":not"===e.parent?.parent?.value?.toLowerCase())return void a.push([{start:e.parent.parent.sourceIndex,end:e.parent.parent.sourceIndex+e.parent.parent.toString().length,option:`:not(${e.nodes.toString()})`}]);if("pseudo"===e.parent?.parent?.type&&":has"===e.parent?.parent?.value?.toLowerCase())return void(e.value=":-csstools-matches");let o=e.parent;for(;o;){if(o.value&&":is"===o.value.toLowerCase()&&"pseudo"===o.type)return void(i=!0);o=o.parent}const n=s.selectorSpecificity(e),t=e.sourceIndex,c=t+e.toString().length,u=[];e.nodes.forEach((e=>{const o={start:t,end:c,option:""},i=s.selectorSpecificity(e);let a=e.toString().trim();const p=Math.max(0,n.a-i.a),h=Math.max(0,n.b-i.b),f=Math.max(0,n.c-i.c);for(let e=0;e{let s="";for(let n=0;n!!e))}function cartesianProduct(...e){const s=[],o=e.length-1;return function helper(n,t){for(let r=0,d=e[t].length;r{const s={specificityMatchingName:"does-not-exist",...e||{}};return{postcssPlugin:"postcss-is-pseudo-class",prepare(){const e=new WeakSet;return{postcssPlugin:"postcss-is-pseudo-class",Rule(o,{result:t}){if(!o.selector)return;if(!n.test(o.selector))return;if(e.has(o))return;let r=!1;const warnOnComplexSelector=()=>{"warning"===s.onComplexSelector&&(r||(r=!0,o.warn(t,`Complex selectors in '${o.selector}' can not be transformed to an equivalent selector without ':is()'.`)))};let d=!1;const warnOnPseudoElements=()=>{"warning"===s.onPseudoElement&&(d||(d=!0,o.warn(t,`Pseudo elements are not allowed in ':is()', unable to transform '${o.selector}'`)))};try{let n=!1;const t=[],r=complexSelectors(splitSelectors(o.selectors,{specificityMatchingName:s.specificityMatchingName}),{onComplexSelector:s.onComplexSelector},warnOnComplexSelector,warnOnPseudoElements);if(Array.from(new Set(r)).forEach((s=>{if(o.selectors.indexOf(s)>-1)t.push(s);else{if(alwaysValidSelector(s))return t.push(s),void(n=!0);e.add(o),o.cloneBefore({selector:s}),n=!0}})),t.length&&n&&(e.add(o),o.cloneBefore({selectors:t})),!s.preserve){if(!n)return;o.remove()}}catch(e){if(!(e instanceof Error))throw e;if(e.message.indexOf("call stack size exceeded")>-1)throw e;o.warn(t,`Failed to parse selector "${o.selector}" with error: ${e.message}`)}}}}}};creator.postcss=!0,module.exports=creator; diff --git a/plugins/postcss-is-pseudo-class/dist/index.mjs b/plugins/postcss-is-pseudo-class/dist/index.mjs index 38fe2171c8..f54924f96e 100644 --- a/plugins/postcss-is-pseudo-class/dist/index.mjs +++ b/plugins/postcss-is-pseudo-class/dist/index.mjs @@ -1 +1 @@ -import e from"postcss-selector-parser";import{selectorSpecificity as s}from"@csstools/selector-specificity";function alwaysValidSelector(s){const o=e().astSync(s);let t=!0;return o.walk((e=>{if("class"!==e.type&&"comment"!==e.type&&"id"!==e.type&&"root"!==e.type&&"selector"!==e.type&&"string"!==e.type&&"tag"!==e.type&&"universal"!==e.type&&("attribute"!==e.type||e.insensitive)&&("combinator"!==e.type||"+"!==e.value&&">"!==e.value&&"~"!==e.value&&" "!==e.value)&&("pseudo"!==e.type||e.nodes?.length||":hover"!==e.value.toLowerCase()&&":focus"!==e.value.toLowerCase())){if("pseudo"===e.type&&1===e.nodes?.length&&":not"===e.value.toLowerCase()){let s=!0;if(e.nodes[0].walkCombinators((()=>{s=!1})),s)return}return t=!1,!1}})),t}function sortCompoundSelectorsInsideComplexSelector(s){if(!s||!s.nodes||1===s.nodes.length)return;const o=[];let t=[];for(let n=0;n"selector"===e.type&&"selector"===s.type&&e.nodes.length&&s.nodes.length?selectorTypeOrder(e.nodes[0],e.nodes[0].type)-selectorTypeOrder(s.nodes[0],s.nodes[0].type):"selector"===e.type&&e.nodes.length?selectorTypeOrder(e.nodes[0],e.nodes[0].type)-selectorTypeOrder(s,s.type):"selector"===s.type&&s.nodes.length?selectorTypeOrder(e,e.type)-selectorTypeOrder(s.nodes[0],s.nodes[0].type):selectorTypeOrder(e,e.type)-selectorTypeOrder(s,s.type)));const t=new Set(s.map((e=>e.type))),r=t.has("universal")&&(t.has("tag")||t.has("attribute")||t.has("class")||t.has("id")||t.has("pseudo"));for(let e=0;e=0;o--){const t=n[o-1];if(n[o].remove(),t&&"tag"===t.type&&"tag"===n[o].type){const t=e.pseudo({value:":is",nodes:[e.selector({value:"",nodes:[n[o]]})]});t.parent=s,s.nodes.unshift(t)}else n[o].parent=s,s.nodes.unshift(n[o])}}function selectorTypeOrder(s,t){return e.isPseudoElement(s)?o.pseudoElement:o[t]}const o={universal:0,tag:1,pseudoElement:2,id:3,class:4,attribute:5,pseudo:6,selector:7,string:8,root:9,comment:10};function childAdjacentChild(e){return!(!e||!e.nodes)&&("selector"===e.type&&(3===e.nodes.length&&(!(!e.nodes[0]||"pseudo"!==e.nodes[0].type||":-csstools-matches"!==e.nodes[0].value)&&(!(!e.nodes[1]||"combinator"!==e.nodes[1].type||"+"!==e.nodes[1].value&&"~"!==e.nodes[1].value)&&(!(!e.nodes[2]||"pseudo"!==e.nodes[2].type||":-csstools-matches"!==e.nodes[2].value)&&(!(!e.nodes[0].nodes||1!==e.nodes[0].nodes.length)&&("selector"===e.nodes[0].nodes[0].type&&(!(!e.nodes[0].nodes[0].nodes||3!==e.nodes[0].nodes[0].nodes.length)&&(!(!e.nodes[0].nodes[0].nodes||"combinator"!==e.nodes[0].nodes[0].nodes[1].type||">"!==e.nodes[0].nodes[0].nodes[1].value)&&(!(!e.nodes[2].nodes||1!==e.nodes[2].nodes.length)&&("selector"===e.nodes[2].nodes[0].type&&(!(!e.nodes[2].nodes[0].nodes||3!==e.nodes[2].nodes[0].nodes.length)&&(!(!e.nodes[2].nodes[0].nodes||"combinator"!==e.nodes[2].nodes[0].nodes[1].type||">"!==e.nodes[2].nodes[0].nodes[1].value)&&(e.nodes[0].nodes[0].insertAfter(e.nodes[0].nodes[0].nodes[0],e.nodes[2].nodes[0].nodes[0].clone()),e.nodes[2].nodes[0].nodes[1].remove(),e.nodes[2].nodes[0].nodes[0].remove(),e.nodes[0].replaceWith(e.nodes[0].nodes[0]),e.nodes[2].replaceWith(e.nodes[2].nodes[0]),!0))))))))))))))}function isInCompoundWithOneOtherElement(s){if(!s||!s.nodes)return!1;if(!e.isSelector(s))return!1;if(2!==s.nodes.length)return!1;let o=-1,t=-1;s.nodes[0]&&e.isPseudoClass(s.nodes[0])&&":-csstools-matches"===s.nodes[0].value?(o=0,t=1):s.nodes[1]&&e.isPseudoClass(s.nodes[1])&&":-csstools-matches"===s.nodes[1].value&&(o=1,t=0);const n=s.nodes[o];if(!n||!e.isPseudoClass(n)||1!==n.nodes.length)return!1;const r=s.nodes[t];return!!r&&(!e.isCombinator(r)&&(n.nodes[0].append(r.clone()),n.replaceWith(...n.nodes[0].nodes),r.remove(),!0))}function isPseudoInFirstCompound(s){if(!s||!s.nodes)return!1;if(!e.isSelector(s))return!1;let o=-1;for(let t=0;t{t.nodes[0].append(e.clone())})),r.forEach((e=>{t.nodes[0].append(e.clone())})),t.replaceWith(...t.nodes),n.forEach((e=>{e.remove()})),r.forEach((e=>{e.remove()})),!0}function complexSelectors(s,o,t,n){return s.flatMap((s=>{if(-1===s.indexOf(":-csstools-matches")&&-1===s.toLowerCase().indexOf(":is"))return s;const r=e().astSync(s);return r.walkPseudos((s=>{if(":is"===s.value.toLowerCase()&&s.nodes&&s.nodes.length&&"selector"===s.nodes[0].type&&0===s.nodes[0].nodes.length)return s.value=":not",void s.nodes[0].append(e.universal());if(":-csstools-matches"===s.value)if(!s.nodes||s.nodes.length){if(s.walkPseudos((s=>{if(e.isPseudoElement(s)){let e=s.value;if(e.startsWith("::-csstools-invalid-"))return;for(;e.startsWith(":");)e=e.slice(1);s.value=`::-csstools-invalid-${e}`,n()}})),1===s.nodes.length&&"selector"===s.nodes[0].type){if(1===s.nodes[0].nodes.length)return void s.replaceWith(s.nodes[0].nodes[0]);if(!s.nodes[0].some((e=>"combinator"===e.type)))return void s.replaceWith(...s.nodes[0].nodes)}1!==r.nodes.length||"selector"!==r.nodes[0].type||1!==r.nodes[0].nodes.length||r.nodes[0].nodes[0]!==s?childAdjacentChild(s.parent)||isInCompoundWithOneOtherElement(s.parent)||isPseudoInFirstCompound(s.parent)||("warning"===o.onComplexSelector&&t(),s.value=":is"):s.replaceWith(...s.nodes[0].nodes)}else s.remove()})),r.walk((e=>{"selector"===e.type&&"nodes"in e&&1===e.nodes.length&&"selector"===e.nodes[0].type&&e.replaceWith(e.nodes[0])})),r.walk((e=>{"nodes"in e&&sortCompoundSelectorsInsideComplexSelector(e)})),r.toString()})).filter((e=>!!e))}function splitSelectors(o,t,n=0){const r=":not(#"+t.specificityMatchingName+")",d=":not(."+t.specificityMatchingName+")",l=":not("+t.specificityMatchingName+")";return o.flatMap((o=>{if(-1===o.toLowerCase().indexOf(":is"))return o;let a=!1;const i=[];if(e().astSync(o).walkPseudos((e=>{if(":is"!==e.value.toLowerCase()||!e.nodes||!e.nodes.length)return;if("selector"===e.nodes[0].type&&0===e.nodes[0].nodes.length)return;if("pseudo"===e.parent?.parent?.type&&":not"===e.parent?.parent?.value?.toLowerCase())return void i.push([{start:e.parent.parent.sourceIndex,end:e.parent.parent.sourceIndex+e.parent.parent.toString().length,option:`:not(${e.nodes.toString()})`}]);if("pseudo"===e.parent?.parent?.type&&":has"===e.parent?.parent?.value?.toLowerCase())return void(e.value=":-csstools-matches");let o=e.parent;for(;o;){if(o.value&&":is"===o.value.toLowerCase()&&"pseudo"===o.type)return void(a=!0);o=o.parent}const t=s(e),n=e.sourceIndex,c=n+e.toString().length,p=[];e.nodes.forEach((e=>{const o={start:n,end:c,option:""},a=s(e);let i=e.toString().trim();const u=Math.max(0,t.a-a.a),h=Math.max(0,t.b-a.b),f=Math.max(0,t.c-a.c);for(let e=0;e{let s="";for(let t=0;t!!e))}function cartesianProduct(...e){const s=[],o=e.length-1;return function helper(t,n){for(let r=0,d=e[n].length;r{const s={specificityMatchingName:"does-not-exist",...e||{}};return{postcssPlugin:"postcss-is-pseudo-class",prepare(){const e=new WeakSet;return{postcssPlugin:"postcss-is-pseudo-class",Rule(o,{result:n}){if(!o.selector)return;if(!t.test(o.selector))return;if(e.has(o))return;let r=!1;const warnOnComplexSelector=()=>{"warning"===s.onComplexSelector&&(r||(r=!0,o.warn(n,`Complex selectors in '${o.selector}' can not be transformed to an equivalent selector without ':is()'.`)))};let d=!1;const warnOnPseudoElements=()=>{"warning"===s.onPseudoElement&&(d||(d=!0,o.warn(n,`Pseudo elements are not allowed in ':is()', unable to transform '${o.selector}'`)))};try{let t=!1;const n=[],r=complexSelectors(splitSelectors(o.selectors,{specificityMatchingName:s.specificityMatchingName}),{onComplexSelector:s.onComplexSelector},warnOnComplexSelector,warnOnPseudoElements);if(Array.from(new Set(r)).forEach((s=>{if(o.selectors.indexOf(s)>-1)n.push(s);else{if(alwaysValidSelector(s))return n.push(s),void(t=!0);e.add(o),o.cloneBefore({selector:s}),t=!0}})),n.length&&t&&(e.add(o),o.cloneBefore({selectors:n})),!s.preserve){if(!t)return;o.remove()}}catch(e){if(!(e instanceof Error))throw e;if(e.message.indexOf("call stack size exceeded")>-1)throw e;o.warn(n,`Failed to parse selector "${o.selector}" with error: ${e.message}`)}}}}}};creator.postcss=!0;export{creator as default}; +import e from"postcss-selector-parser";import{selectorSpecificity as s}from"@csstools/selector-specificity";function alwaysValidSelector(s){const o=e().astSync(s);let n=!0;return o.walk((e=>{if("class"!==e.type&&"comment"!==e.type&&"id"!==e.type&&"root"!==e.type&&"selector"!==e.type&&"string"!==e.type&&"tag"!==e.type&&"universal"!==e.type&&("attribute"!==e.type||e.insensitive)&&("combinator"!==e.type||"+"!==e.value&&">"!==e.value&&"~"!==e.value&&" "!==e.value)&&("pseudo"!==e.type||e.nodes?.length||":hover"!==e.value.toLowerCase()&&":focus"!==e.value.toLowerCase())){if("pseudo"===e.type&&1===e.nodes?.length&&":not"===e.value.toLowerCase()){let s=!0;if(e.nodes[0].walkCombinators((()=>{s=!1})),s)return}return n=!1,!1}})),n}function sortCompoundSelectorsInsideComplexSelector(s){if(!s||!s.nodes||1===s.nodes.length)return;const o=[];let n=[];for(let t=0;t"selector"===e.type&&"selector"===s.type&&e.nodes.length&&s.nodes.length?selectorTypeOrder(e.nodes[0],e.nodes[0].type)-selectorTypeOrder(s.nodes[0],s.nodes[0].type):"selector"===e.type&&e.nodes.length?selectorTypeOrder(e.nodes[0],e.nodes[0].type)-selectorTypeOrder(s,s.type):"selector"===s.type&&s.nodes.length?selectorTypeOrder(e,e.type)-selectorTypeOrder(s.nodes[0],s.nodes[0].type):selectorTypeOrder(e,e.type)-selectorTypeOrder(s,s.type)));const n=new Set(s.map((e=>e.type))),r=n.has("universal")&&(n.has("tag")||n.has("attribute")||n.has("class")||n.has("id")||n.has("pseudo"));for(let e=0;e=0;o--){const n=t[o-1];if(t[o].remove(),n&&"tag"===n.type&&"tag"===t[o].type){const n=e.pseudo({value:":is",nodes:[e.selector({value:"",nodes:[t[o]]})]});n.parent=s,s.nodes.unshift(n)}else t[o].parent=s,s.nodes.unshift(t[o])}}function selectorTypeOrder(s,n){return e.isPseudoElement(s)?o.pseudoElement:o[n]}const o={universal:0,tag:1,pseudoElement:2,id:3,class:4,attribute:5,pseudo:6,selector:7,string:8,root:9,comment:10};function childAdjacentChild(e){return!(!e||!e.nodes)&&("selector"===e.type&&(3===e.nodes.length&&(!(!e.nodes[0]||"pseudo"!==e.nodes[0].type||":-csstools-matches"!==e.nodes[0].value)&&(!(!e.nodes[1]||"combinator"!==e.nodes[1].type||"+"!==e.nodes[1].value&&"~"!==e.nodes[1].value)&&(!(!e.nodes[2]||"pseudo"!==e.nodes[2].type||":-csstools-matches"!==e.nodes[2].value)&&(!(!e.nodes[0].nodes||1!==e.nodes[0].nodes.length)&&("selector"===e.nodes[0].nodes[0].type&&(!(!e.nodes[0].nodes[0].nodes||3!==e.nodes[0].nodes[0].nodes.length)&&(!(!e.nodes[0].nodes[0].nodes||"combinator"!==e.nodes[0].nodes[0].nodes[1].type||">"!==e.nodes[0].nodes[0].nodes[1].value)&&(!(!e.nodes[2].nodes||1!==e.nodes[2].nodes.length)&&("selector"===e.nodes[2].nodes[0].type&&(!(!e.nodes[2].nodes[0].nodes||3!==e.nodes[2].nodes[0].nodes.length)&&(!(!e.nodes[2].nodes[0].nodes||"combinator"!==e.nodes[2].nodes[0].nodes[1].type||">"!==e.nodes[2].nodes[0].nodes[1].value)&&(e.nodes[0].nodes[0].insertAfter(e.nodes[0].nodes[0].nodes[0],e.nodes[2].nodes[0].nodes[0].clone()),e.nodes[2].nodes[0].nodes[1].remove(),e.nodes[2].nodes[0].nodes[0].remove(),e.nodes[0].replaceWith(e.nodes[0].nodes[0]),e.nodes[2].replaceWith(e.nodes[2].nodes[0]),!0))))))))))))))}function isInCompoundWithOneOtherElement(s){if(!s||!s.nodes)return!1;if(!e.isSelector(s))return!1;if(2!==s.nodes.length)return!1;let o=-1,n=-1;s.nodes[0]&&e.isPseudoClass(s.nodes[0])&&":-csstools-matches"===s.nodes[0].value?(o=0,n=1):s.nodes[1]&&e.isPseudoClass(s.nodes[1])&&":-csstools-matches"===s.nodes[1].value&&(o=1,n=0);const t=s.nodes[o];if(!t||!e.isPseudoClass(t)||1!==t.nodes.length)return!1;const r=s.nodes[n];return!!r&&(!e.isCombinator(r)&&(!e.isPseudoElement(r)&&(t.nodes[0].append(r.clone()),t.replaceWith(...t.nodes[0].nodes),r.remove(),!0)))}function isPseudoInFirstCompound(s){if(!s||!s.nodes)return!1;if(!e.isSelector(s))return!1;let o=-1;for(let n=0;n{n.nodes[0].append(e.clone())})),r.forEach((e=>{n.nodes[0].append(e.clone())})),n.replaceWith(...n.nodes),t.forEach((e=>{e.remove()})),r.forEach((e=>{e.remove()})),!0}function samePrecedingElement(s){if(!s||!s.nodes)return!1;if("selector"!==s.type)return!1;let o=-1;for(let n=0;n"!==s.nodes[o].value&&"+"!==s.nodes[o].value)return!1;const t=s.nodes[o].value;if(!s.nodes[n]||"pseudo"!==s.nodes[n].type||":-csstools-matches"!==s.nodes[n].value)return!1;if(!s.nodes[n].nodes||1!==s.nodes[n].nodes.length)return!1;if("selector"!==s.nodes[n].nodes[0].type)return!1;if(!s.nodes[n].nodes[0].nodes||3!==s.nodes[n].nodes[0].nodes.length)return!1;if(!s.nodes[n].nodes[0].nodes||"combinator"!==s.nodes[n].nodes[0].nodes[1].type||s.nodes[n].nodes[0].nodes[1].value!==t)return!1;const r=s.nodes[n];if(!r||!e.isPseudoClass(r))return!1;const d=s.nodes.slice(0,o);return s.each((e=>{e.remove()})),d.forEach((e=>{s.append(e)})),r.nodes[0].nodes.forEach((e=>{s.append(e)})),!0}function complexSelectors(s,o,n,t){return s.flatMap((s=>{if(-1===s.indexOf(":-csstools-matches")&&-1===s.toLowerCase().indexOf(":is"))return s;const r=e().astSync(s);return r.walkPseudos((s=>{if(":is"===s.value.toLowerCase()&&s.nodes&&s.nodes.length&&"selector"===s.nodes[0].type&&0===s.nodes[0].nodes.length)return s.value=":not",void s.nodes[0].append(e.universal());if(":-csstools-matches"===s.value)if(!s.nodes||s.nodes.length){if(s.walkPseudos((s=>{if(e.isPseudoElement(s)){let e=s.value;if(e.startsWith("::-csstools-invalid-"))return;for(;e.startsWith(":");)e=e.slice(1);s.value=`::-csstools-invalid-${e}`,t()}})),1===s.nodes.length&&"selector"===s.nodes[0].type){if(1===s.nodes[0].nodes.length)return void s.replaceWith(s.nodes[0].nodes[0]);if(!s.nodes[0].some((e=>"combinator"===e.type)))return void s.replaceWith(...s.nodes[0].nodes)}1!==r.nodes.length||"selector"!==r.nodes[0].type||1!==r.nodes[0].nodes.length||r.nodes[0].nodes[0]!==s?childAdjacentChild(s.parent)||isInCompoundWithOneOtherElement(s.parent)||isPseudoInFirstCompound(s.parent)||samePrecedingElement(s.parent)||("warning"===o.onComplexSelector&&n(),s.value=":is"):s.replaceWith(...s.nodes[0].nodes)}else s.remove()})),r.walk((e=>{"selector"===e.type&&"nodes"in e&&1===e.nodes.length&&"selector"===e.nodes[0].type&&e.replaceWith(e.nodes[0])})),r.walk((e=>{"nodes"in e&&sortCompoundSelectorsInsideComplexSelector(e)})),r.toString()})).filter((e=>!!e))}function splitSelectors(o,n,t=0){const r=":not(#"+n.specificityMatchingName+")",d=":not(."+n.specificityMatchingName+")",l=":not("+n.specificityMatchingName+")";return o.flatMap((o=>{if(-1===o.toLowerCase().indexOf(":is"))return o;let i=!1;const a=[];if(e().astSync(o).walkPseudos((e=>{if(":is"!==e.value.toLowerCase()||!e.nodes||!e.nodes.length)return;if("selector"===e.nodes[0].type&&0===e.nodes[0].nodes.length)return;if("pseudo"===e.parent?.parent?.type&&":not"===e.parent?.parent?.value?.toLowerCase())return void a.push([{start:e.parent.parent.sourceIndex,end:e.parent.parent.sourceIndex+e.parent.parent.toString().length,option:`:not(${e.nodes.toString()})`}]);if("pseudo"===e.parent?.parent?.type&&":has"===e.parent?.parent?.value?.toLowerCase())return void(e.value=":-csstools-matches");let o=e.parent;for(;o;){if(o.value&&":is"===o.value.toLowerCase()&&"pseudo"===o.type)return void(i=!0);o=o.parent}const n=s(e),t=e.sourceIndex,c=t+e.toString().length,u=[];e.nodes.forEach((e=>{const o={start:t,end:c,option:""},i=s(e);let a=e.toString().trim();const p=Math.max(0,n.a-i.a),h=Math.max(0,n.b-i.b),f=Math.max(0,n.c-i.c);for(let e=0;e{let s="";for(let n=0;n!!e))}function cartesianProduct(...e){const s=[],o=e.length-1;return function helper(n,t){for(let r=0,d=e[t].length;r{const s={specificityMatchingName:"does-not-exist",...e||{}};return{postcssPlugin:"postcss-is-pseudo-class",prepare(){const e=new WeakSet;return{postcssPlugin:"postcss-is-pseudo-class",Rule(o,{result:t}){if(!o.selector)return;if(!n.test(o.selector))return;if(e.has(o))return;let r=!1;const warnOnComplexSelector=()=>{"warning"===s.onComplexSelector&&(r||(r=!0,o.warn(t,`Complex selectors in '${o.selector}' can not be transformed to an equivalent selector without ':is()'.`)))};let d=!1;const warnOnPseudoElements=()=>{"warning"===s.onPseudoElement&&(d||(d=!0,o.warn(t,`Pseudo elements are not allowed in ':is()', unable to transform '${o.selector}'`)))};try{let n=!1;const t=[],r=complexSelectors(splitSelectors(o.selectors,{specificityMatchingName:s.specificityMatchingName}),{onComplexSelector:s.onComplexSelector},warnOnComplexSelector,warnOnPseudoElements);if(Array.from(new Set(r)).forEach((s=>{if(o.selectors.indexOf(s)>-1)t.push(s);else{if(alwaysValidSelector(s))return t.push(s),void(n=!0);e.add(o),o.cloneBefore({selector:s}),n=!0}})),t.length&&n&&(e.add(o),o.cloneBefore({selectors:t})),!s.preserve){if(!n)return;o.remove()}}catch(e){if(!(e instanceof Error))throw e;if(e.message.indexOf("call stack size exceeded")>-1)throw e;o.warn(t,`Failed to parse selector "${o.selector}" with error: ${e.message}`)}}}}}};creator.postcss=!0;export{creator as default}; diff --git a/plugins/postcss-is-pseudo-class/src/split-selectors/complex.ts b/plugins/postcss-is-pseudo-class/src/split-selectors/complex.ts index 4a08cbb5ca..9aa2ebdc94 100644 --- a/plugins/postcss-is-pseudo-class/src/split-selectors/complex.ts +++ b/plugins/postcss-is-pseudo-class/src/split-selectors/complex.ts @@ -4,6 +4,7 @@ import { childAdjacentChild } from './complex/child-adjacent-child'; import { isInCompoundWithOneOtherElement } from './complex/is-in-compound'; import type { Container } from 'postcss-selector-parser'; import { isPseudoInFirstCompound } from './complex/is-pseudo-in-first-compound'; +import { samePrecedingElement } from './complex/same-preceding-element'; export default function complexSelectors(selectors: Array, pluginOptions: { onComplexSelector?: 'warning' }, warnOnComplexSelector: () => void, warnOnPseudoElements: () => void): Array { return selectors.flatMap((selector) => { @@ -82,7 +83,8 @@ export default function complexSelectors(selectors: Array, pluginOptions if ( childAdjacentChild(pseudo.parent) || isInCompoundWithOneOtherElement(pseudo.parent) || - isPseudoInFirstCompound(pseudo.parent) + isPseudoInFirstCompound(pseudo.parent) || + samePrecedingElement(pseudo.parent) ) { return; } diff --git a/plugins/postcss-is-pseudo-class/src/split-selectors/complex/is-in-compound.ts b/plugins/postcss-is-pseudo-class/src/split-selectors/complex/is-in-compound.ts index c3a140ff8a..855ef0b116 100644 --- a/plugins/postcss-is-pseudo-class/src/split-selectors/complex/is-in-compound.ts +++ b/plugins/postcss-is-pseudo-class/src/split-selectors/complex/is-in-compound.ts @@ -46,6 +46,10 @@ export function isInCompoundWithOneOtherElement(selector: parser.Container): boo return false; } + if (parser.isPseudoElement(simpleSelector)) { + return false; + } + isPseudo.nodes[0].append(simpleSelector.clone()); isPseudo.replaceWith(...isPseudo.nodes[0].nodes); simpleSelector.remove(); diff --git a/plugins/postcss-is-pseudo-class/src/split-selectors/complex/is-pseudo-in-first-compound.ts b/plugins/postcss-is-pseudo-class/src/split-selectors/complex/is-pseudo-in-first-compound.ts index 68079bd892..a44f463caf 100644 --- a/plugins/postcss-is-pseudo-class/src/split-selectors/complex/is-pseudo-in-first-compound.ts +++ b/plugins/postcss-is-pseudo-class/src/split-selectors/complex/is-pseudo-in-first-compound.ts @@ -2,6 +2,7 @@ // equivalent to // .a > .b > .c +import type { Pseudo } from 'postcss-selector-parser'; import parser from 'postcss-selector-parser'; // because `:is()` is in the left-most compound selector @@ -21,16 +22,28 @@ export function isPseudoInFirstCompound(selector: parser.Container): boolean { return false; } - if (parser.isPseudoClass(node) && node.value === ':-csstools-matches') { - if (!node.nodes || node.nodes.length !== 1) { - return false; - } + if (parser.isPseudoElement(node)) { + return false; + } + + if (parser.isPseudoClass(node)) { + const nn = node as Pseudo; // because `isPseudoElement` is incorrectly typed + + if (nn.value === ':-csstools-matches') { + if (!nn.nodes || nn.nodes.length !== 1) { + return false; + } - isPseudoIndex = i; - break; + isPseudoIndex = i; + break; + } } } + if (isPseudoIndex === -1) { + return false; + } + const isPseudo = selector.nodes[isPseudoIndex]; if (!isPseudo || !parser.isPseudoClass(isPseudo)) { return false; diff --git a/plugins/postcss-is-pseudo-class/src/split-selectors/complex/same-preceding-element.ts b/plugins/postcss-is-pseudo-class/src/split-selectors/complex/same-preceding-element.ts new file mode 100644 index 0000000000..482cb3181e --- /dev/null +++ b/plugins/postcss-is-pseudo-class/src/split-selectors/complex/same-preceding-element.ts @@ -0,0 +1,94 @@ +import type { Pseudo } from 'postcss-selector-parser'; + +import parser from 'postcss-selector-parser'; + +// .a > :-csstools-matches(.b > .c) +// equivalent to +// .a.b > .c +// +// and +// +// .a + :-csstools-matches(.b + .c) +// equivalent to +// .a.b + .c +// +// because adjacent elements have the same parent element. +export function samePrecedingElement(selector: parser.Container): boolean { + if (!selector || !selector.nodes) { + return false; + } + if (selector.type !== 'selector') { + return false; + } + + let combinatorIndex = -1; + for (let i = 0; i < selector.nodes.length; i++) { + const node = selector.nodes[i]; + if (parser.isCombinator(node)) { + combinatorIndex = i; + break; + } + + if (parser.isPseudoElement(node)) { + return false; + } + } + + if (combinatorIndex === -1) { + return false; + } + + const isPseudoIndex = combinatorIndex + 1; + + // immediate sibling/child combinator + if (!selector.nodes[combinatorIndex] || selector.nodes[combinatorIndex].type !== 'combinator' || (selector.nodes[combinatorIndex].value !== '>' && selector.nodes[combinatorIndex].value !== '+')) { + return false; + } + + const combinator = selector.nodes[combinatorIndex].value; + + if (!selector.nodes[isPseudoIndex] || selector.nodes[isPseudoIndex].type !== 'pseudo' || selector.nodes[isPseudoIndex].value !== ':-csstools-matches') { + return false; + } + + // second `:-csstools-matches` + { + if (!selector.nodes[isPseudoIndex].nodes || selector.nodes[isPseudoIndex].nodes.length !== 1) { + return false; + } + + if (selector.nodes[isPseudoIndex].nodes[0].type !== 'selector') { + return false; + } + + if (!selector.nodes[isPseudoIndex].nodes[0].nodes || selector.nodes[isPseudoIndex].nodes[0].nodes.length !== 3) { + return false; + } + + // same combinator + if (!selector.nodes[isPseudoIndex].nodes[0].nodes || selector.nodes[isPseudoIndex].nodes[0].nodes[1].type !== 'combinator' || selector.nodes[isPseudoIndex].nodes[0].nodes[1].value !== combinator) { + return false; + } + } + + const isPseudo = selector.nodes[isPseudoIndex]; + if (!isPseudo || !parser.isPseudoClass(isPseudo)) { + return false; + } + + const before = selector.nodes.slice(0, combinatorIndex); + + selector.each((node) => { + node.remove(); + }); + + before.forEach((node) => { + selector.append(node); + }); + + isPseudo.nodes[0].nodes.forEach((node) => { + selector.append(node); + }); + + return true; +} diff --git a/plugins/postcss-is-pseudo-class/test/_tape.mjs b/plugins/postcss-is-pseudo-class/test/_tape.mjs index 649a4054f2..71e022a2bb 100644 --- a/plugins/postcss-is-pseudo-class/test/_tape.mjs +++ b/plugins/postcss-is-pseudo-class/test/_tape.mjs @@ -19,7 +19,7 @@ postcssTape(plugin)({ }, 'basic:oncomplex:warning': { message: 'warns on complex selectors', - warnings: 9, + warnings: 10, options: { onComplexSelector: 'warning', }, diff --git a/plugins/postcss-is-pseudo-class/test/basic.css b/plugins/postcss-is-pseudo-class/test/basic.css index 0465402858..2fbbf960c8 100644 --- a/plugins/postcss-is-pseudo-class/test/basic.css +++ b/plugins/postcss-is-pseudo-class/test/basic.css @@ -181,3 +181,7 @@ header:is(.a .b) { div:is(.a > .b) > .c { color: green; } + +::before:is(.a > .b) { + color: green; +} diff --git a/plugins/postcss-is-pseudo-class/test/basic.does-not-exist.expect.css b/plugins/postcss-is-pseudo-class/test/basic.does-not-exist.expect.css index 4ae83af426..8e8b7f9a68 100644 --- a/plugins/postcss-is-pseudo-class/test/basic.does-not-exist.expect.css +++ b/plugins/postcss-is-pseudo-class/test/basic.does-not-exist.expect.css @@ -285,3 +285,7 @@ input:hover, input:focus, button:hover, button:focus { .a > div.b > .c { color: green; } + +::before:is(.a > .b) { + color: green; +} diff --git a/plugins/postcss-is-pseudo-class/test/basic.expect.css b/plugins/postcss-is-pseudo-class/test/basic.expect.css index bbb24b876f..b18c90800e 100644 --- a/plugins/postcss-is-pseudo-class/test/basic.expect.css +++ b/plugins/postcss-is-pseudo-class/test/basic.expect.css @@ -285,3 +285,7 @@ input:hover, input:focus, button:hover, button:focus { .a > div.b > .c { color: green; } + +::before:is(.a > .b) { + color: green; +} diff --git a/plugins/postcss-is-pseudo-class/test/basic.oncomplex.no-warning.expect.css b/plugins/postcss-is-pseudo-class/test/basic.oncomplex.no-warning.expect.css index bbb24b876f..b18c90800e 100644 --- a/plugins/postcss-is-pseudo-class/test/basic.oncomplex.no-warning.expect.css +++ b/plugins/postcss-is-pseudo-class/test/basic.oncomplex.no-warning.expect.css @@ -285,3 +285,7 @@ input:hover, input:focus, button:hover, button:focus { .a > div.b > .c { color: green; } + +::before:is(.a > .b) { + color: green; +} diff --git a/plugins/postcss-is-pseudo-class/test/basic.oncomplex.warning.expect.css b/plugins/postcss-is-pseudo-class/test/basic.oncomplex.warning.expect.css index bbb24b876f..b18c90800e 100644 --- a/plugins/postcss-is-pseudo-class/test/basic.oncomplex.warning.expect.css +++ b/plugins/postcss-is-pseudo-class/test/basic.oncomplex.warning.expect.css @@ -285,3 +285,7 @@ input:hover, input:focus, button:hover, button:focus { .a > div.b > .c { color: green; } + +::before:is(.a > .b) { + color: green; +} diff --git a/plugins/postcss-is-pseudo-class/test/basic.preserve.expect.css b/plugins/postcss-is-pseudo-class/test/basic.preserve.expect.css index 903f511fee..814b338aa5 100644 --- a/plugins/postcss-is-pseudo-class/test/basic.preserve.expect.css +++ b/plugins/postcss-is-pseudo-class/test/basic.preserve.expect.css @@ -437,3 +437,7 @@ header:is(.a .b) { div:is(.a > .b) > .c { color: green; } + +::before:is(.a > .b) { + color: green; +} diff --git a/plugins/postcss-is-pseudo-class/test/basic.with-cloned-declarations.expect.css b/plugins/postcss-is-pseudo-class/test/basic.with-cloned-declarations.expect.css index 906c6a39ce..4f16fc4431 100644 --- a/plugins/postcss-is-pseudo-class/test/basic.with-cloned-declarations.expect.css +++ b/plugins/postcss-is-pseudo-class/test/basic.with-cloned-declarations.expect.css @@ -439,3 +439,7 @@ header:is(.a .b) { div:is(.a > .b) > .c { color: green; } + +::before:is(.a > .b) { + color: green; +} diff --git a/plugins/postcss-is-pseudo-class/test/complex.css b/plugins/postcss-is-pseudo-class/test/complex.css index 63c098bbe2..6fb66c6404 100644 --- a/plugins/postcss-is-pseudo-class/test/complex.css +++ b/plugins/postcss-is-pseudo-class/test/complex.css @@ -279,3 +279,48 @@ cartesianProduct([' ', '+', '>', '~'], [' ', '+', '>', '~'], [' ', '+', '>', '~' console.log(out.join('')); */ + +.ignore :is(.b > .c) { + order: 065; +} + +.ignore > :is(.b + .c) { + order: 066; +} + +.ignore ~ :is(.b + .c) { + order: 067; +} + +/* https: //github.com/csstools/postcss-plugins/issues/1625 */ +.a > :is(.b > .d, .c > .d) { + order: 65; +} + +.a:hover > :is(.b > .d, .c > .d) { + order: 66; +} + +.ignore + .b > :is(.b > .c) { + order: 67; +} + +.ignore::before > :is(.b > .c) { + order: 68; +} + +.a + :is(.b + .d, .c + .d) { + order: 75; +} + +.a:hover + :is(.b + .d, .c + .d) { + order: 76; +} + +.ignore + .b + :is(.b + .c) { + order: 77; +} + +.ignore::before + :is(.b + .c) { + order: 78; +} diff --git a/plugins/postcss-is-pseudo-class/test/complex.expect.css b/plugins/postcss-is-pseudo-class/test/complex.expect.css index 63c098bbe2..b9fbd091aa 100644 --- a/plugins/postcss-is-pseudo-class/test/complex.expect.css +++ b/plugins/postcss-is-pseudo-class/test/complex.expect.css @@ -279,3 +279,48 @@ cartesianProduct([' ', '+', '>', '~'], [' ', '+', '>', '~'], [' ', '+', '>', '~' console.log(out.join('')); */ + +.ignore :is(.b > .c) { + order: 065; +} + +.ignore > :is(.b + .c) { + order: 066; +} + +.ignore ~ :is(.b + .c) { + order: 067; +} + +/* https: //github.com/csstools/postcss-plugins/issues/1625 */ +.a.b > .d, .a.c > .d { + order: 65; +} + +.a.b:hover > .d, .a.c:hover > .d { + order: 66; +} + +.ignore + .b > :is(.b > .c) { + order: 67; +} + +.ignore::before > :is(.b > .c) { + order: 68; +} + +.a.b + .d, .a.c + .d { + order: 75; +} + +.a.b:hover + .d, .a.c:hover + .d { + order: 76; +} + +.ignore + .b + :is(.b + .c) { + order: 77; +} + +.ignore::before + :is(.b + .c) { + order: 78; +} diff --git a/plugins/postcss-is-pseudo-class/test/compound-after-complex-is.css b/plugins/postcss-is-pseudo-class/test/compound-after-complex-is.css index 1cf6476e8e..a4702012ec 100644 --- a/plugins/postcss-is-pseudo-class/test/compound-after-complex-is.css +++ b/plugins/postcss-is-pseudo-class/test/compound-after-complex-is.css @@ -26,7 +26,7 @@ order: 2.1; } -.ignore > :is(.a > .b) > .c { +::before > :is(.a > .b) > .c { order: 2.2; } @@ -38,7 +38,7 @@ order: 3.1; } -.ignore + :is(.a + .b) + .c { +::before + :is(.a + .b) + .c { order: 3.2; } diff --git a/plugins/postcss-is-pseudo-class/test/compound-after-complex-is.expect.css b/plugins/postcss-is-pseudo-class/test/compound-after-complex-is.expect.css index a18a7e6056..5961520644 100644 --- a/plugins/postcss-is-pseudo-class/test/compound-after-complex-is.expect.css +++ b/plugins/postcss-is-pseudo-class/test/compound-after-complex-is.expect.css @@ -26,7 +26,7 @@ order: 2.1; } -.ignore > :is(.a > .b) > .c { +::before > :is(.a > .b) > .c { order: 2.2; } @@ -38,7 +38,7 @@ order: 3.1; } -.ignore + :is(.a + .b) + .c { +::before + :is(.a + .b) + .c { order: 3.2; } From d71cfe51b04b8bb6f950f512c5b746e14cc6dfef Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Tue, 10 Jun 2025 11:51:01 +0200 Subject: [PATCH 2/2] fix --- .../src/split-selectors/complex/same-preceding-element.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/postcss-is-pseudo-class/src/split-selectors/complex/same-preceding-element.ts b/plugins/postcss-is-pseudo-class/src/split-selectors/complex/same-preceding-element.ts index 482cb3181e..3376655ec6 100644 --- a/plugins/postcss-is-pseudo-class/src/split-selectors/complex/same-preceding-element.ts +++ b/plugins/postcss-is-pseudo-class/src/split-selectors/complex/same-preceding-element.ts @@ -1,5 +1,3 @@ -import type { Pseudo } from 'postcss-selector-parser'; - import parser from 'postcss-selector-parser'; // .a > :-csstools-matches(.b > .c)