From 87f5f27cd0d80a07c14095ce6acf9c586e2fe2d3 Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Thu, 12 Feb 2026 17:14:46 +0100 Subject: [PATCH] css-calc: add onParseError option --- packages/css-calc/CHANGELOG.md | 4 + packages/css-calc/dist/index.d.ts | 32 +- packages/css-calc/dist/index.mjs | 2 +- packages/css-calc/docs/css-calc.api.json | 330 +++++++++++++++++- .../docs/css-calc.conversionoptions.md | 3 +- packages/css-calc/docs/css-calc.md | 44 +++ .../docs/css-calc.parseerror._constructor_.md | 76 ++++ packages/css-calc/docs/css-calc.parseerror.md | 112 ++++++ .../docs/css-calc.parseerror.sourceend.md | 13 + .../docs/css-calc.parseerror.sourcestart.md | 13 + .../docs/css-calc.parseerrormessage.md | 14 + ...eerrorwithcomponentvalues._constructor_.md | 62 ++++ ...rrorwithcomponentvalues.componentvalues.md | 13 + .../css-calc.parseerrorwithcomponentvalues.md | 91 +++++ packages/css-calc/src/calculation.ts | 7 +- packages/css-calc/src/error.ts | 35 ++ packages/css-calc/src/functions/calc.ts | 24 +- packages/css-calc/src/index.ts | 5 +- packages/css-calc/src/operation/addition.ts | 32 +- packages/css-calc/src/operation/operation.ts | 3 +- .../css-calc/src/operation/subtraction.ts | 32 +- packages/css-calc/src/options.ts | 10 + packages/css-calc/test/basic/parse-error.mjs | 191 ++++++++++ packages/css-calc/test/test.mjs | 1 + 24 files changed, 1124 insertions(+), 25 deletions(-) create mode 100644 packages/css-calc/docs/css-calc.parseerror._constructor_.md create mode 100644 packages/css-calc/docs/css-calc.parseerror.md create mode 100644 packages/css-calc/docs/css-calc.parseerror.sourceend.md create mode 100644 packages/css-calc/docs/css-calc.parseerror.sourcestart.md create mode 100644 packages/css-calc/docs/css-calc.parseerrormessage.md create mode 100644 packages/css-calc/docs/css-calc.parseerrorwithcomponentvalues._constructor_.md create mode 100644 packages/css-calc/docs/css-calc.parseerrorwithcomponentvalues.componentvalues.md create mode 100644 packages/css-calc/docs/css-calc.parseerrorwithcomponentvalues.md create mode 100644 packages/css-calc/src/error.ts create mode 100644 packages/css-calc/test/basic/parse-error.mjs diff --git a/packages/css-calc/CHANGELOG.md b/packages/css-calc/CHANGELOG.md index ae9aa0a211..fae27dee5c 100644 --- a/packages/css-calc/CHANGELOG.md +++ b/packages/css-calc/CHANGELOG.md @@ -1,5 +1,9 @@ # Changes to CSS Calc +### Unreleased (minor) + +- Add `onParseError` option to `calc()`. + ### 3.0.1 _February 10, 2026_ diff --git a/packages/css-calc/dist/index.d.ts b/packages/css-calc/dist/index.d.ts index 246c78553a..28b8481509 100644 --- a/packages/css-calc/dist/index.d.ts +++ b/packages/css-calc/dist/index.d.ts @@ -1,4 +1,4 @@ -import type { ComponentValue } from '@csstools/css-parser-algorithms'; +import { ComponentValue } from '@csstools/css-parser-algorithms'; import type { TokenDimension } from '@csstools/css-tokenizer'; import type { TokenNumber } from '@csstools/css-tokenizer'; import type { TokenPercentage } from '@csstools/css-tokenizer'; @@ -8,6 +8,14 @@ export declare function calc(css: string, options?: conversionOptions): string; export declare function calcFromComponentValues(componentValuesList: Array>, options?: conversionOptions): Array>; export declare type conversionOptions = { + /** + * If a calc expression can not be solved the parse error might be reported through this callback. + * Not all cases are covered. Open an issue if you need specific errors reported. + * + * Values are recursively visited and at each nesting level an attempt is made to solve the expression. + * Errors can be reported multiple times as a result of this. + */ + onParseError?: (error: ParseError) => void; /** * Pass global values as a map of key value pairs. */ @@ -68,4 +76,26 @@ export declare type GlobalsWithStrings = Map; +/** + * Any errors are reported through the `onParseError` callback. + */ +export declare class ParseError extends Error { + /** The index of the start character of the current token. */ + sourceStart: number; + /** The index of the end character of the current token. */ + sourceEnd: number; + constructor(message: string, sourceStart: number, sourceEnd: number); +} + +export declare const ParseErrorMessage: { + UnexpectedAdditionOfDimensionOrPercentageWithNumber: string; + UnexpectedSubtractionOfDimensionOrPercentageWithNumber: string; +}; + +export declare class ParseErrorWithComponentValues extends ParseError { + /** The associated component values. */ + componentValues: Array; + constructor(message: string, componentValues: Array); +} + export { } diff --git a/packages/css-calc/dist/index.mjs b/packages/css-calc/dist/index.mjs index f7e10695fd..c76a6e3c65 100644 --- a/packages/css-calc/dist/index.mjs +++ b/packages/css-calc/dist/index.mjs @@ -1 +1 @@ -import{TokenNode as e,isTokenNode as n,isWhitespaceNode as t,isCommentNode as r,isWhiteSpaceOrCommentNode as a,isSimpleBlockNode as u,isFunctionNode as i,FunctionNode as o,sourceIndices as l,WhitespaceNode as c,parseCommaSeparatedListOfComponentValues as s,replaceComponentValues as v}from"@csstools/css-parser-algorithms";import{isTokenDimension as f,TokenType as m,NumberType as p,mutateUnit as C,isTokenNumber as d,isTokenPercentage as g,isTokenIdent as D,isTokenNumeric as N,isTokenOpenParen as B,isTokenDelim as A,isTokenComma as h,isToken as F,tokenizer as b,tokenize as w,stringify as E}from"@csstools/css-tokenizer";const I=/[A-Z]/g;function toLowerCaseAZ(e){return e.replace(I,e=>String.fromCharCode(e.charCodeAt(0)+32))}const S={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"},y=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]]),x=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]]),P=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]]),W=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]]),U=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]]),$=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]]),G=new Map([["deg",e=>360*e],["grad",e=>360*e/.9],["rad",e=>360*e/180*Math.PI],["turn",e=>e]]),R=new Map([["cm",y],["mm",O],["q",Z],["in",k],["pc",L],["pt",U],["px",$],["ms",W],["s",q],["deg",M],["grad",T],["rad",z],["turn",G],["hz",x],["khz",P]]);function convertUnit(e,n){if(!f(e))return n;if(!f(n))return n;const t=toLowerCaseAZ(e[4].unit),r=toLowerCaseAZ(n[4].unit);if(t===r)return n;const a=R.get(r);if(!a)return n;const u=a.get(t);if(!u)return n;const i=u(n[4].value),o=[m.Dimension,"",n[2],n[3],{...n[4],signCharacter:i<0?"-":void 0,type:Number.isInteger(i)?p.Integer:p.Number,value:i}];return C(o,e[4].unit),o}function toCanonicalUnit(e){if(!f(e))return e;const n=toLowerCaseAZ(e[4].unit),t=S[n];if(n===t)return e;const r=R.get(n);if(!r)return e;const a=r.get(t);if(!a)return e;const u=a(e[4].value),i=[m.Dimension,"",e[2],e[3],{...e[4],signCharacter:u<0?"-":void 0,type:Number.isInteger(u)?p.Integer:p.Number,value:u}];return C(i,t),i}function addition(n){if(2!==n.length)return-1;const t=n[0].value;let r=n[1].value;if(d(t)&&d(r)){const n=t[4].value+r[4].value;return new e([m.Number,n.toString(),t[2],r[3],{value:n,type:t[4].type===p.Integer&&r[4].type===p.Integer?p.Integer:p.Number}])}if(g(t)&&g(r)){const n=t[4].value+r[4].value;return new e([m.Percentage,n.toString()+"%",t[2],r[3],{value:n}])}if(f(t)&&f(r)&&(r=convertUnit(t,r),toLowerCaseAZ(t[4].unit)===toLowerCaseAZ(r[4].unit))){const n=t[4].value+r[4].value;return new e([m.Dimension,n.toString()+t[4].unit,t[2],r[3],{value:n,type:t[4].type===p.Integer&&r[4].type===p.Integer?p.Integer:p.Number,unit:t[4].unit}])}return-1}function division(n){if(2!==n.length)return-1;const t=n[0].value,r=n[1].value;if(d(t)&&d(r)){const n=t[4].value/r[4].value;return new e([m.Number,n.toString(),t[2],r[3],{value:n,type:Number.isInteger(n)?p.Integer:p.Number}])}if(g(t)&&d(r)){const n=t[4].value/r[4].value;return new e([m.Percentage,n.toString()+"%",t[2],r[3],{value:n}])}if(f(t)&&d(r)){const n=t[4].value/r[4].value;return new e([m.Dimension,n.toString()+t[4].unit,t[2],r[3],{value:n,type:Number.isInteger(n)?p.Integer:p.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){if(-1===e)return-1;const t=[];for(let r=0;rconvertUnit(a,e.value));if(!arrayOfSameNumeric(u))return-1;const i=u.map(e=>e[4].value),o=Math.hypot(...i);return resultToCalculation(e,a,o)}function solveMax(e,t,r){if(!t.every(n))return-1;const a=t[0].value;if(!N(a))return-1;if(!r.rawPercentages&&g(a))return-1;const u=t.map(e=>convertUnit(a,e.value));if(!arrayOfSameNumeric(u))return-1;const i=u.map(e=>e[4].value),o=Math.max(...i);return resultToCalculation(e,a,o)}function solveMin(e,t,r){if(!t.every(n))return-1;const a=t[0].value;if(!N(a))return-1;if(!r.rawPercentages&&g(a))return-1;const u=t.map(e=>convertUnit(a,e.value));if(!arrayOfSameNumeric(u))return-1;const i=u.map(e=>e[4].value),o=Math.min(...i);return resultToCalculation(e,a,o)}function solveMod(e,n,t){const r=n.value;if(!N(r))return-1;const a=convertUnit(r,t.value);if(!twoOfSameNumeric(r,a))return-1;let u;return u=0===a[4].value?Number.NaN:Number.isFinite(r[4].value)&&(Number.isFinite(a[4].value)||(a[4].value!==Number.POSITIVE_INFINITY||r[4].value!==Number.NEGATIVE_INFINITY&&!Object.is(0*r[4].value,-0))&&(a[4].value!==Number.NEGATIVE_INFINITY||r[4].value!==Number.POSITIVE_INFINITY&&!Object.is(0*r[4].value,0)))?Number.isFinite(a[4].value)?(r[4].value%a[4].value+a[4].value)%a[4].value:r[4].value:Number.NaN,resultToCalculation(e,r,u)}function solvePow(e,n,t){const r=n.value,a=t.value;if(!d(r))return-1;if(!twoOfSameNumeric(r,a))return-1;return numberToCalculation(e,Math.pow(r[4].value,a[4].value))}function solveRem(e,n,t){const r=n.value;if(!N(r))return-1;const a=convertUnit(r,t.value);if(!twoOfSameNumeric(r,a))return-1;let u;return u=0===a[4].value?Number.NaN:Number.isFinite(r[4].value)?Number.isFinite(a[4].value)?r[4].value%a[4].value:r[4].value:Number.NaN,resultToCalculation(e,r,u)}function solveRound(e,n,t,r,a){const u=t.value;if(!N(u))return-1;if(!a.rawPercentages&&g(u))return-1;const i=convertUnit(u,r.value);if(!twoOfSameNumeric(u,i))return-1;let o;if(0===i[4].value)o=Number.NaN;else if(Number.isFinite(u[4].value)||Number.isFinite(i[4].value))if(!Number.isFinite(u[4].value)&&Number.isFinite(i[4].value))o=u[4].value;else if(Number.isFinite(u[4].value)&&!Number.isFinite(i[4].value))switch(n){case"down":o=u[4].value<0?-1/0:Object.is(-0,0*u[4].value)?-0:0;break;case"up":o=u[4].value>0?1/0:Object.is(0,0*u[4].value)?0:-0;break;default:o=Object.is(0,0*u[4].value)?0:-0}else if(Number.isFinite(i[4].value))switch(n){case"down":o=Math.floor(u[4].value/i[4].value)*i[4].value;break;case"up":o=Math.ceil(u[4].value/i[4].value)*i[4].value;break;case"to-zero":o=Math.trunc(u[4].value/i[4].value)*i[4].value;break;default:{let e=Math.floor(u[4].value/i[4].value)*i[4].value,n=Math.ceil(u[4].value/i[4].value)*i[4].value;if(e>n){const t=e;e=n,n=t}const t=Math.abs(u[4].value-e),r=Math.abs(u[4].value-n);o=t===r?n:t0?1/0:-1/0:Math.tan(u),numberToCalculation(e,u)}function subtraction(n){if(2!==n.length)return-1;const t=n[0].value;let r=n[1].value;if(d(t)&&d(r)){const n=t[4].value-r[4].value;return new e([m.Number,n.toString(),t[2],r[3],{value:n,type:t[4].type===p.Integer&&r[4].type===p.Integer?p.Integer:p.Number}])}if(g(t)&&g(r)){const n=t[4].value-r[4].value;return new e([m.Percentage,n.toString()+"%",t[2],r[3],{value:n}])}if(f(t)&&f(r)&&(r=convertUnit(t,r),toLowerCaseAZ(t[4].unit)===toLowerCaseAZ(r[4].unit))){const n=t[4].value-r[4].value;return new e([m.Dimension,n.toString()+t[4].unit,t[2],r[3],{value:n,type:t[4].type===p.Integer&&r[4].type===p.Integer?p.Integer:p.Number,unit:t[4].unit}])}return-1}function solveLog(e,t){if(1===t.length){const r=t[0];if(!r||!n(r))return-1;const a=r.value;if(!d(a))return-1;return numberToCalculation(e,Math.log(a[4].value))}if(2===t.length){const r=t[0];if(!r||!n(r))return-1;const a=r.value;if(!d(a))return-1;const u=t[1];if(!u||!n(u))return-1;const i=u.value;if(!d(i))return-1;return numberToCalculation(e,Math.log(a[4].value)/Math.log(i[4].value))}return-1}const V=/^none$/i;function isNone(e){if(Array.isArray(e)){const n=e.filter(e=>!(t(e)&&r(e)));return 1===n.length&&isNone(n[0])}if(!n(e))return!1;const a=e.value;return!!D(a)&&V.test(a[4].value)}const j=String.fromCodePoint(0);function solveRandom(e,n,t,r,a,u){if(-1===n.fixed&&!u.randomCaching)return-1;u.randomCaching||(u.randomCaching={propertyName:"",propertyN:0,elementID:"",documentID:""}),u.randomCaching&&!u.randomCaching.propertyN&&(u.randomCaching.propertyN=0);const i=t.value;if(!N(i))return-1;const o=convertUnit(i,r.value);if(!twoOfSameNumeric(i,o))return-1;let l=null;if(a&&(l=convertUnit(i,a.value),!twoOfSameNumeric(i,l)))return-1;if(!Number.isFinite(i[4].value))return resultToCalculation(e,i,Number.NaN);if(!Number.isFinite(o[4].value))return resultToCalculation(e,i,Number.NaN);if(!Number.isFinite(o[4].value-i[4].value))return resultToCalculation(e,i,Number.NaN);if(l&&!Number.isFinite(l[4].value))return resultToCalculation(e,i,i[4].value);const c=-1===n.fixed?sfc32(crc32([n.dashedIdent?n.dashedIdent:`${u.randomCaching?.propertyName} ${u.randomCaching.propertyN++}`,n.elementShared?"":u.randomCaching.elementID,u.randomCaching.documentID].join(j))):()=>n.fixed;let s=i[4].value,v=o[4].value;if(s>v&&([s,v]=[v,s]),l&&(l[4].value<=0||Math.abs(s-v)/l[4].value>1e10)&&(l=null),l){const n=Math.max(l[4].value/1e3,1e-9),t=[s];let r=0;for(;;){r+=l[4].value;const e=s+r;if(!(e+nv)break}const a=c();return resultToCalculation(e,i,Number(t[Math.floor(t.length*a)].toFixed(5)))}const f=c();return resultToCalculation(e,i,Number((f*(v-s)+s).toFixed(5)))}function sfc32(e=.34944106645296036,n=.19228640875738723,t=.8784393832007205,r=.04850964319275053){return()=>{const a=((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)+a|0,(a>>>0)/4294967296}}function crc32(e){let n=0,t=0,r=0;n^=-1;for(let a=0,u=e.length;a>>8^t;return(-1^n)>>>0}const Y=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],["clamp",function clamp(t,r,u){const i=resolveGlobalsAndConstants([...t.value.filter(e=>!a(e))],r),c=[],s=[],v=[];{let e=c;for(let t=0;t!a(e)),n,t);if(-1===r)return-1;const[u,i]=r,o=variadicArguments(e,i,n,t);if(-1===o)return-1;const[l,c,s]=o;if(!l||!c)return-1;return solveRandom(e,u,l,c,s,t)}],["rem",function rem(e,n,t){return twoCommaSeparatedNodesSolver(e,n,t,solveRem)}],["round",function round(t,r,u){const i=resolveGlobalsAndConstants([...t.value.filter(e=>!a(e))],r);let o="",l=!1;const c=[],s=[];{let e=c;for(let t=0;t!a(e))],t);if(1===o.length&&n(o[0]))return{inputs:[o[0]],operation:unary};let l=0;for(;l!a(e))],n),u=solve(calc$1(calcWrapper(e,r),n,t));return-1===u?-1:u}function twoCommaSeparatedNodesSolver(e,n,t,r){const a=twoCommaSeparatedArguments(e,n,t);if(-1===a)return-1;const[u,i]=a;return r(e,u,i,t)}function twoCommaSeparatedArguments(e,t,r){const u=resolveGlobalsAndConstants([...e.value.filter(e=>!a(e))],t),i=[],o=[];{let e=i;for(let t=0;t!a(e))],r),o=[];{const t=[];let a=[];for(let e=0;e1)return-1;u.fixed=Math.max(0,Math.min(o.value[4].value,1-1e-9));continue}if("auto"!==l)if(l.startsWith("--")){if(-1!==u.fixed||u.isAuto)return-1;u.dashedIdent=l}else;else{if(-1!==u.fixed||u.dashedIdent)return-1;u.isAuto=!0}}else{if(-1!==u.fixed)return-1;u.elementShared=!0}}return-1}function calcWrapper(e,n){return new o([m.Function,"calc(",e.name[2],e.name[3],{value:"calc"}],[m.CloseParen,")",e.endToken[2],e.endToken[3],void 0],n)}function maxWrapper(n,t,r){return new o([m.Function,"max(",n.name[2],n.name[3],{value:"max"}],[m.CloseParen,")",n.endToken[2],n.endToken[3],void 0],[t,new e([m.Comma,",",...l(t),void 0]),r])}function patchNaN(n){if(-1===n)return-1;if(i(n))return n;const t=n.value;return N(t)&&Number.isNaN(t[4].value)?d(t)?new o([m.Function,"calc(",t[2],t[3],{value:"calc"}],[m.CloseParen,")",t[2],t[3],void 0],[new e([m.Ident,"NaN",t[2],t[3],{value:"NaN"}])]):f(t)?new o([m.Function,"calc(",t[2],t[3],{value:"calc"}],[m.CloseParen,")",t[2],t[3],void 0],[new e([m.Ident,"NaN",t[2],t[3],{value:"NaN"}]),new c([[m.Whitespace," ",t[2],t[3],void 0]]),new e([m.Delim,"*",t[2],t[3],{value:"*"}]),new c([[m.Whitespace," ",t[2],t[3],void 0]]),new e([m.Dimension,"1"+t[4].unit,t[2],t[3],{value:1,type:p.Integer,unit:t[4].unit}])]):g(t)?new o([m.Function,"calc(",t[2],t[3],{value:"calc"}],[m.CloseParen,")",t[2],t[3],void 0],[new e([m.Ident,"NaN",t[2],t[3],{value:"NaN"}]),new c([[m.Whitespace," ",t[2],t[3],void 0]]),new e([m.Delim,"*",t[2],t[3],{value:"*"}]),new c([[m.Whitespace," ",t[2],t[3],void 0]]),new e([m.Percentage,"1%",t[2],t[3],{value:1}])]):-1:n}function patchInfinity(n){if(-1===n)return-1;if(i(n))return n;const t=n.value;if(!N(t))return n;if(Number.isFinite(t[4].value)||Number.isNaN(t[4].value))return n;let r="";return Number.NEGATIVE_INFINITY===t[4].value&&(r="-"),d(t)?new o([m.Function,"calc(",t[2],t[3],{value:"calc"}],[m.CloseParen,")",t[2],t[3],void 0],[new e([m.Ident,r+"infinity",t[2],t[3],{value:r+"infinity"}])]):f(t)?new o([m.Function,"calc(",t[2],t[3],{value:"calc"}],[m.CloseParen,")",t[2],t[3],void 0],[new e([m.Ident,r+"infinity",t[2],t[3],{value:r+"infinity"}]),new c([[m.Whitespace," ",t[2],t[3],void 0]]),new e([m.Delim,"*",t[2],t[3],{value:"*"}]),new c([[m.Whitespace," ",t[2],t[3],void 0]]),new e([m.Dimension,"1"+t[4].unit,t[2],t[3],{value:1,type:p.Integer,unit:t[4].unit}])]):new o([m.Function,"calc(",t[2],t[3],{value:"calc"}],[m.CloseParen,")",t[2],t[3],void 0],[new e([m.Ident,r+"infinity",t[2],t[3],{value:r+"infinity"}]),new c([[m.Whitespace," ",t[2],t[3],void 0]]),new e([m.Delim,"*",t[2],t[3],{value:"*"}]),new c([[m.Whitespace," ",t[2],t[3],void 0]]),new e([m.Percentage,"1%",t[2],t[3],{value:1}])])}function patchMinusZero(e){if(-1===e)return-1;if(i(e))return e;const n=e.value;return N(n)&&Object.is(-0,n[4].value)?("-0"===n[1]||(g(n)?n[1]="-0%":f(n)?n[1]="-0"+n[4].unit:n[1]="-0"),e):e}function patchPrecision(e,n=13){if(-1===e)return-1;if(n<=0)return e;if(i(e))return e;const t=e.value;if(!N(t))return e;if(Number.isInteger(t[4].value))return e;const r=Number(t[4].value.toFixed(n)).toString();return d(t)?t[1]=r:g(t)?t[1]=r+"%":f(t)&&(t[1]=r+t[4].unit),e}function patchCanonicalUnit(e){return-1===e?-1:i(e)?e:f(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(F(r))n.set(t,r);else if("string"==typeof r){const e=b({css:r}),a=e.nextToken();if(e.nextToken(),!e.endOfFile())continue;if(!N(a))continue;n.set(t,a);continue}return n}function calc(e,n){return calcFromComponentValues(s(w({css:e}),{}),n).map(e=>e.map(e=>E(...e.tokens())).join("")).join(",")}function calcFromComponentValues(e,n){const t=tokenizeGlobals(n?.globals);return v(e,e=>{if(!i(e))return;const r=Y.get(e.getName().toLowerCase());if(!r)return;const a=patchCalcResult(solve(r(e,t,n??{})),n);return-1!==a?a:void 0})}const H=new Set(Y.keys());export{calc,calcFromComponentValues,H as mathFunctionNames}; +import{sourceIndices as e,TokenNode as n,isTokenNode as t,isWhitespaceNode as r,isCommentNode as a,isWhiteSpaceOrCommentNode as u,isSimpleBlockNode as o,isFunctionNode as i,FunctionNode as l,WhitespaceNode as c,parseCommaSeparatedListOfComponentValues as s,replaceComponentValues as v}from"@csstools/css-parser-algorithms";import{isTokenDimension as f,TokenType as m,NumberType as p,mutateUnit as C,isTokenNumber as d,isTokenPercentage as g,isTokenIdent as D,isTokenNumeric as N,isTokenOpenParen as h,isTokenDelim as B,isTokenComma as A,isToken as b,tokenizer as F,tokenize as w,stringify 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."},I=/[A-Z]/g;function toLowerCaseAZ(e){return e.replace(I,e=>String.fromCharCode(e.charCodeAt(0)+32))}const y={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"},M=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]]),T=new Map([["deg",e=>e],["grad",e=>e/.9],["rad",e=>e/180*Math.PI],["turn",e=>e/360]]),x=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]]),O=new Map([["hz",e=>1e3*e],["khz",e=>e]]),W=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]]),Z=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]]),V=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]]),G=new Map([["deg",e=>360*e],["grad",e=>360*e/.9],["rad",e=>360*e/180*Math.PI],["turn",e=>e]]),R=new Map([["cm",M],["mm",W],["q",V],["in",k],["pc",L],["pt",$],["px",Z],["ms",U],["s",q],["deg",T],["grad",x],["rad",z],["turn",G],["hz",P],["khz",O]]);function convertUnit(e,n){if(!f(e))return n;if(!f(n))return n;const t=toLowerCaseAZ(e[4].unit),r=toLowerCaseAZ(n[4].unit);if(t===r)return n;const a=R.get(r);if(!a)return n;const u=a.get(t);if(!u)return n;const o=u(n[4].value),i=[m.Dimension,"",n[2],n[3],{...n[4],signCharacter:o<0?"-":void 0,type:Number.isInteger(o)?p.Integer:p.Number,value:o}];return C(i,e[4].unit),i}function toCanonicalUnit(e){if(!f(e))return e;const n=toLowerCaseAZ(e[4].unit),t=y[n];if(n===t)return e;const r=R.get(n);if(!r)return e;const a=r.get(t);if(!a)return e;const u=a(e[4].value),o=[m.Dimension,"",e[2],e[3],{...e[4],signCharacter:u<0?"-":void 0,type:Number.isInteger(u)?p.Integer:p.Number,value:u}];return C(o,t),o}function addition(e,t){if(2!==e.length)return-1;const r=e[0].value;let a=e[1].value;if(d(r)&&d(a)){const e=r[4].value+a[4].value;return new n([m.Number,e.toString(),r[2],a[3],{value:e,type:r[4].type===p.Integer&&a[4].type===p.Integer?p.Integer:p.Number}])}if(g(r)&&g(a)){const e=r[4].value+a[4].value;return new n([m.Percentage,e.toString()+"%",r[2],a[3],{value:e}])}if(f(r)&&f(a)&&(a=convertUnit(r,a),toLowerCaseAZ(r[4].unit)===toLowerCaseAZ(a[4].unit))){const e=r[4].value+a[4].value;return new n([m.Dimension,e.toString()+r[4].unit,r[2],a[3],{value:e,type:r[4].type===p.Integer&&a[4].type===p.Integer?p.Integer:p.Number,unit:r[4].unit}])}return(d(r)&&(f(a)||g(a))||d(a)&&(f(r)||g(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(d(t)&&d(r)){const e=t[4].value/r[4].value;return new n([m.Number,e.toString(),t[2],r[3],{value:e,type:Number.isInteger(e)?p.Integer:p.Number}])}if(g(t)&&d(r)){const e=t[4].value/r[4].value;return new n([m.Percentage,e.toString()+"%",t[2],r[3],{value:e}])}if(f(t)&&d(r)){const e=t[4].value/r[4].value;return new n([m.Dimension,e.toString()+t[4].unit,t[2],r[3],{value:e,type:Number.isInteger(e)?p.Integer:p.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=[];for(let a=0;aconvertUnit(a,e.value));if(!arrayOfSameNumeric(u))return-1;const o=u.map(e=>e[4].value),i=Math.hypot(...o);return resultToCalculation(e,a,i)}function solveMax(e,n,r){if(!n.every(t))return-1;const a=n[0].value;if(!N(a))return-1;if(!r.rawPercentages&&g(a))return-1;const u=n.map(e=>convertUnit(a,e.value));if(!arrayOfSameNumeric(u))return-1;const o=u.map(e=>e[4].value),i=Math.max(...o);return resultToCalculation(e,a,i)}function solveMin(e,n,r){if(!n.every(t))return-1;const a=n[0].value;if(!N(a))return-1;if(!r.rawPercentages&&g(a))return-1;const u=n.map(e=>convertUnit(a,e.value));if(!arrayOfSameNumeric(u))return-1;const o=u.map(e=>e[4].value),i=Math.min(...o);return resultToCalculation(e,a,i)}function solveMod(e,n,t){const r=n.value;if(!N(r))return-1;const a=convertUnit(r,t.value);if(!twoOfSameNumeric(r,a))return-1;let u;return u=0===a[4].value?Number.NaN:Number.isFinite(r[4].value)&&(Number.isFinite(a[4].value)||(a[4].value!==Number.POSITIVE_INFINITY||r[4].value!==Number.NEGATIVE_INFINITY&&!Object.is(0*r[4].value,-0))&&(a[4].value!==Number.NEGATIVE_INFINITY||r[4].value!==Number.POSITIVE_INFINITY&&!Object.is(0*r[4].value,0)))?Number.isFinite(a[4].value)?(r[4].value%a[4].value+a[4].value)%a[4].value:r[4].value:Number.NaN,resultToCalculation(e,r,u)}function solvePow(e,n,t){const r=n.value,a=t.value;if(!d(r))return-1;if(!twoOfSameNumeric(r,a))return-1;return numberToCalculation(e,Math.pow(r[4].value,a[4].value))}function solveRem(e,n,t){const r=n.value;if(!N(r))return-1;const a=convertUnit(r,t.value);if(!twoOfSameNumeric(r,a))return-1;let u;return u=0===a[4].value?Number.NaN:Number.isFinite(r[4].value)?Number.isFinite(a[4].value)?r[4].value%a[4].value:r[4].value:Number.NaN,resultToCalculation(e,r,u)}function solveRound(e,n,t,r,a){const u=t.value;if(!N(u))return-1;if(!a.rawPercentages&&g(u))return-1;const o=convertUnit(u,r.value);if(!twoOfSameNumeric(u,o))return-1;let i;if(0===o[4].value)i=Number.NaN;else if(Number.isFinite(u[4].value)||Number.isFinite(o[4].value))if(!Number.isFinite(u[4].value)&&Number.isFinite(o[4].value))i=u[4].value;else if(Number.isFinite(u[4].value)&&!Number.isFinite(o[4].value))switch(n){case"down":i=u[4].value<0?-1/0:Object.is(-0,0*u[4].value)?-0:0;break;case"up":i=u[4].value>0?1/0:Object.is(0,0*u[4].value)?0:-0;break;default:i=Object.is(0,0*u[4].value)?0:-0}else if(Number.isFinite(o[4].value))switch(n){case"down":i=Math.floor(u[4].value/o[4].value)*o[4].value;break;case"up":i=Math.ceil(u[4].value/o[4].value)*o[4].value;break;case"to-zero":i=Math.trunc(u[4].value/o[4].value)*o[4].value;break;default:{let e=Math.floor(u[4].value/o[4].value)*o[4].value,n=Math.ceil(u[4].value/o[4].value)*o[4].value;if(e>n){const t=e;e=n,n=t}const t=Math.abs(u[4].value-e),r=Math.abs(u[4].value-n);i=t===r?n:t0?1/0:-1/0:Math.tan(u),numberToCalculation(e,u)}function subtraction(e,t){if(2!==e.length)return-1;const r=e[0].value;let a=e[1].value;if(d(r)&&d(a)){const e=r[4].value-a[4].value;return new n([m.Number,e.toString(),r[2],a[3],{value:e,type:r[4].type===p.Integer&&a[4].type===p.Integer?p.Integer:p.Number}])}if(g(r)&&g(a)){const e=r[4].value-a[4].value;return new n([m.Percentage,e.toString()+"%",r[2],a[3],{value:e}])}if(f(r)&&f(a)&&(a=convertUnit(r,a),toLowerCaseAZ(r[4].unit)===toLowerCaseAZ(a[4].unit))){const e=r[4].value-a[4].value;return new n([m.Dimension,e.toString()+r[4].unit,r[2],a[3],{value:e,type:r[4].type===p.Integer&&a[4].type===p.Integer?p.Integer:p.Number,unit:r[4].unit}])}return(d(r)&&(f(a)||g(a))||d(a)&&(f(r)||g(r)))&&t.onParseError?.(new ParseErrorWithComponentValues(S.UnexpectedSubtractionOfDimensionOrPercentageWithNumber,e)),-1}function solveLog(e,n){if(1===n.length){const r=n[0];if(!r||!t(r))return-1;const a=r.value;if(!d(a))return-1;return numberToCalculation(e,Math.log(a[4].value))}if(2===n.length){const r=n[0];if(!r||!t(r))return-1;const a=r.value;if(!d(a))return-1;const u=n[1];if(!u||!t(u))return-1;const o=u.value;if(!d(o))return-1;return numberToCalculation(e,Math.log(a[4].value)/Math.log(o[4].value))}return-1}const j=/^none$/i;function isNone(e){if(Array.isArray(e)){const n=e.filter(e=>!(r(e)&&a(e)));return 1===n.length&&isNone(n[0])}if(!t(e))return!1;const n=e.value;return!!D(n)&&j.test(n[4].value)}const Y=String.fromCodePoint(0);function solveRandom(e,n,t,r,a,u){if(-1===n.fixed&&!u.randomCaching)return-1;u.randomCaching||(u.randomCaching={propertyName:"",propertyN:0,elementID:"",documentID:""}),u.randomCaching&&!u.randomCaching.propertyN&&(u.randomCaching.propertyN=0);const o=t.value;if(!N(o))return-1;const i=convertUnit(o,r.value);if(!twoOfSameNumeric(o,i))return-1;let l=null;if(a&&(l=convertUnit(o,a.value),!twoOfSameNumeric(o,l)))return-1;if(!Number.isFinite(o[4].value))return resultToCalculation(e,o,Number.NaN);if(!Number.isFinite(i[4].value))return resultToCalculation(e,o,Number.NaN);if(!Number.isFinite(i[4].value-o[4].value))return resultToCalculation(e,o,Number.NaN);if(l&&!Number.isFinite(l[4].value))return resultToCalculation(e,o,o[4].value);const c=-1===n.fixed?sfc32(crc32([n.dashedIdent?n.dashedIdent:`${u.randomCaching?.propertyName} ${u.randomCaching.propertyN++}`,n.elementShared?"":u.randomCaching.elementID,u.randomCaching.documentID].join(Y))):()=>n.fixed;let s=o[4].value,v=i[4].value;if(s>v&&([s,v]=[v,s]),l&&(l[4].value<=0||Math.abs(s-v)/l[4].value>1e10)&&(l=null),l){const n=Math.max(l[4].value/1e3,1e-9),t=[s];let r=0;for(;;){r+=l[4].value;const e=s+r;if(!(e+nv)break}const a=c();return resultToCalculation(e,o,Number(t[Math.floor(t.length*a)].toFixed(5)))}const f=c();return resultToCalculation(e,o,Number((f*(v-s)+s).toFixed(5)))}function sfc32(e=.34944106645296036,n=.19228640875738723,t=.8784393832007205,r=.04850964319275053){return()=>{const a=((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)+a|0,(a>>>0)/4294967296}}function crc32(e){let n=0,t=0,r=0;n^=-1;for(let a=0,u=e.length;a>>8^t;return(-1^n)>>>0}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],["clamp",function clamp(r,a,o){const i=resolveGlobalsAndConstants([...r.value.filter(e=>!u(e))],a),c=[],s=[],v=[];{let e=c;for(let n=0;n!u(e)),n,t);if(-1===r)return-1;const[a,o]=r,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,r,a){const o=resolveGlobalsAndConstants([...e.value.filter(e=>!u(e))],r);let i="",l=!1;const c=[],s=[];{let e=c;for(let n=0;n!u(e))],n);if(1===a.length&&t(a[0]))return{inputs:[a[0]],operation:unary};let l=0;for(;l!u(e))],n),a=solve(calc$1(calcWrapper(e,r),n,t),t);return-1===a?-1:a}function twoCommaSeparatedNodesSolver(e,n,t,r){const a=twoCommaSeparatedArguments(e,n,t);if(-1===a)return-1;const[u,o]=a;return r(e,u,o,t)}function twoCommaSeparatedArguments(e,n,r){const a=resolveGlobalsAndConstants([...e.value.filter(e=>!u(e))],n),o=[],i=[];{let e=o;for(let n=0;n!u(e))],r),i=[];{const n=[];let u=[];for(let e=0;e1)return-1;u.fixed=Math.max(0,Math.min(i.value[4].value,1-1e-9));continue}if("auto"!==l)if(l.startsWith("--")){if(-1!==u.fixed||u.isAuto)return-1;u.dashedIdent=l}else;else{if(-1!==u.fixed||u.dashedIdent)return-1;u.isAuto=!0}}else{if(-1!==u.fixed)return-1;u.elementShared=!0}}return-1}function calcWrapper(e,n){return new l([m.Function,"calc(",e.name[2],e.name[3],{value:"calc"}],[m.CloseParen,")",e.endToken[2],e.endToken[3],void 0],n)}function maxWrapper(t,r,a){return new l([m.Function,"max(",t.name[2],t.name[3],{value:"max"}],[m.CloseParen,")",t.endToken[2],t.endToken[3],void 0],[r,new n([m.Comma,",",...e(r),void 0]),a])}function patchNaN(e){if(-1===e)return-1;if(i(e))return e;const t=e.value;return N(t)&&Number.isNaN(t[4].value)?d(t)?new l([m.Function,"calc(",t[2],t[3],{value:"calc"}],[m.CloseParen,")",t[2],t[3],void 0],[new n([m.Ident,"NaN",t[2],t[3],{value:"NaN"}])]):f(t)?new l([m.Function,"calc(",t[2],t[3],{value:"calc"}],[m.CloseParen,")",t[2],t[3],void 0],[new n([m.Ident,"NaN",t[2],t[3],{value:"NaN"}]),new c([[m.Whitespace," ",t[2],t[3],void 0]]),new n([m.Delim,"*",t[2],t[3],{value:"*"}]),new c([[m.Whitespace," ",t[2],t[3],void 0]]),new n([m.Dimension,"1"+t[4].unit,t[2],t[3],{value:1,type:p.Integer,unit:t[4].unit}])]):g(t)?new l([m.Function,"calc(",t[2],t[3],{value:"calc"}],[m.CloseParen,")",t[2],t[3],void 0],[new n([m.Ident,"NaN",t[2],t[3],{value:"NaN"}]),new c([[m.Whitespace," ",t[2],t[3],void 0]]),new n([m.Delim,"*",t[2],t[3],{value:"*"}]),new c([[m.Whitespace," ",t[2],t[3],void 0]]),new n([m.Percentage,"1%",t[2],t[3],{value:1}])]):-1:e}function patchInfinity(e){if(-1===e)return-1;if(i(e))return e;const t=e.value;if(!N(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="-"),d(t)?new l([m.Function,"calc(",t[2],t[3],{value:"calc"}],[m.CloseParen,")",t[2],t[3],void 0],[new n([m.Ident,r+"infinity",t[2],t[3],{value:r+"infinity"}])]):f(t)?new l([m.Function,"calc(",t[2],t[3],{value:"calc"}],[m.CloseParen,")",t[2],t[3],void 0],[new n([m.Ident,r+"infinity",t[2],t[3],{value:r+"infinity"}]),new c([[m.Whitespace," ",t[2],t[3],void 0]]),new n([m.Delim,"*",t[2],t[3],{value:"*"}]),new c([[m.Whitespace," ",t[2],t[3],void 0]]),new n([m.Dimension,"1"+t[4].unit,t[2],t[3],{value:1,type:p.Integer,unit:t[4].unit}])]):new l([m.Function,"calc(",t[2],t[3],{value:"calc"}],[m.CloseParen,")",t[2],t[3],void 0],[new n([m.Ident,r+"infinity",t[2],t[3],{value:r+"infinity"}]),new c([[m.Whitespace," ",t[2],t[3],void 0]]),new n([m.Delim,"*",t[2],t[3],{value:"*"}]),new c([[m.Whitespace," ",t[2],t[3],void 0]]),new n([m.Percentage,"1%",t[2],t[3],{value:1}])])}function patchMinusZero(e){if(-1===e)return-1;if(i(e))return e;const n=e.value;return N(n)&&Object.is(-0,n[4].value)?("-0"===n[1]||(g(n)?n[1]="-0%":f(n)?n[1]="-0"+n[4].unit:n[1]="-0"),e):e}function patchPrecision(e,n=13){if(-1===e)return-1;if(n<=0)return e;if(i(e))return e;const t=e.value;if(!N(t))return e;if(Number.isInteger(t[4].value))return e;const r=Number(t[4].value.toFixed(n)).toString();return d(t)?t[1]=r:g(t)?t[1]=r+"%":f(t)&&(t[1]=r+t[4].unit),e}function patchCanonicalUnit(e){return-1===e?-1:i(e)?e:f(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=F({css:r}),a=e.nextToken();if(e.nextToken(),!e.endOfFile())continue;if(!N(a))continue;n.set(t,a);continue}return n}function calc(e,n){return calcFromComponentValues(s(w({css:e}),{}),n).map(e=>e.map(e=>E(...e.tokens())).join("")).join(",")}function calcFromComponentValues(e,n){const t=tokenizeGlobals(n?.globals);return v(e,e=>{if(!i(e))return;const r=_.get(e.getName().toLowerCase());if(!r)return;const a=patchCalcResult(solve(r(e,t,n??{}),n??{}),n);return-1!==a?a:void 0})}const J=new Set(_.keys());export{ParseError,S as ParseErrorMessage,ParseErrorWithComponentValues,calc,calcFromComponentValues,J as mathFunctionNames}; diff --git a/packages/css-calc/docs/css-calc.api.json b/packages/css-calc/docs/css-calc.api.json index de9962e0fa..bed7fb51d7 100644 --- a/packages/css-calc/docs/css-calc.api.json +++ b/packages/css-calc/docs/css-calc.api.json @@ -368,7 +368,16 @@ }, { "kind": "Content", - "text": "{\n globals?: " + "text": "{\n onParseError?: (error: " + }, + { + "kind": "Reference", + "text": "ParseError", + "canonicalReference": "@csstools/css-calc!ParseError:class" + }, + { + "kind": "Content", + "text": ") => void;\n globals?: " }, { "kind": "Reference", @@ -389,7 +398,7 @@ "name": "conversionOptions", "typeTokenRange": { "startIndex": 1, - "endIndex": 4 + "endIndex": 6 } }, { @@ -477,6 +486,323 @@ "startIndex": 1, "endIndex": 3 } + }, + { + "kind": "Class", + "canonicalReference": "@csstools/css-calc!ParseError:class", + "docComment": "/**\n * Any errors are reported through the `onParseError` callback.\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "export declare class ParseError extends " + }, + { + "kind": "Reference", + "text": "Error", + "canonicalReference": "!Error:interface" + }, + { + "kind": "Content", + "text": " " + } + ], + "fileUrlPath": "dist/_types/error.d.ts", + "releaseTag": "Public", + "isAbstract": false, + "name": "ParseError", + "preserveMemberOrder": false, + "members": [ + { + "kind": "Constructor", + "canonicalReference": "@csstools/css-calc!ParseError:constructor(1)", + "docComment": "/**\n * Constructs a new instance of the `ParseError` class\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "constructor(message: " + }, + { + "kind": "Content", + "text": "string" + }, + { + "kind": "Content", + "text": ", sourceStart: " + }, + { + "kind": "Content", + "text": "number" + }, + { + "kind": "Content", + "text": ", sourceEnd: " + }, + { + "kind": "Content", + "text": "number" + }, + { + "kind": "Content", + "text": ");" + } + ], + "releaseTag": "Public", + "isProtected": false, + "overloadIndex": 1, + "parameters": [ + { + "parameterName": "message", + "parameterTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "isOptional": false + }, + { + "parameterName": "sourceStart", + "parameterTypeTokenRange": { + "startIndex": 3, + "endIndex": 4 + }, + "isOptional": false + }, + { + "parameterName": "sourceEnd", + "parameterTypeTokenRange": { + "startIndex": 5, + "endIndex": 6 + }, + "isOptional": false + } + ] + }, + { + "kind": "Property", + "canonicalReference": "@csstools/css-calc!ParseError#sourceEnd:member", + "docComment": "/**\n * The index of the end character of the current token.\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "sourceEnd: " + }, + { + "kind": "Content", + "text": "number" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isReadonly": false, + "isOptional": false, + "releaseTag": "Public", + "name": "sourceEnd", + "propertyTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "isStatic": false, + "isProtected": false, + "isAbstract": false + }, + { + "kind": "Property", + "canonicalReference": "@csstools/css-calc!ParseError#sourceStart:member", + "docComment": "/**\n * The index of the start character of the current token.\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "sourceStart: " + }, + { + "kind": "Content", + "text": "number" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isReadonly": false, + "isOptional": false, + "releaseTag": "Public", + "name": "sourceStart", + "propertyTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "isStatic": false, + "isProtected": false, + "isAbstract": false + } + ], + "extendsTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "implementsTokenRanges": [] + }, + { + "kind": "Variable", + "canonicalReference": "@csstools/css-calc!ParseErrorMessage:var", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "ParseErrorMessage: " + }, + { + "kind": "Content", + "text": "{\n UnexpectedAdditionOfDimensionOrPercentageWithNumber: string;\n UnexpectedSubtractionOfDimensionOrPercentageWithNumber: string;\n}" + } + ], + "fileUrlPath": "dist/_types/error.d.ts", + "isReadonly": true, + "releaseTag": "Public", + "name": "ParseErrorMessage", + "variableTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + } + }, + { + "kind": "Class", + "canonicalReference": "@csstools/css-calc!ParseErrorWithComponentValues:class", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "export declare class ParseErrorWithComponentValues extends " + }, + { + "kind": "Reference", + "text": "ParseError", + "canonicalReference": "@csstools/css-calc!ParseError:class" + }, + { + "kind": "Content", + "text": " " + } + ], + "fileUrlPath": "dist/_types/error.d.ts", + "releaseTag": "Public", + "isAbstract": false, + "name": "ParseErrorWithComponentValues", + "preserveMemberOrder": false, + "members": [ + { + "kind": "Constructor", + "canonicalReference": "@csstools/css-calc!ParseErrorWithComponentValues:constructor(1)", + "docComment": "/**\n * Constructs a new instance of the `ParseErrorWithComponentValues` class\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "constructor(message: " + }, + { + "kind": "Content", + "text": "string" + }, + { + "kind": "Content", + "text": ", componentValues: " + }, + { + "kind": "Reference", + "text": "Array", + "canonicalReference": "!Array:interface" + }, + { + "kind": "Content", + "text": "<" + }, + { + "kind": "Reference", + "text": "ComponentValue", + "canonicalReference": "@csstools/css-parser-algorithms!ComponentValue:type" + }, + { + "kind": "Content", + "text": ">" + }, + { + "kind": "Content", + "text": ");" + } + ], + "releaseTag": "Public", + "isProtected": false, + "overloadIndex": 1, + "parameters": [ + { + "parameterName": "message", + "parameterTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "isOptional": false + }, + { + "parameterName": "componentValues", + "parameterTypeTokenRange": { + "startIndex": 3, + "endIndex": 7 + }, + "isOptional": false + } + ] + }, + { + "kind": "Property", + "canonicalReference": "@csstools/css-calc!ParseErrorWithComponentValues#componentValues:member", + "docComment": "/**\n * The associated component values.\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "componentValues: " + }, + { + "kind": "Reference", + "text": "Array", + "canonicalReference": "!Array:interface" + }, + { + "kind": "Content", + "text": "<" + }, + { + "kind": "Reference", + "text": "ComponentValue", + "canonicalReference": "@csstools/css-parser-algorithms!ComponentValue:type" + }, + { + "kind": "Content", + "text": ">" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isReadonly": false, + "isOptional": false, + "releaseTag": "Public", + "name": "componentValues", + "propertyTypeTokenRange": { + "startIndex": 1, + "endIndex": 5 + }, + "isStatic": false, + "isProtected": false, + "isAbstract": false + } + ], + "extendsTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "implementsTokenRanges": [] } ] } diff --git a/packages/css-calc/docs/css-calc.conversionoptions.md b/packages/css-calc/docs/css-calc.conversionoptions.md index 28681ccf22..3ba189613b 100644 --- a/packages/css-calc/docs/css-calc.conversionoptions.md +++ b/packages/css-calc/docs/css-calc.conversionoptions.md @@ -8,6 +8,7 @@ ```typescript export type conversionOptions = { + onParseError?: (error: ParseError) => void; globals?: GlobalsWithStrings; precision?: number; toCanonicalUnits?: boolean; @@ -21,5 +22,5 @@ export type conversionOptions = { }; }; ``` -**References:** [GlobalsWithStrings](./css-calc.globalswithstrings.md) +**References:** [ParseError](./css-calc.parseerror.md), [GlobalsWithStrings](./css-calc.globalswithstrings.md) diff --git a/packages/css-calc/docs/css-calc.md b/packages/css-calc/docs/css-calc.md index 45ce95eba0..ceebae4a05 100644 --- a/packages/css-calc/docs/css-calc.md +++ b/packages/css-calc/docs/css-calc.md @@ -4,6 +4,41 @@ ## css-calc package +## Classes + + + + +
+ +Class + + + + +Description + + +
+ +[ParseError](./css-calc.parseerror.md) + + + + +Any errors are reported through the `onParseError` callback. + + +
+ +[ParseErrorWithComponentValues](./css-calc.parseerrorwithcomponentvalues.md) + + + + + +
+ ## Functions +
@@ -58,6 +93,15 @@ Description +
+ +[ParseErrorMessage](./css-calc.parseerrormessage.md) + + + + +
diff --git a/packages/css-calc/docs/css-calc.parseerror._constructor_.md b/packages/css-calc/docs/css-calc.parseerror._constructor_.md new file mode 100644 index 0000000000..84d1d7c13b --- /dev/null +++ b/packages/css-calc/docs/css-calc.parseerror._constructor_.md @@ -0,0 +1,76 @@ + + +[Home](./index.md) > [@csstools/css-calc](./css-calc.md) > [ParseError](./css-calc.parseerror.md) > [(constructor)](./css-calc.parseerror._constructor_.md) + +## ParseError.(constructor) + +Constructs a new instance of the `ParseError` class + +**Signature:** + +```typescript +constructor(message: string, sourceStart: number, sourceEnd: number); +``` + +## Parameters + + + + + +
+ +Parameter + + + + +Type + + + + +Description + + +
+ +message + + + + +string + + + + + +
+ +sourceStart + + + + +number + + + + + +
+ +sourceEnd + + + + +number + + + + + +
+ diff --git a/packages/css-calc/docs/css-calc.parseerror.md b/packages/css-calc/docs/css-calc.parseerror.md new file mode 100644 index 0000000000..ef5a6ae1f4 --- /dev/null +++ b/packages/css-calc/docs/css-calc.parseerror.md @@ -0,0 +1,112 @@ + + +[Home](./index.md) > [@csstools/css-calc](./css-calc.md) > [ParseError](./css-calc.parseerror.md) + +## ParseError class + +Any errors are reported through the `onParseError` callback. + +**Signature:** + +```typescript +export declare class ParseError extends Error +``` +**Extends:** Error + +## Constructors + + + +
+ +Constructor + + + + +Modifiers + + + + +Description + + +
+ +[(constructor)(message, sourceStart, sourceEnd)](./css-calc.parseerror._constructor_.md) + + + + + + + +Constructs a new instance of the `ParseError` class + + +
+ +## Properties + + + + +
+ +Property + + + + +Modifiers + + + + +Type + + + + +Description + + +
+ +[sourceEnd](./css-calc.parseerror.sourceend.md) + + + + + + + +number + + + + +The index of the end character of the current token. + + +
+ +[sourceStart](./css-calc.parseerror.sourcestart.md) + + + + + + + +number + + + + +The index of the start character of the current token. + + +
+ diff --git a/packages/css-calc/docs/css-calc.parseerror.sourceend.md b/packages/css-calc/docs/css-calc.parseerror.sourceend.md new file mode 100644 index 0000000000..0ddd19fd8d --- /dev/null +++ b/packages/css-calc/docs/css-calc.parseerror.sourceend.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@csstools/css-calc](./css-calc.md) > [ParseError](./css-calc.parseerror.md) > [sourceEnd](./css-calc.parseerror.sourceend.md) + +## ParseError.sourceEnd property + +The index of the end character of the current token. + +**Signature:** + +```typescript +sourceEnd: number; +``` diff --git a/packages/css-calc/docs/css-calc.parseerror.sourcestart.md b/packages/css-calc/docs/css-calc.parseerror.sourcestart.md new file mode 100644 index 0000000000..f26501339f --- /dev/null +++ b/packages/css-calc/docs/css-calc.parseerror.sourcestart.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@csstools/css-calc](./css-calc.md) > [ParseError](./css-calc.parseerror.md) > [sourceStart](./css-calc.parseerror.sourcestart.md) + +## ParseError.sourceStart property + +The index of the start character of the current token. + +**Signature:** + +```typescript +sourceStart: number; +``` diff --git a/packages/css-calc/docs/css-calc.parseerrormessage.md b/packages/css-calc/docs/css-calc.parseerrormessage.md new file mode 100644 index 0000000000..f4935f16ee --- /dev/null +++ b/packages/css-calc/docs/css-calc.parseerrormessage.md @@ -0,0 +1,14 @@ + + +[Home](./index.md) > [@csstools/css-calc](./css-calc.md) > [ParseErrorMessage](./css-calc.parseerrormessage.md) + +## ParseErrorMessage variable + +**Signature:** + +```typescript +ParseErrorMessage: { + UnexpectedAdditionOfDimensionOrPercentageWithNumber: string; + UnexpectedSubtractionOfDimensionOrPercentageWithNumber: string; +} +``` diff --git a/packages/css-calc/docs/css-calc.parseerrorwithcomponentvalues._constructor_.md b/packages/css-calc/docs/css-calc.parseerrorwithcomponentvalues._constructor_.md new file mode 100644 index 0000000000..cc5a58497d --- /dev/null +++ b/packages/css-calc/docs/css-calc.parseerrorwithcomponentvalues._constructor_.md @@ -0,0 +1,62 @@ + + +[Home](./index.md) > [@csstools/css-calc](./css-calc.md) > [ParseErrorWithComponentValues](./css-calc.parseerrorwithcomponentvalues.md) > [(constructor)](./css-calc.parseerrorwithcomponentvalues._constructor_.md) + +## ParseErrorWithComponentValues.(constructor) + +Constructs a new instance of the `ParseErrorWithComponentValues` class + +**Signature:** + +```typescript +constructor(message: string, componentValues: Array); +``` + +## Parameters + + + + +
+ +Parameter + + + + +Type + + + + +Description + + +
+ +message + + + + +string + + + + + +
+ +componentValues + + + + +Array<ComponentValue> + + + + + +
+ diff --git a/packages/css-calc/docs/css-calc.parseerrorwithcomponentvalues.componentvalues.md b/packages/css-calc/docs/css-calc.parseerrorwithcomponentvalues.componentvalues.md new file mode 100644 index 0000000000..ce49b87c19 --- /dev/null +++ b/packages/css-calc/docs/css-calc.parseerrorwithcomponentvalues.componentvalues.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [@csstools/css-calc](./css-calc.md) > [ParseErrorWithComponentValues](./css-calc.parseerrorwithcomponentvalues.md) > [componentValues](./css-calc.parseerrorwithcomponentvalues.componentvalues.md) + +## ParseErrorWithComponentValues.componentValues property + +The associated component values. + +**Signature:** + +```typescript +componentValues: Array; +``` diff --git a/packages/css-calc/docs/css-calc.parseerrorwithcomponentvalues.md b/packages/css-calc/docs/css-calc.parseerrorwithcomponentvalues.md new file mode 100644 index 0000000000..ff6d86886a --- /dev/null +++ b/packages/css-calc/docs/css-calc.parseerrorwithcomponentvalues.md @@ -0,0 +1,91 @@ + + +[Home](./index.md) > [@csstools/css-calc](./css-calc.md) > [ParseErrorWithComponentValues](./css-calc.parseerrorwithcomponentvalues.md) + +## ParseErrorWithComponentValues class + +**Signature:** + +```typescript +export declare class ParseErrorWithComponentValues extends ParseError +``` +**Extends:** [ParseError](./css-calc.parseerror.md) + +## Constructors + + + +
+ +Constructor + + + + +Modifiers + + + + +Description + + +
+ +[(constructor)(message, componentValues)](./css-calc.parseerrorwithcomponentvalues._constructor_.md) + + + + + + + +Constructs a new instance of the `ParseErrorWithComponentValues` class + + +
+ +## Properties + + + +
+ +Property + + + + +Modifiers + + + + +Type + + + + +Description + + +
+ +[componentValues](./css-calc.parseerrorwithcomponentvalues.componentvalues.md) + + + + + + + +Array<ComponentValue> + + + + +The associated component values. + + +
+ diff --git a/packages/css-calc/src/calculation.ts b/packages/css-calc/src/calculation.ts index ef6786b7d1..c51dd18187 100644 --- a/packages/css-calc/src/calculation.ts +++ b/packages/css-calc/src/calculation.ts @@ -1,5 +1,6 @@ import type { Operation } from './operation/operation'; import type { TokenNode } from '@csstools/css-parser-algorithms'; +import type { conversionOptions } from './options'; import { isTokenNode } from '@csstools/css-parser-algorithms'; export type Calculation = { @@ -11,7 +12,7 @@ export function isCalculation(x: unknown): x is Calculation { return !!(x) && (typeof x === 'object') && ('inputs' in x) && Array.isArray(x.inputs) && ('operation' in x); } -export function solve(calculation: Calculation | -1): TokenNode | -1 { +export function solve(calculation: Calculation | -1, options: conversionOptions): TokenNode | -1 { if (calculation === -1) { return -1; } @@ -24,7 +25,7 @@ export function solve(calculation: Calculation | -1): TokenNode | -1 { continue; } - const result = solve(input); + const result = solve(input, options); if (result === -1) { return -1; } @@ -32,5 +33,5 @@ export function solve(calculation: Calculation | -1): TokenNode | -1 { inputs.push(result); } - return calculation.operation(inputs); + return calculation.operation(inputs, options); } diff --git a/packages/css-calc/src/error.ts b/packages/css-calc/src/error.ts new file mode 100644 index 0000000000..9260e3e926 --- /dev/null +++ b/packages/css-calc/src/error.ts @@ -0,0 +1,35 @@ +import { sourceIndices, type ComponentValue } from "@csstools/css-parser-algorithms"; + +/** + * Any errors are reported through the `onParseError` callback. + */ +export class ParseError extends Error { + /** The index of the start character of the current token. */ + sourceStart: number; + /** The index of the end character of the current token. */ + sourceEnd: number; + + constructor(message: string, sourceStart: number, sourceEnd: number) { + super(message); + this.name = 'ParseError'; + + this.sourceStart = sourceStart; + this.sourceEnd = sourceEnd; + } +} + +export class ParseErrorWithComponentValues extends ParseError { + /** The associated component values. */ + componentValues: Array; + + constructor(message: string, componentValues: Array) { + super(message, ...sourceIndices(componentValues)); + + this.componentValues = componentValues; + } +} + +export const ParseErrorMessage = { + UnexpectedAdditionOfDimensionOrPercentageWithNumber: 'Unexpected addition of a dimension or percentage with a number.', + UnexpectedSubtractionOfDimensionOrPercentageWithNumber: 'Unexpected subtraction of a dimension or percentage with a number.' +} diff --git a/packages/css-calc/src/functions/calc.ts b/packages/css-calc/src/functions/calc.ts index 4db8efd2cc..c0d33cfe11 100644 --- a/packages/css-calc/src/functions/calc.ts +++ b/packages/css-calc/src/functions/calc.ts @@ -232,7 +232,7 @@ function singleArgument(fnNode: FunctionNode, globals: Globals, options: convers globals, ); - const a = solve(calc(calcWrapper(fnNode, nodes), globals, options)); + const a = solve(calc(calcWrapper(fnNode, nodes), globals, options), options); if (a === -1) { return -1; } @@ -283,12 +283,12 @@ function twoCommaSeparatedArguments(fnNode: FunctionNode, globals: Globals, opti } } - const a = solve(calc(calcWrapper(fnNode, aValue), globals, options)); + const a = solve(calc(calcWrapper(fnNode, aValue), globals, options), options); if (a === -1) { return -1; } - const b = solve(calc(calcWrapper(fnNode, bValue), globals, options)); + const b = solve(calc(calcWrapper(fnNode, bValue), globals, options), options); if (b === -1) { return -1; } @@ -334,7 +334,7 @@ function variadicArguments(fnNode: FunctionNode, values: Array, return -1; } - const solvedChunk = solve(calc(calcWrapper(fnNode, chunks[i]), globals, options)); + const solvedChunk = solve(calc(calcWrapper(fnNode, chunks[i]), globals, options), options); if (solvedChunk === -1) { return -1; } @@ -389,21 +389,21 @@ function clamp(clampNode: FunctionNode, globals: Globals, options: conversionOpt return calc(calcWrapper(clampNode, centralValue), globals, options); } - const central = solve(calc(calcWrapper(clampNode, centralValue), globals, options)); + const central = solve(calc(calcWrapper(clampNode, centralValue), globals, options), options); if (central === -1) { return -1; } { if (minimumIsNone) { - const maximum = solve(calc(calcWrapper(clampNode, maximumValue), globals, options)); + const maximum = solve(calc(calcWrapper(clampNode, maximumValue), globals, options), options); if (maximum === -1) { return -1; } return solveMin(minWrapper(clampNode, central, maximum), [central, maximum], options); } else if (maximumIsNone) { - const minimum = solve(calc(calcWrapper(clampNode, minimumValue), globals, options)); + const minimum = solve(calc(calcWrapper(clampNode, minimumValue), globals, options), options); if (minimum === -1) { return -1; } @@ -412,12 +412,12 @@ function clamp(clampNode: FunctionNode, globals: Globals, options: conversionOpt } } - const minimum = solve(calc(calcWrapper(clampNode, minimumValue), globals, options)); + const minimum = solve(calc(calcWrapper(clampNode, minimumValue), globals, options), options); if (minimum === -1) { return -1; } - const maximum = solve(calc(calcWrapper(clampNode, maximumValue), globals, options)); + const maximum = solve(calc(calcWrapper(clampNode, maximumValue), globals, options), options); if (maximum === -1) { return -1; } @@ -487,7 +487,7 @@ function round(roundNode: FunctionNode, globals: Globals, options: conversionOpt } } - const a = solve(calc(calcWrapper(roundNode, aValue), globals, options)); + const a = solve(calc(calcWrapper(roundNode, aValue), globals, options), options); if (a === -1) { return -1; } @@ -500,7 +500,7 @@ function round(roundNode: FunctionNode, globals: Globals, options: conversionOpt ); } - const b = solve(calc(calcWrapper(roundNode, bValue), globals, options)); + const b = solve(calc(calcWrapper(roundNode, bValue), globals, options), options); if (b === -1) { return -1; } @@ -661,7 +661,7 @@ function parseRandomValueSharing(fnNode: FunctionNode, nodes: Array): TokenNode | -1 { +export function addition(inputs: Array, options: conversionOptions): TokenNode | -1 { if (inputs.length !== 2) { return -1; } @@ -45,5 +47,33 @@ export function addition(inputs: Array): TokenNode | -1 { } } + // 1 + 1px + // 1px + 1 + // 1 + 1% + // 1% + 1 + if ( + ( + isTokenNumber(aToken) && + ( + isTokenDimension(bToken) || + isTokenPercentage(bToken) + ) + ) || + ( + isTokenNumber(bToken) && + ( + isTokenDimension(aToken) || + isTokenPercentage(aToken) + ) + ) + ) { + options.onParseError?.( + new ParseErrorWithComponentValues( + ParseErrorMessage.UnexpectedAdditionOfDimensionOrPercentageWithNumber, + inputs + ) + ); + } + return -1; } diff --git a/packages/css-calc/src/operation/operation.ts b/packages/css-calc/src/operation/operation.ts index 56ef06138d..2ceda8b27a 100644 --- a/packages/css-calc/src/operation/operation.ts +++ b/packages/css-calc/src/operation/operation.ts @@ -1,3 +1,4 @@ import type { TokenNode } from '@csstools/css-parser-algorithms'; +import type { conversionOptions } from '../options'; -export type Operation = (inputs: Array) => TokenNode | -1 +export type Operation = (inputs: Array, options: conversionOptions) => TokenNode | -1 diff --git a/packages/css-calc/src/operation/subtraction.ts b/packages/css-calc/src/operation/subtraction.ts index 50e601e957..ca8726034f 100644 --- a/packages/css-calc/src/operation/subtraction.ts +++ b/packages/css-calc/src/operation/subtraction.ts @@ -1,9 +1,11 @@ +import type { conversionOptions } from '../options'; import { TokenNode } from '@csstools/css-parser-algorithms'; import { NumberType, TokenType, isTokenDimension, isTokenNumber, isTokenPercentage } from '@csstools/css-tokenizer'; import { convertUnit } from '../unit-conversions'; import { toLowerCaseAZ } from '../util/to-lower-case-a-z'; +import { ParseErrorWithComponentValues, ParseErrorMessage } from '../error'; -export function subtraction(inputs: Array): TokenNode | -1 { +export function subtraction(inputs: Array, options: conversionOptions): TokenNode | -1 { if (inputs.length !== 2) { return -1; } @@ -47,5 +49,33 @@ export function subtraction(inputs: Array): TokenNode | -1 { } } + // 1 - 1px + // 1px - 1 + // 1 - 1% + // 1% - 1 + if ( + ( + isTokenNumber(aToken) && + ( + isTokenDimension(bToken) || + isTokenPercentage(bToken) + ) + ) || + ( + isTokenNumber(bToken) && + ( + isTokenDimension(aToken) || + isTokenPercentage(aToken) + ) + ) + ) { + options.onParseError?.( + new ParseErrorWithComponentValues( + ParseErrorMessage.UnexpectedSubtractionOfDimensionOrPercentageWithNumber, + inputs + ) + ); + } + return -1; } diff --git a/packages/css-calc/src/options.ts b/packages/css-calc/src/options.ts index df124a3d7a..b9afbb4f15 100644 --- a/packages/css-calc/src/options.ts +++ b/packages/css-calc/src/options.ts @@ -1,8 +1,18 @@ +import type { ParseError } from './error'; import type { GlobalsWithStrings } from './util/globals'; export type { GlobalsWithStrings } from './util/globals'; export type conversionOptions = { /** + * If a calc expression can not be solved the parse error might be reported through this callback. + * Not all cases are covered. Open an issue if you need specific errors reported. + * + * Values are recursively visited and at each nesting level an attempt is made to solve the expression. + * Errors can be reported multiple times as a result of this. + */ + onParseError?: (error: ParseError) => void + + /** * Pass global values as a map of key value pairs. */ globals?: GlobalsWithStrings, diff --git a/packages/css-calc/test/basic/parse-error.mjs b/packages/css-calc/test/basic/parse-error.mjs new file mode 100644 index 0000000000..078abe7267 --- /dev/null +++ b/packages/css-calc/test/basic/parse-error.mjs @@ -0,0 +1,191 @@ +import { calc, ParseErrorMessage } from '@csstools/css-calc'; +import assert from 'node:assert'; +import test from 'node:test'; + +test('calc(2px + 2)', () => { + let parseErrors = []; + + calc('calc(2px + 2)', { + onParseError: (err) => { + parseErrors.push({ + message: err.message, + start: err.sourceStart, + end: err.sourceEnd, + }); + }, + }); + + assert.deepStrictEqual( + parseErrors, + [ + { + message: ParseErrorMessage.UnexpectedAdditionOfDimensionOrPercentageWithNumber, + start: 5, + end: 11, + }, + ], + ); +}); + +test('calc(2 + 2px)', () => { + let parseErrors = []; + + calc('calc(2 + 2px)', { + onParseError: (err) => { + parseErrors.push({ + message: err.message, + start: err.sourceStart, + end: err.sourceEnd, + }); + }, + }); + + assert.deepStrictEqual( + parseErrors, + [ + { + message: ParseErrorMessage.UnexpectedAdditionOfDimensionOrPercentageWithNumber, + start: 5, + end: 11, + }, + ], + ); +}); + +test('calc(2px - 2)', () => { + let parseErrors = []; + + calc('calc(2px - 2)', { + onParseError: (err) => { + parseErrors.push({ + message: err.message, + start: err.sourceStart, + end: err.sourceEnd, + }); + }, + }); + + assert.deepStrictEqual( + parseErrors, + [ + { + message: ParseErrorMessage.UnexpectedSubtractionOfDimensionOrPercentageWithNumber, + start: 5, + end: 11, + }, + ], + ); +}); + +test('calc(2% + 2)', () => { + let parseErrors = []; + + calc('calc(2% + 2)', { + onParseError: (err) => { + parseErrors.push({ + message: err.message, + start: err.sourceStart, + end: err.sourceEnd, + }); + }, + }); + + assert.deepStrictEqual( + parseErrors, + [ + { + message: ParseErrorMessage.UnexpectedAdditionOfDimensionOrPercentageWithNumber, + start: 5, + end: 10, + }, + ], + ); +}); + +test('calc(0.3 * 10px + 1px)', () => { + let parseErrors = []; + + calc('calc(0.3 * 10px + 1px)', { + onParseError: (err) => { + parseErrors.push({ + message: err.message, + start: err.sourceStart, + end: err.sourceEnd, + }); + }, + }); + + assert.deepStrictEqual( + parseErrors, + [], + ); +}); + +test('calc((2%) + (2))', () => { + let parseErrors = []; + + calc('calc((2%) + (2))', { + onParseError: (err) => { + parseErrors.push({ + message: err.message, + start: err.sourceStart, + end: err.sourceEnd, + }); + }, + }); + + assert.deepStrictEqual( + parseErrors, + [ + { + message: ParseErrorMessage.UnexpectedAdditionOfDimensionOrPercentageWithNumber, + start: 6, + end: 13, + }, + ], + ); +}); + +test('min(1px + 1, 2px)', () => { + let parseErrors = []; + + calc('min(1px + 1, 2px)', { + onParseError: (err) => { + parseErrors.push({ + message: err.message, + start: err.sourceStart, + end: err.sourceEnd, + }); + }, + }); + + assert.deepStrictEqual( + parseErrors, + [ + { + message: ParseErrorMessage.UnexpectedAdditionOfDimensionOrPercentageWithNumber, + start: 4, + end: 10, + }, + ], + ); +}); + +test('calc(2px + var(--foo))', () => { + let parseErrors = []; + + calc('calc(2px + var(--foo))', { + onParseError: (err) => { + parseErrors.push({ + message: err.message, + start: err.sourceStart, + end: err.sourceEnd, + }); + }, + }); + + assert.deepStrictEqual( + parseErrors, + [], + ); +}); diff --git a/packages/css-calc/test/test.mjs b/packages/css-calc/test/test.mjs index 48a98f195a..b7375a75c8 100644 --- a/packages/css-calc/test/test.mjs +++ b/packages/css-calc/test/test.mjs @@ -1,5 +1,6 @@ import './basic/test.mjs'; import './basic/none-in-clamp.mjs'; +import './basic/parse-error.mjs'; import './additional/index.mjs'; import './postcss-calc/index.mjs';