Skip to content

Commit f6b3a97

Browse files
committed
feat: use @tokens-studio/unit-calculator instead of regex wrapper
1 parent db34205 commit f6b3a97

File tree

10 files changed

+185
-262
lines changed

10 files changed

+185
-262
lines changed

package-lock.json

Lines changed: 11 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"@bundled-es-modules/deepmerge": "^4.3.1",
3737
"@bundled-es-modules/postcss-calc-ast-parser": "^0.1.6",
3838
"@tokens-studio/types": "^0.5.1",
39+
"@tokens-studio/unit-calculator": "^0.0.1",
3940
"colorjs.io": "^0.5.2",
4041
"expr-eval-fork": "^2.0.2",
4142
"is-mergeable-object": "^1.1.1"

src/checkAndEvaluateMath.ts

Lines changed: 5 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { Parser } from 'expr-eval-fork';
33
import { parse, reduceExpression } from '@bundled-es-modules/postcss-calc-ast-parser';
44
import { defaultFractionDigits } from './utils/constants.js';
55
import { reduceToFixed } from './utils/reduceToFixed.js';
6+
import { strictCheckAndEvaluateMath, MathOptions } from './strictCheckAndEvaluateMath.js';
7+
import { transformByTokenType } from './utils/transformByTokenType.js';
68

79
const mathChars = ['+', '-', '*', '/'];
810

@@ -157,9 +159,9 @@ export function parseAndReduce(
157159
export function checkAndEvaluateMath(
158160
token: DesignToken,
159161
fractionDigits?: number,
160-
isStrict = false,
162+
strictOptions?: Partial<MathOptions>,
161163
): DesignToken['value'] {
162-
if (isStrict) return checkAndEvaluateMath(token, fractionDigits);
164+
if (strictOptions) return strictCheckAndEvaluateMath(token, { fractionDigits, ...strictOptions });
163165

164166
const expr = token.$value ?? token.value;
165167
const type = token.$type ?? token.type;
@@ -180,48 +182,7 @@ export function checkAndEvaluateMath(
180182
return reducedExprs.join(' ');
181183
};
182184

183-
const transformProp = (val: Record<string, number | string>, prop: string) => {
184-
if (typeof val === 'object' && val[prop] !== undefined) {
185-
val[prop] = resolveMath(val[prop]);
186-
}
187-
return val;
188-
};
189-
190-
let transformed = expr;
191-
switch (type) {
192-
case 'typography':
193-
case 'border': {
194-
transformed = transformed as Record<string, number | string>;
195-
// double check that expr is still an object and not already shorthand transformed to a string
196-
if (typeof expr === 'object') {
197-
Object.keys(transformed).forEach(prop => {
198-
transformed = transformProp(transformed, prop);
199-
});
200-
}
201-
break;
202-
}
203-
case 'shadow': {
204-
transformed = transformed as
205-
| Record<string, number | string>
206-
| Record<string, number | string>[];
207-
const transformShadow = (shadowVal: Record<string, number | string>) => {
208-
// double check that expr is still an object and not already shorthand transformed to a string
209-
if (typeof expr === 'object') {
210-
Object.keys(shadowVal).forEach(prop => {
211-
shadowVal = transformProp(shadowVal, prop);
212-
});
213-
}
214-
return shadowVal;
215-
};
216-
if (Array.isArray(transformed)) {
217-
transformed = transformed.map(transformShadow);
218-
}
219-
transformed = transformShadow(transformed);
220-
break;
221-
}
222-
default:
223-
transformed = resolveMath(transformed);
224-
}
185+
const transformed = transformByTokenType(expr, type, resolveMath);
225186

226187
return transformed;
227188
}

src/register.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,14 @@ export async function register(sd: typeof StyleDictionary, transformOpts?: Trans
7373
type: 'value',
7474
transitive: true,
7575
filter: token => ['string', 'object'].includes(typeof (token.$value ?? token.value)),
76-
transform: (token, platformCfg) => checkAndEvaluateMath(token, platformCfg.mathFractionDigits),
76+
transform: (token, platformCfg) =>
77+
checkAndEvaluateMath(
78+
token,
79+
// backwards compability prop
80+
platformCfg.mathFractionDigits,
81+
// strict math mode options
82+
platformCfg.mathOptions,
83+
),
7784
});
7885

7986
sd.registerTransform({

src/strictCheckAndEvaluateMath.ts

Lines changed: 39 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,61 @@
1+
import { run, config as calcConfig } from '@tokens-studio/unit-calculator';
2+
import type { IUnitValue } from '@tokens-studio/unit-calculator';
13
import { Parser } from 'expr-eval-fork';
24
import { DesignToken } from 'style-dictionary/types';
35
import { defaultFractionDigits } from './utils/constants.js';
4-
import { MathExprEvalError, MixedUnitsExpressionError } from './utils/errors.js';
5-
import { parseUnits } from './utils/parseUnits.js';
6+
import { MathExprEvalError } from './utils/errors.js';
67
import { reduceToFixed } from './utils/reduceToFixed.js';
8+
import { transformByTokenType } from './utils/transformByTokenType.js';
9+
10+
const { roundTo } = new Parser().functions;
11+
const config = {
12+
mathFunctions: {
13+
...calcConfig.defaultMathFunctions,
14+
roundTo: (a: IUnitValue, b: IUnitValue) => {
15+
const value = roundTo(a.value, b.value);
16+
return { value, unit: a.unit };
17+
},
18+
},
19+
};
20+
21+
export interface MathOptions {
22+
fractionDigits: number;
23+
}
724

8-
const parser = new Parser();
9-
10-
export function evaluateMathExpr(expr: string, fractionDigits: number): string | number {
11-
const isAlreadyNumber = !isNaN(Number(expr));
12-
if (isAlreadyNumber) {
13-
return expr;
14-
}
15-
16-
const { units, unitlessExpr } = parseUnits(expr);
17-
18-
// Remove unitless "unit" from the units set to count the number of units
19-
const noUnitlessUnits = units.difference(new Set(['']));
20-
if (noUnitlessUnits.size > 1) {
21-
throw new MixedUnitsExpressionError({ units });
22-
}
23-
// Since there's no unit mixing allowed we can take the first item out of the units set as the output unit
24-
const resultUnit: string | null = [...noUnitlessUnits][0];
25-
26-
let evalResult: number;
25+
export function evaluateMathExpr(expr: string, { fractionDigits }: MathOptions): string | number {
2726
try {
28-
evalResult = parser.evaluate(unitlessExpr);
27+
const parsed = run(expr, config);
28+
const values = parsed.exec().map(function (result) {
29+
const { value, unit } = result;
30+
const fixedValue = typeof value === 'number' ? reduceToFixed(value, fractionDigits) : value;
31+
return unit ? `${fixedValue}${unit}` : fixedValue;
32+
});
33+
return values.length > 1 ? values.join(' ') : values[0];
2934
} catch (exception) {
3035
throw new MathExprEvalError({
31-
value: unitlessExpr,
36+
value: expr,
3237
exception: exception instanceof Error ? exception : undefined,
3338
});
3439
}
35-
36-
const fixedNum = reduceToFixed(evalResult, fractionDigits);
37-
const result = resultUnit ? `${fixedNum}${resultUnit}` : fixedNum;
38-
return result;
3940
}
4041

4142
export function strictCheckAndEvaluateMath(
4243
token: DesignToken,
43-
fractionDigits = defaultFractionDigits,
44+
options: Partial<MathOptions> = {},
4445
): DesignToken['value'] {
46+
const opts = { fractionDigits: options.fractionDigits ?? defaultFractionDigits };
47+
4548
const expr = token.$value ?? token.value;
49+
const type = token.$type ?? token.type;
4650

47-
if (!['string'].includes(typeof expr)) {
51+
if (!['string', 'object'].includes(typeof expr)) {
4852
return expr;
4953
}
5054

51-
return evaluateMathExpr(expr, fractionDigits);
55+
const resolveMath = (expr: DesignToken['value']) => {
56+
if (typeof expr !== 'string') return expr;
57+
return evaluateMathExpr(expr, opts);
58+
};
59+
60+
return transformByTokenType(expr, type, resolveMath);
5261
}

src/utils/errors.ts

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,3 @@
1-
import type { Units } from './parseUnits.ts';
2-
3-
export class MixedUnitsExpressionError extends Error {
4-
units: Units;
5-
6-
constructor({ units }: { units: Units }) {
7-
super('Mixed units found in expression');
8-
this.name = 'MixedUnitsExpressionError';
9-
this.units = units;
10-
}
11-
}
12-
131
export class MathExprEvalError extends Error {
142
value: string;
153
exception?: Error;

src/utils/parseUnits.ts

Lines changed: 0 additions & 36 deletions
This file was deleted.

src/utils/transformByTokenType.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import type { DesignToken } from 'style-dictionary/types';
2+
3+
export function transformByTokenType(
4+
expr: DesignToken['value'],
5+
type: string | undefined,
6+
resolveMath: (expr: number | string) => number | string,
7+
): DesignToken['value'] {
8+
const transformProp = (val: Record<string, number | string>, prop: string) => {
9+
if (typeof val === 'object' && val[prop] !== undefined) {
10+
val[prop] = resolveMath(val[prop]);
11+
}
12+
return val;
13+
};
14+
15+
let transformed = expr;
16+
switch (type) {
17+
case 'typography':
18+
case 'border': {
19+
transformed = transformed as Record<string, number | string>;
20+
// double check that expr is still an object and not already shorthand transformed to a string
21+
if (typeof expr === 'object') {
22+
Object.keys(transformed).forEach(prop => {
23+
transformed = transformProp(transformed, prop);
24+
});
25+
}
26+
break;
27+
}
28+
case 'shadow': {
29+
transformed = transformed as
30+
| Record<string, number | string>
31+
| Record<string, number | string>[];
32+
const transformShadow = (shadowVal: Record<string, number | string>) => {
33+
// double check that expr is still an object and not already shorthand transformed to a string
34+
if (typeof expr === 'object') {
35+
Object.keys(shadowVal).forEach(prop => {
36+
shadowVal = transformProp(shadowVal, prop);
37+
});
38+
}
39+
return shadowVal;
40+
};
41+
if (Array.isArray(transformed)) {
42+
transformed = transformed.map(transformShadow);
43+
}
44+
transformed = transformShadow(transformed);
45+
break;
46+
}
47+
default:
48+
transformed = resolveMath(transformed);
49+
}
50+
return transformed;
51+
}

0 commit comments

Comments
 (0)