@terrazzo/parser
Version:
Parser/validator for the Design Tokens Community Group (DTCG) standard.
166 lines • 6.83 kB
JavaScript
import { isTokenMatch } from '@terrazzo/token-tools';
import wcmatch from 'wildcard-match';
import Logger from '../logger.js';
export const SINGLE_VALUE = 'SINGLE_VALUE';
export const MULTI_VALUE = 'MULTI_VALUE';
/** Validate plugin setTransform() calls for immediate feedback */
function validateTransformParams({ params, logger, pluginName, }) {
const baseMessage = { group: 'plugin', label: pluginName, message: '' };
// validate value is valid for SINGLE_VALUE or MULTI_VALUE
if (!params.value ||
(typeof params.value !== 'string' && typeof params.value !== 'object') ||
Array.isArray(params.value)) {
logger.error({
...baseMessage,
message: `setTransform() value expected string or object of strings, received ${Array.isArray(params.value) ? 'Array' : typeof params.value}`,
});
}
if (typeof params.value === 'object' && Object.values(params.value).some((v) => typeof v !== 'string')) {
logger.error({
...baseMessage,
message: 'setTransform() value expected object of strings, received some non-string values',
});
}
}
/**
* Run build stage
*/
export default async function build(tokens, { sources, logger = new Logger(), config }) {
const formats = {};
const result = { outputFiles: [] };
function getTransforms(params) {
return (formats[params.format] ?? []).filter((token) => {
if (params.$type) {
if (typeof params.$type === 'string' && token.token.$type !== params.$type) {
return false;
}
else if (Array.isArray(params.$type) && !params.$type.some(($type) => token.token.$type === $type)) {
return false;
}
}
if (params.id &&
params.id !== '*' &&
!isTokenMatch(token.token.id, Array.isArray(params.id) ? params.id : [params.id])) {
return false;
}
if (params.mode && !wcmatch(params.mode)(token.mode)) {
return false;
}
return true;
});
}
// transform()
let transformsLocked = false; // prevent plugins from transforming after stage has ended
const startTransform = performance.now();
for (const plugin of config.plugins) {
if (typeof plugin.transform === 'function') {
await plugin.transform({
tokens,
sources,
getTransforms,
setTransform(id, params) {
if (transformsLocked) {
logger.warn({
message: 'Attempted to call setTransform() after transform step has completed.',
group: 'plugin',
label: plugin.name,
});
return;
}
const token = tokens[id];
// allow `undefined` values, but remove them here
const cleanValue = typeof params.value === 'string' ? params.value : { ...params.value };
if (typeof cleanValue === 'object') {
for (const k of Object.keys(cleanValue)) {
if (cleanValue[k] === undefined) {
delete cleanValue[k];
}
}
}
validateTransformParams({
logger,
params: { ...params, value: cleanValue },
pluginName: plugin.name,
});
// upsert
if (!formats[params.format]) {
formats[params.format] = [];
}
const foundTokenI = formats[params.format].findIndex((t) => id === t.id &&
(!params.localID || params.localID === t.localID) &&
(!params.mode || params.mode === t.mode));
if (foundTokenI === -1) {
formats[params.format].push({
...params,
id,
value: cleanValue,
type: typeof cleanValue === 'string' ? SINGLE_VALUE : MULTI_VALUE,
mode: params.mode || '.',
token: structuredClone(token),
});
}
else {
formats[params.format][foundTokenI].value = cleanValue;
formats[params.format][foundTokenI].type = typeof cleanValue === 'string' ? SINGLE_VALUE : MULTI_VALUE;
}
},
});
}
}
transformsLocked = true;
logger.debug({
group: 'parser',
label: 'transform',
message: 'transform() step',
timing: performance.now() - startTransform,
});
// build()
const startBuild = performance.now();
for (const plugin of config.plugins) {
if (typeof plugin.build === 'function') {
const pluginBuildStart = performance.now();
await plugin.build({
tokens,
sources,
getTransforms,
outputFile(filename, contents) {
const resolved = new URL(filename, config.outDir);
if (result.outputFiles.some((f) => new URL(f.filename, config.outDir).href === resolved.href)) {
logger.error({
group: 'plugin',
message: `Can’t overwrite file "${filename}"`,
label: plugin.name,
});
}
result.outputFiles.push({
filename,
contents,
plugin: plugin.name,
time: performance.now() - pluginBuildStart,
});
},
});
}
}
logger.debug({
group: 'parser',
label: 'build',
message: 'build() step',
timing: performance.now() - startBuild,
});
// buildEnd()
const startBuildEnd = performance.now();
for (const plugin of config.plugins) {
if (typeof plugin.buildEnd === 'function') {
await plugin.buildEnd({ outputFiles: structuredClone(result.outputFiles) });
}
}
logger.debug({
group: 'parser',
label: 'build',
message: 'buildEnd() step',
timing: performance.now() - startBuildEnd,
});
return result;
}
//# sourceMappingURL=index.js.map