diff --git a/.changeset/brave-timers-burn.md b/.changeset/brave-timers-burn.md new file mode 100644 index 0000000..564c629 --- /dev/null +++ b/.changeset/brave-timers-burn.md @@ -0,0 +1,5 @@ +--- +'@tokens-studio/sd-transforms': patch +--- + +fix: evaluate math expressions with units diff --git a/src/checkAndEvaluateMath.ts b/src/checkAndEvaluateMath.ts index 26048af..eecf39b 100644 --- a/src/checkAndEvaluateMath.ts +++ b/src/checkAndEvaluateMath.ts @@ -78,15 +78,9 @@ function splitMultiIntoSingleValues(expr: string): string[] { function parseAndReduce(expr: string, fractionDigits = defaultFractionDigits): string | number { let result: string | number = expr; - let evaluated; - // Try to evaluate as expr-eval expression - try { - evaluated = parser.evaluate(`${result}`); - if (typeof evaluated === 'number') { - result = evaluated; - } - } catch (ex) { - // + // Check if expression is already a number + if (!isNaN(Number(result))) { + return result; } // We check for px unit, then remove it, since these are essentially numbers in tokens context @@ -108,22 +102,37 @@ function parseAndReduce(expr: string, fractionDigits = defaultFractionDigits): s } const resultUnit = Array.from(foundUnits)[0] ?? (hasPx ? 'px' : ''); - // Remove it here so we can evaluate expressions like 16px + 24px - const calcParsed = parse(noPixExpr, { allowInlineCommnets: false }); + if (!isNaN(Number(noPixExpr))) { + result = Number(noPixExpr); + } - // No expression to evaluate, just return it (in case of number as string e.g. '10') - if (calcParsed.nodes.length === 1 && calcParsed.nodes[0].type === 'Number') { - return `${result}`; + if (typeof result !== 'number') { + // Try to evaluate as expr-eval expression + let evaluated; + try { + evaluated = parser.evaluate(`${noPixExpr}`); + if (typeof evaluated === 'number') { + result = evaluated; + } + } catch (ex) { + // no-op + } } - // Attempt to reduce the math expression - const reduced = reduceExpression(calcParsed); - // E.g. if type is Length, like 4 * 7rem would be 28rem - if (reduced) { - result = reduced.value; + if (typeof result !== 'number') { + // Try to evaluate as postcss-calc-ast-parser expression + const calcParsed = parse(noPixExpr, { allowInlineCommnets: false }); + + // Attempt to reduce the math expression + const reduced = reduceExpression(calcParsed); + // E.g. if type is Length, like 4 * 7rem would be 28rem + if (reduced) { + result = reduced.value; + } } if (typeof result !== 'number') { + // parsing failed, return the original expression return result; } diff --git a/test/spec/checkAndEvaluateMath.spec.ts b/test/spec/checkAndEvaluateMath.spec.ts index ee3cb6e..f047ad7 100644 --- a/test/spec/checkAndEvaluateMath.spec.ts +++ b/test/spec/checkAndEvaluateMath.spec.ts @@ -86,6 +86,27 @@ describe('check and evaluate math', () => { ).to.equal(14); }); + it('supports expr-eval expressions with units', () => { + expect(checkAndEvaluateMath({ value: 'roundTo(4px / 7, 1)', type: 'dimension' })).to.equal( + '0.6px', + ); + expect( + checkAndEvaluateMath({ value: '8 * 14px roundTo(4 / 7px, 1)', type: 'dimension' }), + ).to.equal('112px 0.6px'); + expect( + checkAndEvaluateMath({ value: 'roundTo(4px / 7px, 1) 8 * 14px', type: 'dimension' }), + ).to.equal('0.6px 112px'); + expect( + checkAndEvaluateMath({ + value: 'min(10px, 24px, 5px, 12px, 6px) 8 * 14px', + type: 'dimension', + }), + ).to.equal('5px 112px'); + expect( + checkAndEvaluateMath({ value: 'ceil(roundTo(16px/1.2,0)/2)*2', type: 'dimension' }), + ).to.equal('14px'); + }); + it('should support expr eval expressions in combination with regular math', () => { expect(checkAndEvaluateMath({ value: 'roundTo(4 / 7, 1) * 24', type: 'dimension' })).to.equal( 14.4,