From 51b3bc6a08ecf5699aa3eadc8af5357caedcc893 Mon Sep 17 00:00:00 2001 From: Romain Menke <11521496+romainmenke@users.noreply.github.com> Date: Sun, 9 Apr 2023 12:31:35 +0200 Subject: [PATCH] is-pseudo : handle more complex selector patterns (#923) * is-pseudo : handle more complex selector patterns * clarify --- plugins/postcss-is-pseudo-class/.tape.mjs | 8 +- plugins/postcss-is-pseudo-class/CHANGELOG.md | 4 + .../postcss-is-pseudo-class/dist/index.cjs | 2 +- .../postcss-is-pseudo-class/dist/index.mjs | 2 +- .../complex/is-pseudo-in-first-compound.d.ts | 1 + .../src/split-selectors/complex.ts | 4 +- .../complex/child-adjacent-child.ts | 9 ++- .../complex/is-pseudo-in-first-compound.ts | 57 ++++++++++++++ .../compound-selector-order.ts | 23 +++++- .../postcss-is-pseudo-class/test/basic.css | 4 + .../test/basic.does-not-exist.expect.css | 28 +++++-- .../test/basic.expect.css | 28 +++++-- .../basic.oncomplex.no-warning.expect.css | 28 +++++-- .../test/basic.oncomplex.warning.expect.css | 28 +++++-- .../test/basic.preserve.expect.css | 40 +++++++++- .../basic.with-cloned-declarations.expect.css | 40 +++++++++- .../test/compound-after-complex-is.css | 76 +++++++++++++++++++ .../test/compound-after-complex-is.expect.css | 76 +++++++++++++++++++ 18 files changed, 423 insertions(+), 35 deletions(-) create mode 100644 plugins/postcss-is-pseudo-class/dist/split-selectors/complex/is-pseudo-in-first-compound.d.ts create mode 100644 plugins/postcss-is-pseudo-class/src/split-selectors/complex/is-pseudo-in-first-compound.ts create mode 100644 plugins/postcss-is-pseudo-class/test/compound-after-complex-is.css create mode 100644 plugins/postcss-is-pseudo-class/test/compound-after-complex-is.expect.css diff --git a/plugins/postcss-is-pseudo-class/.tape.mjs b/plugins/postcss-is-pseudo-class/.tape.mjs index 4c270b78f..ac98372ba 100644 --- a/plugins/postcss-is-pseudo-class/.tape.mjs +++ b/plugins/postcss-is-pseudo-class/.tape.mjs @@ -19,7 +19,7 @@ postcssTape(plugin)({ }, 'basic:oncomplex:warning': { message: "warns on complex selectors", - warnings: 10, + warnings: 8, options: { onComplexSelector: 'warning' } @@ -42,6 +42,12 @@ postcssTape(plugin)({ preserve: false } }, + 'compound-after-complex-is': { + message: "can handle compound selectors after complex selectors in :is()", + options: { + preserve: false + } + }, 'complex': { message: "supports complex selectors", options: { diff --git a/plugins/postcss-is-pseudo-class/CHANGELOG.md b/plugins/postcss-is-pseudo-class/CHANGELOG.md index 26b6b4480..e9befcb35 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 (minor) + +- Add support for more complex selector patterns. In particular anything where `:is()` is in the left-most compound selector. + ### 3.1.1 (February 8, 2023) - Reduce the amount of duplicate fallback CSS. diff --git a/plugins/postcss-is-pseudo-class/dist/index.cjs b/plugins/postcss-is-pseudo-class/dist/index.cjs index dada57d3f..5f01152e9 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"),o=require("@csstools/selector-specificity");function alwaysValidSelector(o){const s=e().astSync(o);let t=!0;return s.walk((e=>{var o,s;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||null!=(o=e.nodes)&&o.length||":hover"!==e.value.toLowerCase()&&":focus"!==e.value.toLowerCase())){if("pseudo"===e.type&&1===(null==(s=e.nodes)?void 0:s.length)&&":not"===e.value.toLowerCase()){let o=!0;if(e.nodes[0].walkCombinators((()=>{o=!1})),o)return}return t=!1,!1}})),t}function sortCompoundSelectorsInsideComplexSelector(o){if(!o||!o.nodes)return;const s=[];let t=[];for(let n=0;n"selector"===e.type&&"selector"===o.type&&e.nodes.length&&o.nodes.length?selectorTypeOrder(e.nodes[0],e.nodes[0].type)-selectorTypeOrder(o.nodes[0],o.nodes[0].type):"selector"===e.type&&e.nodes.length?selectorTypeOrder(e.nodes[0],e.nodes[0].type)-selectorTypeOrder(o,o.type):"selector"===o.type&&o.nodes.length?selectorTypeOrder(e,e.type)-selectorTypeOrder(o.nodes[0],o.nodes[0].type):selectorTypeOrder(e,e.type)-selectorTypeOrder(o,o.type)));for(let e=0;e=0;e--)n[e].remove(),o.prepend(n[e])}function selectorTypeOrder(o,t){return e.isPseudoElement(o)?s.pseudoElement:s[t]}const s={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[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(o){if(!o||!o.nodes)return!1;if("selector"!==o.type)return!1;if(2!==o.nodes.length)return!1;let s,t;return o.nodes[0]&&"pseudo"===o.nodes[0].type&&":-csstools-matches"===o.nodes[0].value?(s=0,t=1):o.nodes[1]&&"pseudo"===o.nodes[1].type&&":-csstools-matches"===o.nodes[1].value&&(s=1,t=0),!!s&&(!!o.nodes[t]&&(("selector"!==o.nodes[t].type||!o.nodes[t].some((o=>"combinator"===o.type||e.isPseudoElement(o))))&&(o.nodes[s].append(o.nodes[t].clone()),o.nodes[s].replaceWith(...o.nodes[s].nodes),o.nodes[t].remove(),!0)))}function complexSelectors(o,s,t,n){return o.flatMap((o=>{if(-1===o.indexOf(":-csstools-matches")&&-1===o.toLowerCase().indexOf(":is"))return o;const r=e().astSync(o);return r.walkPseudos((o=>{if(":is"===o.value.toLowerCase()&&o.nodes&&o.nodes.length&&"selector"===o.nodes[0].type&&0===o.nodes[0].nodes.length)return o.value=":not",void o.nodes[0].append(e.universal());if(":-csstools-matches"===o.value)if(!o.nodes||o.nodes.length){if(o.walkPseudos((o=>{if(e.isPseudoElement(o)){let e=o.value;if(e.startsWith("::-csstools-invalid-"))return;for(;e.startsWith(":");)e=e.slice(1);o.value=`::-csstools-invalid-${e}`,n()}})),1===o.nodes.length&&"selector"===o.nodes[0].type){if(1===o.nodes[0].nodes.length)return void o.replaceWith(o.nodes[0].nodes[0]);if(!o.nodes[0].some((e=>"combinator"===e.type)))return void o.replaceWith(...o.nodes[0].nodes)}1!==r.nodes.length||"selector"!==r.nodes[0].type||1!==r.nodes[0].nodes.length||r.nodes[0].nodes[0]!==o?childAdjacentChild(o.parent)||isInCompoundWithOneOtherElement(o.parent)||("warning"===s.onComplexSelector&&t(),o.value=":is"):o.replaceWith(...o.nodes[0].nodes)}else o.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(s,t,n=0){const r=":not(#"+t.specificityMatchingName+")",l=":not(."+t.specificityMatchingName+")",d=":not("+t.specificityMatchingName+")";return s.flatMap((s=>{if(-1===s.toLowerCase().indexOf(":is"))return s;let c=!1;const i=[];if(e().astSync(s).walkPseudos((e=>{var s,t,n,a,p;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"===(null==(s=e.parent)||null==(t=s.parent)?void 0:t.type)&&":not"===(null==(n=e.parent)||null==(a=n.parent)||null==(p=a.value)?void 0:p.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()})`}]);let u=e.parent;for(;u;){if(u.value&&":is"===u.value.toLowerCase()&&"pseudo"===u.type)return void(c=!0);u=u.parent}const h=o.selectorSpecificity(e),f=e.sourceIndex,y=f+e.toString().length,m=[];e.nodes.forEach((e=>{const s={start:f,end:y,option:""},t=o.selectorSpecificity(e);let n=e.toString().trim();const c=Math.max(0,h.a-t.a),i=Math.max(0,h.b-t.b),a=Math.max(0,h.c-t.c);for(let e=0;e{let o="";for(let n=0;n!!e))}function cartesianProduct(...e){const o=[],s=e.length-1;return function helper(t,n){for(let r=0,l=e[n].length;r{const o={specificityMatchingName:"does-not-exist",...e||{}};return{postcssPlugin:"postcss-is-pseudo-class",prepare(){const e=new WeakSet;return{Rule(s,{result:t}){if(!s.selector)return;if(-1===s.selector.toLowerCase().indexOf(":is("))return;if(e.has(s))return;let n=!1;const warnOnComplexSelector=()=>{"warning"===o.onComplexSelector&&(n||(n=!0,s.warn(t,`Complex selectors in '${s.selector}' can not be transformed to an equivalent selector without ':is()'.`)))};let r=!1;const warnOnPseudoElements=()=>{"warning"===o.onPseudoElement&&(r||(r=!0,s.warn(t,`Pseudo elements are not allowed in ':is()', unable to transform '${s.selector}'`)))};try{let t=!1;const n=[],r=complexSelectors(splitSelectors(s.selectors,{specificityMatchingName:o.specificityMatchingName}),{onComplexSelector:o.onComplexSelector},warnOnComplexSelector,warnOnPseudoElements);if(Array.from(new Set(r)).forEach((o=>{if(s.selectors.indexOf(o)>-1)n.push(o);else{if(alwaysValidSelector(o))return n.push(o),void(t=!0);e.add(s),s.cloneBefore({selector:o}),t=!0}})),n.length&&t&&(e.add(s),s.cloneBefore({selectors:n})),!o.preserve){if(!t)return;s.remove()}}catch(e){if(e.message.indexOf("call stack size exceeded")>-1)throw e;s.warn(t,`Failed to parse selector "${s.selector}"`)}}}}}};creator.postcss=!0,module.exports=creator; +"use strict";var e=require("postcss-selector-parser"),o=require("@csstools/selector-specificity");function alwaysValidSelector(o){const s=e().astSync(o);let t=!0;return s.walk((e=>{var o,s;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||null!=(o=e.nodes)&&o.length||":hover"!==e.value.toLowerCase()&&":focus"!==e.value.toLowerCase())){if("pseudo"===e.type&&1===(null==(s=e.nodes)?void 0:s.length)&&":not"===e.value.toLowerCase()){let o=!0;if(e.nodes[0].walkCombinators((()=>{o=!1})),o)return}return t=!1,!1}})),t}function sortCompoundSelectorsInsideComplexSelector(o){if(!o||!o.nodes||1===o.nodes.length)return;const s=[];let t=[];for(let n=0;n"selector"===e.type&&"selector"===o.type&&e.nodes.length&&o.nodes.length?selectorTypeOrder(e.nodes[0],e.nodes[0].type)-selectorTypeOrder(o.nodes[0],o.nodes[0].type):"selector"===e.type&&e.nodes.length?selectorTypeOrder(e.nodes[0],e.nodes[0].type)-selectorTypeOrder(o,o.type):"selector"===o.type&&o.nodes.length?selectorTypeOrder(e,e.type)-selectorTypeOrder(o.nodes[0],o.nodes[0].type):selectorTypeOrder(e,e.type)-selectorTypeOrder(o,o.type)));for(let e=0;e=0;s--){const t=n[s-1];if(n[s].remove(),t&&"tag"===t.type&&"tag"===n[s].type){const t=e.pseudo({value:":is",nodes:[e.selector({value:"",nodes:[n[s]]})]});o.prepend(t)}else o.prepend(n[s])}}function selectorTypeOrder(o,t){return e.isPseudoElement(o)?s.pseudoElement:s[t]}const s={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(o){if(!o||!o.nodes)return!1;if("selector"!==o.type)return!1;if(2!==o.nodes.length)return!1;let s,t;return o.nodes[0]&&"pseudo"===o.nodes[0].type&&":-csstools-matches"===o.nodes[0].value?(s=0,t=1):o.nodes[1]&&"pseudo"===o.nodes[1].type&&":-csstools-matches"===o.nodes[1].value&&(s=1,t=0),!!s&&(!!o.nodes[t]&&(("selector"!==o.nodes[t].type||!o.nodes[t].some((o=>"combinator"===o.type||e.isPseudoElement(o))))&&(o.nodes[s].append(o.nodes[t].clone()),o.nodes[s].replaceWith(...o.nodes[s].nodes),o.nodes[t].remove(),!0)))}function isPseudoInFirstCompound(e){if(!e||!e.nodes)return!1;if("selector"!==e.type)return!1;let o=-1;for(let s=0;s{t.nodes[0].append(e.clone())})),n.forEach((e=>{t.nodes[0].append(e.clone())})),t.replaceWith(...t.nodes),s.forEach((e=>{e.remove()})),n.forEach((e=>{e.remove()})),!0}function complexSelectors(o,s,t,n){return o.flatMap((o=>{if(-1===o.indexOf(":-csstools-matches")&&-1===o.toLowerCase().indexOf(":is"))return o;const r=e().astSync(o);return r.walkPseudos((o=>{if(":is"===o.value.toLowerCase()&&o.nodes&&o.nodes.length&&"selector"===o.nodes[0].type&&0===o.nodes[0].nodes.length)return o.value=":not",void o.nodes[0].append(e.universal());if(":-csstools-matches"===o.value)if(!o.nodes||o.nodes.length){if(o.walkPseudos((o=>{if(e.isPseudoElement(o)){let e=o.value;if(e.startsWith("::-csstools-invalid-"))return;for(;e.startsWith(":");)e=e.slice(1);o.value=`::-csstools-invalid-${e}`,n()}})),1===o.nodes.length&&"selector"===o.nodes[0].type){if(1===o.nodes[0].nodes.length)return void o.replaceWith(o.nodes[0].nodes[0]);if(!o.nodes[0].some((e=>"combinator"===e.type)))return void o.replaceWith(...o.nodes[0].nodes)}1!==r.nodes.length||"selector"!==r.nodes[0].type||1!==r.nodes[0].nodes.length||r.nodes[0].nodes[0]!==o?childAdjacentChild(o.parent)||isInCompoundWithOneOtherElement(o.parent)||isPseudoInFirstCompound(o.parent)||("warning"===s.onComplexSelector&&t(),o.value=":is"):o.replaceWith(...o.nodes[0].nodes)}else o.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(s,t,n=0){const r=":not(#"+t.specificityMatchingName+")",d=":not(."+t.specificityMatchingName+")",l=":not("+t.specificityMatchingName+")";return s.flatMap((s=>{if(-1===s.toLowerCase().indexOf(":is"))return s;let c=!1;const i=[];if(e().astSync(s).walkPseudos((e=>{var s,t,n,a,p;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"===(null==(s=e.parent)||null==(t=s.parent)?void 0:t.type)&&":not"===(null==(n=e.parent)||null==(a=n.parent)||null==(p=a.value)?void 0:p.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()})`}]);let u=e.parent;for(;u;){if(u.value&&":is"===u.value.toLowerCase()&&"pseudo"===u.type)return void(c=!0);u=u.parent}const h=o.selectorSpecificity(e),f=e.sourceIndex,y=f+e.toString().length,m=[];e.nodes.forEach((e=>{const s={start:f,end:y,option:""},t=o.selectorSpecificity(e);let n=e.toString().trim();const c=Math.max(0,h.a-t.a),i=Math.max(0,h.b-t.b),a=Math.max(0,h.c-t.c);for(let e=0;e{let o="";for(let n=0;n!!e))}function cartesianProduct(...e){const o=[],s=e.length-1;return function helper(t,n){for(let r=0,d=e[n].length;r{const o={specificityMatchingName:"does-not-exist",...e||{}};return{postcssPlugin:"postcss-is-pseudo-class",prepare(){const e=new WeakSet;return{Rule(s,{result:t}){if(!s.selector)return;if(-1===s.selector.toLowerCase().indexOf(":is("))return;if(e.has(s))return;let n=!1;const warnOnComplexSelector=()=>{"warning"===o.onComplexSelector&&(n||(n=!0,s.warn(t,`Complex selectors in '${s.selector}' can not be transformed to an equivalent selector without ':is()'.`)))};let r=!1;const warnOnPseudoElements=()=>{"warning"===o.onPseudoElement&&(r||(r=!0,s.warn(t,`Pseudo elements are not allowed in ':is()', unable to transform '${s.selector}'`)))};try{let t=!1;const n=[],r=complexSelectors(splitSelectors(s.selectors,{specificityMatchingName:o.specificityMatchingName}),{onComplexSelector:o.onComplexSelector},warnOnComplexSelector,warnOnPseudoElements);if(Array.from(new Set(r)).forEach((o=>{if(s.selectors.indexOf(o)>-1)n.push(o);else{if(alwaysValidSelector(o))return n.push(o),void(t=!0);e.add(s),s.cloneBefore({selector:o}),t=!0}})),n.length&&t&&(e.add(s),s.cloneBefore({selectors:n})),!o.preserve){if(!t)return;s.remove()}}catch(e){if(e.message.indexOf("call stack size exceeded")>-1)throw e;s.warn(t,`Failed to parse selector "${s.selector}"`)}}}}}};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 ff467edce..f20c89ad0 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 o}from"@csstools/selector-specificity";function alwaysValidSelector(o){const s=e().astSync(o);let t=!0;return s.walk((e=>{var o,s;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||null!=(o=e.nodes)&&o.length||":hover"!==e.value.toLowerCase()&&":focus"!==e.value.toLowerCase())){if("pseudo"===e.type&&1===(null==(s=e.nodes)?void 0:s.length)&&":not"===e.value.toLowerCase()){let o=!0;if(e.nodes[0].walkCombinators((()=>{o=!1})),o)return}return t=!1,!1}})),t}function sortCompoundSelectorsInsideComplexSelector(o){if(!o||!o.nodes)return;const s=[];let t=[];for(let n=0;n"selector"===e.type&&"selector"===o.type&&e.nodes.length&&o.nodes.length?selectorTypeOrder(e.nodes[0],e.nodes[0].type)-selectorTypeOrder(o.nodes[0],o.nodes[0].type):"selector"===e.type&&e.nodes.length?selectorTypeOrder(e.nodes[0],e.nodes[0].type)-selectorTypeOrder(o,o.type):"selector"===o.type&&o.nodes.length?selectorTypeOrder(e,e.type)-selectorTypeOrder(o.nodes[0],o.nodes[0].type):selectorTypeOrder(e,e.type)-selectorTypeOrder(o,o.type)));for(let e=0;e=0;e--)n[e].remove(),o.prepend(n[e])}function selectorTypeOrder(o,t){return e.isPseudoElement(o)?s.pseudoElement:s[t]}const s={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[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(o){if(!o||!o.nodes)return!1;if("selector"!==o.type)return!1;if(2!==o.nodes.length)return!1;let s,t;return o.nodes[0]&&"pseudo"===o.nodes[0].type&&":-csstools-matches"===o.nodes[0].value?(s=0,t=1):o.nodes[1]&&"pseudo"===o.nodes[1].type&&":-csstools-matches"===o.nodes[1].value&&(s=1,t=0),!!s&&(!!o.nodes[t]&&(("selector"!==o.nodes[t].type||!o.nodes[t].some((o=>"combinator"===o.type||e.isPseudoElement(o))))&&(o.nodes[s].append(o.nodes[t].clone()),o.nodes[s].replaceWith(...o.nodes[s].nodes),o.nodes[t].remove(),!0)))}function complexSelectors(o,s,t,n){return o.flatMap((o=>{if(-1===o.indexOf(":-csstools-matches")&&-1===o.toLowerCase().indexOf(":is"))return o;const r=e().astSync(o);return r.walkPseudos((o=>{if(":is"===o.value.toLowerCase()&&o.nodes&&o.nodes.length&&"selector"===o.nodes[0].type&&0===o.nodes[0].nodes.length)return o.value=":not",void o.nodes[0].append(e.universal());if(":-csstools-matches"===o.value)if(!o.nodes||o.nodes.length){if(o.walkPseudos((o=>{if(e.isPseudoElement(o)){let e=o.value;if(e.startsWith("::-csstools-invalid-"))return;for(;e.startsWith(":");)e=e.slice(1);o.value=`::-csstools-invalid-${e}`,n()}})),1===o.nodes.length&&"selector"===o.nodes[0].type){if(1===o.nodes[0].nodes.length)return void o.replaceWith(o.nodes[0].nodes[0]);if(!o.nodes[0].some((e=>"combinator"===e.type)))return void o.replaceWith(...o.nodes[0].nodes)}1!==r.nodes.length||"selector"!==r.nodes[0].type||1!==r.nodes[0].nodes.length||r.nodes[0].nodes[0]!==o?childAdjacentChild(o.parent)||isInCompoundWithOneOtherElement(o.parent)||("warning"===s.onComplexSelector&&t(),o.value=":is"):o.replaceWith(...o.nodes[0].nodes)}else o.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(s,t,n=0){const r=":not(#"+t.specificityMatchingName+")",l=":not(."+t.specificityMatchingName+")",d=":not("+t.specificityMatchingName+")";return s.flatMap((s=>{if(-1===s.toLowerCase().indexOf(":is"))return s;let c=!1;const i=[];if(e().astSync(s).walkPseudos((e=>{var s,t,n,a,p;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"===(null==(s=e.parent)||null==(t=s.parent)?void 0:t.type)&&":not"===(null==(n=e.parent)||null==(a=n.parent)||null==(p=a.value)?void 0:p.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()})`}]);let u=e.parent;for(;u;){if(u.value&&":is"===u.value.toLowerCase()&&"pseudo"===u.type)return void(c=!0);u=u.parent}const h=o(e),f=e.sourceIndex,y=f+e.toString().length,m=[];e.nodes.forEach((e=>{const s={start:f,end:y,option:""},t=o(e);let n=e.toString().trim();const c=Math.max(0,h.a-t.a),i=Math.max(0,h.b-t.b),a=Math.max(0,h.c-t.c);for(let e=0;e{let o="";for(let n=0;n!!e))}function cartesianProduct(...e){const o=[],s=e.length-1;return function helper(t,n){for(let r=0,l=e[n].length;r{const o={specificityMatchingName:"does-not-exist",...e||{}};return{postcssPlugin:"postcss-is-pseudo-class",prepare(){const e=new WeakSet;return{Rule(s,{result:t}){if(!s.selector)return;if(-1===s.selector.toLowerCase().indexOf(":is("))return;if(e.has(s))return;let n=!1;const warnOnComplexSelector=()=>{"warning"===o.onComplexSelector&&(n||(n=!0,s.warn(t,`Complex selectors in '${s.selector}' can not be transformed to an equivalent selector without ':is()'.`)))};let r=!1;const warnOnPseudoElements=()=>{"warning"===o.onPseudoElement&&(r||(r=!0,s.warn(t,`Pseudo elements are not allowed in ':is()', unable to transform '${s.selector}'`)))};try{let t=!1;const n=[],r=complexSelectors(splitSelectors(s.selectors,{specificityMatchingName:o.specificityMatchingName}),{onComplexSelector:o.onComplexSelector},warnOnComplexSelector,warnOnPseudoElements);if(Array.from(new Set(r)).forEach((o=>{if(s.selectors.indexOf(o)>-1)n.push(o);else{if(alwaysValidSelector(o))return n.push(o),void(t=!0);e.add(s),s.cloneBefore({selector:o}),t=!0}})),n.length&&t&&(e.add(s),s.cloneBefore({selectors:n})),!o.preserve){if(!t)return;s.remove()}}catch(e){if(e.message.indexOf("call stack size exceeded")>-1)throw e;s.warn(t,`Failed to parse selector "${s.selector}"`)}}}}}};creator.postcss=!0;export{creator as default}; +import e from"postcss-selector-parser";import{selectorSpecificity as o}from"@csstools/selector-specificity";function alwaysValidSelector(o){const s=e().astSync(o);let n=!0;return s.walk((e=>{var o,s;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||null!=(o=e.nodes)&&o.length||":hover"!==e.value.toLowerCase()&&":focus"!==e.value.toLowerCase())){if("pseudo"===e.type&&1===(null==(s=e.nodes)?void 0:s.length)&&":not"===e.value.toLowerCase()){let o=!0;if(e.nodes[0].walkCombinators((()=>{o=!1})),o)return}return n=!1,!1}})),n}function sortCompoundSelectorsInsideComplexSelector(o){if(!o||!o.nodes||1===o.nodes.length)return;const s=[];let n=[];for(let t=0;t"selector"===e.type&&"selector"===o.type&&e.nodes.length&&o.nodes.length?selectorTypeOrder(e.nodes[0],e.nodes[0].type)-selectorTypeOrder(o.nodes[0],o.nodes[0].type):"selector"===e.type&&e.nodes.length?selectorTypeOrder(e.nodes[0],e.nodes[0].type)-selectorTypeOrder(o,o.type):"selector"===o.type&&o.nodes.length?selectorTypeOrder(e,e.type)-selectorTypeOrder(o.nodes[0],o.nodes[0].type):selectorTypeOrder(e,e.type)-selectorTypeOrder(o,o.type)));for(let e=0;e=0;s--){const n=t[s-1];if(t[s].remove(),n&&"tag"===n.type&&"tag"===t[s].type){const n=e.pseudo({value:":is",nodes:[e.selector({value:"",nodes:[t[s]]})]});o.prepend(n)}else o.prepend(t[s])}}function selectorTypeOrder(o,n){return e.isPseudoElement(o)?s.pseudoElement:s[n]}const s={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(o){if(!o||!o.nodes)return!1;if("selector"!==o.type)return!1;if(2!==o.nodes.length)return!1;let s,n;return o.nodes[0]&&"pseudo"===o.nodes[0].type&&":-csstools-matches"===o.nodes[0].value?(s=0,n=1):o.nodes[1]&&"pseudo"===o.nodes[1].type&&":-csstools-matches"===o.nodes[1].value&&(s=1,n=0),!!s&&(!!o.nodes[n]&&(("selector"!==o.nodes[n].type||!o.nodes[n].some((o=>"combinator"===o.type||e.isPseudoElement(o))))&&(o.nodes[s].append(o.nodes[n].clone()),o.nodes[s].replaceWith(...o.nodes[s].nodes),o.nodes[n].remove(),!0)))}function isPseudoInFirstCompound(e){if(!e||!e.nodes)return!1;if("selector"!==e.type)return!1;let o=-1;for(let s=0;s{n.nodes[0].append(e.clone())})),t.forEach((e=>{n.nodes[0].append(e.clone())})),n.replaceWith(...n.nodes),s.forEach((e=>{e.remove()})),t.forEach((e=>{e.remove()})),!0}function complexSelectors(o,s,n,t){return o.flatMap((o=>{if(-1===o.indexOf(":-csstools-matches")&&-1===o.toLowerCase().indexOf(":is"))return o;const r=e().astSync(o);return r.walkPseudos((o=>{if(":is"===o.value.toLowerCase()&&o.nodes&&o.nodes.length&&"selector"===o.nodes[0].type&&0===o.nodes[0].nodes.length)return o.value=":not",void o.nodes[0].append(e.universal());if(":-csstools-matches"===o.value)if(!o.nodes||o.nodes.length){if(o.walkPseudos((o=>{if(e.isPseudoElement(o)){let e=o.value;if(e.startsWith("::-csstools-invalid-"))return;for(;e.startsWith(":");)e=e.slice(1);o.value=`::-csstools-invalid-${e}`,t()}})),1===o.nodes.length&&"selector"===o.nodes[0].type){if(1===o.nodes[0].nodes.length)return void o.replaceWith(o.nodes[0].nodes[0]);if(!o.nodes[0].some((e=>"combinator"===e.type)))return void o.replaceWith(...o.nodes[0].nodes)}1!==r.nodes.length||"selector"!==r.nodes[0].type||1!==r.nodes[0].nodes.length||r.nodes[0].nodes[0]!==o?childAdjacentChild(o.parent)||isInCompoundWithOneOtherElement(o.parent)||isPseudoInFirstCompound(o.parent)||("warning"===s.onComplexSelector&&n(),o.value=":is"):o.replaceWith(...o.nodes[0].nodes)}else o.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(s,n,t=0){const r=":not(#"+n.specificityMatchingName+")",d=":not(."+n.specificityMatchingName+")",l=":not("+n.specificityMatchingName+")";return s.flatMap((s=>{if(-1===s.toLowerCase().indexOf(":is"))return s;let c=!1;const i=[];if(e().astSync(s).walkPseudos((e=>{var s,n,t,a,p;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"===(null==(s=e.parent)||null==(n=s.parent)?void 0:n.type)&&":not"===(null==(t=e.parent)||null==(a=t.parent)||null==(p=a.value)?void 0:p.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()})`}]);let u=e.parent;for(;u;){if(u.value&&":is"===u.value.toLowerCase()&&"pseudo"===u.type)return void(c=!0);u=u.parent}const h=o(e),f=e.sourceIndex,y=f+e.toString().length,m=[];e.nodes.forEach((e=>{const s={start:f,end:y,option:""},n=o(e);let t=e.toString().trim();const c=Math.max(0,h.a-n.a),i=Math.max(0,h.b-n.b),a=Math.max(0,h.c-n.c);for(let e=0;e{let o="";for(let t=0;t!!e))}function cartesianProduct(...e){const o=[],s=e.length-1;return function helper(n,t){for(let r=0,d=e[t].length;r{const o={specificityMatchingName:"does-not-exist",...e||{}};return{postcssPlugin:"postcss-is-pseudo-class",prepare(){const e=new WeakSet;return{Rule(s,{result:n}){if(!s.selector)return;if(-1===s.selector.toLowerCase().indexOf(":is("))return;if(e.has(s))return;let t=!1;const warnOnComplexSelector=()=>{"warning"===o.onComplexSelector&&(t||(t=!0,s.warn(n,`Complex selectors in '${s.selector}' can not be transformed to an equivalent selector without ':is()'.`)))};let r=!1;const warnOnPseudoElements=()=>{"warning"===o.onPseudoElement&&(r||(r=!0,s.warn(n,`Pseudo elements are not allowed in ':is()', unable to transform '${s.selector}'`)))};try{let n=!1;const t=[],r=complexSelectors(splitSelectors(s.selectors,{specificityMatchingName:o.specificityMatchingName}),{onComplexSelector:o.onComplexSelector},warnOnComplexSelector,warnOnPseudoElements);if(Array.from(new Set(r)).forEach((o=>{if(s.selectors.indexOf(o)>-1)t.push(o);else{if(alwaysValidSelector(o))return t.push(o),void(n=!0);e.add(s),s.cloneBefore({selector:o}),n=!0}})),t.length&&n&&(e.add(s),s.cloneBefore({selectors:t})),!o.preserve){if(!n)return;s.remove()}}catch(e){if(e.message.indexOf("call stack size exceeded")>-1)throw e;s.warn(n,`Failed to parse selector "${s.selector}"`)}}}}}};creator.postcss=!0;export{creator as default}; diff --git a/plugins/postcss-is-pseudo-class/dist/split-selectors/complex/is-pseudo-in-first-compound.d.ts b/plugins/postcss-is-pseudo-class/dist/split-selectors/complex/is-pseudo-in-first-compound.d.ts new file mode 100644 index 000000000..06ba1481e --- /dev/null +++ b/plugins/postcss-is-pseudo-class/dist/split-selectors/complex/is-pseudo-in-first-compound.d.ts @@ -0,0 +1 @@ +export declare function isPseudoInFirstCompound(selector: any): boolean; 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 fb3e799bd..0367c4a96 100644 --- a/plugins/postcss-is-pseudo-class/src/split-selectors/complex.ts +++ b/plugins/postcss-is-pseudo-class/src/split-selectors/complex.ts @@ -3,6 +3,7 @@ import { sortCompoundSelectorsInsideComplexSelector } from './compound-selector- 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'; export default function complexSelectors(selectors: Array, pluginOptions: { onComplexSelector?: 'warning' }, warnOnComplexSelector: () => void, warnOnPseudoElements: () => void) { return selectors.flatMap((selector) => { @@ -80,7 +81,8 @@ export default function complexSelectors(selectors: Array, pluginOptions if ( childAdjacentChild(pseudo.parent) || - isInCompoundWithOneOtherElement(pseudo.parent) + isInCompoundWithOneOtherElement(pseudo.parent) || + isPseudoInFirstCompound(pseudo.parent) ) { return; } diff --git a/plugins/postcss-is-pseudo-class/src/split-selectors/complex/child-adjacent-child.ts b/plugins/postcss-is-pseudo-class/src/split-selectors/complex/child-adjacent-child.ts index b901ee72a..2c85b0b13 100644 --- a/plugins/postcss-is-pseudo-class/src/split-selectors/complex/child-adjacent-child.ts +++ b/plugins/postcss-is-pseudo-class/src/split-selectors/complex/child-adjacent-child.ts @@ -1,6 +1,13 @@ // :-csstools-matches(.a > .b) + :-csstools-matches(.c > .d) // equivalent to // .a.c > .b + .d +// +// and +// +// :-csstools-matches(.a > .b) ~ :-csstools-matches(.c > .d) +// equivalent to +// .a.c ~ .b + .d +// // because adjacent elements have the same parent element. export function childAdjacentChild(selector): boolean { if (!selector || !selector.nodes) { @@ -19,7 +26,7 @@ export function childAdjacentChild(selector): boolean { } // adjacent combinator - if (!selector.nodes[1] || selector.nodes[1].type !== 'combinator' || selector.nodes[1].value !== '+') { + if (!selector.nodes[1] || selector.nodes[1].type !== 'combinator' || (selector.nodes[1].value !== '+' && selector.nodes[1].value !== '~')) { return false; } 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 new file mode 100644 index 000000000..0b39a007a --- /dev/null +++ b/plugins/postcss-is-pseudo-class/src/split-selectors/complex/is-pseudo-in-first-compound.ts @@ -0,0 +1,57 @@ +// :-csstools-matches(.a > .b) > .c +// equivalent to +// .a > .b > .c +// because `:is()` is in the left-most compound selector +export function isPseudoInFirstCompound(selector): boolean { + if (!selector || !selector.nodes) { + return false; + } + if (selector.type !== 'selector') { + return false; + } + + let isPseudoIndex = -1; + for (let i = 0; i < selector.nodes.length; i++) { + const node = selector.nodes[i]; + if (node.type === 'combinator') { + return false; + } + + if (node.type === 'pseudo' && node.value === ':-csstools-matches') { + if (!node.nodes || node.nodes.length !== 1) { + return false; + } + + isPseudoIndex = i; + break; + } + } + + if (isPseudoIndex === -1) { + return false; + } + + const before = selector.nodes.slice(0, isPseudoIndex); + const isPseudo = selector.nodes[isPseudoIndex]; + const after = selector.nodes.slice(isPseudoIndex + 1); + + before.forEach((node) => { + isPseudo.nodes[0].append(node.clone()); + }); + + after.forEach((node) => { + isPseudo.nodes[0].append(node.clone()); + }); + + isPseudo.replaceWith(...isPseudo.nodes); + + before.forEach((node) => { + node.remove(); + }); + + after.forEach((node) => { + node.remove(); + }); + + return true; +} diff --git a/plugins/postcss-is-pseudo-class/src/split-selectors/compound-selector-order.ts b/plugins/postcss-is-pseudo-class/src/split-selectors/compound-selector-order.ts index 41dfb64c4..0aa64d88b 100644 --- a/plugins/postcss-is-pseudo-class/src/split-selectors/compound-selector-order.ts +++ b/plugins/postcss-is-pseudo-class/src/split-selectors/compound-selector-order.ts @@ -1,7 +1,7 @@ import parser from 'postcss-selector-parser'; export function sortCompoundSelectorsInsideComplexSelector(node) { - if (!node || !node.nodes) { + if (!node || !node.nodes || node.nodes.length === 1) { return; } @@ -61,8 +61,27 @@ export function sortCompoundSelectorsInsideComplexSelector(node) { } for (let i = sortedCompoundSelectors.length - 1; i >= 0; i--) { + const previous = sortedCompoundSelectors[i - 1]; + sortedCompoundSelectors[i].remove(); - node.prepend(sortedCompoundSelectors[i]); + + if (previous && previous.type === 'tag' && sortedCompoundSelectors[i].type === 'tag') { + const wrapped = parser.pseudo({ + value: ':is', + nodes: [ + parser.selector({ + value: '', + nodes: [ + sortedCompoundSelectors[i], + ], + }), + ], + }); + + node.prepend(wrapped); + } else { + node.prepend(sortedCompoundSelectors[i]); + } } } diff --git a/plugins/postcss-is-pseudo-class/test/basic.css b/plugins/postcss-is-pseudo-class/test/basic.css index ba45fe526..1a7833652 100644 --- a/plugins/postcss-is-pseudo-class/test/basic.css +++ b/plugins/postcss-is-pseudo-class/test/basic.css @@ -142,6 +142,10 @@ foo[baz=":is(.some, .other)"], .ok { order: 36; } +:is(input, button):is(select, textarea) { + order: 37; +} + :is(input, button):is(:hover, :focus) { to-clone: 1; } 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 6d3cd4c4c..44f43f2d9 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 @@ -74,7 +74,7 @@ foo[baz=":is(.some, .other)"], .ok { order: 10; } -.pre:is(.alpha > .beta) + :is(:focus > .beta) { +.alpha > .beta.pre + :is(:focus > .beta) { order: 11; } @@ -82,15 +82,15 @@ foo[baz=":is(.some, .other)"], .ok { order: 12; } -.post:is(.alpha > .beta) + :is(:focus > .beta) { +.alpha > .beta.post + :is(:focus > .beta) { order: 13; } -:is(.alpha > .beta) .post + :is(:focus > .beta) { +.alpha > .beta .post + :is(:focus > .beta) { order: 14; } -:is(.alpha ~ .delta):focus > .beta + .beta { +.alpha ~ .delta:focus > .beta + .beta { order: 15; } @@ -98,7 +98,7 @@ foo[baz=":is(.some, .other)"], .ok { order: 16; } -:is(.alpha > .beta) ~ :is(:focus > .beta) { +.alpha:focus > .beta ~ .beta { order: 17; } @@ -110,7 +110,7 @@ foo[baz=":is(.some, .other)"], .ok { order: 19; } -.pre.alpha:is(.one > .two) { +.one > .two.pre.alpha { order: 20; } @@ -238,6 +238,22 @@ ol:hover > li.foo, ol:hover > li.bar, ol:focus > li.foo, ol:focus > li.bar, ul:h order: 36; } +input:is(select) { + order: 37; +} + +input:is(textarea) { + order: 37; +} + +button:is(select) { + order: 37; +} + +button:is(textarea) { + order: 37; +} + input:hover, input:focus, button:hover, button:focus { to-clone: 1; } diff --git a/plugins/postcss-is-pseudo-class/test/basic.expect.css b/plugins/postcss-is-pseudo-class/test/basic.expect.css index c447d4d08..0406d2bcd 100644 --- a/plugins/postcss-is-pseudo-class/test/basic.expect.css +++ b/plugins/postcss-is-pseudo-class/test/basic.expect.css @@ -74,7 +74,7 @@ foo[baz=":is(.some, .other)"], .ok { order: 10; } -.pre:is(.alpha > .beta) + :is(:focus > .beta) { +.alpha > .beta.pre + :is(:focus > .beta) { order: 11; } @@ -82,15 +82,15 @@ foo[baz=":is(.some, .other)"], .ok { order: 12; } -.post:is(.alpha > .beta) + :is(:focus > .beta) { +.alpha > .beta.post + :is(:focus > .beta) { order: 13; } -:is(.alpha > .beta) .post + :is(:focus > .beta) { +.alpha > .beta .post + :is(:focus > .beta) { order: 14; } -:is(.alpha ~ .delta):focus > .beta + .beta { +.alpha ~ .delta:focus > .beta + .beta { order: 15; } @@ -98,7 +98,7 @@ foo[baz=":is(.some, .other)"], .ok { order: 16; } -:is(.alpha > .beta) ~ :is(:focus > .beta) { +.alpha:focus > .beta ~ .beta { order: 17; } @@ -110,7 +110,7 @@ foo[baz=":is(.some, .other)"], .ok { order: 19; } -.pre.alpha:is(.one > .two) { +.one > .two.pre.alpha { order: 20; } @@ -238,6 +238,22 @@ ol:hover > li.foo, ol:hover > li.bar, ol:focus > li.foo, ol:focus > li.bar, ul:h order: 36; } +input:is(select) { + order: 37; +} + +input:is(textarea) { + order: 37; +} + +button:is(select) { + order: 37; +} + +button:is(textarea) { + order: 37; +} + input:hover, input:focus, button:hover, button:focus { to-clone: 1; } 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 c447d4d08..0406d2bcd 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 @@ -74,7 +74,7 @@ foo[baz=":is(.some, .other)"], .ok { order: 10; } -.pre:is(.alpha > .beta) + :is(:focus > .beta) { +.alpha > .beta.pre + :is(:focus > .beta) { order: 11; } @@ -82,15 +82,15 @@ foo[baz=":is(.some, .other)"], .ok { order: 12; } -.post:is(.alpha > .beta) + :is(:focus > .beta) { +.alpha > .beta.post + :is(:focus > .beta) { order: 13; } -:is(.alpha > .beta) .post + :is(:focus > .beta) { +.alpha > .beta .post + :is(:focus > .beta) { order: 14; } -:is(.alpha ~ .delta):focus > .beta + .beta { +.alpha ~ .delta:focus > .beta + .beta { order: 15; } @@ -98,7 +98,7 @@ foo[baz=":is(.some, .other)"], .ok { order: 16; } -:is(.alpha > .beta) ~ :is(:focus > .beta) { +.alpha:focus > .beta ~ .beta { order: 17; } @@ -110,7 +110,7 @@ foo[baz=":is(.some, .other)"], .ok { order: 19; } -.pre.alpha:is(.one > .two) { +.one > .two.pre.alpha { order: 20; } @@ -238,6 +238,22 @@ ol:hover > li.foo, ol:hover > li.bar, ol:focus > li.foo, ol:focus > li.bar, ul:h order: 36; } +input:is(select) { + order: 37; +} + +input:is(textarea) { + order: 37; +} + +button:is(select) { + order: 37; +} + +button:is(textarea) { + order: 37; +} + input:hover, input:focus, button:hover, button:focus { to-clone: 1; } 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 c447d4d08..0406d2bcd 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 @@ -74,7 +74,7 @@ foo[baz=":is(.some, .other)"], .ok { order: 10; } -.pre:is(.alpha > .beta) + :is(:focus > .beta) { +.alpha > .beta.pre + :is(:focus > .beta) { order: 11; } @@ -82,15 +82,15 @@ foo[baz=":is(.some, .other)"], .ok { order: 12; } -.post:is(.alpha > .beta) + :is(:focus > .beta) { +.alpha > .beta.post + :is(:focus > .beta) { order: 13; } -:is(.alpha > .beta) .post + :is(:focus > .beta) { +.alpha > .beta .post + :is(:focus > .beta) { order: 14; } -:is(.alpha ~ .delta):focus > .beta + .beta { +.alpha ~ .delta:focus > .beta + .beta { order: 15; } @@ -98,7 +98,7 @@ foo[baz=":is(.some, .other)"], .ok { order: 16; } -:is(.alpha > .beta) ~ :is(:focus > .beta) { +.alpha:focus > .beta ~ .beta { order: 17; } @@ -110,7 +110,7 @@ foo[baz=":is(.some, .other)"], .ok { order: 19; } -.pre.alpha:is(.one > .two) { +.one > .two.pre.alpha { order: 20; } @@ -238,6 +238,22 @@ ol:hover > li.foo, ol:hover > li.bar, ol:focus > li.foo, ol:focus > li.bar, ul:h order: 36; } +input:is(select) { + order: 37; +} + +input:is(textarea) { + order: 37; +} + +button:is(select) { + order: 37; +} + +button:is(textarea) { + order: 37; +} + input:hover, input:focus, button:hover, button:focus { to-clone: 1; } 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 11181888d..f91c6b858 100644 --- a/plugins/postcss-is-pseudo-class/test/basic.preserve.expect.css +++ b/plugins/postcss-is-pseudo-class/test/basic.preserve.expect.css @@ -110,6 +110,10 @@ foo[baz=":is(.some, .other)"], .ok { order: 10; } +.alpha > .beta.pre + :is(:focus > .beta) { + order: 11; +} + .pre:is(.alpha > .beta) + :is(:focus > .beta) { order: 11; } @@ -118,7 +122,7 @@ foo[baz=":is(.some, .other)"], .ok { order: 12; } -.post:is(.alpha > .beta) + :is(:focus > .beta) { +.alpha > .beta.post + :is(:focus > .beta) { order: 13; } @@ -126,11 +130,15 @@ foo[baz=":is(.some, .other)"], .ok { order: 13; } +.alpha > .beta .post + :is(:focus > .beta) { + order: 14; +} + :is(.alpha > .beta) .post + :is(:focus > .beta) { order: 14; } -:is(.alpha ~ .delta):focus > .beta + .beta { +.alpha ~ .delta:focus > .beta + .beta { order: 15; } @@ -146,6 +154,10 @@ foo[baz=":is(.some, .other)"], .ok { order: 16; } +.alpha:focus > .beta ~ .beta { + order: 17; +} + :is(.alpha > .beta) ~ :is(:focus > .beta) { order: 17; } @@ -166,6 +178,10 @@ foo[baz=":is(.some, .other)"], .ok { order: 19; } +.one > .two.pre.alpha { + order: 20; +} + .pre.alpha:is(.one > .two) { order: 20; } @@ -350,6 +366,26 @@ ol:hover > li.foo, ol:hover > li.bar, ol:focus > li.foo, ol:focus > li.bar, ul:h order: 36; } +input:is(select) { + order: 37; +} + +input:is(textarea) { + order: 37; +} + +button:is(select) { + order: 37; +} + +button:is(textarea) { + order: 37; +} + +:is(input, button):is(select, textarea) { + order: 37; +} + input:hover, input:focus, button:hover, button:focus { to-clone: 1; } 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 b8db38ea5..1a7c0c143 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 @@ -110,6 +110,10 @@ foo[baz=":is(.some, .other)"], .ok { order: 10; } +.alpha > .beta.pre + :is(:focus > .beta) { + order: 11; +} + .pre:is(.alpha > .beta) + :is(:focus > .beta) { order: 11; } @@ -118,7 +122,7 @@ foo[baz=":is(.some, .other)"], .ok { order: 12; } -.post:is(.alpha > .beta) + :is(:focus > .beta) { +.alpha > .beta.post + :is(:focus > .beta) { order: 13; } @@ -126,11 +130,15 @@ foo[baz=":is(.some, .other)"], .ok { order: 13; } +.alpha > .beta .post + :is(:focus > .beta) { + order: 14; +} + :is(.alpha > .beta) .post + :is(:focus > .beta) { order: 14; } -:is(.alpha ~ .delta):focus > .beta + .beta { +.alpha ~ .delta:focus > .beta + .beta { order: 15; } @@ -146,6 +154,10 @@ foo[baz=":is(.some, .other)"], .ok { order: 16; } +.alpha:focus > .beta ~ .beta { + order: 17; +} + :is(.alpha > .beta) ~ :is(:focus > .beta) { order: 17; } @@ -166,6 +178,10 @@ foo[baz=":is(.some, .other)"], .ok { order: 19; } +.one > .two.pre.alpha { + order: 20; +} + .pre.alpha:is(.one > .two) { order: 20; } @@ -350,6 +366,26 @@ ol:hover > li.foo, ol:hover > li.bar, ol:focus > li.foo, ol:focus > li.bar, ul:h order: 36; } +input:is(select) { + order: 37; +} + +input:is(textarea) { + order: 37; +} + +button:is(select) { + order: 37; +} + +button:is(textarea) { + order: 37; +} + +:is(input, button):is(select, textarea) { + order: 37; +} + input:hover, input:focus, button:hover, button:focus { cloned: 1; to-clone: 1; 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 new file mode 100644 index 000000000..1cf6476e8 --- /dev/null +++ b/plugins/postcss-is-pseudo-class/test/compound-after-complex-is.css @@ -0,0 +1,76 @@ +:is(.a .b).c { + order: 0.1; +} + +.ignore :is(.a .b).c { + order: 0.2; +} + +:is(.a .b).c .d { + order: 0.3; +} + +:is(.a .b) .c { + order: 1.1; +} + +.ignore :is(.a .b) .c { + order: 1.2; +} + +:is(.a .b) .c .d { + order: 1.3; +} + +:is(.a > .b) > .c { + order: 2.1; +} + +.ignore > :is(.a > .b) > .c { + order: 2.2; +} + +:is(.a > .b) > .c > .d { + order: 2.3; +} + +:is(.a + .b) + .c { + order: 3.1; +} + +.ignore + :is(.a + .b) + .c { + order: 3.2; +} + +:is(.a + .b) + .c + .d { + order: 3.3; +} + +:is(.a ~ .b) ~ .c { + order: 4.1; +} + +.ignore ~ :is(.a ~ .b) ~ .c { + order: 4.2; +} + +:is(.a ~ .b) ~ .c ~ .d { + order: 4.3; +} + +.c:is(.a .b) .d { + order: 5.0; +} + +c:is(a b) d { + order: 5.1; +} + +/* https: //github.com/csstools/postcss-plugins/issues/922 */ +:is(.frame, .frame > .wrapper) > .target { + background-color: #ffc; +} + +:is(:-csstools-matches(.a, .b)) > .c { + order: 100; +} 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 new file mode 100644 index 000000000..a18a7e605 --- /dev/null +++ b/plugins/postcss-is-pseudo-class/test/compound-after-complex-is.expect.css @@ -0,0 +1,76 @@ +.a .b.c { + order: 0.1; +} + +.ignore .c:is(.a .b) { + order: 0.2; +} + +.a .b.c .d { + order: 0.3; +} + +.a .b .c { + order: 1.1; +} + +.ignore :is(.a .b) .c { + order: 1.2; +} + +.a .b .c .d { + order: 1.3; +} + +.a > .b > .c { + order: 2.1; +} + +.ignore > :is(.a > .b) > .c { + order: 2.2; +} + +.a > .b > .c > .d { + order: 2.3; +} + +.a + .b + .c { + order: 3.1; +} + +.ignore + :is(.a + .b) + .c { + order: 3.2; +} + +.a + .b + .c + .d { + order: 3.3; +} + +.a ~ .b ~ .c { + order: 4.1; +} + +.ignore ~ :is(.a ~ .b) ~ .c { + order: 4.2; +} + +.a ~ .b ~ .c ~ .d { + order: 4.3; +} + +.a .b.c .d { + order: 5.0; +} + +a b:is(c) d { + order: 5.1; +} + +/* https: //github.com/csstools/postcss-plugins/issues/922 */ +.frame:not(.does-not-exist) > .target, .frame > .wrapper > .target { + background-color: #ffc; +} + +.a > .c, .b > .c { + order: 100; +}