Skip to content

Commit

Permalink
Improve Tokens Studio inline aliasing
Browse files Browse the repository at this point in the history
  • Loading branch information
drwpow committed Mar 25, 2024
1 parent 37bacf1 commit 31195ac
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 10 deletions.
5 changes: 5 additions & 0 deletions .changeset/cuddly-cooks-kiss.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cobalt-ui/core": patch
---

Improve Tokens Studio inline aliasing
78 changes: 70 additions & 8 deletions packages/core/src/parse/tokens-studio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
* This works by first converting the Tokens Studio format
* into an equivalent DTCG result, then parsing that result
*/
import { parseAlias } from '@cobalt-ui/utils';
import { isAlias, parseAlias } from '@cobalt-ui/utils';
import { parse as culoriParse, rgb } from 'culori';
import type { GradientStop, Group, Token } from '../token.js';

// I’m not sure this is comprehensive at all but better than nothing
Expand Down Expand Up @@ -284,9 +285,12 @@ export function convertTokensStudioFormat(rawTokens: Record<string, unknown>): {
`Token "${tokenID}" is a multi value borderRadius token. Expanding into ${tokenID}TopLeft, ${tokenID}TopRight, ${tokenID}BottomRight, and ${tokenID}BottomLeft.`,
);
let order = [values[0], values[1], values[0], values[1]] as [string, string, string, string]; // TL, BR
if (values.length === 3)
{order = [values[0], values[1], values[2], values[1]] as [string, string, string, string];} // TL, TR/BL, BR
else if (values.length === 4) {order = [values[0], values[1], values[2], values[3]] as [string, string, string, string];} // TL, TR, BR, BL
if (values.length === 3) {
order = [values[0], values[1], values[2], values[1]] as [string, string, string, string];
} // TL, TR/BL, BR
else if (values.length === 4) {
order = [values[0], values[1], values[2], values[3]] as [string, string, string, string];
} // TL, TR, BR, BL
addToken({ $type: 'dimension', $value: order[0], $description: v.description }, [...path, `${k}TopLeft`]);
addToken({ $type: 'dimension', $value: order[1], $description: v.description }, [...path, `${k}TopRight`]);
addToken({ $type: 'dimension', $value: order[2], $description: v.description }, [...path, `${k}BottomRight`]);
Expand Down Expand Up @@ -365,10 +369,65 @@ export function convertTokensStudioFormat(rawTokens: Record<string, unknown>): {
tokenPath,
);
} else {
let value: string | undefined = v.value;
// resolve inline aliases (e.g. `rgba({color.black}, 0.5)`)
if (value.includes('{') && !v.value.startsWith('{')) {
value = resolveAlias(value, path);

if (!value) {
errors.push(`Could not resolve "${v.value}"`);
continue;
}

// note: we did some work earlier to help resolve the aliases, but
// we need to REPLACE them in this scenario so we must do a 2nd pass
const matches = value.match(ALIAS_RE);

Check failure

Code scanning / CodeQL

Polynomial regular expression used on uncontrolled data High

This
regular expression
that depends on
library input
may run slow on strings starting with '{' and with many repetitions of '{'.
for (const match of matches ?? []) {
let currentAlias = parseAlias(match).id;
let resolvedValue: string | undefined;
const aliasHistory = new Set<string>([currentAlias]);
while (!resolvedValue) {
const aliasNode: any = get(rawTokens, currentAlias.split('.'));
// does this resolve to a $value?
if (aliasNode && aliasNode.value) {
// is this another alias?
if (isAlias(aliasNode.value)) {
currentAlias = parseAlias(aliasNode.value).id;
if (aliasHistory.has(currentAlias)) {
errors.push(`Couldn’t resolve circular alias "${v.value}"`);
break;
}
aliasHistory.add(currentAlias);
continue;
}
resolvedValue = aliasNode.value;
}
break;
}

if (resolvedValue) {
value = value.replace(match, resolvedValue);
}
}

if (!culoriParse(value)) {
// fix `rgba(#000000, 0.3)` scenario specifically (common Tokens Studio version)
// but throw err otherwise
if (value.startsWith('rgb') && value.includes('#')) {
const hexValue = value.match(/#[abcdef0-9]+/i);
if (hexValue && hexValue[0]) {
const rgbVal = rgb(hexValue[0]);
if (rgbVal) {
value = value.replace(hexValue[0], `${rgbVal.r * 100}%, ${rgbVal.g * 100}%, ${rgbVal.b * 100}%`);
}
}
}
}
}
addToken(
{
$type: 'color',
$value: v.value,
$value: value,
$description: v.description,
},
tokenPath,
Expand Down Expand Up @@ -441,9 +500,12 @@ export function convertTokensStudioFormat(rawTokens: Record<string, unknown>): {
} else if (values.length === 2 || values.length === 3 || values.length === 4) {
warnings.push(`Token "${tokenID}" is a multi value spacing token. Expanding into ${tokenID}Top, ${tokenID}Right, ${tokenID}Bottom, and ${tokenID}Left.`);
let order: [string, string, string, string] = [values[0], values[1], values[0], values[1]] as [string, string, string, string]; // TB, RL
if (values.length === 3)
{order = [values[0], values[1], values[2], values[1]] as [string, string, string, string];} // T, RL, B
else if (values.length === 4) {order = [values[0], values[1], values[2], values[3]] as [string, string, string, string];} // T, R, B, L
if (values.length === 3) {
order = [values[0], values[1], values[2], values[1]] as [string, string, string, string];
} // T, RL, B
else if (values.length === 4) {
order = [values[0], values[1], values[2], values[3]] as [string, string, string, string];
} // T, R, B, L
addToken({ $type: 'dimension', $value: order[0], $description: v.description }, [...path, `${k}Top`]);
addToken({ $type: 'dimension', $value: order[1], $description: v.description }, [...path, `${k}Right`]);
addToken({ $type: 'dimension', $value: order[2], $description: v.description }, [...path, `${k}Bottom`]);
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/parse/tokens/color.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export function normalizeColorValue(rawValue: unknown, options?: ParseColorOptio
if (typeof rawValue === 'string') {
const parsed = parse(rawValue);
if (!parsed) {
throw new Error(`invalid color "${rawValue}"`);
return rawValue; // TODO: should this warn?
}

let value = parsed as Color;
Expand Down
4 changes: 4 additions & 0 deletions packages/core/test/fixtures/tokens-studio.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@
"value": "#000000",
"type": "color"
},
"black-rgb": {
"value": "0, 0, 0",
"type": "color"
},
"white": {
"value": "#ffffff",
"type": "color"
Expand Down
2 changes: 1 addition & 1 deletion packages/core/test/tokens-studio.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,6 @@ describe('Example files', () => {
const cwd = new URL('./fixtures/', import.meta.url);
const tokens = JSON.parse(fs.readFileSync(new URL('./tokens-studio.json', cwd), 'utf8'));
const parsed = getTokens(tokens);
expect(parsed).toHaveLength(169);
expect(parsed).toHaveLength(170);
});
});

0 comments on commit 31195ac

Please sign in to comment.