UNPKG

@terrazzo/parser

Version:

Parser/validator for the Design Tokens Community Group (DTCG) standard.

472 lines 16.9 kB
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