const BASE_KEYS = ['value', 'isReference', 'isDeprecated', 'comment', 'isBase'] as const type BaseKey = (typeof BASE_KEYS)[number] export type FlattenedToken = { name: string value: string | number isReference?: boolean isDeprecated?: boolean comment?: string isBase?: boolean } type TokenTree = { value?: string | number isReference?: boolean isDeprecated?: boolean comment?: string isBase?: boolean [key: string]: TokenTree | string | number | boolean | undefined } function camelToKebab(str: string) { return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase() } function isBaseKey(key: string): key is BaseKey { return BASE_KEYS.includes(key as BaseKey) } function createFlattenedToken(name: string, tree: TokenTree): FlattenedToken { const token: FlattenedToken = { name, value: tree.value ?? '' } if (tree.isReference) token.isReference = true if (tree.isDeprecated) token.isDeprecated = true if (tree.comment) token.comment = tree.comment if (tree.isBase) token.isBase = true return token } /** * Flatten a nested token tree into token records used by SCSS generation. */ export function flattenTree(nestedTree: TokenTree, prefix = ''): FlattenedToken[] { const keys = Object.keys(nestedTree) if (keys.includes('value') && keys.every(isBaseKey)) { return [createFlattenedToken(prefix, nestedTree)] } if (keys.length === 0 || JSON.stringify(nestedTree) === '{}' || nestedTree === null) { throw new Error('convertJSONtoScss: Invalid JSON data structure: empty object.') } if (!keys.includes('value')) { return keys .map((key) => { const newPrefix = prefix ? `${prefix}-${camelToKebab(key)}` : camelToKebab(key) return flattenTree(nestedTree[key] as TokenTree, newPrefix) }) .flat() } if (keys.includes('value') && keys.some((key) => !isBaseKey(key))) { const valueObj = createFlattenedToken(prefix, nestedTree) const restObjs = keys .filter((key) => !isBaseKey(key)) .map((key) => { const newPrefix = prefix ? `${prefix}-${camelToKebab(key)}` : camelToKebab(key) const childTree = nestedTree[key] if (!childTree || typeof childTree !== 'object') return { name: newPrefix, value: 'not defined' } return flattenTree(childTree, newPrefix) }) .flat() return [valueObj, ...restObjs] } return [] }