diff --git a/packages/css-calc/CHANGELOG.md b/packages/css-calc/CHANGELOG.md index 3c1ffa6706..dc2bb60370 100644 --- a/packages/css-calc/CHANGELOG.md +++ b/packages/css-calc/CHANGELOG.md @@ -1,5 +1,9 @@ # Changes to CSS Calc +### Unreleased (patch) + +- Precompute the CRC32 lookup table instead of parsing a hex substring for every character, avoiding quadratic work when hashing `random()`. + ### 3.4.2 _October 1, 2026_ diff --git a/packages/css-calc/dist/index.mjs b/packages/css-calc/dist/index.mjs index 12e92c8524..c5305a9b2d 100644 --- a/packages/css-calc/dist/index.mjs +++ b/packages/css-calc/dist/index.mjs @@ -1 +1 @@ -import{sourceIndices as e,TokenNode as n,isTokenNode as t,isWhiteSpaceOrCommentNode as r,isFunctionNode as u,isSimpleBlockNode as a,FunctionNode as i,WhitespaceNode as o,parseCommaSeparatedListOfComponentValues as l,walk as c}from"@csstools/css-parser-algorithms";import{isTokenDimension as s,TokenType as v,NumberType as f,mutateUnit as p,isTokenNumber as m,isTokenPercentage as d,isTokenIdent as N,isTokenNumeric as C,isTokenOpenParen as g,isTokenDelim as h,isTokenComma as D,isToken as b,tokenizer as B,tokenize as A,stringify as w,isTokenColon as F,isTokenSemicolon as E}from"@csstools/css-tokenizer";class ParseError extends Error{sourceStart;sourceEnd;constructor(e,n,t){super(e),this.name="ParseError",this.sourceStart=n,this.sourceEnd=t}}class ParseErrorWithComponentValues extends ParseError{componentValues;constructor(n,t){super(n,...e(t)),this.componentValues=t}}const S={UnexpectedAdditionOfDimensionOrPercentageWithNumber:"Unexpected addition of a dimension or percentage with a number.",UnexpectedSubtractionOfDimensionOrPercentageWithNumber:"Unexpected subtraction of a dimension or percentage with a number."},y=/[A-Z]/g;function toLowerCaseAZ(e){return e.replace(y,e=>String.fromCharCode(e.charCodeAt(0)+32))}const I={cm:"px",in:"px",mm:"px",pc:"px",pt:"px",px:"px",q:"px",deg:"deg",grad:"deg",rad:"deg",turn:"deg",ms:"s",s:"s",hz:"hz",khz:"hz"},x=new Map([["cm",e=>e],["mm",e=>10*e],["q",e=>40*e],["in",e=>e/2.54],["pc",e=>e/2.54*6],["pt",e=>e/2.54*72],["px",e=>e/2.54*96]]),M=new Map([["deg",e=>e],["grad",e=>e/.9],["rad",e=>e/180*Math.PI],["turn",e=>e/360]]),T=new Map([["deg",e=>.9*e],["grad",e=>e],["rad",e=>.9*e/180*Math.PI],["turn",e=>.9*e/360]]),P=new Map([["hz",e=>e],["khz",e=>e/1e3]]),k=new Map([["cm",e=>2.54*e],["mm",e=>25.4*e],["q",e=>25.4*e*4],["in",e=>e],["pc",e=>6*e],["pt",e=>72*e],["px",e=>96*e]]),W=new Map([["hz",e=>1e3*e],["khz",e=>e]]),O=new Map([["cm",e=>e/10],["mm",e=>e],["q",e=>4*e],["in",e=>e/25.4],["pc",e=>e/25.4*6],["pt",e=>e/25.4*72],["px",e=>e/25.4*96]]),U=new Map([["ms",e=>e],["s",e=>e/1e3]]),L=new Map([["cm",e=>e/6*2.54],["mm",e=>e/6*25.4],["q",e=>e/6*25.4*4],["in",e=>e/6],["pc",e=>e],["pt",e=>e/6*72],["px",e=>e/6*96]]),$=new Map([["cm",e=>e/72*2.54],["mm",e=>e/72*25.4],["q",e=>e/72*25.4*4],["in",e=>e/72],["pc",e=>e/72*6],["pt",e=>e],["px",e=>e/72*96]]),V=new Map([["cm",e=>e/96*2.54],["mm",e=>e/96*25.4],["q",e=>e/96*25.4*4],["in",e=>e/96],["pc",e=>e/96*6],["pt",e=>e/96*72],["px",e=>e]]),Z=new Map([["cm",e=>e/4/10],["mm",e=>e/4],["q",e=>e],["in",e=>e/4/25.4],["pc",e=>e/4/25.4*6],["pt",e=>e/4/25.4*72],["px",e=>e/4/25.4*96]]),z=new Map([["deg",e=>180*e/Math.PI],["grad",e=>180*e/Math.PI/.9],["rad",e=>e],["turn",e=>180*e/Math.PI/360]]),q=new Map([["ms",e=>1e3*e],["s",e=>e]]),R=new Map([["deg",e=>360*e],["grad",e=>360*e/.9],["rad",e=>360*e/180*Math.PI],["turn",e=>e]]),G=new Map([["cm",x],["mm",O],["q",Z],["in",k],["pc",L],["pt",$],["px",V],["ms",U],["s",q],["deg",M],["grad",T],["rad",z],["turn",R],["hz",P],["khz",W]]);function convertUnit(e,n){if(!s(e))return n;if(!s(n))return n;const t=toLowerCaseAZ(e[4].unit),r=toLowerCaseAZ(n[4].unit);if(t===r)return n;const u=G.get(r);if(!u)return n;const a=u.get(t);if(!a)return n;const i=a(n[4].value),o=[v.Dimension,"",n[2],n[3],{...n[4],signCharacter:i<0?"-":void 0,type:Number.isInteger(i)?f.Integer:f.Number,value:i}];return p(o,e[4].unit),o}function toCanonicalUnit(e){if(!s(e))return e;const n=toLowerCaseAZ(e[4].unit),t=I[n];if(n===t)return e;const r=G.get(n);if(!r)return e;const u=r.get(t);if(!u)return e;const a=u(e[4].value),i=[v.Dimension,"",e[2],e[3],{...e[4],signCharacter:a<0?"-":void 0,type:Number.isInteger(a)?f.Integer:f.Number,value:a}];return p(i,t),i}function addition(e,t){if(2!==e.length)return-1;const r=e[0].value;let u=e[1].value;if(m(r)&&m(u)){const e=r[4].value+u[4].value;return new n([v.Number,e.toString(),r[2],u[3],{value:e,type:r[4].type===f.Integer&&u[4].type===f.Integer?f.Integer:f.Number}])}if(d(r)&&d(u)){const e=r[4].value+u[4].value;return new n([v.Percentage,e.toString()+"%",r[2],u[3],{value:e}])}if(s(r)&&s(u)&&(u=convertUnit(r,u),toLowerCaseAZ(r[4].unit)===toLowerCaseAZ(u[4].unit))){const e=r[4].value+u[4].value;return new n([v.Dimension,e.toString()+r[4].unit,r[2],u[3],{value:e,type:r[4].type===f.Integer&&u[4].type===f.Integer?f.Integer:f.Number,unit:r[4].unit}])}return(m(r)&&(s(u)||d(u))||m(u)&&(s(r)||d(r)))&&t.onParseError?.(new ParseErrorWithComponentValues(S.UnexpectedAdditionOfDimensionOrPercentageWithNumber,e)),-1}function division(e){if(2!==e.length)return-1;const t=e[0].value,r=e[1].value;if(m(t)&&m(r)){const e=t[4].value/r[4].value;return new n([v.Number,e.toString(),t[2],r[3],{value:e,type:Number.isInteger(e)?f.Integer:f.Number}])}if(d(t)&&m(r)){const e=t[4].value/r[4].value;return new n([v.Percentage,e.toString()+"%",t[2],r[3],{value:e}])}if(s(t)&&m(r)){const e=t[4].value/r[4].value;return new n([v.Dimension,e.toString()+t[4].unit,t[2],r[3],{value:e,type:Number.isInteger(e)?f.Integer:f.Number,unit:t[4].unit}])}return-1}function isCalculation(e){return!!e&&"object"==typeof e&&"inputs"in e&&Array.isArray(e.inputs)&&"operation"in e}function solve(e,n){if(-1===e)return-1;const r=[{calculation:e,inputs:[],index:0}];let u=-1;for(;r.length;){const e=r[r.length-1];if(e.indexconvertUnit(u,e.value));if(!arrayOfSameNumeric(a))return-1;if(a.some(e=>Number.isNaN(e[4].value)))return resultToCalculation(e,u,Number.NaN);if(!r.rawPercentages&&d(u))return-1;const i=a.map(e=>e[4].value);if(i.some(Number.isNaN))return resultToCalculation(e,u,Number.NaN);const o=Math.hypot(...i);return resultToCalculation(e,u,o)}function solveMax(e,n,r){if(!n.every(t))return-1;const u=n[0].value;if(!C(u))return-1;const a=n.map(e=>convertUnit(u,e.value));if(!arrayOfSameNumeric(a))return-1;if(a.some(e=>Number.isNaN(e[4].value)))return resultToCalculation(e,u,Number.NaN);if(!r.rawPercentages&&d(u))return-1;const i=a.map(e=>e[4].value),o=Math.max(...i);return resultToCalculation(e,u,o)}function solveMin(e,n,r){if(!n.every(t))return-1;const u=n[0].value;if(!C(u))return-1;const a=n.map(e=>convertUnit(u,e.value));if(!arrayOfSameNumeric(a))return-1;if(a.some(e=>Number.isNaN(e[4].value)))return resultToCalculation(e,u,Number.NaN);if(!r.rawPercentages&&d(u))return-1;const i=a.map(e=>e[4].value),o=Math.min(...i);return resultToCalculation(e,u,o)}function solveMod(e,n,t){const r=n.value;if(!C(r))return-1;const u=convertUnit(r,t.value);if(!twoOfSameNumeric(r,u))return-1;let a;return a=Number.isNaN(r[4].value)||Number.isNaN(u[4].value)||0===u[4].value?Number.NaN:Number.isFinite(r[4].value)&&(Number.isFinite(u[4].value)||(u[4].value!==Number.POSITIVE_INFINITY||r[4].value!==Number.NEGATIVE_INFINITY&&!Object.is(0*r[4].value,-0))&&(u[4].value!==Number.NEGATIVE_INFINITY||r[4].value!==Number.POSITIVE_INFINITY&&!Object.is(0*r[4].value,0)))?Number.isFinite(u[4].value)?(r[4].value%u[4].value+u[4].value)%u[4].value:r[4].value:Number.NaN,resultToCalculation(e,r,a)}function solvePow(e,n,t){const r=n.value,u=t.value;if(!m(r))return-1;if(!twoOfSameNumeric(r,u))return-1;if(Number.isNaN(r[4].value)||Number.isNaN(u[4].value))return numberToCalculation(e,Number.NaN);return numberToCalculation(e,Math.pow(r[4].value,u[4].value))}function solveRem(e,n,t){const r=n.value;if(!C(r))return-1;const u=convertUnit(r,t.value);if(!twoOfSameNumeric(r,u))return-1;let a;return a=Number.isNaN(r[4].value)||Number.isNaN(u[4].value)||0===u[4].value?Number.NaN:Number.isFinite(r[4].value)?Number.isFinite(u[4].value)?r[4].value%u[4].value:r[4].value:Number.NaN,resultToCalculation(e,r,a)}function snapAsBorderWidth(e,n,t){if(!s(n))return-1;const r=t.devicePixelLength??1,u=[v.Dimension,`${r}px`,n[2],n[3],{value:r,type:f.Integer,unit:"px"}],a=convertUnit(u,n);if(!twoOfSameNumeric(a,u))return-1;if(Number.isInteger(a[4].value/r))return resultToCalculation(e,n,n[4].value);if(a[4].value>0){if(a[4].value0?1/0:Object.is(0,0*a[4].value)?0:-0;break;default:o=Object.is(0,0*a[4].value)?0:-0}else switch(n){case"down":o=(i[4].value>0?Math.floor:Math.ceil)(a[4].value/i[4].value)*i[4].value;break;case"up":o=(i[4].value>0?Math.ceil:Math.floor)(a[4].value/i[4].value)*i[4].value;break;case"to-zero":o=Math.trunc(a[4].value/i[4].value)*i[4].value;break;default:{let t=Math.floor(a[4].value/i[4].value)*i[4].value,r=Math.ceil(a[4].value/i[4].value)*i[4].value;if(t>r){const e=t;t=r,r=e}const l=Math.abs(a[4].value-t),c=Math.abs(a[4].value-r);if(o="line-width"!==n||0!==r&&0!==t?l===c?r:l!r(e));return 1===n.length&&isNone(n[0])}if(!t(e))return!1;const n=e.value;return!!N(n)&&j.test(n[4].value)}function patchPrecision(e,n=13){if(-1===e)return-1;if(n<=0)return e;if(u(e))return e;const t=e.value;if(!C(t))return e;if(shouldSkipPrecisionRounding(t[4].value))return e;const r=roundToPrecision(t[4].value,n).toString();return m(t)?t[1]=r:d(t)?t[1]=r+"%":s(t)&&(t[1]=r+t[4].unit),e}function shouldSkipPrecisionRounding(e){if(!Number.isFinite(e)||0===e)return!0;if(Number.isInteger(e))return!0;const n=e.toString();return n.includes("e")||n.includes("E")}function roundToPrecision(e,n=13){return shouldSkipPrecisionRounding(e)?e:Number(e.toFixed(n))}const Y=String.fromCodePoint(0);function solveRandom(e,n,t,r,u,a){if(-1===n.fixed&&!a.randomCaching)return-1;const i=t.value;if(!C(i))return-1;const o=convertUnit(i,r.value);if(!twoOfSameNumeric(i,o))return-1;let l=null;if(u&&(l=convertUnit(i,u.value),!twoOfSameNumeric(i,l)))return-1;if(Number.isNaN(i[4].value)||Number.isNaN(o[4].value))return resultToCalculation(e,i,Number.NaN);if(!Number.isFinite(i[4].value))return resultToCalculation(e,i,i[4].value);if(!Number.isFinite(o[4].value)||!Number.isFinite(o[4].value-i[4].value))return resultToCalculation(e,i,Number.NaN);if(l){if(Number.isNaN(l[4].value))return resultToCalculation(e,i,Number.NaN);if(!Number.isFinite(l[4].value))return resultToCalculation(e,i,i[4].value)}const c=-1!==n.fixed?()=>n.fixed:(a.randomCaching.propertyN||(a.randomCaching.propertyN=0),sfc32(crc32([n.dashedIdent?n.dashedIdent:"",n.elementScoped?a.randomCaching.elementID:"",n.propertyScoped||n.propertyIndexScoped?a.randomCaching.propertyName:"",n.propertyIndexScoped?a.randomCaching.propertyN:"",a.randomCaching.documentID].join(Y)))),s=i[4].value;let v=o[4].value;if(v1e10)&&(l=null),l){const n=l[4].value,t=Math.max(n/1e3,1e-9);let r=Math.floor((v-s)/n);Math.abs(s+r*n-v)>=t&&Math.abs(s+(r+1)*n-v){const u=((e|=0)+(n|=0)|0)+(r|=0)|0;return r=r+1|0,e=n^n>>>9,n=(t|=0)+(t<<3)|0,t=(t=t<<21|t>>>11)+u|0,(u>>>0)/4294967296}}function crc32(e){let n,t,r=0;r^=-1;for(let u=0,a=e.length;u>>8^n;return(-1^r)>>>0}function solveCalcMix(e,n){const t=n[0].calcSum.value;if(!C(t))return-1;const r=n.map(e=>({calcSum:convertUnit(t,e.calcSum.value),percentage:e.percentage}));if(!arrayOfSameNumeric(r.map(e=>e.calcSum)))return-1;const{items:u}=normalizeMixPercentages(r,!1),a=u.map(e=>e.calcSum[4].value*e.percentage/100);let i=0;for(let e=0;e100||a>0&&n)for(const e of u)e.percentage=e.percentage*(100/a);let i=0;return a<100&&(i=100-a),{items:u,leftover:i}}const _=new Map([["abs",function abs(e,n,t){return singleNodeSolver(e,n,t,solveAbs)}],["acos",function acos(e,n,t){return singleNodeSolver(e,n,t,solveACos)}],["asin",function asin(e,n,t){return singleNodeSolver(e,n,t,solveASin)}],["atan",function atan(e,n,t){return singleNodeSolver(e,n,t,solveATan)}],["atan2",function atan2(e,n,t){return twoCommaSeparatedNodesSolver(e,n,t,solveATan2)}],["calc",calc$1],["calc-mix",function calcMix(e,n,t){const r=variadicArgumentsCalcMix(e,e.value,n,t);if(-1===r)return-1;return solveCalcMix(e,r)}],["clamp",function clamp(u,a,o){const l=resolveGlobalsAndConstants([...u.value.filter(e=>!r(e))],a),c=[],s=[],f=[];{let e=c;for(let n=0;n!r(e)),n,t);if(-1===u)return-1;const[a,i]=u,o=variadicArguments(e,i,n,t);if(-1===o)return-1;const[l,c,s]=o;if(!l||!c)return-1;return solveRandom(e,a,l,c,s,t)}],["rem",function rem(e,n,t){return twoCommaSeparatedNodesSolver(e,n,t,solveRem)}],["round",function round(e,u,a){const i=resolveGlobalsAndConstants([...e.value.filter(e=>!r(e))],u);let o="",l=!1;const c=[],p=[];{let e=c;for(let n=0;n!r(e))],n);if(o.length>H)throw new Error(`Maximum number of nodes in a math function (${H}) exceeded, reduce the complexity of your expression`);if(1===o.length&&t(o[0]))return{inputs:[o[0]],operation:unary};let l=0;for(;l!r(e))],n),a=solve(calc$1(calcWrapper(e,u),n,t),t);return-1===a?-1:a}function twoCommaSeparatedNodesSolver(e,n,t,r){const u=twoCommaSeparatedArguments(e,n,t);if(-1===u)return-1;const[a,i]=u;return r(e,a,i,t)}function twoCommaSeparatedArguments(e,n,u){const a=resolveGlobalsAndConstants([...e.value.filter(e=>!r(e))],n),i=[],o=[];{let e=i;for(let n=0;n!r(e))],u),o=[];{const n=[];let r=[];for(let e=0;e!r(e))],u),o=[];{const n=[];let l=[];for(let e=0;e=0;o--){if(r(n[i][o]))continue;if(l=solve(calc$1(calcWrapper(e,n[i].slice(0,o+1)),u,a),a),-1===l)continue;const s=n[i].slice(o+1).filter(e=>!r(e));if(s.length)if(1===s.length&&t(s[0])&&d(s[0].value)){if(c=s[0].value[4].value,c<0||c>100)return-1}else{const n=solve(calc$1(calcWrapper(e,s),u,a),a);if(-1===n)return-1;if(!d(n.value))return-1;c=n.value[4].value,c=Math.min(100,Math.max(0,c))}break}if(-1===l)return-1;o.push({calcSum:l,percentage:c})}}return o}const J=new Set(["nearest","line-width","up","down","to-zero"]);function parseRandomValueSharing(e,n,r,u){const a={dashedIdent:"",fixed:-1,elementScoped:!1,propertyScoped:!1,propertyIndexScoped:!1};let i=!1;const o=n[0];if(!t(o)||!N(o.value))return[a,n];for(let o=0;o1)return-1;a.fixed=Math.max(0,Math.min(l.value[4].value,1-Number.EPSILON/2));continue}if("auto"!==c){if(!c.startsWith("--"))return 0===o?[a,n]:-1;if(-1!==a.fixed||i||a.dashedIdent)return-1;a.dashedIdent=c}else{if(-1!==a.fixed||i||a.dashedIdent||a.elementScoped||a.propertyScoped||a.propertyIndexScoped)return-1;a.elementScoped=!0,a.propertyIndexScoped=!0,i=!0}}else{if(-1!==a.fixed||i||a.propertyScoped||a.propertyIndexScoped)return-1;a.propertyIndexScoped=!0}else{if(-1!==a.fixed||i||a.propertyScoped||a.propertyIndexScoped)return-1;a.propertyScoped=!0}else{if(-1!==a.fixed||i||a.elementScoped)return-1;a.elementScoped=!0}}return-1}function calcWrapper(e,n){return new i([v.Function,"calc(",e.name[2],e.name[3],{value:"calc"}],[v.CloseParen,")",e.endToken[2],e.endToken[3],void 0],n)}function maxWrapper(t,r,u){return new i([v.Function,"max(",t.name[2],t.name[3],{value:"max"}],[v.CloseParen,")",t.endToken[2],t.endToken[3],void 0],[r,new n([v.Comma,",",...e(r),void 0]),u])}function patchNaN(e){if(-1===e)return-1;if(u(e))return e;const t=e.value;return C(t)&&Number.isNaN(t[4].value)?m(t)?new i([v.Function,"calc(",t[2],t[3],{value:"calc"}],[v.CloseParen,")",t[2],t[3],void 0],[new n([v.Ident,"NaN",t[2],t[3],{value:"NaN"}])]):s(t)?new i([v.Function,"calc(",t[2],t[3],{value:"calc"}],[v.CloseParen,")",t[2],t[3],void 0],[new n([v.Ident,"NaN",t[2],t[3],{value:"NaN"}]),new o([[v.Whitespace," ",t[2],t[3],void 0]]),new n([v.Delim,"*",t[2],t[3],{value:"*"}]),new o([[v.Whitespace," ",t[2],t[3],void 0]]),new n([v.Dimension,"1"+t[4].unit,t[2],t[3],{value:1,type:f.Integer,unit:t[4].unit}])]):d(t)?new i([v.Function,"calc(",t[2],t[3],{value:"calc"}],[v.CloseParen,")",t[2],t[3],void 0],[new n([v.Ident,"NaN",t[2],t[3],{value:"NaN"}]),new o([[v.Whitespace," ",t[2],t[3],void 0]]),new n([v.Delim,"*",t[2],t[3],{value:"*"}]),new o([[v.Whitespace," ",t[2],t[3],void 0]]),new n([v.Percentage,"1%",t[2],t[3],{value:1}])]):-1:e}function patchInfinity(e){if(-1===e)return-1;if(u(e))return e;const t=e.value;if(!C(t))return e;if(Number.isFinite(t[4].value)||Number.isNaN(t[4].value))return e;let r="";return Number.NEGATIVE_INFINITY===t[4].value&&(r="-"),m(t)?new i([v.Function,"calc(",t[2],t[3],{value:"calc"}],[v.CloseParen,")",t[2],t[3],void 0],[new n([v.Ident,r+"infinity",t[2],t[3],{value:r+"infinity"}])]):s(t)?new i([v.Function,"calc(",t[2],t[3],{value:"calc"}],[v.CloseParen,")",t[2],t[3],void 0],[new n([v.Ident,r+"infinity",t[2],t[3],{value:r+"infinity"}]),new o([[v.Whitespace," ",t[2],t[3],void 0]]),new n([v.Delim,"*",t[2],t[3],{value:"*"}]),new o([[v.Whitespace," ",t[2],t[3],void 0]]),new n([v.Dimension,"1"+t[4].unit,t[2],t[3],{value:1,type:f.Integer,unit:t[4].unit}])]):new i([v.Function,"calc(",t[2],t[3],{value:"calc"}],[v.CloseParen,")",t[2],t[3],void 0],[new n([v.Ident,r+"infinity",t[2],t[3],{value:r+"infinity"}]),new o([[v.Whitespace," ",t[2],t[3],void 0]]),new n([v.Delim,"*",t[2],t[3],{value:"*"}]),new o([[v.Whitespace," ",t[2],t[3],void 0]]),new n([v.Percentage,"1%",t[2],t[3],{value:1}])])}function patchMinusZero(e){if(-1===e)return-1;if(u(e))return e;const n=e.value;return C(n)&&Object.is(-0,n[4].value)?("-0"===n[1]||(d(n)?n[1]="-0%":s(n)?n[1]="-0"+n[4].unit:n[1]="-0"),e):e}function patchCanonicalUnit(e){return-1===e?-1:u(e)?e:s(e.value)?(e.value=toCanonicalUnit(e.value),e):e}function patchCalcResult(e,n){let t=e;return n?.toCanonicalUnits&&(t=patchCanonicalUnit(t)),t=patchPrecision(t,n?.precision),t=patchMinusZero(t),n?.censorIntoStandardRepresentableValues||(t=patchNaN(t),t=patchInfinity(t)),t}function tokenizeGlobals(e){const n=new Map;if(!e)return n;for(const[t,r]of e)if(b(r))n.set(t,r);else if("string"==typeof r){const e=B({css:r}),u=e.nextToken();if(e.nextToken(),!e.endOfFile())continue;if(!C(u))continue;n.set(t,u);continue}return n}function calc(e,n){return calcFromComponentValues(l(A({css:e}),{}),n).map(e=>e.map(e=>w(e.tokens())).join("")).join(",")}function calcFromComponentValues(e,n){const t=tokenizeGlobals(n?.globals);return replaceComponentValues(e,e=>{if(!u(e))return;const r=_.get(e.getName().toLowerCase());if(!r)return;const a=patchCalcResult(solve(r(e,t,n??{}),n??{}),n);return-1!==a?n?.calcWrapper?calcWrapper(e,[a]):a:void 0})}function replaceComponentValues(n,u){for(let a=0;a{if("number"!=typeof a)return;const i=u(n.node);if(!i)return;const l=[i],c=n.parent.value[a-1];t(c)&&h(c.value)&&("-"===c.value[4].value||"+"===c.value[4].value)&&l.splice(0,0,new o([[v.Whitespace," ",...e(n.node),void 0]]));const s=n.parent.value[a+1];!s||r(s)||t(s)&&(D(s.value)||F(s.value)||E(s.value)||h(s.value)&&"-"!==s.value[4].value&&"+"!==s.value[4].value)||l.push(new o([[v.Whitespace," ",...e(n.node),void 0]])),n.parent.value.splice(a,1,...l)})}return n}const K=new Set(_.keys());export{ParseError,S as ParseErrorMessage,ParseErrorWithComponentValues,calc,calcFromComponentValues,K as mathFunctionNames}; +import{sourceIndices as e,TokenNode as n,isTokenNode as t,isWhiteSpaceOrCommentNode as r,isFunctionNode as u,isSimpleBlockNode as a,FunctionNode as o,WhitespaceNode as i,parseCommaSeparatedListOfComponentValues as l,walk as c}from"@csstools/css-parser-algorithms";import{isTokenDimension as s,TokenType as v,NumberType as f,mutateUnit as p,isTokenNumber as m,isTokenPercentage as d,isTokenIdent as N,isTokenNumeric as C,isTokenOpenParen as g,isTokenDelim as h,isTokenComma as D,isToken as b,tokenizer as B,tokenize as A,stringify as w,isTokenColon as F,isTokenSemicolon as E}from"@csstools/css-tokenizer";class ParseError extends Error{sourceStart;sourceEnd;constructor(e,n,t){super(e),this.name="ParseError",this.sourceStart=n,this.sourceEnd=t}}class ParseErrorWithComponentValues extends ParseError{componentValues;constructor(n,t){super(n,...e(t)),this.componentValues=t}}const S={UnexpectedAdditionOfDimensionOrPercentageWithNumber:"Unexpected addition of a dimension or percentage with a number.",UnexpectedSubtractionOfDimensionOrPercentageWithNumber:"Unexpected subtraction of a dimension or percentage with a number."},y=/[A-Z]/g;function toLowerCaseAZ(e){return e.replace(y,e=>String.fromCharCode(e.charCodeAt(0)+32))}const I={cm:"px",in:"px",mm:"px",pc:"px",pt:"px",px:"px",q:"px",deg:"deg",grad:"deg",rad:"deg",turn:"deg",ms:"s",s:"s",hz:"hz",khz:"hz"},x=new Map([["cm",e=>e],["mm",e=>10*e],["q",e=>40*e],["in",e=>e/2.54],["pc",e=>e/2.54*6],["pt",e=>e/2.54*72],["px",e=>e/2.54*96]]),M=new Map([["deg",e=>e],["grad",e=>e/.9],["rad",e=>e/180*Math.PI],["turn",e=>e/360]]),T=new Map([["deg",e=>.9*e],["grad",e=>e],["rad",e=>.9*e/180*Math.PI],["turn",e=>.9*e/360]]),P=new Map([["hz",e=>e],["khz",e=>e/1e3]]),k=new Map([["cm",e=>2.54*e],["mm",e=>25.4*e],["q",e=>25.4*e*4],["in",e=>e],["pc",e=>6*e],["pt",e=>72*e],["px",e=>96*e]]),W=new Map([["hz",e=>1e3*e],["khz",e=>e]]),O=new Map([["cm",e=>e/10],["mm",e=>e],["q",e=>4*e],["in",e=>e/25.4],["pc",e=>e/25.4*6],["pt",e=>e/25.4*72],["px",e=>e/25.4*96]]),U=new Map([["ms",e=>e],["s",e=>e/1e3]]),L=new Map([["cm",e=>e/6*2.54],["mm",e=>e/6*25.4],["q",e=>e/6*25.4*4],["in",e=>e/6],["pc",e=>e],["pt",e=>e/6*72],["px",e=>e/6*96]]),$=new Map([["cm",e=>e/72*2.54],["mm",e=>e/72*25.4],["q",e=>e/72*25.4*4],["in",e=>e/72],["pc",e=>e/72*6],["pt",e=>e],["px",e=>e/72*96]]),V=new Map([["cm",e=>e/96*2.54],["mm",e=>e/96*25.4],["q",e=>e/96*25.4*4],["in",e=>e/96],["pc",e=>e/96*6],["pt",e=>e/96*72],["px",e=>e]]),Z=new Map([["cm",e=>e/4/10],["mm",e=>e/4],["q",e=>e],["in",e=>e/4/25.4],["pc",e=>e/4/25.4*6],["pt",e=>e/4/25.4*72],["px",e=>e/4/25.4*96]]),z=new Map([["deg",e=>180*e/Math.PI],["grad",e=>180*e/Math.PI/.9],["rad",e=>e],["turn",e=>180*e/Math.PI/360]]),q=new Map([["ms",e=>1e3*e],["s",e=>e]]),R=new Map([["deg",e=>360*e],["grad",e=>360*e/.9],["rad",e=>360*e/180*Math.PI],["turn",e=>e]]),G=new Map([["cm",x],["mm",O],["q",Z],["in",k],["pc",L],["pt",$],["px",V],["ms",U],["s",q],["deg",M],["grad",T],["rad",z],["turn",R],["hz",P],["khz",W]]);function convertUnit(e,n){if(!s(e))return n;if(!s(n))return n;const t=toLowerCaseAZ(e[4].unit),r=toLowerCaseAZ(n[4].unit);if(t===r)return n;const u=G.get(r);if(!u)return n;const a=u.get(t);if(!a)return n;const o=a(n[4].value),i=[v.Dimension,"",n[2],n[3],{...n[4],signCharacter:o<0?"-":void 0,type:Number.isInteger(o)?f.Integer:f.Number,value:o}];return p(i,e[4].unit),i}function toCanonicalUnit(e){if(!s(e))return e;const n=toLowerCaseAZ(e[4].unit),t=I[n];if(n===t)return e;const r=G.get(n);if(!r)return e;const u=r.get(t);if(!u)return e;const a=u(e[4].value),o=[v.Dimension,"",e[2],e[3],{...e[4],signCharacter:a<0?"-":void 0,type:Number.isInteger(a)?f.Integer:f.Number,value:a}];return p(o,t),o}function addition(e,t){if(2!==e.length)return-1;const r=e[0].value;let u=e[1].value;if(m(r)&&m(u)){const e=r[4].value+u[4].value;return new n([v.Number,e.toString(),r[2],u[3],{value:e,type:r[4].type===f.Integer&&u[4].type===f.Integer?f.Integer:f.Number}])}if(d(r)&&d(u)){const e=r[4].value+u[4].value;return new n([v.Percentage,e.toString()+"%",r[2],u[3],{value:e}])}if(s(r)&&s(u)&&(u=convertUnit(r,u),toLowerCaseAZ(r[4].unit)===toLowerCaseAZ(u[4].unit))){const e=r[4].value+u[4].value;return new n([v.Dimension,e.toString()+r[4].unit,r[2],u[3],{value:e,type:r[4].type===f.Integer&&u[4].type===f.Integer?f.Integer:f.Number,unit:r[4].unit}])}return(m(r)&&(s(u)||d(u))||m(u)&&(s(r)||d(r)))&&t.onParseError?.(new ParseErrorWithComponentValues(S.UnexpectedAdditionOfDimensionOrPercentageWithNumber,e)),-1}function division(e){if(2!==e.length)return-1;const t=e[0].value,r=e[1].value;if(m(t)&&m(r)){const e=t[4].value/r[4].value;return new n([v.Number,e.toString(),t[2],r[3],{value:e,type:Number.isInteger(e)?f.Integer:f.Number}])}if(d(t)&&m(r)){const e=t[4].value/r[4].value;return new n([v.Percentage,e.toString()+"%",t[2],r[3],{value:e}])}if(s(t)&&m(r)){const e=t[4].value/r[4].value;return new n([v.Dimension,e.toString()+t[4].unit,t[2],r[3],{value:e,type:Number.isInteger(e)?f.Integer:f.Number,unit:t[4].unit}])}return-1}function isCalculation(e){return!!e&&"object"==typeof e&&"inputs"in e&&Array.isArray(e.inputs)&&"operation"in e}function solve(e,n){if(-1===e)return-1;const r=[{calculation:e,inputs:[],index:0}];let u=-1;for(;r.length;){const e=r[r.length-1];if(e.indexconvertUnit(u,e.value));if(!arrayOfSameNumeric(a))return-1;if(a.some(e=>Number.isNaN(e[4].value)))return resultToCalculation(e,u,Number.NaN);if(!r.rawPercentages&&d(u))return-1;const o=a.map(e=>e[4].value);if(o.some(Number.isNaN))return resultToCalculation(e,u,Number.NaN);const i=Math.hypot(...o);return resultToCalculation(e,u,i)}function solveMax(e,n,r){if(!n.every(t))return-1;const u=n[0].value;if(!C(u))return-1;const a=n.map(e=>convertUnit(u,e.value));if(!arrayOfSameNumeric(a))return-1;if(a.some(e=>Number.isNaN(e[4].value)))return resultToCalculation(e,u,Number.NaN);if(!r.rawPercentages&&d(u))return-1;const o=a.map(e=>e[4].value),i=Math.max(...o);return resultToCalculation(e,u,i)}function solveMin(e,n,r){if(!n.every(t))return-1;const u=n[0].value;if(!C(u))return-1;const a=n.map(e=>convertUnit(u,e.value));if(!arrayOfSameNumeric(a))return-1;if(a.some(e=>Number.isNaN(e[4].value)))return resultToCalculation(e,u,Number.NaN);if(!r.rawPercentages&&d(u))return-1;const o=a.map(e=>e[4].value),i=Math.min(...o);return resultToCalculation(e,u,i)}function solveMod(e,n,t){const r=n.value;if(!C(r))return-1;const u=convertUnit(r,t.value);if(!twoOfSameNumeric(r,u))return-1;let a;return a=Number.isNaN(r[4].value)||Number.isNaN(u[4].value)||0===u[4].value?Number.NaN:Number.isFinite(r[4].value)&&(Number.isFinite(u[4].value)||(u[4].value!==Number.POSITIVE_INFINITY||r[4].value!==Number.NEGATIVE_INFINITY&&!Object.is(0*r[4].value,-0))&&(u[4].value!==Number.NEGATIVE_INFINITY||r[4].value!==Number.POSITIVE_INFINITY&&!Object.is(0*r[4].value,0)))?Number.isFinite(u[4].value)?(r[4].value%u[4].value+u[4].value)%u[4].value:r[4].value:Number.NaN,resultToCalculation(e,r,a)}function solvePow(e,n,t){const r=n.value,u=t.value;if(!m(r))return-1;if(!twoOfSameNumeric(r,u))return-1;if(Number.isNaN(r[4].value)||Number.isNaN(u[4].value))return numberToCalculation(e,Number.NaN);return numberToCalculation(e,Math.pow(r[4].value,u[4].value))}function solveRem(e,n,t){const r=n.value;if(!C(r))return-1;const u=convertUnit(r,t.value);if(!twoOfSameNumeric(r,u))return-1;let a;return a=Number.isNaN(r[4].value)||Number.isNaN(u[4].value)||0===u[4].value?Number.NaN:Number.isFinite(r[4].value)?Number.isFinite(u[4].value)?r[4].value%u[4].value:r[4].value:Number.NaN,resultToCalculation(e,r,a)}function snapAsBorderWidth(e,n,t){if(!s(n))return-1;const r=t.devicePixelLength??1,u=[v.Dimension,`${r}px`,n[2],n[3],{value:r,type:f.Integer,unit:"px"}],a=convertUnit(u,n);if(!twoOfSameNumeric(a,u))return-1;if(Number.isInteger(a[4].value/r))return resultToCalculation(e,n,n[4].value);if(a[4].value>0){if(a[4].value0?1/0:Object.is(0,0*a[4].value)?0:-0;break;default:i=Object.is(0,0*a[4].value)?0:-0}else switch(n){case"down":i=(o[4].value>0?Math.floor:Math.ceil)(a[4].value/o[4].value)*o[4].value;break;case"up":i=(o[4].value>0?Math.ceil:Math.floor)(a[4].value/o[4].value)*o[4].value;break;case"to-zero":i=Math.trunc(a[4].value/o[4].value)*o[4].value;break;default:{let t=Math.floor(a[4].value/o[4].value)*o[4].value,r=Math.ceil(a[4].value/o[4].value)*o[4].value;if(t>r){const e=t;t=r,r=e}const l=Math.abs(a[4].value-t),c=Math.abs(a[4].value-r);if(i="line-width"!==n||0!==r&&0!==t?l===c?r:l!r(e));return 1===n.length&&isNone(n[0])}if(!t(e))return!1;const n=e.value;return!!N(n)&&j.test(n[4].value)}function patchPrecision(e,n=13){if(-1===e)return-1;if(n<=0)return e;if(u(e))return e;const t=e.value;if(!C(t))return e;if(shouldSkipPrecisionRounding(t[4].value))return e;const r=roundToPrecision(t[4].value,n).toString();return m(t)?t[1]=r:d(t)?t[1]=r+"%":s(t)&&(t[1]=r+t[4].unit),e}function shouldSkipPrecisionRounding(e){if(!Number.isFinite(e)||0===e)return!0;if(Number.isInteger(e))return!0;const n=e.toString();return n.includes("e")||n.includes("E")}function roundToPrecision(e,n=13){return shouldSkipPrecisionRounding(e)?e:Number(e.toFixed(n))}const Y=String.fromCodePoint(0);function solveRandom(e,n,t,r,u,a){if(-1===n.fixed&&!a.randomCaching)return-1;const o=t.value;if(!C(o))return-1;const i=convertUnit(o,r.value);if(!twoOfSameNumeric(o,i))return-1;let l=null;if(u&&(l=convertUnit(o,u.value),!twoOfSameNumeric(o,l)))return-1;if(Number.isNaN(o[4].value)||Number.isNaN(i[4].value))return resultToCalculation(e,o,Number.NaN);if(!Number.isFinite(o[4].value))return resultToCalculation(e,o,o[4].value);if(!Number.isFinite(i[4].value)||!Number.isFinite(i[4].value-o[4].value))return resultToCalculation(e,o,Number.NaN);if(l){if(Number.isNaN(l[4].value))return resultToCalculation(e,o,Number.NaN);if(!Number.isFinite(l[4].value))return resultToCalculation(e,o,o[4].value)}const c=-1!==n.fixed?()=>n.fixed:(a.randomCaching.propertyN||(a.randomCaching.propertyN=0),sfc32(crc32([n.dashedIdent?n.dashedIdent:"",n.elementScoped?a.randomCaching.elementID:"",n.propertyScoped||n.propertyIndexScoped?a.randomCaching.propertyName:"",n.propertyIndexScoped?a.randomCaching.propertyN:"",a.randomCaching.documentID].join(Y)))),s=o[4].value;let v=i[4].value;if(v1e10)&&(l=null),l){const n=l[4].value,t=Math.max(n/1e3,1e-9);let r=Math.floor((v-s)/n);Math.abs(s+r*n-v)>=t&&Math.abs(s+(r+1)*n-v){const u=((e|=0)+(n|=0)|0)+(r|=0)|0;return r=r+1|0,e=n^n>>>9,n=(t|=0)+(t<<3)|0,t=(t=t<<21|t>>>11)+u|0,(u>>>0)/4294967296}}const _=(()=>{const e=new Uint32Array(256);for(let n=0;n<256;n++)e[n]=Number("0x"+"00000000 77073096 EE0E612C 990951BA 076DC419 706AF48F E963A535 9E6495A3 0EDB8832 79DCB8A4 E0D5E91E 97D2D988 09B64C2B 7EB17CBD E7B82D07 90BF1D91 1DB71064 6AB020F2 F3B97148 84BE41DE 1ADAD47D 6DDDE4EB F4D4B551 83D385C7 136C9856 646BA8C0 FD62F97A 8A65C9EC 14015C4F 63066CD9 FA0F3D63 8D080DF5 3B6E20C8 4C69105E D56041E4 A2677172 3C03E4D1 4B04D447 D20D85FD A50AB56B 35B5A8FA 42B2986C DBBBC9D6 ACBCF940 32D86CE3 45DF5C75 DCD60DCF ABD13D59 26D930AC 51DE003A C8D75180 BFD06116 21B4F4B5 56B3C423 CFBA9599 B8BDA50F 2802B89E 5F058808 C60CD9B2 B10BE924 2F6F7C87 58684C11 C1611DAB B6662D3D 76DC4190 01DB7106 98D220BC EFD5102A 71B18589 06B6B51F 9FBFE4A5 E8B8D433 7807C9A2 0F00F934 9609A88E E10E9818 7F6A0DBB 086D3D2D 91646C97 E6635C01 6B6B51F4 1C6C6162 856530D8 F262004E 6C0695ED 1B01A57B 8208F4C1 F50FC457 65B0D9C6 12B7E950 8BBEB8EA FCB9887C 62DD1DDF 15DA2D49 8CD37CF3 FBD44C65 4DB26158 3AB551CE A3BC0074 D4BB30E2 4ADFA541 3DD895D7 A4D1C46D D3D6F4FB 4369E96A 346ED9FC AD678846 DA60B8D0 44042D73 33031DE5 AA0A4C5F DD0D7CC9 5005713C 270241AA BE0B1010 C90C2086 5768B525 206F85B3 B966D409 CE61E49F 5EDEF90E 29D9C998 B0D09822 C7D7A8B4 59B33D17 2EB40D81 B7BD5C3B C0BA6CAD EDB88320 9ABFB3B6 03B6E20C 74B1D29A EAD54739 9DD277AF 04DB2615 73DC1683 E3630B12 94643B84 0D6D6A3E 7A6A5AA8 E40ECF0B 9309FF9D 0A00AE27 7D079EB1 F00F9344 8708A3D2 1E01F268 6906C2FE F762575D 806567CB 196C3671 6E6B06E7 FED41B76 89D32BE0 10DA7A5A 67DD4ACC F9B9DF6F 8EBEEFF9 17B7BE43 60B08ED5 D6D6A3E8 A1D1937E 38D8C2C4 4FDFF252 D1BB67F1 A6BC5767 3FB506DD 48B2364B D80D2BDA AF0A1B4C 36034AF6 41047A60 DF60EFC3 A867DF55 316E8EEF 4669BE79 CB61B38C BC66831A 256FD2A0 5268E236 CC0C7795 BB0B4703 220216B9 5505262F C5BA3BBE B2BD0B28 2BB45A92 5CB36A04 C2D7FFA7 B5D0CF31 2CD99E8B 5BDEAE1D 9B64C2B0 EC63F226 756AA39C 026D930A 9C0906A9 EB0E363F 72076785 05005713 95BF4A82 E2B87A14 7BB12BAE 0CB61B38 92D28E9B E5D5BE0D 7CDCEFB7 0BDBDF21 86D3D2D4 F1D4E242 68DDB3F8 1FDA836E 81BE16CD F6B9265B 6FB077E1 18B74777 88085AE6 FF0F6A70 66063BCA 11010B5C 8F659EFF F862AE69 616BFFD3 166CCF45 A00AE278 D70DD2EE 4E048354 3903B3C2 A7672661 D06016F7 4969474D 3E6E77DB AED16A4A D9D65ADC 40DF0B66 37D83BF0 A9BCAE53 DEBB9EC5 47B2CF7F 30B5FFE9 BDBDF21C CABAC28A 53B39330 24B4A3A6 BAD03605 CDD70693 54DE5729 23D967BF B3667A2E C4614AB8 5D681B02 2A6F2B94 B40BBE37 C30C8EA1 5A05DF1B 2D02EF8D".substring(9*n,9*n+8));return e})();function crc32(e){let n,t,r=0;r^=-1;for(let u=0,a=e.length;u>>8^n;return(-1^r)>>>0}function solveCalcMix(e,n){const t=n[0].calcSum.value;if(!C(t))return-1;const r=n.map(e=>({calcSum:convertUnit(t,e.calcSum.value),percentage:e.percentage}));if(!arrayOfSameNumeric(r.map(e=>e.calcSum)))return-1;const{items:u}=normalizeMixPercentages(r,!1),a=u.map(e=>e.calcSum[4].value*e.percentage/100);let o=0;for(let e=0;e100||a>0&&n)for(const e of u)e.percentage=e.percentage*(100/a);let o=0;return a<100&&(o=100-a),{items:u,leftover:o}}const H=new Map([["abs",function abs(e,n,t){return singleNodeSolver(e,n,t,solveAbs)}],["acos",function acos(e,n,t){return singleNodeSolver(e,n,t,solveACos)}],["asin",function asin(e,n,t){return singleNodeSolver(e,n,t,solveASin)}],["atan",function atan(e,n,t){return singleNodeSolver(e,n,t,solveATan)}],["atan2",function atan2(e,n,t){return twoCommaSeparatedNodesSolver(e,n,t,solveATan2)}],["calc",calc$1],["calc-mix",function calcMix(e,n,t){const r=variadicArgumentsCalcMix(e,e.value,n,t);if(-1===r)return-1;return solveCalcMix(e,r)}],["clamp",function clamp(u,a,i){const l=resolveGlobalsAndConstants([...u.value.filter(e=>!r(e))],a),c=[],s=[],f=[];{let e=c;for(let n=0;n!r(e)),n,t);if(-1===u)return-1;const[a,o]=u,i=variadicArguments(e,o,n,t);if(-1===i)return-1;const[l,c,s]=i;if(!l||!c)return-1;return solveRandom(e,a,l,c,s,t)}],["rem",function rem(e,n,t){return twoCommaSeparatedNodesSolver(e,n,t,solveRem)}],["round",function round(e,u,a){const o=resolveGlobalsAndConstants([...e.value.filter(e=>!r(e))],u);let i="",l=!1;const c=[],p=[];{let e=c;for(let n=0;n!r(e))],n);if(i.length>J)throw new Error(`Maximum number of nodes in a math function (${J}) exceeded, reduce the complexity of your expression`);if(1===i.length&&t(i[0]))return{inputs:[i[0]],operation:unary};let l=0;for(;l!r(e))],n),a=solve(calc$1(calcWrapper(e,u),n,t),t);return-1===a?-1:a}function twoCommaSeparatedNodesSolver(e,n,t,r){const u=twoCommaSeparatedArguments(e,n,t);if(-1===u)return-1;const[a,o]=u;return r(e,a,o,t)}function twoCommaSeparatedArguments(e,n,u){const a=resolveGlobalsAndConstants([...e.value.filter(e=>!r(e))],n),o=[],i=[];{let e=o;for(let n=0;n!r(e))],u),i=[];{const n=[];let r=[];for(let e=0;e!r(e))],u),i=[];{const n=[];let l=[];for(let e=0;e=0;i--){if(r(n[o][i]))continue;if(l=solve(calc$1(calcWrapper(e,n[o].slice(0,i+1)),u,a),a),-1===l)continue;const s=n[o].slice(i+1).filter(e=>!r(e));if(s.length)if(1===s.length&&t(s[0])&&d(s[0].value)){if(c=s[0].value[4].value,c<0||c>100)return-1}else{const n=solve(calc$1(calcWrapper(e,s),u,a),a);if(-1===n)return-1;if(!d(n.value))return-1;c=n.value[4].value,c=Math.min(100,Math.max(0,c))}break}if(-1===l)return-1;i.push({calcSum:l,percentage:c})}}return i}const K=new Set(["nearest","line-width","up","down","to-zero"]);function parseRandomValueSharing(e,n,r,u){const a={dashedIdent:"",fixed:-1,elementScoped:!1,propertyScoped:!1,propertyIndexScoped:!1};let o=!1;const i=n[0];if(!t(i)||!N(i.value))return[a,n];for(let i=0;i1)return-1;a.fixed=Math.max(0,Math.min(l.value[4].value,1-Number.EPSILON/2));continue}if("auto"!==c){if(!c.startsWith("--"))return 0===i?[a,n]:-1;if(-1!==a.fixed||o||a.dashedIdent)return-1;a.dashedIdent=c}else{if(-1!==a.fixed||o||a.dashedIdent||a.elementScoped||a.propertyScoped||a.propertyIndexScoped)return-1;a.elementScoped=!0,a.propertyIndexScoped=!0,o=!0}}else{if(-1!==a.fixed||o||a.propertyScoped||a.propertyIndexScoped)return-1;a.propertyIndexScoped=!0}else{if(-1!==a.fixed||o||a.propertyScoped||a.propertyIndexScoped)return-1;a.propertyScoped=!0}else{if(-1!==a.fixed||o||a.elementScoped)return-1;a.elementScoped=!0}}return-1}function calcWrapper(e,n){return new o([v.Function,"calc(",e.name[2],e.name[3],{value:"calc"}],[v.CloseParen,")",e.endToken[2],e.endToken[3],void 0],n)}function maxWrapper(t,r,u){return new o([v.Function,"max(",t.name[2],t.name[3],{value:"max"}],[v.CloseParen,")",t.endToken[2],t.endToken[3],void 0],[r,new n([v.Comma,",",...e(r),void 0]),u])}function patchNaN(e){if(-1===e)return-1;if(u(e))return e;const t=e.value;return C(t)&&Number.isNaN(t[4].value)?m(t)?new o([v.Function,"calc(",t[2],t[3],{value:"calc"}],[v.CloseParen,")",t[2],t[3],void 0],[new n([v.Ident,"NaN",t[2],t[3],{value:"NaN"}])]):s(t)?new o([v.Function,"calc(",t[2],t[3],{value:"calc"}],[v.CloseParen,")",t[2],t[3],void 0],[new n([v.Ident,"NaN",t[2],t[3],{value:"NaN"}]),new i([[v.Whitespace," ",t[2],t[3],void 0]]),new n([v.Delim,"*",t[2],t[3],{value:"*"}]),new i([[v.Whitespace," ",t[2],t[3],void 0]]),new n([v.Dimension,"1"+t[4].unit,t[2],t[3],{value:1,type:f.Integer,unit:t[4].unit}])]):d(t)?new o([v.Function,"calc(",t[2],t[3],{value:"calc"}],[v.CloseParen,")",t[2],t[3],void 0],[new n([v.Ident,"NaN",t[2],t[3],{value:"NaN"}]),new i([[v.Whitespace," ",t[2],t[3],void 0]]),new n([v.Delim,"*",t[2],t[3],{value:"*"}]),new i([[v.Whitespace," ",t[2],t[3],void 0]]),new n([v.Percentage,"1%",t[2],t[3],{value:1}])]):-1:e}function patchInfinity(e){if(-1===e)return-1;if(u(e))return e;const t=e.value;if(!C(t))return e;if(Number.isFinite(t[4].value)||Number.isNaN(t[4].value))return e;let r="";return Number.NEGATIVE_INFINITY===t[4].value&&(r="-"),m(t)?new o([v.Function,"calc(",t[2],t[3],{value:"calc"}],[v.CloseParen,")",t[2],t[3],void 0],[new n([v.Ident,r+"infinity",t[2],t[3],{value:r+"infinity"}])]):s(t)?new o([v.Function,"calc(",t[2],t[3],{value:"calc"}],[v.CloseParen,")",t[2],t[3],void 0],[new n([v.Ident,r+"infinity",t[2],t[3],{value:r+"infinity"}]),new i([[v.Whitespace," ",t[2],t[3],void 0]]),new n([v.Delim,"*",t[2],t[3],{value:"*"}]),new i([[v.Whitespace," ",t[2],t[3],void 0]]),new n([v.Dimension,"1"+t[4].unit,t[2],t[3],{value:1,type:f.Integer,unit:t[4].unit}])]):new o([v.Function,"calc(",t[2],t[3],{value:"calc"}],[v.CloseParen,")",t[2],t[3],void 0],[new n([v.Ident,r+"infinity",t[2],t[3],{value:r+"infinity"}]),new i([[v.Whitespace," ",t[2],t[3],void 0]]),new n([v.Delim,"*",t[2],t[3],{value:"*"}]),new i([[v.Whitespace," ",t[2],t[3],void 0]]),new n([v.Percentage,"1%",t[2],t[3],{value:1}])])}function patchMinusZero(e){if(-1===e)return-1;if(u(e))return e;const n=e.value;return C(n)&&Object.is(-0,n[4].value)?("-0"===n[1]||(d(n)?n[1]="-0%":s(n)?n[1]="-0"+n[4].unit:n[1]="-0"),e):e}function patchCanonicalUnit(e){return-1===e?-1:u(e)?e:s(e.value)?(e.value=toCanonicalUnit(e.value),e):e}function patchCalcResult(e,n){let t=e;return n?.toCanonicalUnits&&(t=patchCanonicalUnit(t)),t=patchPrecision(t,n?.precision),t=patchMinusZero(t),n?.censorIntoStandardRepresentableValues||(t=patchNaN(t),t=patchInfinity(t)),t}function tokenizeGlobals(e){const n=new Map;if(!e)return n;for(const[t,r]of e)if(b(r))n.set(t,r);else if("string"==typeof r){const e=B({css:r}),u=e.nextToken();if(e.nextToken(),!e.endOfFile())continue;if(!C(u))continue;n.set(t,u);continue}return n}function calc(e,n){return calcFromComponentValues(l(A({css:e}),{}),n).map(e=>e.map(e=>w(e.tokens())).join("")).join(",")}function calcFromComponentValues(e,n){const t=tokenizeGlobals(n?.globals);return replaceComponentValues(e,e=>{if(!u(e))return;const r=H.get(e.getName().toLowerCase());if(!r)return;const a=patchCalcResult(solve(r(e,t,n??{}),n??{}),n);return-1!==a?n?.calcWrapper?calcWrapper(e,[a]):a:void 0})}function replaceComponentValues(n,u){for(let a=0;a{if("number"!=typeof a)return;const o=u(n.node);if(!o)return;const l=[o],c=n.parent.value[a-1];t(c)&&h(c.value)&&("-"===c.value[4].value||"+"===c.value[4].value)&&l.splice(0,0,new i([[v.Whitespace," ",...e(n.node),void 0]]));const s=n.parent.value[a+1];!s||r(s)||t(s)&&(D(s.value)||F(s.value)||E(s.value)||h(s.value)&&"-"!==s.value[4].value&&"+"!==s.value[4].value)||l.push(new i([[v.Whitespace," ",...e(n.node),void 0]])),n.parent.value.splice(a,1,...l)})}return n}const Q=new Set(H.keys());export{ParseError,S as ParseErrorMessage,ParseErrorWithComponentValues,calc,calcFromComponentValues,Q as mathFunctionNames}; diff --git a/packages/css-calc/src/functions/random.ts b/packages/css-calc/src/functions/random.ts index e65d922fb6..0d2be6a740 100644 --- a/packages/css-calc/src/functions/random.ts +++ b/packages/css-calc/src/functions/random.ts @@ -170,8 +170,20 @@ function sfc32(a: number = 0.34944106645296036, b: number = 0.19228640875738723, }; } +// The lookup table is parsed once instead of re-parsing a hex substring for +// every single character of the input. +const CRC32_TABLE: Uint32Array = ((): Uint32Array => { + const hex = "00000000 77073096 EE0E612C 990951BA 076DC419 706AF48F E963A535 9E6495A3 0EDB8832 79DCB8A4 E0D5E91E 97D2D988 09B64C2B 7EB17CBD E7B82D07 90BF1D91 1DB71064 6AB020F2 F3B97148 84BE41DE 1ADAD47D 6DDDE4EB F4D4B551 83D385C7 136C9856 646BA8C0 FD62F97A 8A65C9EC 14015C4F 63066CD9 FA0F3D63 8D080DF5 3B6E20C8 4C69105E D56041E4 A2677172 3C03E4D1 4B04D447 D20D85FD A50AB56B 35B5A8FA 42B2986C DBBBC9D6 ACBCF940 32D86CE3 45DF5C75 DCD60DCF ABD13D59 26D930AC 51DE003A C8D75180 BFD06116 21B4F4B5 56B3C423 CFBA9599 B8BDA50F 2802B89E 5F058808 C60CD9B2 B10BE924 2F6F7C87 58684C11 C1611DAB B6662D3D 76DC4190 01DB7106 98D220BC EFD5102A 71B18589 06B6B51F 9FBFE4A5 E8B8D433 7807C9A2 0F00F934 9609A88E E10E9818 7F6A0DBB 086D3D2D 91646C97 E6635C01 6B6B51F4 1C6C6162 856530D8 F262004E 6C0695ED 1B01A57B 8208F4C1 F50FC457 65B0D9C6 12B7E950 8BBEB8EA FCB9887C 62DD1DDF 15DA2D49 8CD37CF3 FBD44C65 4DB26158 3AB551CE A3BC0074 D4BB30E2 4ADFA541 3DD895D7 A4D1C46D D3D6F4FB 4369E96A 346ED9FC AD678846 DA60B8D0 44042D73 33031DE5 AA0A4C5F DD0D7CC9 5005713C 270241AA BE0B1010 C90C2086 5768B525 206F85B3 B966D409 CE61E49F 5EDEF90E 29D9C998 B0D09822 C7D7A8B4 59B33D17 2EB40D81 B7BD5C3B C0BA6CAD EDB88320 9ABFB3B6 03B6E20C 74B1D29A EAD54739 9DD277AF 04DB2615 73DC1683 E3630B12 94643B84 0D6D6A3E 7A6A5AA8 E40ECF0B 9309FF9D 0A00AE27 7D079EB1 F00F9344 8708A3D2 1E01F268 6906C2FE F762575D 806567CB 196C3671 6E6B06E7 FED41B76 89D32BE0 10DA7A5A 67DD4ACC F9B9DF6F 8EBEEFF9 17B7BE43 60B08ED5 D6D6A3E8 A1D1937E 38D8C2C4 4FDFF252 D1BB67F1 A6BC5767 3FB506DD 48B2364B D80D2BDA AF0A1B4C 36034AF6 41047A60 DF60EFC3 A867DF55 316E8EEF 4669BE79 CB61B38C BC66831A 256FD2A0 5268E236 CC0C7795 BB0B4703 220216B9 5505262F C5BA3BBE B2BD0B28 2BB45A92 5CB36A04 C2D7FFA7 B5D0CF31 2CD99E8B 5BDEAE1D 9B64C2B0 EC63F226 756AA39C 026D930A 9C0906A9 EB0E363F 72076785 05005713 95BF4A82 E2B87A14 7BB12BAE 0CB61B38 92D28E9B E5D5BE0D 7CDCEFB7 0BDBDF21 86D3D2D4 F1D4E242 68DDB3F8 1FDA836E 81BE16CD F6B9265B 6FB077E1 18B74777 88085AE6 FF0F6A70 66063BCA 11010B5C 8F659EFF F862AE69 616BFFD3 166CCF45 A00AE278 D70DD2EE 4E048354 3903B3C2 A7672661 D06016F7 4969474D 3E6E77DB AED16A4A D9D65ADC 40DF0B66 37D83BF0 A9BCAE53 DEBB9EC5 47B2CF7F 30B5FFE9 BDBDF21C CABAC28A 53B39330 24B4A3A6 BAD03605 CDD70693 54DE5729 23D967BF B3667A2E C4614AB8 5D681B02 2A6F2B94 B40BBE37 C30C8EA1 5A05DF1B 2D02EF8D"; + + const table = new Uint32Array(256); + for (let i = 0; i < 256; i++) { + table[i] = Number("0x" + hex.substring((i * 9), (i * 9) + 8)); + } + + return table; +})(); + function crc32(str: string): number { - const table = "00000000 77073096 EE0E612C 990951BA 076DC419 706AF48F E963A535 9E6495A3 0EDB8832 79DCB8A4 E0D5E91E 97D2D988 09B64C2B 7EB17CBD E7B82D07 90BF1D91 1DB71064 6AB020F2 F3B97148 84BE41DE 1ADAD47D 6DDDE4EB F4D4B551 83D385C7 136C9856 646BA8C0 FD62F97A 8A65C9EC 14015C4F 63066CD9 FA0F3D63 8D080DF5 3B6E20C8 4C69105E D56041E4 A2677172 3C03E4D1 4B04D447 D20D85FD A50AB56B 35B5A8FA 42B2986C DBBBC9D6 ACBCF940 32D86CE3 45DF5C75 DCD60DCF ABD13D59 26D930AC 51DE003A C8D75180 BFD06116 21B4F4B5 56B3C423 CFBA9599 B8BDA50F 2802B89E 5F058808 C60CD9B2 B10BE924 2F6F7C87 58684C11 C1611DAB B6662D3D 76DC4190 01DB7106 98D220BC EFD5102A 71B18589 06B6B51F 9FBFE4A5 E8B8D433 7807C9A2 0F00F934 9609A88E E10E9818 7F6A0DBB 086D3D2D 91646C97 E6635C01 6B6B51F4 1C6C6162 856530D8 F262004E 6C0695ED 1B01A57B 8208F4C1 F50FC457 65B0D9C6 12B7E950 8BBEB8EA FCB9887C 62DD1DDF 15DA2D49 8CD37CF3 FBD44C65 4DB26158 3AB551CE A3BC0074 D4BB30E2 4ADFA541 3DD895D7 A4D1C46D D3D6F4FB 4369E96A 346ED9FC AD678846 DA60B8D0 44042D73 33031DE5 AA0A4C5F DD0D7CC9 5005713C 270241AA BE0B1010 C90C2086 5768B525 206F85B3 B966D409 CE61E49F 5EDEF90E 29D9C998 B0D09822 C7D7A8B4 59B33D17 2EB40D81 B7BD5C3B C0BA6CAD EDB88320 9ABFB3B6 03B6E20C 74B1D29A EAD54739 9DD277AF 04DB2615 73DC1683 E3630B12 94643B84 0D6D6A3E 7A6A5AA8 E40ECF0B 9309FF9D 0A00AE27 7D079EB1 F00F9344 8708A3D2 1E01F268 6906C2FE F762575D 806567CB 196C3671 6E6B06E7 FED41B76 89D32BE0 10DA7A5A 67DD4ACC F9B9DF6F 8EBEEFF9 17B7BE43 60B08ED5 D6D6A3E8 A1D1937E 38D8C2C4 4FDFF252 D1BB67F1 A6BC5767 3FB506DD 48B2364B D80D2BDA AF0A1B4C 36034AF6 41047A60 DF60EFC3 A867DF55 316E8EEF 4669BE79 CB61B38C BC66831A 256FD2A0 5268E236 CC0C7795 BB0B4703 220216B9 5505262F C5BA3BBE B2BD0B28 2BB45A92 5CB36A04 C2D7FFA7 B5D0CF31 2CD99E8B 5BDEAE1D 9B64C2B0 EC63F226 756AA39C 026D930A 9C0906A9 EB0E363F 72076785 05005713 95BF4A82 E2B87A14 7BB12BAE 0CB61B38 92D28E9B E5D5BE0D 7CDCEFB7 0BDBDF21 86D3D2D4 F1D4E242 68DDB3F8 1FDA836E 81BE16CD F6B9265B 6FB077E1 18B74777 88085AE6 FF0F6A70 66063BCA 11010B5C 8F659EFF F862AE69 616BFFD3 166CCF45 A00AE278 D70DD2EE 4E048354 3903B3C2 A7672661 D06016F7 4969474D 3E6E77DB AED16A4A D9D65ADC 40DF0B66 37D83BF0 A9BCAE53 DEBB9EC5 47B2CF7F 30B5FFE9 BDBDF21C CABAC28A 53B39330 24B4A3A6 BAD03605 CDD70693 54DE5729 23D967BF B3667A2E C4614AB8 5D681B02 2A6F2B94 B40BBE37 C30C8EA1 5A05DF1B 2D02EF8D"; let crc = 0; let x: number; let y: number; @@ -179,7 +191,7 @@ function crc32(str: string): number { crc = crc ^ (-1); for (let i = 0, iTop = str.length; i < iTop; i++) { y = (crc ^ str.charCodeAt(i)) & 0xFF; - x = Number("0x" + table.substring((y * 9), (y * 9) + 8)); + x = CRC32_TABLE[y]; crc = (crc >>> 8) ^ x; } diff --git a/plugins/postcss-attribute-case-insensitive/CHANGELOG.md b/plugins/postcss-attribute-case-insensitive/CHANGELOG.md index 46851018ae..0379f4c8cd 100644 --- a/plugins/postcss-attribute-case-insensitive/CHANGELOG.md +++ b/plugins/postcss-attribute-case-insensitive/CHANGELOG.md @@ -1,5 +1,10 @@ # Changes to PostCSS Attribute Case Insensitive +### Unreleased (patch) + +- Bound the combined number of selector variants across all case insensitive attributes. + + ### 8.0.1 _September 25, 2026_ diff --git a/plugins/postcss-attribute-case-insensitive/dist/index.mjs b/plugins/postcss-attribute-case-insensitive/dist/index.mjs index e2a91930cf..ee2d52becd 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??!1)}function selectorHasInsensitiveAttribute(e){return e.some(nodeIsInsensitiveAttribute)}function transformString(e,t,s){const r=s.charAt(t);if(""===r)return e;const n=r.toLocaleUpperCase(),o=n!==r;if(e.length*(o?2:1)>1e4)throw new Error("Too many selector variants when trying to resolve case insensitive attributes, reduce the complexity of your selectors");let c=e.map(e=>e+r);return o&&(c=c.concat(e.map(e=>e+n))),transformString(c,t+1,s)}function createNewSelectors(t){let s=[e.selector({value:"",nodes:[]})];return t.each(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}const creator=t=>{const s=Object.assign({preserve:!1},t);return{postcssPlugin:"postcss-attribute-case-insensitive",prepare(){const t=new WeakSet;return{postcssPlugin:"postcss-attribute-case-insensitive",Rule(r,{result:n}){if(t.has(r))return;if(!/i\s*\]/i.test(r.selector))return;let o;try{o=e(e=>{let t=[];e.each(e=>{selectorHasInsensitiveAttribute(e)&&(t=t.concat(createNewSelectors(e)),e.remove())}),t.length&&t.forEach(t=>e.append(t))}).processSync(r.selector)}catch(e){return void r.warn(n,`Failed to parse selector : "${r.selector}" with message: "${e instanceof Error?e.message:e}"`)}o!==r.selector&&(t.add(r),r.cloneBefore({selector:o}),s.preserve||r.remove())}}}}};creator.postcss=!0;export{creator as default,creator as"module.exports"}; +import e from"postcss-selector-parser";function nodeIsInsensitiveAttribute(e){return"attribute"===e.type&&(e.insensitive??!1)}function selectorHasInsensitiveAttribute(e){return e.some(nodeIsInsensitiveAttribute)}const t=1e4;function transformString(e,s,r){const n=r.charAt(s);if(""===n)return e;const o=n.toLocaleUpperCase(),c=o!==n;if(e.length*(c?2:1)>t)throw new Error("Too many selector variants when trying to resolve case insensitive attributes, reduce the complexity of your selectors");let i=e.map(e=>e+n);return c&&(i=i.concat(e.map(e=>e+o))),transformString(i,s+1,r)}function createNewSelectors(s){let r=[e.selector({value:"",nodes:[]})];return s.each(e=>{if(!nodeIsInsensitiveAttribute(e))return void r.forEach(t=>{t.append(e.clone())});const s=transformString([""],0,(n=e).value??"").map(e=>{const t=n.clone({spaces:{after:n.spaces.after,before:n.spaces.before},insensitive:!1});return t.setValue(e),t});var n;const o=[];if(s.length*r.length>t)throw new Error("Too many selector variants when trying to resolve case insensitive attributes, reduce the complexity of your selectors");s.forEach(e=>{r.forEach(t=>{const s=t.clone({});s.append(e),o.push(s)})}),r=o}),r}const creator=s=>{const r=Object.assign({preserve:!1},s);return{postcssPlugin:"postcss-attribute-case-insensitive",prepare(){const s=new WeakSet;return{postcssPlugin:"postcss-attribute-case-insensitive",Rule(n,{result:o}){if(s.has(n))return;if(!/i\s*\]/i.test(n.selector))return;let c;try{c=e(e=>{let s=[];e.each(e=>{if(selectorHasInsensitiveAttribute(e)){const r=createNewSelectors(e);if(s.length+r.length>t)throw new Error("Too many selector variants when trying to resolve case insensitive attributes, reduce the complexity of your selectors");s=s.concat(r),e.remove()}}),s.length&&s.forEach(t=>e.append(t))}).processSync(n.selector)}catch(e){return void n.warn(o,`Failed to parse selector : "${n.selector}" with message: "${e instanceof Error?e.message:e}"`)}c!==n.selector&&(s.add(n),n.cloneBefore({selector:c}),r.preserve||n.remove())}}}}};creator.postcss=!0;export{creator as default,creator as"module.exports"}; diff --git a/plugins/postcss-attribute-case-insensitive/src/index.ts b/plugins/postcss-attribute-case-insensitive/src/index.ts index 617e6a9071..0f41b00f73 100644 --- a/plugins/postcss-attribute-case-insensitive/src/index.ts +++ b/plugins/postcss-attribute-case-insensitive/src/index.ts @@ -66,6 +66,13 @@ function createNewSelectors(selector: selectorParser.Selector): Array = []; + // The per-attribute cap only bounds a single attribute. The number of + // generated selectors is the product across all insensitive attributes, + // so bound the combined total as well. + if (sensitiveAttributes.length * newSelectors.length > MAX_SELECTOR_VARIANTS) { + throw new Error(`Too many selector variants when trying to resolve case insensitive attributes, reduce the complexity of your selectors`); + } + sensitiveAttributes.forEach(newNode => { newSelectors.forEach(newSelector => { const newSelectorWithNewNode = newSelector.clone({}); @@ -120,7 +127,12 @@ const creator: PluginCreator = (opts?: pluginOptions) => { selectors.each(selector => { if (selectorHasInsensitiveAttribute(selector)) { - newSelectors = newSelectors.concat(createNewSelectors(selector)); + const expanded = createNewSelectors(selector); + if (newSelectors.length + expanded.length > MAX_SELECTOR_VARIANTS) { + throw new Error(`Too many selector variants when trying to resolve case insensitive attributes, reduce the complexity of your selectors`); + } + + newSelectors = newSelectors.concat(expanded); selector.remove(); } }); diff --git a/plugins/postcss-container-rule-prelude-list/CHANGELOG.md b/plugins/postcss-container-rule-prelude-list/CHANGELOG.md index 97183e62a9..aba15a9c63 100644 --- a/plugins/postcss-container-rule-prelude-list/CHANGELOG.md +++ b/plugins/postcss-container-rule-prelude-list/CHANGELOG.md @@ -1,5 +1,9 @@ # Changes to PostCSS Container Rule Prelude List +### Unreleased (patch) + +- Bound the total number of generated at-rules to prevent an exponential blowup from nested `@container` rules. + ### 1.0.3 _October 1, 2026_ diff --git a/plugins/postcss-container-rule-prelude-list/dist/index.mjs b/plugins/postcss-container-rule-prelude-list/dist/index.mjs index 95ac856fd3..b1aae50c76 100644 --- a/plugins/postcss-container-rule-prelude-list/dist/index.mjs +++ b/plugins/postcss-container-rule-prelude-list/dist/index.mjs @@ -1 +1 @@ -import{parseCommaSeparatedListOfComponentValues as s,stringify as e}from"@csstools/css-parser-algorithms";import{tokenize as r}from"@csstools/css-tokenizer";const t=/^container$/i,creator=o=>{const a=Object.assign({preserve:!1},o);return{postcssPlugin:"postcss-container-rule-prelude-list",AtRule(o){if(!t.test(o.name))return;if(!o.params.includes(","))return;const c=s(r({css:o.params})).map(s=>e([s]));c.length<=1||(c.forEach(s=>{o.cloneBefore({params:s.trim()})}),a.preserve||o.remove())}}};creator.postcss=!0;export{creator as default,creator as"module.exports"}; +import{parseCommaSeparatedListOfComponentValues as e,stringify as s}from"@csstools/css-parser-algorithms";import{tokenize as r}from"@csstools/css-tokenizer";const t=/^container$/i,creator=o=>{const n=Object.assign({preserve:!1},o);let c=0;return{postcssPlugin:"postcss-container-rule-prelude-list",Once(){c=0},AtRule(o){if(!t.test(o.name))return;if(!o.params.includes(","))return;const i=e(r({css:o.params})).map(e=>s([e]));if(!(i.length<=1)){if(c+=i.length,c>1e4)throw new Error("Maximum @container expansion size exceeded, reduce the complexity of your stylesheet");i.forEach(e=>{o.cloneBefore({params:e.trim()})}),n.preserve||o.remove()}}}};creator.postcss=!0;export{creator as default,creator as"module.exports"}; diff --git a/plugins/postcss-container-rule-prelude-list/src/index.ts b/plugins/postcss-container-rule-prelude-list/src/index.ts index be779063b1..b0b4644736 100644 --- a/plugins/postcss-container-rule-prelude-list/src/index.ts +++ b/plugins/postcss-container-rule-prelude-list/src/index.ts @@ -4,6 +4,8 @@ import type { PluginCreator } from 'postcss'; const CONTAINER_NAME_REGEX = /^container$/i; +const MAX_EXPANDED_AT_RULES = 10_000; + /** postcss-container-rule-prelude-list plugin options */ export type pluginOptions = { /** Preserve the original notation. default: false */ @@ -20,8 +22,13 @@ const creator: PluginCreator = (opts?: pluginOptions) => { opts, ); + let expandedAtRules = 0; + return { postcssPlugin: 'postcss-container-rule-prelude-list', + Once(): void { + expandedAtRules = 0; + }, AtRule(rule): void { if (!CONTAINER_NAME_REGEX.test(rule.name)) { return; @@ -36,6 +43,11 @@ const creator: PluginCreator = (opts?: pluginOptions) => { return; } + expandedAtRules += list.length; + if (expandedAtRules > MAX_EXPANDED_AT_RULES) { + throw new Error('Maximum @container expansion size exceeded, reduce the complexity of your stylesheet'); + } + list.forEach((item) => { rule.cloneBefore({ params: item.trim() }); }); diff --git a/plugins/postcss-custom-properties/CHANGELOG.md b/plugins/postcss-custom-properties/CHANGELOG.md index 00efd14acd..b1a9a21d2a 100644 --- a/plugins/postcss-custom-properties/CHANGELOG.md +++ b/plugins/postcss-custom-properties/CHANGELOG.md @@ -1,5 +1,10 @@ # Changes to PostCSS Custom Properties +### Unreleased (patch) + +- Share a single expansion budget across all declarations. +- Avoid a quadratic sibling scan when checking for an existing fallback declaration. + ### 15.0.3 _October 1, 2026_ diff --git a/plugins/postcss-custom-properties/dist/index.mjs b/plugins/postcss-custom-properties/dist/index.mjs index cd5b99dbdc..759b8fc230 100644 --- a/plugins/postcss-custom-properties/dist/index.mjs +++ b/plugins/postcss-custom-properties/dist/index.mjs @@ -1 +1 @@ -import{parse as e,addLayerToModel as t}from"@csstools/cascade-layer-name-parser";import n from"postcss-value-parser";import{hasSupportsAtRuleAncestor as o}from"@csstools/utilities";const r=e("csstools-implicit-layer")[0];function collectCascadeLayerOrder(n){const o=new Map,s=new Map,a=[];let i=1;n.walkAtRules(n=>{if("layer"!==n.name.toLowerCase())return;{let e=n.parent,t=0;for(;e;){if("atrule"!==e.type||"layer"!==e.name.toLowerCase()){if(e===n.root())break;return}if(t++,t>100)throw new Error("Maximum nested @layer depth exceeded, reduce the complexity of your layers");e=e.parent}}let c;if(n.nodes)c=normalizeLayerName(n.params,i++);else{if(!n.params.trim())return;c=n.params}let l=e(c);if(l?.length){{let e=n.parent;for(;e&&"atrule"===e.type&&"layer"===e.name.toLowerCase();){const t=s.get(e);if(t){l=l.map(e=>t.concat(e));break}e=e.parent}}if(t(a,l),n.nodes){const e=l[0].concat(r);o.set(n,e),s.set(n,l[0])}}});for(const e of o.values())t(a,[e]);const c=new WeakMap;for(const[e,t]of o)c.set(e,a.findIndex(e=>t.equal(e)));return c}function normalizeLayerName(e,t){return e.trim()?e:`csstools-anon-layer--${t}`}const s=/(?:!\s*)?postcss-custom-properties:\s*off\b/i,a=new WeakMap;function isBlockIgnored(e){if(!e||!e.nodes)return!1;if(a.has(e))return a.get(e);const t=e.some(e=>isIgnoreComment(e,s));return a.set(e,t),t}const i=/(?:!\s*)?postcss-custom-properties:\s*ignore\s+next\b/i;function isDeclarationIgnored(e){return!!e&&(!!isBlockIgnored(e.parent)||isIgnoreComment(e.prev(),i))}function isIgnoreComment(e,t){return!!e&&"comment"===e.type&&t.test(e.text)}const c=new Set(["layer"]);function isProcessableRule(e){let t=e.parent;for(;t;){if("atrule"===t.type&&!c.has(t.name.toLowerCase()))return!1;t=t.parent}return!0}const l=/^html$/i,u=/^:where\(html\)$/i,p=/^:root$/i,f=/^:where\(:root\)$/i,d=/(html|:root)/i,m=/^var$/i;function isVarFunction(e){return"function"===e.type&&m.test(e.value)&&e.nodes?.length>0}const h=/\bvar\(/i;function parseOrCached(e,t){let o=t.get(e);return o||(o=n(e),t.set(e,o),o)}function toposort(e,t){let n=e.length;const o=new Array(n),r=new Set,s=makeOutgoingEdges(t),a=makeNodesHash(e);for(let t=e.length-1;t>=0;t--){if(r.has(t))continue;const i=e[t],c=new Set,l=[];for(r.add(t),c.add(i),l.push({node:i,outgoing:Array.from(s.get(i)||new Set),next:0});l.length;){const e=l[l.length-1];if(e.nexts.get(e)??0,getLowLink=e=>a.get(e)??0;for(const e of o){if(s.has(e))continue;const t=[{node:e,childIndex:0}];for(s.set(e,l),a.set(e,l),l++,c.push(e),i.add(e);t.length;){const e=t[t.length-1],o=r.get(e.node)??[];if(e.childIndex1)for(const e of t)n.add(e)}}}for(const t of n)e.delete(t);return n}function makeOutgoingEdges(e){const t=new Map;for(let n=0,o=e.length;n{if(o.remaining--,o.remaining<0)throw new Error("Maximum custom property expansion size exceeded, reduce the complexity of your custom properties");if(!isVarFunction(e))return;const a=parseVarFunction(e);if(!a)return;let i=!1;a.fallback&&n.walk(a.fallback,e=>{if(!isVarFunction(e))return;const n=parseVarFunction(e);return n?n.fallback||t.has(n.name.value)?void 0:(i=!0,!1):void 0});let c=t.get(a.name.value)?.nodes;c||!a.fallback||i||(c=a.fallback),void 0!==c&&(c.length?replaceNodeWithNodes(s,r,c):s.splice(r,1,{type:"div",value:" ",before:"",after:"",sourceIndex:e.sourceIndex,sourceEndIndex:e.sourceEndIndex}))}),n.stringify(e.nodes)):""}function walk(e,t){let n,o,r;for(n=0,o=e.length;n"comment"!==e.type&&"space"!==e.type);return 1===t.length&&("word"===t[0].type&&w.test(t[0].value))}function buildCustomPropertiesMap(e,t,o){if(!e.size)return t;const r=new Map(t);{const s=[];for(const[a,i]of e.entries()){const c=parseOrCached(i,o);let l=!1;n.walk(c.nodes,n=>{if(!isVarFunction(n))return;const o=parseVarFunction(n);o&&(o.fallback||e.has(o.name.value)||t.has(o.name.value)?s.push([o.name.value,a]):l=!0)}),l||r.set(a,c)}removeCyclicReferences(r,s)}{const e=[];for(const[t,o]of r.entries())n.walk(o.nodes,n=>{if(!isVarFunction(n))return;const o=parseVarFunction(n);o&&(o.fallback||r.has(o.name.value)?e.push([o.name.value,t]):r.delete(t))});for(let t=0;t{d.test(e.selector)&&e.nodes?.length&&isProcessableRule(e)&&(isBlockIgnored(e)||e.selectors.forEach(t=>{let s;if(u.test(t)||f.test(t))s=0;else if(l.test(t))s=1;else{if(!p.test(t))return;s=2}const a=(c=r,((i=e).parent&&"atrule"===i.parent.type&&"layer"===i.parent.name.toLowerCase()?c.has(i.parent)?c.get(i.parent)+1:0:1e7)+10+s);var i,c;e.each(e=>{if("decl"!==e.type)return;if(!e.variable||isDeclarationIgnored(e))return;if("initial"===e.value.toLowerCase().trim())return;const t=o.get(e.prop)??-1;a>=t&&(o.set(e.prop,a),n.set(e.prop,e.value))})}))}),buildCustomPropertiesMap(n,new Map,t)}function getCustomPropertiesFromSiblings(e,t,n){if(!e.parent)return t;const o=new Map;return e.parent.each(t=>{"decl"===t.type&&t.variable&&e!==t&&(isDeclarationIgnored(t)||o.set(t.prop,t.value))}),o.size?buildCustomPropertiesMap(o,t,n):t}function transformProperties(e,t,o){if(isTransformableDecl(e)&&!isDeclarationIgnored(e)){const r=e.raws?.value?.raw??e.value,s=transformValueAST(n(r),t);if(s===r)return;if(parentHasExactFallback(e,s))return void(o.preserve||e.remove());const a=e.cloneBefore({value:s});a.raws?.value?.raw&&(a.raws.value.raw=""),o?.preserve||e.remove()}}const isTransformableDecl=e=>!e.variable&&e.value.includes("--")&&e.value.toLowerCase().includes("var(");function parentHasExactFallback(e,t){if(!e||!e.parent)return!1;let n=!1;const o=e.parent.index(e);return e.parent.each((r,s)=>r!==e&&(!(s>=o)&&void("decl"===r.type&&r.prop.toLowerCase()===e.prop.toLowerCase()&&r.value===t&&(n=!0)))),n}const v=/\bvar\(|\(top: var\(--f\)/i,creator=e=>{const t=!("preserve"in Object(e))||Boolean(e?.preserve);if("importFrom"in Object(e))throw new Error('[postcss-custom-properties] "importFrom" is no longer supported');if("exportTo"in Object(e))throw new Error('[postcss-custom-properties] "exportTo" is no longer supported');return{postcssPlugin:"postcss-custom-properties",prepare(){let e=new Map;const n=new WeakMap,r=new Map;return{postcssPlugin:"postcss-custom-properties",Once(t){e=getCustomPropertiesFromRoot(t,r)},Declaration(s){if(!h.test(s.value))return;if(o(s,v))return;let a=e;t&&s.parent&&(a=n.get(s.parent)??getCustomPropertiesFromSiblings(s,e,r),n.set(s.parent,a)),transformProperties(s,a,{preserve:t})}}}}};creator.postcss=!0;export{creator as default,creator as"module.exports"}; +import{parse as e,addLayerToModel as t}from"@csstools/cascade-layer-name-parser";import n from"postcss-value-parser";import{hasSupportsAtRuleAncestor as r}from"@csstools/utilities";const o=e("csstools-implicit-layer")[0];function collectCascadeLayerOrder(n){const r=new Map,s=new Map,a=[];let i=1;n.walkAtRules(n=>{if("layer"!==n.name.toLowerCase())return;{let e=n.parent,t=0;for(;e;){if("atrule"!==e.type||"layer"!==e.name.toLowerCase()){if(e===n.root())break;return}if(t++,t>100)throw new Error("Maximum nested @layer depth exceeded, reduce the complexity of your layers");e=e.parent}}let c;if(n.nodes)c=normalizeLayerName(n.params,i++);else{if(!n.params.trim())return;c=n.params}let l=e(c);if(l?.length){{let e=n.parent;for(;e&&"atrule"===e.type&&"layer"===e.name.toLowerCase();){const t=s.get(e);if(t){l=l.map(e=>t.concat(e));break}e=e.parent}}if(t(a,l),n.nodes){const e=l[0].concat(o);r.set(n,e),s.set(n,l[0])}}});for(const e of r.values())t(a,[e]);const c=new WeakMap;for(const[e,t]of r)c.set(e,a.findIndex(e=>t.equal(e)));return c}function normalizeLayerName(e,t){return e.trim()?e:`csstools-anon-layer--${t}`}const s=/(?:!\s*)?postcss-custom-properties:\s*off\b/i,a=new WeakMap;function isBlockIgnored(e){if(!e||!e.nodes)return!1;if(a.has(e))return a.get(e);const t=e.some(e=>isIgnoreComment(e,s));return a.set(e,t),t}const i=/(?:!\s*)?postcss-custom-properties:\s*ignore\s+next\b/i;function isDeclarationIgnored(e){return!!e&&(!!isBlockIgnored(e.parent)||isIgnoreComment(e.prev(),i))}function isIgnoreComment(e,t){return!!e&&"comment"===e.type&&t.test(e.text)}const c=new Set(["layer"]);function isProcessableRule(e){let t=e.parent;for(;t;){if("atrule"===t.type&&!c.has(t.name.toLowerCase()))return!1;t=t.parent}return!0}const l=/^html$/i,u=/^:where\(html\)$/i,p=/^:root$/i,f=/^:where\(:root\)$/i,d=/(html|:root)/i,m=/^var$/i;function isVarFunction(e){return"function"===e.type&&m.test(e.value)&&e.nodes?.length>0}const g=/\bvar\(/i;function parseOrCached(e,t){let r=t.get(e);return r||(r=n(e),t.set(e,r),r)}function toposort(e,t){let n=e.length;const r=new Array(n),o=new Set,s=makeOutgoingEdges(t),a=makeNodesHash(e);for(let t=e.length-1;t>=0;t--){if(o.has(t))continue;const i=e[t],c=new Set,l=[];for(o.add(t),c.add(i),l.push({node:i,outgoing:Array.from(s.get(i)||new Set),next:0});l.length;){const e=l[l.length-1];if(e.nexts.get(e)??0,getLowLink=e=>a.get(e)??0;for(const e of r){if(s.has(e))continue;const t=[{node:e,childIndex:0}];for(s.set(e,l),a.set(e,l),l++,c.push(e),i.add(e);t.length;){const e=t[t.length-1],r=o.get(e.node)??[];if(e.childIndex1)for(const e of t)n.add(e)}}}for(const t of n)e.delete(t);return n}function makeOutgoingEdges(e){const t=new Map;for(let n=0,r=e.length;n{if(r.remaining--,r.remaining<0)throw new Error("Maximum custom property expansion size exceeded, reduce the complexity of your custom properties");if(!isVarFunction(e))return;const a=parseVarFunction(e);if(!a)return;let i=!1;a.fallback&&n.walk(a.fallback,e=>{if(!isVarFunction(e))return;const n=parseVarFunction(e);return n?n.fallback||t.has(n.name.value)?void 0:(i=!0,!1):void 0});let c=t.get(a.name.value)?.nodes;c||!a.fallback||i||(c=a.fallback),void 0!==c&&(c.length?replaceNodeWithNodes(s,o,c):s.splice(o,1,{type:"div",value:" ",before:"",after:"",sourceIndex:e.sourceIndex,sourceEndIndex:e.sourceEndIndex}))}),n.stringify(e.nodes)):""}function walk(e,t){let n,r,o;for(n=0,r=e.length;n"comment"!==e.type&&"space"!==e.type);return 1===t.length&&("word"===t[0].type&&w.test(t[0].value))}function buildCustomPropertiesMap(e,t,r){if(!e.size)return t;const o=new Map(t);{const s=[];for(const[a,i]of e.entries()){const c=parseOrCached(i,r);let l=!1;n.walk(c.nodes,n=>{if(!isVarFunction(n))return;const r=parseVarFunction(n);r&&(r.fallback||e.has(r.name.value)||t.has(r.name.value)?s.push([r.name.value,a]):l=!0)}),l||o.set(a,c)}removeCyclicReferences(o,s)}{const e=[];for(const[t,r]of o.entries())n.walk(r.nodes,n=>{if(!isVarFunction(n))return;const r=parseVarFunction(n);r&&(r.fallback||o.has(r.name.value)?e.push([r.name.value,t]):o.delete(t))});for(let t=0;t{d.test(e.selector)&&e.nodes?.length&&isProcessableRule(e)&&(isBlockIgnored(e)||e.selectors.forEach(t=>{let s;if(u.test(t)||f.test(t))s=0;else if(l.test(t))s=1;else{if(!p.test(t))return;s=2}const a=(c=o,((i=e).parent&&"atrule"===i.parent.type&&"layer"===i.parent.name.toLowerCase()?c.has(i.parent)?c.get(i.parent)+1:0:1e7)+10+s);var i,c;e.each(e=>{if("decl"!==e.type)return;if(!e.variable||isDeclarationIgnored(e))return;if("initial"===e.value.toLowerCase().trim())return;const t=r.get(e.prop)??-1;a>=t&&(r.set(e.prop,a),n.set(e.prop,e.value))})}))}),buildCustomPropertiesMap(n,new Map,t)}function getCustomPropertiesFromSiblings(e,t,n){if(!e.parent)return t;const r=new Map;return e.parent.each(t=>{"decl"===t.type&&t.variable&&e!==t&&(isDeclarationIgnored(t)||r.set(t.prop,t.value))}),r.size?buildCustomPropertiesMap(r,t,n):t}function transformProperties(e,t,r){if(isTransformableDecl(e)&&!isDeclarationIgnored(e)){const o=e.raws?.value?.raw??e.value,s=transformValueAST(n(o),t,r.budget);if(s===o)return;if(parentHasExactFallback(e,s))return void(r.preserve||e.remove());const a=e.cloneBefore({value:s});a.raws?.value?.raw&&(a.raws.value.raw=""),r?.preserve||e.remove()}}const isTransformableDecl=e=>!e.variable&&e.value.includes("--")&&e.value.toLowerCase().includes("var(");function parentHasExactFallback(e,t){if(!e||!e.parent)return!1;const n=e.prop.toLowerCase();let r=e.prev();for(;r;){if("decl"===r.type&&r.prop.toLowerCase()===n&&r.value===t)return!0;r=r.prev()}return!1}const v=/\bvar\(|\(top: var\(--f\)/i,creator=e=>{const t=!("preserve"in Object(e))||Boolean(e?.preserve);if("importFrom"in Object(e))throw new Error('[postcss-custom-properties] "importFrom" is no longer supported');if("exportTo"in Object(e))throw new Error('[postcss-custom-properties] "exportTo" is no longer supported');return{postcssPlugin:"postcss-custom-properties",prepare(){let e=new Map;const n=new WeakMap,o=new Map;let s={remaining:h};return{postcssPlugin:"postcss-custom-properties",Once(t){s={remaining:h},e=getCustomPropertiesFromRoot(t,o)},Declaration(a){if(!g.test(a.value))return;if(r(a,v))return;let i=e;t&&a.parent&&(i=n.get(a.parent)??getCustomPropertiesFromSiblings(a,e,o),n.set(a.parent,i)),transformProperties(a,i,{preserve:t,budget:s})}}}}};creator.postcss=!0;export{creator as default,creator as"module.exports"}; diff --git a/plugins/postcss-custom-properties/src/index.ts b/plugins/postcss-custom-properties/src/index.ts index 9f2533498e..fa508a545c 100644 --- a/plugins/postcss-custom-properties/src/index.ts +++ b/plugins/postcss-custom-properties/src/index.ts @@ -6,6 +6,8 @@ import getCustomPropertiesFromSiblings from './get-custom-properties-from-siblin import { HAS_VAR_FUNCTION_REGEX } from './is-var-function'; import { hasSupportsAtRuleAncestor } from '@csstools/utilities'; import { transformProperties } from './transform-properties'; +import { MAX_TRANSFORMED_NODES } from './transform-value-ast'; +import type { TransformValueASTBudget } from './transform-value-ast'; /** postcss-custom-properties plugin options */ export type pluginOptions = { @@ -33,9 +35,15 @@ const creator: PluginCreator = (opts?: pluginOptions) => { const customPropertiesByParent: WeakMap> = new WeakMap(); const parsedValuesCache: Map = new Map(); + // Share a single expansion budget across all declarations in this + // document so that a value that resolves to a large subtree can not be + // inlined once per declaration. + let expansionBudget: TransformValueASTBudget = { remaining: MAX_TRANSFORMED_NODES }; + return { postcssPlugin: 'postcss-custom-properties', Once(root): void { + expansionBudget = { remaining: MAX_TRANSFORMED_NODES }; rootCustomProperties = getCustomPropertiesFromRoot(root, parsedValuesCache); }, Declaration(decl): void { @@ -54,7 +62,7 @@ const creator: PluginCreator = (opts?: pluginOptions) => { customPropertiesByParent.set(decl.parent, customProperties); } - transformProperties(decl, customProperties, { preserve: preserve }); + transformProperties(decl, customProperties, { preserve: preserve, budget: expansionBudget }); }, }; }, diff --git a/plugins/postcss-custom-properties/src/transform-properties.ts b/plugins/postcss-custom-properties/src/transform-properties.ts index da423c6401..fb531b3ad7 100644 --- a/plugins/postcss-custom-properties/src/transform-properties.ts +++ b/plugins/postcss-custom-properties/src/transform-properties.ts @@ -2,14 +2,15 @@ import type { Declaration } from 'postcss'; import valuesParser from 'postcss-value-parser'; import transformValueAST from './transform-value-ast'; +import type { TransformValueASTBudget } from './transform-value-ast'; import { isDeclarationIgnored } from './is-ignored'; // transform custom pseudo selectors with custom selectors -export function transformProperties(decl: Declaration, customProperties: Map, opts: { preserve?: boolean }): void { +export function transformProperties(decl: Declaration, customProperties: Map, opts: { preserve?: boolean, budget?: TransformValueASTBudget }): void { if (isTransformableDecl(decl) && !isDeclarationIgnored(decl)) { const originalValue = decl.raws?.value?.raw ?? decl.value; const valueAST = valuesParser(originalValue); - const value = transformValueAST(valueAST, customProperties); + const value = transformValueAST(valueAST, customProperties, opts.budget); if (value === originalValue) { return; @@ -45,25 +46,19 @@ function parentHasExactFallback(decl: Declaration, value: string): boolean { return false; } - let hasFallback = false; - const declIndex = decl.parent.index(decl); - decl.parent.each((sibling, index) => { - if (sibling === decl) { - return false; - } - - if (index >= declIndex) { - return false; - } + const prop = decl.prop.toLowerCase(); - if (sibling.type !== 'decl') { - return; + // Walk backwards from the declaration instead of scanning from the start of + // the container. `parent.index()` + a full sibling scan for every + // declaration is O(n^2) for a rule with many declarations. + let sibling = decl.prev(); + while (sibling) { + if (sibling.type === 'decl' && sibling.prop.toLowerCase() === prop && sibling.value === value) { + return true; } - if (sibling.prop.toLowerCase() === decl.prop.toLowerCase() && sibling.value === value) { - hasFallback = true; - } - }); + sibling = sibling.prev(); + } - return hasFallback; + return false; } diff --git a/plugins/postcss-custom-properties/src/transform-value-ast.ts b/plugins/postcss-custom-properties/src/transform-value-ast.ts index 732c501eb9..23810a14b6 100644 --- a/plugins/postcss-custom-properties/src/transform-value-ast.ts +++ b/plugins/postcss-custom-properties/src/transform-value-ast.ts @@ -9,7 +9,7 @@ import { parseVarFunction } from './parse-var-function'; * Custom properties can reference each other and duplicate subtrees, which can grow * exponentially. Sharing a single budget bounds the total expansion. */ -type TransformValueASTBudget = { +export type TransformValueASTBudget = { remaining: number, }; diff --git a/plugins/postcss-custom-properties/test/_tape.mjs b/plugins/postcss-custom-properties/test/_tape.mjs index 7f4636aba1..19ee5af476 100644 --- a/plugins/postcss-custom-properties/test/_tape.mjs +++ b/plugins/postcss-custom-properties/test/_tape.mjs @@ -18,6 +18,9 @@ postcssTape(plugin)({ 'cascade-layers-nested': { message: 'qualifies nested cascade layer names exactly once', }, + 'cascade-layers-escaped': { + message: 'treats escaped layer names as their unescaped equivalent', + }, 'cyclic-on-different-element-1': { message: 'supports cyclic variables on different elements', }, diff --git a/plugins/postcss-custom-properties/test/cascade-layers-escaped.css b/plugins/postcss-custom-properties/test/cascade-layers-escaped.css new file mode 100644 index 0000000000..1ed28c163e --- /dev/null +++ b/plugins/postcss-custom-properties/test/cascade-layers-escaped.css @@ -0,0 +1,15 @@ +@layer foo { + :root { + --x: red; + } +} + +@layer \66 oo { + :root { + --x: blue; + } +} + +a { + color: var(--x); +} diff --git a/plugins/postcss-custom-properties/test/cascade-layers-escaped.expect.css b/plugins/postcss-custom-properties/test/cascade-layers-escaped.expect.css new file mode 100644 index 0000000000..e96cd1788f --- /dev/null +++ b/plugins/postcss-custom-properties/test/cascade-layers-escaped.expect.css @@ -0,0 +1,16 @@ +@layer foo { + :root { + --x: red; + } +} + +@layer \66 oo { + :root { + --x: blue; + } +} + +a { + color: blue; + color: var(--x); +} diff --git a/plugins/postcss-custom-selectors/CHANGELOG.md b/plugins/postcss-custom-selectors/CHANGELOG.md index 9e9da64b0c..2f51d8d338 100644 --- a/plugins/postcss-custom-selectors/CHANGELOG.md +++ b/plugins/postcss-custom-selectors/CHANGELOG.md @@ -1,5 +1,9 @@ # Changes to PostCSS Custom Selectors +### Unreleased (patch) + +- Bound the total number of generated selectors to prevent an exponential blowup from nested custom selectors. + ### 9.0.3 _October 1, 2026_ diff --git a/plugins/postcss-custom-selectors/dist/index.mjs b/plugins/postcss-custom-selectors/dist/index.mjs index 5856301ff2..ed24883bd3 100644 --- a/plugins/postcss-custom-selectors/dist/index.mjs +++ b/plugins/postcss-custom-selectors/dist/index.mjs @@ -1 +1 @@ -import e from"postcss-selector-parser";import{parse as r,addLayerToModel as t}from"@csstools/cascade-layer-name-parser";const s=r("csstools-implicit-layer")[0];function collectCascadeLayerOrder(e){const o=new Map,n=new Map,a=[];let c=1;e.walkAtRules(e=>{if("layer"!==e.name.toLowerCase())return;{let r=e.parent,t=0;for(;r;){if("atrule"!==r.type||"layer"!==r.name.toLowerCase()){if(r===e.root())break;return}if(t++,t>100)throw new Error("Maximum nested @layer depth exceeded, reduce the complexity of your layers");r=r.parent}}let l;if(e.nodes)l=normalizeLayerName(e.params,c++);else{if(!e.params.trim())return;l=e.params}let u=r(l);if(u?.length){{let r=e.parent;for(;r&&"atrule"===r.type&&"layer"===r.name.toLowerCase();){const e=n.get(r);if(e){u=u.map(r=>e.concat(r));break}r=r.parent}}if(t(a,u),e.nodes){const r=u[0].concat(s);o.set(e,r),n.set(e,u[0])}}});for(const e of o.values())t(a,[e]);const l=new WeakMap;for(const[e,r]of o)l.set(e,a.findIndex(e=>r.equal(e)));return l}function normalizeLayerName(e,r){return e.trim()?e:`csstools-anon-layer--${r}`}const o=new Set(["scope","container","layer"]);function isProcessableCustomSelectorRule(e){if("atrule"!==e.type)return!1;if("custom-selector"!==e.name.toLowerCase())return!1;if(!e.params||!e.params.includes(":--"))return!1;if(e.nodes&&e.nodes.length>0)return!1;let r=e.parent;for(;r;){if("rule"===r.type)return!1;if("atrule"===r.type&&!o.has(r.name.toLowerCase()))return!1;r=r.parent}return!0}function getCustomSelectors(r,t,s){const o=new Map,n=new Map,a=collectCascadeLayerOrder(r);return r.walkAtRules(r=>{var c,l;if(isProcessableCustomSelectorRule(r))try{const t=r.params.trim(),u=e().astSync(t),p=u?.nodes?.[0]?.nodes?.[0];if(!p||"pseudo"!==p.type||!p.value.startsWith(":--"))return;const i=p.toString(),m=(l=a,(c=r).parent&&"atrule"===c.parent.type&&"layer"===c.parent.name.toLowerCase()?l.has(c.parent)?l.get(c.parent)+1:0:1e7),f=n.get(i)??-1;if(m&&m>=f&&(n.set(i,m),o.set(i,e().astSync(t.slice(i.length).trim()))),!s.preserve){const e=r.parent;r.remove(),removeEmptyAncestorBlocks(e)}}catch(e){r.warn(t,`Failed to parse selector : "${r.params}" with message: "${e instanceof Error?e.message:e}"`)}}),o}function removeEmptyAncestorBlocks(e){if(!e)return;let r=e;for(;r;){if(r.nodes&&r.nodes.length>0)return;const e=r.parent;r.remove(),r=e}}function transformRule(r,t,s){let o=r.selector;try{o=e(r=>{r.walkPseudos(r=>{if(!s.has(r.value))return;const t=e.pseudo({value:":is",nodes:[]}),o=s.get(r.value);o&&(o.each(e=>{t.append(e.clone())}),r.replaceWith(t))})}).processSync(r.selector)}catch(e){return r.warn(t,`Failed to parse selector : "${o}" with message: "${e instanceof Error?e.message:e}"`),r.selector}return o}const creator=e=>{const r=e?.preserve??!1;if("importFrom"in Object(e))throw new Error('[postcss-custom-selectors] "importFrom" is no longer supported');if("exportTo"in Object(e))throw new Error('[postcss-custom-selectors] "exportTo" is no longer supported');return{postcssPlugin:"postcss-custom-selectors",prepare(){const e=new WeakSet;let t=new Map;return{postcssPlugin:"postcss-custom-selectors",Once(e,{result:s}){t=getCustomSelectors(e,s,{preserve:r})},Rule(s,{result:o}){if(e.has(s))return;if(!s.selector?.includes(":--"))return;const n=transformRule(s,o,t);if(n!==s.selector){if(n.length>1e5)throw s.error("Maximum custom selector expansion length exceeded, reduce the complexity of your custom selectors");e.add(s),s.cloneBefore({selector:n}),r||s.remove()}}}}}};creator.postcss=!0;export{creator as default,creator as"module.exports"}; +import e from"postcss-selector-parser";import{parse as r,addLayerToModel as t}from"@csstools/cascade-layer-name-parser";const s=r("csstools-implicit-layer")[0];function collectCascadeLayerOrder(e){const o=new Map,n=new Map,a=[];let c=1;e.walkAtRules(e=>{if("layer"!==e.name.toLowerCase())return;{let r=e.parent,t=0;for(;r;){if("atrule"!==r.type||"layer"!==r.name.toLowerCase()){if(r===e.root())break;return}if(t++,t>100)throw new Error("Maximum nested @layer depth exceeded, reduce the complexity of your layers");r=r.parent}}let l;if(e.nodes)l=normalizeLayerName(e.params,c++);else{if(!e.params.trim())return;l=e.params}let u=r(l);if(u?.length){{let r=e.parent;for(;r&&"atrule"===r.type&&"layer"===r.name.toLowerCase();){const e=n.get(r);if(e){u=u.map(r=>e.concat(r));break}r=r.parent}}if(t(a,u),e.nodes){const r=u[0].concat(s);o.set(e,r),n.set(e,u[0])}}});for(const e of o.values())t(a,[e]);const l=new WeakMap;for(const[e,r]of o)l.set(e,a.findIndex(e=>r.equal(e)));return l}function normalizeLayerName(e,r){return e.trim()?e:`csstools-anon-layer--${r}`}const o=new Set(["scope","container","layer"]);function isProcessableCustomSelectorRule(e){if("atrule"!==e.type)return!1;if("custom-selector"!==e.name.toLowerCase())return!1;if(!e.params||!e.params.includes(":--"))return!1;if(e.nodes&&e.nodes.length>0)return!1;let r=e.parent;for(;r;){if("rule"===r.type)return!1;if("atrule"===r.type&&!o.has(r.name.toLowerCase()))return!1;r=r.parent}return!0}function getCustomSelectors(r,t,s){const o=new Map,n=new Map,a=collectCascadeLayerOrder(r);return r.walkAtRules(r=>{var c,l;if(isProcessableCustomSelectorRule(r))try{const t=r.params.trim(),u=e().astSync(t),i=u?.nodes?.[0]?.nodes?.[0];if(!i||"pseudo"!==i.type||!i.value.startsWith(":--"))return;const p=i.toString(),m=(l=a,(c=r).parent&&"atrule"===c.parent.type&&"layer"===c.parent.name.toLowerCase()?l.has(c.parent)?l.get(c.parent)+1:0:1e7),f=n.get(p)??-1;if(m&&m>=f&&(n.set(p,m),o.set(p,e().astSync(t.slice(p.length).trim()))),!s.preserve){const e=r.parent;r.remove(),removeEmptyAncestorBlocks(e)}}catch(e){r.warn(t,`Failed to parse selector : "${r.params}" with message: "${e instanceof Error?e.message:e}"`)}}),o}function removeEmptyAncestorBlocks(e){if(!e)return;let r=e;for(;r;){if(r.nodes&&r.nodes.length>0)return;const e=r.parent;r.remove(),r=e}}function transformRule(r,t,s){let o=r.selector;try{let t=1e5;o=e(r=>{r.walkPseudos(r=>{if(!s.has(r.value))return;const o=e.pseudo({value:":is",nodes:[]}),n=s.get(r.value);if(n){if(t--,t<0)throw new Error("Maximum custom selector expansion size exceeded, reduce the complexity of your custom selectors");n.each(e=>{o.append(e.clone())}),r.replaceWith(o)}})}).processSync(r.selector)}catch(e){return r.warn(t,`Failed to parse selector : "${o}" with message: "${e instanceof Error?e.message:e}"`),r.selector}return o}const creator=e=>{const r=e?.preserve??!1;if("importFrom"in Object(e))throw new Error('[postcss-custom-selectors] "importFrom" is no longer supported');if("exportTo"in Object(e))throw new Error('[postcss-custom-selectors] "exportTo" is no longer supported');return{postcssPlugin:"postcss-custom-selectors",prepare(){const e=new WeakSet;let t=new Map;return{postcssPlugin:"postcss-custom-selectors",Once(e,{result:s}){t=getCustomSelectors(e,s,{preserve:r})},Rule(s,{result:o}){if(e.has(s))return;if(!s.selector?.includes(":--"))return;const n=transformRule(s,o,t);if(n!==s.selector){if(n.length>1e5)throw s.error("Maximum custom selector expansion length exceeded, reduce the complexity of your custom selectors");e.add(s),s.cloneBefore({selector:n}),r||s.remove()}}}}}};creator.postcss=!0;export{creator as default,creator as"module.exports"}; diff --git a/plugins/postcss-custom-selectors/src/transform-rule.ts b/plugins/postcss-custom-selectors/src/transform-rule.ts index e7ff0b9321..18c8917bef 100644 --- a/plugins/postcss-custom-selectors/src/transform-rule.ts +++ b/plugins/postcss-custom-selectors/src/transform-rule.ts @@ -2,11 +2,19 @@ import parser from 'postcss-selector-parser'; import type { Result, Rule } from 'postcss'; import type { Root } from 'postcss-selector-parser'; +// Custom selectors can reference each other and duplicate their contents. +// Every substitution can duplicate the remaining references, so the number of +// substitutions grows exponentially with the nesting depth. Bound the total +// number of substitutions performed for a single rule. +const MAX_EXPANSIONS = 100_000; + // transform custom pseudo selectors with custom selectors export function transformRule(rule: Rule, result: Result, customSelectors: Map): string { let selector = rule.selector; try { + let expansionBudget = MAX_EXPANSIONS; + selector = parser(selectors => { selectors.walkPseudos((pseudo) => { if (!customSelectors.has(pseudo.value)) { @@ -24,6 +32,11 @@ export function transformRule(rule: Rule, result: Result, customSelectors: Map { isWrapper.append(node.clone()); }); diff --git a/plugins/postcss-double-position-gradients/CHANGELOG.md b/plugins/postcss-double-position-gradients/CHANGELOG.md index dcd6d0185f..83881938c4 100644 --- a/plugins/postcss-double-position-gradients/CHANGELOG.md +++ b/plugins/postcss-double-position-gradients/CHANGELOG.md @@ -1,5 +1,9 @@ # Changes to PostCSS Double Position Gradients +### Unreleased (patch) + +- Improve performance when a gradient contains multiple double-position stops. + ### 7.0.4 _September 6, 2026_ diff --git a/plugins/postcss-double-position-gradients/dist/index.mjs b/plugins/postcss-double-position-gradients/dist/index.mjs index 52821259bd..f19784579a 100644 --- a/plugins/postcss-double-position-gradients/dist/index.mjs +++ b/plugins/postcss-double-position-gradients/dist/index.mjs @@ -1 +1 @@ -import e from"@csstools/postcss-progressive-custom-properties";import t from"postcss-value-parser";import{hasFallback as s,hasSupportsAtRuleAncestor as r}from"@csstools/utilities";const o=/(?:repeating-)?(?:conic|linear|radial)-gradient\(/i,i=/^(?:repeating-)?(?:conic|linear|radial)-gradient$/i,n=["at","bottom","center","circle","closest-corner","closest-side","ellipse","farthest-corner","farthest-side","from","in","left","right","to","top"];function isPunctuationCommaNode(e){return!!e&&"div"===e.type&&","===e.value}function isNumericNode(e){if(!e)return!1;try{return!1!==t.unit(e.value)}catch{return!1}}const basePlugin=e=>({postcssPlugin:"postcss-double-position-gradients",Declaration(a,{result:c}){if(!o.test(a.value))return;if(s(a))return;if(r(a,o))return;let u;try{u=t(a.value)}catch{a.warn(c,`Failed to parse value '${a.value}' as a CSS gradient. Leaving the original value intact.`)}if(void 0===u)return;u.walk(e=>{if("function"!==e.type||!i.test(e.value))return;const t=e.nodes.filter(e=>"comment"!==e.type&&"space"!==e.type);let s=!1;t.forEach((t,r,o)=>{if("word"===t.type&&n.includes(t.value.toLowerCase())&&(s=!0),"div"===t.type&&","===t.value&&(s=!1),s)return;const i=o[r-1],a=o[r-2],c=o[r+1];if(a&&a.type&&isNumericNode(i)&&isNumericNode(t)){const s=a,r={type:"div",value:",",before:isPunctuationCommaNode(c)?c.before:"",after:isPunctuationCommaNode(c)?"":" ",sourceIndex:0,sourceEndIndex:0};e.nodes.splice(e.nodes.indexOf(t)-1,0,r,s)}})});const l=u.toString();l!==a.value&&(a.cloneBefore({value:l}),e?.preserve||a.remove())}});basePlugin.postcss=!0;const postcssPlugin=t=>{const s=Object.assign({enableProgressiveCustomProperties:!0,preserve:!0},t);return s.enableProgressiveCustomProperties&&s.preserve?{postcssPlugin:"postcss-double-position-gradients",plugins:[e(),basePlugin(s)]}:basePlugin(s)};postcssPlugin.postcss=!0;export{postcssPlugin as default,postcssPlugin as"module.exports"}; +import e from"@csstools/postcss-progressive-custom-properties";import t from"postcss-value-parser";import{hasFallback as r,hasSupportsAtRuleAncestor as s}from"@csstools/utilities";const o=/(?:repeating-)?(?:conic|linear|radial)-gradient\(/i,i=/^(?:repeating-)?(?:conic|linear|radial)-gradient$/i,n=["at","bottom","center","circle","closest-corner","closest-side","ellipse","farthest-corner","farthest-side","from","in","left","right","to","top"];function isPunctuationCommaNode(e){return!!e&&"div"===e.type&&","===e.value}function isNumericNode(e){if(!e)return!1;try{return!1!==t.unit(e.value)}catch{return!1}}const basePlugin=e=>({postcssPlugin:"postcss-double-position-gradients",Declaration(a,{result:c}){if(!o.test(a.value))return;if(r(a))return;if(s(a,o))return;let u;try{u=t(a.value)}catch{a.warn(c,`Failed to parse value '${a.value}' as a CSS gradient. Leaving the original value intact.`)}if(void 0===u)return;u.walk(e=>{if("function"!==e.type||!i.test(e.value))return;const t=e.nodes.filter(e=>"comment"!==e.type&&"space"!==e.type),r=new Map;e.nodes.forEach((e,t)=>{r.set(e,t)});let s=!1,o=0;t.forEach((t,i,a)=>{if("word"===t.type&&n.includes(t.value.toLowerCase())&&(s=!0),"div"===t.type&&","===t.value&&(s=!1),s)return;const c=a[i-1],u=a[i-2],l=a[i+1];if(u&&u.type&&isNumericNode(c)&&isNumericNode(t)){const s=u,i={type:"div",value:",",before:isPunctuationCommaNode(l)?l.before:"",after:isPunctuationCommaNode(l)?"":" ",sourceIndex:0,sourceEndIndex:0},n=r.get(t);if(void 0===n)return;e.nodes.splice(n+2*o-1,0,i,s),o++}})});const l=u.toString();l!==a.value&&(a.cloneBefore({value:l}),e?.preserve||a.remove())}});basePlugin.postcss=!0;const postcssPlugin=t=>{const r=Object.assign({enableProgressiveCustomProperties:!0,preserve:!0},t);return r.enableProgressiveCustomProperties&&r.preserve?{postcssPlugin:"postcss-double-position-gradients",plugins:[e(),basePlugin(r)]}:basePlugin(r)};postcssPlugin.postcss=!0;export{postcssPlugin as default,postcssPlugin as"module.exports"}; diff --git a/plugins/postcss-double-position-gradients/src/index.ts b/plugins/postcss-double-position-gradients/src/index.ts index 991bbf40e8..14d2ee64e7 100644 --- a/plugins/postcss-double-position-gradients/src/index.ts +++ b/plugins/postcss-double-position-gradients/src/index.ts @@ -85,7 +85,13 @@ const basePlugin: PluginCreator<{ preserve?: boolean }> = (opts?: { preserve?: b return x.type !== 'comment' && x.type !== 'space'; }); + const funcNodeIndexes = new Map(); + func.nodes.forEach((funcNode, funcNodeIndex) => { + funcNodeIndexes.set(funcNode, funcNodeIndex); + }); + let inPrefix = false; + let inserted = 0; nodes.forEach((node, index, currentNodes) => { if (node.type === 'word' && keywords.includes(node.value.toLowerCase())) { @@ -118,15 +124,22 @@ const basePlugin: PluginCreator<{ preserve?: boolean }> = (opts?: { preserve?: b sourceEndIndex: 0, }; + const funcNodeIndex = funcNodeIndexes.get(node); + if (typeof funcNodeIndex === 'undefined') { + return; + } + func.nodes.splice( - // 1 before the current node - func.nodes.indexOf(node) - 1, + // 1 before the current node, offset by the nodes inserted by earlier matches + funcNodeIndex + (inserted * 2) - 1, // remove none 0, // insert these : comma, color, ); + + inserted++; } }); }); diff --git a/plugins/postcss-is-pseudo-class/CHANGELOG.md b/plugins/postcss-is-pseudo-class/CHANGELOG.md index 998f789664..3c53de047e 100644 --- a/plugins/postcss-is-pseudo-class/CHANGELOG.md +++ b/plugins/postcss-is-pseudo-class/CHANGELOG.md @@ -1,5 +1,10 @@ # Changes to PostCSS Is Pseudo Class +### Unreleased (patch) + +- Bound the total number of generated selectors per rule and insert generated rules in a single batch. + + ### 6.0.1 _September 25, 2026_ diff --git a/plugins/postcss-is-pseudo-class/dist/index.mjs b/plugins/postcss-is-pseudo-class/dist/index.mjs index c869eea110..67f9b2696f 100644 --- a/plugins/postcss-is-pseudo-class/dist/index.mjs +++ b/plugins/postcss-is-pseudo-class/dist/index.mjs @@ -1 +1 @@ -import e from"postcss-selector-parser";import{selectorSpecificity as s}from"@csstools/selector-specificity";function alwaysValidSelector(s){const o=e().astSync(s);let n=!0;return o.walk(e=>{if("class"!==e.type&&"comment"!==e.type&&"id"!==e.type&&"root"!==e.type&&"selector"!==e.type&&"string"!==e.type&&"tag"!==e.type&&"universal"!==e.type&&("attribute"!==e.type||e.insensitive)&&("combinator"!==e.type||"+"!==e.value&&">"!==e.value&&"~"!==e.value&&" "!==e.value)&&("pseudo"!==e.type||e.nodes?.length||":hover"!==e.value.toLowerCase()&&":focus"!==e.value.toLowerCase())){if("pseudo"===e.type&&1===e.nodes?.length&&":not"===e.value.toLowerCase()){let s=!0;if(e.nodes[0].walkCombinators(()=>{s=!1}),s)return}return n=!1,!1}}),n}function sortCompoundSelectorsInsideComplexSelector(s){if(!s||!s.nodes||1===s.nodes.length)return;const o=[];let n=[];for(let t=0;t"selector"===e.type&&"selector"===s.type&&e.nodes.length&&s.nodes.length?selectorTypeOrder(e.nodes[0],e.nodes[0].type)-selectorTypeOrder(s.nodes[0],s.nodes[0].type):"selector"===e.type&&e.nodes.length?selectorTypeOrder(e.nodes[0],e.nodes[0].type)-selectorTypeOrder(s,s.type):"selector"===s.type&&s.nodes.length?selectorTypeOrder(e,e.type)-selectorTypeOrder(s.nodes[0],s.nodes[0].type):selectorTypeOrder(e,e.type)-selectorTypeOrder(s,s.type));const n=new Set(s.map(e=>e.type)),r=n.has("universal")&&(n.has("tag")||n.has("attribute")||n.has("class")||n.has("id")||n.has("pseudo"));for(let e=0;e=0;o--){const n=t[o-1];if(t[o].remove(),n&&"tag"===n.type&&"tag"===t[o].type){const n=e.pseudo({value:":is",nodes:[e.selector({value:"",nodes:[t[o]]})]});n.parent=s,s.nodes.unshift(n)}else t[o].parent=s,s.nodes.unshift(t[o])}}function selectorTypeOrder(s,n){return e.isPseudoElement(s)?o.pseudoElement:o[n]}const o={universal:0,tag:1,pseudoElement:2,id:3,class:4,attribute:5,pseudo:6,selector:7,string:8,root:9,comment:10};function childAdjacentChild(e){return!(!e||!e.nodes)&&("selector"===e.type&&(3===e.nodes.length&&(!(!e.nodes[0]||"pseudo"!==e.nodes[0].type||":-csstools-matches"!==e.nodes[0].value)&&(!(!e.nodes[1]||"combinator"!==e.nodes[1].type||"+"!==e.nodes[1].value&&"~"!==e.nodes[1].value)&&(!(!e.nodes[2]||"pseudo"!==e.nodes[2].type||":-csstools-matches"!==e.nodes[2].value)&&(!(!e.nodes[0].nodes||1!==e.nodes[0].nodes.length)&&("selector"===e.nodes[0].nodes[0].type&&(!(!e.nodes[0].nodes[0].nodes||3!==e.nodes[0].nodes[0].nodes.length)&&(!(!e.nodes[0].nodes[0].nodes||"combinator"!==e.nodes[0].nodes[0].nodes[1].type||">"!==e.nodes[0].nodes[0].nodes[1].value)&&(!(!e.nodes[2].nodes||1!==e.nodes[2].nodes.length)&&("selector"===e.nodes[2].nodes[0].type&&(!(!e.nodes[2].nodes[0].nodes||3!==e.nodes[2].nodes[0].nodes.length)&&(!(!e.nodes[2].nodes[0].nodes||"combinator"!==e.nodes[2].nodes[0].nodes[1].type||">"!==e.nodes[2].nodes[0].nodes[1].value)&&(e.nodes[0].nodes[0].insertAfter(e.nodes[0].nodes[0].nodes[0],e.nodes[2].nodes[0].nodes[0].clone()),e.nodes[2].nodes[0].nodes[1].remove(),e.nodes[2].nodes[0].nodes[0].remove(),e.nodes[0].replaceWith(e.nodes[0].nodes[0]),e.nodes[2].replaceWith(e.nodes[2].nodes[0]),!0))))))))))))))}function isInCompoundWithOneOtherElement(s){if(!s||!s.nodes)return!1;if(!e.isSelector(s))return!1;if(2!==s.nodes.length)return!1;let o=-1,n=-1;s.nodes[0]&&e.isPseudoClass(s.nodes[0])&&":-csstools-matches"===s.nodes[0].value?(o=0,n=1):s.nodes[1]&&e.isPseudoClass(s.nodes[1])&&":-csstools-matches"===s.nodes[1].value&&(o=1,n=0);const t=s.nodes[o];if(!t||!e.isPseudoClass(t)||1!==t.nodes.length)return!1;const r=s.nodes[n];return!!r&&(!e.isCombinator(r)&&(!e.isPseudoElement(r)&&(t.nodes[0].append(r.clone()),t.replaceWith(...t.nodes[0].nodes),r.remove(),!0)))}function isPseudoInFirstCompound(s){if(!s||!s.nodes)return!1;if(!e.isSelector(s))return!1;let o=-1;for(let n=0;n{n.nodes[0].append(e.clone())}),r.forEach(e=>{n.nodes[0].append(e.clone())}),n.replaceWith(...n.nodes),t.forEach(e=>{e.remove()}),r.forEach(e=>{e.remove()}),!0}function samePrecedingElement(s){if(!s||!s.nodes)return!1;if("selector"!==s.type)return!1;let o=-1;for(let n=0;n"!==s.nodes[o].value&&"+"!==s.nodes[o].value)return!1;const t=s.nodes[o].value;if(!s.nodes[n]||"pseudo"!==s.nodes[n].type||":-csstools-matches"!==s.nodes[n].value)return!1;if(!s.nodes[n].nodes||1!==s.nodes[n].nodes.length)return!1;if("selector"!==s.nodes[n].nodes[0].type)return!1;if(!s.nodes[n].nodes[0].nodes||3!==s.nodes[n].nodes[0].nodes.length)return!1;if(!s.nodes[n].nodes[0].nodes||"combinator"!==s.nodes[n].nodes[0].nodes[1].type||s.nodes[n].nodes[0].nodes[1].value!==t)return!1;const r=s.nodes[n];if(!r||!e.isPseudoClass(r))return!1;const d=s.nodes.slice(0,o),l=s.nodes.slice(n+1);return s.each(e=>{e.remove()}),d.forEach(e=>{s.append(e)}),r.nodes[0].nodes.forEach(e=>{s.append(e)}),l.forEach(e=>{s.append(e)}),!0}function complexSelectors(s,o,n,t){return s.flatMap(s=>{if(-1===s.indexOf(":-csstools-matches")&&-1===s.toLowerCase().indexOf(":is"))return s;const r=e().astSync(s);return r.walkPseudos(s=>{if(":is"===s.value.toLowerCase()&&s.nodes&&s.nodes.length&&"selector"===s.nodes[0].type&&0===s.nodes[0].nodes.length)return s.value=":not",void s.nodes[0].append(e.universal());if(":-csstools-matches"===s.value)if(!s.nodes||s.nodes.length){if(s.walkPseudos(s=>{if(e.isPseudoElement(s)){let e=s.value;if(e.startsWith("::-csstools-invalid-"))return;for(;e.startsWith(":");)e=e.slice(1);s.value=`::-csstools-invalid-${e}`,t()}}),1===s.nodes.length&&"selector"===s.nodes[0].type){if(1===s.nodes[0].nodes.length)return void s.replaceWith(s.nodes[0].nodes[0]);if(!s.nodes[0].some(e=>"combinator"===e.type))return void s.replaceWith(...s.nodes[0].nodes)}1!==r.nodes.length||"selector"!==r.nodes[0].type||1!==r.nodes[0].nodes.length||r.nodes[0].nodes[0]!==s?childAdjacentChild(s.parent)||isInCompoundWithOneOtherElement(s.parent)||isPseudoInFirstCompound(s.parent)||samePrecedingElement(s.parent)||("warning"===o.onComplexSelector&&n(),s.value=":is"):s.replaceWith(...s.nodes[0].nodes)}else s.remove()}),r.walk(e=>{"selector"===e.type&&"nodes"in e&&1===e.nodes.length&&"selector"===e.nodes[0].type&&e.replaceWith(e.nodes[0])}),r.walk(e=>{"nodes"in e&&sortCompoundSelectorsInsideComplexSelector(e)}),r.toString()}).filter(e=>!!e)}function splitSelectors(o,n,t=0){const r=":not(#"+n.specificityMatchingName+")",d=":not(."+n.specificityMatchingName+")",l=":not("+n.specificityMatchingName+")";return o.flatMap(o=>{if(-1===o.toLowerCase().indexOf(":is"))return o;let i=!1;const a=[];if(e().astSync(o).walkPseudos(e=>{if(":is"!==e.value.toLowerCase()||!e.nodes||!e.nodes.length)return;if("selector"===e.nodes[0].type&&0===e.nodes[0].nodes.length)return;if("pseudo"===e.parent?.parent?.type&&":not"===e.parent?.parent?.value?.toLowerCase())return void a.push([{start:e.parent.parent.sourceIndex,end:e.parent.parent.sourceIndex+e.parent.parent.toString().length,option:`:not(${e.nodes.toString()})`}]);if("pseudo"===e.parent?.parent?.type&&":has"===e.parent?.parent?.value?.toLowerCase())return void(e.value=":-csstools-matches");let o=e.parent;for(;o;){if(o.value&&":is"===o.value.toLowerCase()&&"pseudo"===o.type)return void(i=!0);o=o.parent}const n=s(e),t=e.sourceIndex,c=t+e.toString().length,p=[];e.nodes.forEach(e=>{const o={start:t,end:c,option:""},i=s(e);let a=e.toString().trim();const u=Math.max(0,n.a-i.a),h=Math.max(0,n.b-i.b),f=Math.max(0,n.c-i.c);for(let e=0;e{let s="";for(let n=0;n!!e)}const n=1e4;function cartesianProduct(...e){let s=1;for(let o=0;on)throw new Error("Too many combinations when trying to resolve a selector with `:is()` lists, reduce the complexity of your selectors");const o=[],t=e.length-1;return function helper(s,n){for(let r=0,d=e[n].length;r{const s={specificityMatchingName:"does-not-exist",...e||{}};return{postcssPlugin:"postcss-is-pseudo-class",prepare(){const e=new WeakSet;return{postcssPlugin:"postcss-is-pseudo-class",Rule(o,{result:n}){if(!o.selector)return;if(!t.test(o.selector))return;if(e.has(o))return;let r=!1;const warnOnComplexSelector=()=>{"warning"===s.onComplexSelector&&(r||(r=!0,o.warn(n,`Complex selectors in '${o.selector}' can not be transformed to an equivalent selector without ':is()'.`)))};let d=!1;const warnOnPseudoElements=()=>{"warning"===s.onPseudoElement&&(d||(d=!0,o.warn(n,`Pseudo elements are not allowed in ':is()', unable to transform '${o.selector}'`)))};try{let n=!1;const t=[],r=complexSelectors(splitSelectors(o.selectors,{specificityMatchingName:s.specificityMatchingName}),{onComplexSelector:s.onComplexSelector},warnOnComplexSelector,warnOnPseudoElements);if(Array.from(new Set(r)).forEach(s=>{if(o.selectors.indexOf(s)>-1)t.push(s);else{if(alwaysValidSelector(s))return t.push(s),void(n=!0);e.add(o),o.cloneBefore({selector:s}),n=!0}}),t.length&&n&&(e.add(o),o.cloneBefore({selectors:t})),!s.preserve){if(!n)return;o.remove()}}catch(e){if(!(e instanceof Error))throw e;if(e.message.indexOf("call stack size exceeded")>-1)throw e;o.warn(n,`Failed to parse selector "${o.selector}" with error: ${e.message}`)}}}}}};creator.postcss=!0;export{creator as default,creator as"module.exports"}; +import e from"postcss-selector-parser";import{selectorSpecificity as s}from"@csstools/selector-specificity";function alwaysValidSelector(s){const o=e().astSync(s);let n=!0;return o.walk(e=>{if("class"!==e.type&&"comment"!==e.type&&"id"!==e.type&&"root"!==e.type&&"selector"!==e.type&&"string"!==e.type&&"tag"!==e.type&&"universal"!==e.type&&("attribute"!==e.type||e.insensitive)&&("combinator"!==e.type||"+"!==e.value&&">"!==e.value&&"~"!==e.value&&" "!==e.value)&&("pseudo"!==e.type||e.nodes?.length||":hover"!==e.value.toLowerCase()&&":focus"!==e.value.toLowerCase())){if("pseudo"===e.type&&1===e.nodes?.length&&":not"===e.value.toLowerCase()){let s=!0;if(e.nodes[0].walkCombinators(()=>{s=!1}),s)return}return n=!1,!1}}),n}function sortCompoundSelectorsInsideComplexSelector(s){if(!s||!s.nodes||1===s.nodes.length)return;const o=[];let n=[];for(let t=0;t"selector"===e.type&&"selector"===s.type&&e.nodes.length&&s.nodes.length?selectorTypeOrder(e.nodes[0],e.nodes[0].type)-selectorTypeOrder(s.nodes[0],s.nodes[0].type):"selector"===e.type&&e.nodes.length?selectorTypeOrder(e.nodes[0],e.nodes[0].type)-selectorTypeOrder(s,s.type):"selector"===s.type&&s.nodes.length?selectorTypeOrder(e,e.type)-selectorTypeOrder(s.nodes[0],s.nodes[0].type):selectorTypeOrder(e,e.type)-selectorTypeOrder(s,s.type));const n=new Set(s.map(e=>e.type)),r=n.has("universal")&&(n.has("tag")||n.has("attribute")||n.has("class")||n.has("id")||n.has("pseudo"));for(let e=0;e=0;o--){const n=t[o-1];if(t[o].remove(),n&&"tag"===n.type&&"tag"===t[o].type){const n=e.pseudo({value:":is",nodes:[e.selector({value:"",nodes:[t[o]]})]});n.parent=s,s.nodes.unshift(n)}else t[o].parent=s,s.nodes.unshift(t[o])}}function selectorTypeOrder(s,n){return e.isPseudoElement(s)?o.pseudoElement:o[n]}const o={universal:0,tag:1,pseudoElement:2,id:3,class:4,attribute:5,pseudo:6,selector:7,string:8,root:9,comment:10};function childAdjacentChild(e){return!(!e||!e.nodes)&&("selector"===e.type&&(3===e.nodes.length&&(!(!e.nodes[0]||"pseudo"!==e.nodes[0].type||":-csstools-matches"!==e.nodes[0].value)&&(!(!e.nodes[1]||"combinator"!==e.nodes[1].type||"+"!==e.nodes[1].value&&"~"!==e.nodes[1].value)&&(!(!e.nodes[2]||"pseudo"!==e.nodes[2].type||":-csstools-matches"!==e.nodes[2].value)&&(!(!e.nodes[0].nodes||1!==e.nodes[0].nodes.length)&&("selector"===e.nodes[0].nodes[0].type&&(!(!e.nodes[0].nodes[0].nodes||3!==e.nodes[0].nodes[0].nodes.length)&&(!(!e.nodes[0].nodes[0].nodes||"combinator"!==e.nodes[0].nodes[0].nodes[1].type||">"!==e.nodes[0].nodes[0].nodes[1].value)&&(!(!e.nodes[2].nodes||1!==e.nodes[2].nodes.length)&&("selector"===e.nodes[2].nodes[0].type&&(!(!e.nodes[2].nodes[0].nodes||3!==e.nodes[2].nodes[0].nodes.length)&&(!(!e.nodes[2].nodes[0].nodes||"combinator"!==e.nodes[2].nodes[0].nodes[1].type||">"!==e.nodes[2].nodes[0].nodes[1].value)&&(e.nodes[0].nodes[0].insertAfter(e.nodes[0].nodes[0].nodes[0],e.nodes[2].nodes[0].nodes[0].clone()),e.nodes[2].nodes[0].nodes[1].remove(),e.nodes[2].nodes[0].nodes[0].remove(),e.nodes[0].replaceWith(e.nodes[0].nodes[0]),e.nodes[2].replaceWith(e.nodes[2].nodes[0]),!0))))))))))))))}function isInCompoundWithOneOtherElement(s){if(!s||!s.nodes)return!1;if(!e.isSelector(s))return!1;if(2!==s.nodes.length)return!1;let o=-1,n=-1;s.nodes[0]&&e.isPseudoClass(s.nodes[0])&&":-csstools-matches"===s.nodes[0].value?(o=0,n=1):s.nodes[1]&&e.isPseudoClass(s.nodes[1])&&":-csstools-matches"===s.nodes[1].value&&(o=1,n=0);const t=s.nodes[o];if(!t||!e.isPseudoClass(t)||1!==t.nodes.length)return!1;const r=s.nodes[n];return!!r&&(!e.isCombinator(r)&&(!e.isPseudoElement(r)&&(t.nodes[0].append(r.clone()),t.replaceWith(...t.nodes[0].nodes),r.remove(),!0)))}function isPseudoInFirstCompound(s){if(!s||!s.nodes)return!1;if(!e.isSelector(s))return!1;let o=-1;for(let n=0;n{n.nodes[0].append(e.clone())}),r.forEach(e=>{n.nodes[0].append(e.clone())}),n.replaceWith(...n.nodes),t.forEach(e=>{e.remove()}),r.forEach(e=>{e.remove()}),!0}function samePrecedingElement(s){if(!s||!s.nodes)return!1;if("selector"!==s.type)return!1;let o=-1;for(let n=0;n"!==s.nodes[o].value&&"+"!==s.nodes[o].value)return!1;const t=s.nodes[o].value;if(!s.nodes[n]||"pseudo"!==s.nodes[n].type||":-csstools-matches"!==s.nodes[n].value)return!1;if(!s.nodes[n].nodes||1!==s.nodes[n].nodes.length)return!1;if("selector"!==s.nodes[n].nodes[0].type)return!1;if(!s.nodes[n].nodes[0].nodes||3!==s.nodes[n].nodes[0].nodes.length)return!1;if(!s.nodes[n].nodes[0].nodes||"combinator"!==s.nodes[n].nodes[0].nodes[1].type||s.nodes[n].nodes[0].nodes[1].value!==t)return!1;const r=s.nodes[n];if(!r||!e.isPseudoClass(r))return!1;const d=s.nodes.slice(0,o),l=s.nodes.slice(n+1);return s.each(e=>{e.remove()}),d.forEach(e=>{s.append(e)}),r.nodes[0].nodes.forEach(e=>{s.append(e)}),l.forEach(e=>{s.append(e)}),!0}function complexSelectors(s,o,n,t){return s.flatMap(s=>{if(-1===s.indexOf(":-csstools-matches")&&-1===s.toLowerCase().indexOf(":is"))return s;const r=e().astSync(s);return r.walkPseudos(s=>{if(":is"===s.value.toLowerCase()&&s.nodes&&s.nodes.length&&"selector"===s.nodes[0].type&&0===s.nodes[0].nodes.length)return s.value=":not",void s.nodes[0].append(e.universal());if(":-csstools-matches"===s.value)if(!s.nodes||s.nodes.length){if(s.walkPseudos(s=>{if(e.isPseudoElement(s)){let e=s.value;if(e.startsWith("::-csstools-invalid-"))return;for(;e.startsWith(":");)e=e.slice(1);s.value=`::-csstools-invalid-${e}`,t()}}),1===s.nodes.length&&"selector"===s.nodes[0].type){if(1===s.nodes[0].nodes.length)return void s.replaceWith(s.nodes[0].nodes[0]);if(!s.nodes[0].some(e=>"combinator"===e.type))return void s.replaceWith(...s.nodes[0].nodes)}1!==r.nodes.length||"selector"!==r.nodes[0].type||1!==r.nodes[0].nodes.length||r.nodes[0].nodes[0]!==s?childAdjacentChild(s.parent)||isInCompoundWithOneOtherElement(s.parent)||isPseudoInFirstCompound(s.parent)||samePrecedingElement(s.parent)||("warning"===o.onComplexSelector&&n(),s.value=":is"):s.replaceWith(...s.nodes[0].nodes)}else s.remove()}),r.walk(e=>{"selector"===e.type&&"nodes"in e&&1===e.nodes.length&&"selector"===e.nodes[0].type&&e.replaceWith(e.nodes[0])}),r.walk(e=>{"nodes"in e&&sortCompoundSelectorsInsideComplexSelector(e)}),r.toString()}).filter(e=>!!e)}function splitSelectors(o,n,r=0,d={remaining:t}){const l=":not(#"+n.specificityMatchingName+")",i=":not(."+n.specificityMatchingName+")",a=":not("+n.specificityMatchingName+")";return o.flatMap(o=>{if(-1===o.toLowerCase().indexOf(":is"))return o;let t=!1;const c=[];if(e().astSync(o).walkPseudos(e=>{if(":is"!==e.value.toLowerCase()||!e.nodes||!e.nodes.length)return;if("selector"===e.nodes[0].type&&0===e.nodes[0].nodes.length)return;if("pseudo"===e.parent?.parent?.type&&":not"===e.parent?.parent?.value?.toLowerCase())return void c.push([{start:e.parent.parent.sourceIndex,end:e.parent.parent.sourceIndex+e.parent.parent.toString().length,option:`:not(${e.nodes.toString()})`}]);if("pseudo"===e.parent?.parent?.type&&":has"===e.parent?.parent?.value?.toLowerCase())return void(e.value=":-csstools-matches");let o=e.parent;for(;o;){if(o.value&&":is"===o.value.toLowerCase()&&"pseudo"===o.type)return void(t=!0);o=o.parent}const n=s(e),r=e.sourceIndex,d=r+e.toString().length,p=[];e.nodes.forEach(e=>{const o={start:r,end:d,option:""},t=s(e);let c=e.toString().trim();const u=Math.max(0,n.a-t.a),h=Math.max(0,n.b-t.b),f=Math.max(0,n.c-t.c);for(let e=0;e{let s="";for(let n=0;n!!e)}const n=1e4,t=1e5;function cartesianProduct(...e){let s=1;for(let o=0;on)throw new Error("Too many combinations when trying to resolve a selector with `:is()` lists, reduce the complexity of your selectors");const o=[],t=e.length-1;return function helper(s,n){for(let r=0,d=e[n].length;r{const s={specificityMatchingName:"does-not-exist",...e||{}};return{postcssPlugin:"postcss-is-pseudo-class",prepare(){const e=new WeakSet;return{postcssPlugin:"postcss-is-pseudo-class",Rule(o,{result:n}){if(!o.selector)return;if(!r.test(o.selector))return;if(e.has(o))return;let t=!1;const warnOnComplexSelector=()=>{"warning"===s.onComplexSelector&&(t||(t=!0,o.warn(n,`Complex selectors in '${o.selector}' can not be transformed to an equivalent selector without ':is()'.`)))};let d=!1;const warnOnPseudoElements=()=>{"warning"===s.onPseudoElement&&(d||(d=!0,o.warn(n,`Pseudo elements are not allowed in ':is()', unable to transform '${o.selector}'`)))};try{let n=!1;const t=[],r=complexSelectors(splitSelectors(o.selectors,{specificityMatchingName:s.specificityMatchingName}),{onComplexSelector:s.onComplexSelector},warnOnComplexSelector,warnOnPseudoElements),d=Array.from(new Set(r)),l=new Set(o.selectors),i=[];d.forEach(e=>{if(l.has(e))t.push(e);else{if(alwaysValidSelector(e))return t.push(e),void(n=!0);i.push(e),n=!0}});const a=i.map(e=>o.clone({selector:e}));if(t.length&&n&&a.push(o.clone({selectors:t})),a.length&&(e.add(o),o.before(a)),!s.preserve){if(!n)return;o.remove()}}catch(e){if(!(e instanceof Error))throw e;if(e.message.indexOf("call stack size exceeded")>-1)throw e;o.warn(n,`Failed to parse selector "${o.selector}" with error: ${e.message}`)}}}}}};creator.postcss=!0;export{creator as default,creator as"module.exports"}; diff --git a/plugins/postcss-is-pseudo-class/src/index.ts b/plugins/postcss-is-pseudo-class/src/index.ts index 76461d3a8a..b97cc273f4 100644 --- a/plugins/postcss-is-pseudo-class/src/index.ts +++ b/plugins/postcss-is-pseudo-class/src/index.ts @@ -1,4 +1,4 @@ -import type { Plugin, PluginCreator } from 'postcss'; +import type { Plugin, PluginCreator, Rule } from 'postcss'; import alwaysValidSelector from './split-selectors/always-valid'; import complexSelectors from './split-selectors/complex'; import splitSelectors from './split-selectors/split-selectors'; @@ -106,12 +106,16 @@ const creator: PluginCreator = (opts?: pluginOptions) => { const uniqueResolvedComplexSelectors = Array.from(new Set(resolvedComplexSelectors)); // 4. Replace. + // `rule.selectors` re-splits the selector string on every access. + const originalSelectors = new Set(rule.selectors); + const clonedSelectors: Array = []; + uniqueResolvedComplexSelectors.forEach((modifiedSelector) => { // `::is()` is incorrect but can't be detected without parsing. // It will be left as is and will eventually trigger this condition. // This prevents an infinite loop. // didModify is the signal to prevent the infinite loop. - if (rule.selectors.indexOf(modifiedSelector) > -1) { + if (originalSelectors.has(modifiedSelector)) { selectorListOnOriginalNode.push(modifiedSelector); return; } @@ -122,14 +126,21 @@ const creator: PluginCreator = (opts?: pluginOptions) => { return; } - transformedNodes.add(rule); - rule.cloneBefore({ selector: modifiedSelector }); + clonedSelectors.push(modifiedSelector); didModify = true; }); + // Insert as a batch: resolving the reference index for every + // single insertion is quadratic. + const clones: Array = clonedSelectors.map((modifiedSelector) => rule.clone({ selector: modifiedSelector })); + if (selectorListOnOriginalNode.length && didModify) { + clones.push(rule.clone({ selectors: selectorListOnOriginalNode })); + } + + if (clones.length) { transformedNodes.add(rule); - rule.cloneBefore({ selectors: selectorListOnOriginalNode }); + rule.before(clones); } if (!options.preserve) { diff --git a/plugins/postcss-is-pseudo-class/src/split-selectors/split-selectors.ts b/plugins/postcss-is-pseudo-class/src/split-selectors/split-selectors.ts index 87683a8334..bc3064058c 100644 --- a/plugins/postcss-is-pseudo-class/src/split-selectors/split-selectors.ts +++ b/plugins/postcss-is-pseudo-class/src/split-selectors/split-selectors.ts @@ -4,7 +4,7 @@ import { selectorSpecificity } from '@csstools/selector-specificity'; // splitSelectors handles the forgiving list behavior of ":is". // After created all combinations it wraps the individual selectors in ":-csstools-matches". // This makes it easy to recursively resolve all ":is" selectors without infinite loops. -export default function splitSelectors(selectors: Array, pluginOptions: { specificityMatchingName: string }, recursionDepth = 0): Array { +export default function splitSelectors(selectors: Array, pluginOptions: { specificityMatchingName: string }, recursionDepth = 0, budget: { remaining: number } = { remaining: MAX_TOTAL_COMBINATIONS }): Array { const specificityMatchingNameId = ':not(#' + pluginOptions.specificityMatchingName + ')'; const specificityMatchingNameClass = ':not(.' + pluginOptions.specificityMatchingName + ')'; const specificityMatchingNameTag = ':not(' + pluginOptions.specificityMatchingName + ')'; @@ -99,6 +99,20 @@ export default function splitSelectors(selectors: Array, pluginOptions: } let results: Array = []; + + // The per-`:is()` combination cap only bounds a single selector group. + // A rule with several selectors multiplies the total work, so bound the + // total number of generated selectors for the whole rule as well. + let combinations = 1; + for (let i = 0; i < replacements.length; i++) { + combinations *= replacements[i].length; + } + + budget.remaining -= combinations; + if (budget.remaining < 0) { + throw new Error('Too many combinations when trying to resolve a selector with `:is()` lists, reduce the complexity of your selectors'); + } + cartesianProduct(...replacements).forEach((replacement) => { let result = ''; @@ -118,7 +132,7 @@ export default function splitSelectors(selectors: Array, pluginOptions: if (foundNestedIs && recursionDepth < 10) { // recursion to transform `:is(a :is(b,c))` - results = splitSelectors(results, pluginOptions, recursionDepth + 1); + results = splitSelectors(results, pluginOptions, recursionDepth + 1, budget); } return results; @@ -132,6 +146,9 @@ export default function splitSelectors(selectors: Array, pluginOptions: // bound a small selector can exhaust memory and CPU. const MAX_SELECTOR_COMBINATIONS = 10_000; +// The maximum number of generated selectors for a single rule. +const MAX_TOTAL_COMBINATIONS = 100_000; + // https://en.wikipedia.org/wiki/Cartesian_product function cartesianProduct(...args: Array>): Array> { let total = 1; diff --git a/plugins/postcss-logical/CHANGELOG.md b/plugins/postcss-logical/CHANGELOG.md index 68f2ec80ac..a2d757ca6e 100644 --- a/plugins/postcss-logical/CHANGELOG.md +++ b/plugins/postcss-logical/CHANGELOG.md @@ -1,5 +1,9 @@ # Changes to PostCSS Logical Properties +### Unreleased (patch) + +- Improved performance by inserting transformed declarations in a single batch + ### 9.0.1 _September 25, 2026_ diff --git a/plugins/postcss-logical/dist/index.mjs b/plugins/postcss-logical/dist/index.mjs index 17f34370bb..ef41da05d8 100644 --- a/plugins/postcss-logical/dist/index.mjs +++ b/plugins/postcss-logical/dist/index.mjs @@ -1 +1 @@ -import r from"postcss-value-parser";var e,n;!function(r){r.Block="block",r.Inline="inline"}(e||(e={})),function(r){r.Start="start",r.End="end"}(n||(n={}));const o={BlockStart:"block-start",BlockEnd:"block-end",InlineStart:"inline-start",InlineEnd:"inline-end"};var t,i;function directionFlowToAxes(r){switch(r){case t.TopToBottom:return[i.Top,i.Bottom];case t.BottomToTop:return[i.Bottom,i.Top];case t.RightToLeft:return[i.Right,i.Left];case t.LeftToRight:return[i.Left,i.Right]}}function cloneDeclaration(r,e,n){return r.parent&&r.parent.some(r=>"decl"===r.type&&r.prop===n&&r.value===e)?[]:[r.clone({value:e,prop:n})]}!function(r){r.TopToBottom="top-to-bottom",r.BottomToTop="bottom-to-top",r.RightToLeft="right-to-left",r.LeftToRight="left-to-right"}(t||(t={})),function(r){r.Top="top",r.Right="right",r.Bottom="bottom",r.Left="left"}(i||(i={}));function transformTransition(e,n,o){const{prop:t,value:i}=e,l=r(i),a=[];let s=[];l.nodes.forEach(r=>{if("div"===r.type&&","===r.value)return a.push(s),void(s=[]);s.push(r)}),a.push(s);for(let r=0;r1e4)throw new Error("Too many combinations when transforming a transition, reduce the complexity of your transition");for(let n=d.length-1;n>=0;n--){const o=JSON.parse(JSON.stringify(e));o[t].value=d[n].prop,a.splice(r+1,0,o)}a.splice(r,1),r++}}}const d=[];for(let r=0;r"space"!==r.type);if(n.length>2||0===n.length){const r=`[postcss-logical] Invalid number of values for ${e.prop}. Found ${n.length} values, expected 1 or 2.`;throw e.error(r)}let o,t;return 1===n.length?(o=r.stringify(n[0]),t=o):(o=r.stringify(n[0]),t=r.stringify(n[1])),[o,t]}function transformBorder(r,e){return n=>cloneDeclaration(n,n.value,`border-${e}-${r}`)}function transformBorderProperty(r,e){return n=>{const[o,t]=e,[i,l]=parseValueCouple(n);return[...cloneDeclaration(n,i,`border-${o}-${r}`),...cloneDeclaration(n,l,`border-${t}-${r}`)]}}function transformBorderShorthand(r){return e=>r.flatMap(r=>cloneDeclaration(e,e.value,`border-${r}`))}function transformBorderRadius(r){return e=>{let n;switch(e.prop.toLowerCase()){case"border-start-start-radius":n=`border-${r.inlineIsHorizontal?`${r.block[0]}-${r.inline[0]}`:`${r.inline[0]}-${r.block[0]}`}-radius`;break;case"border-start-end-radius":n=`border-${r.inlineIsHorizontal?`${r.block[0]}-${r.inline[1]}`:`${r.inline[1]}-${r.block[0]}`}-radius`;break;case"border-end-start-radius":n=`border-${r.inlineIsHorizontal?`${r.block[1]}-${r.inline[0]}`:`${r.inline[0]}-${r.block[1]}`}-radius`;break;case"border-end-end-radius":n=`border-${r.inlineIsHorizontal?`${r.block[1]}-${r.inline[1]}`:`${r.inline[1]}-${r.block[1]}`}-radius`;break}return n?cloneDeclaration(e,e.value,n):[]}}function transformLogicalSize(r){return e=>{const{value:n}=e,o=r.inlineIsHorizontal?"width":"height",t=r.inlineIsHorizontal?"height":"width",i=e.prop.toLowerCase().replace("inline-size",o).replace("block-size",t);return cloneDeclaration(e,n,i)}}function transformOffset(r){return e=>cloneDeclaration(e,e.value,r)}function transformOffsetShorthand(r){return e=>{const[n,o]=r,[t,i]=parseValueCouple(e);return[...cloneDeclaration(e,t,n),...cloneDeclaration(e,i,o)]}}function transformSide(r,e){return n=>cloneDeclaration(n,n.value,`${r}-${e}`)}function transformSideShorthand(r,e){return n=>{const[o,t]=e,[i,l]=parseValueCouple(n);return[...cloneDeclaration(n,i,`${r}-${o}`),...cloneDeclaration(n,l,`${r}-${t}`)]}}function logicalToPhysical(r,e){const[n,t]=e.block,[i,l]=e.inline;switch(r){case o.BlockStart:return n;case o.BlockEnd:return t;case o.InlineStart:return i;case o.InlineEnd:return l;default:throw new Error("Unsupported logical direction")}}function doTransform(e,n,o){const{prop:t,value:i}=e,l=r(i);l.nodes.forEach(r=>{if("word"===r.type){const e=r.value.toLowerCase();n.includes(e)&&(r.value=logicalToPhysical(e,o))}});const a=l.toString();return a!==i?cloneDeclaration(e,a,t):[]}function prepareTransforms(e,t,i,l,a){return{"caption-side":(d=e,r=>doTransform(r,Object.values(o),d)),"text-align":e.inlineIsHorizontal?(s=e.inline,e=>{const{prop:o,value:t}=e,i=r(t),[l,a]=s;i.nodes.forEach(r=>{if("word"===r.type){const e=r.value.toLowerCase();if(e===n.End)return void(r.value=a);e===n.Start&&(r.value=l)}});const d=i.toString();return d!==t?cloneDeclaration(e,d,o):[]}):null,"block-size":transformLogicalSize(e),"inline-size":transformLogicalSize(e),"min-block-size":transformLogicalSize(e),"max-block-size":transformLogicalSize(e),"min-inline-size":transformLogicalSize(e),"max-inline-size":transformLogicalSize(e),"margin-block-start":transformSide("margin",t),"margin-block-end":transformSide("margin",i),"margin-inline-start":transformSide("margin",l),"margin-inline-end":transformSide("margin",a),"margin-block":transformSideShorthand("margin",e.block),"margin-inline":transformSideShorthand("margin",e.inline),"inset-block":transformOffsetShorthand(e.block),"inset-block-start":transformOffset(t),"inset-block-end":transformOffset(i),"inset-inline":transformOffsetShorthand(e.inline),"inset-inline-start":transformOffset(l),"inset-inline-end":transformOffset(a),inset:e=>{const n=r(e.value).nodes.filter(r=>"space"!==r.type&&"comment"!==r.type);if(n.length>4){const r=`[postcss-logical] Invalid number of values for ${e.prop}. Found ${n.length} values, expected up to 4 values.`;throw e.error(r)}let o,t,i,l;if(1===n.length)o=r.stringify(n[0]),t=o,i=o,l=o;else if(2===n.length)o=r.stringify(n[0]),t=r.stringify(n[1]),i=o,l=t;else if(3===n.length)o=r.stringify(n[0]),t=r.stringify(n[1]),l=t,i=r.stringify(n[2]);else{if(4!==n.length)return[];o=r.stringify(n[0]),t=r.stringify(n[1]),i=r.stringify(n[2]),l=r.stringify(n[3])}return[...cloneDeclaration(e,o,"top"),...cloneDeclaration(e,t,"right"),...cloneDeclaration(e,i,"bottom"),...cloneDeclaration(e,l,"left")]},"padding-block-start":transformSide("padding",t),"padding-block-end":transformSide("padding",i),"padding-inline-start":transformSide("padding",l),"padding-inline-end":transformSide("padding",a),"padding-block":transformSideShorthand("padding",e.block),"padding-inline":transformSideShorthand("padding",e.inline),"border-block-start-width":transformBorder("width",t),"border-block-end-width":transformBorder("width",i),"border-inline-start-width":transformBorder("width",l),"border-inline-end-width":transformBorder("width",a),"border-block-width":transformBorderProperty("width",e.block),"border-inline-width":transformBorderProperty("width",e.inline),"border-block-start-style":transformBorder("style",t),"border-block-end-style":transformBorder("style",i),"border-inline-start-style":transformBorder("style",l),"border-inline-end-style":transformBorder("style",a),"border-block-style":transformBorderProperty("style",e.block),"border-inline-style":transformBorderProperty("style",e.inline),"border-block-start-color":transformBorder("color",t),"border-block-end-color":transformBorder("color",i),"border-inline-start-color":transformBorder("color",l),"border-inline-end-color":transformBorder("color",a),"border-block-color":transformBorderProperty("color",e.block),"border-inline-color":transformBorderProperty("color",e.inline),"border-block":transformBorderShorthand(e.block),"border-block-start":transformBorderShorthand([t]),"border-block-end":transformBorderShorthand([i]),"border-inline":transformBorderShorthand(e.inline),"border-inline-start":transformBorderShorthand([l]),"border-inline-end":transformBorderShorthand([a]),"border-start-start-radius":transformBorderRadius(e),"border-start-end-radius":transformBorderRadius(e),"border-end-start-radius":transformBorderRadius(e),"border-end-end-radius":transformBorderRadius(e)};var s,d}const l=/var\(/i,creator=r=>{const e=Object.assign({blockDirection:t.TopToBottom,inlineDirection:t.LeftToRight},r),n=Object.values(t);if(!n.includes(e.blockDirection))throw new Error(`[postcss-logical] "blockDirection" must be one of ${n.join(", ")}`);if(!n.includes(e.inlineDirection))throw new Error(`[postcss-logical] "inlineDirection" must be one of ${n.join(", ")}`);const[o,a]=directionFlowToAxes(e.blockDirection),[s,d]=directionFlowToAxes(e.inlineDirection);if(!Object.values(i).every(r=>[o,a,s,d].includes(r)))throw new Error('[postcss-logical] "blockDirection" and "inlineDirection" must be on separate axes');const makeTransform=r=>(n,{result:o})=>{if(!r)return;if(e.ignoreCustomProperties&&l.test(n.value))return;let t;try{t=r(n)}catch(r){return void n.warn(o,r instanceof Error?r.message:String(r))}t?.length&&(t.forEach(r=>{n.cloneBefore(r)}),n.remove())},c=prepareTransforms({block:[o,a],inline:[s,d],inlineIsHorizontal:[t.LeftToRight,t.RightToLeft].includes(e.inlineDirection)},o,a,s,d);return{postcssPlugin:"postcss-logical",Declaration:{"caption-side":makeTransform(c["caption-side"]),"text-align":makeTransform(c["text-align"]),"block-size":makeTransform(c["block-size"]),"inline-size":makeTransform(c["inline-size"]),"min-block-size":makeTransform(c["min-block-size"]),"max-block-size":makeTransform(c["max-block-size"]),"min-inline-size":makeTransform(c["min-inline-size"]),"max-inline-size":makeTransform(c["max-inline-size"]),"margin-block-start":makeTransform(c["margin-block-start"]),"margin-block-end":makeTransform(c["margin-block-end"]),"margin-inline-start":makeTransform(c["margin-inline-start"]),"margin-inline-end":makeTransform(c["margin-inline-end"]),"margin-block":makeTransform(c["margin-block"]),"margin-inline":makeTransform(c["margin-inline"]),"inset-block":makeTransform(c["inset-block"]),"inset-block-start":makeTransform(c["inset-block-start"]),"inset-block-end":makeTransform(c["inset-block-end"]),"inset-inline":makeTransform(c["inset-inline"]),"inset-inline-start":makeTransform(c["inset-inline-start"]),"inset-inline-end":makeTransform(c["inset-inline-end"]),inset:makeTransform(c.inset),"padding-block-start":makeTransform(c["padding-block-start"]),"padding-block-end":makeTransform(c["padding-block-end"]),"padding-inline-start":makeTransform(c["padding-inline-start"]),"padding-inline-end":makeTransform(c["padding-inline-end"]),"padding-block":makeTransform(c["padding-block"]),"padding-inline":makeTransform(c["padding-inline"]),"border-block-start-width":makeTransform(c["border-block-start-width"]),"border-block-end-width":makeTransform(c["border-block-end-width"]),"border-inline-start-width":makeTransform(c["border-inline-start-width"]),"border-inline-end-width":makeTransform(c["border-inline-end-width"]),"border-block-width":makeTransform(c["border-block-width"]),"border-inline-width":makeTransform(c["border-inline-width"]),"border-block-start-style":makeTransform(c["border-block-start-style"]),"border-block-end-style":makeTransform(c["border-block-end-style"]),"border-inline-start-style":makeTransform(c["border-inline-start-style"]),"border-inline-end-style":makeTransform(c["border-inline-end-style"]),"border-block-style":makeTransform(c["border-block-style"]),"border-inline-style":makeTransform(c["border-inline-style"]),"border-block-start-color":makeTransform(c["border-block-start-color"]),"border-block-end-color":makeTransform(c["border-block-end-color"]),"border-inline-start-color":makeTransform(c["border-inline-start-color"]),"border-inline-end-color":makeTransform(c["border-inline-end-color"]),"border-block-color":makeTransform(c["border-block-color"]),"border-inline-color":makeTransform(c["border-inline-color"]),"border-block":makeTransform(c["border-block"]),"border-block-start":makeTransform(c["border-block-start"]),"border-block-end":makeTransform(c["border-block-end"]),"border-inline":makeTransform(c["border-inline"]),"border-inline-start":makeTransform(c["border-inline-start"]),"border-inline-end":makeTransform(c["border-inline-end"]),"border-start-start-radius":makeTransform(c["border-start-start-radius"]),"border-start-end-radius":makeTransform(c["border-start-end-radius"]),"border-end-start-radius":makeTransform(c["border-end-start-radius"]),"border-end-end-radius":makeTransform(c["border-end-end-radius"]),transition:(r,{result:n,postcss:o})=>{if(e.ignoreCustomProperties&&l.test(r.value))return;let t;try{t=transformTransition(r,o,c)}catch(e){return void r.warn(n,e instanceof Error?e.message:String(e))}t?.length&&(t.forEach(e=>{r.cloneBefore(e)}),r.remove())}}}};creator.postcss=!0;export{creator as default,creator as"module.exports"}; +import r from"postcss-value-parser";var e,n;!function(r){r.Block="block",r.Inline="inline"}(e||(e={})),function(r){r.Start="start",r.End="end"}(n||(n={}));const o={BlockStart:"block-start",BlockEnd:"block-end",InlineStart:"inline-start",InlineEnd:"inline-end"};var t,i;function directionFlowToAxes(r){switch(r){case t.TopToBottom:return[i.Top,i.Bottom];case t.BottomToTop:return[i.Bottom,i.Top];case t.RightToLeft:return[i.Right,i.Left];case t.LeftToRight:return[i.Left,i.Right]}}function cloneDeclaration(r,e,n){return r.parent&&r.parent.some(r=>"decl"===r.type&&r.prop===n&&r.value===e)?[]:[r.clone({value:e,prop:n})]}!function(r){r.TopToBottom="top-to-bottom",r.BottomToTop="bottom-to-top",r.RightToLeft="right-to-left",r.LeftToRight="left-to-right"}(t||(t={})),function(r){r.Top="top",r.Right="right",r.Bottom="bottom",r.Left="left"}(i||(i={}));function transformTransition(e,n,o){const{prop:t,value:i}=e,l=r(i),a=[];let s=[];l.nodes.forEach(r=>{if("div"===r.type&&","===r.value)return a.push(s),void(s=[]);s.push(r)}),a.push(s);for(let r=0;r1e4)throw new Error("Too many combinations when transforming a transition, reduce the complexity of your transition");for(let n=d.length-1;n>=0;n--){const o=JSON.parse(JSON.stringify(e));o[t].value=d[n].prop,a.splice(r+1,0,o)}a.splice(r,1),r++}}}const d=[];for(let r=0;r"space"!==r.type);if(n.length>2||0===n.length){const r=`[postcss-logical] Invalid number of values for ${e.prop}. Found ${n.length} values, expected 1 or 2.`;throw e.error(r)}let o,t;return 1===n.length?(o=r.stringify(n[0]),t=o):(o=r.stringify(n[0]),t=r.stringify(n[1])),[o,t]}function transformBorder(r,e){return n=>cloneDeclaration(n,n.value,`border-${e}-${r}`)}function transformBorderProperty(r,e){return n=>{const[o,t]=e,[i,l]=parseValueCouple(n);return[...cloneDeclaration(n,i,`border-${o}-${r}`),...cloneDeclaration(n,l,`border-${t}-${r}`)]}}function transformBorderShorthand(r){return e=>r.flatMap(r=>cloneDeclaration(e,e.value,`border-${r}`))}function transformBorderRadius(r){return e=>{let n;switch(e.prop.toLowerCase()){case"border-start-start-radius":n=`border-${r.inlineIsHorizontal?`${r.block[0]}-${r.inline[0]}`:`${r.inline[0]}-${r.block[0]}`}-radius`;break;case"border-start-end-radius":n=`border-${r.inlineIsHorizontal?`${r.block[0]}-${r.inline[1]}`:`${r.inline[1]}-${r.block[0]}`}-radius`;break;case"border-end-start-radius":n=`border-${r.inlineIsHorizontal?`${r.block[1]}-${r.inline[0]}`:`${r.inline[0]}-${r.block[1]}`}-radius`;break;case"border-end-end-radius":n=`border-${r.inlineIsHorizontal?`${r.block[1]}-${r.inline[1]}`:`${r.inline[1]}-${r.block[1]}`}-radius`;break}return n?cloneDeclaration(e,e.value,n):[]}}function transformLogicalSize(r){return e=>{const{value:n}=e,o=r.inlineIsHorizontal?"width":"height",t=r.inlineIsHorizontal?"height":"width",i=e.prop.toLowerCase().replace("inline-size",o).replace("block-size",t);return cloneDeclaration(e,n,i)}}function transformOffset(r){return e=>cloneDeclaration(e,e.value,r)}function transformOffsetShorthand(r){return e=>{const[n,o]=r,[t,i]=parseValueCouple(e);return[...cloneDeclaration(e,t,n),...cloneDeclaration(e,i,o)]}}function transformSide(r,e){return n=>cloneDeclaration(n,n.value,`${r}-${e}`)}function transformSideShorthand(r,e){return n=>{const[o,t]=e,[i,l]=parseValueCouple(n);return[...cloneDeclaration(n,i,`${r}-${o}`),...cloneDeclaration(n,l,`${r}-${t}`)]}}function logicalToPhysical(r,e){const[n,t]=e.block,[i,l]=e.inline;switch(r){case o.BlockStart:return n;case o.BlockEnd:return t;case o.InlineStart:return i;case o.InlineEnd:return l;default:throw new Error("Unsupported logical direction")}}function doTransform(e,n,o){const{prop:t,value:i}=e,l=r(i);l.nodes.forEach(r=>{if("word"===r.type){const e=r.value.toLowerCase();n.includes(e)&&(r.value=logicalToPhysical(e,o))}});const a=l.toString();return a!==i?cloneDeclaration(e,a,t):[]}function prepareTransforms(e,t,i,l,a){return{"caption-side":(d=e,r=>doTransform(r,Object.values(o),d)),"text-align":e.inlineIsHorizontal?(s=e.inline,e=>{const{prop:o,value:t}=e,i=r(t),[l,a]=s;i.nodes.forEach(r=>{if("word"===r.type){const e=r.value.toLowerCase();if(e===n.End)return void(r.value=a);e===n.Start&&(r.value=l)}});const d=i.toString();return d!==t?cloneDeclaration(e,d,o):[]}):null,"block-size":transformLogicalSize(e),"inline-size":transformLogicalSize(e),"min-block-size":transformLogicalSize(e),"max-block-size":transformLogicalSize(e),"min-inline-size":transformLogicalSize(e),"max-inline-size":transformLogicalSize(e),"margin-block-start":transformSide("margin",t),"margin-block-end":transformSide("margin",i),"margin-inline-start":transformSide("margin",l),"margin-inline-end":transformSide("margin",a),"margin-block":transformSideShorthand("margin",e.block),"margin-inline":transformSideShorthand("margin",e.inline),"inset-block":transformOffsetShorthand(e.block),"inset-block-start":transformOffset(t),"inset-block-end":transformOffset(i),"inset-inline":transformOffsetShorthand(e.inline),"inset-inline-start":transformOffset(l),"inset-inline-end":transformOffset(a),inset:e=>{const n=r(e.value).nodes.filter(r=>"space"!==r.type&&"comment"!==r.type);if(n.length>4){const r=`[postcss-logical] Invalid number of values for ${e.prop}. Found ${n.length} values, expected up to 4 values.`;throw e.error(r)}let o,t,i,l;if(1===n.length)o=r.stringify(n[0]),t=o,i=o,l=o;else if(2===n.length)o=r.stringify(n[0]),t=r.stringify(n[1]),i=o,l=t;else if(3===n.length)o=r.stringify(n[0]),t=r.stringify(n[1]),l=t,i=r.stringify(n[2]);else{if(4!==n.length)return[];o=r.stringify(n[0]),t=r.stringify(n[1]),i=r.stringify(n[2]),l=r.stringify(n[3])}return[...cloneDeclaration(e,o,"top"),...cloneDeclaration(e,t,"right"),...cloneDeclaration(e,i,"bottom"),...cloneDeclaration(e,l,"left")]},"padding-block-start":transformSide("padding",t),"padding-block-end":transformSide("padding",i),"padding-inline-start":transformSide("padding",l),"padding-inline-end":transformSide("padding",a),"padding-block":transformSideShorthand("padding",e.block),"padding-inline":transformSideShorthand("padding",e.inline),"border-block-start-width":transformBorder("width",t),"border-block-end-width":transformBorder("width",i),"border-inline-start-width":transformBorder("width",l),"border-inline-end-width":transformBorder("width",a),"border-block-width":transformBorderProperty("width",e.block),"border-inline-width":transformBorderProperty("width",e.inline),"border-block-start-style":transformBorder("style",t),"border-block-end-style":transformBorder("style",i),"border-inline-start-style":transformBorder("style",l),"border-inline-end-style":transformBorder("style",a),"border-block-style":transformBorderProperty("style",e.block),"border-inline-style":transformBorderProperty("style",e.inline),"border-block-start-color":transformBorder("color",t),"border-block-end-color":transformBorder("color",i),"border-inline-start-color":transformBorder("color",l),"border-inline-end-color":transformBorder("color",a),"border-block-color":transformBorderProperty("color",e.block),"border-inline-color":transformBorderProperty("color",e.inline),"border-block":transformBorderShorthand(e.block),"border-block-start":transformBorderShorthand([t]),"border-block-end":transformBorderShorthand([i]),"border-inline":transformBorderShorthand(e.inline),"border-inline-start":transformBorderShorthand([l]),"border-inline-end":transformBorderShorthand([a]),"border-start-start-radius":transformBorderRadius(e),"border-start-end-radius":transformBorderRadius(e),"border-end-start-radius":transformBorderRadius(e),"border-end-end-radius":transformBorderRadius(e)};var s,d}const l=/var\(/i,creator=r=>{const e=Object.assign({blockDirection:t.TopToBottom,inlineDirection:t.LeftToRight},r),n=Object.values(t);if(!n.includes(e.blockDirection))throw new Error(`[postcss-logical] "blockDirection" must be one of ${n.join(", ")}`);if(!n.includes(e.inlineDirection))throw new Error(`[postcss-logical] "inlineDirection" must be one of ${n.join(", ")}`);const[o,a]=directionFlowToAxes(e.blockDirection),[s,d]=directionFlowToAxes(e.inlineDirection);if(!Object.values(i).every(r=>[o,a,s,d].includes(r)))throw new Error('[postcss-logical] "blockDirection" and "inlineDirection" must be on separate axes');const makeTransform=r=>(n,{result:o})=>{if(!r)return;if(e.ignoreCustomProperties&&l.test(n.value))return;let t;try{t=r(n)}catch(r){return void n.warn(o,r instanceof Error?r.message:String(r))}t?.length&&(n.before(t),n.remove())},c=prepareTransforms({block:[o,a],inline:[s,d],inlineIsHorizontal:[t.LeftToRight,t.RightToLeft].includes(e.inlineDirection)},o,a,s,d);return{postcssPlugin:"postcss-logical",Declaration:{"caption-side":makeTransform(c["caption-side"]),"text-align":makeTransform(c["text-align"]),"block-size":makeTransform(c["block-size"]),"inline-size":makeTransform(c["inline-size"]),"min-block-size":makeTransform(c["min-block-size"]),"max-block-size":makeTransform(c["max-block-size"]),"min-inline-size":makeTransform(c["min-inline-size"]),"max-inline-size":makeTransform(c["max-inline-size"]),"margin-block-start":makeTransform(c["margin-block-start"]),"margin-block-end":makeTransform(c["margin-block-end"]),"margin-inline-start":makeTransform(c["margin-inline-start"]),"margin-inline-end":makeTransform(c["margin-inline-end"]),"margin-block":makeTransform(c["margin-block"]),"margin-inline":makeTransform(c["margin-inline"]),"inset-block":makeTransform(c["inset-block"]),"inset-block-start":makeTransform(c["inset-block-start"]),"inset-block-end":makeTransform(c["inset-block-end"]),"inset-inline":makeTransform(c["inset-inline"]),"inset-inline-start":makeTransform(c["inset-inline-start"]),"inset-inline-end":makeTransform(c["inset-inline-end"]),inset:makeTransform(c.inset),"padding-block-start":makeTransform(c["padding-block-start"]),"padding-block-end":makeTransform(c["padding-block-end"]),"padding-inline-start":makeTransform(c["padding-inline-start"]),"padding-inline-end":makeTransform(c["padding-inline-end"]),"padding-block":makeTransform(c["padding-block"]),"padding-inline":makeTransform(c["padding-inline"]),"border-block-start-width":makeTransform(c["border-block-start-width"]),"border-block-end-width":makeTransform(c["border-block-end-width"]),"border-inline-start-width":makeTransform(c["border-inline-start-width"]),"border-inline-end-width":makeTransform(c["border-inline-end-width"]),"border-block-width":makeTransform(c["border-block-width"]),"border-inline-width":makeTransform(c["border-inline-width"]),"border-block-start-style":makeTransform(c["border-block-start-style"]),"border-block-end-style":makeTransform(c["border-block-end-style"]),"border-inline-start-style":makeTransform(c["border-inline-start-style"]),"border-inline-end-style":makeTransform(c["border-inline-end-style"]),"border-block-style":makeTransform(c["border-block-style"]),"border-inline-style":makeTransform(c["border-inline-style"]),"border-block-start-color":makeTransform(c["border-block-start-color"]),"border-block-end-color":makeTransform(c["border-block-end-color"]),"border-inline-start-color":makeTransform(c["border-inline-start-color"]),"border-inline-end-color":makeTransform(c["border-inline-end-color"]),"border-block-color":makeTransform(c["border-block-color"]),"border-inline-color":makeTransform(c["border-inline-color"]),"border-block":makeTransform(c["border-block"]),"border-block-start":makeTransform(c["border-block-start"]),"border-block-end":makeTransform(c["border-block-end"]),"border-inline":makeTransform(c["border-inline"]),"border-inline-start":makeTransform(c["border-inline-start"]),"border-inline-end":makeTransform(c["border-inline-end"]),"border-start-start-radius":makeTransform(c["border-start-start-radius"]),"border-start-end-radius":makeTransform(c["border-start-end-radius"]),"border-end-start-radius":makeTransform(c["border-end-start-radius"]),"border-end-end-radius":makeTransform(c["border-end-end-radius"]),transition:(r,{result:n,postcss:o})=>{if(e.ignoreCustomProperties&&l.test(r.value))return;let t;try{t=transformTransition(r,o,c)}catch(e){return void r.warn(n,e instanceof Error?e.message:String(e))}t?.length&&(r.before(t),r.remove())}}}};creator.postcss=!0;export{creator as default,creator as"module.exports"}; diff --git a/plugins/postcss-logical/src/index.ts b/plugins/postcss-logical/src/index.ts index e1bef3672b..38907c179a 100644 --- a/plugins/postcss-logical/src/index.ts +++ b/plugins/postcss-logical/src/index.ts @@ -79,9 +79,9 @@ const creator: PluginCreator = (opts?: pluginOptions) => { return; } - transformed.forEach((transformedDecl) => { - decl.cloneBefore(transformedDecl); - }); + // Insert as a batch: resolving the reference index for every single + // insertion is quadratic for a rule with many declarations. + decl.before(transformed); decl.remove(); }; @@ -177,9 +177,9 @@ const creator: PluginCreator = (opts?: pluginOptions) => { return; } - transformed.forEach((transformedDecl) => { - decl.cloneBefore(transformedDecl); - }); + // Insert as a batch: resolving the reference index for every single + // insertion is quadratic for a rule with many declarations. + decl.before(transformed); decl.remove(); }, diff --git a/plugins/postcss-media-minmax/CHANGELOG.md b/plugins/postcss-media-minmax/CHANGELOG.md index 7aaf0116cb..d2aea420fc 100644 --- a/plugins/postcss-media-minmax/CHANGELOG.md +++ b/plugins/postcss-media-minmax/CHANGELOG.md @@ -1,5 +1,9 @@ # Changes to PostCSS Media MinMax +### Unreleased (patch) + +- Improve performance when transforming media queries with multiple range features. + ### 3.0.7 _October 1, 2026_ diff --git a/plugins/postcss-media-minmax/dist/index.mjs b/plugins/postcss-media-minmax/dist/index.mjs index cd3170f5a4..7905bde80f 100644 --- a/plugins/postcss-media-minmax/dist/index.mjs +++ b/plugins/postcss-media-minmax/dist/index.mjs @@ -1 +1 @@ -import{isFunctionNode as e,isTokenNode as t,gatherNodeAncestry as r}from"@csstools/css-parser-algorithms";import{isTokenNumeric as i,TokenType as a,NumberType as n,isTokenNumber as o,isTokenDimension as s,stringify as u,isTokenWhitespace as l}from"@csstools/css-tokenizer";import{invertComparison as c,MediaFeatureEQ as d,MediaFeatureLT as f,MediaFeatureGT as m,newMediaFeaturePlain as v,matchesRatioExactly as p,matchesRatio as h,isMediaFeatureRange as g,isMediaFeature as w,isMediaFeatureRangeNameValue as y,isMediaFeatureRangeValueName as x,isMediaInParens as N,MediaInParens as b,MediaAnd as P,MediaConditionListWithAnd as L,MediaCondition as I,isMediaConditionListWithAnd as Q,isMediaAnd as S,isMediaCondition as W,isMediaQuery as O,parseCustomMedia as M,parse as T}from"@csstools/media-query-list-parser";import{mathFunctionNames as _,calcFromComponentValues as A}from"@csstools/css-calc";const C={width:"px",height:"px","device-width":"px","device-height":"px","aspect-ratio":"","device-aspect-ratio":"",color:"","color-index":"",monochrome:"",resolution:"dpi"},E={width:!1,height:!1,"device-width":!1,"device-height":!1,"aspect-ratio":!1,"device-aspect-ratio":!1,color:!0,"color-index":!0,monochrome:!0,resolution:"dpi"};function featureNamePrefix(e){return e===f.LT||e===f.LT_OR_EQ?"max-":e===m.GT||e===m.GT_OR_EQ?"min-":""}const k={">":1,"<":-1},R=.001;function transformSingleNameValuePair(r,u,l,g){let w=l.before,y=l.after;if(g||(w=l.after,y=l.before),!g){const e=c(u);if(!1===e)return;u=e}if(u===d.EQ||u===f.LT_OR_EQ||u===m.GT_OR_EQ)return Array.isArray(l.value)?v(featureNamePrefix(u)+r,...w,...l.value.flatMap(e=>e.tokens()),...y):v(featureNamePrefix(u)+r,...w,...l.value.tokens(),...y);let x,N,b=!1;if(Array.isArray(l.value)){if(!p(l.value))return;if("aspect-ratio"!==r&&"device-aspect-ratio"!==r)return;const e=h(l.value);if(-1===e)return;b=!0,x=l.value[e[0]],N=[...l.value.slice(e[0]+1).flatMap(e=>e.tokens())]}else x=l.value,N=[];const P=C[r.toLowerCase()];if(e(x)){const e=x.getName().toLowerCase();if(_.has(e)||"env"===e){const[[e]]=A([[x]],{precision:5,toCanonicalUnits:!0});if(!(e&&t(e)&&i(e.value)&&Number.isInteger(e.value[4].value))){let e;if(void 0!==P){const t=k[u]*("px"===P?.02:R);e=[a.Dimension,`${t.toString()}${P}`,-1,-1,{value:t,unit:P,type:n.Integer}]}else if(!0===E[r]){const t=k[u];e=[a.Number,t.toString(),-1,-1,{value:t,type:n.Integer}]}else if(b){const t=k[u]*R;e=[a.Number,t.toString(),-1,-1,{value:t,type:n.Integer}]}else{const t=k[u];e=[a.Number,t.toString(),-1,-1,{value:t,type:n.Integer}]}return v(featureNamePrefix(u)+r,...w,[a.Function,"calc(",-1,-1,{value:"calc("}],[a.OpenParen,"(",-1,-1,void 0],...x.tokens().slice(1),[a.Whitespace," ",-1,-1,void 0],[a.Delim,"+",-1,-1,{value:"+"}],[a.Whitespace," ",-1,-1,void 0],e,[a.CloseParen,")",-1,-1,void 0],...N,...y)}x=e}}if(!t(x))return;let L,I=x.value,Q="";if(void 0!==P&&o(I)&&0===I[4].value)L=k[u],Q=P;else if(o(I)&&0===I[4].value)L=k[u],Q="";else if(s(I)&&0===I[4].value)L=k[u],Q=I[4].unit;else if(o(I)&&!0===E[r])L=I[4].value+k[u];else if(s(I)&&"px"===I[4].unit&&I[4].type===n.Integer)L=Number(Math.round(Number(I[4].value+.02*k[u]+"e6"))+"e-6");else{if(!s(I)&&!o(I))return;L=Number(Math.round(Number(I[4].value+R*k[u]+"e6"))+"e-6")}return Q&&(I=[a.Dimension,I[1],I[2],I[3],{value:I[4].value,unit:Q,type:I[4].type}]),I[4].value=L,s(I)?I[1]=I[4].value.toString()+I[4].unit:I[1]=I[4].value.toString(),v(featureNamePrefix(u)+r,...w,I,...N,...y)}const $=new Set(["aspect-ratio","color","color-index","device-aspect-ratio","device-height","device-width","height","horizontal-viewport-segments","monochrome","resolution","vertical-viewport-segments","width"]);function transform(e){return e.map((e,t)=>{const i=r(e);e.walk(t=>{const r=t.node;if(!g(r))return;const n=t.parent;if(!w(n))return;const o=r.name.getName().toLowerCase();if(!$.has(o))return;if(y(r)||x(r)){const e=r.operatorKind();if(!1===e)return;const t=transformSingleNameValuePair(o,e,r.value,y(r));return void(t&&(n.feature=t.feature))}const s=i.get(n);if(!N(s))return;let u=null,l=null;{const e=r.valueOneOperatorKind();if(!1===e)return;const t=transformSingleNameValuePair(o,e,r.valueOne,!1);if(!t)return;e===f.LT||e===f.LT_OR_EQ?(u=t,u.before=n.before):(l=t,l.after=n.after)}{const e=r.valueTwoOperatorKind();if(!1===e)return;const t=transformSingleNameValuePair(o,e,r.valueTwo,!0);if(!t)return;e===f.LT||e===f.LT_OR_EQ?(l=t,l.before=n.before):(u=t,u.after=n.after)}if(!u||!l)return;const c=new b(u),d=new b(l),m=getMediaConditionListWithAndFromAncestry(s,i);if(m)return m.leading===s?(m.leading=c,void(m.list=[new P([[a.Whitespace," ",-1,-1,void 0],[a.Ident,"and",-1,-1,{value:"and"}],[a.Whitespace," ",-1,-1,void 0]],d),...m.list])):void m.list.splice(m.indexOf(i.get(s)),1,new P([[a.Whitespace," ",-1,-1,void 0],[a.Ident,"and",-1,-1,{value:"and"}],[a.Whitespace," ",-1,-1,void 0]],c),new P([[a.Whitespace," ",-1,-1,void 0],[a.Ident,"and",-1,-1,{value:"and"}],[a.Whitespace," ",-1,-1,void 0]],d));const v=new L(c,[new P([[a.Whitespace," ",-1,-1,void 0],[a.Ident,"and",-1,-1,{value:"and"}],[a.Whitespace," ",-1,-1,void 0]],d)],[[a.Whitespace," ",-1,-1,void 0]]),p=getMediaConditionInShallowMediaQueryFromAncestry(s,e,i);p?p.media=v:s.media=new I(new b(new I(v),[[a.Whitespace," ",-1,-1,void 0],[a.OpenParen,"(",-1,-1,void 0]],[[a.CloseParen,")",-1,-1,void 0]]))});const n=e.tokens();return u(...n.filter((e,r)=>(0!==r||0!==t||!l(e))&&!(l(e)&&n[r+1]&&l(n[r+1]))))}).join(",")}function getMediaConditionListWithAndFromAncestry(e,t){let r=e;if(r){if(r=t.get(r),Q(r))return r;if(S(r))return r=t.get(r),Q(r)?r:void 0}}function getMediaConditionInShallowMediaQueryFromAncestry(e,t,r){let i=e;if(!i)return;if(i=r.get(i),!W(i))return;const a=i;return i=r.get(i),O(i)&&i===t?a:void 0}const creator=()=>({postcssPlugin:"postcss-media-minmax",AtRule:{media(e){if(!(e.params.includes("<")||e.params.includes(">")||e.params.includes("=")))return;const t=transform(T(e.params,{preserveInvalidMediaQueries:!0,onParseError:()=>{throw e.error(`Unable to parse media query "${e.params}"`)}}));e.params!==t&&(e.params=t)},"custom-media"(e){if(!(e.params.includes("<")||e.params.includes(">")||e.params.includes("=")))return;const t=M(e.params,{preserveInvalidMediaQueries:!0,onParseError:()=>{throw e.error(`Unable to parse media query "${e.params}"`)}});if(!t||!t.mediaQueryList)return;const r=t.mediaQueryList.map(e=>e.toString()).join(","),i=transform(t.mediaQueryList);r!==i&&(e.params=`${u(...t.name)} ${i}`)}}});creator.postcss=!0;export{creator as default,creator as"module.exports"}; +import{isFunctionNode as e,isTokenNode as t,gatherNodeAncestry as r}from"@csstools/css-parser-algorithms";import{isTokenNumeric as i,TokenType as n,NumberType as a,isTokenNumber as o,isTokenDimension as s,stringify as u,isTokenWhitespace as l}from"@csstools/css-tokenizer";import{invertComparison as c,MediaFeatureEQ as d,MediaFeatureLT as f,MediaFeatureGT as m,newMediaFeaturePlain as p,matchesRatioExactly as v,matchesRatio as h,isMediaFeatureRange as g,isMediaFeature as w,isMediaFeatureRangeNameValue as y,isMediaFeatureRangeValueName as x,isMediaInParens as N,MediaInParens as b,MediaAnd as P,MediaConditionListWithAnd as L,MediaCondition as W,isMediaConditionListWithAnd as I,isMediaAnd as M,isMediaCondition as Q,isMediaQuery as S,parseCustomMedia as O,parse as T}from"@csstools/media-query-list-parser";import{mathFunctionNames as _,calcFromComponentValues as A}from"@csstools/css-calc";const C={width:"px",height:"px","device-width":"px","device-height":"px","aspect-ratio":"","device-aspect-ratio":"",color:"","color-index":"",monochrome:"",resolution:"dpi"},E={width:!1,height:!1,"device-width":!1,"device-height":!1,"aspect-ratio":!1,"device-aspect-ratio":!1,color:!0,"color-index":!0,monochrome:!0,resolution:"dpi"};function featureNamePrefix(e){return e===f.LT||e===f.LT_OR_EQ?"max-":e===m.GT||e===m.GT_OR_EQ?"min-":""}const k={">":1,"<":-1},R=.001;function transformSingleNameValuePair(r,u,l,g){let w=l.before,y=l.after;if(g||(w=l.after,y=l.before),!g){const e=c(u);if(!1===e)return;u=e}if(u===d.EQ||u===f.LT_OR_EQ||u===m.GT_OR_EQ)return Array.isArray(l.value)?p(featureNamePrefix(u)+r,...w,...l.value.flatMap(e=>e.tokens()),...y):p(featureNamePrefix(u)+r,...w,...l.value.tokens(),...y);let x,N,b=!1;if(Array.isArray(l.value)){if(!v(l.value))return;if("aspect-ratio"!==r&&"device-aspect-ratio"!==r)return;const e=h(l.value);if(-1===e)return;b=!0,x=l.value[e[0]],N=[...l.value.slice(e[0]+1).flatMap(e=>e.tokens())]}else x=l.value,N=[];const P=C[r.toLowerCase()];if(e(x)){const e=x.getName().toLowerCase();if(_.has(e)||"env"===e){const[[e]]=A([[x]],{precision:5,toCanonicalUnits:!0});if(!(e&&t(e)&&i(e.value)&&Number.isInteger(e.value[4].value))){let e;if(void 0!==P){const t=k[u]*("px"===P?.02:R);e=[n.Dimension,`${t.toString()}${P}`,-1,-1,{value:t,unit:P,type:a.Integer}]}else if(!0===E[r]){const t=k[u];e=[n.Number,t.toString(),-1,-1,{value:t,type:a.Integer}]}else if(b){const t=k[u]*R;e=[n.Number,t.toString(),-1,-1,{value:t,type:a.Integer}]}else{const t=k[u];e=[n.Number,t.toString(),-1,-1,{value:t,type:a.Integer}]}return p(featureNamePrefix(u)+r,...w,[n.Function,"calc(",-1,-1,{value:"calc("}],[n.OpenParen,"(",-1,-1,void 0],...x.tokens().slice(1),[n.Whitespace," ",-1,-1,void 0],[n.Delim,"+",-1,-1,{value:"+"}],[n.Whitespace," ",-1,-1,void 0],e,[n.CloseParen,")",-1,-1,void 0],...N,...y)}x=e}}if(!t(x))return;let L,W=x.value,I="";if(void 0!==P&&o(W)&&0===W[4].value)L=k[u],I=P;else if(o(W)&&0===W[4].value)L=k[u],I="";else if(s(W)&&0===W[4].value)L=k[u],I=W[4].unit;else if(o(W)&&!0===E[r])L=W[4].value+k[u];else if(s(W)&&"px"===W[4].unit&&W[4].type===a.Integer)L=Number(Math.round(Number(W[4].value+.02*k[u]+"e6"))+"e-6");else{if(!s(W)&&!o(W))return;L=Number(Math.round(Number(W[4].value+R*k[u]+"e6"))+"e-6")}return I&&(W=[n.Dimension,W[1],W[2],W[3],{value:W[4].value,unit:I,type:W[4].type}]),W[4].value=L,s(W)?W[1]=W[4].value.toString()+W[4].unit:W[1]=W[4].value.toString(),p(featureNamePrefix(u)+r,...w,W,...N,...y)}const $=new Set(["aspect-ratio","color","color-index","device-aspect-ratio","device-height","device-width","height","horizontal-viewport-segments","monochrome","resolution","vertical-viewport-segments","width"]);function transform(e){return e.map((e,t)=>{const i=r(e),a=new WeakMap;e.walk(t=>{const r=t.node;if(!g(r))return;const o=t.parent;if(!w(o))return;const s=r.name.getName().toLowerCase();if(!$.has(s))return;if(y(r)||x(r)){const e=r.operatorKind();if(!1===e)return;const t=transformSingleNameValuePair(s,e,r.value,y(r));return void(t&&(o.feature=t.feature))}const u=i.get(o);if(!N(u))return;let l=null,c=null;{const e=r.valueOneOperatorKind();if(!1===e)return;const t=transformSingleNameValuePair(s,e,r.valueOne,!1);if(!t)return;e===f.LT||e===f.LT_OR_EQ?(l=t,l.before=o.before):(c=t,c.after=o.after)}{const e=r.valueTwoOperatorKind();if(!1===e)return;const t=transformSingleNameValuePair(s,e,r.valueTwo,!0);if(!t)return;e===f.LT||e===f.LT_OR_EQ?(c=t,c.before=o.before):(l=t,l.after=o.after)}if(!l||!c)return;const d=new b(l),m=new b(c),p=getMediaConditionListWithAndFromAncestry(u,i);if(p){let e=a.get(p);if(!e){const t=new Map;p.list.forEach((e,r)=>{t.set(e,r)}),e={indexes:t,inserted:0},a.set(p,e)}if(p.leading===u)return p.leading=d,p.list=[new P([[n.Whitespace," ",-1,-1,void 0],[n.Ident,"and",-1,-1,{value:"and"}],[n.Whitespace," ",-1,-1,void 0]],m),...p.list],void(e.inserted+=1);const t=i.get(u),r=e.indexes.get(t);return p.list.splice("number"==typeof r?r+e.inserted:p.list.indexOf(t),1,new P([[n.Whitespace," ",-1,-1,void 0],[n.Ident,"and",-1,-1,{value:"and"}],[n.Whitespace," ",-1,-1,void 0]],d),new P([[n.Whitespace," ",-1,-1,void 0],[n.Ident,"and",-1,-1,{value:"and"}],[n.Whitespace," ",-1,-1,void 0]],m)),void(e.inserted+=1)}const v=new L(d,[new P([[n.Whitespace," ",-1,-1,void 0],[n.Ident,"and",-1,-1,{value:"and"}],[n.Whitespace," ",-1,-1,void 0]],m)],[[n.Whitespace," ",-1,-1,void 0]]),h=getMediaConditionInShallowMediaQueryFromAncestry(u,e,i);h?h.media=v:u.media=new W(new b(new W(v),[[n.Whitespace," ",-1,-1,void 0],[n.OpenParen,"(",-1,-1,void 0]],[[n.CloseParen,")",-1,-1,void 0]]))});const o=e.tokens();return u(...o.filter((e,r)=>(0!==r||0!==t||!l(e))&&!(l(e)&&o[r+1]&&l(o[r+1]))))}).join(",")}function getMediaConditionListWithAndFromAncestry(e,t){let r=e;if(r){if(r=t.get(r),I(r))return r;if(M(r))return r=t.get(r),I(r)?r:void 0}}function getMediaConditionInShallowMediaQueryFromAncestry(e,t,r){let i=e;if(!i)return;if(i=r.get(i),!Q(i))return;const n=i;return i=r.get(i),S(i)&&i===t?n:void 0}const creator=()=>({postcssPlugin:"postcss-media-minmax",AtRule:{media(e){if(!(e.params.includes("<")||e.params.includes(">")||e.params.includes("=")))return;const t=transform(T(e.params,{preserveInvalidMediaQueries:!0,onParseError:()=>{throw e.error(`Unable to parse media query "${e.params}"`)}}));e.params!==t&&(e.params=t)},"custom-media"(e){if(!(e.params.includes("<")||e.params.includes(">")||e.params.includes("=")))return;const t=O(e.params,{preserveInvalidMediaQueries:!0,onParseError:()=>{throw e.error(`Unable to parse media query "${e.params}"`)}});if(!t||!t.mediaQueryList)return;const r=t.mediaQueryList.map(e=>e.toString()).join(","),i=transform(t.mediaQueryList);r!==i&&(e.params=`${u(...t.name)} ${i}`)}}});creator.postcss=!0;export{creator as default,creator as"module.exports"}; diff --git a/plugins/postcss-media-minmax/src/transform.ts b/plugins/postcss-media-minmax/src/transform.ts index 673d7c43fc..34f42f8187 100644 --- a/plugins/postcss-media-minmax/src/transform.ts +++ b/plugins/postcss-media-minmax/src/transform.ts @@ -23,6 +23,11 @@ export function transform(mediaQueries: Array): string { return mediaQueries.map((mediaQuery, mediaQueryIndex) => { const ancestry = gatherNodeAncestry(mediaQuery); + // Every mutation of a `MediaConditionListWithAnd` inserts exactly one extra + // `MediaAnd`. Remembering the original position of each `MediaAnd` and how + // many insertions happened before it avoids a repeated `indexOf` per match. + const andListPositions = new WeakMap, inserted: number }>(); + mediaQuery.walk((entry) => { const node = entry.node; if (!isMediaFeatureRange(node)) { @@ -116,6 +121,17 @@ export function transform(mediaQueries: Array): string { // ((300px < width < 400px) and (color)) const andList = getMediaConditionListWithAndFromAncestry(grandParent, ancestry); if (andList) { + let andListPosition = andListPositions.get(andList); + if (!andListPosition) { + const indexes = new Map(); + andList.list.forEach((item, index) => { + indexes.set(item, index); + }); + + andListPosition = { indexes, inserted: 0 }; + andListPositions.set(andList, andListPosition); + } + if (andList.leading === grandParent) { andList.leading = parensOne; @@ -131,11 +147,16 @@ export function transform(mediaQueries: Array): string { ...andList.list, ]; + andListPosition.inserted += 1; + return; } + const target = ancestry.get(grandParent) as MediaAnd; + const originalIndex = andListPosition.indexes.get(target); + andList.list.splice( - andList.indexOf(ancestry.get(grandParent) as MediaAnd) as number, + typeof originalIndex === 'number' ? originalIndex + andListPosition.inserted : andList.list.indexOf(target), 1, new MediaAnd( [ @@ -155,6 +176,8 @@ export function transform(mediaQueries: Array): string { ), ); + andListPosition.inserted += 1; + return; } diff --git a/plugins/postcss-minify/CHANGELOG.md b/plugins/postcss-minify/CHANGELOG.md index 941739fb6f..d35467b5d3 100644 --- a/plugins/postcss-minify/CHANGELOG.md +++ b/plugins/postcss-minify/CHANGELOG.md @@ -1,5 +1,9 @@ # Changes to PostCSS Minify +### Unreleased (patch) + +- Bound the internal minification cache to avoid unbounded memory growth. + ### 3.0.1 _September 25, 2026_ diff --git a/plugins/postcss-minify/dist/index.mjs b/plugins/postcss-minify/dist/index.mjs index 7c86cc8862..278e0dac60 100644 --- a/plugins/postcss-minify/dist/index.mjs +++ b/plugins/postcss-minify/dist/index.mjs @@ -1 +1 @@ -import{tokenizer as e,isTokenWhitespace as r,isTokenComment as t}from"@csstools/css-tokenizer";const s=/license|copyright|sourcemappingurl/i,o=/\s|\/\*/,n=/^layer$/i;function minify(s,n){if(!n)return n;if(s.has(n))return s.get(n);const a=n.trim();if(""===a)return s.set(n,""),"";if(!o.test(a))return s.set(n,a),a;let i=!1,c=!1,m="";const f=e({css:a});for(;!f.endOfFile();){const e=f.nextToken();r(e)?(i||(m+=" "),i=!0,c=!1):t(e)?(c||(m+="/**/"),i=!1,c=!0):(i=!1,c=!1,m+=e[1])}return s.set(n,m),m}function removeEmptyNodes(e){if("rule"===e.type){if(0===e.nodes?.length){const r=e.parent;return!!r&&(e.remove(),removeEmptyNodes(r),!0)}}else if("atrule"===e.type&&0===e.nodes?.length&&!n.test(e.name)){const r=e.parent;return!!r&&(e.remove(),removeEmptyNodes(r),!0)}return!1}function setSemicolon(e){if(!e.raws.semicolon)return;const r=e.last;"decl"===r?.type&&r.variable||(e.raws.semicolon=!1)}const creator=()=>{const e=new Map;return{postcssPlugin:"postcss-minify",OnceExit(r){r.raws.before="",r.raws.after="\n",r.walk(r=>{switch(r.type){case"atrule":if(removeEmptyNodes(r))return;return r.raws.after="",r.raws.afterName=" ",r.raws.before="",r.raws.between="",r.raws.params=void 0,setSemicolon(r),void(r.params=minify(e,r.params));case"rule":if(removeEmptyNodes(r))return;return r.raws.after="",r.raws.before="",r.raws.between="",r.raws.selector=void 0,setSemicolon(r),void(r.selector=minify(e,r.selector));case"decl":return r.prop.startsWith("--")?void(r.raws.before=""):(r.raws.before="",r.raws.between=":",r.raws.important=r.important?"!important":"",r.raws.value=void 0,void(r.value=minify(e,r.value)));case"comment":if(r.text.startsWith("!")||s.test(r.text))return void(r.raws.before="");r.remove()}})}}};creator.postcss=!0;export{creator as default,creator as"module.exports"}; +import{tokenizer as e,isTokenWhitespace as t,isTokenComment as r}from"@csstools/css-tokenizer";const s=/license|copyright|sourcemappingurl/i,o=/\s|\/\*/,n=/^layer$/i;function cacheSet(e,t,r){e.size>5e3&&e.clear(),e.set(t,r)}function minify(s,n){if(!n)return n;if(s.has(n))return s.get(n);const a=n.trim();if(""===a)return cacheSet(s,n,""),"";if(!o.test(a))return cacheSet(s,n,a),a;let i=!1,c=!1,m="";const f=e({css:a});for(;!f.endOfFile();){const e=f.nextToken();t(e)?(i||(m+=" "),i=!0,c=!1):r(e)?(c||(m+="/**/"),i=!1,c=!0):(i=!1,c=!1,m+=e[1])}return cacheSet(s,n,m),m}function removeEmptyNodes(e){if("rule"===e.type){if(0===e.nodes?.length){const t=e.parent;return!!t&&(e.remove(),removeEmptyNodes(t),!0)}}else if("atrule"===e.type&&0===e.nodes?.length&&!n.test(e.name)){const t=e.parent;return!!t&&(e.remove(),removeEmptyNodes(t),!0)}return!1}function setSemicolon(e){if(!e.raws.semicolon)return;const t=e.last;"decl"===t?.type&&t.variable||(e.raws.semicolon=!1)}const creator=()=>{const e=new Map;return{postcssPlugin:"postcss-minify",OnceExit(t){t.raws.before="",t.raws.after="\n",t.walk(t=>{switch(t.type){case"atrule":if(removeEmptyNodes(t))return;return t.raws.after="",t.raws.afterName=" ",t.raws.before="",t.raws.between="",t.raws.params=void 0,setSemicolon(t),void(t.params=minify(e,t.params));case"rule":if(removeEmptyNodes(t))return;return t.raws.after="",t.raws.before="",t.raws.between="",t.raws.selector=void 0,setSemicolon(t),void(t.selector=minify(e,t.selector));case"decl":return t.prop.startsWith("--")?void(t.raws.before=""):(t.raws.before="",t.raws.between=":",t.raws.important=t.important?"!important":"",t.raws.value=void 0,void(t.value=minify(e,t.value)));case"comment":if(t.text.startsWith("!")||s.test(t.text))return void(t.raws.before="");t.remove()}})}}};creator.postcss=!0;export{creator as default,creator as"module.exports"}; diff --git a/plugins/postcss-minify/src/index.ts b/plugins/postcss-minify/src/index.ts index 0b40df0637..571fcfa80e 100644 --- a/plugins/postcss-minify/src/index.ts +++ b/plugins/postcss-minify/src/index.ts @@ -5,6 +5,18 @@ const HAS_LEGAL_KEYWORDS_OR_SOURCE_MAP_REGEX = /license|copyright|sourcemappingu const HAS_WHITESPACE_OR_COMMENTS_REGEX = /\s|\/\*/; const IS_LAYER_REGEX = /^layer$/i; +// Bound the per-instance memoization cache so that processing many files in +// watch or bundler mode does not grow it without limit. +const MAX_CACHE_SIZE = 5000; + +function cacheSet(cache: Map, key: string, value: string): void { + if (cache.size > MAX_CACHE_SIZE) { + cache.clear(); + } + + cache.set(key, value); +} + function minify(cache: Map, x: string): string { if (!x) { return x; @@ -17,12 +29,12 @@ function minify(cache: Map, x: string): string { const y = x.trim(); if (y === '') { - cache.set(x, ''); + cacheSet(cache, x, ''); return ''; } if (!HAS_WHITESPACE_OR_COMMENTS_REGEX.test(y)) { - cache.set(x, y); + cacheSet(cache, x, y); return y; } @@ -56,7 +68,7 @@ function minify(cache: Map, x: string): string { } } - cache.set(x, minified); + cacheSet(cache, x, minified); return minified; } diff --git a/plugins/postcss-overflow-shorthand/CHANGELOG.md b/plugins/postcss-overflow-shorthand/CHANGELOG.md index 9cfb746474..e96ec07d8f 100644 --- a/plugins/postcss-overflow-shorthand/CHANGELOG.md +++ b/plugins/postcss-overflow-shorthand/CHANGELOG.md @@ -1,5 +1,9 @@ # Changes to PostCSS Overflow Shorthand +### Unreleased (patch) + +- Improved performance by inserting generated declarations as a single batch + ### 7.0.0 _January 14, 2026_ diff --git a/plugins/postcss-overflow-shorthand/dist/index.mjs b/plugins/postcss-overflow-shorthand/dist/index.mjs index 3425e99168..a6cfa082c8 100644 --- a/plugins/postcss-overflow-shorthand/dist/index.mjs +++ b/plugins/postcss-overflow-shorthand/dist/index.mjs @@ -1 +1 @@ -import e from"postcss-value-parser";const creator=o=>{const r=Object.assign({preserve:!0},o);return{postcssPlugin:"postcss-overflow-shorthand",Declaration(o,{result:t}){if("overflow"!==o.prop.toLowerCase())return;let s,a;const l=o.value;try{const o=e(l).nodes.slice().filter(e=>"comment"!==e.type&&"space"!==e.type);if(o.length<2)return;s=e.stringify(o[0]),a=e.stringify(o[1])}catch{return void o.warn(t,`Failed to parse value '${l}' as a shorthand for "overflow". Leaving the original value intact.`)}s&&a&&(s.toLowerCase()===a.toLowerCase()?o.cloneBefore({value:s}):(o.cloneBefore({prop:"overflow-x",value:s}),o.cloneBefore({prop:"overflow-y",value:a})),r.preserve||o.remove())}}};creator.postcss=!0;export{creator as default,creator as"module.exports"}; +import e from"postcss-value-parser";const creator=o=>{const r=Object.assign({preserve:!0},o);return{postcssPlugin:"postcss-overflow-shorthand",Declaration(o,{result:t}){if("overflow"!==o.prop.toLowerCase())return;let s,a;const l=o.value;try{const o=e(l).nodes.slice().filter(e=>"comment"!==e.type&&"space"!==e.type);if(o.length<2)return;s=e.stringify(o[0]),a=e.stringify(o[1])}catch{return void o.warn(t,`Failed to parse value '${l}' as a shorthand for "overflow". Leaving the original value intact.`)}s&&a&&(s.toLowerCase()===a.toLowerCase()?o.cloneBefore({value:s}):o.before([o.clone({prop:"overflow-x",value:s}),o.clone({prop:"overflow-y",value:a})]),r.preserve||o.remove())}}};creator.postcss=!0;export{creator as default,creator as"module.exports"}; diff --git a/plugins/postcss-overflow-shorthand/src/index.ts b/plugins/postcss-overflow-shorthand/src/index.ts index d617b456db..b7cc5e8a29 100644 --- a/plugins/postcss-overflow-shorthand/src/index.ts +++ b/plugins/postcss-overflow-shorthand/src/index.ts @@ -58,15 +58,18 @@ const creator: PluginCreator = (opts?: pluginOptions) => { value: xValue, }); } else { - decl.cloneBefore({ - prop: 'overflow-x', - value: xValue, - }); - - decl.cloneBefore({ - prop: 'overflow-y', - value: yValue, - }); + // Insert as a batch: resolving the reference index for every single + // insertion is quadratic for a rule with many declarations. + decl.before([ + decl.clone({ + prop: 'overflow-x', + value: xValue, + }), + decl.clone({ + prop: 'overflow-y', + value: yValue, + }), + ]); } if (!options.preserve) { diff --git a/plugins/postcss-place/CHANGELOG.md b/plugins/postcss-place/CHANGELOG.md index e454743ad3..99be504e41 100644 --- a/plugins/postcss-place/CHANGELOG.md +++ b/plugins/postcss-place/CHANGELOG.md @@ -1,5 +1,9 @@ # Changes to PostCSS Place Properties +### Unreleased (patch) + +- Improved performance by inserting generated declarations as a single batch + ### 11.0.0 _January 14, 2026_ diff --git a/plugins/postcss-place/dist/index.mjs b/plugins/postcss-place/dist/index.mjs index 26bd4fe3db..70012ac753 100644 --- a/plugins/postcss-place/dist/index.mjs +++ b/plugins/postcss-place/dist/index.mjs @@ -1 +1 @@ -import e from"postcss-value-parser";function onCSSDeclaration(o,s,r){const a=o.prop.match(t)?.[1].toLowerCase();if(!a)return;let n,l;try{n=e(o.value)}catch{o.warn(s,`Failed to parse value '${o.value}'. Leaving the original value intact.`)}void 0!==n&&(l=n.nodes.length?n.nodes.filter(e=>"word"===e.type||"function"===e.type).map(t=>e.stringify(t)):[e.stringify(n.nodes)],o.cloneBefore({prop:`align-${a}`,value:l[0]}),o.cloneBefore({prop:`justify-${a}`,value:l[1]||l[0]}),r.preserve||o.remove())}const t=/^place-(content|items|self)/i,creator=e=>{const o=Object.assign({preserve:!0},e);return{postcssPlugin:"postcss-place",Declaration(e,{result:s}){t.test(e.prop)&&onCSSDeclaration(e,s,o)}}};creator.postcss=!0;export{creator as default,creator as"module.exports"}; +import e from"postcss-value-parser";function onCSSDeclaration(o,s,r){const a=o.prop.match(t)?.[1].toLowerCase();if(!a)return;let n,l;try{n=e(o.value)}catch{o.warn(s,`Failed to parse value '${o.value}'. Leaving the original value intact.`)}void 0!==n&&(l=n.nodes.length?n.nodes.filter(e=>"word"===e.type||"function"===e.type).map(t=>e.stringify(t)):[e.stringify(n.nodes)],o.before([o.clone({prop:`align-${a}`,value:l[0]}),o.clone({prop:`justify-${a}`,value:l[1]||l[0]})]),r.preserve||o.remove())}const t=/^place-(content|items|self)/i,creator=e=>{const o=Object.assign({preserve:!0},e);return{postcssPlugin:"postcss-place",Declaration(e,{result:s}){t.test(e.prop)&&onCSSDeclaration(e,s,o)}}};creator.postcss=!0;export{creator as default,creator as"module.exports"}; diff --git a/plugins/postcss-place/src/on-css-declaration.ts b/plugins/postcss-place/src/on-css-declaration.ts index 468124f4d8..b7f08e3f9d 100644 --- a/plugins/postcss-place/src/on-css-declaration.ts +++ b/plugins/postcss-place/src/on-css-declaration.ts @@ -36,15 +36,18 @@ export function onCSSDeclaration(decl: Declaration, result: Result , options: { }); } - decl.cloneBefore({ - prop: `align-${alignment}`, - value: alignmentValues[0], - }); - - decl.cloneBefore({ - prop: `justify-${alignment}`, - value: alignmentValues[1] || alignmentValues[0], - }); + // Insert as a batch: resolving the reference index for every single + // insertion is quadratic for a rule with many declarations. + decl.before([ + decl.clone({ + prop: `align-${alignment}`, + value: alignmentValues[0], + }), + decl.clone({ + prop: `justify-${alignment}`, + value: alignmentValues[1] || alignmentValues[0], + }), + ]); // conditionally remove place-[alignment] if (!options.preserve) { diff --git a/plugins/postcss-progressive-custom-properties/CHANGELOG.md b/plugins/postcss-progressive-custom-properties/CHANGELOG.md index 3d8c1bdd94..7050c7d91d 100644 --- a/plugins/postcss-progressive-custom-properties/CHANGELOG.md +++ b/plugins/postcss-progressive-custom-properties/CHANGELOG.md @@ -1,5 +1,9 @@ # Changes to PostCSS Progressive Custom Properties +### Unreleased (patch) + +- Improve performance when inserting generated `@supports` rules. + ### 5.1.3 _September 6, 2026_ diff --git a/plugins/postcss-progressive-custom-properties/dist/index.mjs b/plugins/postcss-progressive-custom-properties/dist/index.mjs index 6d4a9996b4..c72404ab4d 100644 --- a/plugins/postcss-progressive-custom-properties/dist/index.mjs +++ b/plugins/postcss-progressive-custom-properties/dist/index.mjs @@ -1 +1 @@ -import e from"postcss-value-parser";const r=["at","bottom","center","circle","closest-corner","closest-side","ellipse","farthest-corner","farthest-side","from","in","left","right","to","top"];function doublePositionGradients(e){const t=[],a=e.value.toLowerCase();if("function"===e.type&&("conic-gradient"===a||"linear-gradient"===a||"radial-gradient"===a||"repeating-conic-gradient"===a||"repeating-linear-gradient"===a||"repeating-radial-gradient"===a)){let a=0,o=!1,i=!1;e:for(let s=0;si.includes(e.sniff)&&(e.only_on_property??r.prop)===r.prop);let p=!1;try{e(i).walk(r=>{"function"===r.type&&a.test(r.value)&&(p=!0);try{const t=e.unit(r.value);!1!==t&&(r.dimension=t)}catch{}for(let e=0;e{"decl"===t.type&&t.prop===r.prop||e.append(t.clone())}),void e.append(r.clone());const t=o.get(r.prop.toLowerCase());if(!t?.length)return void e.append(r.clone());const a=[r];let i=r.next();for(;i;)"decl"===i.type&&t.includes(i.prop)&&a.push(i),i=i.next();a.forEach(r=>{e.append(r.clone())})}const creator=()=>({postcssPlugin:"postcss-progressive-custom-properties",prepare(){const e=new WeakMap;return{postcssPlugin:"postcss-progressive-custom-properties",OnceExit(r,{postcss:t}){r.walkAtRules(e=>{if(!p.test(e.name))return;if(inSupports(e))return;const r=[],a={propNames:new Set},o=e.clone();if(e.walkDecls(e=>{let t=e.prop;if(e.variable||(t=e.prop.toLowerCase()),!a.propNames.has(t))return void a.propNames.add(t);if(!e.variable&&!i.test(e.value))return;if(s.test(e.value))return;if(d.test(e.value))return;const o=conditionsFromValue(e,!e.variable);o.length&&(r.push(...o),e.remove())}),!r.length)return;const n=Array.from(new Set(r)).sort().join(" and ");if(!n)return;const l=t.atRule({name:"supports",params:n,source:e.source,raws:{before:"\n\n",after:"\n"}});l.append(o),e.after(l)}),r.walkDecls(r=>{if(!r.parent)return;if(inKeyframes(r))return;const a=e.get(r.parent)||{conditionalRules:[],propNames:new Set,lastConditionParams:void 0,lastConditionalRule:void 0};if(e.set(r.parent,a),r.variable){if(!a.propNames.has(r.prop))return void a.propNames.add(r.prop)}else{const e=r.prop.toLowerCase();if(!a.propNames.has(e))return void a.propNames.add(e)}if(!(r.variable||"atrule"===r.parent.type&&n.test(r.parent.name)||i.test(r.value)))return;if(s.test(r.value))return;if(d.test(r.value))return;const o=conditionsFromValue(r,!(r.variable||"atrule"===r.parent.type&&n.test(r.parent.name))).join(" and ");if(!o)return;if(a.lastConditionParams!==o&&(a.lastConditionalRule=void 0),a.lastConditionalRule)return cloneDeclarations(a.lastConditionalRule,r),void r.remove();const p=[];if(o&&p.push(t.atRule({name:"supports",params:o,source:r.parent.source,raws:{before:"\n\n",after:"\n"}})),!p.length)return;for(let e=0;e{const t=e.get(r);t&&0!==t.conditionalRules.length&&t.conditionalRules.reverse().forEach(e=>{r.after(e)})})}}}});creator.postcss=!0;export{creator as default,creator as"module.exports"}; +import e from"postcss-value-parser";const r=["at","bottom","center","circle","closest-corner","closest-side","ellipse","farthest-corner","farthest-side","from","in","left","right","to","top"];function doublePositionGradients(e){const t=[],a=e.value.toLowerCase();if("function"===e.type&&("conic-gradient"===a||"linear-gradient"===a||"radial-gradient"===a||"repeating-conic-gradient"===a||"repeating-linear-gradient"===a||"repeating-radial-gradient"===a)){let a=0,o=!1,i=!1;e:for(let s=0;si.includes(e.sniff)&&(e.only_on_property??r.prop)===r.prop);let p=!1;try{e(i).walk(r=>{"function"===r.type&&a.test(r.value)&&(p=!0);try{const t=e.unit(r.value);!1!==t&&(r.dimension=t)}catch{}for(let e=0;e{"decl"===t.type&&t.prop===r.prop||e.append(t.clone())}),void e.append(r.clone());const t=o.get(r.prop.toLowerCase());if(!t?.length)return void e.append(r.clone());const a=[r];let i=r.next();for(;i;)"decl"===i.type&&t.includes(i.prop)&&a.push(i),i=i.next();a.forEach(r=>{e.append(r.clone())})}const creator=()=>({postcssPlugin:"postcss-progressive-custom-properties",prepare(){const e=new WeakMap;return{postcssPlugin:"postcss-progressive-custom-properties",OnceExit(r,{postcss:t}){r.walkAtRules(e=>{if(!p.test(e.name))return;if(inSupports(e))return;const r=[],a={propNames:new Set},o=e.clone();if(e.walkDecls(e=>{let t=e.prop;if(e.variable||(t=e.prop.toLowerCase()),!a.propNames.has(t))return void a.propNames.add(t);if(!e.variable&&!i.test(e.value))return;if(s.test(e.value))return;if(d.test(e.value))return;const o=conditionsFromValue(e,!e.variable);o.length&&(r.push(...o),e.remove())}),!r.length)return;const n=Array.from(new Set(r)).sort().join(" and ");if(!n)return;const l=t.atRule({name:"supports",params:n,source:e.source,raws:{before:"\n\n",after:"\n"}});l.append(o),e.after(l)}),r.walkDecls(r=>{if(!r.parent)return;if(inKeyframes(r))return;const a=e.get(r.parent)||{conditionalRules:[],propNames:new Set,lastConditionParams:void 0,lastConditionalRule:void 0};if(e.set(r.parent,a),r.variable){if(!a.propNames.has(r.prop))return void a.propNames.add(r.prop)}else{const e=r.prop.toLowerCase();if(!a.propNames.has(e))return void a.propNames.add(e)}if(!(r.variable||"atrule"===r.parent.type&&n.test(r.parent.name)||i.test(r.value)))return;if(s.test(r.value))return;if(d.test(r.value))return;const o=conditionsFromValue(r,!(r.variable||"atrule"===r.parent.type&&n.test(r.parent.name))).join(" and ");if(!o)return;if(a.lastConditionParams!==o&&(a.lastConditionalRule=void 0),a.lastConditionalRule)return cloneDeclarations(a.lastConditionalRule,r),void r.remove();const p=[];if(o&&p.push(t.atRule({name:"supports",params:o,source:r.parent.source,raws:{before:"\n\n",after:"\n"}})),!p.length)return;for(let e=0;e{const t=e.get(r);t&&0!==t.conditionalRules.length&&r.after(t.conditionalRules)})}}}});creator.postcss=!0;export{creator as default,creator as"module.exports"}; diff --git a/plugins/postcss-progressive-custom-properties/src/index.ts b/plugins/postcss-progressive-custom-properties/src/index.ts index c03a878086..5c9faca882 100644 --- a/plugins/postcss-progressive-custom-properties/src/index.ts +++ b/plugins/postcss-progressive-custom-properties/src/index.ts @@ -291,11 +291,8 @@ const creator: PluginCreator = () => { return; } - // rule.after reverses the at rule order. - // reversing the call order gives in the correct order overall. - state.conditionalRules.reverse().forEach((atSupports) => { - node.after(atSupports); - }); + // `after` inserts an array of nodes in order. + node.after(state.conditionalRules); }); }, }; diff --git a/plugins/postcss-property-rule-prelude-list/CHANGELOG.md b/plugins/postcss-property-rule-prelude-list/CHANGELOG.md index b4493c541f..22e0044f7a 100644 --- a/plugins/postcss-property-rule-prelude-list/CHANGELOG.md +++ b/plugins/postcss-property-rule-prelude-list/CHANGELOG.md @@ -1,5 +1,9 @@ # Changes to PostCSS Property Rule Prelude List +### Unreleased (patch) + +- Bound the total number of generated at-rules to prevent an exponential blowup from nested `@property` rules. + ### 2.0.2 _October 1, 2026_ diff --git a/plugins/postcss-property-rule-prelude-list/dist/index.mjs b/plugins/postcss-property-rule-prelude-list/dist/index.mjs index 7435cceca5..a09758759e 100644 --- a/plugins/postcss-property-rule-prelude-list/dist/index.mjs +++ b/plugins/postcss-property-rule-prelude-list/dist/index.mjs @@ -1 +1 @@ -import{parseCommaSeparatedListOfComponentValues as s,stringify as r}from"@csstools/css-parser-algorithms";import{tokenize as e}from"@csstools/css-tokenizer";const o=/^property$/i,creator=()=>({postcssPlugin:"postcss-property-rule-prelude-list",AtRule(t){if(!o.test(t.name))return;if(!t.params.includes(","))return;const p=s(e({css:t.params}));p.length<2||(p.forEach(s=>{t.cloneBefore({params:r([s]).trim()})}),t.remove())}});creator.postcss=!0;export{creator as default,creator as"module.exports"}; +import{parseCommaSeparatedListOfComponentValues as e,stringify as s}from"@csstools/css-parser-algorithms";import{tokenize as r}from"@csstools/css-tokenizer";const t=/^property$/i,creator=()=>{let o=0;return{postcssPlugin:"postcss-property-rule-prelude-list",Once(){o=0},AtRule(p){if(!t.test(p.name))return;if(!p.params.includes(","))return;const c=e(r({css:p.params}));if(!(c.length<2)){if(o+=c.length,o>5e3)throw new Error("Maximum @property expansion size exceeded, reduce the complexity of your stylesheet");c.forEach(e=>{p.cloneBefore({params:s([e]).trim()})}),p.remove()}}}};creator.postcss=!0;export{creator as default,creator as"module.exports"}; diff --git a/plugins/postcss-property-rule-prelude-list/src/index.ts b/plugins/postcss-property-rule-prelude-list/src/index.ts index 9e9b3099f2..126ee8450d 100644 --- a/plugins/postcss-property-rule-prelude-list/src/index.ts +++ b/plugins/postcss-property-rule-prelude-list/src/index.ts @@ -7,10 +7,20 @@ export type pluginOptions = never; const IS_AT_PROPERTY_REGEX = /^property$/i; +// A comma separated prelude is expanded by cloning the whole at-rule (including +// its children) once per list item. When such rules are nested, the clones are +// expanded again and the output grows exponentially with the nesting depth. +// Bound the total number of at-rules generated per stylesheet. +const MAX_EXPANDED_AT_RULES = 5_000; + const creator: PluginCreator = () => { + let expandedAtRules = 0; return { postcssPlugin: 'postcss-property-rule-prelude-list', + Once(): void { + expandedAtRules = 0; + }, AtRule(atRule): void { if (!IS_AT_PROPERTY_REGEX.test(atRule.name)) { return; @@ -25,6 +35,11 @@ const creator: PluginCreator = () => { return; } + expandedAtRules += list.length; + if (expandedAtRules > MAX_EXPANDED_AT_RULES) { + throw new Error('Maximum @property expansion size exceeded, reduce the complexity of your stylesheet'); + } + list.forEach((params) => { atRule.cloneBefore({ params: stringify([params]).trim(), diff --git a/plugins/postcss-text-decoration-shorthand/CHANGELOG.md b/plugins/postcss-text-decoration-shorthand/CHANGELOG.md index 787e65a86e..6f9240b4de 100644 --- a/plugins/postcss-text-decoration-shorthand/CHANGELOG.md +++ b/plugins/postcss-text-decoration-shorthand/CHANGELOG.md @@ -1,5 +1,9 @@ # Changes to PostCSS Text Decoration Shorthand +### Unreleased (patch) + +- Improved performance by removing the O(n) `parent.index()` lookup and sibling value scan per declaration + ### 5.0.6 _September 25, 2026_ diff --git a/plugins/postcss-text-decoration-shorthand/dist/index.mjs b/plugins/postcss-text-decoration-shorthand/dist/index.mjs index 44389a1184..9f5f0cc850 100644 --- a/plugins/postcss-text-decoration-shorthand/dist/index.mjs +++ b/plugins/postcss-text-decoration-shorthand/dist/index.mjs @@ -1 +1 @@ -import e from"postcss-value-parser";import{namedColors as t}from"@csstools/color-helpers";const o=/^text-decoration$/i,creator=t=>{const c=Object.assign({preserve:!0},t);return{postcssPlugin:"postcss-text-decoration-shorthand",prepare(){const t=new Map;return{postcssPlugin:"postcss-text-decoration-shorthand",OnceExit(){t.clear()},Declaration(i){if(!o.test(i.prop))return;const a=i.parent;if(!a)return;const u=a.index(i),d=[];for(let e=0;et.get(i.value)===e.value))return;const p=e(i.value),f=p.nodes.filter(e=>"space"!==e.type&&"comment"!==e.type);if(f.find(e=>"var"===e.value.toLowerCase()&&"function"===e.type))return;if(f.find(e=>"word"===e.type&&r.includes(e.value)))return;const v={line:[],style:null,color:null,thickness:null};for(let t=0;tt.get(i.value)===e.value))return;if(i.value.toLowerCase()===y.toLowerCase()){let e=i.next();for(;e&&"comment"===e.type;)e=e.next();return void(e&&"decl"===e.type&&"text-decoration"===e.prop.toLowerCase()||i.cloneBefore({prop:"-webkit-text-decoration",value:y}))}i.cloneBefore({prop:"text-decoration",value:y});const h=e.stringify([...v.line,{before:"",after:"",sourceIndex:0,sourceEndIndex:0,type:"space",value:" "},v.style,{before:"",after:"",sourceIndex:0,sourceEndIndex:0,type:"space",value:" "},v.color]);v.thickness&&i.cloneBefore({prop:"text-decoration",value:h}),v.thickness&&i.cloneBefore({prop:"text-decoration-thickness",value:e.stringify([v.thickness])}),t.set(h,y),c.preserve||i.remove()}}}}};function nodeIsAColor(e){return!("word"!==e.type||!e.value.startsWith("#"))||(!("word"!==e.type||!i.includes(e.value.toLowerCase()))||!("function"!==e.type||!c.includes(e.value.toLowerCase())))}creator.postcss=!0;const r=["unset","inherit","initial","revert","revert-layer"],n=["underline","overline","line-through","blink","spelling-error","grammar-error"],s=["solid","double","dotted","dashed","wavy"],l=["auto","from-font"],c=["color","color-mix","hsl","hsla","hwb","lab","lch","oklab","oklch","rgb","rgba"],i=["currentcolor","transparent",...Object.keys(t)];function genericNodeParts(){return{before:"",after:"",sourceIndex:0,sourceEndIndex:0}}export{creator as default,creator as"module.exports"}; +import e from"postcss-value-parser";import{namedColors as t}from"@csstools/color-helpers";const o=/^text-decoration$/i,creator=t=>{const c=Object.assign({preserve:!0},t);return{postcssPlugin:"postcss-text-decoration-shorthand",prepare(){const t=new Map;return{postcssPlugin:"postcss-text-decoration-shorthand",OnceExit(){t.clear()},Declaration(i){if(!o.test(i.prop))return;const a=i.parent;if(!a)return;const u=a.index(i),d=new Set;for(let e=0;e"space"!==e.type&&"comment"!==e.type);if(v.find(e=>"var"===e.value.toLowerCase()&&"function"===e.type))return;if(v.find(e=>"word"===e.type&&r.includes(e.value)))return;const y={line:[],style:null,color:null,thickness:null};for(let t=0;t = (opts?: pluginOptions) => { return; } - const ownIndex = parent.index(decl); + const selfIndex = parent.index(decl); - const siblingTextDecorationProperties: Array = []; + const siblingValues = new Set(); for (let i = 0; i < parent.nodes.length; i++) { - if (i === ownIndex) { + if (i === selfIndex) { continue; } const node = parent.nodes[i]; if (node.type === 'decl' && IS_TEXT_DECORATION_REGEX.test(node.prop)) { - siblingTextDecorationProperties.push(node); + siblingValues.add(node.value); } } - if (siblingTextDecorationProperties.some((node) => { - return convertedValues.get(decl.value) === node.value; - })) { + let convertedValue = convertedValues.get(decl.value); + if (convertedValue !== undefined && siblingValues.has(convertedValue)) { return; } @@ -202,9 +201,8 @@ const creator: PluginCreator = (opts?: pluginOptions) => { const nonShortHandValue = valueParser.stringify(data.line); convertedValues.set(decl.value, nonShortHandValue); - if (siblingTextDecorationProperties.some((node) => { - return convertedValues.get(decl.value) === node.value; - })) { + convertedValue = convertedValues.get(decl.value); + if (convertedValue !== undefined && siblingValues.has(convertedValue)) { return; }