Skip to content

Commit 0dea2af

Browse files
fix: evaluate math expressions with units (#304)
* fix: evaluate math expressions with units * Add changeset
1 parent bc57cd0 commit 0dea2af

File tree

3 files changed

+54
-19
lines changed

3 files changed

+54
-19
lines changed

.changeset/brave-timers-burn.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@tokens-studio/sd-transforms': patch
3+
---
4+
5+
fix: evaluate math expressions with units

src/checkAndEvaluateMath.ts

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -78,15 +78,9 @@ function splitMultiIntoSingleValues(expr: string): string[] {
7878
function parseAndReduce(expr: string, fractionDigits = defaultFractionDigits): string | number {
7979
let result: string | number = expr;
8080

81-
let evaluated;
82-
// Try to evaluate as expr-eval expression
83-
try {
84-
evaluated = parser.evaluate(`${result}`);
85-
if (typeof evaluated === 'number') {
86-
result = evaluated;
87-
}
88-
} catch (ex) {
89-
//
81+
// Check if expression is already a number
82+
if (!isNaN(Number(result))) {
83+
return result;
9084
}
9185

9286
// 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
108102
}
109103
const resultUnit = Array.from(foundUnits)[0] ?? (hasPx ? 'px' : '');
110104

111-
// Remove it here so we can evaluate expressions like 16px + 24px
112-
const calcParsed = parse(noPixExpr, { allowInlineCommnets: false });
105+
if (!isNaN(Number(noPixExpr))) {
106+
result = Number(noPixExpr);
107+
}
113108

114-
// No expression to evaluate, just return it (in case of number as string e.g. '10')
115-
if (calcParsed.nodes.length === 1 && calcParsed.nodes[0].type === 'Number') {
116-
return `${result}`;
109+
if (typeof result !== 'number') {
110+
// Try to evaluate as expr-eval expression
111+
let evaluated;
112+
try {
113+
evaluated = parser.evaluate(`${noPixExpr}`);
114+
if (typeof evaluated === 'number') {
115+
result = evaluated;
116+
}
117+
} catch (ex) {
118+
// no-op
119+
}
117120
}
118121

119-
// Attempt to reduce the math expression
120-
const reduced = reduceExpression(calcParsed);
121-
// E.g. if type is Length, like 4 * 7rem would be 28rem
122-
if (reduced) {
123-
result = reduced.value;
122+
if (typeof result !== 'number') {
123+
// Try to evaluate as postcss-calc-ast-parser expression
124+
const calcParsed = parse(noPixExpr, { allowInlineCommnets: false });
125+
126+
// Attempt to reduce the math expression
127+
const reduced = reduceExpression(calcParsed);
128+
// E.g. if type is Length, like 4 * 7rem would be 28rem
129+
if (reduced) {
130+
result = reduced.value;
131+
}
124132
}
125133

126134
if (typeof result !== 'number') {
135+
// parsing failed, return the original expression
127136
return result;
128137
}
129138

test/spec/checkAndEvaluateMath.spec.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,27 @@ describe('check and evaluate math', () => {
8686
).to.equal(14);
8787
});
8888

89+
it('supports expr-eval expressions with units', () => {
90+
expect(checkAndEvaluateMath({ value: 'roundTo(4px / 7, 1)', type: 'dimension' })).to.equal(
91+
'0.6px',
92+
);
93+
expect(
94+
checkAndEvaluateMath({ value: '8 * 14px roundTo(4 / 7px, 1)', type: 'dimension' }),
95+
).to.equal('112px 0.6px');
96+
expect(
97+
checkAndEvaluateMath({ value: 'roundTo(4px / 7px, 1) 8 * 14px', type: 'dimension' }),
98+
).to.equal('0.6px 112px');
99+
expect(
100+
checkAndEvaluateMath({
101+
value: 'min(10px, 24px, 5px, 12px, 6px) 8 * 14px',
102+
type: 'dimension',
103+
}),
104+
).to.equal('5px 112px');
105+
expect(
106+
checkAndEvaluateMath({ value: 'ceil(roundTo(16px/1.2,0)/2)*2', type: 'dimension' }),
107+
).to.equal('14px');
108+
});
109+
89110
it('should support expr eval expressions in combination with regular math', () => {
90111
expect(checkAndEvaluateMath({ value: 'roundTo(4 / 7, 1) * 24', type: 'dimension' })).to.equal(
91112
14.4,

0 commit comments

Comments
 (0)