@terrazzo/parser
Version:
Parser/validator for the Design Tokens Community Group (DTCG) standard.
120 lines • 4.69 kB
JavaScript
import * as momoa from '@humanwhocodes/momoa';
import { bundle, encodeFragment, getObjMember, replaceNode, traverse, } from '@terrazzo/json-schema-tools';
import { toMomoa } from '../lib/momoa.js';
import { filterResolverPaths } from '../lib/resolver-utils.js';
import { isLikelyResolver } from '../resolver/validate.js';
import { processTokens } from './process.js';
/** Load from multiple entries, while resolving remote files */
export async function loadSources(inputs, { config, logger, req, continueOnError, yamlToMomoa, resolveAliases = true, transform, }) {
const entry = { group: 'parser', label: 'init' };
// 1. Bundle root documents together
const firstLoad = performance.now();
let document = {};
/** The original user inputs, in original order, with parsed ASTs */
const sources = inputs.map((input, i) => ({
...input,
document: {},
filename: input.filename || new URL(`virtual:${i}`), // for objects created in memory, an index-based ID helps associate tokens with these
}));
/** The sources array, indexed by filename */
let sourceByFilename = {};
try {
const result = await bundle(sources, {
req,
parse: transform ? transformer(transform) : undefined,
yamlToMomoa,
});
document = result.document;
sourceByFilename = result.sources;
for (const [filename, source] of Object.entries(result.sources)) {
const i = sources.findIndex((s) => s.filename.href === filename);
if (i === -1) {
sources.push(source);
}
else {
sources[i].src = source.src; // this is a sanitized source that is easier to work with
sources[i].document = source.document;
}
}
}
catch (error) {
let src = sources.find((s) => s.filename.href === error.filename)?.src;
if (src && typeof src !== 'string') {
src = JSON.stringify(src, undefined, 2);
}
logger.error({
...entry,
continueOnError,
message: error.message,
node: error.node,
src,
});
}
logger.debug({ ...entry, message: `JSON loaded`, timing: performance.now() - firstLoad });
const rootSource = {
filename: sources[0].filename,
document,
src: momoa.print(document, { indent: 2 }).replaceAll(String.raw `\/`, '/'),
};
return {
tokens: processTokens(rootSource, {
config,
logger,
resolveAliases,
sources,
sourceByFilename,
}),
sources,
};
}
function transformer(transform) {
// oxlint-disable-next-line require-await
return async (src, filename) => {
let document = toMomoa(src);
let lastPath = '#/';
let last$type;
if (transform.root) {
const result = transform.root(document, { filename, parent: undefined, path: [] });
if (result) {
document = result;
}
}
const isResolver = isLikelyResolver(document);
traverse(document, {
enter(node, parent, rawPath) {
const path = isResolver ? filterResolverPaths(rawPath) : rawPath;
if (node.type !== 'Object' || path.length === 0) {
return;
}
const ctx = { filename, parent, path };
const next$type = getObjMember(node, '$type');
if (next$type?.type === 'String') {
const jsonPath = encodeFragment(path);
if (jsonPath.startsWith(lastPath)) {
last$type = next$type.value;
}
lastPath = jsonPath;
}
if (getObjMember(node, '$value')) {
let result = transform.token?.(structuredClone(node), ctx);
if (result) {
replaceNode(node, result);
result = undefined;
}
result = transform[last$type]?.(structuredClone(node), ctx);
if (result) {
replaceNode(node, result);
}
}
else if (!path.includes('$value')) {
const result = transform.group?.(structuredClone(node), ctx);
if (result) {
replaceNode(node, result);
}
}
},
});
return document;
};
}
//# sourceMappingURL=load.js.map