UNPKG

@terrazzo/parser

Version:

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

706 lines (704 loc) 24.9 kB
import { AliasToken, AliasValue, BooleanToken, BooleanTokenNormalized, BooleanValue, BorderToken, BorderTokenNormalized, BorderValue, BorderValueNormalized, ColorSpace, ColorToken, ColorTokenNormalized, ColorValue, ColorValueNormalized, CubicBezierToken, CubicBezierTokenNormalized, CubicBezierValue, CustomTransformOptions, DimensionToken, DimensionTokenNormalized, DimensionValue, DurationToken, DurationTokenNormalized, DurationValue, FontFamilyToken, FontFamilyTokenNormalized, FontFamilyValue, FontFamilyValueNormalized, FontWeightToken, FontWeightTokenNormalized, FontWeightValue, FontWeightValueNormalized, GradientStop, GradientStopNormalized, GradientToken, GradientTokenNormalized, GradientValue, GradientValueNormalized, Group, GroupCore, GroupOrToken, LinkToken, LinkTokenNormalized, LinkValue, ModeMap, NumberToken, NumberTokenNormalized, NumberValue, ShadowToken, ShadowTokenNormalized, ShadowValue, ShadowValueNormalized, StringToken, StringTokenNormalized, StringValue, StrokeStyleToken, StrokeStyleTokenNormalized, StrokeStyleValue, StrokeStyleValueExpanded, Token, Token as Token$1, TokenCore, TokenMode, TokenNormalized, TokenNormalized as TokenNormalized$1, TokenNormalizedCore, TokenNormalizedSet, TokenNormalizedSet as TokenNormalizedSet$1, TokensSet, TransitionToken, TransitionTokenNormalized, TransitionValue, TransitionValueNormalized, TypographyToken, TypographyTokenNormalized, TypographyValue, TypographyValueNormalized } from "@terrazzo/token-tools"; import { AnyNode, DocumentNode, MemberNode, ObjectNode, ParseOptions as ParseOptions$1, ValueNode } from "@humanwhocodes/momoa"; import ytm from "yaml-to-momoa"; //#region src/logger.d.ts declare const LOG_ORDER: readonly ["error", "warn", "info", "debug"]; type LogSeverity = 'error' | 'warn' | 'info' | 'debug'; type LogLevel = LogSeverity | 'silent'; type LogGroup = 'config' | 'parser' | 'lint' | 'plugin' | 'server'; interface LogEntry { /** Originator of log message */ group: LogGroup; /** Error message to be logged */ message: string; /** Prefix message with label */ label?: string; /** File in disk */ filename?: URL; /** * Continue on error? * @default false */ continueOnError?: boolean; /** Show a code frame for the erring node */ node?: AnyNode; /** To show a code frame, provide the original source code */ src?: string; } interface DebugEntry { group: LogGroup; /** Error message to be logged */ message: string; /** Current subtask or submodule */ label?: string; /** Show code below message */ codeFrame?: { src: string; line: number; column: number; }; /** Display performance timing */ timing?: number; } /** * @param {Entry} entry * @param {Severity} severity * @return {string} */ declare function formatMessage(entry: LogEntry, severity: LogSeverity): string; declare class Logger { level: LogLevel; debugScope: string; errorCount: number; warnCount: number; infoCount: number; debugCount: number; constructor(options?: { level?: LogLevel; debugScope?: string; }); setLevel(level: LogLevel): void; /** Log an error message (always; can’t be silenced) */ error(entry: LogEntry): void; /** Log an info message (if logging level permits) */ info(entry: LogEntry): void; /** Log a warning message (if logging level permits) */ warn(entry: LogEntry): void; /** Log a diagnostics message (if logging level permits) */ debug(entry: DebugEntry): void; /** Get stats for current logger instance */ stats(): { errorCount: number; warnCount: number; infoCount: number; debugCount: number; }; } declare class TokensJSONError extends Error { constructor(message: string); } //# sourceMappingURL=logger.d.ts.map //#endregion //#region src/types.d.ts interface BuildHookOptions { /** Map of tokens */ tokens: Record<string, TokenNormalized$1>; /** Query transformed values */ getTransforms(params: TransformParams): TokenTransformed[]; /** Momoa documents */ sources: InputSource[]; outputFile: (/** Filename to output (relative to outDir) */ filename: string, /** Contents to write to file */ contents: string | Buffer) => void; } interface BuildRunnerResult { outputFiles: OutputFile[]; } interface BuildEndHookOptions { /** Map of tokens */ tokens: Record<string, TokenNormalized$1>; /** Query transformed values */ getTransforms(params: TransformParams): TokenTransformed[]; /** Momoa documents */ sources: InputSource[]; /** Final files to be written */ outputFiles: OutputFileExpanded[]; } interface Config { /** * Path to tokens.json * @default "./tokens.json" */ tokens?: string | string[]; /** * Output directory * @default "./tokens/" */ outDir?: string; /** Specify plugins */ plugins?: Plugin[]; /** 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; }; } interface ConfigInit { tokens: URL[]; outDir: URL; plugins: Plugin[]; lint: { build: NonNullable<NonNullable<Config['lint']>['build']>; rules: Record<string, LintRuleLonghand>; }; ignore: { tokens: NonNullable<NonNullable<Config['ignore']>['tokens']>; deprecated: NonNullable<NonNullable<Config['ignore']>['deprecated']>; }; } 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; } interface InputSource { filename?: URL; src: any; document: DocumentNode; } interface LintNotice { /** Lint message shown to the user */ message: string; /** Erring node (used to point to a specific line) */ node?: AnyNode; } type LintRuleSeverity = 'error' | 'warn' | 'off'; type LintRuleShorthand = LintRuleSeverity | 0 | 1 | 2; type LintRuleLonghand = [LintRuleSeverity | 0 | 1 | 2, any]; interface LintRuleNormalized<O = any> { id: string; severity: LintRuleSeverity; options?: O; } type LintReportDescriptor<MessageIds extends string> = { /** To error on a specific token source file, provide an erring node */ node?: AnyNode; /** To error on a specific token source file, also provide the source */ source?: InputSource; /** 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; }); interface LintRule<MessageIds extends string, LintRuleOptions extends object | undefined = undefined, 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; } 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; /** Source file the token came from. */ src: string; /** Source file location. */ filename?: URL; /** ID:Token map of all tokens. */ tokens: Record<string, TokenNormalized$1>; /** Reports a problem in the code. */ report(descriptor: LintReportDescriptor<MessageIds>): void; } 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 parameterised 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; } interface LintRuleMetaDataDocs { /** Concise description of the rule. */ description: string; /** The URL of the rule's docs. */ url?: string; } 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; } interface OutputFileExpanded extends OutputFile { /** The `name` of the plugin that produced this file. */ plugin: string; /** How long this output took to make. */ time: number; } interface Plugin { name: string; /** Read config, and optionally modify */ config?(config: ConfigInit): 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): Promise<void>; build?(options: BuildHookOptions): Promise<void>; buildEnd?(result: BuildRunnerResult): Promise<void>; } /** Transformed token with a single value. Note that this may be any type! */ interface TokenTransformedSingleValue { /** Original Token ID */ id: string; /** ID unique to this format. */ localID?: string; type: 'SINGLE_VALUE'; value: string; /** * The mode of this value * @default "." */ mode: string; /** The original token. */ token: TokenNormalized$1; } /** Transformed token with multiple values. Note that this may be any type! */ interface TokenTransformedMultiValue { /** Original Token ID */ id: string; /** ID unique to this format.*/ localID?: string; type: 'MULTI_VALUE'; value: Record<string, string>; /** * The mode of this value * @default "." */ mode: string; /** The original token */ token: TokenNormalized$1; } type TokenTransformed = TokenTransformedSingleValue | TokenTransformedMultiValue; interface TransformParams { /** 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[]; /** * Mode name, if selecting a mode * @default "." */ mode?: string | string[]; } interface TransformHookOptions { /** Map of tokens */ tokens: Record<string, TokenNormalized$1>; /** Query transformed values */ getTransforms(params: TransformParams): TokenTransformed[]; /** Update transformed values */ setTransform(id: string, params: { format: string; localID?: string; value: string | Record<string, string>; mode?: string; }): void; /** Momoa documents */ sources: InputSource[]; } //# sourceMappingURL=types.d.ts.map //#endregion //#region src/build/index.d.ts interface BuildRunnerOptions { sources: { filename?: URL; src: string; document: DocumentNode; }[]; config: ConfigInit; logger?: Logger; } declare const SINGLE_VALUE = "SINGLE_VALUE"; declare const MULTI_VALUE = "MULTI_VALUE"; /** Run build stage */ declare function build(tokens: Record<string, TokenNormalized$1>, { sources, logger, config }: BuildRunnerOptions): Promise<BuildRunnerResult>; //# sourceMappingURL=index.d.ts.map //#endregion //#region src/config.d.ts /** * Validate and normalize a config */ declare function defineConfig(rawConfig: Config, { logger, cwd }?: ConfigOptions): ConfigInit; /** Merge configs */ declare function mergeConfigs(a: Config, b: Config): Config; //# sourceMappingURL=config.d.ts.map //#endregion //#region src/lint/index.d.ts interface LintRunnerOptions { tokens: Record<string, TokenNormalized$1>; filename?: URL; config: ConfigInit; src: string; logger: Logger; } declare function lintRunner({ tokens, filename, config, src, logger }: LintRunnerOptions): Promise<void>; //# sourceMappingURL=index.d.ts.map //#endregion //#region src/parse/normalize.d.ts declare const FONT_WEIGHT_MAP: { thin: number; hairline: number; 'extra-light': number; 'ultra-light': number; light: number; normal: number; regular: number; book: number; medium: number; 'semi-bold': number; 'demi-bold': number; bold: number; 'extra-bold': number; 'ultra-bold': number; black: number; heavy: number; 'extra-black': number; 'ultra-black': number; }; /** Fill in defaults, and return predictable shapes for tokens */ declare function normalizeValue<T extends Token$1>(token: T): T['$value']; //# sourceMappingURL=normalize.d.ts.map //#endregion //#region src/parse/validate.d.ts interface ValidateOptions { filename?: URL; src: string; logger: Logger; } interface Visitors { color?: Visitor; dimension?: Visitor; fontFamily?: Visitor; fontWeight?: Visitor; duration?: Visitor; cubicBezier?: Visitor; number?: Visitor; link?: Visitor; boolean?: Visitor; strokeStyle?: Visitor; border?: Visitor; transition?: Visitor; shadow?: Visitor; gradient?: Visitor; typography?: Visitor; root?: Visitor; group?: Visitor; [key: string]: Visitor | undefined; } type Visitor = (json: any, path: string, ast: AnyNode) => any | undefined | null; declare const VALID_COLORSPACES: Set<string>; declare const FONT_WEIGHT_VALUES: Set<string>; declare const STROKE_STYLE_VALUES: Set<string>; declare const STROKE_STYLE_LINE_CAP_VALUES: Set<string>; /** Verify an Alias $value is formatted correctly */ declare function validateAliasSyntax($value: ValueNode, _node: AnyNode, { filename, src, logger }: ValidateOptions): void; /** Verify a Border token is valid */ declare function validateBorder($value: ValueNode, node: AnyNode, { filename, src, logger }: ValidateOptions): void; /** Verify a Color token is valid */ declare function validateColor($value: ValueNode, node: AnyNode, { filename, src, logger }: ValidateOptions): void; /** Verify a Cubic Bézier token is valid */ declare function validateCubicBezier($value: ValueNode, _node: AnyNode, { filename, src, logger }: ValidateOptions): void; /** Verify a Dimension token is valid */ declare function validateDimension($value: ValueNode, _node: AnyNode, { filename, src, logger }: ValidateOptions): void; /** Verify a Duration token is valid */ declare function validateDuration($value: ValueNode, _node: AnyNode, { filename, src, logger }: ValidateOptions): void; /** Verify a Font Family token is valid */ declare function validateFontFamily($value: ValueNode, _node: AnyNode, { filename, src, logger }: ValidateOptions): void; /** Verify a Font Weight token is valid */ declare function validateFontWeight($value: ValueNode, _node: AnyNode, { filename, src, logger }: ValidateOptions): void; /** Verify a Gradient token is valid */ declare function validateGradient($value: ValueNode, _node: AnyNode, { filename, src, logger }: ValidateOptions): void; /** Verify a Number token is valid */ declare function validateNumber($value: ValueNode, _node: AnyNode, { filename, src, logger }: ValidateOptions): void; /** Verify a Boolean token is valid */ declare function validateBoolean($value: ValueNode, _node: AnyNode, { filename, src, logger }: ValidateOptions): void; /** Verify a Shadow token’s value is valid */ declare function validateShadowLayer($value: ValueNode, node: AnyNode, { filename, src, logger }: ValidateOptions): void; /** Verify a Stroke Style token is valid. */ declare function validateStrokeStyle($value: ValueNode, node: AnyNode, { filename, src, logger }: ValidateOptions): void; /** Verify a Transition token is valid */ declare function validateTransition($value: ValueNode, node: AnyNode, { filename, src, logger }: ValidateOptions): void; /** * Validate a MemberNode (the entire token object, plus its key in the parent * object) to see if it’s a valid DTCG token or not. Keeping the parent key * really helps in debug messages. */ declare function validateTokenMemberNode(node: MemberNode, { filename, src, logger }: ValidateOptions): void; /** Return any token node with its inherited $type */ declare function getInheritedType(node: MemberNode, { subpath, $typeInheritance }: { subpath: string[]; $typeInheritance?: Record<string, MemberNode>; }): MemberNode | undefined; interface ValidateTokenNodeOptions { subpath: string[]; src: string; filename: URL; config: ConfigInit; logger: Logger; parent: AnyNode | undefined; transform?: Visitors; inheritedTypeNode?: MemberNode; } /** * Validate does a little more than validate; it also converts to TokenNormalized * and sets up the basic data structure. But aliases are unresolved, and we need * a 2nd normalization pass afterward. */ declare function validateTokenNode(node: MemberNode, { config, filename, logger, parent, inheritedTypeNode, src, subpath }: ValidateTokenNodeOptions): TokenNormalized$1 | undefined; //# sourceMappingURL=validate.d.ts.map //#endregion //#region src/parse/alias.d.ts interface ApplyAliasOptions { tokensSet: TokenNormalizedSet$1; filename: URL; src: string; node: ObjectNode; logger: Logger; } type PreAliased<T extends TokenNormalized$1> = { $value: T['$value'] | string; mode: Record<string, T['mode'][string] & { $value: T['$value'] | string; }>; }; /** * Resolve aliases and update the token nodes. * * Data structures are in an awkward in-between phase, where they have * placeholders for data but we still need to resolve everything. As such, * TypeScript will raise errors expecting the final shape. * * This is also a bit tricky because different token types alias slightly * differently. For example, color tokens and other “primitive” tokens behave * as-expected. But composite tokens like Typography, Gradient, Border, etc. can * either fully- or partially-alias their values. Then we add modes to the mix, * and we have to do the work all over again for each mode declared. * * All that to say, there are a generous amount of TypeScript overrides here rather * than try to codify indeterminate shapes. */ //#endregion //#region src/parse/json.d.ts interface JSONVisitor { enter?: (node: AnyNode, parent: AnyNode | undefined, path: string[]) => void; exit?: (node: AnyNode, parent: AnyNode | undefined, path: string[]) => void; } declare const CHILD_KEYS: { Document: readonly ["body"]; Object: readonly ["members"]; Member: readonly ["name", "value"]; Element: readonly ["value"]; Array: readonly ["elements"]; String: readonly []; Number: readonly []; Boolean: readonly []; Null: readonly []; Identifier: readonly []; NaN: readonly []; Infinity: readonly []; }; /** Determines if a given value is an AST node. */ declare function isNode(value: unknown): boolean; type ValueNodeWithIndex = ValueNode & { index: number; }; /** Get ObjectNode members as object */ declare function getObjMembers(node: ObjectNode): Record<string | number, ValueNodeWithIndex>; /** Inject members to ObjectNode */ declare function injectObjMembers(node: ObjectNode, members?: MemberNode[]): void; /** Replace an ObjectNode’s contents outright with another */ declare function replaceObjMembers(a: ObjectNode, b: DocumentNode | ObjectNode): void; /** * Variation of Momoa’s traverse(), which keeps track of global path. * Allows mutation of AST (along with any consequences) */ declare function traverse(root: AnyNode, visitor: JSONVisitor): void; /** Determine if an input is likely a JSON string */ declare function maybeRawJSON(input: string): boolean; /** Find Momoa node by traversing paths */ declare function findNode<T = AnyNode>(node: AnyNode, path: string[]): T | undefined; interface ToMomoaOptions { filename?: URL; continueOnError?: boolean; logger: Logger; yamlToMomoa?: typeof ytm; } declare function toMomoa(input: string | Record<string, any>, { continueOnError, filename, logger, yamlToMomoa: ytm }: ToMomoaOptions): InputSource; /** Momoa, just with default options pre-set */ declare function parseJSON(input: string | Record<string, any>, options?: ParseOptions$1): any; //# sourceMappingURL=json.d.ts.map //#endregion //#region src/parse/index.d.ts interface ParseOptions { logger?: Logger; config: ConfigInit; /** * 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?: Visitors; /** (internal cache; do not use) */ _sources?: Record<string, InputSource>; } interface ParseResult { tokens: Record<string, TokenNormalized$1>; sources: InputSource[]; } /** Parse */ declare function parse(_input: Omit<InputSource, 'document'> | Omit<InputSource, 'document'>[], { logger, skipLint, config, continueOnError, yamlToMomoa, transform, _sources }?: ParseOptions): Promise<ParseResult>; //# sourceMappingURL=index.d.ts.map //#endregion export { AliasToken, AliasValue, ApplyAliasOptions, BooleanToken, BooleanTokenNormalized, BooleanValue, BorderToken, BorderTokenNormalized, BorderValue, BorderValueNormalized, BuildEndHookOptions, BuildHookOptions, BuildRunnerOptions, BuildRunnerResult, CHILD_KEYS, ColorSpace, ColorToken, ColorTokenNormalized, ColorValue, ColorValueNormalized, Config, ConfigInit, ConfigOptions, CubicBezierToken, CubicBezierTokenNormalized, CubicBezierValue, CustomTransformOptions, DebugEntry, DimensionToken, DimensionTokenNormalized, DimensionValue, DurationToken, DurationTokenNormalized, DurationValue, FONT_WEIGHT_MAP, FONT_WEIGHT_VALUES, FontFamilyToken, FontFamilyTokenNormalized, FontFamilyValue, FontFamilyValueNormalized, FontWeightToken, FontWeightTokenNormalized, FontWeightValue, FontWeightValueNormalized, GradientStop, GradientStopNormalized, GradientToken, GradientTokenNormalized, GradientValue, GradientValueNormalized, Group, GroupCore, GroupOrToken, InputSource, JSONVisitor, LOG_ORDER, LinkToken, LinkTokenNormalized, LinkValue, LintNotice, LintReportDescriptor, LintRule, LintRuleContext, LintRuleLonghand, LintRuleMetaData, LintRuleMetaDataDocs, LintRuleNormalized, LintRuleSeverity, LintRuleShorthand, LintRunnerOptions, LogEntry, LogGroup, LogLevel, LogSeverity, Logger, MULTI_VALUE, ModeMap, NumberToken, NumberTokenNormalized, NumberValue, OutputFile, OutputFileExpanded, ParseOptions, ParseResult, Plugin, PreAliased, SINGLE_VALUE, STROKE_STYLE_LINE_CAP_VALUES, STROKE_STYLE_VALUES, ShadowToken, ShadowTokenNormalized, ShadowValue, ShadowValueNormalized, StringToken, StringTokenNormalized, StringValue, StrokeStyleToken, StrokeStyleTokenNormalized, StrokeStyleValue, StrokeStyleValueExpanded, ToMomoaOptions, Token, TokenCore, TokenMode, TokenNormalized, TokenNormalizedCore, TokenNormalizedSet, TokenTransformed, TokenTransformedMultiValue, TokenTransformedSingleValue, TokensJSONError, TokensSet, TransformHookOptions, TransformParams, TransitionToken, TransitionTokenNormalized, TransitionValue, TransitionValueNormalized, TypographyToken, TypographyTokenNormalized, TypographyValue, TypographyValueNormalized, VALID_COLORSPACES, ValidateOptions, ValidateTokenNodeOptions, ValueNodeWithIndex, Visitor, Visitors, build, defineConfig, findNode, formatMessage, getInheritedType, getObjMembers, injectObjMembers, isNode, lintRunner, maybeRawJSON, mergeConfigs, normalizeValue as normalize, parse, parseJSON, replaceObjMembers, toMomoa, traverse, validateAliasSyntax, validateBoolean, validateBorder, validateColor, validateCubicBezier, validateDimension, validateDuration, validateFontFamily, validateFontWeight, validateGradient, validateNumber, validateShadowLayer, validateStrokeStyle, validateTokenMemberNode, validateTokenNode, validateTransition }; //# sourceMappingURL=index.d.ts.map