@terrazzo/parser 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/parse/index.js ADDED
@@ -0,0 +1,372 @@
1
+ import { evaluate, parse as parseJSON } from '@humanwhocodes/momoa';
2
+ import { isAlias, parseAlias, splitID } from '@terrazzo/token-tools';
3
+ import lintRunner from '../lint/index.js';
4
+ import Logger from '../logger.js';
5
+ import normalize from './normalize.js';
6
+ import parseYAML from './yaml.js';
7
+ import validate from './validate.js';
8
+ import { getObjMembers, injectObjMembers, traverse } from './json.js';
9
+
10
+ export * from './validate.js';
11
+
12
+ /**
13
+ * @typedef {import("@humanwhocodes/momoa").DocumentNode} DocumentNode
14
+ * @typedef {import("../config.js").Plugin} Plugin
15
+ * @typedef {import("../types.js").TokenNormalized} TokenNormalized
16
+ * @typedef {object} ParseOptions
17
+ * @typedef {Logger} ParseOptions.logger
18
+ * @typedef {boolean=false} ParseOptions.skipLint
19
+ * @typedef {Plugin[]} ParseOptions.plugins
20
+ * @typedef {object} ParseResult
21
+ * @typedef {Record<string, TokenNormalized} ParseResult.tokens
22
+ * @typedef {DocumentNode} ParseResult.ast
23
+ */
24
+
25
+ /**
26
+ * Parse
27
+ * @param {string | object} input
28
+ * @param {ParseOptions} options
29
+ * @return {Promise<ParseResult>}
30
+ */
31
+ export default async function parse(input, { logger = new Logger(), skipLint = false, config } = {}) {
32
+ const { plugins } = config;
33
+
34
+ const totalStart = performance.now();
35
+
36
+ // 1. Build AST
37
+ const startParsing = performance.now();
38
+ logger.debug({ group: 'parser', task: 'parse', message: 'Start tokens parsing' });
39
+ let ast;
40
+ if (typeof input === 'string' && !maybeJSONString(input)) {
41
+ ast = parseYAML(input, { logger }); // if string, but not JSON, attempt YAML
42
+ } else {
43
+ ast = parseJSON(typeof input === 'string' ? input : JSON.stringify(input, undefined, 2), {
44
+ mode: 'jsonc',
45
+ }); // everything else: assert it’s JSON-serializable
46
+ }
47
+ logger.debug({
48
+ group: 'parser',
49
+ task: 'parse',
50
+ message: 'Finish tokens parsing',
51
+ timing: performance.now() - startParsing,
52
+ });
53
+
54
+ const tokens = {};
55
+
56
+ // 2. Walk AST once to validate tokens
57
+ const startValidation = performance.now();
58
+ logger.debug({ group: 'parser', task: 'validate', message: 'Start tokens validation' });
59
+ let last$Type;
60
+ let last$TypePath = '';
61
+ traverse(ast, {
62
+ enter(node, parent, path) {
63
+ // reset last$Type if not in a direct ancestor tree
64
+ if (!last$TypePath || !path.join('.').startsWith(last$TypePath)) {
65
+ last$Type = undefined;
66
+ }
67
+
68
+ if (node.type === 'Member' && node.value.type === 'Object' && node.value.members) {
69
+ const members = getObjMembers(node.value);
70
+
71
+ // keep track of closest-scoped $type
72
+ // note: this is only reliable in a synchronous, single-pass traversal;
73
+ // otherwise we’d have to do something more complicated
74
+ if (members.$type && members.$type.type === 'String' && !members.$value) {
75
+ last$Type = node.value.members.find((m) => m.name.value === '$type');
76
+ last$TypePath = path.join('.');
77
+ }
78
+
79
+ if (members.$value) {
80
+ const extensions = members.$extensions ? getObjMembers(members.$extensions) : undefined;
81
+ const sourceNode = structuredClone(node);
82
+ if (last$Type && !members.$type) {
83
+ sourceNode.value = injectObjMembers(sourceNode.value, [last$Type]);
84
+ }
85
+ validate(sourceNode, { ast, logger });
86
+
87
+ const id = path.join('.');
88
+ const group = { id: splitID(id).group, tokens: [] };
89
+ if (last$Type) {
90
+ group.$type = last$Type.value.value;
91
+ }
92
+ // note: this will also include sibling tokens, so be selective about only accessing group-specific properties
93
+ const groupMembers = getObjMembers(parent);
94
+ if (groupMembers.$description) {
95
+ group.$description = evaluate(groupMembers.$description);
96
+ }
97
+ if (groupMembers.$extensions) {
98
+ group.$extensions = evaluate(groupMembers.$extensions);
99
+ }
100
+ const token = {
101
+ $type: members.$type?.value ?? last$Type?.value.value,
102
+ $value: evaluate(members.$value),
103
+ id,
104
+ mode: {},
105
+ originalValue: evaluate(node.value),
106
+ group,
107
+ sourceNode: sourceNode.value,
108
+ };
109
+ if (members.$description?.value) {
110
+ token.$description = members.$description.value;
111
+ }
112
+
113
+ // handle modes
114
+ // note that circular refs are avoided here, such as not duplicating `modes`
115
+ const modeValues = extensions?.mode ? getObjMembers(extensions.mode) : {};
116
+ for (const mode of ['.', ...Object.keys(modeValues)]) {
117
+ token.mode[mode] = {
118
+ id: token.id,
119
+ $type: token.$type,
120
+ $value: mode === '.' ? token.$value : evaluate(modeValues[mode]),
121
+ sourceNode: mode === '.' ? structuredClone(token.sourceNode) : modeValues[mode],
122
+ };
123
+ if (token.$description) {
124
+ token.mode[mode].$description = token.$description;
125
+ }
126
+ }
127
+
128
+ tokens[id] = token;
129
+ } else if (members.value) {
130
+ logger.warn({ message: `Group ${id} has "value". Did you mean "$value"?`, node, ast });
131
+ }
132
+ }
133
+ },
134
+ });
135
+ logger.debug({
136
+ group: 'parser',
137
+ task: 'validate',
138
+ message: 'Finish tokens validation',
139
+ timing: performance.now() - startValidation,
140
+ });
141
+
142
+ // 3. Execute lint runner with loaded plugins
143
+ if (!skipLint && plugins?.length) {
144
+ const lintStart = performance.now();
145
+ logger.debug({
146
+ group: 'parser',
147
+ task: 'validate',
148
+ message: 'Start token linting',
149
+ });
150
+ await lintRunner({ ast, config, logger });
151
+ logger.debug({
152
+ group: 'parser',
153
+ task: 'validate',
154
+ message: 'Finish token linting',
155
+ timing: performance.now() - lintStart,
156
+ });
157
+ }
158
+
159
+ // 4. normalize values
160
+ const normalizeStart = performance.now();
161
+ logger.debug({
162
+ group: 'parser',
163
+ task: 'normalize',
164
+ message: 'Start token normalization',
165
+ });
166
+ for (const id in tokens) {
167
+ if (!Object.hasOwn(tokens, id)) {
168
+ continue;
169
+ }
170
+ try {
171
+ tokens[id].$value = normalize(tokens[id]);
172
+ } catch (err) {
173
+ logger.error({ message: err.message, ast, node: tokens[id].sourceNode });
174
+ }
175
+ for (const mode in tokens[id].mode) {
176
+ if (mode === '.') {
177
+ continue;
178
+ }
179
+ try {
180
+ tokens[id].mode[mode].$value = normalize(tokens[id].mode[mode]);
181
+ } catch (err) {
182
+ logger.error({ message: err.message, ast, node: tokens[id].mode[mode].sourceNode });
183
+ }
184
+ }
185
+ }
186
+ logger.debug({
187
+ group: 'parser',
188
+ task: 'normalize',
189
+ message: 'Finish token normalization',
190
+ timing: performance.now() - normalizeStart,
191
+ });
192
+
193
+ // 5. Resolve aliases and populate groups
194
+ for (const id in tokens) {
195
+ if (!Object.hasOwn(tokens, id)) {
196
+ continue;
197
+ }
198
+ const token = tokens[id];
199
+ applyAliases(token, { tokens, ast, node: token.sourceNode, logger });
200
+ token.mode['.'].$value = token.$value;
201
+ if (token.aliasOf) {
202
+ token.mode['.'].aliasOf = token.aliasOf;
203
+ }
204
+ if (token.partialAliasOf) {
205
+ token.mode['.'].partialAliasOf = token.partialAliasOf;
206
+ }
207
+ const { group: parentGroup } = splitID(id);
208
+ for (const siblingID in tokens) {
209
+ const { group: siblingGroup } = splitID(siblingID);
210
+ if (siblingGroup?.startsWith(parentGroup)) {
211
+ token.group.tokens.push(siblingID);
212
+ }
213
+ }
214
+ }
215
+
216
+ // 6. resolve mode aliases
217
+ const modesStart = performance.now();
218
+ logger.debug({
219
+ group: 'parser',
220
+ task: 'modes',
221
+ message: 'Start mode resolution',
222
+ });
223
+ for (const id in tokens) {
224
+ if (!Object.hasOwn(tokens, id)) {
225
+ continue;
226
+ }
227
+ for (const mode in tokens[id].mode) {
228
+ if (mode === '.') {
229
+ continue; // skip shadow of root value
230
+ }
231
+ applyAliases(tokens[id].mode[mode], { tokens, ast, node: tokens[id].mode[mode].sourceNode, logger });
232
+ }
233
+ }
234
+ logger.debug({
235
+ group: 'parser',
236
+ task: 'modes',
237
+ message: 'Finish token modes',
238
+ timing: performance.now() - modesStart,
239
+ });
240
+
241
+ logger.debug({
242
+ group: 'parser',
243
+ task: 'core',
244
+ message: 'Finish all parser tasks',
245
+ timing: performance.now() - totalStart,
246
+ });
247
+
248
+ return {
249
+ tokens,
250
+ ast,
251
+ };
252
+ }
253
+
254
+ /**
255
+ * Determine if an input is likely a JSON string
256
+ * @param {string} input
257
+ * @return {boolean}
258
+ */
259
+ export function maybeJSONString(input) {
260
+ return typeof input === 'string' && input.trim().startsWith('{');
261
+ }
262
+
263
+ /**
264
+ * Resolve alias
265
+ * @param {string} alias
266
+ * @param {Object} options
267
+ * @param {Record<string, TokenNormalized>} options.tokens
268
+ * @param {Logger} options.logger
269
+ * @param {AnyNode | undefined} options.node
270
+ * @param {DocumentNode | undefined} options.ast
271
+ * @param {string[]=[]} options.scanned
272
+ * @param {string}
273
+ */
274
+ export function resolveAlias(alias, { tokens, logger, ast, node, scanned = [] }) {
275
+ const { id } = parseAlias(alias);
276
+ if (!tokens[id]) {
277
+ logger.error({ message: `Alias "${alias}" not found`, ast, node });
278
+ }
279
+ if (scanned.includes(id)) {
280
+ logger.error({ message: `Circular alias detected from "${alias}"`, ast, node });
281
+ }
282
+ const token = tokens[id];
283
+ if (!isAlias(token.$value)) {
284
+ return id;
285
+ }
286
+ return resolveAlias(alias, { tokens, logger, ast, node, scanned: [...scanned, id] });
287
+ }
288
+
289
+ /** Resolve aliases, update values, and mutate `token` to add `aliasOf` / `partialAliasOf` */
290
+ function applyAliases(token, { tokens, logger, ast, node }) {
291
+ // handle simple aliases
292
+ if (isAlias(token.$value)) {
293
+ const aliasOfID = resolveAlias(token.$value, { tokens, logger, node, ast });
294
+ const { mode: aliasMode } = parseAlias(token.$value);
295
+ const aliasOf = tokens[aliasOfID];
296
+ token.aliasOf = aliasOfID;
297
+ token.$value = aliasOf.mode[aliasMode]?.$value || aliasOf.$value;
298
+ if (token.$type && token.$type !== aliasOf.$type) {
299
+ logger.warn({
300
+ message: `Token ${token.id} has $type "${token.$type}" but aliased ${aliasOfID} of $type "${aliasOf.$type}"`,
301
+ node,
302
+ ast,
303
+ });
304
+ token.$type = aliasOf.$type;
305
+ }
306
+ }
307
+ // handle aliases within array values (e.g. cubicBezier, gradient)
308
+ else if (Array.isArray(token.$value)) {
309
+ // some arrays are primitives, some are objects. handle both
310
+ for (let i = 0; i < token.$value.length; i++) {
311
+ if (isAlias(token.$value[i])) {
312
+ if (!token.partialAliasOf) {
313
+ token.partialAliasOf = [];
314
+ }
315
+ const aliasOfID = resolveAlias(token.$value[i], { tokens, logger, node, ast });
316
+ const { mode: aliasMode } = parseAlias(token.$value[i]);
317
+ token.partialAliasOf[i] = aliasOfID;
318
+ token.$value[i] = tokens[aliasOfID].mode[aliasMode]?.$value || tokens[aliasOfID].$value;
319
+ } else if (typeof token.$value[i] === 'object') {
320
+ for (const property in token.$value[i]) {
321
+ if (isAlias(token.$value[i][property])) {
322
+ if (!token.partialAliasOf) {
323
+ token.partialAliasOf = [];
324
+ }
325
+ if (!token.partialAliasOf[i]) {
326
+ token.partialAliasOf[i] = {};
327
+ }
328
+ const aliasOfID = resolveAlias(token.$value[i][property], { tokens, logger, node, ast });
329
+ const { mode: aliasMode } = parseAlias(token.$value[i][property]);
330
+ token.$value[i][property] = tokens[aliasOfID].mode[aliasMode]?.$value || tokens[aliasOfID].$value;
331
+ token.partialAliasOf[i][property] = aliasOfID;
332
+ }
333
+ }
334
+ }
335
+ }
336
+ }
337
+ // handle aliases within object (composite) values (e.g. border, typography, transition)
338
+ else if (typeof token.$value === 'object') {
339
+ for (const property in token.$value) {
340
+ if (!Object.hasOwn(token.$value, property)) {
341
+ continue;
342
+ }
343
+
344
+ if (isAlias(token.$value[property])) {
345
+ if (!token.partialAliasOf) {
346
+ token.partialAliasOf = {};
347
+ }
348
+ const aliasOfID = resolveAlias(token.$value[property], { tokens, logger, node, ast });
349
+ const { mode: aliasMode } = parseAlias(token.$value[property]);
350
+ token.partialAliasOf[property] = aliasOfID;
351
+ token.$value[property] = tokens[aliasOfID].mode[aliasMode]?.$value || tokens[aliasOfID].$value;
352
+ }
353
+ // strokeStyle has an array within an object
354
+ else if (Array.isArray(token.$value[property])) {
355
+ for (let i = 0; i < token.$value[property].length; i++) {
356
+ if (isAlias(token.$value[property][i])) {
357
+ const aliasOfID = resolveAlias(token.$value[property][i], { tokens, logger, node, ast });
358
+ if (!token.partialAliasOf) {
359
+ token.partialAliasOf = {};
360
+ }
361
+ if (!token.partialAliasOf[property]) {
362
+ token.partialAliasOf[property] = [];
363
+ }
364
+ const { mode: aliasMode } = parseAlias(token.$value[property][i]);
365
+ token.partialAliasOf[property][i] = aliasOfID;
366
+ token.$value[property][i] = tokens[aliasOfID].mode[aliasMode]?.$value || tokens[aliasOfID].$value;
367
+ }
368
+ }
369
+ }
370
+ }
371
+ }
372
+ }
@@ -0,0 +1,30 @@
1
+ import type { AnyNode, MemberNode, ObjectNode, ValueNode } from '@humanwhocodes/momoa';
2
+
3
+ export interface Visitor {
4
+ enter?: (node: AnyNode, parent: AnyNode | undefined, path: string[]) => void;
5
+ exit?: (node: AnyNode, parent: AnyNode | undefined, path: string[]) => void;
6
+ }
7
+
8
+ declare const CHILD_KEYS: {
9
+ Document: readonly ['body'];
10
+ Object: readonly ['members'];
11
+ Member: readonly ['name', 'value'];
12
+ Element: readonly ['value'];
13
+ Array: readonly ['elements'];
14
+ String: readonly [];
15
+ Number: readonly [];
16
+ Boolean: readonly [];
17
+ Null: readonly [];
18
+ };
19
+
20
+ /** Determines if a given value is an AST node. */
21
+ export function isNode(value: unknown): boolean;
22
+
23
+ /** Get ObjectNode members as object */
24
+ export function getObjMembers(node: ObjectNode): Record<string | number, ValueNode | undefined>;
25
+
26
+ /** Inject members to ObjectNode and return a clone */
27
+ export function injectObjMembers(node: ObjectNode, members: MemberNode[]): ObjectNode;
28
+
29
+ /** Variation of Momoa’s traverse(), which keeps track of global path */
30
+ export function traverse(root: AnyNode, visitor: Visitor): void;
package/parse/json.js ADDED
@@ -0,0 +1,94 @@
1
+ /**
2
+ * @typedef {import("@humanwhocodes/momoa").AnyNode} AnyNode
3
+ * @typedef {import("@humanwhocodes/momoa").ObjectNode} ObjectNode
4
+ * @typedef {import("@humanwhocodes/momoa").ValueNode} ValueNode
5
+ */
6
+
7
+ export const CHILD_KEYS = {
8
+ Document: ['body'],
9
+ Object: ['members'],
10
+ Member: ['name', 'value'],
11
+ Element: ['value'],
12
+ Array: ['elements'],
13
+ String: [],
14
+ Number: [],
15
+ Boolean: [],
16
+ Null: [],
17
+ };
18
+
19
+ /** Determines if a given value is an AST node. */
20
+ export function isNode(value) {
21
+ return value && typeof value === 'object' && 'type' in value && typeof value.type === 'string';
22
+ }
23
+
24
+ /**
25
+ * Get ObjectNode members as object
26
+ * @param {ObjectNode} node
27
+ * @return {Record<string, ValueNode}
28
+ */
29
+ export function getObjMembers(node) {
30
+ const members = {};
31
+ if (node.type !== 'Object') {
32
+ return members;
33
+ }
34
+ for (const m of node.members) {
35
+ if (m.name.type !== 'String' && m.name.type !== 'Number') {
36
+ continue;
37
+ }
38
+ members[m.name.value] = m.value;
39
+ }
40
+ return members;
41
+ }
42
+
43
+ /**
44
+ * Inject members to ObjectNode and return a clone
45
+ * @param {ObjectNode} node
46
+ * @param {MemberNode[]} members
47
+ * @return {ObjectNode}
48
+ */
49
+ export function injectObjMembers(node, members = []) {
50
+ if (node.type !== 'Object') {
51
+ return node;
52
+ }
53
+ const newNode = structuredClone(node);
54
+ newNode.members.push(...members);
55
+ return newNode;
56
+ }
57
+
58
+ /**
59
+ * Variation of Momoa’s traverse(), which keeps track of global path
60
+ */
61
+ export function traverse(root, visitor) {
62
+ /**
63
+ * Recursively visits a node.
64
+ * @param {AnyNode} node The node to visit.
65
+ * @param {AnyNode} [parent] The parent of the node to visit.
66
+ * @return {void}
67
+ */
68
+ function visitNode(node, parent, path = []) {
69
+ const nextPath = [...path];
70
+ if (node.type === 'Member') {
71
+ nextPath.push(node.name.value);
72
+ }
73
+
74
+ visitor.enter?.(node, parent, nextPath);
75
+
76
+ for (const key of CHILD_KEYS[node.type] ?? []) {
77
+ const value = node[key];
78
+
79
+ if (value && typeof value === 'object') {
80
+ if (Array.isArray(value)) {
81
+ for (let i = 0; i < value.length; i++) {
82
+ visitNode(value[i], node, key === 'elements' ? [...nextPath, String(i)] : nextPath);
83
+ }
84
+ } else if (isNode(value)) {
85
+ visitNode(value, node, nextPath);
86
+ }
87
+ }
88
+ }
89
+
90
+ visitor.exit?.(node, parent, nextPath);
91
+ }
92
+
93
+ visitNode(root, undefined, []);
94
+ }
@@ -0,0 +1,3 @@
1
+ import type { TokenNormalized } from '../types.js';
2
+
3
+ export default function normalize<T extends TokenNormalized>(token: T): T['$value'];
@@ -0,0 +1,114 @@
1
+ import { isAlias, parseColor } from '@terrazzo/token-tools';
2
+
3
+ export const FONT_WEIGHT_MAP = {
4
+ thin: 100,
5
+ hairline: 100,
6
+ 'extra-light': 200,
7
+ 'ultra-light': 200,
8
+ light: 300,
9
+ normal: 400,
10
+ regular: 400,
11
+ book: 400,
12
+ medium: 500,
13
+ 'semi-bold': 600,
14
+ 'demi-bold': 600,
15
+ bold: 700,
16
+ 'extra-bold': 800,
17
+ 'ultra-bold': 800,
18
+ black: 900,
19
+ heavy: 900,
20
+ 'extra-black': 950,
21
+ 'ultra-black': 950,
22
+ };
23
+
24
+ export default function normalizeValue(token) {
25
+ if (isAlias(token.$value)) {
26
+ return token.$value;
27
+ }
28
+ switch (token.$type) {
29
+ case 'boolean': {
30
+ return !!token.$value;
31
+ }
32
+ case 'border': {
33
+ return {
34
+ color: normalizeValue({ $type: 'color', $value: token.$value.color ?? '#000000' }),
35
+ style: normalizeValue({ $type: 'strokeStyle', $value: token.$value.style ?? 'solid' }),
36
+ width: normalizeValue({ $type: 'dimension', $value: token.$value.width }),
37
+ };
38
+ }
39
+ case 'color': {
40
+ return typeof token.$value === 'string' ? parseColor(token.$value) : token.$value;
41
+ }
42
+ case 'cubicBezier': {
43
+ return token.$value.map((value) =>
44
+ typeof value === 'number' ? normalizeValue({ $type: 'number', $value: value }) : value,
45
+ );
46
+ }
47
+ case 'dimension': {
48
+ if (token.$value === 0) {
49
+ return 0;
50
+ }
51
+ return typeof token.$value === 'number' ? `${token.$value}px` : token.$value;
52
+ }
53
+ case 'duration': {
54
+ if (token.$value === 0) {
55
+ return 0;
56
+ }
57
+ return typeof token.$value === 'number' ? `${token.$value}ms` : token.$value;
58
+ }
59
+ case 'fontFamily': {
60
+ return Array.isArray(token.$value) ? token.$value : [token.$value];
61
+ }
62
+ case 'fontWeight': {
63
+ if (typeof token.$value === 'string' && FONT_WEIGHT_MAP[token.$value]) {
64
+ return FONT_WEIGHT_MAP[token.$value];
65
+ }
66
+ return Number.parseInt(token.$value);
67
+ }
68
+ case 'gradient': {
69
+ const output = [];
70
+ for (let i = 0; i < token.$value.length; i++) {
71
+ const stop = { ...token.$value[i] };
72
+ stop.color = normalizeValue({ $type: 'color', $value: stop.color });
73
+ if (typeof stop.position !== 'number') {
74
+ stop.position = i / (token.$value.length - 1);
75
+ }
76
+ output.push(stop);
77
+ }
78
+ return output;
79
+ }
80
+ case 'number': {
81
+ return typeof token.$value === 'number' ? token.$value : Number.parseFloat(token.$value);
82
+ }
83
+ case 'shadow': {
84
+ return Array.isArray(token.$value) ? token.$value : [token.$value];
85
+ }
86
+ case 'strokeStyle': {
87
+ return token.$value;
88
+ }
89
+ case 'string': {
90
+ return String(token.$value);
91
+ }
92
+ case 'transition': {
93
+ return {
94
+ duration: normalizeValue({ $type: 'duration', $value: token.$value.duration ?? 0 }),
95
+ delay: normalizeValue({ $type: 'duration', $value: token.$value.delay ?? 0 }),
96
+ timingFunction: normalizeValue({ $type: 'cubicBezier', $value: token.$value.timingFunction }),
97
+ };
98
+ }
99
+ case 'typography': {
100
+ const output = {};
101
+ for (const k in token.$value) {
102
+ if (k === 'fontSize') {
103
+ output[k] = normalizeValue({ $type: 'dimension', $value: token.$value[k] });
104
+ } else {
105
+ output[k] = token.$value[k];
106
+ }
107
+ }
108
+ return output;
109
+ }
110
+ default: {
111
+ return token.$value;
112
+ }
113
+ }
114
+ }
@@ -0,0 +1,42 @@
1
+ import type { AnyNode, DocumentNode, MemberNode, ValueNode } from '@humanwhocodes/momoa';
2
+ import type Logger from '../logger.js';
3
+
4
+ declare const FONT_WEIGHT_VALUES: Set<string>;
5
+
6
+ declare const STROKE_STYLE_VALUES: Set<string>;
7
+ declare const STROKE_STYLE_LINE_CAP_VALUES: Set<string>;
8
+
9
+ export interface ValidateOptions {
10
+ ast: DocumentNode;
11
+ logger: Logger;
12
+ }
13
+
14
+ export function validateAlias($value: ValueNode, node: AnyNode, options: ValidateOptions): void;
15
+
16
+ export function validateBorder($value: ValueNode, node: AnyNode, options: ValidateOptions): void;
17
+
18
+ export function validateColor($value: ValueNode, node: AnyNode, options: ValidateOptions): void;
19
+
20
+ export function validateCubicBézier($value: ValueNode, node: AnyNode, options: ValidateOptions): void;
21
+
22
+ export function validateDimension($value: ValueNode, node: AnyNode, options: ValidateOptions): void;
23
+
24
+ export function validateDuration($value: ValueNode, node: AnyNode, options: ValidateOptions): void;
25
+
26
+ export function validateFontFamily($value: ValueNode, node: AnyNode, options: ValidateOptions): void;
27
+
28
+ export function validateFontWeight($value: ValueNode, node: AnyNode, options: ValidateOptions): void;
29
+
30
+ export function validateGradient($value: ValueNode, node: AnyNode, options: ValidateOptions): void;
31
+
32
+ export function validateNumber($value: ValueNode, node: AnyNode, options: ValidateOptions): void;
33
+
34
+ export function validateShadowLayer($value: ValueNode, node: AnyNode, options: ValidateOptions): void;
35
+
36
+ export function validateStrokeStyle($value: ValueNode, node: AnyNode, options: ValidateOptions): void;
37
+
38
+ export function validateTransition($value: ValueNode, node: AnyNode, options: ValidateOptions): void;
39
+
40
+ export function validateTypography($value: ValueNode, node: AnyNode, options: ValidateOptions): void;
41
+
42
+ export default function validate(node: MemberNode, options: ValidateOptions): void;