Skip to content

Commit 6a58ace

Browse files
authored
has pseudo : fix cleanup of rules in browsers with native support (#751)
* has pseudo : fix cleanup of rules in browsers with native support * changelog
1 parent 23a85d9 commit 6a58ace

11 files changed

+18
-23
lines changed

plugins/css-has-pseudo/CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
- Fix: Do not throw when a selector is invalid, show a warning instead.
77
- Fix: make `:has()` unforgiving. `:has(.foo, :some-invalid-selector)` will no longer match elements that have children with `.foo`.
88

9+
### 4.0.2 (December 12, 2022)
10+
11+
- Fix: correctly cleanup style rules when a browser has native support. [backported](https://github.com/csstools/postcss-plugins/pull/752)
12+
913
### 4.0.1 (August 23, 2022)
1014

1115
- Fix: assign global browser polyfill to `window`, `self` or a blank object.

plugins/css-has-pseudo/dist/browser-global.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

plugins/css-has-pseudo/dist/browser-global.js.map

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

plugins/css-has-pseudo/dist/browser.cjs

+1-1
Large diffs are not rendered by default.

plugins/css-has-pseudo/dist/browser.cjs.map

+1-1
Large diffs are not rendered by default.

plugins/css-has-pseudo/dist/browser.mjs

+1-1
Large diffs are not rendered by default.

plugins/css-has-pseudo/dist/browser.mjs.map

+1-1
Large diffs are not rendered by default.

plugins/css-has-pseudo/dist/index.cjs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
"use strict";var e=require("postcss-selector-parser"),t=require("@csstools/selector-specificity"),s=require("postcss-value-parser");function encodeCSS(e){if(""===e)return"";let t,s="";for(let o=0;o<e.length;o++)t=e.charCodeAt(o).toString(36),s+=0===o?t:"-"+t;return"csstools-has-"+s}function isGuardedByAtSupportsFromAtRuleParams(e){if(!e.toLowerCase().includes(":has("))return!1;let t=!1;try{const o=new Set;s(e).walk((e=>{if("function"===e.type&&"selector"===e.value.toLowerCase())return o.add(s.stringify(e.nodes)),!1})),o.forEach((e=>{selectorContainsHasPseudo(e)&&(t=!0)}))}catch(e){}return t}function selectorContainsHasPseudo(t){if(!t.toLowerCase().includes(":has("))return!1;let s=!1;try{e().astSync(t).walk((e=>{if("pseudo"===e.type&&":has"===e.value.toLowerCase()&&e.nodes&&e.nodes.length>0)return s=!0,!1}))}catch(e){}return s}const creator=s=>{const o={preserve:!0,specificityMatchingName:"does-not-exist",...s||{}},r=":not(#"+o.specificityMatchingName+")",n=":not(."+o.specificityMatchingName+")",a=":not("+o.specificityMatchingName+")";return{postcssPlugin:"css-has-pseudo-experimental",RuleExit:(s,{result:c})=>{if(!s.selector.toLowerCase().includes(":has(")||isWithinSupportCheck(s))return;const i=s.selectors.map((i=>{if(!i.toLowerCase().includes(":has("))return i;let l;try{l=e().astSync(i)}catch(e){return s.warn(c,`Failed to parse selector : "${i}" with message: "${e.message}"`),i}if(void 0===l)return i;l.walkPseudos((t=>{let s=t.parent,r=!1;for(;s;)e.isPseudoClass(s)&&":has"===s.value.toLowerCase()&&(r=!0),s=s.parent;r&&(":visited"===t.value.toLowerCase()&&t.replaceWith(e.className({value:o.specificityMatchingName})),":any-link"===t.value.toLowerCase()&&(t.value=":link"))})),l.walkPseudos((s=>{if(":has"!==s.value.toLowerCase()||!s.nodes)return;let o=s.parent??s;if(o!==s){let t=o.nodes.length;e:for(let s=0;s<o.nodes.length;s++){const r=o.nodes[s];if(e.isPseudoElement(r))for(let e=s-1;e>=0;e--)if("combinator"!==o.nodes[s].type&&"comment"!==o.nodes[s].type){t=e+1;break e}}if(t<o.nodes.length){const s=e.selector({value:"",nodes:[]});o.nodes.slice(0,t).forEach((e=>{delete e.parent,s.append(e)}));const r=e.selector({value:"",nodes:[]});o.nodes.slice(t).forEach((e=>{delete e.parent,r.append(e)}));const n=e.selector({value:"",nodes:[]});n.append(s),n.append(r),o.replaceWith(n),o=s}}const c="["+encodeCSS(o.toString())+"]",i=t.selectorSpecificity(o);let l=c;for(let e=0;e<i.a;e++)l+=r;const u=Math.max(1,i.b)-1;for(let e=0;e<u;e++)l+=n;for(let e=0;e<i.c;e++)l+=a;const p=e().astSync(l);o.replaceWith(p.nodes[0])}));const u=l.toString();return u!==i?".js-has-pseudo "+u:i}));i.join(",")!==s.selectors.join(",")&&(s.cloneBefore({selectors:i}),o.preserve||s.remove())}}};function isWithinSupportCheck(e){let t=e.parent;for(;t;){if("atrule"===t.type&&isGuardedByAtSupportsFromAtRuleParams(t.params))return!0;t=t.parent}return!1}creator.postcss=!0,module.exports=creator;
1+
"use strict";var e=require("postcss-selector-parser"),t=require("@csstools/selector-specificity"),s=require("postcss-value-parser");function encodeCSS(e){if(""===e)return"";let t,s="";for(let o=0;o<e.length;o++)t=e.charCodeAt(o).toString(36),s+=0===o?t:"-"+t;return"csstools-has-"+s}function isGuardedByAtSupportsFromAtRuleParams(e){if(!e.toLowerCase().includes(":has("))return!1;let t=!1;try{const o=new Set;s(e).walk((e=>{if("function"===e.type&&"selector"===e.value.toLowerCase())return o.add(s.stringify(e.nodes)),!1})),o.forEach((e=>{selectorContainsHasPseudo(e)&&(t=!0)}))}catch(e){}return t}function selectorContainsHasPseudo(t){if(!t.toLowerCase().includes(":has("))return!1;let s=!1;try{e().astSync(t).walk((e=>{if("pseudo"===e.type&&":has"===e.value.toLowerCase()&&e.nodes&&e.nodes.length>0)return s=!0,!1}))}catch(e){}return s}const creator=s=>{const o={preserve:!0,specificityMatchingName:"does-not-exist",...s||{}},r=":not(#"+o.specificityMatchingName+")",n=":not(."+o.specificityMatchingName+")",a=":not("+o.specificityMatchingName+")";return{postcssPlugin:"css-has-pseudo",RuleExit:(s,{result:c})=>{if(!s.selector.toLowerCase().includes(":has(")||isWithinSupportCheck(s))return;const i=s.selectors.map((i=>{if(!i.toLowerCase().includes(":has("))return i;let l;try{l=e().astSync(i)}catch(e){return s.warn(c,`Failed to parse selector : "${i}" with message: "${e.message}"`),i}if(void 0===l)return i;l.walkPseudos((t=>{let s=t.parent,r=!1;for(;s;)e.isPseudoClass(s)&&":has"===s.value.toLowerCase()&&(r=!0),s=s.parent;r&&(":visited"===t.value.toLowerCase()&&t.replaceWith(e.className({value:o.specificityMatchingName})),":any-link"===t.value.toLowerCase()&&(t.value=":link"))})),l.walkPseudos((s=>{if(":has"!==s.value.toLowerCase()||!s.nodes)return;let o=s.parent??s;if(o!==s){let t=o.nodes.length;e:for(let s=0;s<o.nodes.length;s++){const r=o.nodes[s];if(e.isPseudoElement(r))for(let e=s-1;e>=0;e--)if("combinator"!==o.nodes[s].type&&"comment"!==o.nodes[s].type){t=e+1;break e}}if(t<o.nodes.length){const s=e.selector({value:"",nodes:[]});o.nodes.slice(0,t).forEach((e=>{delete e.parent,s.append(e)}));const r=e.selector({value:"",nodes:[]});o.nodes.slice(t).forEach((e=>{delete e.parent,r.append(e)}));const n=e.selector({value:"",nodes:[]});n.append(s),n.append(r),o.replaceWith(n),o=s}}const c="["+encodeCSS(o.toString())+"]",i=t.selectorSpecificity(o);let l=c;for(let e=0;e<i.a;e++)l+=r;const u=Math.max(1,i.b)-1;for(let e=0;e<u;e++)l+=n;for(let e=0;e<i.c;e++)l+=a;const p=e().astSync(l);o.replaceWith(p.nodes[0])}));const u=l.toString();return u!==i?".js-has-pseudo "+u:i}));i.join(",")!==s.selectors.join(",")&&(s.cloneBefore({selectors:i}),o.preserve||s.remove())}}};function isWithinSupportCheck(e){let t=e.parent;for(;t;){if("atrule"===t.type&&isGuardedByAtSupportsFromAtRuleParams(t.params))return!0;t=t.parent}return!1}creator.postcss=!0,module.exports=creator;

plugins/css-has-pseudo/dist/index.mjs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
import e from"postcss-selector-parser";import{selectorSpecificity as t}from"@csstools/selector-specificity";import s from"postcss-value-parser";function encodeCSS(e){if(""===e)return"";let t,s="";for(let o=0;o<e.length;o++)t=e.charCodeAt(o).toString(36),s+=0===o?t:"-"+t;return"csstools-has-"+s}function isGuardedByAtSupportsFromAtRuleParams(e){if(!e.toLowerCase().includes(":has("))return!1;let t=!1;try{const o=new Set;s(e).walk((e=>{if("function"===e.type&&"selector"===e.value.toLowerCase())return o.add(s.stringify(e.nodes)),!1})),o.forEach((e=>{selectorContainsHasPseudo(e)&&(t=!0)}))}catch(e){}return t}function selectorContainsHasPseudo(t){if(!t.toLowerCase().includes(":has("))return!1;let s=!1;try{e().astSync(t).walk((e=>{if("pseudo"===e.type&&":has"===e.value.toLowerCase()&&e.nodes&&e.nodes.length>0)return s=!0,!1}))}catch(e){}return s}const creator=s=>{const o={preserve:!0,specificityMatchingName:"does-not-exist",...s||{}},r=":not(#"+o.specificityMatchingName+")",n=":not(."+o.specificityMatchingName+")",a=":not("+o.specificityMatchingName+")";return{postcssPlugin:"css-has-pseudo-experimental",RuleExit:(s,{result:c})=>{if(!s.selector.toLowerCase().includes(":has(")||isWithinSupportCheck(s))return;const i=s.selectors.map((i=>{if(!i.toLowerCase().includes(":has("))return i;let l;try{l=e().astSync(i)}catch(e){return s.warn(c,`Failed to parse selector : "${i}" with message: "${e.message}"`),i}if(void 0===l)return i;l.walkPseudos((t=>{let s=t.parent,r=!1;for(;s;)e.isPseudoClass(s)&&":has"===s.value.toLowerCase()&&(r=!0),s=s.parent;r&&(":visited"===t.value.toLowerCase()&&t.replaceWith(e.className({value:o.specificityMatchingName})),":any-link"===t.value.toLowerCase()&&(t.value=":link"))})),l.walkPseudos((s=>{if(":has"!==s.value.toLowerCase()||!s.nodes)return;let o=s.parent??s;if(o!==s){let t=o.nodes.length;e:for(let s=0;s<o.nodes.length;s++){const r=o.nodes[s];if(e.isPseudoElement(r))for(let e=s-1;e>=0;e--)if("combinator"!==o.nodes[s].type&&"comment"!==o.nodes[s].type){t=e+1;break e}}if(t<o.nodes.length){const s=e.selector({value:"",nodes:[]});o.nodes.slice(0,t).forEach((e=>{delete e.parent,s.append(e)}));const r=e.selector({value:"",nodes:[]});o.nodes.slice(t).forEach((e=>{delete e.parent,r.append(e)}));const n=e.selector({value:"",nodes:[]});n.append(s),n.append(r),o.replaceWith(n),o=s}}const c="["+encodeCSS(o.toString())+"]",i=t(o);let l=c;for(let e=0;e<i.a;e++)l+=r;const u=Math.max(1,i.b)-1;for(let e=0;e<u;e++)l+=n;for(let e=0;e<i.c;e++)l+=a;const p=e().astSync(l);o.replaceWith(p.nodes[0])}));const u=l.toString();return u!==i?".js-has-pseudo "+u:i}));i.join(",")!==s.selectors.join(",")&&(s.cloneBefore({selectors:i}),o.preserve||s.remove())}}};function isWithinSupportCheck(e){let t=e.parent;for(;t;){if("atrule"===t.type&&isGuardedByAtSupportsFromAtRuleParams(t.params))return!0;t=t.parent}return!1}creator.postcss=!0;export{creator as default};
1+
import e from"postcss-selector-parser";import{selectorSpecificity as t}from"@csstools/selector-specificity";import s from"postcss-value-parser";function encodeCSS(e){if(""===e)return"";let t,s="";for(let o=0;o<e.length;o++)t=e.charCodeAt(o).toString(36),s+=0===o?t:"-"+t;return"csstools-has-"+s}function isGuardedByAtSupportsFromAtRuleParams(e){if(!e.toLowerCase().includes(":has("))return!1;let t=!1;try{const o=new Set;s(e).walk((e=>{if("function"===e.type&&"selector"===e.value.toLowerCase())return o.add(s.stringify(e.nodes)),!1})),o.forEach((e=>{selectorContainsHasPseudo(e)&&(t=!0)}))}catch(e){}return t}function selectorContainsHasPseudo(t){if(!t.toLowerCase().includes(":has("))return!1;let s=!1;try{e().astSync(t).walk((e=>{if("pseudo"===e.type&&":has"===e.value.toLowerCase()&&e.nodes&&e.nodes.length>0)return s=!0,!1}))}catch(e){}return s}const creator=s=>{const o={preserve:!0,specificityMatchingName:"does-not-exist",...s||{}},r=":not(#"+o.specificityMatchingName+")",n=":not(."+o.specificityMatchingName+")",a=":not("+o.specificityMatchingName+")";return{postcssPlugin:"css-has-pseudo",RuleExit:(s,{result:c})=>{if(!s.selector.toLowerCase().includes(":has(")||isWithinSupportCheck(s))return;const i=s.selectors.map((i=>{if(!i.toLowerCase().includes(":has("))return i;let l;try{l=e().astSync(i)}catch(e){return s.warn(c,`Failed to parse selector : "${i}" with message: "${e.message}"`),i}if(void 0===l)return i;l.walkPseudos((t=>{let s=t.parent,r=!1;for(;s;)e.isPseudoClass(s)&&":has"===s.value.toLowerCase()&&(r=!0),s=s.parent;r&&(":visited"===t.value.toLowerCase()&&t.replaceWith(e.className({value:o.specificityMatchingName})),":any-link"===t.value.toLowerCase()&&(t.value=":link"))})),l.walkPseudos((s=>{if(":has"!==s.value.toLowerCase()||!s.nodes)return;let o=s.parent??s;if(o!==s){let t=o.nodes.length;e:for(let s=0;s<o.nodes.length;s++){const r=o.nodes[s];if(e.isPseudoElement(r))for(let e=s-1;e>=0;e--)if("combinator"!==o.nodes[s].type&&"comment"!==o.nodes[s].type){t=e+1;break e}}if(t<o.nodes.length){const s=e.selector({value:"",nodes:[]});o.nodes.slice(0,t).forEach((e=>{delete e.parent,s.append(e)}));const r=e.selector({value:"",nodes:[]});o.nodes.slice(t).forEach((e=>{delete e.parent,r.append(e)}));const n=e.selector({value:"",nodes:[]});n.append(s),n.append(r),o.replaceWith(n),o=s}}const c="["+encodeCSS(o.toString())+"]",i=t(o);let l=c;for(let e=0;e<i.a;e++)l+=r;const u=Math.max(1,i.b)-1;for(let e=0;e<u;e++)l+=n;for(let e=0;e<i.c;e++)l+=a;const p=e().astSync(l);o.replaceWith(p.nodes[0])}));const u=l.toString();return u!==i?".js-has-pseudo "+u:i}));i.join(",")!==s.selectors.join(",")&&(s.cloneBefore({selectors:i}),o.preserve||s.remove())}}};function isWithinSupportCheck(e){let t=e.parent;for(;t;){if("atrule"===t.type&&isGuardedByAtSupportsFromAtRuleParams(t.params))return!0;t=t.parent}return!1}creator.postcss=!0;export{creator as default};

plugins/css-has-pseudo/src/browser.js

+5-14
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,9 @@ import '@mrhenry/core-web/modules/~element-qsa-has.js';
44
import extractEncodedSelectors from './encode/extract.mjs';
55
import encodeCSS from './encode/encode.mjs';
66

7-
function hasNativeSupport(document) {
7+
function hasNativeSupport() {
88
try {
9-
// Chrome does not support forgiving selector lists in :has()
10-
document.querySelector(':has(*, :does-not-exist, > *)');
11-
document.querySelector(':has(:has(any))');
12-
13-
// Safari incorrectly returns the html element with this query
14-
if (document.querySelector(':has(:scope *)')) {
15-
return false;
16-
}
17-
18-
if (!('CSS' in self) || !('supports' in self.CSS) || !self.CSS.supports(':has(any)')) {
9+
if (!('CSS' in self) || !('supports' in self.CSS) || !self.CSS.supports('selector(:has(div))')) {
1910
return false;
2011
}
2112

@@ -40,7 +31,7 @@ export default function cssHasPseudo(document, options) {
4031
forcePolyfill: (!!options.forcePolyfill) || false,
4132
};
4233

43-
options.mustPolyfill = options.forcePolyfill || !hasNativeSupport(document);
34+
options.mustPolyfill = options.forcePolyfill || !hasNativeSupport();
4435

4536
if (!Array.isArray(options.observedAttributes)) {
4637
options.observedAttributes = [];
@@ -238,7 +229,7 @@ export default function cssHasPseudo(document, options) {
238229
function walkStyleSheet(styleSheet) {
239230
try {
240231
// walk a css rule to collect observed css rules
241-
[].forEach.call(styleSheet.cssRules || [], (rule) => {
232+
[].forEach.call(styleSheet.cssRules || [], (rule, index) => {
242233
if (rule.selectorText) {
243234
rule.selectorText = rule.selectorText.replace(/\.js-has-pseudo\s/g, '');
244235

@@ -250,7 +241,7 @@ export default function cssHasPseudo(document, options) {
250241
}
251242

252243
if (!options.mustPolyfill) {
253-
rule.deleteRule();
244+
styleSheet.deleteRule(index);
254245
return;
255246
}
256247

plugins/css-has-pseudo/src/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const creator: PluginCreator<pluginOptions> = (opts?: pluginOptions) => {
2424
const specificityMatchingNameTag = ':not(' + options.specificityMatchingName + ')';
2525

2626
return {
27-
postcssPlugin: 'css-has-pseudo-experimental',
27+
postcssPlugin: 'css-has-pseudo',
2828
RuleExit: (rule, { result }) => {
2929
if (!rule.selector.toLowerCase().includes(':has(') || isWithinSupportCheck(rule)) {
3030
return;

0 commit comments

Comments
 (0)