Skip to content

Commit

Permalink
fix: better deal with mixed unit math computations
Browse files Browse the repository at this point in the history
  • Loading branch information
jorenbroekema committed Aug 12, 2024
1 parent ae75978 commit 3cb180e
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 7 deletions.
5 changes: 5 additions & 0 deletions .changeset/unlucky-bulldogs-applaud.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tokens-studio/sd-transforms': patch
---

Improve math compute utility to better deal with mixed units computations. Expand on tests.
11 changes: 10 additions & 1 deletion src/checkAndEvaluateMath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,17 @@ function parseAndReduce(expr: string, fractionDigits = defaultFractionDigits): s
}

if (typeof result !== 'number') {
let exprToParse = noPixExpr;
// math operators, excluding *
// (** or ^ exponent would theoretically be fine, but postcss-calc-ast-parser does not support it
const operatorsRegex = /[/+%-]/g;
// if we only have * operator, we can consider expression as unitless and compute it that way
// we already know we dont have mixed units from (foundUnits.size > 1) guard above
if (!exprToParse.match(operatorsRegex)) {
exprToParse = exprToParse.replace(new RegExp(resultUnit, 'g'), '');
}
// Try to evaluate as postcss-calc-ast-parser expression
const calcParsed = parse(noPixExpr, { allowInlineCommnets: false });
const calcParsed = parse(exprToParse, { allowInlineCommnets: false });

// Attempt to reduce the math expression
const reduced = reduceExpression(calcParsed);
Expand Down
20 changes: 14 additions & 6 deletions test/spec/checkAndEvaluateMath.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,21 @@ describe('check and evaluate math', () => {
expect(checkAndEvaluateMath({ value: '4 * 7px * 8px', type: 'dimension' })).to.equal('224px');
});

it('cannot evaluate math expressions where more than one token has a unit, when unit is not px', () => {
expect(checkAndEvaluateMath({ value: '4em * 7em', type: 'dimension' })).to.equal('4em * 7em');
expect(checkAndEvaluateMath({ value: '4 * 7em * 8em', type: 'dimension' })).to.equal(
'4 * 7em * 8em',
);
// exception for pixels, it strips px, making it 4 * 7em = 28em = 448px, where 4px * 7em would be 4px * 112px = 448px as well
it('can evaluate math expressions where more than one token has a unit, assuming no mixed units are used', () => {
expect(checkAndEvaluateMath({ value: '4em + 7em', type: 'dimension' })).to.equal('11em');
expect(checkAndEvaluateMath({ value: '4 + 7rem', type: 'dimension' })).to.equal('4 + 7rem');
expect(checkAndEvaluateMath({ value: '4em + 7rem', type: 'dimension' })).to.equal('4em + 7rem');
});

it('can evaluate mixed units if operators are exclusively multiplication and the mix is px or unitless', () => {
expect(checkAndEvaluateMath({ value: '4 * 7em * 8em', type: 'dimension' })).to.equal('224em');
expect(checkAndEvaluateMath({ value: '4px * 7em', type: 'dimension' })).to.equal('28em');
// 50em would be incorrect when dividing, as em grows, result should shrink, but doesn't
expect(checkAndEvaluateMath({ value: '1000 / 20em', type: 'dimension' })).to.equal(
'1000 / 20em',
);
// cannot be expressed/resolved without knowing the value of em
expect(checkAndEvaluateMath({ value: '4px + 7em', type: 'dimension' })).to.equal('4px + 7em');
});

it('can evaluate math expressions where more than one token has a unit, as long as for each piece of the expression the unit is the same', () => {
Expand Down

0 comments on commit 3cb180e

Please sign in to comment.