diff --git a/experimental/css-has-pseudo/.tape.mjs b/experimental/css-has-pseudo/.tape.mjs index f3b59acde..a7015ef4a 100644 --- a/experimental/css-has-pseudo/.tape.mjs +++ b/experimental/css-has-pseudo/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from '@csstools/css-has-pseudo-experimental'; postcssTape(plugin)({ diff --git a/experimental/postcss-nesting/.tape.mjs b/experimental/postcss-nesting/.tape.mjs index 3e3cde171..959943ca6 100644 --- a/experimental/postcss-nesting/.tape.mjs +++ b/experimental/postcss-nesting/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from '@csstools/postcss-nesting-experimental'; const mixinPluginRule = () => { diff --git a/package-lock.json b/package-lock.json index 3fa8928ed..1d941d575 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8081,7 +8081,7 @@ }, "plugins/postcss-todo-or-die": { "name": "@csstools/postcss-todo-or-die", - "version": "1.0.0", + "version": "0.0.0", "license": "CC0-1.0", "dependencies": { "@csstools/css-parser-algorithms": "^2.0.0", diff --git a/packages/postcss-tape/README.md b/packages/postcss-tape/README.md index b2895838b..2e9d805f9 100644 --- a/packages/postcss-tape/README.md +++ b/packages/postcss-tape/README.md @@ -5,7 +5,7 @@ _Internal package_ See [.tape.mjs](https://github.com/csstools/postcss-plugins/blob/main/plugins/postcss-base-plugin/.tape.mjs) in the base plugin for a minimal example. ```js -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from '@csstools/postcss-base-plugin'; postcssTape(plugin)({ @@ -30,7 +30,7 @@ Browse the source code and tests here or see tests in plugins for more usage det ## After node 20 is released do a find/replace to migrate fully to workspaces : -- `import postcssTape from '../../packages/postcss-tape/dist/index.mjs';` -- `import postcssTape from '@csstools/postcss-tape';` +- `import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs';` +- `import { postcssTape } from '@csstools/postcss-tape';` Then add `@csstools/postcss-tape` the each `package.json` as a dev dependency. diff --git a/packages/postcss-tape/dist/index.cjs b/packages/postcss-tape/dist/index.cjs index 850555173..d7992bdb6 100644 --- a/packages/postcss-tape/dist/index.cjs +++ b/packages/postcss-tape/dist/index.cjs @@ -1 +1 @@ -"use strict";var n=require("path"),e=require("fs"),o=require("assert"),s=require("postcss"),t=require("postcss-8.4"),r=require("postcss-html");function formatGitHubActionAnnotation(e,o="error",s={}){let t="::"+o;const r=Object.keys(s).map((e=>{let o=String(s[e]);return"file"===e&&process.env.GITHUB_WORKSPACE&&(o=n.relative(process.env.GITHUB_WORKSPACE,n.resolve(o))),`${e}=${t=o,t.replace(/\r/g,"%0D").replace(/\n/g,"%0A").replace(/]/g,"%5D").replace(/;/g,"%3B")}`;var t})).join(",");return r&&(t+=` ${r}`),`${t}::${i=e||"",i.replace(/\r/g,"%0D").replace(/\n/g,"%0A")}`;var i}const i="----------------------------------------";function formatCSSAssertError(n,e,o,s=!1){let t="";if(t+=`\n${n}\n\n`,e.message&&(t+=`message :\n ${e.message}\n\n`),e.options)try{t+=`options :\n${JSON.stringify(e.options,null,2)}\n\n`}catch(n){}return t+=`output changed :\n${prettyDiff(o.message)}\n`,s||(t+="\n"+i),t}function formatWarningsAssertError(n,e,o,s,t=!1){let r="";if(r+=`\n${n}\n\n`,e.message&&(r+=`message :\n ${e.message}\n\n`),e.options)try{r+=`options :\n${JSON.stringify(e.options,null,2)}\n\n`}catch(n){}return r+=`unexpected or missing warnings :\n+ actual ${o.length}\n- expected ${s}\n`,t||(o.forEach((n=>{r+=`\n[${n.plugin}]: ${n.text}`})),o.length&&(r+="\n"),r+="\n"+i),r}function prettyDiff(n){return n.replace(/[^\\](\\n)/gm,((n,e)=>n.replace(e," "))).replace(/(\\t)/gm,((n,e)=>n.replace(e," "))).replace(/\+$/gm,"").replace(/^Expected values to be strictly equal:\n/,"")}const noopPlugin=()=>({postcssPlugin:"noop-plugin",Rule(){}});function reduceInformationInCssSyntaxError(n){"CssSyntaxError"!==n.name||process.env.DEBUG||(delete n.source,n.input&&delete n.input.source,delete n.postcssNode)}noopPlugin.postcss=!0;const c=process.env.GITHUB_ACTIONS&&"true"===process.env.ENABLE_ANNOTATIONS_FOR_NODE&&"true"===process.env.ENABLE_ANNOTATIONS_FOR_OS;function postcssSyntax(n){return n.postcssSyntaxHTML?r():null}module.exports=function runner(r){let a=!1;{!0!==r.postcss&&(a=!0,c?console.log(formatGitHubActionAnnotation('postcss flag not set to "true" on exported plugin object',"error",{file:"./package.json",line:1,col:1})):console.error(`\npostcss flag not set to "true"\n\n${i}`));const n=r();n.postcssPlugin&&"string"==typeof n.postcssPlugin||(a=!0,c?console.log(formatGitHubActionAnnotation('plugin name not set via "postcssPlugin"',"error",{file:"./package.json",line:1,col:1})):console.error(`\nplugin name not set via "postcssPlugin"\n\n${i}`));const o=JSON.parse(e.readFileSync("./package.json").toString());o.keywords&&o.keywords.includes("postcss-plugin")||(a=!0,c?console.log(formatGitHubActionAnnotation('package.json does not include "postcss-plugin" keyword',"error",{file:"./package.json",line:1,col:1})):console.error(`\npackage.json does not include "postcss-plugin" keyword\n\n${i}`));const s=["css-has-pseudo","css-blank-pseudo","css-prefers-color-scheme","@csstools/css-has-pseudo-experimental"].includes(o.name);o.name.startsWith("postcss-")||o.name.startsWith("@csstools/postcss-")||s||(a=!0,c?console.log(formatGitHubActionAnnotation('plugin name in package.json does not start with "postcss-"',"error",{file:"./package.json",line:1,col:1})):console.error(`\nplugin name in package.json does not start with "postcss-"\n\n${i}`)),Object.keys(Object(o.dependencies)).includes("postcss")&&!("postcssTapeSelfTest"in r)&&(a=!0,c?console.log(formatGitHubActionAnnotation("postcss should only be a peer and/or dev dependency","error",{file:"./package.json",line:1,col:1})):console.error(`\npostcss should only be a peer and/or dev dependency\n\n${i}`))}return async l=>{const p=new Set;for(const u in l){const g=l[u];g.before&&await g.before();const f=n.join(".","test",u.split(":")[0]),d=n.join(".","test",u.replace(/:/g,"."));let m="css";g.postcssSyntaxHTML&&(m="html");const S=`${f}.${m}`;let A=`${d}.expect.${m}`,$=`${d}.result.${m}`;g.expect&&(A=n.join(".","test",g.expect)),g.result&&($=n.join(".","test",g.result));const y=g.plugins??[r(g.options)],w=await e.promises.readFile(S,"utf8");let h,b="";try{b=await e.promises.readFile(A,"utf8")}catch(e){a=!0,b=!1,c?console.log(formatGitHubActionAnnotation(`${u}\n\nmissing or broken "expect" file: "${n.parse(A).base}"`,"error",{file:S,line:1,col:1})):(p.add(u),console.error(`\n${u}\n\nmissing or broken "expect" file: "${n.parse(A).base}"\n\n${i}`))}let x=!1;try{h=await s(y).process(w,{from:S,to:$,map:{inline:!1,annotation:!1},syntax:postcssSyntax(g)})}catch(n){if(reduceInformationInCssSyntaxError(n),x=!0,g.exception&&g.exception.test(n.message))continue;throw n}!x&&g.exception&&(a=!0,c?console.log(formatGitHubActionAnnotation(`${u}\n\nexpected an exception but got none`,"error",{file:S,line:1,col:1})):(p.add(u),console.error(`\n${u}\n\nexpected an exception but got none\n\n${i}`)));const E=h.css.toString();if(await e.promises.writeFile($,E,"utf8"),process.env.REWRITE_EXPECTS&&e.promises.writeFile(A,E,"utf8"),!1!==b){try{o.strict.strictEqual(E,b)}catch(n){a=!0,c?console.log(formatGitHubActionAnnotation(formatCSSAssertError(u,g,n,!0),"error",{file:A,line:1,col:1})):(p.add(u),console.error(formatCSSAssertError(u,g,n)))}try{if(!g.postcssSyntaxHTML&&h.map.toJSON().sources.includes(""))throw new Error("Sourcemap is broken")}catch(n){a=!0;const e='\nThis is most likely a newly created PostCSS AST Node without a value for "source".\nsee :\n- https://github.com/postcss/postcss/blob/main/docs/guidelines/plugin.md#24-set-nodesource-for-new-nodes\n- https://postcss.org/api/#node-source';c?console.log(formatGitHubActionAnnotation(`${u}\n\nbroken source map: ${JSON.stringify(h.map.toJSON().sources)}\n${e}`,"error",{file:S,line:1,col:1})):(p.add(u),console.error(`\n${u}\n\nbroken source map: ${JSON.stringify(h.map.toJSON().sources)}\n${e}\n\n${i}`))}g.after&&await g.after();try{const n=await e.promises.readFile($,"utf8");if((await s([noopPlugin()]).process(n,{from:$,to:$,map:{inline:!1,annotation:!1},syntax:postcssSyntax(g)})).warnings().length)throw new Error("Unexpected warnings on second pass")}catch(n){a=!0,c?console.log(formatGitHubActionAnnotation(`${u}\n\nresult was not parsable with PostCSS.`,"error",{file:A,line:1,col:1})):(p.add(u),console.error(`\n${u}\n\nresult was not parsable with PostCSS.\n\n${i}`))}if(s([noopPlugin()]).version!==t([noopPlugin()]).version){const n=await t(y).process(w,{from:S,to:$,map:{inline:!1,annotation:!1}});try{o.strict.strictEqual(n.css.toString(),E)}catch(n){reduceInformationInCssSyntaxError(n),a=!0,c?console.log(formatGitHubActionAnnotation("testing older PostCSS:\n"+formatCSSAssertError(u,g,n,!0),"error",{file:A,line:1,col:1})):(p.add(u),console.error("testing older PostCSS:\n"+formatCSSAssertError(u,g,n)))}}try{(h.warnings().length||g.warnings)&&o.strict.strictEqual(h.warnings().length,g.warnings)}catch(n){a=!0,c?console.log(formatGitHubActionAnnotation(formatWarningsAssertError(u,g,h.warnings(),g.warnings,!0),"error",{file:A,line:1,col:1})):(p.add(u),console.error(formatWarningsAssertError(u,g,h.warnings(),g.warnings)))}}}if(p.size){console.error("\nunexpected failures:");for(const n of p.values())console.error(" - "+n)}a&&process.exit(1),console.warn("pass "+r().postcssPlugin)}}; +"use strict";var e=require("path"),n=require("fs"),o=require("assert"),s=require("postcss"),t=require("postcss-8.4"),r=require("postcss-html");function formatGitHubActionAnnotation(n,o="error",s={}){let t="::"+o;const r=Object.keys(s).map((n=>{let o=String(s[n]);return"file"===n&&process.env.GITHUB_WORKSPACE&&(o=e.relative(process.env.GITHUB_WORKSPACE,e.resolve(o))),`${n}=${t=o,t.replace(/\r/g,"%0D").replace(/\n/g,"%0A").replace(/]/g,"%5D").replace(/;/g,"%3B")}`;var t})).join(",");return r&&(t+=` ${r}`),`${t}::${c=n||"",c.replace(/\r/g,"%0D").replace(/\n/g,"%0A")}`;var c}const c="----------------------------------------";function formatCSSAssertError(e,n,o,s=!1){let t="";if(t+=`\n${e}\n\n`,n.message&&(t+=`message :\n ${n.message}\n\n`),n.options)try{t+=`options :\n${JSON.stringify(n.options,null,2)}\n\n`}catch(e){}return t+=`output changed :\n${prettyDiff(o.message)}\n`,s||(t+="\n"+c),t}function formatWarningsAssertError(e,n,o,s,t=!1){let r="";if(r+=`\n${e}\n\n`,n.message&&(r+=`message :\n ${n.message}\n\n`),n.options)try{r+=`options :\n${JSON.stringify(n.options,null,2)}\n\n`}catch(e){}return r+=`unexpected or missing warnings :\n+ actual ${o.length}\n- expected ${s}\n`,t||(o.forEach((e=>{r+=`\n[${e.plugin}]: ${e.text}`})),o.length&&(r+="\n"),r+="\n"+c),r}function prettyDiff(e){return e.replace(/[^\\](\\n)/gm,((e,n)=>e.replace(n," "))).replace(/(\\t)/gm,((e,n)=>e.replace(n," "))).replace(/\+$/gm,"").replace(/^Expected values to be strictly equal:\n/,"")}const noopPlugin=()=>({postcssPlugin:"noop-plugin",Rule(){}});function reduceInformationInCssSyntaxError(e){"CssSyntaxError"!==e.name||process.env.DEBUG||(delete e.source,e.input&&delete e.input.source,delete e.postcssNode)}noopPlugin.postcss=!0;const i=process.env.GITHUB_ACTIONS&&"true"===process.env.ENABLE_ANNOTATIONS_FOR_NODE&&"true"===process.env.ENABLE_ANNOTATIONS_FOR_OS;function postcssSyntax(e){return e.postcssSyntaxHTML?r():null}const a={postcssPlugin:"declaration-cloner",Declaration(e){"to-clone"===e.prop&&e.cloneBefore({prop:"cloned"})}},l={postcssPlugin:"rule-cloner",prepare(){const e=new WeakSet;return{Rule(n){e.has(n)||"to-clone"===n.selector&&(e.add(n),n.cloneBefore({selector:"cloned"}))}}}};exports.declarationClonerPlugin=a,exports.postcssTape=function postcssTape(r){let a=!1;{!0!==r.postcss&&(a=!0,i?console.log(formatGitHubActionAnnotation('postcss flag not set to "true" on exported plugin object',"error",{file:"./package.json",line:1,col:1})):console.error(`\npostcss flag not set to "true"\n\n${c}`));const e=r();e.postcssPlugin&&"string"==typeof e.postcssPlugin||(a=!0,i?console.log(formatGitHubActionAnnotation('plugin name not set via "postcssPlugin"',"error",{file:"./package.json",line:1,col:1})):console.error(`\nplugin name not set via "postcssPlugin"\n\n${c}`));const o=JSON.parse(n.readFileSync("./package.json").toString());o.keywords&&o.keywords.includes("postcss-plugin")||(a=!0,i?console.log(formatGitHubActionAnnotation('package.json does not include "postcss-plugin" keyword',"error",{file:"./package.json",line:1,col:1})):console.error(`\npackage.json does not include "postcss-plugin" keyword\n\n${c}`));const s=["css-has-pseudo","css-blank-pseudo","css-prefers-color-scheme","@csstools/css-has-pseudo-experimental"].includes(o.name);o.name.startsWith("postcss-")||o.name.startsWith("@csstools/postcss-")||s||(a=!0,i?console.log(formatGitHubActionAnnotation('plugin name in package.json does not start with "postcss-"',"error",{file:"./package.json",line:1,col:1})):console.error(`\nplugin name in package.json does not start with "postcss-"\n\n${c}`)),Object.keys(Object(o.dependencies)).includes("postcss")&&!("postcssTapeSelfTest"in r)&&(a=!0,i?console.log(formatGitHubActionAnnotation("postcss should only be a peer and/or dev dependency","error",{file:"./package.json",line:1,col:1})):console.error(`\npostcss should only be a peer and/or dev dependency\n\n${c}`))}return async l=>{const p=new Set;for(const u in l){const g=l[u];g.before&&await g.before();const f=e.join(".","test",u.split(":")[0]),d=e.join(".","test",u.replace(/:/g,"."));let m="css";g.postcssSyntaxHTML&&(m="html");const S=`${f}.${m}`;let A=`${d}.expect.${m}`,$=`${d}.result.${m}`;g.expect&&(A=e.join(".","test",g.expect)),g.result&&($=e.join(".","test",g.result));const w=g.plugins??[r(g.options)],y=await n.promises.readFile(S,"utf8");let h,b="";try{b=await n.promises.readFile(A,"utf8")}catch(n){a=!0,b=!1,i?console.log(formatGitHubActionAnnotation(`${u}\n\nmissing or broken "expect" file: "${e.parse(A).base}"`,"error",{file:S,line:1,col:1})):(p.add(u),console.error(`\n${u}\n\nmissing or broken "expect" file: "${e.parse(A).base}"\n\n${c}`))}let x=!1;try{h=await s(w).process(y,{from:S,to:$,map:{inline:!1,annotation:!1},syntax:postcssSyntax(g)})}catch(e){if(reduceInformationInCssSyntaxError(e),x=!0,g.exception&&g.exception.test(e.message))continue;throw e}!x&&g.exception&&(a=!0,i?console.log(formatGitHubActionAnnotation(`${u}\n\nexpected an exception but got none`,"error",{file:S,line:1,col:1})):(p.add(u),console.error(`\n${u}\n\nexpected an exception but got none\n\n${c}`)));const E=h.css.toString();if(await n.promises.writeFile($,E,"utf8"),process.env.REWRITE_EXPECTS&&n.promises.writeFile(A,E,"utf8"),!1!==b){try{o.strict.strictEqual(E,b)}catch(e){a=!0,i?console.log(formatGitHubActionAnnotation(formatCSSAssertError(u,g,e,!0),"error",{file:A,line:1,col:1})):(p.add(u),console.error(formatCSSAssertError(u,g,e)))}try{if(!g.postcssSyntaxHTML&&h.map.toJSON().sources.includes(""))throw new Error("Sourcemap is broken")}catch(e){a=!0;const n='\nThis is most likely a newly created PostCSS AST Node without a value for "source".\nsee :\n- https://github.com/postcss/postcss/blob/main/docs/guidelines/plugin.md#24-set-nodesource-for-new-nodes\n- https://postcss.org/api/#node-source';i?console.log(formatGitHubActionAnnotation(`${u}\n\nbroken source map: ${JSON.stringify(h.map.toJSON().sources)}\n${n}`,"error",{file:S,line:1,col:1})):(p.add(u),console.error(`\n${u}\n\nbroken source map: ${JSON.stringify(h.map.toJSON().sources)}\n${n}\n\n${c}`))}g.after&&await g.after();try{const e=await n.promises.readFile($,"utf8");if((await s([noopPlugin()]).process(e,{from:$,to:$,map:{inline:!1,annotation:!1},syntax:postcssSyntax(g)})).warnings().length)throw new Error("Unexpected warnings on second pass")}catch(e){a=!0,i?console.log(formatGitHubActionAnnotation(`${u}\n\nresult was not parsable with PostCSS.`,"error",{file:A,line:1,col:1})):(p.add(u),console.error(`\n${u}\n\nresult was not parsable with PostCSS.\n\n${c}`))}if(s([noopPlugin()]).version!==t([noopPlugin()]).version){const e=await t(w).process(y,{from:S,to:$,map:{inline:!1,annotation:!1}});try{o.strict.strictEqual(e.css.toString(),E)}catch(e){reduceInformationInCssSyntaxError(e),a=!0,i?console.log(formatGitHubActionAnnotation("testing older PostCSS:\n"+formatCSSAssertError(u,g,e,!0),"error",{file:A,line:1,col:1})):(p.add(u),console.error("testing older PostCSS:\n"+formatCSSAssertError(u,g,e)))}}try{(h.warnings().length||g.warnings)&&o.strict.strictEqual(h.warnings().length,g.warnings)}catch(e){a=!0,i?console.log(formatGitHubActionAnnotation(formatWarningsAssertError(u,g,h.warnings(),g.warnings,!0),"error",{file:A,line:1,col:1})):(p.add(u),console.error(formatWarningsAssertError(u,g,h.warnings(),g.warnings)))}}}if(p.size){console.error("\nunexpected failures:");for(const e of p.values())console.error(" - "+e)}a&&process.exit(1),console.warn("pass "+r().postcssPlugin)}},exports.ruleClonerPlugin=l; diff --git a/packages/postcss-tape/dist/index.d.ts b/packages/postcss-tape/dist/index.d.ts index 9476ea58d..2c76bb3b2 100644 --- a/packages/postcss-tape/dist/index.d.ts +++ b/packages/postcss-tape/dist/index.d.ts @@ -11,5 +11,15 @@ type TestCaseOptions = { after?: () => void | Promise; postcssSyntaxHTML?: boolean; }; -export default function runner(currentPlugin: PluginCreator): (options: Record) => Promise; +export declare function postcssTape(currentPlugin: PluginCreator): (options: Record) => Promise; +export declare const declarationClonerPlugin: { + postcssPlugin: string; + Declaration(decl: any): void; +}; +export declare const ruleClonerPlugin: { + postcssPlugin: string; + prepare(): { + Rule(rule: any): void; + }; +}; export {}; diff --git a/packages/postcss-tape/dist/index.mjs b/packages/postcss-tape/dist/index.mjs index 935819714..ca91a7f9b 100644 --- a/packages/postcss-tape/dist/index.mjs +++ b/packages/postcss-tape/dist/index.mjs @@ -1 +1 @@ -import n from"path";import o,{promises as e}from"fs";import{strict as t}from"assert";import s from"postcss";import r from"postcss-8.4";import c from"postcss-html";function formatGitHubActionAnnotation(o,e="error",t={}){let s="::"+e;const r=Object.keys(t).map((o=>{let e=String(t[o]);return"file"===o&&process.env.GITHUB_WORKSPACE&&(e=n.relative(process.env.GITHUB_WORKSPACE,n.resolve(e))),`${o}=${s=e,s.replace(/\r/g,"%0D").replace(/\n/g,"%0A").replace(/]/g,"%5D").replace(/;/g,"%3B")}`;var s})).join(",");return r&&(s+=` ${r}`),`${s}::${c=o||"",c.replace(/\r/g,"%0D").replace(/\n/g,"%0A")}`;var c}const i="----------------------------------------";function formatCSSAssertError(n,o,e,t=!1){let s="";if(s+=`\n${n}\n\n`,o.message&&(s+=`message :\n ${o.message}\n\n`),o.options)try{s+=`options :\n${JSON.stringify(o.options,null,2)}\n\n`}catch(n){}return s+=`output changed :\n${prettyDiff(e.message)}\n`,t||(s+="\n"+i),s}function formatWarningsAssertError(n,o,e,t,s=!1){let r="";if(r+=`\n${n}\n\n`,o.message&&(r+=`message :\n ${o.message}\n\n`),o.options)try{r+=`options :\n${JSON.stringify(o.options,null,2)}\n\n`}catch(n){}return r+=`unexpected or missing warnings :\n+ actual ${e.length}\n- expected ${t}\n`,s||(e.forEach((n=>{r+=`\n[${n.plugin}]: ${n.text}`})),e.length&&(r+="\n"),r+="\n"+i),r}function prettyDiff(n){return n.replace(/[^\\](\\n)/gm,((n,o)=>n.replace(o," "))).replace(/(\\t)/gm,((n,o)=>n.replace(o," "))).replace(/\+$/gm,"").replace(/^Expected values to be strictly equal:\n/,"")}const noopPlugin=()=>({postcssPlugin:"noop-plugin",Rule(){}});function reduceInformationInCssSyntaxError(n){"CssSyntaxError"!==n.name||process.env.DEBUG||(delete n.source,n.input&&delete n.input.source,delete n.postcssNode)}noopPlugin.postcss=!0;const a=process.env.GITHUB_ACTIONS&&"true"===process.env.ENABLE_ANNOTATIONS_FOR_NODE&&"true"===process.env.ENABLE_ANNOTATIONS_FOR_OS;function postcssSyntax(n){return n.postcssSyntaxHTML?c():null}function runner(c){let l=!1;{!0!==c.postcss&&(l=!0,a?console.log(formatGitHubActionAnnotation('postcss flag not set to "true" on exported plugin object',"error",{file:"./package.json",line:1,col:1})):console.error(`\npostcss flag not set to "true"\n\n${i}`));const n=c();n.postcssPlugin&&"string"==typeof n.postcssPlugin||(l=!0,a?console.log(formatGitHubActionAnnotation('plugin name not set via "postcssPlugin"',"error",{file:"./package.json",line:1,col:1})):console.error(`\nplugin name not set via "postcssPlugin"\n\n${i}`));const e=JSON.parse(o.readFileSync("./package.json").toString());e.keywords&&e.keywords.includes("postcss-plugin")||(l=!0,a?console.log(formatGitHubActionAnnotation('package.json does not include "postcss-plugin" keyword',"error",{file:"./package.json",line:1,col:1})):console.error(`\npackage.json does not include "postcss-plugin" keyword\n\n${i}`));const t=["css-has-pseudo","css-blank-pseudo","css-prefers-color-scheme","@csstools/css-has-pseudo-experimental"].includes(e.name);e.name.startsWith("postcss-")||e.name.startsWith("@csstools/postcss-")||t||(l=!0,a?console.log(formatGitHubActionAnnotation('plugin name in package.json does not start with "postcss-"',"error",{file:"./package.json",line:1,col:1})):console.error(`\nplugin name in package.json does not start with "postcss-"\n\n${i}`)),Object.keys(Object(e.dependencies)).includes("postcss")&&!("postcssTapeSelfTest"in c)&&(l=!0,a?console.log(formatGitHubActionAnnotation("postcss should only be a peer and/or dev dependency","error",{file:"./package.json",line:1,col:1})):console.error(`\npostcss should only be a peer and/or dev dependency\n\n${i}`))}return async o=>{const p=new Set;for(const u in o){const g=o[u];g.before&&await g.before();const f=n.join(".","test",u.split(":")[0]),m=n.join(".","test",u.replace(/:/g,"."));let d="css";g.postcssSyntaxHTML&&(d="html");const S=`${f}.${d}`;let A=`${m}.expect.${d}`,$=`${m}.result.${d}`;g.expect&&(A=n.join(".","test",g.expect)),g.result&&($=n.join(".","test",g.result));const y=g.plugins??[c(g.options)],w=await e.readFile(S,"utf8");let h,b="";try{b=await e.readFile(A,"utf8")}catch(o){l=!0,b=!1,a?console.log(formatGitHubActionAnnotation(`${u}\n\nmissing or broken "expect" file: "${n.parse(A).base}"`,"error",{file:S,line:1,col:1})):(p.add(u),console.error(`\n${u}\n\nmissing or broken "expect" file: "${n.parse(A).base}"\n\n${i}`))}let x=!1;try{h=await s(y).process(w,{from:S,to:$,map:{inline:!1,annotation:!1},syntax:postcssSyntax(g)})}catch(n){if(reduceInformationInCssSyntaxError(n),x=!0,g.exception&&g.exception.test(n.message))continue;throw n}!x&&g.exception&&(l=!0,a?console.log(formatGitHubActionAnnotation(`${u}\n\nexpected an exception but got none`,"error",{file:S,line:1,col:1})):(p.add(u),console.error(`\n${u}\n\nexpected an exception but got none\n\n${i}`)));const E=h.css.toString();if(await e.writeFile($,E,"utf8"),process.env.REWRITE_EXPECTS&&e.writeFile(A,E,"utf8"),!1!==b){try{t.strictEqual(E,b)}catch(n){l=!0,a?console.log(formatGitHubActionAnnotation(formatCSSAssertError(u,g,n,!0),"error",{file:A,line:1,col:1})):(p.add(u),console.error(formatCSSAssertError(u,g,n)))}try{if(!g.postcssSyntaxHTML&&h.map.toJSON().sources.includes(""))throw new Error("Sourcemap is broken")}catch(n){l=!0;const o='\nThis is most likely a newly created PostCSS AST Node without a value for "source".\nsee :\n- https://github.com/postcss/postcss/blob/main/docs/guidelines/plugin.md#24-set-nodesource-for-new-nodes\n- https://postcss.org/api/#node-source';a?console.log(formatGitHubActionAnnotation(`${u}\n\nbroken source map: ${JSON.stringify(h.map.toJSON().sources)}\n${o}`,"error",{file:S,line:1,col:1})):(p.add(u),console.error(`\n${u}\n\nbroken source map: ${JSON.stringify(h.map.toJSON().sources)}\n${o}\n\n${i}`))}g.after&&await g.after();try{const n=await e.readFile($,"utf8");if((await s([noopPlugin()]).process(n,{from:$,to:$,map:{inline:!1,annotation:!1},syntax:postcssSyntax(g)})).warnings().length)throw new Error("Unexpected warnings on second pass")}catch(n){l=!0,a?console.log(formatGitHubActionAnnotation(`${u}\n\nresult was not parsable with PostCSS.`,"error",{file:A,line:1,col:1})):(p.add(u),console.error(`\n${u}\n\nresult was not parsable with PostCSS.\n\n${i}`))}if(s([noopPlugin()]).version!==r([noopPlugin()]).version){const n=await r(y).process(w,{from:S,to:$,map:{inline:!1,annotation:!1}});try{t.strictEqual(n.css.toString(),E)}catch(n){reduceInformationInCssSyntaxError(n),l=!0,a?console.log(formatGitHubActionAnnotation("testing older PostCSS:\n"+formatCSSAssertError(u,g,n,!0),"error",{file:A,line:1,col:1})):(p.add(u),console.error("testing older PostCSS:\n"+formatCSSAssertError(u,g,n)))}}try{(h.warnings().length||g.warnings)&&t.strictEqual(h.warnings().length,g.warnings)}catch(n){l=!0,a?console.log(formatGitHubActionAnnotation(formatWarningsAssertError(u,g,h.warnings(),g.warnings,!0),"error",{file:A,line:1,col:1})):(p.add(u),console.error(formatWarningsAssertError(u,g,h.warnings(),g.warnings)))}}}if(p.size){console.error("\nunexpected failures:");for(const n of p.values())console.error(" - "+n)}l&&process.exit(1),console.warn("pass "+c().postcssPlugin)}}export{runner as default}; +import o from"path";import n,{promises as e}from"fs";import{strict as t}from"assert";import s from"postcss";import r from"postcss-8.4";import c from"postcss-html";function formatGitHubActionAnnotation(n,e="error",t={}){let s="::"+e;const r=Object.keys(t).map((n=>{let e=String(t[n]);return"file"===n&&process.env.GITHUB_WORKSPACE&&(e=o.relative(process.env.GITHUB_WORKSPACE,o.resolve(e))),`${n}=${s=e,s.replace(/\r/g,"%0D").replace(/\n/g,"%0A").replace(/]/g,"%5D").replace(/;/g,"%3B")}`;var s})).join(",");return r&&(s+=` ${r}`),`${s}::${c=n||"",c.replace(/\r/g,"%0D").replace(/\n/g,"%0A")}`;var c}const i="----------------------------------------";function formatCSSAssertError(o,n,e,t=!1){let s="";if(s+=`\n${o}\n\n`,n.message&&(s+=`message :\n ${n.message}\n\n`),n.options)try{s+=`options :\n${JSON.stringify(n.options,null,2)}\n\n`}catch(o){}return s+=`output changed :\n${prettyDiff(e.message)}\n`,t||(s+="\n"+i),s}function formatWarningsAssertError(o,n,e,t,s=!1){let r="";if(r+=`\n${o}\n\n`,n.message&&(r+=`message :\n ${n.message}\n\n`),n.options)try{r+=`options :\n${JSON.stringify(n.options,null,2)}\n\n`}catch(o){}return r+=`unexpected or missing warnings :\n+ actual ${e.length}\n- expected ${t}\n`,s||(e.forEach((o=>{r+=`\n[${o.plugin}]: ${o.text}`})),e.length&&(r+="\n"),r+="\n"+i),r}function prettyDiff(o){return o.replace(/[^\\](\\n)/gm,((o,n)=>o.replace(n," "))).replace(/(\\t)/gm,((o,n)=>o.replace(n," "))).replace(/\+$/gm,"").replace(/^Expected values to be strictly equal:\n/,"")}const noopPlugin=()=>({postcssPlugin:"noop-plugin",Rule(){}});function reduceInformationInCssSyntaxError(o){"CssSyntaxError"!==o.name||process.env.DEBUG||(delete o.source,o.input&&delete o.input.source,delete o.postcssNode)}noopPlugin.postcss=!0;const a=process.env.GITHUB_ACTIONS&&"true"===process.env.ENABLE_ANNOTATIONS_FOR_NODE&&"true"===process.env.ENABLE_ANNOTATIONS_FOR_OS;function postcssSyntax(o){return o.postcssSyntaxHTML?c():null}function postcssTape(c){let l=!1;{!0!==c.postcss&&(l=!0,a?console.log(formatGitHubActionAnnotation('postcss flag not set to "true" on exported plugin object',"error",{file:"./package.json",line:1,col:1})):console.error(`\npostcss flag not set to "true"\n\n${i}`));const o=c();o.postcssPlugin&&"string"==typeof o.postcssPlugin||(l=!0,a?console.log(formatGitHubActionAnnotation('plugin name not set via "postcssPlugin"',"error",{file:"./package.json",line:1,col:1})):console.error(`\nplugin name not set via "postcssPlugin"\n\n${i}`));const e=JSON.parse(n.readFileSync("./package.json").toString());e.keywords&&e.keywords.includes("postcss-plugin")||(l=!0,a?console.log(formatGitHubActionAnnotation('package.json does not include "postcss-plugin" keyword',"error",{file:"./package.json",line:1,col:1})):console.error(`\npackage.json does not include "postcss-plugin" keyword\n\n${i}`));const t=["css-has-pseudo","css-blank-pseudo","css-prefers-color-scheme","@csstools/css-has-pseudo-experimental"].includes(e.name);e.name.startsWith("postcss-")||e.name.startsWith("@csstools/postcss-")||t||(l=!0,a?console.log(formatGitHubActionAnnotation('plugin name in package.json does not start with "postcss-"',"error",{file:"./package.json",line:1,col:1})):console.error(`\nplugin name in package.json does not start with "postcss-"\n\n${i}`)),Object.keys(Object(e.dependencies)).includes("postcss")&&!("postcssTapeSelfTest"in c)&&(l=!0,a?console.log(formatGitHubActionAnnotation("postcss should only be a peer and/or dev dependency","error",{file:"./package.json",line:1,col:1})):console.error(`\npostcss should only be a peer and/or dev dependency\n\n${i}`))}return async n=>{const p=new Set;for(const u in n){const g=n[u];g.before&&await g.before();const f=o.join(".","test",u.split(":")[0]),d=o.join(".","test",u.replace(/:/g,"."));let m="css";g.postcssSyntaxHTML&&(m="html");const S=`${f}.${m}`;let A=`${d}.expect.${m}`,$=`${d}.result.${m}`;g.expect&&(A=o.join(".","test",g.expect)),g.result&&($=o.join(".","test",g.result));const w=g.plugins??[c(g.options)],y=await e.readFile(S,"utf8");let h,b="";try{b=await e.readFile(A,"utf8")}catch(n){l=!0,b=!1,a?console.log(formatGitHubActionAnnotation(`${u}\n\nmissing or broken "expect" file: "${o.parse(A).base}"`,"error",{file:S,line:1,col:1})):(p.add(u),console.error(`\n${u}\n\nmissing or broken "expect" file: "${o.parse(A).base}"\n\n${i}`))}let x=!1;try{h=await s(w).process(y,{from:S,to:$,map:{inline:!1,annotation:!1},syntax:postcssSyntax(g)})}catch(o){if(reduceInformationInCssSyntaxError(o),x=!0,g.exception&&g.exception.test(o.message))continue;throw o}!x&&g.exception&&(l=!0,a?console.log(formatGitHubActionAnnotation(`${u}\n\nexpected an exception but got none`,"error",{file:S,line:1,col:1})):(p.add(u),console.error(`\n${u}\n\nexpected an exception but got none\n\n${i}`)));const E=h.css.toString();if(await e.writeFile($,E,"utf8"),process.env.REWRITE_EXPECTS&&e.writeFile(A,E,"utf8"),!1!==b){try{t.strictEqual(E,b)}catch(o){l=!0,a?console.log(formatGitHubActionAnnotation(formatCSSAssertError(u,g,o,!0),"error",{file:A,line:1,col:1})):(p.add(u),console.error(formatCSSAssertError(u,g,o)))}try{if(!g.postcssSyntaxHTML&&h.map.toJSON().sources.includes(""))throw new Error("Sourcemap is broken")}catch(o){l=!0;const n='\nThis is most likely a newly created PostCSS AST Node without a value for "source".\nsee :\n- https://github.com/postcss/postcss/blob/main/docs/guidelines/plugin.md#24-set-nodesource-for-new-nodes\n- https://postcss.org/api/#node-source';a?console.log(formatGitHubActionAnnotation(`${u}\n\nbroken source map: ${JSON.stringify(h.map.toJSON().sources)}\n${n}`,"error",{file:S,line:1,col:1})):(p.add(u),console.error(`\n${u}\n\nbroken source map: ${JSON.stringify(h.map.toJSON().sources)}\n${n}\n\n${i}`))}g.after&&await g.after();try{const o=await e.readFile($,"utf8");if((await s([noopPlugin()]).process(o,{from:$,to:$,map:{inline:!1,annotation:!1},syntax:postcssSyntax(g)})).warnings().length)throw new Error("Unexpected warnings on second pass")}catch(o){l=!0,a?console.log(formatGitHubActionAnnotation(`${u}\n\nresult was not parsable with PostCSS.`,"error",{file:A,line:1,col:1})):(p.add(u),console.error(`\n${u}\n\nresult was not parsable with PostCSS.\n\n${i}`))}if(s([noopPlugin()]).version!==r([noopPlugin()]).version){const o=await r(w).process(y,{from:S,to:$,map:{inline:!1,annotation:!1}});try{t.strictEqual(o.css.toString(),E)}catch(o){reduceInformationInCssSyntaxError(o),l=!0,a?console.log(formatGitHubActionAnnotation("testing older PostCSS:\n"+formatCSSAssertError(u,g,o,!0),"error",{file:A,line:1,col:1})):(p.add(u),console.error("testing older PostCSS:\n"+formatCSSAssertError(u,g,o)))}}try{(h.warnings().length||g.warnings)&&t.strictEqual(h.warnings().length,g.warnings)}catch(o){l=!0,a?console.log(formatGitHubActionAnnotation(formatWarningsAssertError(u,g,h.warnings(),g.warnings,!0),"error",{file:A,line:1,col:1})):(p.add(u),console.error(formatWarningsAssertError(u,g,h.warnings(),g.warnings)))}}}if(p.size){console.error("\nunexpected failures:");for(const o of p.values())console.error(" - "+o)}l&&process.exit(1),console.warn("pass "+c().postcssPlugin)}}const l={postcssPlugin:"declaration-cloner",Declaration(o){"to-clone"===o.prop&&o.cloneBefore({prop:"cloned"})}},p={postcssPlugin:"rule-cloner",prepare(){const o=new WeakSet;return{Rule(n){o.has(n)||"to-clone"===n.selector&&(o.add(n),n.cloneBefore({selector:"cloned"}))}}}};export{l as declarationClonerPlugin,postcssTape,p as ruleClonerPlugin}; diff --git a/packages/postcss-tape/src/index.ts b/packages/postcss-tape/src/index.ts index 86e02447d..a04ba46ac 100644 --- a/packages/postcss-tape/src/index.ts +++ b/packages/postcss-tape/src/index.ts @@ -51,7 +51,7 @@ function postcssSyntax(options: TestCaseOptions) { return null; } -export default function runner(currentPlugin: PluginCreator) { +export function postcssTape(currentPlugin: PluginCreator) { let hasErrors = false; // Plugin conforms to https://github.com/postcss/postcss/blob/main/docs/guidelines/plugin.md @@ -416,3 +416,32 @@ export default function runner(currentPlugin: PluginCreator) { console.warn('pass ' + (currentPlugin() as Plugin).postcssPlugin); }; } + +export const declarationClonerPlugin = { + postcssPlugin: 'declaration-cloner', + Declaration(decl) { + if (decl.prop === 'to-clone') { + decl.cloneBefore({ prop: 'cloned' }); + } + }, +}; + +export const ruleClonerPlugin = { + postcssPlugin: 'rule-cloner', + prepare() { + const transformedNodes = new WeakSet(); + + return { + Rule(rule) { + if (transformedNodes.has(rule)) { + return; + } + + if (rule.selector === 'to-clone') { + transformedNodes.add(rule); + rule.cloneBefore({ selector: 'cloned' }); + } + }, + }; + }, +}; diff --git a/packages/postcss-tape/test/basic.before-after.mjs b/packages/postcss-tape/test/basic.before-after.mjs index 61dc60fa9..a6ffb7c03 100644 --- a/packages/postcss-tape/test/basic.before-after.mjs +++ b/packages/postcss-tape/test/basic.before-after.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../dist/index.mjs'; +import { postcssTape } from '../dist/index.mjs'; import plugin from './_a-plugin.mjs'; import { promises as fsp } from 'fs'; diff --git a/packages/postcss-tape/test/basic.break-css.mjs b/packages/postcss-tape/test/basic.break-css.mjs index 472c4b21f..392152984 100644 --- a/packages/postcss-tape/test/basic.break-css.mjs +++ b/packages/postcss-tape/test/basic.break-css.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../dist/index.mjs'; +import { postcssTape } from '../dist/index.mjs'; import plugin from './_a-plugin.mjs'; import { promises as fsp } from 'fs'; diff --git a/packages/postcss-tape/test/basic.broken-sourcemap.mjs b/packages/postcss-tape/test/basic.broken-sourcemap.mjs index f65ad3d24..221d2302d 100644 --- a/packages/postcss-tape/test/basic.broken-sourcemap.mjs +++ b/packages/postcss-tape/test/basic.broken-sourcemap.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../dist/index.mjs'; +import { postcssTape } from '../dist/index.mjs'; import plugin from './_a-plugin.mjs'; diff --git a/packages/postcss-tape/test/basic.mjs b/packages/postcss-tape/test/basic.mjs index 6604c6dcf..96b7a845b 100644 --- a/packages/postcss-tape/test/basic.mjs +++ b/packages/postcss-tape/test/basic.mjs @@ -1,7 +1,6 @@ -import postcssTape from '../dist/index.mjs'; +import { postcssTape } from '../dist/index.mjs'; import plugin from './_a-plugin.mjs'; - postcssTape(plugin)({ 'basic': { message: 'supports basic usage', diff --git a/packages/postcss-tape/test/basic.with-diff-in-expect.mjs b/packages/postcss-tape/test/basic.with-diff-in-expect.mjs index af332f661..9a24ec5f4 100644 --- a/packages/postcss-tape/test/basic.with-diff-in-expect.mjs +++ b/packages/postcss-tape/test/basic.with-diff-in-expect.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../dist/index.mjs'; +import { postcssTape } from '../dist/index.mjs'; import plugin from './_a-plugin.mjs'; diff --git a/packages/postcss-tape/test/basic.with-warnings.mjs b/packages/postcss-tape/test/basic.with-warnings.mjs index e645f0154..f97e8578c 100644 --- a/packages/postcss-tape/test/basic.with-warnings.mjs +++ b/packages/postcss-tape/test/basic.with-warnings.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../dist/index.mjs'; +import { postcssTape } from '../dist/index.mjs'; import plugin from './_a-plugin.mjs'; diff --git a/packages/postcss-tape/test/basic.without-expect.mjs b/packages/postcss-tape/test/basic.without-expect.mjs index 715f4ddfe..717467bcf 100644 --- a/packages/postcss-tape/test/basic.without-expect.mjs +++ b/packages/postcss-tape/test/basic.without-expect.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../dist/index.mjs'; +import { postcssTape } from '../dist/index.mjs'; import plugin from './_a-plugin.mjs'; diff --git a/packages/postcss-tape/test/document.mjs b/packages/postcss-tape/test/document.mjs index 7b1807a54..1eba0cf35 100644 --- a/packages/postcss-tape/test/document.mjs +++ b/packages/postcss-tape/test/document.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../dist/index.mjs'; +import { postcssTape } from '../dist/index.mjs'; import plugin from './_b-plugin.mjs'; postcssTape(plugin)({ diff --git a/plugin-packs/postcss-preset-env/.tape.mjs b/plugin-packs/postcss-preset-env/.tape.mjs index 5af350916..26a436201 100644 --- a/plugin-packs/postcss-preset-env/.tape.mjs +++ b/plugin-packs/postcss-preset-env/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from 'postcss-preset-env'; import postcssImport from 'postcss-import'; diff --git a/plugins/css-blank-pseudo/.tape.mjs b/plugins/css-blank-pseudo/.tape.mjs index 9fc5a4127..966a2cf2c 100644 --- a/plugins/css-blank-pseudo/.tape.mjs +++ b/plugins/css-blank-pseudo/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { declarationClonerPlugin, postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from 'css-blank-pseudo'; postcssTape(plugin)({ @@ -30,6 +30,15 @@ postcssTape(plugin)({ disablePolyfillReadyClass: true } }, + 'basic:with-cloned-declarations': { + message: 'doesn\'t cause duplicate CSS', + plugins: [ + declarationClonerPlugin, + plugin({ + preserve: true + }) + ] + }, 'invalid-selector': { message: 'warns on invalid selectors', warnings: 1 diff --git a/plugins/css-blank-pseudo/CHANGELOG.md b/plugins/css-blank-pseudo/CHANGELOG.md index 7005de0aa..961b61ae7 100644 --- a/plugins/css-blank-pseudo/CHANGELOG.md +++ b/plugins/css-blank-pseudo/CHANGELOG.md @@ -1,5 +1,9 @@ # Changes to CSS Blank Pseudo +### Unreleased (patch) + +- Reduce the amount of duplicate fallback CSS. + ### 5.0.1 (January 28, 2023) - Improve `types` declaration in `package.json` diff --git a/plugins/css-blank-pseudo/README.md b/plugins/css-blank-pseudo/README.md index b1a826777..4c60a7fdb 100644 --- a/plugins/css-blank-pseudo/README.md +++ b/plugins/css-blank-pseudo/README.md @@ -121,7 +121,7 @@ input:blank { /* becomes */ -input[blank], input[blank] { +input[blank] { background-color: yellow; } input:blank { diff --git a/plugins/css-blank-pseudo/dist/index.cjs b/plugins/css-blank-pseudo/dist/index.cjs index 4788a578b..59e1898a0 100644 --- a/plugins/css-blank-pseudo/dist/index.cjs +++ b/plugins/css-blank-pseudo/dist/index.cjs @@ -1 +1 @@ -"use strict";var e=require("postcss-selector-parser");const s=[" ",">","~",":","+","@","#","(",")"];function isValidReplacement(e){let n=!0;for(let l=0,t=s.length;l-1&&(n=!1);return n}const n="js-blank-pseudo",l=":blank",creator=s=>{const t=Object.assign({preserve:!0,replaceWith:"[blank]",disablePolyfillReadyClass:!1},s),o=e().astSync(t.replaceWith);return isValidReplacement(t.replaceWith)?{postcssPlugin:"css-blank-pseudo",Rule(s,{result:r}){if(!s.selector.toLowerCase().includes(l))return;const a=s.selectors.flatMap((a=>{if(!a.toLowerCase().includes(l))return[a];let i;try{i=e().astSync(a)}catch(e){return s.warn(r,`Failed to parse selector : "${a}" with message: "${e.message}"`),[a]}if(void 0===i)return[a];let c=!1;if(i.walkPseudos((e=>{e.value.toLowerCase()===l&&(e.nodes&&e.nodes.length||(c=!0,e.replaceWith(o.clone({}))))})),!c)return[a];const d=i.clone();if(!t.disablePolyfillReadyClass){var u,p,f,m,b;if(null!=(u=i.nodes)&&null!=(p=u[0])&&null!=(f=p.nodes)&&f.length)for(let s=0;s{e.warn(s,`${t.replaceWith} is not a valid replacement since it can't be applied to single elements.`)}}};creator.postcss=!0,module.exports=creator; +"use strict";var e=require("postcss-selector-parser");const s=[" ",">","~",":","+","@","#","(",")"];function isValidReplacement(e){let n=!0;for(let t=0,l=s.length;t-1&&(n=!1);return n}const n="js-blank-pseudo",t=":blank",creator=s=>{const l=Object.assign({preserve:!0,replaceWith:"[blank]",disablePolyfillReadyClass:!1},s),r=e().astSync(l.replaceWith);return isValidReplacement(l.replaceWith)?{postcssPlugin:"css-blank-pseudo",prepare(){const s=new WeakSet;return{Rule(o,{result:a}){if(s.has(o))return;if(!o.selector.toLowerCase().includes(t))return;const i=o.selectors.flatMap((s=>{if(!s.toLowerCase().includes(t))return[s];let i;try{i=e().astSync(s)}catch(e){return o.warn(a,`Failed to parse selector : "${s}" with message: "${e.message}"`),[s]}if(void 0===i)return[s];let c=!1;if(i.walkPseudos((e=>{e.value.toLowerCase()===t&&(e.nodes&&e.nodes.length||(c=!0,e.replaceWith(r.clone({}))))})),!c)return[s];const d=i.clone();if(!l.disablePolyfillReadyClass){var u,p,f,g,m;if(null!=(u=i.nodes)&&null!=(p=u[0])&&null!=(f=p.nodes)&&f.length)for(let s=0;s{e.warn(s,`${l.replaceWith} is not a valid replacement since it can't be applied to single elements.`)}}};creator.postcss=!0,module.exports=creator; diff --git a/plugins/css-blank-pseudo/dist/index.mjs b/plugins/css-blank-pseudo/dist/index.mjs index fd280f565..abf8967d5 100644 --- a/plugins/css-blank-pseudo/dist/index.mjs +++ b/plugins/css-blank-pseudo/dist/index.mjs @@ -1 +1 @@ -import e from"postcss-selector-parser";const s=[" ",">","~",":","+","@","#","(",")"];function isValidReplacement(e){let n=!0;for(let l=0,t=s.length;l-1&&(n=!1);return n}const n="js-blank-pseudo",l=":blank",creator=s=>{const t=Object.assign({preserve:!0,replaceWith:"[blank]",disablePolyfillReadyClass:!1},s),o=e().astSync(t.replaceWith);return isValidReplacement(t.replaceWith)?{postcssPlugin:"css-blank-pseudo",Rule(s,{result:a}){if(!s.selector.toLowerCase().includes(l))return;const r=s.selectors.flatMap((r=>{if(!r.toLowerCase().includes(l))return[r];let i;try{i=e().astSync(r)}catch(e){return s.warn(a,`Failed to parse selector : "${r}" with message: "${e.message}"`),[r]}if(void 0===i)return[r];let c=!1;if(i.walkPseudos((e=>{e.value.toLowerCase()===l&&(e.nodes&&e.nodes.length||(c=!0,e.replaceWith(o.clone({}))))})),!c)return[r];const d=i.clone();if(!t.disablePolyfillReadyClass){var u,p,f,m,b;if(null!=(u=i.nodes)&&null!=(p=u[0])&&null!=(f=p.nodes)&&f.length)for(let s=0;s{e.warn(s,`${t.replaceWith} is not a valid replacement since it can't be applied to single elements.`)}}};creator.postcss=!0;export{creator as default}; +import e from"postcss-selector-parser";const s=[" ",">","~",":","+","@","#","(",")"];function isValidReplacement(e){let n=!0;for(let t=0,l=s.length;t-1&&(n=!1);return n}const n="js-blank-pseudo",t=":blank",creator=s=>{const l=Object.assign({preserve:!0,replaceWith:"[blank]",disablePolyfillReadyClass:!1},s),o=e().astSync(l.replaceWith);return isValidReplacement(l.replaceWith)?{postcssPlugin:"css-blank-pseudo",prepare(){const s=new WeakSet;return{Rule(r,{result:a}){if(s.has(r))return;if(!r.selector.toLowerCase().includes(t))return;const i=r.selectors.flatMap((s=>{if(!s.toLowerCase().includes(t))return[s];let i;try{i=e().astSync(s)}catch(e){return r.warn(a,`Failed to parse selector : "${s}" with message: "${e.message}"`),[s]}if(void 0===i)return[s];let c=!1;if(i.walkPseudos((e=>{e.value.toLowerCase()===t&&(e.nodes&&e.nodes.length||(c=!0,e.replaceWith(o.clone({}))))})),!c)return[s];const d=i.clone();if(!l.disablePolyfillReadyClass){var u,p,f,m,g;if(null!=(u=i.nodes)&&null!=(p=u[0])&&null!=(f=p.nodes)&&f.length)for(let s=0;s{e.warn(s,`${l.replaceWith} is not a valid replacement since it can't be applied to single elements.`)}}};creator.postcss=!0;export{creator as default}; diff --git a/plugins/css-blank-pseudo/src/index.ts b/plugins/css-blank-pseudo/src/index.ts index fbc089e24..ae3f4dc82 100644 --- a/plugins/css-blank-pseudo/src/index.ts +++ b/plugins/css-blank-pseudo/src/index.ts @@ -42,90 +42,103 @@ const creator: PluginCreator = (opts?: pluginOptions) => { return { postcssPlugin: 'css-blank-pseudo', - Rule(rule, { result }) { - if (!rule.selector.toLowerCase().includes(PSEUDO)) { - return; - } - - const selectors = rule.selectors.flatMap((selector) => { - if (!selector.toLowerCase().includes(PSEUDO)) { - return [selector]; - } - - let selectorAST; - - try { - selectorAST = parser().astSync(selector); - } catch (err) { - rule.warn(result, `Failed to parse selector : "${selector}" with message: "${err.message}"`); - return [selector]; - } - - if (typeof selectorAST === 'undefined') { - return [selector]; - } - - let containsPseudo = false; - selectorAST.walkPseudos((pseudo) => { - if (pseudo.value.toLowerCase() !== PSEUDO) { + prepare() { + const transformedNodes = new WeakSet(); + + return { + Rule(rule, { result }) { + if (transformedNodes.has(rule)) { return; } - if (pseudo.nodes && pseudo.nodes.length) { + if (!rule.selector.toLowerCase().includes(PSEUDO)) { return; } - containsPseudo = true; - pseudo.replaceWith(replacementAST.clone({})); - }); - - if (!containsPseudo) { - return [selector]; - } - - const selectorASTClone = selectorAST.clone(); - - // html > .foo:focus-within - // becomes: - // html.js-blank-pseudo > .foo:focus-within, - // .js-blank-pseudo html > .foo:focus-within - if (!options.disablePolyfillReadyClass) { - if (selectorAST.nodes?.[0]?.nodes?.length) { - for (let i = 0; i < selectorAST.nodes[0].nodes.length; i++) { - const node = selectorAST.nodes[0].nodes[i]; - if (node.type === 'combinator' || parser.isPseudoElement(node)) { - // Insert the class before the first combinator or pseudo element. - selectorAST.nodes[0].insertBefore(node, parser.className({ value: POLYFILL_READY_CLASSNAME })); - break; + const selectors = rule.selectors.flatMap((selector) => { + if (!selector.toLowerCase().includes(PSEUDO)) { + return [selector]; + } + + let selectorAST; + + try { + selectorAST = parser().astSync(selector); + } catch (err) { + rule.warn(result, `Failed to parse selector : "${selector}" with message: "${err.message}"`); + return [selector]; + } + + if (typeof selectorAST === 'undefined') { + return [selector]; + } + + let containsPseudo = false; + selectorAST.walkPseudos((pseudo) => { + if (pseudo.value.toLowerCase() !== PSEUDO) { + return; } - if (i === selectorAST.nodes[0].nodes.length - 1) { - // Append the class to the end of the selector if not combinator or pseudo element was found. - selectorAST.nodes[0].append(parser.className({ value: POLYFILL_READY_CLASSNAME })); - break; + if (pseudo.nodes && pseudo.nodes.length) { + return; } + + containsPseudo = true; + pseudo.replaceWith(replacementAST.clone({})); + }); + + if (!containsPseudo) { + return [selector]; } - } - if (selectorAST.nodes?.[0]?.nodes) { - // Prepend a space combinator and the class to the beginning of the selector. - selectorASTClone.nodes[0].prepend(parser.combinator({ value: ' ' })); - selectorASTClone.nodes[0].prepend(parser.className({ value: POLYFILL_READY_CLASSNAME })); - } - } + const selectorASTClone = selectorAST.clone(); + + // html > .foo:focus-within + // becomes: + // html.js-blank-pseudo > .foo:focus-within, + // .js-blank-pseudo html > .foo:focus-within + if (!options.disablePolyfillReadyClass) { + if (selectorAST.nodes?.[0]?.nodes?.length) { + for (let i = 0; i < selectorAST.nodes[0].nodes.length; i++) { + const node = selectorAST.nodes[0].nodes[i]; + if (node.type === 'combinator' || parser.isPseudoElement(node)) { + // Insert the class before the first combinator or pseudo element. + selectorAST.nodes[0].insertBefore(node, parser.className({ value: POLYFILL_READY_CLASSNAME })); + break; + } + + if (i === selectorAST.nodes[0].nodes.length - 1) { + // Append the class to the end of the selector if not combinator or pseudo element was found. + selectorAST.nodes[0].append(parser.className({ value: POLYFILL_READY_CLASSNAME })); + break; + } + } + } + + if (selectorAST.nodes?.[0]?.nodes) { + // Prepend a space combinator and the class to the beginning of the selector. + selectorASTClone.nodes[0].prepend(parser.combinator({ value: ' ' })); + selectorASTClone.nodes[0].prepend(parser.className({ value: POLYFILL_READY_CLASSNAME })); + } - return [selectorAST.toString(), selectorASTClone.toString()]; - }); + return [selectorAST.toString(), selectorASTClone.toString()]; + } - if (selectors.join(',') === rule.selectors.join(',')) { - return; - } + return [selectorAST.toString()]; + }); - rule.cloneBefore({ selectors: selectors }); + if (selectors.join(',') === rule.selectors.join(',')) { + return; + } - if (!options.preserve) { - rule.remove(); - } + transformedNodes.add(rule); + rule.cloneBefore({ selectors: selectors }); + + if (!options.preserve) { + rule.remove(); + } + }, + }; }, }; }; diff --git a/plugins/css-blank-pseudo/test/basic.css b/plugins/css-blank-pseudo/test/basic.css index bd42aa82f..b64840c02 100644 --- a/plugins/css-blank-pseudo/test/basic.css +++ b/plugins/css-blank-pseudo/test/basic.css @@ -37,3 +37,7 @@ test:not(:blank) { uppercase :BLaNK { order: 6; } + +:blank { + to-clone: 1; +} diff --git a/plugins/css-blank-pseudo/test/basic.disable-polyfill-ready-class.expect.css b/plugins/css-blank-pseudo/test/basic.disable-polyfill-ready-class.expect.css index 0b1d6c28c..10e85c2cf 100644 --- a/plugins/css-blank-pseudo/test/basic.disable-polyfill-ready-class.expect.css +++ b/plugins/css-blank-pseudo/test/basic.disable-polyfill-ready-class.expect.css @@ -1,4 +1,4 @@ -[blank], [blank] { +[blank] { order: 1; } @@ -7,32 +7,18 @@ } [blank], -[blank], -[blank] test, [blank] test, test [blank], -test [blank], test test[blank], -test test[blank], -test [blank] test, test [blank] test, test test[blank] test, -test test[blank] test, -test [blank] [blank] test, test [blank] [blank] test, test :matches([blank]) test, -test :matches([blank]) test, -test :matches([blank] test) test, test :matches([blank] test) test, test :matches(test [blank]) test, -test :matches(test [blank]) test, -test :matches(test test[blank]) test, test :matches(test test[blank]) test, test :matches(test [blank] test) test, -test :matches(test [blank] test) test, test :matches(test test[blank] test) test, -test :matches(test test[blank] test) test, -test :matches(test [blank] [blank] test) test, test :matches(test [blank] [blank] test) test { order: 2; } @@ -65,7 +51,7 @@ test :matches(test :blank :blank test) test { order: 4; } -test:not([blank]), test:not([blank]) { +test:not([blank]) { order: 5; } @@ -73,10 +59,18 @@ test:not(:blank) { order: 5; } -uppercase [blank], uppercase [blank] { +uppercase [blank] { order: 6; } uppercase :BLaNK { order: 6; } + +[blank] { + to-clone: 1; +} + +:blank { + to-clone: 1; +} diff --git a/plugins/css-blank-pseudo/test/basic.expect.css b/plugins/css-blank-pseudo/test/basic.expect.css index f010758e9..3a369137a 100644 --- a/plugins/css-blank-pseudo/test/basic.expect.css +++ b/plugins/css-blank-pseudo/test/basic.expect.css @@ -80,3 +80,11 @@ uppercase.js-blank-pseudo [blank], .js-blank-pseudo uppercase [blank] { uppercase :BLaNK { order: 6; } + +[blank].js-blank-pseudo, .js-blank-pseudo [blank] { + to-clone: 1; +} + +:blank { + to-clone: 1; +} diff --git a/plugins/css-blank-pseudo/test/basic.preserve.expect.css b/plugins/css-blank-pseudo/test/basic.preserve.expect.css index 6492c7519..d337939c3 100644 --- a/plugins/css-blank-pseudo/test/basic.preserve.expect.css +++ b/plugins/css-blank-pseudo/test/basic.preserve.expect.css @@ -51,3 +51,7 @@ test:not([blank]).js-blank-pseudo, .js-blank-pseudo test:not([blank]) { uppercase.js-blank-pseudo [blank], .js-blank-pseudo uppercase [blank] { order: 6; } + +[blank].js-blank-pseudo, .js-blank-pseudo [blank] { + to-clone: 1; +} diff --git a/plugins/css-blank-pseudo/test/basic.replacewith.expect.css b/plugins/css-blank-pseudo/test/basic.replacewith.expect.css index 612840589..e406b17d7 100644 --- a/plugins/css-blank-pseudo/test/basic.replacewith.expect.css +++ b/plugins/css-blank-pseudo/test/basic.replacewith.expect.css @@ -80,3 +80,11 @@ uppercase.js-blank-pseudo .css-blank, .js-blank-pseudo uppercase .css-blank { uppercase :BLaNK { order: 6; } + +.css-blank.js-blank-pseudo, .js-blank-pseudo .css-blank { + to-clone: 1; +} + +:blank { + to-clone: 1; +} diff --git a/plugins/css-blank-pseudo/test/basic.with-cloned-declarations.expect.css b/plugins/css-blank-pseudo/test/basic.with-cloned-declarations.expect.css new file mode 100644 index 000000000..beae9da41 --- /dev/null +++ b/plugins/css-blank-pseudo/test/basic.with-cloned-declarations.expect.css @@ -0,0 +1,92 @@ +[blank].js-blank-pseudo, .js-blank-pseudo [blank] { + order: 1; +} + +:blank { + order: 1; +} + +[blank].js-blank-pseudo, +.js-blank-pseudo [blank], +[blank].js-blank-pseudo test, +.js-blank-pseudo [blank] test, +test.js-blank-pseudo [blank], +.js-blank-pseudo test [blank], +test.js-blank-pseudo test[blank], +.js-blank-pseudo test test[blank], +test.js-blank-pseudo [blank] test, +.js-blank-pseudo test [blank] test, +test.js-blank-pseudo test[blank] test, +.js-blank-pseudo test test[blank] test, +test.js-blank-pseudo [blank] [blank] test, +.js-blank-pseudo test [blank] [blank] test, +test.js-blank-pseudo :matches([blank]) test, +.js-blank-pseudo test :matches([blank]) test, +test.js-blank-pseudo :matches([blank] test) test, +.js-blank-pseudo test :matches([blank] test) test, +test.js-blank-pseudo :matches(test [blank]) test, +.js-blank-pseudo test :matches(test [blank]) test, +test.js-blank-pseudo :matches(test test[blank]) test, +.js-blank-pseudo test :matches(test test[blank]) test, +test.js-blank-pseudo :matches(test [blank] test) test, +.js-blank-pseudo test :matches(test [blank] test) test, +test.js-blank-pseudo :matches(test test[blank] test) test, +.js-blank-pseudo test :matches(test test[blank] test) test, +test.js-blank-pseudo :matches(test [blank] [blank] test) test, +.js-blank-pseudo test :matches(test [blank] [blank] test) test { + order: 2; +} + +:blank, +:blank test, +test :blank, +test test:blank, +test :blank test, +test test:blank test, +test :blank :blank test, +test :matches(:blank) test, +test :matches(:blank test) test, +test :matches(test :blank) test, +test :matches(test test:blank) test, +test :matches(test :blank test) test, +test :matches(test test:blank test) test, +test :matches(test :blank :blank test) test { + order: 2; +} + +:ignore-blank, +:blank-ignore, +:ignoreblank, +:blankignore { + order: 3; +} + +:blank(ignore) { + order: 4; +} + +test:not([blank]).js-blank-pseudo, .js-blank-pseudo test:not([blank]) { + order: 5; +} + +test:not(:blank) { + order: 5; +} + +uppercase.js-blank-pseudo [blank], .js-blank-pseudo uppercase [blank] { + order: 6; +} + +uppercase :BLaNK { + order: 6; +} + +[blank].js-blank-pseudo, .js-blank-pseudo [blank] { + cloned: 1; + to-clone: 1; +} + +:blank { + cloned: 1; + to-clone: 1; +} diff --git a/plugins/css-blank-pseudo/test/basic.wrong-replacewith.expect.css b/plugins/css-blank-pseudo/test/basic.wrong-replacewith.expect.css index bd42aa82f..b64840c02 100644 --- a/plugins/css-blank-pseudo/test/basic.wrong-replacewith.expect.css +++ b/plugins/css-blank-pseudo/test/basic.wrong-replacewith.expect.css @@ -37,3 +37,7 @@ test:not(:blank) { uppercase :BLaNK { order: 6; } + +:blank { + to-clone: 1; +} diff --git a/plugins/css-blank-pseudo/test/examples/example.disable-polyfill-ready-class.expect.css b/plugins/css-blank-pseudo/test/examples/example.disable-polyfill-ready-class.expect.css index 9c7c21135..df6c00a20 100644 --- a/plugins/css-blank-pseudo/test/examples/example.disable-polyfill-ready-class.expect.css +++ b/plugins/css-blank-pseudo/test/examples/example.disable-polyfill-ready-class.expect.css @@ -1,4 +1,4 @@ -input[blank], input[blank] { +input[blank] { background-color: yellow; } input:blank { diff --git a/plugins/css-has-pseudo/.tape.mjs b/plugins/css-has-pseudo/.tape.mjs index 740b2253f..7d12d82f7 100644 --- a/plugins/css-has-pseudo/.tape.mjs +++ b/plugins/css-has-pseudo/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from 'css-has-pseudo'; import postcssLogical from 'postcss-logical'; import postcssNesting from 'postcss-nesting'; diff --git a/plugins/css-has-pseudo/CHANGELOG.md b/plugins/css-has-pseudo/CHANGELOG.md index 8d2df0bcd..c286a3604 100644 --- a/plugins/css-has-pseudo/CHANGELOG.md +++ b/plugins/css-has-pseudo/CHANGELOG.md @@ -1,5 +1,9 @@ # Changes to CSS Has Pseudo +### Unreleased (patch) + +- Reduce the amount of duplicate fallback CSS. + ### 5.0.1 (January 28, 2023) - Improve `types` declaration in `package.json` diff --git a/plugins/css-has-pseudo/dist/index.cjs b/plugins/css-has-pseudo/dist/index.cjs index 9361a7247..c41920328 100644 --- a/plugins/css-has-pseudo/dist/index.cjs +++ b/plugins/css-has-pseudo/dist/index.cjs @@ -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{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=0;e--)if("combinator"!==o.nodes[s].type&&"comment"!==o.nodes[s].type){t=e+1;break e}}if(t{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{if("function"===e.type&&"selector"===e.value.toLowerCase())return r.add(s.stringify(e.nodes)),!1})),r.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 r={preserve:!0,specificityMatchingName:"does-not-exist",...s||{}},o=":not(#"+r.specificityMatchingName+")",n=":not(."+r.specificityMatchingName+")",a=":not("+r.specificityMatchingName+")";return{postcssPlugin:"css-has-pseudo",prepare(){const s=new WeakSet;return{RuleExit:(c,{result:i})=>{if(s.has(c))return;if(!c.selector.toLowerCase().includes(":has(")||isWithinSupportCheck(c))return;const l=c.selectors.map((s=>{if(!s.toLowerCase().includes(":has("))return s;let l;try{l=e().astSync(s)}catch(e){return c.warn(i,`Failed to parse selector : "${s}" with message: "${e.message}"`),s}if(void 0===l)return s;l.walkPseudos((t=>{let s=t.parent,o=!1;for(;s;)e.isPseudoClass(s)&&":has"===s.value.toLowerCase()&&(o=!0),s=s.parent;o&&(":visited"===t.value.toLowerCase()&&t.replaceWith(e.className({value:r.specificityMatchingName})),":any-link"===t.value.toLowerCase()&&(t.value=":link"))})),l.walkPseudos((s=>{if(":has"!==s.value.toLowerCase()||!s.nodes)return;let r=s.parent??s;if(r!==s){let t=r.nodes.length;e:for(let s=0;s=0;e--)if("combinator"!==r.nodes[s].type&&"comment"!==r.nodes[s].type){t=e+1;break e}}if(t{delete e.parent,s.append(e)}));const o=e.selector({value:"",nodes:[]});r.nodes.slice(t).forEach((e=>{delete e.parent,o.append(e)}));const n=e.selector({value:"",nodes:[]});n.append(s),n.append(o),r.replaceWith(n),r=s}}const c="["+encodeCSS(r.toString())+"]",i=t.selectorSpecificity(r);let l=c;for(let e=0;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=0;e--)if("combinator"!==o.nodes[s].type&&"comment"!==o.nodes[s].type){t=e+1;break e}}if(t{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{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",prepare(){const s=new WeakSet;return{RuleExit:(c,{result:i})=>{if(s.has(c))return;if(!c.selector.toLowerCase().includes(":has(")||isWithinSupportCheck(c))return;const l=c.selectors.map((s=>{if(!s.toLowerCase().includes(":has("))return s;let l;try{l=e().astSync(s)}catch(e){return c.warn(i,`Failed to parse selector : "${s}" with message: "${e.message}"`),s}if(void 0===l)return s;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=0;e--)if("combinator"!==o.nodes[s].type&&"comment"!==o.nodes[s].type){t=e+1;break e}}if(t{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 = (opts?: pluginOptions) => { return { postcssPlugin: 'css-has-pseudo', - RuleExit: (rule, { result }) => { - if (!rule.selector.toLowerCase().includes(':has(') || isWithinSupportCheck(rule)) { - return; - } - - const selectors = rule.selectors.map((selector) => { - if (!selector.toLowerCase().includes(':has(')) { - return selector; - } - - let selectorAST; - try { - selectorAST = parser().astSync(selector); - } catch (err) { - rule.warn(result, `Failed to parse selector : "${selector}" with message: "${err.message}"`); - return selector; - } - - if (typeof selectorAST === 'undefined') { - return selector; - } - - selectorAST.walkPseudos((node) => { - let parent = node.parent; - let insideHasPseudoClass = false; - while (parent) { - if (parser.isPseudoClass(parent) && parent.value.toLowerCase() === ':has') { - insideHasPseudoClass = true; - } + prepare() { + const transformedNodes = new WeakSet(); - parent = parent.parent; + return { + RuleExit: (rule, { result }) => { + if (transformedNodes.has(rule)) { + return; } - if (!insideHasPseudoClass) { + if (!rule.selector.toLowerCase().includes(':has(') || isWithinSupportCheck(rule)) { return; } - // see : https://bugs.chromium.org/p/chromium/issues/detail?id=669058#c34 - // When we have ':has(:visited) {...}', the subject elements of the rule - // are the ancestors of the visited link element. - - // To prevent leaking visitedness to the link's ancestors, the ':visited' - // selector does not match if it is inside the ':has()' argument selector. - // So if a ':has()' argument selector requires a matching ':visited', the - // style rule are not applied. - if (node.value.toLowerCase() === ':visited') { - // We can't leave `:has` untouched as that might cause broken selector lists. - // Replacing with the specificity matching name as this should never match anything without `:not()`. - node.replaceWith(parser.className({ - value: options.specificityMatchingName, - })); - } + const selectors = rule.selectors.map((selector) => { + if (!selector.toLowerCase().includes(':has(')) { + return selector; + } - if (node.value.toLowerCase() === ':any-link') { - // we can transform `:any-link` to `:link` as this is allowed - node.value = ':link'; - } - }); + let selectorAST; + try { + selectorAST = parser().astSync(selector); + } catch (err) { + rule.warn(result, `Failed to parse selector : "${selector}" with message: "${err.message}"`); + return selector; + } - selectorAST.walkPseudos((node) => { - if (node.value.toLowerCase() !== ':has' || !node.nodes) { - return; - } + if (typeof selectorAST === 'undefined') { + return selector; + } - let container = node.parent ?? node; + selectorAST.walkPseudos((node) => { + let parent = node.parent; + let insideHasPseudoClass = false; + while (parent) { + if (parser.isPseudoClass(parent) && parent.value.toLowerCase() === ':has') { + insideHasPseudoClass = true; + } - // Split the selector at the pseudo element boundary - // - :has(...)::before -> :has(...) | ::before - // - :has(...) ~ span::before -> :has(...) ~ span | ::before - if (container !== node) { - let sliceIndex = container.nodes.length; + parent = parent.parent; + } + + if (!insideHasPseudoClass) { + return; + } + + // see : https://bugs.chromium.org/p/chromium/issues/detail?id=669058#c34 + // When we have ':has(:visited) {...}', the subject elements of the rule + // are the ancestors of the visited link element. + + // To prevent leaking visitedness to the link's ancestors, the ':visited' + // selector does not match if it is inside the ':has()' argument selector. + // So if a ':has()' argument selector requires a matching ':visited', the + // style rule are not applied. + if (node.value.toLowerCase() === ':visited') { + // We can't leave `:has` untouched as that might cause broken selector lists. + // Replacing with the specificity matching name as this should never match anything without `:not()`. + node.replaceWith(parser.className({ + value: options.specificityMatchingName, + })); + } + + if (node.value.toLowerCase() === ':any-link') { + // we can transform `:any-link` to `:link` as this is allowed + node.value = ':link'; + } + }); + + selectorAST.walkPseudos((node) => { + if (node.value.toLowerCase() !== ':has' || !node.nodes) { + return; + } - PSEUDO_ELEMENT_LOOP: - for (let i = 0; i < container.nodes.length; i++) { - const element = container.nodes[i]; + let container = node.parent ?? node; - if (parser.isPseudoElement(element)) { - for (let j = i - 1; j >= 0; j--) { - if (container.nodes[i].type === 'combinator' || container.nodes[i].type === 'comment') { - continue; + // Split the selector at the pseudo element boundary + // - :has(...)::before -> :has(...) | ::before + // - :has(...) ~ span::before -> :has(...) ~ span | ::before + if (container !== node) { + let sliceIndex = container.nodes.length; + + PSEUDO_ELEMENT_LOOP: + for (let i = 0; i < container.nodes.length; i++) { + const element = container.nodes[i]; + + if (parser.isPseudoElement(element)) { + for (let j = i - 1; j >= 0; j--) { + if (container.nodes[i].type === 'combinator' || container.nodes[i].type === 'comment') { + continue; + } + + sliceIndex = j + 1; + break PSEUDO_ELEMENT_LOOP; + } } + } - sliceIndex = j + 1; - break PSEUDO_ELEMENT_LOOP; + if (sliceIndex < container.nodes.length) { + const a = parser.selector({ + value: '', + nodes: [], + }); + + const aNodes = container.nodes.slice(0, sliceIndex); + aNodes.forEach((x) => { + delete x.parent; + a.append(x); + }); + + const b = parser.selector({ + value: '', + nodes: [], + }); + + const bNodes = container.nodes.slice(sliceIndex); + bNodes.forEach((x) => { + delete x.parent; + b.append(x); + }); + + const newContainer = parser.selector({ + value: '', + nodes: [], + }); + + newContainer.append(a); + newContainer.append(b); + + container.replaceWith(newContainer); + container = a; } } - } - if (sliceIndex < container.nodes.length) { - const a = parser.selector({ - value: '', - nodes: [], - }); - - const aNodes = container.nodes.slice(0, sliceIndex); - aNodes.forEach((x) => { - delete x.parent; - a.append(x); - }); - - const b = parser.selector({ - value: '', - nodes: [], - }); - - const bNodes = container.nodes.slice(sliceIndex); - bNodes.forEach((x) => { - delete x.parent; - b.append(x); - }); - - const newContainer = parser.selector({ - value: '', - nodes: [], - }); - - newContainer.append(a); - newContainer.append(b); - - container.replaceWith(newContainer); - container = a; - } - } + const encodedSelector = '[' + encodeCSS(container.toString()) + ']'; + const abcSpecificity = selectorSpecificity(container); - const encodedSelector = '[' + encodeCSS(container.toString()) + ']'; - const abcSpecificity = selectorSpecificity(container); - - let encodedSelectorWithSpecificity = encodedSelector; - for (let i = 0; i < abcSpecificity.a; i++) { - encodedSelectorWithSpecificity += specificityMatchingNameId; - } - const bSpecificity = Math.max(1, abcSpecificity.b) - 1; - for (let i = 0; i < bSpecificity; i++) { - encodedSelectorWithSpecificity += specificityMatchingNameClass; - } - for (let i = 0; i < abcSpecificity.c; i++) { - encodedSelectorWithSpecificity += specificityMatchingNameTag; - } + let encodedSelectorWithSpecificity = encodedSelector; + for (let i = 0; i < abcSpecificity.a; i++) { + encodedSelectorWithSpecificity += specificityMatchingNameId; + } + const bSpecificity = Math.max(1, abcSpecificity.b) - 1; + for (let i = 0; i < bSpecificity; i++) { + encodedSelectorWithSpecificity += specificityMatchingNameClass; + } + for (let i = 0; i < abcSpecificity.c; i++) { + encodedSelectorWithSpecificity += specificityMatchingNameTag; + } - const encodedSelectorAST = parser().astSync(encodedSelectorWithSpecificity); + const encodedSelectorAST = parser().astSync(encodedSelectorWithSpecificity); - container.replaceWith(encodedSelectorAST.nodes[0]); - }); + container.replaceWith(encodedSelectorAST.nodes[0]); + }); - const modifiedSelector = selectorAST.toString(); - if (modifiedSelector !== selector) { - return '.js-has-pseudo ' + modifiedSelector; - } + const modifiedSelector = selectorAST.toString(); + if (modifiedSelector !== selector) { + return '.js-has-pseudo ' + modifiedSelector; + } - return selector; - }); + return selector; + }); - if (selectors.join(',') === rule.selectors.join(',')) { - return; - } + if (selectors.join(',') === rule.selectors.join(',')) { + return; + } - rule.cloneBefore({ selectors: selectors }); + transformedNodes.add(rule); + rule.cloneBefore({ selectors: selectors }); - if (!options.preserve) { - rule.remove(); - } + if (!options.preserve) { + rule.remove(); + } + }, + }; }, }; }; diff --git a/plugins/css-has-pseudo/test/plugin-order-logical.after.preserve.expect.css b/plugins/css-has-pseudo/test/plugin-order-logical.after.preserve.expect.css index 026ede546..a4f4e6b15 100644 --- a/plugins/css-has-pseudo/test/plugin-order-logical.after.preserve.expect.css +++ b/plugins/css-has-pseudo/test/plugin-order-logical.after.preserve.expect.css @@ -1,9 +1,6 @@ .js-has-pseudo [csstools-has-2p-1m-2w-2p-37-14-1a-2q-15]:not(does-not-exist) { margin-left: 2px; } -.js-has-pseudo [csstools-has-2p-1m-2w-2p-37-14-1a-2q-15]:not(does-not-exist) { - margin-left: 2px; -} a:has(.b) { margin-left: 2px; } diff --git a/plugins/css-has-pseudo/test/plugin-order-logical.before.preserve.expect.css b/plugins/css-has-pseudo/test/plugin-order-logical.before.preserve.expect.css index 026ede546..a4f4e6b15 100644 --- a/plugins/css-has-pseudo/test/plugin-order-logical.before.preserve.expect.css +++ b/plugins/css-has-pseudo/test/plugin-order-logical.before.preserve.expect.css @@ -1,9 +1,6 @@ .js-has-pseudo [csstools-has-2p-1m-2w-2p-37-14-1a-2q-15]:not(does-not-exist) { margin-left: 2px; } -.js-has-pseudo [csstools-has-2p-1m-2w-2p-37-14-1a-2q-15]:not(does-not-exist) { - margin-left: 2px; -} a:has(.b) { margin-left: 2px; } diff --git a/plugins/css-prefers-color-scheme/.tape.mjs b/plugins/css-prefers-color-scheme/.tape.mjs index f13b92e5b..73ba51538 100644 --- a/plugins/css-prefers-color-scheme/.tape.mjs +++ b/plugins/css-prefers-color-scheme/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { declarationClonerPlugin, postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from 'css-prefers-color-scheme'; postcssTape(plugin)({ @@ -17,6 +17,15 @@ postcssTape(plugin)({ preserve: false } }, + 'basic:with-cloned-declarations': { + message: 'doesn\'t cause duplicate CSS', + plugins: [ + declarationClonerPlugin, + plugin({ + preserve: true + }) + ] + }, 'examples/example': { message: 'minimal example', }, diff --git a/plugins/css-prefers-color-scheme/CHANGELOG.md b/plugins/css-prefers-color-scheme/CHANGELOG.md index 09a8bc708..56afa0bbd 100644 --- a/plugins/css-prefers-color-scheme/CHANGELOG.md +++ b/plugins/css-prefers-color-scheme/CHANGELOG.md @@ -1,5 +1,9 @@ # Changes to Prefers Color Scheme +### Unreleased (patch) + +- Reduce the amount of duplicate fallback CSS. + ### 8.0.1 (January 28, 2023) - Improve `types` declaration in `package.json` diff --git a/plugins/css-prefers-color-scheme/dist/index.cjs b/plugins/css-prefers-color-scheme/dist/index.cjs index 0d840512f..defe39e47 100644 --- a/plugins/css-prefers-color-scheme/dist/index.cjs +++ b/plugins/css-prefers-color-scheme/dist/index.cjs @@ -1 +1 @@ -"use strict";const e=/\(\s*prefers-color-scheme\s*:\s*(dark|light)\s*\)/gi,s={dark:"48842621",light:"70318723"},prefersInterfaceColorDepthReplacer=(e,r)=>`(color: ${s[r.toLowerCase()]})`,creator=s=>{const r=Object.assign({preserve:!0},s);return{postcssPlugin:"postcss-prefers-color-scheme",AtRule:s=>{if("media"!==s.name.toLowerCase())return;const{params:o}=s,t=o.replace(e,prefersInterfaceColorDepthReplacer);o!==t&&(s.cloneBefore({params:t}),r.preserve||s.remove())}}};creator.postcss=!0,module.exports=creator; +"use strict";const e=/\(\s*prefers-color-scheme\s*:\s*(dark|light)\s*\)/gi,s={dark:"48842621",light:"70318723"},prefersInterfaceColorDepthReplacer=(e,r)=>`(color: ${s[r.toLowerCase()]})`,creator=s=>{const r=Object.assign({preserve:!0},s);return{postcssPlugin:"postcss-prefers-color-scheme",prepare(){const s=new WeakSet;return{AtRule:o=>{if(s.has(o))return;if("media"!==o.name.toLowerCase())return;const{params:t}=o,a=t.replace(e,prefersInterfaceColorDepthReplacer);t!==a&&(s.add(o),o.cloneBefore({params:a}),r.preserve||o.remove())}}}}};creator.postcss=!0,module.exports=creator; diff --git a/plugins/css-prefers-color-scheme/dist/index.mjs b/plugins/css-prefers-color-scheme/dist/index.mjs index 457040ce5..5ba6e6c5f 100644 --- a/plugins/css-prefers-color-scheme/dist/index.mjs +++ b/plugins/css-prefers-color-scheme/dist/index.mjs @@ -1 +1 @@ -const e=/\(\s*prefers-color-scheme\s*:\s*(dark|light)\s*\)/gi,s={dark:"48842621",light:"70318723"},prefersInterfaceColorDepthReplacer=(e,r)=>`(color: ${s[r.toLowerCase()]})`,creator=s=>{const r=Object.assign({preserve:!0},s);return{postcssPlugin:"postcss-prefers-color-scheme",AtRule:s=>{if("media"!==s.name.toLowerCase())return;const{params:o}=s,t=o.replace(e,prefersInterfaceColorDepthReplacer);o!==t&&(s.cloneBefore({params:t}),r.preserve||s.remove())}}};creator.postcss=!0;export{creator as default}; +const e=/\(\s*prefers-color-scheme\s*:\s*(dark|light)\s*\)/gi,r={dark:"48842621",light:"70318723"},prefersInterfaceColorDepthReplacer=(e,s)=>`(color: ${r[s.toLowerCase()]})`,creator=r=>{const s=Object.assign({preserve:!0},r);return{postcssPlugin:"postcss-prefers-color-scheme",prepare(){const r=new WeakSet;return{AtRule:o=>{if(r.has(o))return;if("media"!==o.name.toLowerCase())return;const{params:t}=o,a=t.replace(e,prefersInterfaceColorDepthReplacer);t!==a&&(r.add(o),o.cloneBefore({params:a}),s.preserve||o.remove())}}}}};creator.postcss=!0;export{creator as default}; diff --git a/plugins/css-prefers-color-scheme/src/index.ts b/plugins/css-prefers-color-scheme/src/index.ts index 2257c4f10..f241d1e8b 100644 --- a/plugins/css-prefers-color-scheme/src/index.ts +++ b/plugins/css-prefers-color-scheme/src/index.ts @@ -23,22 +23,33 @@ const creator: PluginCreator = (opts?: pluginOptions) => { return { postcssPlugin: 'postcss-prefers-color-scheme', - AtRule: (atRule) => { - if (atRule.name.toLowerCase() !== 'media') { - return; - } - - const { params } = atRule; - const altParamsColorDepth = params.replace(prefersInterfaceRegExp, prefersInterfaceColorDepthReplacer); - if (params === altParamsColorDepth) { - return; - } - - atRule.cloneBefore({ params: altParamsColorDepth }); - - if (!options.preserve) { - atRule.remove(); - } + prepare() { + const transformedNodes = new WeakSet(); + + return { + AtRule: (atRule) => { + if (transformedNodes.has(atRule)) { + return; + } + + if (atRule.name.toLowerCase() !== 'media') { + return; + } + + const { params } = atRule; + const altParamsColorDepth = params.replace(prefersInterfaceRegExp, prefersInterfaceColorDepthReplacer); + if (params === altParamsColorDepth) { + return; + } + + transformedNodes.add(atRule); + atRule.cloneBefore({ params: altParamsColorDepth }); + + if (!options.preserve) { + atRule.remove(); + } + }, + }; }, }; }; diff --git a/plugins/css-prefers-color-scheme/test/basic.css b/plugins/css-prefers-color-scheme/test/basic.css index 275805ffb..dc8d396ed 100644 --- a/plugins/css-prefers-color-scheme/test/basic.css +++ b/plugins/css-prefers-color-scheme/test/basic.css @@ -32,3 +32,9 @@ body { color: var(--site-color, #111); font: 100%/1.5 system-ui; } + +@media (prefers-color-scheme: dark) { + .foo { + to-clone: 1; + } +} diff --git a/plugins/css-prefers-color-scheme/test/basic.expect.css b/plugins/css-prefers-color-scheme/test/basic.expect.css index d4d9be8a2..c9d83e3f4 100644 --- a/plugins/css-prefers-color-scheme/test/basic.expect.css +++ b/plugins/css-prefers-color-scheme/test/basic.expect.css @@ -53,3 +53,15 @@ body { color: var(--site-color, #111); font: 100%/1.5 system-ui; } + +@media (color: 48842621) { + .foo { + to-clone: 1; + } +} + +@media (prefers-color-scheme: dark) { + .foo { + to-clone: 1; + } +} diff --git a/plugins/css-prefers-color-scheme/test/basic.preserve.expect.css b/plugins/css-prefers-color-scheme/test/basic.preserve.expect.css index d4d9be8a2..c9d83e3f4 100644 --- a/plugins/css-prefers-color-scheme/test/basic.preserve.expect.css +++ b/plugins/css-prefers-color-scheme/test/basic.preserve.expect.css @@ -53,3 +53,15 @@ body { color: var(--site-color, #111); font: 100%/1.5 system-ui; } + +@media (color: 48842621) { + .foo { + to-clone: 1; + } +} + +@media (prefers-color-scheme: dark) { + .foo { + to-clone: 1; + } +} diff --git a/plugins/css-prefers-color-scheme/test/basic.preserve.false.expect.css b/plugins/css-prefers-color-scheme/test/basic.preserve.false.expect.css index eb562ef1d..23d47afb4 100644 --- a/plugins/css-prefers-color-scheme/test/basic.preserve.false.expect.css +++ b/plugins/css-prefers-color-scheme/test/basic.preserve.false.expect.css @@ -32,3 +32,9 @@ body { color: var(--site-color, #111); font: 100%/1.5 system-ui; } + +@media (color: 48842621) { + .foo { + to-clone: 1; + } +} diff --git a/plugins/css-prefers-color-scheme/test/basic.with-cloned-declarations.expect.css b/plugins/css-prefers-color-scheme/test/basic.with-cloned-declarations.expect.css new file mode 100644 index 000000000..2344c1171 --- /dev/null +++ b/plugins/css-prefers-color-scheme/test/basic.with-cloned-declarations.expect.css @@ -0,0 +1,69 @@ +@media (prefers-color-scheme: no-preference) { + /* ignored now, this was removed from the specification */ + :root { + --site-bgcolor: #f9f9f9; + --site-color: #111; + } +} + +@media (color: 48842621) { + :root { + --site-bgcolor: #1b1b1b; + --site-color: #fff; + } +} + +@media (prefers-color-scheme: dark) { + :root { + --site-bgcolor: #1b1b1b; + --site-color: #fff; + } +} + +@media (color: 70318723) { + :root { + --site-bgcolor: #fff; + --site-color: #222; + } +} + +@media (prefers-color-scheme: light) { + :root { + --site-bgcolor: #fff; + --site-color: #222; + } +} + +@media only screen and ((color: 70318723) and (min-width: 300px)) or ((color: 48842621) and (max-width: 299px)) { + :root { + --site-bgcolor: #fff; + --site-color: #222; + } +} + +@media only screen and ((prefers-color-scheme: light) and (min-width: 300px)) or ((prefers-color-scheme: dark) and (max-width: 299px)) { + :root { + --site-bgcolor: #fff; + --site-color: #222; + } +} + +body { + background-color: var(--site-bgcolor, #f9f9f9); + color: var(--site-color, #111); + font: 100%/1.5 system-ui; +} + +@media (color: 48842621) { + .foo { + cloned: 1; + to-clone: 1; + } +} + +@media (prefers-color-scheme: dark) { + .foo { + cloned: 1; + to-clone: 1; + } +} diff --git a/plugins/postcss-attribute-case-insensitive/.tape.mjs b/plugins/postcss-attribute-case-insensitive/.tape.mjs index 28cbbf3d8..7ff4b914a 100644 --- a/plugins/postcss-attribute-case-insensitive/.tape.mjs +++ b/plugins/postcss-attribute-case-insensitive/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { declarationClonerPlugin, postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from 'postcss-attribute-case-insensitive'; postcssTape(plugin)({ @@ -11,6 +11,15 @@ postcssTape(plugin)({ preserve: true } }, + 'basic:with-cloned-declarations': { + message: 'doesn\'t cause duplicate CSS', + plugins: [ + declarationClonerPlugin, + plugin({ + preserve: true + }) + ] + }, 'invalid-selector': { message: 'warns on invalid selectors', warnings: 1 diff --git a/plugins/postcss-attribute-case-insensitive/CHANGELOG.md b/plugins/postcss-attribute-case-insensitive/CHANGELOG.md index 7c7f8ee0a..266c1d818 100644 --- a/plugins/postcss-attribute-case-insensitive/CHANGELOG.md +++ b/plugins/postcss-attribute-case-insensitive/CHANGELOG.md @@ -1,5 +1,9 @@ # Changes to PostCSS Attribute Case Insensitive +### Unreleased (patch) + +- Reduce the amount of duplicate fallback CSS. + ### 6.0.1 (January 28, 2023) - Improve `types` declaration in `package.json` diff --git a/plugins/postcss-attribute-case-insensitive/dist/index.cjs b/plugins/postcss-attribute-case-insensitive/dist/index.cjs index c786e5a16..85dc26940 100644 --- a/plugins/postcss-attribute-case-insensitive/dist/index.cjs +++ b/plugins/postcss-attribute-case-insensitive/dist/index.cjs @@ -1 +1 @@ -"use strict";var e=require("postcss-selector-parser");function nodeIsInsensitiveAttribute(e){return"attribute"===e.type&&e.insensitive}function selectorHasInsensitiveAttribute(e){return e.some(nodeIsInsensitiveAttribute)}function transformString(e,t,s){const r=s.charAt(t);if(""===r)return e;let n=e.map((e=>e+r));const o=r.toLocaleUpperCase();return o!==r&&(n=n.concat(e.map((e=>e+o)))),transformString(n,t+1,s)}function createNewSelectors(t){let s=[e.selector({value:"",nodes:[]})];return t.walk((e=>{if(!nodeIsInsensitiveAttribute(e))return void s.forEach((t=>{t.append(e.clone())}));const t=transformString([""],0,(r=e).value).map((e=>{const t=r.clone({spaces:{after:r.spaces.after,before:r.spaces.before},insensitive:!1});return t.setValue(e),t}));var r;const n=[];t.forEach((e=>{s.forEach((t=>{const s=t.clone({});s.append(e),n.push(s)}))})),s=n})),s}function transform(e){let t=[];e.each((e=>{selectorHasInsensitiveAttribute(e)&&(t=t.concat(createNewSelectors(e)),e.remove())})),t.length&&t.forEach((t=>e.append(t)))}const creator=t=>{const s=Object.assign({preserve:!1},t);return{postcssPlugin:"postcss-attribute-case-insensitive",Rule(t,{result:r}){if(!/i\s*]/gim.test(t.selector))return;let n=t.selector;try{n=e(transform).processSync(t.selector)}catch(e){return void t.warn(r,`Failed to parse selector : "${t.selector}" with message: "${e.message}"`)}n!==t.selector&&(t.cloneBefore({selector:n}),s.preserve||t.remove())}}};creator.postcss=!0,module.exports=creator; +"use strict";var e=require("postcss-selector-parser");function nodeIsInsensitiveAttribute(e){return"attribute"===e.type&&e.insensitive}function selectorHasInsensitiveAttribute(e){return e.some(nodeIsInsensitiveAttribute)}function transformString(e,t,r){const s=r.charAt(t);if(""===s)return e;let n=e.map((e=>e+s));const o=s.toLocaleUpperCase();return o!==s&&(n=n.concat(e.map((e=>e+o)))),transformString(n,t+1,r)}function createNewSelectors(t){let r=[e.selector({value:"",nodes:[]})];return t.walk((e=>{if(!nodeIsInsensitiveAttribute(e))return void r.forEach((t=>{t.append(e.clone())}));const t=transformString([""],0,(s=e).value).map((e=>{const t=s.clone({spaces:{after:s.spaces.after,before:s.spaces.before},insensitive:!1});return t.setValue(e),t}));var s;const n=[];t.forEach((e=>{r.forEach((t=>{const r=t.clone({});r.append(e),n.push(r)}))})),r=n})),r}function transform(e){let t=[];e.each((e=>{selectorHasInsensitiveAttribute(e)&&(t=t.concat(createNewSelectors(e)),e.remove())})),t.length&&t.forEach((t=>e.append(t)))}const creator=t=>{const r=Object.assign({preserve:!1},t);return{postcssPlugin:"postcss-attribute-case-insensitive",prepare(){const t=new WeakSet;return{Rule(s,{result:n}){if(t.has(s))return;if(!/i\s*]/gim.test(s.selector))return;let o=s.selector;try{o=e(transform).processSync(s.selector)}catch(e){return void s.warn(n,`Failed to parse selector : "${s.selector}" with message: "${e.message}"`)}o!==s.selector&&(t.add(s),s.cloneBefore({selector:o}),r.preserve||s.remove())}}}}};creator.postcss=!0,module.exports=creator; diff --git a/plugins/postcss-attribute-case-insensitive/dist/index.mjs b/plugins/postcss-attribute-case-insensitive/dist/index.mjs index 9cfcb333a..436119956 100644 --- a/plugins/postcss-attribute-case-insensitive/dist/index.mjs +++ b/plugins/postcss-attribute-case-insensitive/dist/index.mjs @@ -1 +1 @@ -import e from"postcss-selector-parser";function nodeIsInsensitiveAttribute(e){return"attribute"===e.type&&e.insensitive}function selectorHasInsensitiveAttribute(e){return e.some(nodeIsInsensitiveAttribute)}function transformString(e,t,s){const r=s.charAt(t);if(""===r)return e;let n=e.map((e=>e+r));const o=r.toLocaleUpperCase();return o!==r&&(n=n.concat(e.map((e=>e+o)))),transformString(n,t+1,s)}function createNewSelectors(t){let s=[e.selector({value:"",nodes:[]})];return t.walk((e=>{if(!nodeIsInsensitiveAttribute(e))return void s.forEach((t=>{t.append(e.clone())}));const t=transformString([""],0,(r=e).value).map((e=>{const t=r.clone({spaces:{after:r.spaces.after,before:r.spaces.before},insensitive:!1});return t.setValue(e),t}));var r;const n=[];t.forEach((e=>{s.forEach((t=>{const s=t.clone({});s.append(e),n.push(s)}))})),s=n})),s}function transform(e){let t=[];e.each((e=>{selectorHasInsensitiveAttribute(e)&&(t=t.concat(createNewSelectors(e)),e.remove())})),t.length&&t.forEach((t=>e.append(t)))}const creator=t=>{const s=Object.assign({preserve:!1},t);return{postcssPlugin:"postcss-attribute-case-insensitive",Rule(t,{result:r}){if(!/i\s*]/gim.test(t.selector))return;let n=t.selector;try{n=e(transform).processSync(t.selector)}catch(e){return void t.warn(r,`Failed to parse selector : "${t.selector}" with message: "${e.message}"`)}n!==t.selector&&(t.cloneBefore({selector:n}),s.preserve||t.remove())}}};creator.postcss=!0;export{creator as default}; +import e from"postcss-selector-parser";function nodeIsInsensitiveAttribute(e){return"attribute"===e.type&&e.insensitive}function selectorHasInsensitiveAttribute(e){return e.some(nodeIsInsensitiveAttribute)}function transformString(e,t,r){const s=r.charAt(t);if(""===s)return e;let n=e.map((e=>e+s));const o=s.toLocaleUpperCase();return o!==s&&(n=n.concat(e.map((e=>e+o)))),transformString(n,t+1,r)}function createNewSelectors(t){let r=[e.selector({value:"",nodes:[]})];return t.walk((e=>{if(!nodeIsInsensitiveAttribute(e))return void r.forEach((t=>{t.append(e.clone())}));const t=transformString([""],0,(s=e).value).map((e=>{const t=s.clone({spaces:{after:s.spaces.after,before:s.spaces.before},insensitive:!1});return t.setValue(e),t}));var s;const n=[];t.forEach((e=>{r.forEach((t=>{const r=t.clone({});r.append(e),n.push(r)}))})),r=n})),r}function transform(e){let t=[];e.each((e=>{selectorHasInsensitiveAttribute(e)&&(t=t.concat(createNewSelectors(e)),e.remove())})),t.length&&t.forEach((t=>e.append(t)))}const creator=t=>{const r=Object.assign({preserve:!1},t);return{postcssPlugin:"postcss-attribute-case-insensitive",prepare(){const t=new WeakSet;return{Rule(s,{result:n}){if(t.has(s))return;if(!/i\s*]/gim.test(s.selector))return;let o=s.selector;try{o=e(transform).processSync(s.selector)}catch(e){return void s.warn(n,`Failed to parse selector : "${s.selector}" with message: "${e.message}"`)}o!==s.selector&&(t.add(s),s.cloneBefore({selector:o}),r.preserve||s.remove())}}}}};creator.postcss=!0;export{creator as default}; diff --git a/plugins/postcss-attribute-case-insensitive/src/index.ts b/plugins/postcss-attribute-case-insensitive/src/index.ts index 086d8d87d..f0a9de968 100644 --- a/plugins/postcss-attribute-case-insensitive/src/index.ts +++ b/plugins/postcss-attribute-case-insensitive/src/index.ts @@ -104,29 +104,40 @@ const creator: PluginCreator = (opts?: pluginOptions) => { return { postcssPlugin: 'postcss-attribute-case-insensitive', - Rule(rule, { result }) { - if (!(/i\s*]/gmi.test(rule.selector))) { - return; - } - - let modifiedSelector = rule.selector; - - try { - modifiedSelector = selectorParser(transform).processSync(rule.selector); - } catch (err) { - rule.warn(result, `Failed to parse selector : "${rule.selector}" with message: "${err.message}"`); - return; - } - - if (modifiedSelector === rule.selector) { - return; - } - - rule.cloneBefore({ selector: modifiedSelector }); - - if (!options.preserve) { - rule.remove(); - } + prepare() { + const transformedNodes = new WeakSet(); + + return { + Rule(rule, { result }) { + if (transformedNodes.has(rule)) { + return; + } + + if (!(/i\s*]/gmi.test(rule.selector))) { + return; + } + + let modifiedSelector = rule.selector; + + try { + modifiedSelector = selectorParser(transform).processSync(rule.selector); + } catch (err) { + rule.warn(result, `Failed to parse selector : "${rule.selector}" with message: "${err.message}"`); + return; + } + + if (modifiedSelector === rule.selector) { + return; + } + + transformedNodes.add(rule); + rule.cloneBefore({ selector: modifiedSelector }); + + if (!options.preserve) { + rule.remove(); + } + }, + }; }, }; }; diff --git a/plugins/postcss-attribute-case-insensitive/test/basic.css b/plugins/postcss-attribute-case-insensitive/test/basic.css index a4c6d5d91..e78f97be7 100644 --- a/plugins/postcss-attribute-case-insensitive/test/basic.css +++ b/plugins/postcss-attribute-case-insensitive/test/basic.css @@ -47,3 +47,7 @@ ] { order: 5.7; } + +[foo="a b" i] { + to-clone: 1; +} diff --git a/plugins/postcss-attribute-case-insensitive/test/basic.expect.css b/plugins/postcss-attribute-case-insensitive/test/basic.expect.css index ade975b89..2efa01069 100644 --- a/plugins/postcss-attribute-case-insensitive/test/basic.expect.css +++ b/plugins/postcss-attribute-case-insensitive/test/basic.expect.css @@ -45,3 +45,7 @@ [foo="h"],[foo="H"] { order: 5.7; } + +[foo="a b"],[foo="A b"],[foo="a B"],[foo="A B"] { + to-clone: 1; +} diff --git a/plugins/postcss-attribute-case-insensitive/test/basic.preserve-true.expect.css b/plugins/postcss-attribute-case-insensitive/test/basic.preserve-true.expect.css index 5df3c672a..48e92b4c6 100644 --- a/plugins/postcss-attribute-case-insensitive/test/basic.preserve-true.expect.css +++ b/plugins/postcss-attribute-case-insensitive/test/basic.preserve-true.expect.css @@ -91,3 +91,11 @@ ] { order: 5.7; } + +[foo="a b"],[foo="A b"],[foo="a B"],[foo="A B"] { + to-clone: 1; +} + +[foo="a b" i] { + to-clone: 1; +} diff --git a/plugins/postcss-attribute-case-insensitive/test/basic.with-cloned-declarations.expect.css b/plugins/postcss-attribute-case-insensitive/test/basic.with-cloned-declarations.expect.css new file mode 100644 index 000000000..8708b6b1d --- /dev/null +++ b/plugins/postcss-attribute-case-insensitive/test/basic.with-cloned-declarations.expect.css @@ -0,0 +1,103 @@ +[data-foo=test],[data-foo=Test],[data-foo=tEst],[data-foo=TEst],[data-foo=teSt],[data-foo=TeSt],[data-foo=tESt],[data-foo=TESt],[data-foo=tesT],[data-foo=TesT],[data-foo=tEsT],[data-foo=TEsT],[data-foo=teST],[data-foo=TeST],[data-foo=tEST],[data-foo=TEST] { + order: 1; +} + +[data-foo=test i] { + order: 1; +} + +[foo="a b"],[foo="A b"],[foo="a B"],[foo="A B"] { + order: 2; +} + +[foo="a b" i] { + order: 2; +} + +[foo=a]{ + order: 3; +} + +[foobar=b],[foo=a],[foo=A],[bar=c],[bar=C]{ + order: 4; +} + +[foo=a i],[foobar=b],[bar=c i]{ + order: 4; +} + +[foo="a"],[foo="A"] { + order: 5.0; +} + +[foo="a" i] { + order: 5.0; +} + +[foo="b"],[foo="B"] { + order: 5.1; +} + +[foo="b" I] { + order: 5.1; +} + +[foo="c"],[foo="C"] { + order: 5.2; +} + +[foo="c"i] { + order: 5.2; +} + +[foo="d"],[foo="D"] { + order: 5.3; +} + +[foo="d"I] { + order: 5.3; +} + +[foo="e"],[foo="E"] { + order: 5.4; +} + +[foo="e" i ] { + order: 5.4; +} + +[foo="f"],[foo="F"] { + order: 5.5; +} + +[foo="f" I ] { + order: 5.5; +} + +[foo="g"],[foo="G"] { + order: 5.6; +} + +[foo="g" i +] { + order: 5.6; +} + +[foo="h"],[foo="H"] { + order: 5.7; +} + +[foo="h" I +] { + order: 5.7; +} + +[foo="a b"],[foo="A b"],[foo="a B"],[foo="A B"] { + cloned: 1; + to-clone: 1; +} + +[foo="a b" i] { + cloned: 1; + to-clone: 1; +} diff --git a/plugins/postcss-base-plugin/.tape.mjs b/plugins/postcss-base-plugin/.tape.mjs index c9f55d0d8..04432aca1 100644 --- a/plugins/postcss-base-plugin/.tape.mjs +++ b/plugins/postcss-base-plugin/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from '@csstools/postcss-base-plugin'; postcssTape(plugin)({ diff --git a/plugins/postcss-cascade-layers/.tape.mjs b/plugins/postcss-cascade-layers/.tape.mjs index 3f304e7a1..d9e0fd4f5 100644 --- a/plugins/postcss-cascade-layers/.tape.mjs +++ b/plugins/postcss-cascade-layers/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from '@csstools/postcss-cascade-layers'; import postcssImport from 'postcss-import'; diff --git a/plugins/postcss-color-function/.tape.mjs b/plugins/postcss-color-function/.tape.mjs index cd8e6cdc5..74abb913a 100644 --- a/plugins/postcss-color-function/.tape.mjs +++ b/plugins/postcss-color-function/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape, ruleClonerPlugin } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from '@csstools/postcss-color-function'; import lab from 'postcss-lab-function'; @@ -13,6 +13,16 @@ postcssTape(plugin)({ preserve: true } }, + 'basic:with-cloned-rules': { + message: 'doesn\'t cause duplicate CSS', + warnings: 6, + plugins: [ + ruleClonerPlugin, + plugin({ + preserve: true + }) + ] + }, 'lab-function-interop': { message: 'supports usage along side postcss-lab-function', plugins: [ diff --git a/plugins/postcss-color-function/dist/index.cjs b/plugins/postcss-color-function/dist/index.cjs index 6671672e4..9e27f4073 100644 --- a/plugins/postcss-color-function/dist/index.cjs +++ b/plugins/postcss-color-function/dist/index.cjs @@ -1 +1 @@ -"use strict";var e=require("@csstools/postcss-progressive-custom-properties"),r=require("postcss-value-parser"),o=require("@csstools/color-helpers");function hasFallback(e){const r=e.parent;if(!r)return!1;const o=e.prop.toLowerCase(),n=r.index(e);for(let e=0;e"comment"!==e.type&&"space"!==e.type));let i,l=null;if("color"===u.toLowerCase()&&(l=colorFunctionContents(c)),!l)return;switch(e.value="rgb",transformAlpha(e,l.slash,l.alpha),l.colorSpace){case"srgb":i=o.conversions.sRGB_to_sRGB;break;case"srgb-linear":i=o.conversions.sRGB_linear_to_sRGB;break;case"a98-rgb":i=o.conversions.a98_RGB_to_sRGB;break;case"prophoto-rgb":i=o.conversions.proPhoto_RGB_to_sRGB;break;case"display-p3":i=o.conversions.p3_to_sRGB;break;case"rec2020":i=o.conversions.rec_2020_to_sRGB;break;case"xyz-d50":i=o.conversions.cie_XYZ_50_to_sRGB;break;case"xyz-d65":case"xyz":i=o.conversions.cie_XYZ_65_to_sRGB;break;default:return}const p=(d=l,d.parameters.map((e=>e.value))).map((e=>parseFloat(e.number)));var d;const v=i(p);!o.utils.inGamut(p)&&s&&n.warn(t,`"${a}" is out of gamut for "${l.colorSpace}". Given "preserve: true" is set, this will lead to unexpected results in some browsers.`),e.nodes=[{sourceIndex:0,sourceEndIndex:1,value:String(Math.round(255*v[0])),type:"word"},{sourceIndex:0,sourceEndIndex:1,value:",",type:"div",before:"",after:""},{sourceIndex:0,sourceEndIndex:1,value:String(Math.round(255*v[1])),type:"word"},{sourceIndex:0,sourceEndIndex:1,value:",",type:"div",before:"",after:""},{sourceIndex:0,sourceEndIndex:1,value:String(Math.round(255*v[2])),type:"word"}],l.alpha&&(e.nodes.push({sourceIndex:0,sourceEndIndex:1,value:",",type:"div",before:"",after:""}),e.nodes.push(l.alpha))}function isColorSpaceNode(e){if(!e||"word"!==e.type)return!1;switch(e.value.toLowerCase()){case"srgb":case"srgb-linear":case"display-p3":case"a98-rgb":case"prophoto-rgb":case"rec2020":case"xyz-d50":case"xyz-d65":case"xyz":return!0;default:return!1}}function isNumericNode(e){if(!e||"word"!==e.type)return!1;if(!canParseAsUnit(e))return!1;const o=r.unit(e.value);return!!o&&!!o.number}function isNumericNodePercentageOrNumber(e){if(!e||"word"!==e.type)return!1;if(!canParseAsUnit(e))return!1;const o=r.unit(e.value);return!!o&&("%"===o.unit||""===o.unit)}function isCalcNode(e){return e&&"function"===e.type&&"calc"===e.value.toLowerCase()}function isVarNode(e){return e&&"function"===e.type&&"var"===e.value.toLowerCase()}function colorFunctionContents(e){if(!isColorSpaceNode(e[0]))return null;const o={colorSpace:e[0].value.toLowerCase(),colorSpaceNode:e[0],parameters:[]};for(let t=1;t3&&(o.parameters=o.parameters.slice(0,3))),o}function transformAlpha(e,o,n){if(!o||!n)return;if(e.value="rgba",o.value=",",o.before="",!isNumericNode(n))return;const t=r.unit(n.value);t&&"%"===t.unit&&(t.number=String(parseFloat(t.number)/100),n.value=String(t.number))}function canParseAsUnit(e){if(!e||!e.value)return!1;try{return!1!==r.unit(e.value)}catch(e){return!1}}function modifiedValues(e,o,n,t){let s;try{s=r(e)}catch(r){o.warn(n,`Failed to parse value '${e}' as a color function. Leaving the original value intact.`)}if(void 0===s)return;s.walk((e=>{e.type&&"function"===e.type&&"color"===e.value.toLowerCase()&&onCSSFunctionSRgb(e,o,n,t)}));const a=String(s);return a!==e?a:void 0}const basePlugin=e=>{const r="preserve"in Object(e)&&Boolean(e.preserve);return{postcssPlugin:"postcss-color-function",Declaration:(e,{result:o})=>{if(hasFallback(e))return;if(hasSupportsAtRuleAncestor(e))return;const n=e.value;if(!n.toLowerCase().includes("color("))return;const t=modifiedValues(n,e,o,r);void 0!==t&&(e.cloneBefore({value:t}),r||e.remove())}}};basePlugin.postcss=!0;const postcssPlugin=r=>{const o=Object.assign({preserve:!1,enableProgressiveCustomProperties:!0},r);return o.enableProgressiveCustomProperties&&o.preserve?{postcssPlugin:"postcss-color-function",plugins:[e(),basePlugin(o)]}:basePlugin(o)};postcssPlugin.postcss=!0,module.exports=postcssPlugin; +"use strict";var e=require("@csstools/postcss-progressive-custom-properties"),r=require("postcss-value-parser"),o=require("@csstools/color-helpers");function hasFallback(e){const r=e.parent;if(!r)return!1;const o=e.prop.toLowerCase(),n=r.index(e);for(let e=0;e"comment"!==e.type&&"space"!==e.type));let i,l=null;if("color"===u.toLowerCase()&&(l=colorFunctionContents(c)),!l)return;switch(e.value="rgb",transformAlpha(e,l.slash,l.alpha),l.colorSpace){case"srgb":i=o.conversions.sRGB_to_sRGB;break;case"srgb-linear":i=o.conversions.sRGB_linear_to_sRGB;break;case"a98-rgb":i=o.conversions.a98_RGB_to_sRGB;break;case"prophoto-rgb":i=o.conversions.proPhoto_RGB_to_sRGB;break;case"display-p3":i=o.conversions.p3_to_sRGB;break;case"rec2020":i=o.conversions.rec_2020_to_sRGB;break;case"xyz-d50":i=o.conversions.cie_XYZ_50_to_sRGB;break;case"xyz-d65":case"xyz":i=o.conversions.cie_XYZ_65_to_sRGB;break;default:return}const p=(d=l,d.parameters.map((e=>e.value))).map((e=>parseFloat(e.number)));var d;const v=i(p);!o.utils.inGamut(p)&&s&&n.warn(t,`"${a}" is out of gamut for "${l.colorSpace}". Given "preserve: true" is set, this will lead to unexpected results in some browsers.`),e.nodes=[{sourceIndex:0,sourceEndIndex:1,value:String(Math.round(255*v[0])),type:"word"},{sourceIndex:0,sourceEndIndex:1,value:",",type:"div",before:"",after:""},{sourceIndex:0,sourceEndIndex:1,value:String(Math.round(255*v[1])),type:"word"},{sourceIndex:0,sourceEndIndex:1,value:",",type:"div",before:"",after:""},{sourceIndex:0,sourceEndIndex:1,value:String(Math.round(255*v[2])),type:"word"}],l.alpha&&(e.nodes.push({sourceIndex:0,sourceEndIndex:1,value:",",type:"div",before:"",after:""}),e.nodes.push(l.alpha))}function isColorSpaceNode(e){if(!e||"word"!==e.type)return!1;switch(e.value.toLowerCase()){case"srgb":case"srgb-linear":case"display-p3":case"a98-rgb":case"prophoto-rgb":case"rec2020":case"xyz-d50":case"xyz-d65":case"xyz":return!0;default:return!1}}function isNumericNode(e){if(!e||"word"!==e.type)return!1;if(!canParseAsUnit(e))return!1;const o=r.unit(e.value);return!!o&&!!o.number}function isNumericNodePercentageOrNumber(e){if(!e||"word"!==e.type)return!1;if(!canParseAsUnit(e))return!1;const o=r.unit(e.value);return!!o&&("%"===o.unit||""===o.unit)}function isCalcNode(e){return e&&"function"===e.type&&"calc"===e.value.toLowerCase()}function isVarNode(e){return e&&"function"===e.type&&"var"===e.value.toLowerCase()}function colorFunctionContents(e){if(!isColorSpaceNode(e[0]))return null;const o={colorSpace:e[0].value.toLowerCase(),colorSpaceNode:e[0],parameters:[]};for(let t=1;t3&&(o.parameters=o.parameters.slice(0,3))),o}function transformAlpha(e,o,n){if(!o||!n)return;if(e.value="rgba",o.value=",",o.before="",!isNumericNode(n))return;const t=r.unit(n.value);t&&"%"===t.unit&&(t.number=String(parseFloat(t.number)/100),n.value=String(t.number))}function canParseAsUnit(e){if(!e||!e.value)return!1;try{return!1!==r.unit(e.value)}catch(e){return!1}}function modifiedValues(e,o,n,t){let s;try{s=r(e)}catch(r){o.warn(n,`Failed to parse value '${e}' as a color function. Leaving the original value intact.`)}if(void 0===s)return;s.walk((e=>{e.type&&"function"===e.type&&"color"===e.value.toLowerCase()&&onCSSFunctionSRgb(e,o,n,t)}));const a=String(s);return a!==e?a:void 0}const basePlugin=e=>{const r="preserve"in Object(e)&&Boolean(e.preserve);return{postcssPlugin:"postcss-color-function",Declaration:(e,{result:o})=>{const n=e.value;if(!n.toLowerCase().includes("color("))return;if(hasFallback(e))return;if(hasSupportsAtRuleAncestor(e))return;const t=modifiedValues(n,e,o,r);void 0!==t&&(e.cloneBefore({value:t}),r||e.remove())}}};basePlugin.postcss=!0;const postcssPlugin=r=>{const o=Object.assign({preserve:!1,enableProgressiveCustomProperties:!0},r);return o.enableProgressiveCustomProperties&&o.preserve?{postcssPlugin:"postcss-color-function",plugins:[e(),basePlugin(o)]}:basePlugin(o)};postcssPlugin.postcss=!0,module.exports=postcssPlugin; diff --git a/plugins/postcss-color-function/dist/index.mjs b/plugins/postcss-color-function/dist/index.mjs index 1449c4584..6055a8e55 100644 --- a/plugins/postcss-color-function/dist/index.mjs +++ b/plugins/postcss-color-function/dist/index.mjs @@ -1 +1 @@ -import e from"@csstools/postcss-progressive-custom-properties";import r from"postcss-value-parser";import{conversions as t,utils as o}from"@csstools/color-helpers";function hasFallback(e){const r=e.parent;if(!r)return!1;const t=e.prop.toLowerCase(),o=r.index(e);for(let e=0;e"comment"!==e.type&&"space"!==e.type));let l,p=null;if("color"===c.toLowerCase()&&(p=colorFunctionContents(i)),!p)return;switch(e.value="rgb",transformAlpha(e,p.slash,p.alpha),p.colorSpace){case"srgb":l=t.sRGB_to_sRGB;break;case"srgb-linear":l=t.sRGB_linear_to_sRGB;break;case"a98-rgb":l=t.a98_RGB_to_sRGB;break;case"prophoto-rgb":l=t.proPhoto_RGB_to_sRGB;break;case"display-p3":l=t.p3_to_sRGB;break;case"rec2020":l=t.rec_2020_to_sRGB;break;case"xyz-d50":l=t.cie_XYZ_50_to_sRGB;break;case"xyz-d65":case"xyz":l=t.cie_XYZ_65_to_sRGB;break;default:return}const d=(f=p,f.parameters.map((e=>e.value))).map((e=>parseFloat(e.number)));var f;const v=l(d);!o.inGamut(d)&&a&&n.warn(s,`"${u}" is out of gamut for "${p.colorSpace}". Given "preserve: true" is set, this will lead to unexpected results in some browsers.`),e.nodes=[{sourceIndex:0,sourceEndIndex:1,value:String(Math.round(255*v[0])),type:"word"},{sourceIndex:0,sourceEndIndex:1,value:",",type:"div",before:"",after:""},{sourceIndex:0,sourceEndIndex:1,value:String(Math.round(255*v[1])),type:"word"},{sourceIndex:0,sourceEndIndex:1,value:",",type:"div",before:"",after:""},{sourceIndex:0,sourceEndIndex:1,value:String(Math.round(255*v[2])),type:"word"}],p.alpha&&(e.nodes.push({sourceIndex:0,sourceEndIndex:1,value:",",type:"div",before:"",after:""}),e.nodes.push(p.alpha))}function isColorSpaceNode(e){if(!e||"word"!==e.type)return!1;switch(e.value.toLowerCase()){case"srgb":case"srgb-linear":case"display-p3":case"a98-rgb":case"prophoto-rgb":case"rec2020":case"xyz-d50":case"xyz-d65":case"xyz":return!0;default:return!1}}function isNumericNode(e){if(!e||"word"!==e.type)return!1;if(!canParseAsUnit(e))return!1;const t=r.unit(e.value);return!!t&&!!t.number}function isNumericNodePercentageOrNumber(e){if(!e||"word"!==e.type)return!1;if(!canParseAsUnit(e))return!1;const t=r.unit(e.value);return!!t&&("%"===t.unit||""===t.unit)}function isCalcNode(e){return e&&"function"===e.type&&"calc"===e.value.toLowerCase()}function isVarNode(e){return e&&"function"===e.type&&"var"===e.value.toLowerCase()}function colorFunctionContents(e){if(!isColorSpaceNode(e[0]))return null;const t={colorSpace:e[0].value.toLowerCase(),colorSpaceNode:e[0],parameters:[]};for(let n=1;n3&&(t.parameters=t.parameters.slice(0,3))),t}function transformAlpha(e,t,o){if(!t||!o)return;if(e.value="rgba",t.value=",",t.before="",!isNumericNode(o))return;const n=r.unit(o.value);n&&"%"===n.unit&&(n.number=String(parseFloat(n.number)/100),o.value=String(n.number))}function canParseAsUnit(e){if(!e||!e.value)return!1;try{return!1!==r.unit(e.value)}catch(e){return!1}}function modifiedValues(e,t,o,n){let s;try{s=r(e)}catch(r){t.warn(o,`Failed to parse value '${e}' as a color function. Leaving the original value intact.`)}if(void 0===s)return;s.walk((e=>{e.type&&"function"===e.type&&"color"===e.value.toLowerCase()&&onCSSFunctionSRgb(e,t,o,n)}));const a=String(s);return a!==e?a:void 0}const basePlugin=e=>{const r="preserve"in Object(e)&&Boolean(e.preserve);return{postcssPlugin:"postcss-color-function",Declaration:(e,{result:t})=>{if(hasFallback(e))return;if(hasSupportsAtRuleAncestor(e))return;const o=e.value;if(!o.toLowerCase().includes("color("))return;const n=modifiedValues(o,e,t,r);void 0!==n&&(e.cloneBefore({value:n}),r||e.remove())}}};basePlugin.postcss=!0;const postcssPlugin=r=>{const t=Object.assign({preserve:!1,enableProgressiveCustomProperties:!0},r);return t.enableProgressiveCustomProperties&&t.preserve?{postcssPlugin:"postcss-color-function",plugins:[e(),basePlugin(t)]}:basePlugin(t)};postcssPlugin.postcss=!0;export{postcssPlugin as default}; +import e from"@csstools/postcss-progressive-custom-properties";import r from"postcss-value-parser";import{conversions as t,utils as o}from"@csstools/color-helpers";function hasFallback(e){const r=e.parent;if(!r)return!1;const t=e.prop.toLowerCase(),o=r.index(e);for(let e=0;e"comment"!==e.type&&"space"!==e.type));let l,p=null;if("color"===c.toLowerCase()&&(p=colorFunctionContents(i)),!p)return;switch(e.value="rgb",transformAlpha(e,p.slash,p.alpha),p.colorSpace){case"srgb":l=t.sRGB_to_sRGB;break;case"srgb-linear":l=t.sRGB_linear_to_sRGB;break;case"a98-rgb":l=t.a98_RGB_to_sRGB;break;case"prophoto-rgb":l=t.proPhoto_RGB_to_sRGB;break;case"display-p3":l=t.p3_to_sRGB;break;case"rec2020":l=t.rec_2020_to_sRGB;break;case"xyz-d50":l=t.cie_XYZ_50_to_sRGB;break;case"xyz-d65":case"xyz":l=t.cie_XYZ_65_to_sRGB;break;default:return}const d=(f=p,f.parameters.map((e=>e.value))).map((e=>parseFloat(e.number)));var f;const v=l(d);!o.inGamut(d)&&a&&n.warn(s,`"${u}" is out of gamut for "${p.colorSpace}". Given "preserve: true" is set, this will lead to unexpected results in some browsers.`),e.nodes=[{sourceIndex:0,sourceEndIndex:1,value:String(Math.round(255*v[0])),type:"word"},{sourceIndex:0,sourceEndIndex:1,value:",",type:"div",before:"",after:""},{sourceIndex:0,sourceEndIndex:1,value:String(Math.round(255*v[1])),type:"word"},{sourceIndex:0,sourceEndIndex:1,value:",",type:"div",before:"",after:""},{sourceIndex:0,sourceEndIndex:1,value:String(Math.round(255*v[2])),type:"word"}],p.alpha&&(e.nodes.push({sourceIndex:0,sourceEndIndex:1,value:",",type:"div",before:"",after:""}),e.nodes.push(p.alpha))}function isColorSpaceNode(e){if(!e||"word"!==e.type)return!1;switch(e.value.toLowerCase()){case"srgb":case"srgb-linear":case"display-p3":case"a98-rgb":case"prophoto-rgb":case"rec2020":case"xyz-d50":case"xyz-d65":case"xyz":return!0;default:return!1}}function isNumericNode(e){if(!e||"word"!==e.type)return!1;if(!canParseAsUnit(e))return!1;const t=r.unit(e.value);return!!t&&!!t.number}function isNumericNodePercentageOrNumber(e){if(!e||"word"!==e.type)return!1;if(!canParseAsUnit(e))return!1;const t=r.unit(e.value);return!!t&&("%"===t.unit||""===t.unit)}function isCalcNode(e){return e&&"function"===e.type&&"calc"===e.value.toLowerCase()}function isVarNode(e){return e&&"function"===e.type&&"var"===e.value.toLowerCase()}function colorFunctionContents(e){if(!isColorSpaceNode(e[0]))return null;const t={colorSpace:e[0].value.toLowerCase(),colorSpaceNode:e[0],parameters:[]};for(let n=1;n3&&(t.parameters=t.parameters.slice(0,3))),t}function transformAlpha(e,t,o){if(!t||!o)return;if(e.value="rgba",t.value=",",t.before="",!isNumericNode(o))return;const n=r.unit(o.value);n&&"%"===n.unit&&(n.number=String(parseFloat(n.number)/100),o.value=String(n.number))}function canParseAsUnit(e){if(!e||!e.value)return!1;try{return!1!==r.unit(e.value)}catch(e){return!1}}function modifiedValues(e,t,o,n){let s;try{s=r(e)}catch(r){t.warn(o,`Failed to parse value '${e}' as a color function. Leaving the original value intact.`)}if(void 0===s)return;s.walk((e=>{e.type&&"function"===e.type&&"color"===e.value.toLowerCase()&&onCSSFunctionSRgb(e,t,o,n)}));const a=String(s);return a!==e?a:void 0}const basePlugin=e=>{const r="preserve"in Object(e)&&Boolean(e.preserve);return{postcssPlugin:"postcss-color-function",Declaration:(e,{result:t})=>{const o=e.value;if(!o.toLowerCase().includes("color("))return;if(hasFallback(e))return;if(hasSupportsAtRuleAncestor(e))return;const n=modifiedValues(o,e,t,r);void 0!==n&&(e.cloneBefore({value:n}),r||e.remove())}}};basePlugin.postcss=!0;const postcssPlugin=r=>{const t=Object.assign({preserve:!1,enableProgressiveCustomProperties:!0},r);return t.enableProgressiveCustomProperties&&t.preserve?{postcssPlugin:"postcss-color-function",plugins:[e(),basePlugin(t)]}:basePlugin(t)};postcssPlugin.postcss=!0;export{postcssPlugin as default}; diff --git a/plugins/postcss-color-function/src/index.ts b/plugins/postcss-color-function/src/index.ts index 375782251..e6721be30 100644 --- a/plugins/postcss-color-function/src/index.ts +++ b/plugins/postcss-color-function/src/index.ts @@ -15,16 +15,16 @@ const basePlugin: PluginCreator = (opts: basePluginOptions) = return { postcssPlugin: 'postcss-color-function', Declaration: (decl: Declaration, { result }: { result: Result }) => { - if (hasFallback(decl)) { + const originalValue = decl.value; + if (!originalValue.toLowerCase().includes('color(')) { return; } - if (hasSupportsAtRuleAncestor(decl)) { + if (hasFallback(decl)) { return; } - const originalValue = decl.value; - if (!originalValue.toLowerCase().includes('color(')) { + if (hasSupportsAtRuleAncestor(decl)) { return; } diff --git a/plugins/postcss-color-function/test/basic.css b/plugins/postcss-color-function/test/basic.css index a4f166994..9f21433bf 100644 --- a/plugins/postcss-color-function/test/basic.css +++ b/plugins/postcss-color-function/test/basic.css @@ -209,3 +209,7 @@ --one-a50-var: color(display-p3 0.1 0.1 0.1); } } + +to-clone { + color: color(display-p3 1); +} diff --git a/plugins/postcss-color-function/test/basic.expect.css b/plugins/postcss-color-function/test/basic.expect.css index c4dc2cb9a..25e8cb54d 100644 --- a/plugins/postcss-color-function/test/basic.expect.css +++ b/plugins/postcss-color-function/test/basic.expect.css @@ -209,3 +209,7 @@ --one-a50-var: color(display-p3 0.1 0.1 0.1); } } + +to-clone { + color: rgb(255,10,11); +} diff --git a/plugins/postcss-color-function/test/basic.preserve-true.expect.css b/plugins/postcss-color-function/test/basic.preserve-true.expect.css index b0322787b..e1974ae3c 100644 --- a/plugins/postcss-color-function/test/basic.preserve-true.expect.css +++ b/plugins/postcss-color-function/test/basic.preserve-true.expect.css @@ -298,3 +298,8 @@ --one-a50-var: color(display-p3 0.1 0.1 0.1); } } + +to-clone { + color: rgb(255,10,11); + color: color(display-p3 1); +} diff --git a/plugins/postcss-color-function/test/basic.with-cloned-rules.expect.css b/plugins/postcss-color-function/test/basic.with-cloned-rules.expect.css new file mode 100644 index 000000000..8a480e931 --- /dev/null +++ b/plugins/postcss-color-function/test/basic.with-cloned-rules.expect.css @@ -0,0 +1,310 @@ +.test-color-multi-channel { + color-1: rgb(255,10,11); + color-1: color(display-p3 1); + color-2: rgba(255,10,11,0.5); + color-2: color(display-p3 1 / 0.5); + color-3: rgb(254,255,0); + color-3: color(display-p3 1 1); + color-4: rgba(254,255,0,0.5); + color-4: color(display-p3 1 1 / 0.5); + color-5: rgb(255,255,255); + color-5: color(display-p3 1 1 1 1); + color-6: rgba(255,255,255,0.5); + color-6: color(display-p3 1 1 1 1 / 0.5); +} + +.test-case { + color-1: rgb(0,0,0); + color-1: color(DISPLAY-P3 0 0 0); + color-3: rgba(0,0,0,CALC(1 + 1)); + color-3: color(display-P3 0 0 0 / CALC(1 + 1)); + color-4: rgba(255,255,255,VAR(--ALPHA)); + color-4: color(Display-P3 1 1 1 / VAR(--ALPHA)); +} + +.test-color-none { + color-1: color(display-p3 none); + color-3: color(display-p3 none none); + color-4: color(display-p3 none none none); + color-5: color(display-p3 1 none 1); + color-6: color(display-p3 1 none none); + color-7: color(display-p3 none 1 none); + color-8: color(display-p3 none none 1); +} + +.test-css-color-5-interop { + color-1: color(from rgb(196,129,72) a98-rgb r g b / none); + color-1: color(from color(a98-rgb 0.7 0.5 0.3) a98-rgb r g b / none); + color-2: color(from rgb(234,133,82) prophoto-rgb r g none); + color-2: color(from color(prophoto-rgb 0.7 0.5 0.3) prophoto-rgb r g none); +} + +.test-unknown-space { + color-1: color(unknown 0.64331 0.19245 0.16771); +} + +.test-percentage-rgb { + color-1: rgb(164,49,43); + color-1: color(srgb 64.331% 0.19245 0.16771); + color-2: rgb(164,49,43); + color-2: color(srgb 0.64331 19.245% 0.16771); + color-3: rgb(164,49,43); + color-3: color(srgb 0.64331 0.19245 16.771%); +} + +.test-srgb { + color-1: rgb(164,49,43); + color-1: color(srgb 0.64331 0.19245 0.16771); + color-2: rgba(164,49,43,1); + color-2: color(srgb 0.64331 0.19245 0.16771 / 1); + color-3: rgba(164,49,43,.5); + color-3: color(srgb 0.64331 0.19245 0.16771 / .5); + color-4: rgba(164,49,43,1); + color-4: color(srgb 0.64331 0.19245 0.16771 / 100%); + color-5: rgba(164,49,43,0.5); + color-5: color(srgb 0.64331 0.19245 0.16771 / 50%); + color-6: rgb(208,111,146); + color-6: color(srgb 0.81388 0.43646 0.57322); + color-7: color(srgb 0.81388 0.43646 foo); +} + +.test-display-p3 { + color-1: rgb(179,35,35); + color-1: color(display-p3 0.64331 0.19245 0.16771); + color-2: rgba(179,35,35,1); + color-2: color(display-p3 0.64331 0.19245 0.16771 / 1); + color-3: rgba(179,35,35,.5); + color-3: color(display-p3 0.64331 0.19245 0.16771 / .5); + color-4: rgba(179,35,35,1); + color-4: color(display-p3 0.64331 0.19245 0.16771 / 100%); + color-5: rgba(179,35,35,0.5); + color-5: color(display-p3 0.64331 0.19245 0.16771 / 50%); + color-6: rgb(222,105,147); + color-6: color(display-p3 0.81388 0.43646 0.57322); + color-7: color(display-p3 0.81388 0.43646 foo); +} + +.test-linear-srgb { + color-1: rgb(176,35,66); + color-1: color(srgb 0.691 0.139 0.259); + color-2: rgb(176,35,66); + color-2: color(srgb-linear 0.435 0.017 0.055); +} + +.test-xyz { + color-1: rgb(118,84,205); + color-1: color(xyz-d50 0.2005 0.14089 0.4472); + color-2: rgb(118,84,205); + color-2: color(xyz-d65 0.21661 0.14602 0.59452); + color-3: rgb(118,84,205); + color-3: color(xyz 0.21661 0.14602 0.59452); +} + +.test-percentage-xyz { + color-1: rgb(252,0,135); + color-1: color(xyz-d50 64.331% 0.19245 0.16771); + color-2: rgb(255,0,126); + color-2: color(xyz-d65 0.64331 19.245% 0.16771); + color-3: rgb(255,0,126); + color-3: color(xyz 0.64331 0.19245 16.771%); +} + +.test-author-provided-fallback { + color: rgb(0, 0, 0); + color: color(display-p3 1 1 1); +} + +/* this will most likely be a mistake by the author */ +.test-author-provided-override { + color: rgb(255,255,255); + color: color(display-p3 1 1 1); + color: rgb(0, 0, 0); +} + +.test-alpha { + color-1: rgba(7,3,1,1); + color-1: color(display-p3 0.02472 0.01150 0.00574 / 1); + color-2: rgba(7,3,1,calc(33 / 22)); + color-2: color(display-p3 0.02472 0.01150 0.00574 / calc(33 / 22)); + color-3: rgba(7,3,1,calc(20% * 5)); + color-3: color(display-p3 0.02472 0.01150 0.00574 / calc(20% * 5)); + color-4: rgba(7,3,1,var(--foo)); + color-4: color(display-p3 0.02472 0.01150 0.00574 / var(--foo)); + color-5: rgb(7,3,1); + color-5: color(display-p3 0.02472 0.01150 0.00574 /); +} + + +.test-ignore { + prop-1: 'color(display-p3 0.02472 0.01150 0.00574 / 1)'; + prop-2: url('color(display-p3 0.02472 0.01150 0.00574 / 1)'); +} + +.test-unparseable-lab-function { + background-image: color(; ); +} + +.test-gamut { + /* https://romainmenke.github.io/lab-lch-display-p3/lab-interactive-b.html#%5B%5B48%2C-53%2C12%5D%2C%5B58%2C-54%2C69%5D%5D */ + /* out */ + prop-1: rgb(0,132,94); + prop-1: color(display-p3 0.00000 0.51872 0.36985); + /* out */ + prop-2: rgb(38,161,0); + prop-2: color(display-p3 0.31275 0.62335 0.08647); + + /* https://romainmenke.github.io/lab-lch-display-p3/lab-interactive-b.html#%5B%5B45%2C-13%2C29%5D%2C%5B77%2C33%2C97%5D%5D */ + /* in */ + prop-3: rgb(97,112,56); + prop-3: color(display-p3 0.39215 0.43776 0.24705); + /* out */ + prop-4: rgb(255,165,0); + prop-4: color(display-p3 0.99733 0.66278 0.12085); + + /* https://romainmenke.github.io/lab-lch-display-p3/lab-interactive-b.html#%5B%5B76%2C-29%2C-23%5D%2C%5B16%2C-32%2C24%5D%5D */ + /* in */ + prop-5: rgb(85,204,229); + prop-5: color(display-p3 0.46284 0.78863 0.88439); + /* out */ + prop-6: rgb(0,50,0); + prop-6: color(display-p3 0.00652 0.18999 0.01056); + + /* https://romainmenke.github.io/lab-lch-display-p3/lab-interactive-b.html#%5B%5B85%2C16%2C29%5D%2C%5B55%2C-42%2C58%5D%5D */ + /* out */ + prop-7: rgb(255,201,158); + prop-7: color(display-p3 0.96684 0.79482 0.64336); + /* out */ + prop-8: rgb(70,149,0); + prop-8: color(display-p3 0.35483 0.57788 0.15007); + + /* https://romainmenke.github.io/lab-lch-display-p3/lab-interactive-b.html#%5B%5B87%2C-94%2C103%5D%2C%5B88%2C-9%2C62%5D%5D */ + /* out */ + prop-9: rgb(0,253,0); + prop-9: color(display-p3 0.32231 0.99185 0.02928); + /* in */ + prop-10: rgb(231,224,96); + prop-10: color(display-p3 0.90245 0.87996 0.45339); + + /* https://romainmenke.github.io/lab-lch-display-p3/lab-interactive-b.html#%5B%5B69%2C-35%2C-39%5D%2C%5B68%2C61%2C39%5D%5D */ + /* out */ + prop-11: rgb(0,187,234); + prop-11: color(display-p3 0.16541 0.72332 0.91352); + /* out */ + prop-12: rgb(255,117,105); + prop-12: color(display-p3 0.99104 0.47662 0.41939); + + /* https://romainmenke.github.io/lab-lch-display-p3/lab-interactive-b.html#%5B%5B60%2C-28%2C74%5D%2C%5B65%2C81%2C-13%5D%5D */ + /* out */ + prop-13: rgb(119,157,0); + prop-13: color(display-p3 0.49844 0.61099 0.07831); + /* out */ + prop-14: rgb(255,89,183); + prop-14: color(display-p3 0.99687 0.35134 0.71095); + + /* https://romainmenke.github.io/lab-lch-display-p3/lab-interactive-b.html#%5B%5B60%2C-28%2C15%5D%2C%5B65%2C81%2C-11%5D%5D */ + /* in */ + prop-15: rgb(96,158,117); + prop-15: color(display-p3 0.43165 0.61289 0.47061); + /* out */ + prop-16: rgb(255,90,179); + prop-16: color(display-p3 0.99937 0.35096 0.69833); + + /* https://romainmenke.github.io/lab-lch-display-p3/lab-interactive-b.html#%5B%5B78%2C-64%2C-44%5D%2C%5B76%2C81%2C-11%5D%5D */ + /* out */ + prop-17: rgb(0,214,243); + prop-17: color(display-p3 0.00000 0.83784 0.97033); + /* out */ + prop-18: rgb(255,156,205); + prop-18: color(display-p3 1.00000 0.62326 0.79886); +} + +.test-out-of-range-values-srgb { + color-1: rgb(128,0,255); + color-1: color(srgb 0.5 0 1); + color-2: rgb(125,0,245); + color-2: color(srgb 0.5 -0.2 1); + color-3: rgb(25,255,25); + color-3: color(srgb 0.1 1 0.1); + color-4: rgb(159,255,151); + color-4: color(srgb 0.1 1.1 0.1); +} + +.test-lime { + color-1: rgb(0,255,0); + color-1: color(srgb 0.0005556487875468122 0.9999999999999999 -0.00220276712790066); + color-2: rgb(0,255,0); + color-2: color(srgb-linear 0.000043006872101146454 0.9999999999999999 -0.00017049281175701703); + color-3: rgb(0,255,0); + color-3: color(a98-rgb 0.565 1 0.234); + color-4: rgb(0,255,0); + color-4: color(prophoto-rgb 0.5402796954751572 0.9275945561561767 0.30435477084804174); + color-5: rgb(0,255,0); + color-5: color(display-p3 0.45844420720487417 0.9852652233445233 0.29798036139719497); + color-6: rgb(0,255,0); + color-6: color(rec2020 0.5675603321833232 0.9592792129938423 0.2686829491074993); + color-7: rgb(0,255,0); + color-7: color(xyz-d50 0.3851458288094242 0.7168862873215868 0.09696013568183873); + color-8: rgb(0,255,0); + color-8: color(xyz-d65 0.35757130434515494 0.7151655154354521 0.11903355225337156); + color-9: rgb(0,255,0); + color-9: color(xyz 0.35757130434515494 0.7151655154354521 0.11903355225337156); +} + +.test-blue-teal { + color-1: rgb(0,132,138); + color-1: color(srgb -0.13610556145124594 0.5177053690420603 0.540031109817831); + color-2: rgb(0,132,138); + color-2: color(srgb-linear -0.01656723676661187 0.23079644121427306 0.25298181882824156); + color-3: rgb(0,132,138); + color-3: color(a98-rgb 0.265 0.5134 0.5344); + color-4: rgb(0,132,138); + color-4: color(prophoto-rgb 0.28284813138491105 0.41695332740189284 0.4586239337463392); + color-5: rgb(0,132,138); + color-5: color(display-p3 0.18049383596973329 0.5091259470889726 0.5339002129941044); + color-6: rgb(0,132,138); + color-6: color(rec2020 0.24657637908526203 0.44994210472189566 0.486194553499425); + color-7: rgb(0,132,138); + color-7: color(xyz-d50 0.11786343156307554 0.1771045882551784 0.2028294891298204); + color-8: rgb(0,132,138); + color-8: color(xyz-d65 0.12135537506539545 0.1797988884168019 0.2676568254682071); + color-9: rgb(0,132,138); + color-9: color(xyz 0.12135537506539545 0.1797988884168019 0.2676568254682071); +} + +.test-bright-purple { + color-1: rgb(229,125,245); + color-1: color(srgb 0.8978862558205767 0.4885001647805462 0.9594359763905097); + color-2: rgb(229,125,245); + color-2: color(srgb-linear 0.7832360124544266 0.2035510416163499 0.9101924728483531); + color-3: rgb(229,125,245); + color-3: color(a98-rgb 0.8035122804301492 0.484896415622613 0.9440692746539695); + color-4: rgb(229,125,245); + color-4: color(prophoto-rgb 0.7596595159204217 0.4934889951894072 0.8985832663171222); + color-5: rgb(229,125,245); + color-5: color(display-p3 0.843565234 0.509345345 0.9342344435); + color-6: rgb(229,125,245); + color-6: color(rec2020 0.7728366085950608 0.49153213847089583 0.9202627474826224); + color-7: rgb(229,125,245); + color-7: color(xyz-d50 0.5501693084815327 0.37536346388820246 0.6806345611398199); + color-8: rgb(229,125,245); + color-8: color(xyz-d65 0.5600582450343325 0.37782875858447507 0.904570025128693); + color-9: rgb(229,125,245); + color-9: color(xyz 0.5600582450343325 0.37782875858447507 0.904570025128693); +} + +/* manual @supports */ +@supports (color: color(display-p3 0 0 0)) and (contain: content) { + :root { + --one-a50-var: color(display-p3 0.1 0.1 0.1); + } +} + +cloned { + color: rgb(255,10,11); + color: color(display-p3 1); +} + +to-clone { + color: rgb(255,10,11); + color: color(display-p3 1); +} diff --git a/plugins/postcss-color-functional-notation/.tape.mjs b/plugins/postcss-color-functional-notation/.tape.mjs index 90f0c0814..f0277124e 100644 --- a/plugins/postcss-color-functional-notation/.tape.mjs +++ b/plugins/postcss-color-functional-notation/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape, ruleClonerPlugin } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from 'postcss-color-functional-notation'; postcssTape(plugin)({ @@ -11,6 +11,15 @@ postcssTape(plugin)({ preserve: true } }, + 'basic:with-cloned-rules': { + message: 'doesn\'t cause duplicate CSS', + plugins: [ + ruleClonerPlugin, + plugin({ + preserve: true + }) + ] + }, 'variables': { message: 'supports variables', }, diff --git a/plugins/postcss-color-functional-notation/CHANGELOG.md b/plugins/postcss-color-functional-notation/CHANGELOG.md index 9a94ee20f..549db5535 100644 --- a/plugins/postcss-color-functional-notation/CHANGELOG.md +++ b/plugins/postcss-color-functional-notation/CHANGELOG.md @@ -1,5 +1,9 @@ # Changes to PostCSS Color Functional Notation +### Unreleased (patch) + +- Reduce the amount of duplicate fallback CSS. + ### 5.0.1 (January 28, 2023) - Improve `types` declaration in `package.json` diff --git a/plugins/postcss-color-functional-notation/dist/has-fallback-decl.d.ts b/plugins/postcss-color-functional-notation/dist/has-fallback-decl.d.ts new file mode 100644 index 000000000..d7ae8d890 --- /dev/null +++ b/plugins/postcss-color-functional-notation/dist/has-fallback-decl.d.ts @@ -0,0 +1,2 @@ +import type { Declaration } from 'postcss'; +export declare function hasFallback(node: Declaration): boolean; diff --git a/plugins/postcss-color-functional-notation/dist/index.cjs b/plugins/postcss-color-functional-notation/dist/index.cjs index 31fccf0e6..cdde979ee 100644 --- a/plugins/postcss-color-functional-notation/dist/index.cjs +++ b/plugins/postcss-color-functional-notation/dist/index.cjs @@ -1 +1 @@ -"use strict";var e=require("postcss-value-parser");function onCSSFunction(e){const r=e.value.toLowerCase();if(!needsConversion("rgb"===r||"rgba"===r,e.nodes))return;const n=convertOldSyntaxToNewSyntaxBeforeTransform(e.nodes).slice().filter((e=>"comment"!==e.type&&"space"!==e.type));let t=null;if("hsl"===r||"hsla"===r?t=hslFunctionContents(n):"rgb"!==r&&"rgba"!==r||(t=rgbFunctionContents(n)),!t)return;if(n.length>3&&(!t.slash||!t.alpha))return;transformAlpha(e,t.slash,t.alpha);const[u,o]=channelNodes(t);e.nodes.splice(e.nodes.indexOf(u)+1,0,{sourceIndex:0,sourceEndIndex:1,value:",",type:"div",before:"",after:""}),e.nodes.splice(e.nodes.indexOf(o)+1,0,{sourceIndex:0,sourceEndIndex:1,value:",",type:"div",before:"",after:""})}function isNumericNode(r){if(!r||"word"!==r.type)return!1;if(!canParseAsUnit(r))return!1;const n=e.unit(r.value);return!!n&&!!n.number}function isNumericNodeHueLike(r){if(!r||"word"!==r.type)return!1;if(!canParseAsUnit(r))return!1;const n=e.unit(r.value);if(!n)return!1;const t=n.unit.toLowerCase();return!!n.number&&("deg"===t||"grad"===t||"rad"===t||"turn"===t||""===n.unit)}function isNumericNodePercentageOrNumber(r){if(!r||"word"!==r.type)return!1;if(!canParseAsUnit(r))return!1;const n=e.unit(r.value);return!!n&&("%"===n.unit||""===n.unit)}function isCalcNode(e){return e&&"function"===e.type&&"calc"===e.value.toLowerCase()}function isVarNode(e){return e&&"function"===e.type&&"var"===e.value.toLowerCase()}function isSlashNode(e){return e&&"div"===e.type&&"/"===e.value}function hslFunctionContents(r){if(!isNumericNodeHueLike(r[0]))return null;if(!isNumericNodePercentageOrNumber(r[1]))return null;if(!isNumericNodePercentageOrNumber(r[2]))return null;const n={h:e.unit(r[0].value),hNode:r[0],s:e.unit(r[1].value),sNode:r[1],l:e.unit(r[2].value),lNode:r[2]};return normalizeHueNode(n.h),""!==n.h.unit?null:(n.hNode.value=n.h.number,isSlashNode(r[3])&&(n.slash=r[3]),(isNumericNodePercentageOrNumber(r[4])||isCalcNode(r[4])||isVarNode(r[4]))&&(n.alpha=r[4]),n)}function rgbFunctionContents(r){if(!isNumericNodePercentageOrNumber(r[0]))return null;if(!isNumericNodePercentageOrNumber(r[1]))return null;if(!isNumericNodePercentageOrNumber(r[2]))return null;const n={r:e.unit(r[0].value),rNode:r[0],g:e.unit(r[1].value),gNode:r[1],b:e.unit(r[2].value),bNode:r[2]};return"%"===n.r.unit&&(n.r.number=String(Math.floor(Number(n.r.number)/100*255)),n.rNode.value=n.r.number),"%"===n.g.unit&&(n.g.number=String(Math.floor(Number(n.g.number)/100*255)),n.gNode.value=n.g.number),"%"===n.b.unit&&(n.b.number=String(Math.floor(Number(n.b.number)/100*255)),n.bNode.value=n.b.number),isSlashNode(r[3])&&(n.slash=r[3]),(isNumericNodePercentageOrNumber(r[4])||isCalcNode(r[4])||isVarNode(r[4]))&&(n.alpha=r[4]),n}function isRgb(e){return void 0!==e.r}function channelNodes(e){return isRgb(e)?[e.rNode,e.gNode,e.bNode]:[e.hNode,e.sNode,e.lNode]}function transformAlpha(r,n,t){if("hsl"===r.value.toLowerCase()||"hsla"===r.value.toLowerCase()?r.value="hsl":"rgb"!==r.value.toLowerCase()&&"rgba"!==r.value.toLowerCase()||(r.value="rgb"),!n||!t)return;if("hsl"===r.value.toLowerCase()?r.value="hsla":r.value="rgba",n.value=",",n.before="",!isNumericNode(t))return;const u=e.unit(t.value);u&&"%"===u.unit&&(u.number=String(parseFloat(u.number)/100),t.value=String(u.number))}function normalizeHueNode(e){switch(e.unit.toLowerCase()){case"deg":return void(e.unit="");case"rad":return e.unit="",void(e.number=Math.round(180*parseFloat(e.number)/Math.PI).toString());case"grad":return e.unit="",void(e.number=Math.round(.9*parseFloat(e.number)).toString());case"turn":return e.unit="",void(e.number=Math.round(360*parseFloat(e.number)).toString())}}function canParseAsUnit(r){if(!r||!r.value)return!1;try{return!1!==e.unit(r.value)}catch(e){return!1}}function convertOldSyntaxToNewSyntaxBeforeTransform(e){let r=0;for(let n=0;n2)return;r++}}return e}function needsConversion(e,r){let n=!1,t=!1,u=!1;const o=r.slice().filter((e=>"comment"!==e.type&&"space"!==e.type));for(let a=0;a{const n="preserve"in Object(r)&&Boolean(r.preserve);return{postcssPlugin:"postcss-color-functional-notation",Declaration:(r,{result:t,postcss:u})=>{if(hasSupportsAtRuleAncestor(r))return;const o=r.value,a=o.toLowerCase();if(!(a.includes("rgb")||a.includes("rgba")||a.includes("hsl")||a.includes("hsla")))return;let s;try{s=e(o)}catch(e){r.warn(t,`Failed to parse value '${o}' as a hsl or rgb function. Leaving the original value intact.`)}if(void 0===s)return;s.walk((e=>{if(!e.type||"function"!==e.type)return;const r=e.value.toLowerCase();"hsl"!==r&&"hsla"!==r&&"rgb"!==r&&"rgba"!==r||onCSSFunction(e)}));const i=String(s);if(i!==o)if(n&&r.variable){const e=r.parent,n="(color: rgb(0 0 0 / 0.5)) and (color: hsl(0 0% 0% / 0.5))",t=u.atRule({name:"supports",params:n,source:r.source}),o=e.clone();o.removeAll(),o.append(r.clone()),t.append(o);let a=e,s=e.next();for(;a&&s&&"atrule"===s.type&&"supports"===s.name&&s.params===n;)a=s,s=s.next();a.after(t),r.replaceWith(r.clone({value:i}))}else n?r.cloneBefore({value:i}):r.replaceWith(r.clone({value:i}))}}};postcssPlugin.postcss=!0,module.exports=postcssPlugin; +"use strict";var e=require("postcss-value-parser");function onCSSFunction(e){const r=e.value.toLowerCase();if(!needsConversion("rgb"===r||"rgba"===r,e.nodes))return;const n=convertOldSyntaxToNewSyntaxBeforeTransform(e.nodes).slice().filter((e=>"comment"!==e.type&&"space"!==e.type));let t=null;if("hsl"===r||"hsla"===r?t=hslFunctionContents(n):"rgb"!==r&&"rgba"!==r||(t=rgbFunctionContents(n)),!t)return;if(n.length>3&&(!t.slash||!t.alpha))return;transformAlpha(e,t.slash,t.alpha);const[o,u]=channelNodes(t);e.nodes.splice(e.nodes.indexOf(o)+1,0,{sourceIndex:0,sourceEndIndex:1,value:",",type:"div",before:"",after:""}),e.nodes.splice(e.nodes.indexOf(u)+1,0,{sourceIndex:0,sourceEndIndex:1,value:",",type:"div",before:"",after:""})}function isNumericNode(r){if(!r||"word"!==r.type)return!1;if(!canParseAsUnit(r))return!1;const n=e.unit(r.value);return!!n&&!!n.number}function isNumericNodeHueLike(r){if(!r||"word"!==r.type)return!1;if(!canParseAsUnit(r))return!1;const n=e.unit(r.value);if(!n)return!1;const t=n.unit.toLowerCase();return!!n.number&&("deg"===t||"grad"===t||"rad"===t||"turn"===t||""===n.unit)}function isNumericNodePercentageOrNumber(r){if(!r||"word"!==r.type)return!1;if(!canParseAsUnit(r))return!1;const n=e.unit(r.value);return!!n&&("%"===n.unit||""===n.unit)}function isCalcNode(e){return e&&"function"===e.type&&"calc"===e.value.toLowerCase()}function isVarNode(e){return e&&"function"===e.type&&"var"===e.value.toLowerCase()}function isSlashNode(e){return e&&"div"===e.type&&"/"===e.value}function hslFunctionContents(r){if(!isNumericNodeHueLike(r[0]))return null;if(!isNumericNodePercentageOrNumber(r[1]))return null;if(!isNumericNodePercentageOrNumber(r[2]))return null;const n={h:e.unit(r[0].value),hNode:r[0],s:e.unit(r[1].value),sNode:r[1],l:e.unit(r[2].value),lNode:r[2]};return normalizeHueNode(n.h),""!==n.h.unit?null:(n.hNode.value=n.h.number,isSlashNode(r[3])&&(n.slash=r[3]),(isNumericNodePercentageOrNumber(r[4])||isCalcNode(r[4])||isVarNode(r[4]))&&(n.alpha=r[4]),n)}function rgbFunctionContents(r){if(!isNumericNodePercentageOrNumber(r[0]))return null;if(!isNumericNodePercentageOrNumber(r[1]))return null;if(!isNumericNodePercentageOrNumber(r[2]))return null;const n={r:e.unit(r[0].value),rNode:r[0],g:e.unit(r[1].value),gNode:r[1],b:e.unit(r[2].value),bNode:r[2]};return"%"===n.r.unit&&(n.r.number=String(Math.floor(Number(n.r.number)/100*255)),n.rNode.value=n.r.number),"%"===n.g.unit&&(n.g.number=String(Math.floor(Number(n.g.number)/100*255)),n.gNode.value=n.g.number),"%"===n.b.unit&&(n.b.number=String(Math.floor(Number(n.b.number)/100*255)),n.bNode.value=n.b.number),isSlashNode(r[3])&&(n.slash=r[3]),(isNumericNodePercentageOrNumber(r[4])||isCalcNode(r[4])||isVarNode(r[4]))&&(n.alpha=r[4]),n}function isRgb(e){return void 0!==e.r}function channelNodes(e){return isRgb(e)?[e.rNode,e.gNode,e.bNode]:[e.hNode,e.sNode,e.lNode]}function transformAlpha(r,n,t){if("hsl"===r.value.toLowerCase()||"hsla"===r.value.toLowerCase()?r.value="hsl":"rgb"!==r.value.toLowerCase()&&"rgba"!==r.value.toLowerCase()||(r.value="rgb"),!n||!t)return;if("hsl"===r.value.toLowerCase()?r.value="hsla":r.value="rgba",n.value=",",n.before="",!isNumericNode(t))return;const o=e.unit(t.value);o&&"%"===o.unit&&(o.number=String(parseFloat(o.number)/100),t.value=String(o.number))}function normalizeHueNode(e){switch(e.unit.toLowerCase()){case"deg":return void(e.unit="");case"rad":return e.unit="",void(e.number=Math.round(180*parseFloat(e.number)/Math.PI).toString());case"grad":return e.unit="",void(e.number=Math.round(.9*parseFloat(e.number)).toString());case"turn":return e.unit="",void(e.number=Math.round(360*parseFloat(e.number)).toString())}}function canParseAsUnit(r){if(!r||!r.value)return!1;try{return!1!==e.unit(r.value)}catch(e){return!1}}function convertOldSyntaxToNewSyntaxBeforeTransform(e){let r=0;for(let n=0;n2)return;r++}}return e}function needsConversion(e,r){let n=!1,t=!1,o=!1;const u=r.slice().filter((e=>"comment"!==e.type&&"space"!==e.type));for(let a=0;a{const n="preserve"in Object(r)&&Boolean(r.preserve);return{postcssPlugin:"postcss-color-functional-notation",Declaration:(r,{result:t,postcss:o})=>{const u=r.value,a=u.toLowerCase();if(!(a.includes("rgb")||a.includes("rgba")||a.includes("hsl")||a.includes("hsla")))return;if(hasFallback(r))return;if(hasSupportsAtRuleAncestor(r))return;let s;try{s=e(u)}catch(e){r.warn(t,`Failed to parse value '${u}' as a hsl or rgb function. Leaving the original value intact.`)}if(void 0===s)return;s.walk((e=>{if(!e.type||"function"!==e.type)return;const r=e.value.toLowerCase();"hsl"!==r&&"hsla"!==r&&"rgb"!==r&&"rgba"!==r||onCSSFunction(e)}));const i=String(s);if(i!==u)if(n&&r.variable){const e=r.parent,n="(color: rgb(0 0 0 / 0.5)) and (color: hsl(0 0% 0% / 0.5))",t=o.atRule({name:"supports",params:n,source:r.source}),u=e.clone();u.removeAll(),u.append(r.clone()),t.append(u);let a=e,s=e.next();for(;a&&s&&"atrule"===s.type&&"supports"===s.name&&s.params===n;)a=s,s=s.next();a.after(t),r.replaceWith(r.clone({value:i}))}else n?r.cloneBefore({value:i}):r.replaceWith(r.clone({value:i}))}}};postcssPlugin.postcss=!0,module.exports=postcssPlugin; diff --git a/plugins/postcss-color-functional-notation/dist/index.mjs b/plugins/postcss-color-functional-notation/dist/index.mjs index 611b67fce..c20f5d91d 100644 --- a/plugins/postcss-color-functional-notation/dist/index.mjs +++ b/plugins/postcss-color-functional-notation/dist/index.mjs @@ -1 +1 @@ -import e from"postcss-value-parser";function onCSSFunction(e){const r=e.value.toLowerCase();if(!needsConversion("rgb"===r||"rgba"===r,e.nodes))return;const n=convertOldSyntaxToNewSyntaxBeforeTransform(e.nodes).slice().filter((e=>"comment"!==e.type&&"space"!==e.type));let t=null;if("hsl"===r||"hsla"===r?t=hslFunctionContents(n):"rgb"!==r&&"rgba"!==r||(t=rgbFunctionContents(n)),!t)return;if(n.length>3&&(!t.slash||!t.alpha))return;transformAlpha(e,t.slash,t.alpha);const[u,o]=channelNodes(t);e.nodes.splice(e.nodes.indexOf(u)+1,0,{sourceIndex:0,sourceEndIndex:1,value:",",type:"div",before:"",after:""}),e.nodes.splice(e.nodes.indexOf(o)+1,0,{sourceIndex:0,sourceEndIndex:1,value:",",type:"div",before:"",after:""})}function isNumericNode(r){if(!r||"word"!==r.type)return!1;if(!canParseAsUnit(r))return!1;const n=e.unit(r.value);return!!n&&!!n.number}function isNumericNodeHueLike(r){if(!r||"word"!==r.type)return!1;if(!canParseAsUnit(r))return!1;const n=e.unit(r.value);if(!n)return!1;const t=n.unit.toLowerCase();return!!n.number&&("deg"===t||"grad"===t||"rad"===t||"turn"===t||""===n.unit)}function isNumericNodePercentageOrNumber(r){if(!r||"word"!==r.type)return!1;if(!canParseAsUnit(r))return!1;const n=e.unit(r.value);return!!n&&("%"===n.unit||""===n.unit)}function isCalcNode(e){return e&&"function"===e.type&&"calc"===e.value.toLowerCase()}function isVarNode(e){return e&&"function"===e.type&&"var"===e.value.toLowerCase()}function isSlashNode(e){return e&&"div"===e.type&&"/"===e.value}function hslFunctionContents(r){if(!isNumericNodeHueLike(r[0]))return null;if(!isNumericNodePercentageOrNumber(r[1]))return null;if(!isNumericNodePercentageOrNumber(r[2]))return null;const n={h:e.unit(r[0].value),hNode:r[0],s:e.unit(r[1].value),sNode:r[1],l:e.unit(r[2].value),lNode:r[2]};return normalizeHueNode(n.h),""!==n.h.unit?null:(n.hNode.value=n.h.number,isSlashNode(r[3])&&(n.slash=r[3]),(isNumericNodePercentageOrNumber(r[4])||isCalcNode(r[4])||isVarNode(r[4]))&&(n.alpha=r[4]),n)}function rgbFunctionContents(r){if(!isNumericNodePercentageOrNumber(r[0]))return null;if(!isNumericNodePercentageOrNumber(r[1]))return null;if(!isNumericNodePercentageOrNumber(r[2]))return null;const n={r:e.unit(r[0].value),rNode:r[0],g:e.unit(r[1].value),gNode:r[1],b:e.unit(r[2].value),bNode:r[2]};return"%"===n.r.unit&&(n.r.number=String(Math.floor(Number(n.r.number)/100*255)),n.rNode.value=n.r.number),"%"===n.g.unit&&(n.g.number=String(Math.floor(Number(n.g.number)/100*255)),n.gNode.value=n.g.number),"%"===n.b.unit&&(n.b.number=String(Math.floor(Number(n.b.number)/100*255)),n.bNode.value=n.b.number),isSlashNode(r[3])&&(n.slash=r[3]),(isNumericNodePercentageOrNumber(r[4])||isCalcNode(r[4])||isVarNode(r[4]))&&(n.alpha=r[4]),n}function isRgb(e){return void 0!==e.r}function channelNodes(e){return isRgb(e)?[e.rNode,e.gNode,e.bNode]:[e.hNode,e.sNode,e.lNode]}function transformAlpha(r,n,t){if("hsl"===r.value.toLowerCase()||"hsla"===r.value.toLowerCase()?r.value="hsl":"rgb"!==r.value.toLowerCase()&&"rgba"!==r.value.toLowerCase()||(r.value="rgb"),!n||!t)return;if("hsl"===r.value.toLowerCase()?r.value="hsla":r.value="rgba",n.value=",",n.before="",!isNumericNode(t))return;const u=e.unit(t.value);u&&"%"===u.unit&&(u.number=String(parseFloat(u.number)/100),t.value=String(u.number))}function normalizeHueNode(e){switch(e.unit.toLowerCase()){case"deg":return void(e.unit="");case"rad":return e.unit="",void(e.number=Math.round(180*parseFloat(e.number)/Math.PI).toString());case"grad":return e.unit="",void(e.number=Math.round(.9*parseFloat(e.number)).toString());case"turn":return e.unit="",void(e.number=Math.round(360*parseFloat(e.number)).toString())}}function canParseAsUnit(r){if(!r||!r.value)return!1;try{return!1!==e.unit(r.value)}catch(e){return!1}}function convertOldSyntaxToNewSyntaxBeforeTransform(e){let r=0;for(let n=0;n2)return;r++}}return e}function needsConversion(e,r){let n=!1,t=!1,u=!1;const o=r.slice().filter((e=>"comment"!==e.type&&"space"!==e.type));for(let a=0;a{const n="preserve"in Object(r)&&Boolean(r.preserve);return{postcssPlugin:"postcss-color-functional-notation",Declaration:(r,{result:t,postcss:u})=>{if(hasSupportsAtRuleAncestor(r))return;const o=r.value,a=o.toLowerCase();if(!(a.includes("rgb")||a.includes("rgba")||a.includes("hsl")||a.includes("hsla")))return;let s;try{s=e(o)}catch(e){r.warn(t,`Failed to parse value '${o}' as a hsl or rgb function. Leaving the original value intact.`)}if(void 0===s)return;s.walk((e=>{if(!e.type||"function"!==e.type)return;const r=e.value.toLowerCase();"hsl"!==r&&"hsla"!==r&&"rgb"!==r&&"rgba"!==r||onCSSFunction(e)}));const i=String(s);if(i!==o)if(n&&r.variable){const e=r.parent,n="(color: rgb(0 0 0 / 0.5)) and (color: hsl(0 0% 0% / 0.5))",t=u.atRule({name:"supports",params:n,source:r.source}),o=e.clone();o.removeAll(),o.append(r.clone()),t.append(o);let a=e,s=e.next();for(;a&&s&&"atrule"===s.type&&"supports"===s.name&&s.params===n;)a=s,s=s.next();a.after(t),r.replaceWith(r.clone({value:i}))}else n?r.cloneBefore({value:i}):r.replaceWith(r.clone({value:i}))}}};postcssPlugin.postcss=!0;export{postcssPlugin as default}; +import e from"postcss-value-parser";function onCSSFunction(e){const r=e.value.toLowerCase();if(!needsConversion("rgb"===r||"rgba"===r,e.nodes))return;const n=convertOldSyntaxToNewSyntaxBeforeTransform(e.nodes).slice().filter((e=>"comment"!==e.type&&"space"!==e.type));let t=null;if("hsl"===r||"hsla"===r?t=hslFunctionContents(n):"rgb"!==r&&"rgba"!==r||(t=rgbFunctionContents(n)),!t)return;if(n.length>3&&(!t.slash||!t.alpha))return;transformAlpha(e,t.slash,t.alpha);const[o,u]=channelNodes(t);e.nodes.splice(e.nodes.indexOf(o)+1,0,{sourceIndex:0,sourceEndIndex:1,value:",",type:"div",before:"",after:""}),e.nodes.splice(e.nodes.indexOf(u)+1,0,{sourceIndex:0,sourceEndIndex:1,value:",",type:"div",before:"",after:""})}function isNumericNode(r){if(!r||"word"!==r.type)return!1;if(!canParseAsUnit(r))return!1;const n=e.unit(r.value);return!!n&&!!n.number}function isNumericNodeHueLike(r){if(!r||"word"!==r.type)return!1;if(!canParseAsUnit(r))return!1;const n=e.unit(r.value);if(!n)return!1;const t=n.unit.toLowerCase();return!!n.number&&("deg"===t||"grad"===t||"rad"===t||"turn"===t||""===n.unit)}function isNumericNodePercentageOrNumber(r){if(!r||"word"!==r.type)return!1;if(!canParseAsUnit(r))return!1;const n=e.unit(r.value);return!!n&&("%"===n.unit||""===n.unit)}function isCalcNode(e){return e&&"function"===e.type&&"calc"===e.value.toLowerCase()}function isVarNode(e){return e&&"function"===e.type&&"var"===e.value.toLowerCase()}function isSlashNode(e){return e&&"div"===e.type&&"/"===e.value}function hslFunctionContents(r){if(!isNumericNodeHueLike(r[0]))return null;if(!isNumericNodePercentageOrNumber(r[1]))return null;if(!isNumericNodePercentageOrNumber(r[2]))return null;const n={h:e.unit(r[0].value),hNode:r[0],s:e.unit(r[1].value),sNode:r[1],l:e.unit(r[2].value),lNode:r[2]};return normalizeHueNode(n.h),""!==n.h.unit?null:(n.hNode.value=n.h.number,isSlashNode(r[3])&&(n.slash=r[3]),(isNumericNodePercentageOrNumber(r[4])||isCalcNode(r[4])||isVarNode(r[4]))&&(n.alpha=r[4]),n)}function rgbFunctionContents(r){if(!isNumericNodePercentageOrNumber(r[0]))return null;if(!isNumericNodePercentageOrNumber(r[1]))return null;if(!isNumericNodePercentageOrNumber(r[2]))return null;const n={r:e.unit(r[0].value),rNode:r[0],g:e.unit(r[1].value),gNode:r[1],b:e.unit(r[2].value),bNode:r[2]};return"%"===n.r.unit&&(n.r.number=String(Math.floor(Number(n.r.number)/100*255)),n.rNode.value=n.r.number),"%"===n.g.unit&&(n.g.number=String(Math.floor(Number(n.g.number)/100*255)),n.gNode.value=n.g.number),"%"===n.b.unit&&(n.b.number=String(Math.floor(Number(n.b.number)/100*255)),n.bNode.value=n.b.number),isSlashNode(r[3])&&(n.slash=r[3]),(isNumericNodePercentageOrNumber(r[4])||isCalcNode(r[4])||isVarNode(r[4]))&&(n.alpha=r[4]),n}function isRgb(e){return void 0!==e.r}function channelNodes(e){return isRgb(e)?[e.rNode,e.gNode,e.bNode]:[e.hNode,e.sNode,e.lNode]}function transformAlpha(r,n,t){if("hsl"===r.value.toLowerCase()||"hsla"===r.value.toLowerCase()?r.value="hsl":"rgb"!==r.value.toLowerCase()&&"rgba"!==r.value.toLowerCase()||(r.value="rgb"),!n||!t)return;if("hsl"===r.value.toLowerCase()?r.value="hsla":r.value="rgba",n.value=",",n.before="",!isNumericNode(t))return;const o=e.unit(t.value);o&&"%"===o.unit&&(o.number=String(parseFloat(o.number)/100),t.value=String(o.number))}function normalizeHueNode(e){switch(e.unit.toLowerCase()){case"deg":return void(e.unit="");case"rad":return e.unit="",void(e.number=Math.round(180*parseFloat(e.number)/Math.PI).toString());case"grad":return e.unit="",void(e.number=Math.round(.9*parseFloat(e.number)).toString());case"turn":return e.unit="",void(e.number=Math.round(360*parseFloat(e.number)).toString())}}function canParseAsUnit(r){if(!r||!r.value)return!1;try{return!1!==e.unit(r.value)}catch(e){return!1}}function convertOldSyntaxToNewSyntaxBeforeTransform(e){let r=0;for(let n=0;n2)return;r++}}return e}function needsConversion(e,r){let n=!1,t=!1,o=!1;const u=r.slice().filter((e=>"comment"!==e.type&&"space"!==e.type));for(let a=0;a{const n="preserve"in Object(r)&&Boolean(r.preserve);return{postcssPlugin:"postcss-color-functional-notation",Declaration:(r,{result:t,postcss:o})=>{const u=r.value,a=u.toLowerCase();if(!(a.includes("rgb")||a.includes("rgba")||a.includes("hsl")||a.includes("hsla")))return;if(hasFallback(r))return;if(hasSupportsAtRuleAncestor(r))return;let s;try{s=e(u)}catch(e){r.warn(t,`Failed to parse value '${u}' as a hsl or rgb function. Leaving the original value intact.`)}if(void 0===s)return;s.walk((e=>{if(!e.type||"function"!==e.type)return;const r=e.value.toLowerCase();"hsl"!==r&&"hsla"!==r&&"rgb"!==r&&"rgba"!==r||onCSSFunction(e)}));const i=String(s);if(i!==u)if(n&&r.variable){const e=r.parent,n="(color: rgb(0 0 0 / 0.5)) and (color: hsl(0 0% 0% / 0.5))",t=o.atRule({name:"supports",params:n,source:r.source}),u=e.clone();u.removeAll(),u.append(r.clone()),t.append(u);let a=e,s=e.next();for(;a&&s&&"atrule"===s.type&&"supports"===s.name&&s.params===n;)a=s,s=s.next();a.after(t),r.replaceWith(r.clone({value:i}))}else n?r.cloneBefore({value:i}):r.replaceWith(r.clone({value:i}))}}};postcssPlugin.postcss=!0;export{postcssPlugin as default}; diff --git a/plugins/postcss-color-functional-notation/src/has-fallback-decl.ts b/plugins/postcss-color-functional-notation/src/has-fallback-decl.ts new file mode 100644 index 000000000..5fc4da9e0 --- /dev/null +++ b/plugins/postcss-color-functional-notation/src/has-fallback-decl.ts @@ -0,0 +1,20 @@ +import type { Declaration } from 'postcss'; + +export function hasFallback(node: Declaration): boolean { + const parent = node.parent; + if (!parent) { + return false; + } + + const nodeProp = node.prop.toLowerCase(); + + const currentNodeIndex = parent.index(node); + for (let i = 0; i < currentNodeIndex; i++) { + const precedingSibling = parent.nodes[i]; + if (precedingSibling.type === 'decl' && precedingSibling.prop.toLowerCase() === nodeProp) { + return true; + } + } + + return false; +} diff --git a/plugins/postcss-color-functional-notation/src/index.ts b/plugins/postcss-color-functional-notation/src/index.ts index c42f9e600..8505d1bee 100644 --- a/plugins/postcss-color-functional-notation/src/index.ts +++ b/plugins/postcss-color-functional-notation/src/index.ts @@ -5,6 +5,7 @@ import onCSSFunction from './on-css-function'; import type { PluginCreator } from 'postcss'; import { hasSupportsAtRuleAncestor } from './has-supports-at-rule-ancestor'; +import { hasFallback } from './has-fallback-decl'; /** postcss-color-functional-notation plugin options */ export type pluginOptions = { @@ -19,10 +20,6 @@ const postcssPlugin: PluginCreator = (opts?: pluginOptions) => { return { postcssPlugin: 'postcss-color-functional-notation', Declaration: (decl: Declaration, { result, postcss }: { result: Result, postcss: Postcss }) => { - if (hasSupportsAtRuleAncestor(decl)) { - return; - } - const originalValue = decl.value; const lowerCaseValue = originalValue.toLowerCase(); if ( @@ -34,6 +31,14 @@ const postcssPlugin: PluginCreator = (opts?: pluginOptions) => { return; } + if (hasFallback(decl)) { + return; + } + + if (hasSupportsAtRuleAncestor(decl)) { + return; + } + let valueAST: ParsedValue|undefined; try { diff --git a/plugins/postcss-color-functional-notation/test/basic.css b/plugins/postcss-color-functional-notation/test/basic.css index a235049ca..b11235a1f 100644 --- a/plugins/postcss-color-functional-notation/test/basic.css +++ b/plugins/postcss-color-functional-notation/test/basic.css @@ -1,97 +1,106 @@ .test-rgb { - color: rgb(178 34 34); - color: rgb(178 34 34 / 1); - color: rGB(178 34 34 / .5); - color: rgb(178 34 34 / 100%); - color: rgb(178 34 34 / 50%); - color: rgb(178 34 34 / var(--alpha-50)); - color: rgb(178 34 34 / calc(1 / 2)); + color-1: rgb(178 34 34); + color-2: rgb(178 34 34 / 1); + color-3: rGB(178 34 34 / .5); + color-4: rgb(178 34 34 / 100%); + color-5: rgb(178 34 34 / 50%); + color-6: rgb(178 34 34 / var(--alpha-50)); + color-7: rgb(178 34 34 / calc(1 / 2)); } .test-rgba { - color: rgba(178 34 34); - color: rgbA(178 34 34 / 1); - color: rgba(178 34 34 / .5); - color: rgba(178 34 34 / VaR(--alpha-50)); - color: rgba(178 34 34 / CaLC(1 / 2)); + color-1: rgba(178 34 34); + color-2: rgbA(178 34 34 / 1); + color-3: rgba(178 34 34 / .5); + color-4: rgba(178 34 34 / VaR(--alpha-50)); + color-5: rgba(178 34 34 / CaLC(1 / 2)); } .test-rgb-percentages { - color: rgba(70% 13.5% 13.5%); - color: rgba(70% 13.5% 13.5% / 100%); - color: rgba(70% 13.5% 13.5% / 50%); + color-1: rgba(70% 13.5% 13.5%); + color-2: rgba(70% 13.5% 13.5% / 100%); + color-3: rgba(70% 13.5% 13.5% / 50%); } .test-hsl { - color: hsl(0 0% 100%); - color: hsl(120deg 100% 50%); - color: hsl(120 100% 50%); - color: HSL(120 100% 50% / 1); - color: hsl(120 100% 50% / .5); - color: hsl(120 100% 50% / 100%); - color: hsl(120 100% 50% / 50%); - color: hslA(120deg 100% 50%); + color-1: hsl(0 0% 100%); + color-2: hsl(120deg 100% 50%); + color-3: hsl(120 100% 50%); + color-4: HSL(120 100% 50% / 1); + color-5: hsl(120 100% 50% / .5); + color-6: hsl(120 100% 50% / 100%); + color-7: hsl(120 100% 50% / 50%); + color-8: hslA(120deg 100% 50%); - color: hsl(0.5turn 100% 50%); - color: hsl(200grad 100% 50%); - color: hsl(3.14159rad 100% 50%); - color: hsl(3.14159rad 100% 50 / var(--alpha-50)); - color: hsl(3.14159rad 100% 50 / calc(1 / 2)); + color-9: hsl(0.5turn 100% 50%); + color-10: hsl(200grad 100% 50%); + color-11: hsl(3.14159rad 100% 50%); + color-12: hsl(3.14159rad 100% 50 / var(--alpha-50)); + color-13: hsl(3.14159rad 100% 50 / calc(1 / 2)); } .test-hsla { - color: hsla(120 100% 50%); - color: hsla(120 100% 50% / 1); - color: hsla(120 100% 50% / .5); - color: hsla(120 100% 50% / 100%); - color: hsla(120 100% 50% / 50%); - color: hsla(120 100% 50% / var(--alpha-50)); - color: hsla(120 100% 50% / calc(1 / 2)); + color-1: hsla(120 100% 50%); + color-2: hsla(120 100% 50% / 1); + color-3: hsla(120 100% 50% / .5); + color-4: hsla(120 100% 50% / 100%); + color-5: hsla(120 100% 50% / 50%); + color-6: hsla(120 100% 50% / var(--alpha-50)); + color-7: hsla(120 100% 50% / calc(1 / 2)); } .test-hwb { - color: hwb(0deg 0% 0%); - color: hwb(0 0% 0%); - color: hwb(0 0% 0% / 1); - color: hwb(0 0% 0% / .5); - color: hwb(0 0% 0% / 100%); - color: hwb(0 0% 0% / 50%); + color-1: hwb(0deg 0% 0%); + color-2: hwb(0 0% 0%); + color-3: hwb(0 0% 0% / 1); + color-4: hwb(0 0% 0% / .5); + color-5: hwb(0 0% 0% / 100%); + color-6: hwb(0 0% 0% / 50%); } .test-unparseable-color-function { - color: rgb(; ); + color-1: rgb(; ); } .rgb-with-alpha { - color: rgb(70% 13.5% 13.5% / 50%); - color: rgb(0, 0, 0, 0); + color-1: rgb(70% 13.5% 13.5% / 50%); + color-2: rgb(0, 0, 0, 0); } .hsl-with-alpha { - color: hsl(120 100% 50% / 50%); - color: hsl(120, 100%, 50%, 0); + color-1: hsl(120 100% 50% / 50%); + color-2: hsl(120, 100%, 50%, 0); } .test-legacy-notation-with-modern-components--rgb { - color: rgb(50%, 34, 34); - color: rgb(178, 10%, 34); - color: rgb(178, 34, 10%); + color-1: rgb(50%, 34, 34); + color-2: rgb(178, 10%, 34); + color-3: rgb(178, 34, 10%); } .test-legacy-notation-with-modern-components--rgba { - color: rgba(50%, 34, 34, 1); - color: rgba(178, 10%, 34, 1); - color: rgba(178, 34, 10%, 1); - color: rgba(178, 34, 34, 100%); + color-1: rgba(50%, 34, 34, 1); + color-2: rgba(178, 10%, 34, 1); + color-3: rgba(178, 34, 10%, 1); + color-4: rgba(178, 34, 34, 100%); } .test-legacy-notation-with-modern-components--hsla { - color: hsla(120, 100%, 50%, 100%); + color-1: hsla(120, 100%, 50%, 100%); } .test-ignore { /* this plugin shouldn't format */ - color: rgba(0,0,0,0.1); /* spaceless */ - color: rgba(0, 0, 0, 0.2); /* with spaces */ - color: rGB(178, 34, 34); /* legacy format, but with capitals */ + color-1: rgba(0,0,0,0.1); /* spaceless */ + color-2: rgba(0, 0, 0, 0.2); /* with spaces */ + color-3: rGB(178, 34, 34); /* legacy format, but with capitals */ +} + +.test-manual-fallback { + color: red; + color: rgb(255 0 0); +} + +to-clone { + color: rgb(255 0 0); } diff --git a/plugins/postcss-color-functional-notation/test/basic.expect.css b/plugins/postcss-color-functional-notation/test/basic.expect.css index 57492408d..5bcca0e30 100644 --- a/plugins/postcss-color-functional-notation/test/basic.expect.css +++ b/plugins/postcss-color-functional-notation/test/basic.expect.css @@ -1,97 +1,106 @@ .test-rgb { - color: rgb(178, 34, 34); - color: rgba(178, 34, 34, 1); - color: rgba(178, 34, 34, .5); - color: rgba(178, 34, 34, 1); - color: rgba(178, 34, 34, 0.5); - color: rgba(178, 34, 34, var(--alpha-50)); - color: rgba(178, 34, 34, calc(1 / 2)); + color-1: rgb(178, 34, 34); + color-2: rgba(178, 34, 34, 1); + color-3: rgba(178, 34, 34, .5); + color-4: rgba(178, 34, 34, 1); + color-5: rgba(178, 34, 34, 0.5); + color-6: rgba(178, 34, 34, var(--alpha-50)); + color-7: rgba(178, 34, 34, calc(1 / 2)); } .test-rgba { - color: rgb(178, 34, 34); - color: rgba(178, 34, 34, 1); - color: rgba(178, 34, 34, .5); - color: rgba(178, 34, 34, VaR(--alpha-50)); - color: rgba(178, 34, 34, CaLC(1 / 2)); + color-1: rgb(178, 34, 34); + color-2: rgba(178, 34, 34, 1); + color-3: rgba(178, 34, 34, .5); + color-4: rgba(178, 34, 34, VaR(--alpha-50)); + color-5: rgba(178, 34, 34, CaLC(1 / 2)); } .test-rgb-percentages { - color: rgb(178, 34, 34); - color: rgba(178, 34, 34, 1); - color: rgba(178, 34, 34, 0.5); + color-1: rgb(178, 34, 34); + color-2: rgba(178, 34, 34, 1); + color-3: rgba(178, 34, 34, 0.5); } .test-hsl { - color: hsl(0, 0%, 100%); - color: hsl(120, 100%, 50%); - color: hsl(120, 100%, 50%); - color: hsla(120, 100%, 50%, 1); - color: hsla(120, 100%, 50%, .5); - color: hsla(120, 100%, 50%, 1); - color: hsla(120, 100%, 50%, 0.5); - color: hsl(120, 100%, 50%); + color-1: hsl(0, 0%, 100%); + color-2: hsl(120, 100%, 50%); + color-3: hsl(120, 100%, 50%); + color-4: hsla(120, 100%, 50%, 1); + color-5: hsla(120, 100%, 50%, .5); + color-6: hsla(120, 100%, 50%, 1); + color-7: hsla(120, 100%, 50%, 0.5); + color-8: hsl(120, 100%, 50%); - color: hsl(180, 100%, 50%); - color: hsl(180, 100%, 50%); - color: hsl(180, 100%, 50%); - color: hsla(180, 100%, 50, var(--alpha-50)); - color: hsla(180, 100%, 50, calc(1 / 2)); + color-9: hsl(180, 100%, 50%); + color-10: hsl(180, 100%, 50%); + color-11: hsl(180, 100%, 50%); + color-12: hsla(180, 100%, 50, var(--alpha-50)); + color-13: hsla(180, 100%, 50, calc(1 / 2)); } .test-hsla { - color: hsl(120, 100%, 50%); - color: hsla(120, 100%, 50%, 1); - color: hsla(120, 100%, 50%, .5); - color: hsla(120, 100%, 50%, 1); - color: hsla(120, 100%, 50%, 0.5); - color: hsla(120, 100%, 50%, var(--alpha-50)); - color: hsla(120, 100%, 50%, calc(1 / 2)); + color-1: hsl(120, 100%, 50%); + color-2: hsla(120, 100%, 50%, 1); + color-3: hsla(120, 100%, 50%, .5); + color-4: hsla(120, 100%, 50%, 1); + color-5: hsla(120, 100%, 50%, 0.5); + color-6: hsla(120, 100%, 50%, var(--alpha-50)); + color-7: hsla(120, 100%, 50%, calc(1 / 2)); } .test-hwb { - color: hwb(0deg 0% 0%); - color: hwb(0 0% 0%); - color: hwb(0 0% 0% / 1); - color: hwb(0 0% 0% / .5); - color: hwb(0 0% 0% / 100%); - color: hwb(0 0% 0% / 50%); + color-1: hwb(0deg 0% 0%); + color-2: hwb(0 0% 0%); + color-3: hwb(0 0% 0% / 1); + color-4: hwb(0 0% 0% / .5); + color-5: hwb(0 0% 0% / 100%); + color-6: hwb(0 0% 0% / 50%); } .test-unparseable-color-function { - color: rgb(; ); + color-1: rgb(; ); } .rgb-with-alpha { - color: rgba(178, 34, 34, 0.5); - color: rgb(0, 0, 0, 0); + color-1: rgba(178, 34, 34, 0.5); + color-2: rgb(0, 0, 0, 0); } .hsl-with-alpha { - color: hsla(120, 100%, 50%, 0.5); - color: hsl(120, 100%, 50%, 0); + color-1: hsla(120, 100%, 50%, 0.5); + color-2: hsl(120, 100%, 50%, 0); } .test-legacy-notation-with-modern-components--rgb { - color: rgb(127, 34, 34); - color: rgb(178, 25, 34); - color: rgb(178, 34, 25); + color-1: rgb(127, 34, 34); + color-2: rgb(178, 25, 34); + color-3: rgb(178, 34, 25); } .test-legacy-notation-with-modern-components--rgba { - color: rgba(127, 34, 34, 1); - color: rgba(178, 25, 34, 1); - color: rgba(178, 34, 25, 1); - color: rgba(178, 34, 34, 1); + color-1: rgba(127, 34, 34, 1); + color-2: rgba(178, 25, 34, 1); + color-3: rgba(178, 34, 25, 1); + color-4: rgba(178, 34, 34, 1); } .test-legacy-notation-with-modern-components--hsla { - color: hsla(120, 100%, 50%, 1); + color-1: hsla(120, 100%, 50%, 1); } .test-ignore { /* this plugin shouldn't format */ - color: rgba(0,0,0,0.1); /* spaceless */ - color: rgba(0, 0, 0, 0.2); /* with spaces */ - color: rGB(178, 34, 34); /* legacy format, but with capitals */ + color-1: rgba(0,0,0,0.1); /* spaceless */ + color-2: rgba(0, 0, 0, 0.2); /* with spaces */ + color-3: rGB(178, 34, 34); /* legacy format, but with capitals */ +} + +.test-manual-fallback { + color: red; + color: rgb(255 0 0); +} + +to-clone { + color: rgb(255, 0, 0); } diff --git a/plugins/postcss-color-functional-notation/test/basic.preserve-true.expect.css b/plugins/postcss-color-functional-notation/test/basic.preserve-true.expect.css index 46d7f7a8a..1cd009654 100644 --- a/plugins/postcss-color-functional-notation/test/basic.preserve-true.expect.css +++ b/plugins/postcss-color-functional-notation/test/basic.preserve-true.expect.css @@ -1,143 +1,153 @@ .test-rgb { - color: rgb(178, 34, 34); - color: rgb(178 34 34); - color: rgba(178, 34, 34, 1); - color: rgb(178 34 34 / 1); - color: rgba(178, 34, 34, .5); - color: rGB(178 34 34 / .5); - color: rgba(178, 34, 34, 1); - color: rgb(178 34 34 / 100%); - color: rgba(178, 34, 34, 0.5); - color: rgb(178 34 34 / 50%); - color: rgba(178, 34, 34, var(--alpha-50)); - color: rgb(178 34 34 / var(--alpha-50)); - color: rgba(178, 34, 34, calc(1 / 2)); - color: rgb(178 34 34 / calc(1 / 2)); + color-1: rgb(178, 34, 34); + color-1: rgb(178 34 34); + color-2: rgba(178, 34, 34, 1); + color-2: rgb(178 34 34 / 1); + color-3: rgba(178, 34, 34, .5); + color-3: rGB(178 34 34 / .5); + color-4: rgba(178, 34, 34, 1); + color-4: rgb(178 34 34 / 100%); + color-5: rgba(178, 34, 34, 0.5); + color-5: rgb(178 34 34 / 50%); + color-6: rgba(178, 34, 34, var(--alpha-50)); + color-6: rgb(178 34 34 / var(--alpha-50)); + color-7: rgba(178, 34, 34, calc(1 / 2)); + color-7: rgb(178 34 34 / calc(1 / 2)); } .test-rgba { - color: rgb(178, 34, 34); - color: rgba(178 34 34); - color: rgba(178, 34, 34, 1); - color: rgbA(178 34 34 / 1); - color: rgba(178, 34, 34, .5); - color: rgba(178 34 34 / .5); - color: rgba(178, 34, 34, VaR(--alpha-50)); - color: rgba(178 34 34 / VaR(--alpha-50)); - color: rgba(178, 34, 34, CaLC(1 / 2)); - color: rgba(178 34 34 / CaLC(1 / 2)); + color-1: rgb(178, 34, 34); + color-1: rgba(178 34 34); + color-2: rgba(178, 34, 34, 1); + color-2: rgbA(178 34 34 / 1); + color-3: rgba(178, 34, 34, .5); + color-3: rgba(178 34 34 / .5); + color-4: rgba(178, 34, 34, VaR(--alpha-50)); + color-4: rgba(178 34 34 / VaR(--alpha-50)); + color-5: rgba(178, 34, 34, CaLC(1 / 2)); + color-5: rgba(178 34 34 / CaLC(1 / 2)); } .test-rgb-percentages { - color: rgb(178, 34, 34); - color: rgba(70% 13.5% 13.5%); - color: rgba(178, 34, 34, 1); - color: rgba(70% 13.5% 13.5% / 100%); - color: rgba(178, 34, 34, 0.5); - color: rgba(70% 13.5% 13.5% / 50%); + color-1: rgb(178, 34, 34); + color-1: rgba(70% 13.5% 13.5%); + color-2: rgba(178, 34, 34, 1); + color-2: rgba(70% 13.5% 13.5% / 100%); + color-3: rgba(178, 34, 34, 0.5); + color-3: rgba(70% 13.5% 13.5% / 50%); } .test-hsl { - color: hsl(0, 0%, 100%); - color: hsl(0 0% 100%); - color: hsl(120, 100%, 50%); - color: hsl(120deg 100% 50%); - color: hsl(120, 100%, 50%); - color: hsl(120 100% 50%); - color: hsla(120, 100%, 50%, 1); - color: HSL(120 100% 50% / 1); - color: hsla(120, 100%, 50%, .5); - color: hsl(120 100% 50% / .5); - color: hsla(120, 100%, 50%, 1); - color: hsl(120 100% 50% / 100%); - color: hsla(120, 100%, 50%, 0.5); - color: hsl(120 100% 50% / 50%); - color: hsl(120, 100%, 50%); - color: hslA(120deg 100% 50%); - - color: hsl(180, 100%, 50%); - - color: hsl(0.5turn 100% 50%); - color: hsl(180, 100%, 50%); - color: hsl(200grad 100% 50%); - color: hsl(180, 100%, 50%); - color: hsl(3.14159rad 100% 50%); - color: hsla(180, 100%, 50, var(--alpha-50)); - color: hsl(3.14159rad 100% 50 / var(--alpha-50)); - color: hsla(180, 100%, 50, calc(1 / 2)); - color: hsl(3.14159rad 100% 50 / calc(1 / 2)); + color-1: hsl(0, 0%, 100%); + color-1: hsl(0 0% 100%); + color-2: hsl(120, 100%, 50%); + color-2: hsl(120deg 100% 50%); + color-3: hsl(120, 100%, 50%); + color-3: hsl(120 100% 50%); + color-4: hsla(120, 100%, 50%, 1); + color-4: HSL(120 100% 50% / 1); + color-5: hsla(120, 100%, 50%, .5); + color-5: hsl(120 100% 50% / .5); + color-6: hsla(120, 100%, 50%, 1); + color-6: hsl(120 100% 50% / 100%); + color-7: hsla(120, 100%, 50%, 0.5); + color-7: hsl(120 100% 50% / 50%); + color-8: hsl(120, 100%, 50%); + color-8: hslA(120deg 100% 50%); + + color-9: hsl(180, 100%, 50%); + + color-9: hsl(0.5turn 100% 50%); + color-10: hsl(180, 100%, 50%); + color-10: hsl(200grad 100% 50%); + color-11: hsl(180, 100%, 50%); + color-11: hsl(3.14159rad 100% 50%); + color-12: hsla(180, 100%, 50, var(--alpha-50)); + color-12: hsl(3.14159rad 100% 50 / var(--alpha-50)); + color-13: hsla(180, 100%, 50, calc(1 / 2)); + color-13: hsl(3.14159rad 100% 50 / calc(1 / 2)); } .test-hsla { - color: hsl(120, 100%, 50%); - color: hsla(120 100% 50%); - color: hsla(120, 100%, 50%, 1); - color: hsla(120 100% 50% / 1); - color: hsla(120, 100%, 50%, .5); - color: hsla(120 100% 50% / .5); - color: hsla(120, 100%, 50%, 1); - color: hsla(120 100% 50% / 100%); - color: hsla(120, 100%, 50%, 0.5); - color: hsla(120 100% 50% / 50%); - color: hsla(120, 100%, 50%, var(--alpha-50)); - color: hsla(120 100% 50% / var(--alpha-50)); - color: hsla(120, 100%, 50%, calc(1 / 2)); - color: hsla(120 100% 50% / calc(1 / 2)); + color-1: hsl(120, 100%, 50%); + color-1: hsla(120 100% 50%); + color-2: hsla(120, 100%, 50%, 1); + color-2: hsla(120 100% 50% / 1); + color-3: hsla(120, 100%, 50%, .5); + color-3: hsla(120 100% 50% / .5); + color-4: hsla(120, 100%, 50%, 1); + color-4: hsla(120 100% 50% / 100%); + color-5: hsla(120, 100%, 50%, 0.5); + color-5: hsla(120 100% 50% / 50%); + color-6: hsla(120, 100%, 50%, var(--alpha-50)); + color-6: hsla(120 100% 50% / var(--alpha-50)); + color-7: hsla(120, 100%, 50%, calc(1 / 2)); + color-7: hsla(120 100% 50% / calc(1 / 2)); } .test-hwb { - color: hwb(0deg 0% 0%); - color: hwb(0 0% 0%); - color: hwb(0 0% 0% / 1); - color: hwb(0 0% 0% / .5); - color: hwb(0 0% 0% / 100%); - color: hwb(0 0% 0% / 50%); + color-1: hwb(0deg 0% 0%); + color-2: hwb(0 0% 0%); + color-3: hwb(0 0% 0% / 1); + color-4: hwb(0 0% 0% / .5); + color-5: hwb(0 0% 0% / 100%); + color-6: hwb(0 0% 0% / 50%); } .test-unparseable-color-function { - color: rgb(; ); + color-1: rgb(; ); } .rgb-with-alpha { - color: rgba(178, 34, 34, 0.5); - color: rgb(70% 13.5% 13.5% / 50%); - color: rgb(0, 0, 0, 0); + color-1: rgba(178, 34, 34, 0.5); + color-1: rgb(70% 13.5% 13.5% / 50%); + color-2: rgb(0, 0, 0, 0); } .hsl-with-alpha { - color: hsla(120, 100%, 50%, 0.5); - color: hsl(120 100% 50% / 50%); - color: hsl(120, 100%, 50%, 0); + color-1: hsla(120, 100%, 50%, 0.5); + color-1: hsl(120 100% 50% / 50%); + color-2: hsl(120, 100%, 50%, 0); } .test-legacy-notation-with-modern-components--rgb { - color: rgb(127, 34, 34); - color: rgb(50%, 34, 34); - color: rgb(178, 25, 34); - color: rgb(178, 10%, 34); - color: rgb(178, 34, 25); - color: rgb(178, 34, 10%); + color-1: rgb(127, 34, 34); + color-1: rgb(50%, 34, 34); + color-2: rgb(178, 25, 34); + color-2: rgb(178, 10%, 34); + color-3: rgb(178, 34, 25); + color-3: rgb(178, 34, 10%); } .test-legacy-notation-with-modern-components--rgba { - color: rgba(127, 34, 34, 1); - color: rgba(50%, 34, 34, 1); - color: rgba(178, 25, 34, 1); - color: rgba(178, 10%, 34, 1); - color: rgba(178, 34, 25, 1); - color: rgba(178, 34, 10%, 1); - color: rgba(178, 34, 34, 1); - color: rgba(178, 34, 34, 100%); + color-1: rgba(127, 34, 34, 1); + color-1: rgba(50%, 34, 34, 1); + color-2: rgba(178, 25, 34, 1); + color-2: rgba(178, 10%, 34, 1); + color-3: rgba(178, 34, 25, 1); + color-3: rgba(178, 34, 10%, 1); + color-4: rgba(178, 34, 34, 1); + color-4: rgba(178, 34, 34, 100%); } .test-legacy-notation-with-modern-components--hsla { - color: hsla(120, 100%, 50%, 1); - color: hsla(120, 100%, 50%, 100%); + color-1: hsla(120, 100%, 50%, 1); + color-1: hsla(120, 100%, 50%, 100%); } .test-ignore { /* this plugin shouldn't format */ - color: rgba(0,0,0,0.1); /* spaceless */ - color: rgba(0, 0, 0, 0.2); /* with spaces */ - color: rGB(178, 34, 34); /* legacy format, but with capitals */ + color-1: rgba(0,0,0,0.1); /* spaceless */ + color-2: rgba(0, 0, 0, 0.2); /* with spaces */ + color-3: rGB(178, 34, 34); /* legacy format, but with capitals */ +} + +.test-manual-fallback { + color: red; + color: rgb(255 0 0); +} + +to-clone { + color: rgb(255, 0, 0); + color: rgb(255 0 0); } diff --git a/plugins/postcss-color-functional-notation/test/basic.with-cloned-rules.expect.css b/plugins/postcss-color-functional-notation/test/basic.with-cloned-rules.expect.css new file mode 100644 index 000000000..8f090f68a --- /dev/null +++ b/plugins/postcss-color-functional-notation/test/basic.with-cloned-rules.expect.css @@ -0,0 +1,158 @@ +.test-rgb { + color-1: rgb(178, 34, 34); + color-1: rgb(178 34 34); + color-2: rgba(178, 34, 34, 1); + color-2: rgb(178 34 34 / 1); + color-3: rgba(178, 34, 34, .5); + color-3: rGB(178 34 34 / .5); + color-4: rgba(178, 34, 34, 1); + color-4: rgb(178 34 34 / 100%); + color-5: rgba(178, 34, 34, 0.5); + color-5: rgb(178 34 34 / 50%); + color-6: rgba(178, 34, 34, var(--alpha-50)); + color-6: rgb(178 34 34 / var(--alpha-50)); + color-7: rgba(178, 34, 34, calc(1 / 2)); + color-7: rgb(178 34 34 / calc(1 / 2)); +} + +.test-rgba { + color-1: rgb(178, 34, 34); + color-1: rgba(178 34 34); + color-2: rgba(178, 34, 34, 1); + color-2: rgbA(178 34 34 / 1); + color-3: rgba(178, 34, 34, .5); + color-3: rgba(178 34 34 / .5); + color-4: rgba(178, 34, 34, VaR(--alpha-50)); + color-4: rgba(178 34 34 / VaR(--alpha-50)); + color-5: rgba(178, 34, 34, CaLC(1 / 2)); + color-5: rgba(178 34 34 / CaLC(1 / 2)); +} + +.test-rgb-percentages { + color-1: rgb(178, 34, 34); + color-1: rgba(70% 13.5% 13.5%); + color-2: rgba(178, 34, 34, 1); + color-2: rgba(70% 13.5% 13.5% / 100%); + color-3: rgba(178, 34, 34, 0.5); + color-3: rgba(70% 13.5% 13.5% / 50%); +} + +.test-hsl { + color-1: hsl(0, 0%, 100%); + color-1: hsl(0 0% 100%); + color-2: hsl(120, 100%, 50%); + color-2: hsl(120deg 100% 50%); + color-3: hsl(120, 100%, 50%); + color-3: hsl(120 100% 50%); + color-4: hsla(120, 100%, 50%, 1); + color-4: HSL(120 100% 50% / 1); + color-5: hsla(120, 100%, 50%, .5); + color-5: hsl(120 100% 50% / .5); + color-6: hsla(120, 100%, 50%, 1); + color-6: hsl(120 100% 50% / 100%); + color-7: hsla(120, 100%, 50%, 0.5); + color-7: hsl(120 100% 50% / 50%); + color-8: hsl(120, 100%, 50%); + color-8: hslA(120deg 100% 50%); + + color-9: hsl(180, 100%, 50%); + + color-9: hsl(0.5turn 100% 50%); + color-10: hsl(180, 100%, 50%); + color-10: hsl(200grad 100% 50%); + color-11: hsl(180, 100%, 50%); + color-11: hsl(3.14159rad 100% 50%); + color-12: hsla(180, 100%, 50, var(--alpha-50)); + color-12: hsl(3.14159rad 100% 50 / var(--alpha-50)); + color-13: hsla(180, 100%, 50, calc(1 / 2)); + color-13: hsl(3.14159rad 100% 50 / calc(1 / 2)); +} + +.test-hsla { + color-1: hsl(120, 100%, 50%); + color-1: hsla(120 100% 50%); + color-2: hsla(120, 100%, 50%, 1); + color-2: hsla(120 100% 50% / 1); + color-3: hsla(120, 100%, 50%, .5); + color-3: hsla(120 100% 50% / .5); + color-4: hsla(120, 100%, 50%, 1); + color-4: hsla(120 100% 50% / 100%); + color-5: hsla(120, 100%, 50%, 0.5); + color-5: hsla(120 100% 50% / 50%); + color-6: hsla(120, 100%, 50%, var(--alpha-50)); + color-6: hsla(120 100% 50% / var(--alpha-50)); + color-7: hsla(120, 100%, 50%, calc(1 / 2)); + color-7: hsla(120 100% 50% / calc(1 / 2)); +} + +.test-hwb { + color-1: hwb(0deg 0% 0%); + color-2: hwb(0 0% 0%); + color-3: hwb(0 0% 0% / 1); + color-4: hwb(0 0% 0% / .5); + color-5: hwb(0 0% 0% / 100%); + color-6: hwb(0 0% 0% / 50%); +} + +.test-unparseable-color-function { + color-1: rgb(; ); +} + +.rgb-with-alpha { + color-1: rgba(178, 34, 34, 0.5); + color-1: rgb(70% 13.5% 13.5% / 50%); + color-2: rgb(0, 0, 0, 0); +} + +.hsl-with-alpha { + color-1: hsla(120, 100%, 50%, 0.5); + color-1: hsl(120 100% 50% / 50%); + color-2: hsl(120, 100%, 50%, 0); +} + +.test-legacy-notation-with-modern-components--rgb { + color-1: rgb(127, 34, 34); + color-1: rgb(50%, 34, 34); + color-2: rgb(178, 25, 34); + color-2: rgb(178, 10%, 34); + color-3: rgb(178, 34, 25); + color-3: rgb(178, 34, 10%); +} + +.test-legacy-notation-with-modern-components--rgba { + color-1: rgba(127, 34, 34, 1); + color-1: rgba(50%, 34, 34, 1); + color-2: rgba(178, 25, 34, 1); + color-2: rgba(178, 10%, 34, 1); + color-3: rgba(178, 34, 25, 1); + color-3: rgba(178, 34, 10%, 1); + color-4: rgba(178, 34, 34, 1); + color-4: rgba(178, 34, 34, 100%); +} + +.test-legacy-notation-with-modern-components--hsla { + color-1: hsla(120, 100%, 50%, 1); + color-1: hsla(120, 100%, 50%, 100%); +} + +.test-ignore { + /* this plugin shouldn't format */ + color-1: rgba(0,0,0,0.1); /* spaceless */ + color-2: rgba(0, 0, 0, 0.2); /* with spaces */ + color-3: rGB(178, 34, 34); /* legacy format, but with capitals */ +} + +.test-manual-fallback { + color: red; + color: rgb(255 0 0); +} + +cloned { + color: rgb(255, 0, 0); + color: rgb(255 0 0); +} + +to-clone { + color: rgb(255, 0, 0); + color: rgb(255 0 0); +} diff --git a/plugins/postcss-color-hex-alpha/.tape.mjs b/plugins/postcss-color-hex-alpha/.tape.mjs index 579050aed..bdf1e6cda 100644 --- a/plugins/postcss-color-hex-alpha/.tape.mjs +++ b/plugins/postcss-color-hex-alpha/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape, ruleClonerPlugin } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from 'postcss-color-hex-alpha'; postcssTape(plugin)({ @@ -11,6 +11,15 @@ postcssTape(plugin)({ preserve: true } }, + 'basic:with-cloned-rules': { + message: 'doesn\'t cause duplicate CSS', + plugins: [ + ruleClonerPlugin, + plugin({ + preserve: true + }) + ] + }, 'clip-path': { message: 'ignores clip-path with hash in url' }, diff --git a/plugins/postcss-color-hex-alpha/CHANGELOG.md b/plugins/postcss-color-hex-alpha/CHANGELOG.md index a5218393e..f7c519bc2 100644 --- a/plugins/postcss-color-hex-alpha/CHANGELOG.md +++ b/plugins/postcss-color-hex-alpha/CHANGELOG.md @@ -1,5 +1,9 @@ # Changes to PostCSS Color Hex Alpha +### Unreleased (patch) + +- Reduce the amount of duplicate fallback CSS. + ### 9.0.1 (January 28, 2023) - Improve `types` declaration in `package.json` diff --git a/plugins/postcss-color-hex-alpha/dist/has-fallback-decl.d.ts b/plugins/postcss-color-hex-alpha/dist/has-fallback-decl.d.ts new file mode 100644 index 000000000..d7ae8d890 --- /dev/null +++ b/plugins/postcss-color-hex-alpha/dist/has-fallback-decl.d.ts @@ -0,0 +1,2 @@ +import type { Declaration } from 'postcss'; +export declare function hasFallback(node: Declaration): boolean; diff --git a/plugins/postcss-color-hex-alpha/dist/index.cjs b/plugins/postcss-color-hex-alpha/dist/index.cjs index 9fdf974f2..a77b6ad30 100644 --- a/plugins/postcss-color-hex-alpha/dist/index.cjs +++ b/plugins/postcss-color-hex-alpha/dist/index.cjs @@ -1 +1 @@ -"use strict";var e=require("postcss-value-parser");const creator=s=>{const t=Object.assign({preserve:!1},s);return{postcssPlugin:"postcss-color-hex-alpha",Declaration(s){if(!/#([0-9A-Fa-f]{4}(?:[0-9A-Fa-f]{4})?)\b/.test(s.value))return;const{value:r}=s,a=e(r);a.walk((e=>{if("function"===e.type&&"url"===e.value)return!1;isAlphaHex(e)&&hexa2rgba(e)}));const l=a.toString();l!==r&&(s.cloneBefore({value:l}),t.preserve||s.remove())}}};function isAlphaHex(e){return"word"===e.type&&/^#([0-9A-Fa-f]{4}(?:[0-9A-Fa-f]{4})?)$/.test(e.value)}creator.postcss=!0;const s=1e5,hexa2rgba=e=>{const t=e.value,r=`0x${5===t.length?t.slice(1).replace(/[0-9A-Fa-f]/g,"$&$&"):t.slice(1)}`,[a,l,n,c]=[parseInt(r.slice(2,4),16),parseInt(r.slice(4,6),16),parseInt(r.slice(6,8),16),Math.round(parseInt(r.slice(8,10),16)/255*s)/s];e.value=`rgba(${a},${l},${n},${c})`};module.exports=creator; +"use strict";var e=require("postcss-value-parser");function hasFallback(e){const s=e.parent;if(!s)return!1;const t=e.prop.toLowerCase(),r=s.index(e);for(let e=0;e{const t=Object.assign({preserve:!1},s);return{postcssPlugin:"postcss-color-hex-alpha",Declaration(s){if(!/#([0-9A-Fa-f]{4}(?:[0-9A-Fa-f]{4})?)\b/.test(s.value))return;if(hasFallback(s))return;const{value:r}=s,a=e(r);a.walk((e=>{if("function"===e.type&&"url"===e.value)return!1;isAlphaHex(e)&&hexa2rgba(e)}));const n=a.toString();n!==r&&(s.cloneBefore({value:n}),t.preserve||s.remove())}}};function isAlphaHex(e){return"word"===e.type&&/^#([0-9A-Fa-f]{4}(?:[0-9A-Fa-f]{4})?)$/.test(e.value)}creator.postcss=!0;const s=1e5,hexa2rgba=e=>{const t=e.value,r=`0x${5===t.length?t.slice(1).replace(/[0-9A-Fa-f]/g,"$&$&"):t.slice(1)}`,[a,n,o,l]=[parseInt(r.slice(2,4),16),parseInt(r.slice(4,6),16),parseInt(r.slice(6,8),16),Math.round(parseInt(r.slice(8,10),16)/255*s)/s];e.value=`rgba(${a},${n},${o},${l})`};module.exports=creator; diff --git a/plugins/postcss-color-hex-alpha/dist/index.mjs b/plugins/postcss-color-hex-alpha/dist/index.mjs index 53cc5eeb7..bc8443339 100644 --- a/plugins/postcss-color-hex-alpha/dist/index.mjs +++ b/plugins/postcss-color-hex-alpha/dist/index.mjs @@ -1 +1 @@ -import e from"postcss-value-parser";const creator=s=>{const t=Object.assign({preserve:!1},s);return{postcssPlugin:"postcss-color-hex-alpha",Declaration(s){if(!/#([0-9A-Fa-f]{4}(?:[0-9A-Fa-f]{4})?)\b/.test(s.value))return;const{value:a}=s,r=e(a);r.walk((e=>{if("function"===e.type&&"url"===e.value)return!1;isAlphaHex(e)&&hexa2rgba(e)}));const l=r.toString();l!==a&&(s.cloneBefore({value:l}),t.preserve||s.remove())}}};function isAlphaHex(e){return"word"===e.type&&/^#([0-9A-Fa-f]{4}(?:[0-9A-Fa-f]{4})?)$/.test(e.value)}creator.postcss=!0;const s=1e5,hexa2rgba=e=>{const t=e.value,a=`0x${5===t.length?t.slice(1).replace(/[0-9A-Fa-f]/g,"$&$&"):t.slice(1)}`,[r,l,n,o]=[parseInt(a.slice(2,4),16),parseInt(a.slice(4,6),16),parseInt(a.slice(6,8),16),Math.round(parseInt(a.slice(8,10),16)/255*s)/s];e.value=`rgba(${r},${l},${n},${o})`};export{creator as default}; +import e from"postcss-value-parser";function hasFallback(e){const t=e.parent;if(!t)return!1;const s=e.prop.toLowerCase(),r=t.index(e);for(let e=0;e{const s=Object.assign({preserve:!1},t);return{postcssPlugin:"postcss-color-hex-alpha",Declaration(t){if(!/#([0-9A-Fa-f]{4}(?:[0-9A-Fa-f]{4})?)\b/.test(t.value))return;if(hasFallback(t))return;const{value:r}=t,a=e(r);a.walk((e=>{if("function"===e.type&&"url"===e.value)return!1;isAlphaHex(e)&&hexa2rgba(e)}));const n=a.toString();n!==r&&(t.cloneBefore({value:n}),s.preserve||t.remove())}}};function isAlphaHex(e){return"word"===e.type&&/^#([0-9A-Fa-f]{4}(?:[0-9A-Fa-f]{4})?)$/.test(e.value)}creator.postcss=!0;const t=1e5,hexa2rgba=e=>{const s=e.value,r=`0x${5===s.length?s.slice(1).replace(/[0-9A-Fa-f]/g,"$&$&"):s.slice(1)}`,[a,n,o,l]=[parseInt(r.slice(2,4),16),parseInt(r.slice(4,6),16),parseInt(r.slice(6,8),16),Math.round(parseInt(r.slice(8,10),16)/255*t)/t];e.value=`rgba(${a},${n},${o},${l})`};export{creator as default}; diff --git a/plugins/postcss-color-hex-alpha/src/has-fallback-decl.ts b/plugins/postcss-color-hex-alpha/src/has-fallback-decl.ts new file mode 100644 index 000000000..5fc4da9e0 --- /dev/null +++ b/plugins/postcss-color-hex-alpha/src/has-fallback-decl.ts @@ -0,0 +1,20 @@ +import type { Declaration } from 'postcss'; + +export function hasFallback(node: Declaration): boolean { + const parent = node.parent; + if (!parent) { + return false; + } + + const nodeProp = node.prop.toLowerCase(); + + const currentNodeIndex = parent.index(node); + for (let i = 0; i < currentNodeIndex; i++) { + const precedingSibling = parent.nodes[i]; + if (precedingSibling.type === 'decl' && precedingSibling.prop.toLowerCase() === nodeProp) { + return true; + } + } + + return false; +} diff --git a/plugins/postcss-color-hex-alpha/src/index.ts b/plugins/postcss-color-hex-alpha/src/index.ts index 089cd671c..508732a0a 100644 --- a/plugins/postcss-color-hex-alpha/src/index.ts +++ b/plugins/postcss-color-hex-alpha/src/index.ts @@ -1,5 +1,6 @@ import type { PluginCreator } from 'postcss'; import valuesParser from 'postcss-value-parser'; +import { hasFallback } from './has-fallback-decl'; /** postcss-color-hex-alpha plugin options */ export type pluginOptions = { @@ -24,6 +25,10 @@ const creator: PluginCreator = (opts?: pluginOptions) => { return; } + if (hasFallback(decl)) { + return; + } + const { value: originalValue } = decl; // replace instances of hexa with rgba() diff --git a/plugins/postcss-color-hex-alpha/test/basic.css b/plugins/postcss-color-hex-alpha/test/basic.css index 0233e08be..d7f0442b6 100644 --- a/plugins/postcss-color-hex-alpha/test/basic.css +++ b/plugins/postcss-color-hex-alpha/test/basic.css @@ -14,3 +14,12 @@ body { background-color: #f3f3f3f3; color: #0003; } + +.test-manual-fallback { + color: purple; + color: #9823f8a9; +} + +to-clone { + color: #9823f8a9; +} diff --git a/plugins/postcss-color-hex-alpha/test/basic.expect.css b/plugins/postcss-color-hex-alpha/test/basic.expect.css index 70c5b8aa5..9e3d309b6 100644 --- a/plugins/postcss-color-hex-alpha/test/basic.expect.css +++ b/plugins/postcss-color-hex-alpha/test/basic.expect.css @@ -2,10 +2,10 @@ body { background: #9d9 linear-gradient(rgba(152,35,248,0.66275), rgba(152,35,248,0.20392)); color: red; color: #f00; - color: rgba(255,0,0,1); - color: rgba(255,204,0,1); + color: #f00f; + color: #FC0F; color: #0000ff; - color: rgba(0,0,255,0); + color: #0000ff00; content: "#f00"; content: "#0000ff00"; } @@ -14,3 +14,12 @@ body { background-color: rgba(243,243,243,0.95294); color: rgba(0,0,0,0.2); } + +.test-manual-fallback { + color: purple; + color: #9823f8a9; +} + +to-clone { + color: rgba(152,35,248,0.66275); +} diff --git a/plugins/postcss-color-hex-alpha/test/basic.preserve.expect.css b/plugins/postcss-color-hex-alpha/test/basic.preserve.expect.css index e4ddad230..2d7de5dbd 100644 --- a/plugins/postcss-color-hex-alpha/test/basic.preserve.expect.css +++ b/plugins/postcss-color-hex-alpha/test/basic.preserve.expect.css @@ -3,12 +3,9 @@ body { background: #9d9 linear-gradient(#9823f8a9, #9823f834); color: red; color: #f00; - color: rgba(255,0,0,1); color: #f00f; - color: rgba(255,204,0,1); color: #FC0F; color: #0000ff; - color: rgba(0,0,255,0); color: #0000ff00; content: "#f00"; content: "#0000ff00"; @@ -20,3 +17,13 @@ body { color: rgba(0,0,0,0.2); color: #0003; } + +.test-manual-fallback { + color: purple; + color: #9823f8a9; +} + +to-clone { + color: rgba(152,35,248,0.66275); + color: #9823f8a9; +} diff --git a/plugins/postcss-color-hex-alpha/test/basic.with-cloned-rules.expect.css b/plugins/postcss-color-hex-alpha/test/basic.with-cloned-rules.expect.css new file mode 100644 index 000000000..dbb6bad42 --- /dev/null +++ b/plugins/postcss-color-hex-alpha/test/basic.with-cloned-rules.expect.css @@ -0,0 +1,34 @@ +body { + background: #9d9 linear-gradient(rgba(152,35,248,0.66275), rgba(152,35,248,0.20392)); + background: #9d9 linear-gradient(#9823f8a9, #9823f834); + color: red; + color: #f00; + color: #f00f; + color: #FC0F; + color: #0000ff; + color: #0000ff00; + content: "#f00"; + content: "#0000ff00"; +} + +body { + background-color: rgba(243,243,243,0.95294); + background-color: #f3f3f3f3; + color: rgba(0,0,0,0.2); + color: #0003; +} + +.test-manual-fallback { + color: purple; + color: #9823f8a9; +} + +cloned { + color: rgba(152,35,248,0.66275); + color: #9823f8a9; +} + +to-clone { + color: rgba(152,35,248,0.66275); + color: #9823f8a9; +} diff --git a/plugins/postcss-color-rebeccapurple/.tape.mjs b/plugins/postcss-color-rebeccapurple/.tape.mjs index 6609968a5..952add1c2 100644 --- a/plugins/postcss-color-rebeccapurple/.tape.mjs +++ b/plugins/postcss-color-rebeccapurple/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape, ruleClonerPlugin } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from 'postcss-color-rebeccapurple'; postcssTape(plugin)({ @@ -11,6 +11,15 @@ postcssTape(plugin)({ preserve: true } }, + 'basic:with-cloned-rules': { + message: 'doesn\'t cause duplicate CSS', + plugins: [ + ruleClonerPlugin, + plugin({ + preserve: true + }) + ] + }, 'generated-value-cases': { message: "supports generated test cases", }, diff --git a/plugins/postcss-color-rebeccapurple/CHANGELOG.md b/plugins/postcss-color-rebeccapurple/CHANGELOG.md index de71624d1..054d3ec5a 100644 --- a/plugins/postcss-color-rebeccapurple/CHANGELOG.md +++ b/plugins/postcss-color-rebeccapurple/CHANGELOG.md @@ -1,5 +1,9 @@ # Changes to PostCSS RebeccaPurple +### Unreleased (patch) + +- Reduce the amount of duplicate fallback CSS. + ### 8.0.1 (January 28, 2023) - Improve `types` declaration in `package.json` diff --git a/plugins/postcss-color-rebeccapurple/dist/has-fallback-decl.d.ts b/plugins/postcss-color-rebeccapurple/dist/has-fallback-decl.d.ts new file mode 100644 index 000000000..d7ae8d890 --- /dev/null +++ b/plugins/postcss-color-rebeccapurple/dist/has-fallback-decl.d.ts @@ -0,0 +1,2 @@ +import type { Declaration } from 'postcss'; +export declare function hasFallback(node: Declaration): boolean; diff --git a/plugins/postcss-color-rebeccapurple/dist/index.cjs b/plugins/postcss-color-rebeccapurple/dist/index.cjs index 1e104fe8f..40b1f886b 100644 --- a/plugins/postcss-color-rebeccapurple/dist/index.cjs +++ b/plugins/postcss-color-rebeccapurple/dist/index.cjs @@ -1 +1 @@ -"use strict";var e=require("postcss-value-parser");const creator=r=>{const s=Object.assign({preserve:!1},r);return{postcssPlugin:"postcss-color-rebeccapurple",Declaration(r){if(!r.value.toLowerCase().includes("rebeccapurple"))return;const o=e(r.value);o.walk((e=>{"word"===e.type&&"rebeccapurple"===e.value.toLowerCase()&&(e.value="#639")}));const c=String(o);c!==r.value&&(r.cloneBefore({value:c}),s.preserve||r.remove())}}};creator.postcss=!0,module.exports=creator; +"use strict";var e=require("postcss-value-parser");function hasFallback(e){const r=e.parent;if(!r)return!1;const s=e.prop.toLowerCase(),o=r.index(e);for(let e=0;e{const s=Object.assign({preserve:!1},r);return{postcssPlugin:"postcss-color-rebeccapurple",Declaration(r){if(!r.value.toLowerCase().includes("rebeccapurple"))return;if(hasFallback(r))return;const o=e(r.value);o.walk((e=>{"word"===e.type&&"rebeccapurple"===e.value.toLowerCase()&&(e.value="#639")}));const t=String(o);t!==r.value&&(r.cloneBefore({value:t}),s.preserve||r.remove())}}};creator.postcss=!0,module.exports=creator; diff --git a/plugins/postcss-color-rebeccapurple/dist/index.mjs b/plugins/postcss-color-rebeccapurple/dist/index.mjs index 093764044..88cf945fb 100644 --- a/plugins/postcss-color-rebeccapurple/dist/index.mjs +++ b/plugins/postcss-color-rebeccapurple/dist/index.mjs @@ -1 +1 @@ -import e from"postcss-value-parser";const creator=r=>{const s=Object.assign({preserve:!1},r);return{postcssPlugin:"postcss-color-rebeccapurple",Declaration(r){if(!r.value.toLowerCase().includes("rebeccapurple"))return;const o=e(r.value);o.walk((e=>{"word"===e.type&&"rebeccapurple"===e.value.toLowerCase()&&(e.value="#639")}));const a=String(o);a!==r.value&&(r.cloneBefore({value:a}),s.preserve||r.remove())}}};creator.postcss=!0;export{creator as default}; +import e from"postcss-value-parser";function hasFallback(e){const r=e.parent;if(!r)return!1;const o=e.prop.toLowerCase(),s=r.index(e);for(let e=0;e{const o=Object.assign({preserve:!1},r);return{postcssPlugin:"postcss-color-rebeccapurple",Declaration(r){if(!r.value.toLowerCase().includes("rebeccapurple"))return;if(hasFallback(r))return;const s=e(r.value);s.walk((e=>{"word"===e.type&&"rebeccapurple"===e.value.toLowerCase()&&(e.value="#639")}));const t=String(s);t!==r.value&&(r.cloneBefore({value:t}),o.preserve||r.remove())}}};creator.postcss=!0;export{creator as default}; diff --git a/plugins/postcss-color-rebeccapurple/src/has-fallback-decl.ts b/plugins/postcss-color-rebeccapurple/src/has-fallback-decl.ts new file mode 100644 index 000000000..5fc4da9e0 --- /dev/null +++ b/plugins/postcss-color-rebeccapurple/src/has-fallback-decl.ts @@ -0,0 +1,20 @@ +import type { Declaration } from 'postcss'; + +export function hasFallback(node: Declaration): boolean { + const parent = node.parent; + if (!parent) { + return false; + } + + const nodeProp = node.prop.toLowerCase(); + + const currentNodeIndex = parent.index(node); + for (let i = 0; i < currentNodeIndex; i++) { + const precedingSibling = parent.nodes[i]; + if (precedingSibling.type === 'decl' && precedingSibling.prop.toLowerCase() === nodeProp) { + return true; + } + } + + return false; +} diff --git a/plugins/postcss-color-rebeccapurple/src/index.ts b/plugins/postcss-color-rebeccapurple/src/index.ts index 5e2f37a72..61e330460 100644 --- a/plugins/postcss-color-rebeccapurple/src/index.ts +++ b/plugins/postcss-color-rebeccapurple/src/index.ts @@ -1,5 +1,6 @@ import type { PluginCreator } from 'postcss'; import valuesParser from 'postcss-value-parser'; +import { hasFallback } from './has-fallback-decl'; /** postcss-color-rebeccapurple plugin options */ export type pluginOptions = { @@ -24,6 +25,10 @@ const creator: PluginCreator = (opts?: pluginOptions) => { return; } + if (hasFallback(decl)) { + return; + } + const valueAST = valuesParser(decl.value); valueAST.walk(node => { if (node.type === 'word' && node.value.toLowerCase() === 'rebeccapurple') { diff --git a/plugins/postcss-color-rebeccapurple/test/basic.css b/plugins/postcss-color-rebeccapurple/test/basic.css index 74d047bee..bec8ea94c 100644 --- a/plugins/postcss-color-rebeccapurple/test/basic.css +++ b/plugins/postcss-color-rebeccapurple/test/basic.css @@ -6,3 +6,12 @@ body { footer { color: RebeccaPurple; } + +.test-manual-fallback { + color: purple; + color: rebeccapurple; +} + +to-clone { + color: rebeccapurple; +} diff --git a/plugins/postcss-color-rebeccapurple/test/basic.expect.css b/plugins/postcss-color-rebeccapurple/test/basic.expect.css index 1485fd64d..abcab496d 100644 --- a/plugins/postcss-color-rebeccapurple/test/basic.expect.css +++ b/plugins/postcss-color-rebeccapurple/test/basic.expect.css @@ -6,3 +6,12 @@ body { footer { color: #639; } + +.test-manual-fallback { + color: purple; + color: rebeccapurple; +} + +to-clone { + color: #639; +} diff --git a/plugins/postcss-color-rebeccapurple/test/basic.preserve-true.expect.css b/plugins/postcss-color-rebeccapurple/test/basic.preserve-true.expect.css index 35f6df5e8..f9c9eef95 100644 --- a/plugins/postcss-color-rebeccapurple/test/basic.preserve-true.expect.css +++ b/plugins/postcss-color-rebeccapurple/test/basic.preserve-true.expect.css @@ -9,3 +9,13 @@ footer { color: #639; color: RebeccaPurple; } + +.test-manual-fallback { + color: purple; + color: rebeccapurple; +} + +to-clone { + color: #639; + color: rebeccapurple; +} diff --git a/plugins/postcss-color-rebeccapurple/test/basic.with-cloned-rules.expect.css b/plugins/postcss-color-rebeccapurple/test/basic.with-cloned-rules.expect.css new file mode 100644 index 000000000..afeaad40d --- /dev/null +++ b/plugins/postcss-color-rebeccapurple/test/basic.with-cloned-rules.expect.css @@ -0,0 +1,26 @@ +body { + color: #639; + color: rebeccapurple; + background: linear-gradient(#639, blue 50%, #639); + background: linear-gradient(rebeccapurple, blue 50%, rebeccapurple); +} + +footer { + color: #639; + color: RebeccaPurple; +} + +.test-manual-fallback { + color: purple; + color: rebeccapurple; +} + +cloned { + color: #639; + color: rebeccapurple; +} + +to-clone { + color: #639; + color: rebeccapurple; +} diff --git a/plugins/postcss-conditional-values/.tape.mjs b/plugins/postcss-conditional-values/.tape.mjs index fde8d952e..4d48cf945 100644 --- a/plugins/postcss-conditional-values/.tape.mjs +++ b/plugins/postcss-conditional-values/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from '@csstools/postcss-conditional-values'; postcssTape(plugin)({ diff --git a/plugins/postcss-custom-media/.tape.mjs b/plugins/postcss-custom-media/.tape.mjs index 8d03ecc6b..bcec29bd8 100644 --- a/plugins/postcss-custom-media/.tape.mjs +++ b/plugins/postcss-custom-media/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.cjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.cjs'; import plugin from 'postcss-custom-media'; postcssTape(plugin)({ diff --git a/plugins/postcss-custom-properties/.tape.mjs b/plugins/postcss-custom-properties/.tape.mjs index df0f586a3..f01460aed 100644 --- a/plugins/postcss-custom-properties/.tape.mjs +++ b/plugins/postcss-custom-properties/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from 'postcss-custom-properties'; import postcssImport from 'postcss-import'; diff --git a/plugins/postcss-custom-selectors/.tape.mjs b/plugins/postcss-custom-selectors/.tape.mjs index c71256076..2eb662574 100644 --- a/plugins/postcss-custom-selectors/.tape.mjs +++ b/plugins/postcss-custom-selectors/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from 'postcss-custom-selectors'; postcssTape(plugin)({ diff --git a/plugins/postcss-design-tokens/.tape.mjs b/plugins/postcss-design-tokens/.tape.mjs index 4ab9117a5..17c1834e6 100644 --- a/plugins/postcss-design-tokens/.tape.mjs +++ b/plugins/postcss-design-tokens/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from '@csstools/postcss-design-tokens'; import postcssImport from 'postcss-import'; diff --git a/plugins/postcss-dir-pseudo-class/.tape.mjs b/plugins/postcss-dir-pseudo-class/.tape.mjs index a8661b320..76117a3f1 100644 --- a/plugins/postcss-dir-pseudo-class/.tape.mjs +++ b/plugins/postcss-dir-pseudo-class/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { declarationClonerPlugin, postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from 'postcss-dir-pseudo-class'; postcssTape(plugin)({ @@ -30,6 +30,16 @@ postcssTape(plugin)({ shadow: true }, }, + 'basic:with-cloned-declarations': { + message: 'doesn\'t cause duplicate CSS', + warnings: 2, + plugins: [ + declarationClonerPlugin, + plugin({ + preserve: true + }) + ] + }, 'generated-selector-cases': { message: 'correctly handles generated cases', warnings: 38, diff --git a/plugins/postcss-dir-pseudo-class/CHANGELOG.md b/plugins/postcss-dir-pseudo-class/CHANGELOG.md index 0f9c7db7b..deebf94b9 100644 --- a/plugins/postcss-dir-pseudo-class/CHANGELOG.md +++ b/plugins/postcss-dir-pseudo-class/CHANGELOG.md @@ -1,5 +1,9 @@ # Changes to PostCSS Dir Pseudo Class +### Unreleased (patch) + +- Reduce the amount of duplicate fallback CSS. + ### 7.0.1 (January 28, 2023) - Improve `types` declaration in `package.json` diff --git a/plugins/postcss-dir-pseudo-class/dist/index.cjs b/plugins/postcss-dir-pseudo-class/dist/index.cjs index 72bfb28fd..f91c7465f 100644 --- a/plugins/postcss-dir-pseudo-class/dist/index.cjs +++ b/plugins/postcss-dir-pseudo-class/dist/index.cjs @@ -1 +1 @@ -"use strict";var e=require("postcss-selector-parser");const creator=t=>{const r=Object.assign({dir:null,preserve:!1,shadow:!1},t);return{postcssPlugin:"postcss-dir-pseudo-class",Rule(t,{result:o}){let s,a=!1;if(t.selector.toLowerCase().includes(":dir(")){try{s=e((s=>{s.nodes.forEach((s=>{s.walk((s=>{if("pseudo"!==s.type)return;if(":dir"!==s.value.toLowerCase())return;if(!s.nodes||!s.nodes.length)return;const n=s.nodes.toString().toLowerCase();if("rtl"!==n&&"ltr"!==n)return;const l=s.parent;l.nodes.filter((e=>"pseudo"===e.type&&":dir"===e.value.toLowerCase())).length>1&&!a&&(a=!0,t.warn(o,`Hierarchical :dir pseudo class usage can't be transformed correctly to [dir] attributes. This will lead to incorrect selectors for "${t.selector}"`));const c=s.prev(),i=s.next(),p=c&&c.type&&"combinator"!==c.type,u=i&&i.type&&"combinator"!==i.type,d=i&&i.type&&("combinator"!==i.type||"combinator"===i.type&&" "===i.value);p||u||0===l.nodes.indexOf(s)&&d||1===l.nodes.length?s.remove():s.replaceWith(e.universal());const v=l.nodes[0],y=v&&"combinator"===v.type&&" "===v.value,w=v&&"tag"===v.type&&"html"===v.value.toLowerCase(),b=v&&"pseudo"===v.type&&":root"===v.value.toLowerCase();!v||w||b||y||l.prepend(e.combinator({value:" "}));const f=r.dir===n,h=e.attribute({attribute:"dir",operator:"=",quoteMark:'"',value:`"${n}"`,raws:null}),m=e.pseudo({value:":host-context"});m.append(h);const g=e.pseudo({value:(w||b?"":"html")+":not"});g.append(e.attribute({attribute:"dir",operator:"=",quoteMark:'"',value:`"${"ltr"===n?"rtl":"ltr"}"`,raws:null})),f?w?l.insertAfter(v,g):l.prepend(g):w?l.insertAfter(v,h):r.shadow&&!b?l.prepend(m):l.prepend(h)}))}))})).processSync(t.selector)}catch(e){return void t.warn(o,`Failed to parse selector : ${t.selector}`)}void 0!==s&&s!==t.selector&&(t.cloneBefore({selector:s}),r.preserve||t.remove())}}}};creator.postcss=!0,module.exports=creator; +"use strict";var e=require("postcss-selector-parser");const creator=t=>{const r=Object.assign({dir:null,preserve:!1,shadow:!1},t);return{postcssPlugin:"postcss-dir-pseudo-class",prepare(){const t=new WeakSet;return{Rule(o,{result:s}){if(t.has(o))return;let a,n=!1;if(o.selector.toLowerCase().includes(":dir(")){try{a=e((t=>{t.nodes.forEach((t=>{t.walk((t=>{if("pseudo"!==t.type)return;if(":dir"!==t.value.toLowerCase())return;if(!t.nodes||!t.nodes.length)return;const a=t.nodes.toString().toLowerCase();if("rtl"!==a&&"ltr"!==a)return;const l=t.parent;l.nodes.filter((e=>"pseudo"===e.type&&":dir"===e.value.toLowerCase())).length>1&&!n&&(n=!0,o.warn(s,`Hierarchical :dir pseudo class usage can't be transformed correctly to [dir] attributes. This will lead to incorrect selectors for "${o.selector}"`));const c=t.prev(),p=t.next(),i=c&&c.type&&"combinator"!==c.type,u=p&&p.type&&"combinator"!==p.type,d=p&&p.type&&("combinator"!==p.type||"combinator"===p.type&&" "===p.value);i||u||0===l.nodes.indexOf(t)&&d||1===l.nodes.length?t.remove():t.replaceWith(e.universal());const v=l.nodes[0],w=v&&"combinator"===v.type&&" "===v.value,y=v&&"tag"===v.type&&"html"===v.value.toLowerCase(),f=v&&"pseudo"===v.type&&":root"===v.value.toLowerCase();!v||y||f||w||l.prepend(e.combinator({value:" "}));const h=r.dir===a,b=e.attribute({attribute:"dir",operator:"=",quoteMark:'"',value:`"${a}"`,raws:null}),m=e.pseudo({value:":host-context"});m.append(b);const g=e.pseudo({value:(y||f?"":"html")+":not"});g.append(e.attribute({attribute:"dir",operator:"=",quoteMark:'"',value:`"${"ltr"===a?"rtl":"ltr"}"`,raws:null})),h?y?l.insertAfter(v,g):l.prepend(g):y?l.insertAfter(v,b):r.shadow&&!f?l.prepend(m):l.prepend(b)}))}))})).processSync(o.selector)}catch(e){return void o.warn(s,`Failed to parse selector : ${o.selector}`)}void 0!==a&&a!==o.selector&&(t.add(o),o.cloneBefore({selector:a}),r.preserve||o.remove())}}}}}};creator.postcss=!0,module.exports=creator; diff --git a/plugins/postcss-dir-pseudo-class/dist/index.mjs b/plugins/postcss-dir-pseudo-class/dist/index.mjs index d03c686ea..66e7dabeb 100644 --- a/plugins/postcss-dir-pseudo-class/dist/index.mjs +++ b/plugins/postcss-dir-pseudo-class/dist/index.mjs @@ -1 +1 @@ -import e from"postcss-selector-parser";const creator=t=>{const r=Object.assign({dir:null,preserve:!1,shadow:!1},t);return{postcssPlugin:"postcss-dir-pseudo-class",Rule(t,{result:o}){let s,a=!1;if(t.selector.toLowerCase().includes(":dir(")){try{s=e((s=>{s.nodes.forEach((s=>{s.walk((s=>{if("pseudo"!==s.type)return;if(":dir"!==s.value.toLowerCase())return;if(!s.nodes||!s.nodes.length)return;const n=s.nodes.toString().toLowerCase();if("rtl"!==n&&"ltr"!==n)return;const l=s.parent;l.nodes.filter((e=>"pseudo"===e.type&&":dir"===e.value.toLowerCase())).length>1&&!a&&(a=!0,t.warn(o,`Hierarchical :dir pseudo class usage can't be transformed correctly to [dir] attributes. This will lead to incorrect selectors for "${t.selector}"`));const p=s.prev(),c=s.next(),i=p&&p.type&&"combinator"!==p.type,d=c&&c.type&&"combinator"!==c.type,u=c&&c.type&&("combinator"!==c.type||"combinator"===c.type&&" "===c.value);i||d||0===l.nodes.indexOf(s)&&u||1===l.nodes.length?s.remove():s.replaceWith(e.universal());const v=l.nodes[0],f=v&&"combinator"===v.type&&" "===v.value,y=v&&"tag"===v.type&&"html"===v.value.toLowerCase(),w=v&&"pseudo"===v.type&&":root"===v.value.toLowerCase();!v||y||w||f||l.prepend(e.combinator({value:" "}));const b=r.dir===n,h=e.attribute({attribute:"dir",operator:"=",quoteMark:'"',value:`"${n}"`,raws:null}),m=e.pseudo({value:":host-context"});m.append(h);const g=e.pseudo({value:(y||w?"":"html")+":not"});g.append(e.attribute({attribute:"dir",operator:"=",quoteMark:'"',value:`"${"ltr"===n?"rtl":"ltr"}"`,raws:null})),b?y?l.insertAfter(v,g):l.prepend(g):y?l.insertAfter(v,h):r.shadow&&!w?l.prepend(m):l.prepend(h)}))}))})).processSync(t.selector)}catch(e){return void t.warn(o,`Failed to parse selector : ${t.selector}`)}void 0!==s&&s!==t.selector&&(t.cloneBefore({selector:s}),r.preserve||t.remove())}}}};creator.postcss=!0;export{creator as default}; +import e from"postcss-selector-parser";const creator=t=>{const r=Object.assign({dir:null,preserve:!1,shadow:!1},t);return{postcssPlugin:"postcss-dir-pseudo-class",prepare(){const t=new WeakSet;return{Rule(o,{result:s}){if(t.has(o))return;let a,n=!1;if(o.selector.toLowerCase().includes(":dir(")){try{a=e((t=>{t.nodes.forEach((t=>{t.walk((t=>{if("pseudo"!==t.type)return;if(":dir"!==t.value.toLowerCase())return;if(!t.nodes||!t.nodes.length)return;const a=t.nodes.toString().toLowerCase();if("rtl"!==a&&"ltr"!==a)return;const l=t.parent;l.nodes.filter((e=>"pseudo"===e.type&&":dir"===e.value.toLowerCase())).length>1&&!n&&(n=!0,o.warn(s,`Hierarchical :dir pseudo class usage can't be transformed correctly to [dir] attributes. This will lead to incorrect selectors for "${o.selector}"`));const p=t.prev(),c=t.next(),i=p&&p.type&&"combinator"!==p.type,d=c&&c.type&&"combinator"!==c.type,u=c&&c.type&&("combinator"!==c.type||"combinator"===c.type&&" "===c.value);i||d||0===l.nodes.indexOf(t)&&u||1===l.nodes.length?t.remove():t.replaceWith(e.universal());const v=l.nodes[0],f=v&&"combinator"===v.type&&" "===v.value,w=v&&"tag"===v.type&&"html"===v.value.toLowerCase(),y=v&&"pseudo"===v.type&&":root"===v.value.toLowerCase();!v||w||y||f||l.prepend(e.combinator({value:" "}));const h=r.dir===a,b=e.attribute({attribute:"dir",operator:"=",quoteMark:'"',value:`"${a}"`,raws:null}),m=e.pseudo({value:":host-context"});m.append(b);const g=e.pseudo({value:(w||y?"":"html")+":not"});g.append(e.attribute({attribute:"dir",operator:"=",quoteMark:'"',value:`"${"ltr"===a?"rtl":"ltr"}"`,raws:null})),h?w?l.insertAfter(v,g):l.prepend(g):w?l.insertAfter(v,b):r.shadow&&!y?l.prepend(m):l.prepend(b)}))}))})).processSync(o.selector)}catch(e){return void o.warn(s,`Failed to parse selector : ${o.selector}`)}void 0!==a&&a!==o.selector&&(t.add(o),o.cloneBefore({selector:a}),r.preserve||o.remove())}}}}}};creator.postcss=!0;export{creator as default}; diff --git a/plugins/postcss-dir-pseudo-class/src/index.ts b/plugins/postcss-dir-pseudo-class/src/index.ts index 1b6136815..cad404afe 100644 --- a/plugins/postcss-dir-pseudo-class/src/index.ts +++ b/plugins/postcss-dir-pseudo-class/src/index.ts @@ -25,158 +25,169 @@ const creator: PluginCreator = (opts?: pluginOptions) => { return { postcssPlugin: 'postcss-dir-pseudo-class', - Rule(rule, { result }) { - let emittedWarningForHierarchicalDir = false; - - // walk rules using the :dir pseudo-class - if (!rule.selector.toLowerCase().includes(':dir(')) { - return; - } - - let modifiedSelector; - - try { - modifiedSelector = selectorParser(selectors => { - // for each (comma separated) selector - selectors.nodes.forEach(selector => { - // walk all selector nodes that are :dir pseudo-classes - selector.walk(node => { - if ('pseudo' !== node.type) { - return; - } - - if (':dir' !== node.value.toLowerCase()) { - return; - } - - if (!node.nodes || !node.nodes.length) { - return; - } - - // value of the :dir pseudo-class - const value = node.nodes.toString().toLowerCase(); /* needs to be lower case because attribute value selectors are case sensitive */ - if (value !== 'rtl' && value !== 'ltr') { - return; - } - - const parent = node.parent; - const otherDirPseudos = parent.nodes.filter((other) => { - return 'pseudo' === other.type && ':dir' === other.value.toLowerCase(); + prepare() { + const transformedNodes = new WeakSet(); + + return { + Rule(rule, { result }) { + if (transformedNodes.has(rule)) { + return; + } + + let emittedWarningForHierarchicalDir = false; + + // walk rules using the :dir pseudo-class + if (!rule.selector.toLowerCase().includes(':dir(')) { + return; + } + + let modifiedSelector; + + try { + modifiedSelector = selectorParser(selectors => { + // for each (comma separated) selector + selectors.nodes.forEach(selector => { + // walk all selector nodes that are :dir pseudo-classes + selector.walk(node => { + if ('pseudo' !== node.type) { + return; + } + + if (':dir' !== node.value.toLowerCase()) { + return; + } + + if (!node.nodes || !node.nodes.length) { + return; + } + + // value of the :dir pseudo-class + const value = node.nodes.toString().toLowerCase(); /* needs to be lower case because attribute value selectors are case sensitive */ + if (value !== 'rtl' && value !== 'ltr') { + return; + } + + const parent = node.parent; + const otherDirPseudos = parent.nodes.filter((other) => { + return 'pseudo' === other.type && ':dir' === other.value.toLowerCase(); + }); + if (otherDirPseudos.length > 1 && !emittedWarningForHierarchicalDir) { + emittedWarningForHierarchicalDir = true; + rule.warn(result, `Hierarchical :dir pseudo class usage can't be transformed correctly to [dir] attributes. This will lead to incorrect selectors for "${rule.selector}"`); + } + + // previous and next selector nodes + const prev = node.prev(); + const next = node.next(); + + const prevIsNonCombinator = prev && prev.type && 'combinator' !== prev.type; + const nextIsNonCombinator = next && next.type && 'combinator' !== next.type; + const nextIsNonCombinatorOrSpace = next && next.type && ('combinator' !== next.type || ('combinator' === next.type && ' ' === next.value)); + + if (prevIsNonCombinator) { + node.remove(); + } else if (nextIsNonCombinator) { + node.remove(); + } else if (parent.nodes.indexOf(node) === 0 && nextIsNonCombinatorOrSpace) { + node.remove(); + } else if (parent.nodes.length === 1) { + node.remove(); + } else { + node.replaceWith( + selectorParser.universal(), + ); + } + + // conditionally prepend a combinator before inserting the [dir] attribute + const first = parent.nodes[0]; + const firstIsSpaceCombinator = first && 'combinator' === first.type && ' ' === first.value; + const firstIsHtml = first && 'tag' === first.type && 'html' === first.value.toLowerCase(); + const firstIsRoot = first && 'pseudo' === first.type && ':root' === first.value.toLowerCase(); + + if (first && !firstIsHtml && !firstIsRoot && !firstIsSpaceCombinator) { + parent.prepend( + selectorParser.combinator({ + value: ' ', + }) as unknown as selectorParser.Selector, + ); + } + + // whether :dir matches the presumed direction + const isdir = options.dir === value; + + // [dir] attribute + const dirAttr = selectorParser.attribute({ + attribute: 'dir', + operator: '=', + quoteMark: '"', + value: `"${value}"`, + raws: null, + }); + + // :host-context([dir]) for Shadow DOM CSS + const hostContextPseudo = selectorParser.pseudo({ + value: ':host-context', + }); + hostContextPseudo.append(dirAttr as unknown as selectorParser.Selector); + + // not[dir] attribute + const notDirAttr = selectorParser.pseudo({ + value: `${firstIsHtml || firstIsRoot ? '' : 'html'}:not`, + }); + + notDirAttr.append( + selectorParser.attribute({ + attribute: 'dir', + operator: '=', + quoteMark: '"', + value: `"${'ltr' === value ? 'rtl' : 'ltr'}"`, + raws: null, + }) as unknown as selectorParser.Selector, + ); + + if (isdir) { + // if the direction is presumed + if (firstIsHtml) { + // insert :root after html tag + parent.insertAfter(first, notDirAttr); + } else { + // prepend :root + parent.prepend(notDirAttr as unknown as selectorParser.Selector); + } + } else if (firstIsHtml) { + // insert dir attribute after html tag + parent.insertAfter(first, dirAttr); + } else if (options.shadow && !firstIsRoot) { + // prepend :host-context([dir]) + parent.prepend(hostContextPseudo as unknown as selectorParser.Selector); + } else { + // otherwise, prepend the dir attribute + parent.prepend(dirAttr as unknown as selectorParser.Selector); + } + }); }); - if (otherDirPseudos.length > 1 && !emittedWarningForHierarchicalDir) { - emittedWarningForHierarchicalDir = true; - rule.warn(result, `Hierarchical :dir pseudo class usage can't be transformed correctly to [dir] attributes. This will lead to incorrect selectors for "${rule.selector}"`); - } - - // previous and next selector nodes - const prev = node.prev(); - const next = node.next(); - - const prevIsNonCombinator = prev && prev.type && 'combinator' !== prev.type; - const nextIsNonCombinator = next && next.type && 'combinator' !== next.type; - const nextIsNonCombinatorOrSpace = next && next.type && ('combinator' !== next.type || ('combinator' === next.type && ' ' === next.value)); - - if (prevIsNonCombinator) { - node.remove(); - } else if (nextIsNonCombinator) { - node.remove(); - } else if (parent.nodes.indexOf(node) === 0 && nextIsNonCombinatorOrSpace) { - node.remove(); - } else if (parent.nodes.length === 1) { - node.remove(); - } else { - node.replaceWith( - selectorParser.universal(), - ); - } - - // conditionally prepend a combinator before inserting the [dir] attribute - const first = parent.nodes[0]; - const firstIsSpaceCombinator = first && 'combinator' === first.type && ' ' === first.value; - const firstIsHtml = first && 'tag' === first.type && 'html' === first.value.toLowerCase(); - const firstIsRoot = first && 'pseudo' === first.type && ':root' === first.value.toLowerCase(); - - if (first && !firstIsHtml && !firstIsRoot && !firstIsSpaceCombinator) { - parent.prepend( - selectorParser.combinator({ - value: ' ', - }) as unknown as selectorParser.Selector, - ); - } - - // whether :dir matches the presumed direction - const isdir = options.dir === value; - - // [dir] attribute - const dirAttr = selectorParser.attribute({ - attribute: 'dir', - operator: '=', - quoteMark: '"', - value: `"${value}"`, - raws: null, - }); - - // :host-context([dir]) for Shadow DOM CSS - const hostContextPseudo = selectorParser.pseudo({ - value: ':host-context', - }); - hostContextPseudo.append(dirAttr as unknown as selectorParser.Selector); - - // not[dir] attribute - const notDirAttr = selectorParser.pseudo({ - value: `${firstIsHtml || firstIsRoot ? '' : 'html'}:not`, - }); - - notDirAttr.append( - selectorParser.attribute({ - attribute: 'dir', - operator: '=', - quoteMark: '"', - value: `"${'ltr' === value ? 'rtl' : 'ltr'}"`, - raws: null, - }) as unknown as selectorParser.Selector, - ); - - if (isdir) { - // if the direction is presumed - if (firstIsHtml) { - // insert :root after html tag - parent.insertAfter(first, notDirAttr); - } else { - // prepend :root - parent.prepend(notDirAttr as unknown as selectorParser.Selector); - } - } else if (firstIsHtml) { - // insert dir attribute after html tag - parent.insertAfter(first, dirAttr); - } else if (options.shadow && !firstIsRoot) { - // prepend :host-context([dir]) - parent.prepend(hostContextPseudo as unknown as selectorParser.Selector); - } else { - // otherwise, prepend the dir attribute - parent.prepend(dirAttr as unknown as selectorParser.Selector); - } - }); - }); - }).processSync(rule.selector); - } catch (_) { - rule.warn(result, `Failed to parse selector : ${rule.selector}`); - return; - } - - if (typeof modifiedSelector === 'undefined') { - return; - } - - if (modifiedSelector === rule.selector) { - return; - } - - rule.cloneBefore({ selector: modifiedSelector }); - - if (!options.preserve) { - rule.remove(); - } + }).processSync(rule.selector); + } catch (_) { + rule.warn(result, `Failed to parse selector : ${rule.selector}`); + return; + } + + if (typeof modifiedSelector === 'undefined') { + return; + } + + if (modifiedSelector === rule.selector) { + return; + } + + transformedNodes.add(rule); + rule.cloneBefore({ selector: modifiedSelector }); + + if (!options.preserve) { + rule.remove(); + } + }, + }; }, }; }; diff --git a/plugins/postcss-dir-pseudo-class/test/basic.css b/plugins/postcss-dir-pseudo-class/test/basic.css index 3d70a3f84..a079c4f30 100644 --- a/plugins/postcss-dir-pseudo-class/test/basic.css +++ b/plugins/postcss-dir-pseudo-class/test/basic.css @@ -97,3 +97,7 @@ html :dir(rtl) { :dir(ignore) { order: 21; } + +:dir(ltr) { + to-clone: 1; +} diff --git a/plugins/postcss-dir-pseudo-class/test/basic.dir.expect.css b/plugins/postcss-dir-pseudo-class/test/basic.dir.expect.css index 633668ef1..8dfd66d67 100644 --- a/plugins/postcss-dir-pseudo-class/test/basic.dir.expect.css +++ b/plugins/postcss-dir-pseudo-class/test/basic.dir.expect.css @@ -97,3 +97,7 @@ html:not([dir="rtl"]) :is([dir="rtl"] html:not([dir="rtl"]) * + *) { :dir(ignore) { order: 21; } + +html:not([dir="rtl"]) { + to-clone: 1; +} diff --git a/plugins/postcss-dir-pseudo-class/test/basic.expect.css b/plugins/postcss-dir-pseudo-class/test/basic.expect.css index c0f763eee..7487fdc71 100644 --- a/plugins/postcss-dir-pseudo-class/test/basic.expect.css +++ b/plugins/postcss-dir-pseudo-class/test/basic.expect.css @@ -97,3 +97,7 @@ html[dir="rtl"] * { :dir(ignore) { order: 21; } + +[dir="ltr"] { + to-clone: 1; +} diff --git a/plugins/postcss-dir-pseudo-class/test/basic.preserve.expect.css b/plugins/postcss-dir-pseudo-class/test/basic.preserve.expect.css index 058acaed5..69df5445a 100644 --- a/plugins/postcss-dir-pseudo-class/test/basic.preserve.expect.css +++ b/plugins/postcss-dir-pseudo-class/test/basic.preserve.expect.css @@ -189,3 +189,11 @@ html :dir(rtl) { :dir(ignore) { order: 21; } + +[dir="ltr"] { + to-clone: 1; +} + +:dir(ltr) { + to-clone: 1; +} diff --git a/plugins/postcss-dir-pseudo-class/test/basic.shadow.expect.css b/plugins/postcss-dir-pseudo-class/test/basic.shadow.expect.css index 9fb12609c..3b8767751 100644 --- a/plugins/postcss-dir-pseudo-class/test/basic.shadow.expect.css +++ b/plugins/postcss-dir-pseudo-class/test/basic.shadow.expect.css @@ -97,3 +97,7 @@ html[dir="rtl"] * { :dir(ignore) { order: 21; } + +:host-context([dir="ltr"]) { + to-clone: 1; +} diff --git a/plugins/postcss-dir-pseudo-class/test/basic.with-cloned-declarations.expect.css b/plugins/postcss-dir-pseudo-class/test/basic.with-cloned-declarations.expect.css new file mode 100644 index 000000000..5fe4da6da --- /dev/null +++ b/plugins/postcss-dir-pseudo-class/test/basic.with-cloned-declarations.expect.css @@ -0,0 +1,201 @@ +[dir="ltr"] { + order: 0; +} + +:dir(ltr) { + order: 0; +} + +[dir="ltr"] { + order: 0.1; +} + +:DIR(lTr) { + order: 0.1; +} + +[dir="rtl"] { + order: 0.2; +} + +:dIr(RtL) { + order: 0.2; +} + +[dir="ltr"] test { + order: 1; +} + +:dir(ltr) test { + order: 1; +} + +[dir="ltr"] test * { + order: 2; +} + +test :dir(ltr) { + order: 2; +} + +[dir="ltr"] test * test { + order: 3; +} + +test :dir(ltr) test { + order: 3; +} + +[dir="ltr"] test { + order: 4; +} + +test:dir(ltr) { + order: 4; +} + +[dir="ltr"] test test { + order: 5; +} + +test:dir(ltr) test { + order: 5; +} + +[dir="ltr"] test test { + order: 6; +} + +test test:dir(ltr) { + order: 6; +} + +[dir="ltr"]:root * { + order: 7; +} + +:root :dir(ltr) { + order: 7; +} + +html[dir="ltr"] * { + order: 8; +} + +html :dir(ltr) { + order: 8; +} + +[dir="rtl"] { + order: 9; +} + +:dir(rtl) { + order: 9; +} + +[dir="rtl"] test { + order: 10; +} + +:dir(rtl) test { + order: 10; +} + +[dir="rtl"] test * { + order: 11; +} + +test :dir(rtl) { + order: 11; +} + +[dir="rtl"] test * test { + order: 12; +} + +test :dir(rtl) test { + order: 12; +} + +[dir="rtl"] test { + order: 13; +} + +test:dir(rtl) { + order: 13; +} + +[dir="rtl"] test test { + order: 14; +} + +test:dir(rtl) test { + order: 14; +} + +[dir="rtl"] test test { + order: 15; +} + +test test:dir(rtl) { + order: 15; +} + +[dir="rtl"]:root * { + order: 16; +} + +:root :dir(rtl) { + order: 16; +} + +html[dir="rtl"] * { + order: 17; +} + +html :dir(rtl) { + order: 17; +} + +[dir="rtl"] [dir="ltr"] * { + order: 18; +} + +:dir(ltr) :dir(rtl) { + order: 18; +} + +[dir="ltr"] :has([dir="rtl"] .foo *) { + order: 19; +} + +:dir(ltr) :has(.foo :dir(rtl)) { + order: 19; +} + +[dir="ltr"] :is([dir="rtl"] [dir="ltr"] * + *) { + order: 19; +} + +:dir(ltr) :is(:dir(ltr) + :dir(rtl)) { + order: 19; +} + +:root :dir(var(--dir)) { + order: 20; +} + +:dir(ignore) { + order: 21; +} + +[dir="ltr"] { + cloned: 1; + to-clone: 1; +} + +:dir(ltr) { + cloned: 1; + to-clone: 1; +} diff --git a/plugins/postcss-double-position-gradients/.tape.mjs b/plugins/postcss-double-position-gradients/.tape.mjs index 303c70976..457675e2b 100644 --- a/plugins/postcss-double-position-gradients/.tape.mjs +++ b/plugins/postcss-double-position-gradients/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from 'postcss-double-position-gradients'; postcssTape(plugin)({ diff --git a/plugins/postcss-env-function/.tape.cjs b/plugins/postcss-env-function/.tape.cjs index 60e338afa..e8b4e4622 100644 --- a/plugins/postcss-env-function/.tape.cjs +++ b/plugins/postcss-env-function/.tape.cjs @@ -1,7 +1,7 @@ const postcssTape = require('../../packages/postcss-tape/dist/index.cjs'); const plugin = require('postcss-env-function'); -postcssTape(plugin)({ +postcssTape.postcssTape(plugin)({ 'basic': { message: 'supports basic usage', }, diff --git a/plugins/postcss-extract/.tape.mjs b/plugins/postcss-extract/.tape.mjs index efc352250..982fc6322 100644 --- a/plugins/postcss-extract/.tape.mjs +++ b/plugins/postcss-extract/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from '@csstools/postcss-extract'; import assert from 'assert'; diff --git a/plugins/postcss-focus-visible/.tape.mjs b/plugins/postcss-focus-visible/.tape.mjs index d526dbfee..403b3fa38 100644 --- a/plugins/postcss-focus-visible/.tape.mjs +++ b/plugins/postcss-focus-visible/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from 'postcss-focus-visible'; postcssTape(plugin)({ diff --git a/plugins/postcss-focus-within/.tape.mjs b/plugins/postcss-focus-within/.tape.mjs index e403935be..0d0295e11 100644 --- a/plugins/postcss-focus-within/.tape.mjs +++ b/plugins/postcss-focus-within/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from 'postcss-focus-within'; postcssTape(plugin)({ diff --git a/plugins/postcss-font-format-keywords/.tape.mjs b/plugins/postcss-font-format-keywords/.tape.mjs index 7e18e0c47..f6e7b5550 100644 --- a/plugins/postcss-font-format-keywords/.tape.mjs +++ b/plugins/postcss-font-format-keywords/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from '@csstools/postcss-font-format-keywords'; postcssTape(plugin)({ diff --git a/plugins/postcss-gap-properties/.tape.mjs b/plugins/postcss-gap-properties/.tape.mjs index ce3da26b8..7fd7fad69 100644 --- a/plugins/postcss-gap-properties/.tape.mjs +++ b/plugins/postcss-gap-properties/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from 'postcss-gap-properties'; postcssTape(plugin)({ diff --git a/plugins/postcss-gradients-interpolation-method/.tape.mjs b/plugins/postcss-gradients-interpolation-method/.tape.mjs index 7f4807561..67eb10504 100644 --- a/plugins/postcss-gradients-interpolation-method/.tape.mjs +++ b/plugins/postcss-gradients-interpolation-method/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from '@csstools/postcss-gradients-interpolation-method'; postcssTape(plugin)({ diff --git a/plugins/postcss-hwb-function/.tape.mjs b/plugins/postcss-hwb-function/.tape.mjs index b261f8d50..4a01b6c29 100644 --- a/plugins/postcss-hwb-function/.tape.mjs +++ b/plugins/postcss-hwb-function/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from '@csstools/postcss-hwb-function'; postcssTape(plugin)({ diff --git a/plugins/postcss-ic-unit/.tape.mjs b/plugins/postcss-ic-unit/.tape.mjs index d827349b7..0fef01104 100644 --- a/plugins/postcss-ic-unit/.tape.mjs +++ b/plugins/postcss-ic-unit/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from '@csstools/postcss-ic-unit'; postcssTape(plugin)({ diff --git a/plugins/postcss-image-set-function/.tape.mjs b/plugins/postcss-image-set-function/.tape.mjs index 432b13914..bb8cd747d 100644 --- a/plugins/postcss-image-set-function/.tape.mjs +++ b/plugins/postcss-image-set-function/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from 'postcss-image-set-function'; postcssTape(plugin)({ diff --git a/plugins/postcss-is-pseudo-class/.tape.mjs b/plugins/postcss-is-pseudo-class/.tape.mjs index db2af6e49..aa42e5785 100644 --- a/plugins/postcss-is-pseudo-class/.tape.mjs +++ b/plugins/postcss-is-pseudo-class/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from '@csstools/postcss-is-pseudo-class'; postcssTape(plugin)({ diff --git a/plugins/postcss-lab-function/.tape.mjs b/plugins/postcss-lab-function/.tape.mjs index 11d3176bf..9bce881b7 100644 --- a/plugins/postcss-lab-function/.tape.mjs +++ b/plugins/postcss-lab-function/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from 'postcss-lab-function'; postcssTape(plugin)({ diff --git a/plugins/postcss-logical-float-and-clear/.tape.mjs b/plugins/postcss-logical-float-and-clear/.tape.mjs index 68721bc51..c1b2c848d 100644 --- a/plugins/postcss-logical-float-and-clear/.tape.mjs +++ b/plugins/postcss-logical-float-and-clear/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from '@csstools/postcss-logical-float-and-clear'; postcssTape(plugin)({ diff --git a/plugins/postcss-logical-resize/.tape.mjs b/plugins/postcss-logical-resize/.tape.mjs index 49235294a..98e97ded3 100644 --- a/plugins/postcss-logical-resize/.tape.mjs +++ b/plugins/postcss-logical-resize/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from '@csstools/postcss-logical-resize'; postcssTape(plugin)({ diff --git a/plugins/postcss-logical-viewport-units/.tape.mjs b/plugins/postcss-logical-viewport-units/.tape.mjs index ac23bcc9d..a2f3435ef 100644 --- a/plugins/postcss-logical-viewport-units/.tape.mjs +++ b/plugins/postcss-logical-viewport-units/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from '@csstools/postcss-logical-viewport-units'; postcssTape(plugin)({ diff --git a/plugins/postcss-logical/.tape.mjs b/plugins/postcss-logical/.tape.mjs index a7628f499..fd9eddd39 100644 --- a/plugins/postcss-logical/.tape.mjs +++ b/plugins/postcss-logical/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from 'postcss-logical'; postcssTape(plugin)({ diff --git a/plugins/postcss-media-queries-aspect-ratio-number-values/.tape.mjs b/plugins/postcss-media-queries-aspect-ratio-number-values/.tape.mjs index 46af91ed9..5cd18d593 100644 --- a/plugins/postcss-media-queries-aspect-ratio-number-values/.tape.mjs +++ b/plugins/postcss-media-queries-aspect-ratio-number-values/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from '@csstools/postcss-media-queries-aspect-ratio-number-values'; postcssTape(plugin)({ diff --git a/plugins/postcss-nested-calc/.tape.mjs b/plugins/postcss-nested-calc/.tape.mjs index d4f549f1a..b276c863e 100644 --- a/plugins/postcss-nested-calc/.tape.mjs +++ b/plugins/postcss-nested-calc/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from '@csstools/postcss-nested-calc'; postcssTape(plugin)({ diff --git a/plugins/postcss-nesting/.tape.mjs b/plugins/postcss-nesting/.tape.mjs index 3d1387e42..baf3c4897 100644 --- a/plugins/postcss-nesting/.tape.mjs +++ b/plugins/postcss-nesting/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from 'postcss-nesting'; const mixinPluginRule = () => { diff --git a/plugins/postcss-normalize-display-values/.tape.mjs b/plugins/postcss-normalize-display-values/.tape.mjs index f99d4b4cc..42876f4af 100644 --- a/plugins/postcss-normalize-display-values/.tape.mjs +++ b/plugins/postcss-normalize-display-values/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from '@csstools/postcss-normalize-display-values'; postcssTape(plugin)({ diff --git a/plugins/postcss-oklab-function/.tape.mjs b/plugins/postcss-oklab-function/.tape.mjs index 0caca9ae1..7afdf038d 100644 --- a/plugins/postcss-oklab-function/.tape.mjs +++ b/plugins/postcss-oklab-function/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from '@csstools/postcss-oklab-function'; postcssTape(plugin)({ diff --git a/plugins/postcss-overflow-shorthand/.tape.mjs b/plugins/postcss-overflow-shorthand/.tape.mjs index 7ecdf8856..d803b9bea 100644 --- a/plugins/postcss-overflow-shorthand/.tape.mjs +++ b/plugins/postcss-overflow-shorthand/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from 'postcss-overflow-shorthand'; postcssTape(plugin)({ diff --git a/plugins/postcss-place/.tape.mjs b/plugins/postcss-place/.tape.mjs index 66c4e3fa7..4951daf48 100644 --- a/plugins/postcss-place/.tape.mjs +++ b/plugins/postcss-place/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from 'postcss-place'; postcssTape(plugin)({ diff --git a/plugins/postcss-progressive-custom-properties/.tape.mjs b/plugins/postcss-progressive-custom-properties/.tape.mjs index 8aba61841..dd11b392a 100644 --- a/plugins/postcss-progressive-custom-properties/.tape.mjs +++ b/plugins/postcss-progressive-custom-properties/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from '@csstools/postcss-progressive-custom-properties'; postcssTape(plugin)({ diff --git a/plugins/postcss-pseudo-class-any-link/.tape.mjs b/plugins/postcss-pseudo-class-any-link/.tape.mjs index f104f7bb5..6e89ad464 100644 --- a/plugins/postcss-pseudo-class-any-link/.tape.mjs +++ b/plugins/postcss-pseudo-class-any-link/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from 'postcss-pseudo-class-any-link'; postcssTape(plugin)({ diff --git a/plugins/postcss-scope-pseudo-class/.tape.mjs b/plugins/postcss-scope-pseudo-class/.tape.mjs index c869236e8..a28089f77 100644 --- a/plugins/postcss-scope-pseudo-class/.tape.mjs +++ b/plugins/postcss-scope-pseudo-class/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from '@csstools/postcss-scope-pseudo-class'; postcssTape(plugin)({ diff --git a/plugins/postcss-selector-not/.tape.mjs b/plugins/postcss-selector-not/.tape.mjs index 0f7979fc9..d59a5e077 100644 --- a/plugins/postcss-selector-not/.tape.mjs +++ b/plugins/postcss-selector-not/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from 'postcss-selector-not'; postcssTape(plugin)({ diff --git a/plugins/postcss-stepped-value-functions/.tape.mjs b/plugins/postcss-stepped-value-functions/.tape.mjs index 455616b84..e16c041ed 100644 --- a/plugins/postcss-stepped-value-functions/.tape.mjs +++ b/plugins/postcss-stepped-value-functions/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from '@csstools/postcss-stepped-value-functions'; postcssTape(plugin)({ diff --git a/plugins/postcss-text-decoration-shorthand/.tape.mjs b/plugins/postcss-text-decoration-shorthand/.tape.mjs index 8200ea0cd..0f29168b1 100644 --- a/plugins/postcss-text-decoration-shorthand/.tape.mjs +++ b/plugins/postcss-text-decoration-shorthand/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from '@csstools/postcss-text-decoration-shorthand'; import autoprefixer from 'autoprefixer'; diff --git a/plugins/postcss-todo-or-die/.tape.mjs b/plugins/postcss-todo-or-die/.tape.mjs index 1f9906790..5535c98c1 100644 --- a/plugins/postcss-todo-or-die/.tape.mjs +++ b/plugins/postcss-todo-or-die/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from '@csstools/postcss-todo-or-die'; process.env['BROWSERSLIST'] = '> 1%, chrome 79'; diff --git a/plugins/postcss-trigonometric-functions/.tape.mjs b/plugins/postcss-trigonometric-functions/.tape.mjs index 6783d575c..089c5fe8c 100644 --- a/plugins/postcss-trigonometric-functions/.tape.mjs +++ b/plugins/postcss-trigonometric-functions/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from '@csstools/postcss-trigonometric-functions'; postcssTape(plugin)({ diff --git a/plugins/postcss-unset-value/.tape.mjs b/plugins/postcss-unset-value/.tape.mjs index f55eac83c..80dd59969 100644 --- a/plugins/postcss-unset-value/.tape.mjs +++ b/plugins/postcss-unset-value/.tape.mjs @@ -1,4 +1,4 @@ -import postcssTape from '../../packages/postcss-tape/dist/index.mjs'; +import { postcssTape } from '../../packages/postcss-tape/dist/index.mjs'; import plugin from '@csstools/postcss-unset-value'; postcssTape(plugin)({