@terrazzo/parser
Version:
Parser/validator for the Design Tokens Community Group (DTCG) standard.
172 lines • 5.95 kB
JavaScript
import * as momoa from '@humanwhocodes/momoa';
import { CachedWildcardMatcher } from '@terrazzo/token-tools';
import pc from 'picocolors';
import { codeFrameColumns } from './lib/code-frame.js';
export const LOG_ORDER = ['error', 'warn', 'info', 'debug'];
const GROUP_COLOR = {
config: pc.cyan,
import: pc.green,
lint: pc.yellowBright,
parser: pc.magenta,
plugin: pc.greenBright,
resolver: pc.magentaBright,
server: pc.gray,
};
const MESSAGE_COLOR = {
error: pc.red,
warn: pc.yellow,
info: (msg) => msg,
debug: pc.gray,
};
const timeFormatter = new Intl.DateTimeFormat('en-us', {
hour: 'numeric',
hour12: false,
minute: 'numeric',
second: 'numeric',
fractionalSecondDigits: 3,
});
/**
* @param {Entry} entry
* @param {Severity} severity
* @return {string}
*/
export function formatMessage(entry, severity) {
const groupColor = GROUP_COLOR[entry.group];
const messageColor = MESSAGE_COLOR[severity];
let message = entry.message;
message = `${groupColor(`${entry.group}${entry.label ? `:${entry.label}` : ''}:`)} ${messageColor(message)}`;
if (typeof entry.timing === 'number') {
message = `${message} ${formatTiming(entry.timing)}`;
}
if (entry.node) {
const start = entry.node?.loc?.start ?? { line: 0, column: 0 };
// strip "file://" protocol, but not href
const loc = entry.filename
? `${entry.filename?.href.replace(/^file:\/\//, '')}:${start?.line ?? 0}:${start?.column ?? 0}\n\n`
: '';
const codeFrame = codeFrameColumns(entry.src ?? momoa.print(entry.node, { indent: 2 }), { start }, { highlightCode: false });
message = `${message}\n\n${loc}${codeFrame}`;
}
return message;
}
const debugMatch = new CachedWildcardMatcher();
export default class Logger {
level = 'info';
debugScope = '*';
errorCount = 0;
warnCount = 0;
infoCount = 0;
debugCount = 0;
constructor(options) {
if (options?.level) {
this.level = options.level;
}
if (options?.debugScope) {
this.debugScope = options.debugScope;
}
}
setLevel(level) {
this.level = level;
}
/** Log an error message (always; can’t be silenced) */
error(...entries) {
const message = [];
let firstNode;
for (const entry of entries) {
this.errorCount++;
message.push(formatMessage(entry, 'error'));
if (entry.node) {
firstNode = entry.node;
}
}
if (entries.every((e) => e.continueOnError)) {
// oxlint-disable-next-line no-console -- this is a logger
console.error(message.join('\n\n'));
}
else {
const e = firstNode
? new TokensJSONError(message.join('\n\n'))
: new Error(message.join('\n\n'));
throw e;
}
}
/** Log an info message (if logging level permits) */
info(...entries) {
for (const entry of entries) {
this.infoCount++;
if (this.level === 'silent' || LOG_ORDER.indexOf(this.level) < LOG_ORDER.indexOf('info')) {
return;
}
const message = formatMessage(entry, 'info');
// oxlint-disable-next-line no-console -- this is a logger
console.log(message);
}
}
/** Log a warning message (if logging level permits) */
warn(...entries) {
for (const entry of entries) {
this.warnCount++;
if (this.level === 'silent' || LOG_ORDER.indexOf(this.level) < LOG_ORDER.indexOf('warn')) {
return;
}
const message = formatMessage(entry, 'warn');
// oxlint-disable-next-line no-console -- this is a logger
console.warn(message);
}
}
/** Log a diagnostics message (if logging level permits) */
debug(...entries) {
for (const entry of entries) {
if (this.level === 'silent' || LOG_ORDER.indexOf(this.level) < LOG_ORDER.indexOf('debug')) {
return;
}
this.debugCount++;
let message = formatMessage(entry, 'debug');
const debugPrefix = entry.label ? `${entry.group}:${entry.label}` : entry.group;
if (this.debugScope !== '*' && !debugMatch.match(this.debugScope)(debugPrefix)) {
return;
}
// debug color
message
.replace(/\[config[^\]]+\]/, (match) => pc.green(match))
.replace(/\[parser[^\]]+\]/, (match) => pc.magenta(match))
.replace(/\[lint[^\]]+\]/, (match) => pc.yellow(match))
.replace(/\[plugin[^\]]+\]/, (match) => pc.cyan(match));
message = `${pc.dim(timeFormatter.format(performance.now()))} ${message}`;
if (typeof entry.timing === 'number') {
message = `${message} ${formatTiming(entry.timing)}`;
}
// oxlint-disable-next-line no-console -- this is a logger
console.log(message);
}
}
/** Get stats for current logger instance */
stats() {
return {
errorCount: this.errorCount,
warnCount: this.warnCount,
infoCount: this.infoCount,
debugCount: this.debugCount,
};
}
}
function formatTiming(timing) {
let output = '';
if (timing < 1_000) {
output = `${Math.round(timing * 100) / 100}ms`;
}
else if (timing < 60_000) {
output = `${Math.round(timing) / 1_000}s`;
}
else {
output = `${Math.round(timing / 1_000) / 60}m`;
}
return pc.dim(`[${output}]`);
}
export class TokensJSONError extends Error {
constructor(message) {
super(message);
this.name = 'TokensJSONError';
}
}
//# sourceMappingURL=logger.js.map