@terrazzo/parser
Version:
Parser/validator for the Design Tokens Community Group (DTCG) standard.
472 lines • 16.9 kB
TypeScript
import type * as momoa from '@humanwhocodes/momoa';
import type { InputSourceWithDocument } from '@terrazzo/json-schema-tools';
import type { Group, TokenNormalized, TokenNormalizedSet, TokenTransformed, TokenTransformedBase } from '@terrazzo/token-types';
import type ytm from 'yaml-to-momoa';
import type Logger from './logger.js';
export type { Group, TokenNormalized, TokenNormalizedSet, TokenTransformed, TokenTransformedBase, } from '@terrazzo/token-types';
export interface PluginHookContext {
logger: Logger;
}
export interface BuildHookOptions {
/** Plugin hook context (provides access to shared logger) */
context: PluginHookContext;
/** Map of tokens */
tokens: Record<string, TokenNormalized>;
/** Query transformed values */
getTransforms(this: void, params: TransformParams): TokenTransformed[];
/** Momoa documents */
sources: InputSourceWithDocument[];
/** Resolver */
resolver: Resolver;
outputFile: (
/** Filename to output (relative to outDir) */
filename: string,
/** Contents to write to file */
contents: string | Buffer) => void;
}
export interface BuildRunnerResult {
outputFiles: OutputFileExpanded[];
}
export interface BuildEndHookOptions {
/** Plugin hook context (provides access to shared logger) */
context: PluginHookContext;
/** Map of tokens */
tokens: Record<string, TokenNormalized>;
/** Query transformed values */
getTransforms(this: void, params: TransformParams): TokenTransformed[];
/** Momoa documents */
sources: InputSourceWithDocument[];
/** Final files to be written */
outputFiles: OutputFileExpanded[];
}
export interface Config {
/**
* Path to tokens.json
* @default "./tokens.json"
*/
tokens?: string | string[];
/**
* Output directory
* @default "./tokens/"
*/
outDir?: string;
/** Specify plugins */
plugins?: Plugin[];
/** Alphabetize tokens by ID to make output more consistent (note: some plugins may not preserve this order). @default true */
alphabetize?: boolean;
/** Specify linting settings */
lint?: {
/** Configure build behavior */
build?: {
/**
* Should linters run with `tz build`?
* @default true
*/
enabled?: boolean;
};
/** Configure lint rules */
rules?: Record<string, LintRuleShorthand | LintRuleLonghand>;
};
/** Ignore token groups */
ignore?: {
/** Token patterns to ignore. Accepts globs. */
tokens?: string[];
/** Ignore deprecated tokens */
deprecated?: boolean;
};
/**
* Set the maximum number of permutations before the listPermutations API is disabled.
*
* This is a safety feature to prevent denial of service issues on complex resolvers.
*
* @default 1000
*/
permutationLimit?: number;
}
export interface VisitorContext {
parent?: momoa.AnyNode;
filename: URL;
path: string[];
}
export type Visitor<T extends momoa.AnyNode = momoa.ObjectNode | momoa.DocumentNode> = (node: T, context: VisitorContext) => T | void | null | undefined;
export interface TransformVisitors {
boolean?: Visitor;
border?: Visitor;
color?: Visitor;
cubicBezier?: Visitor;
dimension?: Visitor;
duration?: Visitor;
fontFamily?: Visitor;
fontWeight?: Visitor;
gradient?: Visitor;
group?: Visitor;
link?: Visitor;
number?: Visitor;
root?: Visitor;
shadow?: Visitor;
strokeStyle?: Visitor;
token?: Visitor;
transition?: Visitor;
typography?: Visitor;
[key: string]: Visitor | undefined;
}
export interface ConfigInit {
tokens: URL[];
outDir: URL;
plugins: Plugin[];
alphabetize: boolean;
lint: {
build: NonNullable<NonNullable<Config['lint']>['build']>;
rules: Record<string, LintRuleLonghand>;
};
ignore: {
tokens: NonNullable<NonNullable<Config['ignore']>['tokens']>;
deprecated: NonNullable<NonNullable<Config['ignore']>['deprecated']>;
};
permutationLimit: number;
}
export interface ConfigOptions {
logger?: Logger;
/** @terrazzo/parser needs cwd so this can be run without Node.js. Importing defineConfig from @terrazzo/cli doesn’t need this. */
cwd: URL;
}
export interface LintNotice {
/** Lint message shown to the user */
message: string;
/** Erring node (used to point to a specific line) */
node?: momoa.AnyNode;
}
export type LintRuleSeverity = 'error' | 'warn' | 'off';
export type LintRuleShorthand = LintRuleSeverity | 0 | 1 | 2;
export type LintRuleLonghand = [LintRuleSeverity | 0 | 1 | 2, any];
export interface LintRuleNormalized<O = any> {
id: string;
severity: LintRuleSeverity;
options?: O;
}
export type LintReportDescriptor<MessageIds extends string> = {
/** To error on a specific token source file, provide a Momoa node */
node?: momoa.AnyNode;
/** To provide correct line numbers, specify the filename (usually found on `token.source.loc`) */
filename?: string;
/** Provide data for messages */
data?: Record<string, unknown>;
} & ({
/** Provide the error message to display */
message: string;
messageId?: never;
} | {
message?: never;
/** Provide the error message ID */
messageId: MessageIds;
});
export interface LintRule<MessageIds extends string, LintRuleOptions extends Record<string, any> = Record<string, never>, LintRuleDocs = unknown> {
meta?: LintRuleMetaData<MessageIds, LintRuleOptions, LintRuleDocs>;
/**
* Function which returns an object with methods that ESLint calls to “visit”
* nodes while traversing the abstract syntax tree.
*/
create(context: Readonly<LintRuleContext<MessageIds, LintRuleOptions>>): void | Promise<void>;
/**
* Default options the rule will be run with
*/
defaultOptions: LintRuleOptions;
}
export interface LintRuleContext<MessageIds extends string, LintRuleOptions extends object | undefined = undefined> {
/** The rule ID. */
id: string;
/**
* An array of the configured options for this rule. This array does not
* include the rule severity.
*/
options: LintRuleOptions;
/** The current working directory. */
cwd?: URL;
/**
* All source files present in this run. To find the original source, match a
* token’s `source.loc` filename to one of the source’s `filename`s.
*/
sources: InputSourceWithDocument[];
/** Source file location. */
filename?: URL;
/** ID:Token map of all tokens. */
tokens: Record<string, TokenNormalized>;
/** Reports a problem in the code. */
report(descriptor: LintReportDescriptor<MessageIds>): void;
}
export interface LintRuleMetaData<MessageIds extends string, LintRuleOptions extends object | undefined = undefined, LintRuleDocs = unknown> {
/**
* Documentation for the rule
*/
docs?: LintRuleDocs & LintRuleMetaDataDocs;
/**
* A map of messages which the rule can report. The key is the messageId, and
* the string is the parameterized error string.
*/
messages?: Record<MessageIds, string>;
/**
* Specifies default options for the rule. If present, any user-provided
* options in their config will be merged on top of them recursively. This
* merging will be applied directly to `context.options`.
*/
defaultOptions?: LintRuleOptions;
}
export interface LintRuleMetaDataDocs {
/** Concise description of the rule. */
description: string;
/** The URL of the rule's docs. */
url?: string;
}
export interface OutputFile {
/** Filename, relative to outDir */
filename: string;
/** File contents */
contents: string | Buffer;
/** Plugin name that generated the file */
plugin?: string;
/** Time taken to generate file */
time?: number;
}
export interface OutputFileExpanded extends OutputFile {
/** The `name` of the plugin that produced this file. */
plugin: string;
/** How long this output took to make. */
time: number;
}
export interface ParseOptions {
logger?: Logger;
config: ConfigInit;
/**
* Handle requests to loading remote files, either from a remote URL or on the filesystem.
* - Remote requests will have an "https:' protocol
* - Filesystem files will have a "file:" protocol
*/
req?: (src: URL, origin: URL) => Promise<string>;
/**
* Skip lint step
* @default false
*/
skipLint?: boolean;
/**
* Continue on error? (Useful for `tz check`)
* @default false
*/
continueOnError?: boolean;
/** Provide yamlToMomoa module to parse YAML (by default, this isn’t shipped to cut down on package weight) */
yamlToMomoa?: typeof ytm;
/**
* Transform API
* @see https://terrazzo.app/docs/api/js#transform-api
*/
transform?: TransformVisitors;
/** (internal cache; do not use) */
_sources?: Record<string, InputSourceWithDocument>;
/** Resolve DTCG aliases? You’d typically only pass in `false` when normalizing or performing partial parsing. */
resolveAliases?: boolean;
}
export interface Plugin {
name: string;
/** Read config, and optionally modify */
config?(config: ConfigInit, context: PluginHookContext): void | ConfigInit | undefined;
/**
* Declare:
* - `"pre"`: run this plugin BEFORE all others
* - `"post"`: run this plugin AFTER all others
* - (default) run this plugin in default order (array order)
*/
enforce?: 'pre' | 'post';
/** Throw lint errors/warnings */
lint?(): Record<string, LintRule<any, any, any>>;
transform?(options: TransformHookOptions): void | Promise<void>;
build?(options: BuildHookOptions): void | Promise<void>;
buildEnd?(options: BuildEndHookOptions): void | Promise<void>;
}
export interface ReferenceObject {
$ref: string;
}
export type ResolverInput = Record<string, string>;
export interface ResolverApplicationOptions {
/**
* Resolve DTCG aliases when applying the input.
*
* @default true
*/
resolveAliases?: boolean;
/**
* Limit input application only to the listed sets.
*
* In combination with `modifiers`, this will limit output to only
* tokens declared within the options given. If tokens are referenced by alises
* outside these options, the application will fail unless `resolveAliases` is
* set to false.
*/
sets?: string[];
/**
* Limit input application only to the listed modifiers.
*
* In combination with `sets`, this will limit output to only
* tokens declared within the options given. If tokens are referenced by alises
* outside these options, the application will fail unless `resolveAliases` is
* set to false.
*/
modifiers?: string[];
}
export interface Resolver<Inputs extends Record<string, string[]> = Record<string, string[]>, Input = Record<keyof Inputs, Inputs[keyof Inputs][number]>> {
/**
* Supply values to modifiers to produce a final tokens set. This caches the
* results, so calling a 2nd time with the same inputs will return the same
* results (it ignores object key order, and takes defaults into account for
* better caching).
*/
apply: (input: Partial<Input>, options?: ResolverApplicationOptions) => TokenNormalizedSet;
/**
* List all possible valid input combinations. Ignores default values, as they
* would duplicate some other permutations. This also caches results, so it’s
* only computed once on the first call.
*
* If the resolver is deemed to complex, this API is not provided.
*/
listPermutations?: () => Input[];
getPermutationID: (input: Input) => string;
/** The original resolver document, simplified */
source: ResolverSourceNormalized;
/** Helper function for permutations—see if a particular input is valid. Automatically applies default values. */
isValidInput: (input: Input, throwError?: boolean) => boolean;
/**
* Do all modifiers in this resolver operate on unique tokens?
*
* This is all-or-nothing, if even a single token is referenced in 2
* modifiers, the entire resolver is non-orthogonal.
*/
orthogonal: boolean;
}
export interface ResolverSource {
/** Human-friendly name of this resolver */
name?: string;
/** DTCG version */
version: '2025.10';
/** Description of this resolver */
description?: string;
/** Mapping of sets */
sets?: Record<string, ResolverSet>;
/** Mapping of modifiers */
modifiers?: Record<string, ResolverModifier>;
resolutionOrder: (ResolverSetInline | ResolverModifierInline | ReferenceObject)[];
$extensions?: Record<string, unknown>;
$defs?: Record<string, unknown>;
}
/** Resolver where all tokens are loaded and flattened in-memory, so only the final merging is left */
export interface ResolverSourceNormalized {
name: string | undefined;
version: '2025.10';
description: string | undefined;
sets: Record<string, ResolverSetNormalized> | undefined;
modifiers: Record<string, ResolverModifierNormalized> | undefined;
/**
* Array of all sets and modifiers that have been converted to inline,
* regardless of original declaration. In a normalized resolver, only a single
* pass over the resolutionOrder array is needed.
*/
resolutionOrder: (ResolverSetNormalized | ResolverModifierNormalized)[];
_source: {
filename?: URL;
document: momoa.DocumentNode;
};
}
export interface ResolverModifier<Context extends string = string> {
description?: string;
contexts: Record<Context, (Group | ReferenceObject)[]>;
default?: Context;
$extensions?: Record<string, unknown>;
$defs?: Record<string, unknown>;
}
export type ResolverModifierInline<Context extends string = string> = ResolverModifier<Context> & {
name: string;
type: 'modifier';
};
export interface ResolverModifierNormalized {
name: string;
type: 'modifier';
description: string | undefined;
contexts: Record<string, Group[]>;
default: string | undefined;
$extensions: Record<string, unknown> | undefined;
$defs: Record<string, unknown> | undefined;
}
export interface ResolverSet {
description?: string;
sources: (Group | ReferenceObject)[];
$extensions?: Record<string, unknown>;
$defs?: Record<string, unknown>;
}
export type ResolverSetInline = ResolverSet & {
name: string;
type: 'set';
};
export interface ResolverSetNormalized {
name: string;
type: 'set';
description: string | undefined;
sources: Group[];
$extensions: Record<string, unknown> | undefined;
$defs: Record<string, unknown> | undefined;
}
export type TransformParams = TransformParamsLegacy | TransformParamsResolver;
export interface TransformParamsBase {
/** ID of an existing format */
format: string;
/** Glob of tokens to select (e.g. `"color.*"` to select all tokens starting with `"color."`) */
id?: string | string[];
/** $type(s) to filter for */
$type?: string | string[];
}
export interface TransformParamsLegacy extends TransformParamsBase {
/**
* Mode name, if selecting a mode
* @deprecated Use input instead.
* @default "."
*/
mode?: string | string[];
/** Input that marks the transformation as a permutation */
input?: never;
}
export interface TransformParamsResolver extends TransformParamsBase {
mode?: never;
/** Input that marks the transformation as a permutation */
input: ResolverInput;
}
export interface TransformHookOptions {
/** Plugin hook context (provides access to shared logger) */
context: PluginHookContext;
/** Map of tokens */
tokens: Record<string, TokenNormalized>;
/** Query transformed values */
getTransforms(this: void, params: TransformParams): TokenTransformed[];
/** Update transformed values */
setTransform(this: void, id: string, params: {
format: string;
localID?: string;
value: string | Record<string, string>;
/** @deprecated */
mode?: string;
input?: never;
meta?: TokenTransformedBase['meta'];
} | {
format: string;
localID?: string;
value: string | Record<string, string>;
mode?: never;
input: ResolverInput;
meta?: TokenTransformedBase['meta'];
}): void;
/** Resolver */
resolver: Resolver;
/** Momoa documents */
sources: InputSourceWithDocument[];
}
export interface RefMapEntry {
filename: string;
refChain: string[];
}
export type RefMap = Record<string, RefMapEntry>;
//# sourceMappingURL=types.d.ts.map