@terrazzo/parser 2.0.0-alpha.7 → 2.0.0-beta.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/dist/index.d.ts +39 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +578 -512
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/build/index.ts +0 -209
- package/src/config.ts +0 -304
- package/src/index.ts +0 -95
- package/src/lib/code-frame.ts +0 -177
- package/src/lib/momoa.ts +0 -10
- package/src/lib/resolver-utils.ts +0 -35
- package/src/lint/index.ts +0 -142
- package/src/lint/plugin-core/index.ts +0 -103
- package/src/lint/plugin-core/lib/docs.ts +0 -3
- package/src/lint/plugin-core/rules/a11y-min-contrast.ts +0 -91
- package/src/lint/plugin-core/rules/a11y-min-font-size.ts +0 -66
- package/src/lint/plugin-core/rules/colorspace.ts +0 -108
- package/src/lint/plugin-core/rules/consistent-naming.ts +0 -65
- package/src/lint/plugin-core/rules/descriptions.ts +0 -43
- package/src/lint/plugin-core/rules/duplicate-values.ts +0 -85
- package/src/lint/plugin-core/rules/max-gamut.ts +0 -144
- package/src/lint/plugin-core/rules/required-children.ts +0 -106
- package/src/lint/plugin-core/rules/required-modes.ts +0 -75
- package/src/lint/plugin-core/rules/required-type.ts +0 -28
- package/src/lint/plugin-core/rules/required-typography-properties.ts +0 -65
- package/src/lint/plugin-core/rules/valid-boolean.ts +0 -41
- package/src/lint/plugin-core/rules/valid-border.ts +0 -57
- package/src/lint/plugin-core/rules/valid-color.ts +0 -265
- package/src/lint/plugin-core/rules/valid-cubic-bezier.ts +0 -83
- package/src/lint/plugin-core/rules/valid-dimension.ts +0 -199
- package/src/lint/plugin-core/rules/valid-duration.ts +0 -123
- package/src/lint/plugin-core/rules/valid-font-family.ts +0 -68
- package/src/lint/plugin-core/rules/valid-font-weight.ts +0 -89
- package/src/lint/plugin-core/rules/valid-gradient.ts +0 -79
- package/src/lint/plugin-core/rules/valid-link.ts +0 -41
- package/src/lint/plugin-core/rules/valid-number.ts +0 -63
- package/src/lint/plugin-core/rules/valid-shadow.ts +0 -67
- package/src/lint/plugin-core/rules/valid-string.ts +0 -41
- package/src/lint/plugin-core/rules/valid-stroke-style.ts +0 -104
- package/src/lint/plugin-core/rules/valid-transition.ts +0 -61
- package/src/lint/plugin-core/rules/valid-typography.ts +0 -67
- package/src/logger.ts +0 -213
- package/src/parse/index.ts +0 -124
- package/src/parse/load.ts +0 -172
- package/src/parse/normalize.ts +0 -163
- package/src/parse/process.ts +0 -251
- package/src/parse/token.ts +0 -553
- package/src/resolver/create-synthetic-resolver.ts +0 -86
- package/src/resolver/index.ts +0 -7
- package/src/resolver/load.ts +0 -215
- package/src/resolver/normalize.ts +0 -133
- package/src/resolver/validate.ts +0 -375
- package/src/types.ts +0 -468
package/src/resolver/load.ts
DELETED
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
import type * as momoa from '@humanwhocodes/momoa';
|
|
2
|
-
import { type InputSource, type InputSourceWithDocument, maybeRawJSON } from '@terrazzo/json-schema-tools';
|
|
3
|
-
import type { TokenNormalizedSet } from '@terrazzo/token-tools';
|
|
4
|
-
import { merge } from 'merge-anything';
|
|
5
|
-
import type yamlToMomoa from 'yaml-to-momoa';
|
|
6
|
-
import { toMomoa } from '../lib/momoa.js';
|
|
7
|
-
import { makeInputKey } from '../lib/resolver-utils.js';
|
|
8
|
-
import type Logger from '../logger.js';
|
|
9
|
-
import { processTokens } from '../parse/process.js';
|
|
10
|
-
import type { ConfigInit, Resolver, ResolverSourceNormalized } from '../types.js';
|
|
11
|
-
import { normalizeResolver } from './normalize.js';
|
|
12
|
-
import { isLikelyResolver, validateResolver } from './validate.js';
|
|
13
|
-
|
|
14
|
-
export interface LoadResolverOptions {
|
|
15
|
-
config: ConfigInit;
|
|
16
|
-
logger: Logger;
|
|
17
|
-
req: (url: URL, origin: URL) => Promise<string>;
|
|
18
|
-
yamlToMomoa?: typeof yamlToMomoa;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/** Quick-parse input sources and find a resolver */
|
|
22
|
-
export async function loadResolver(
|
|
23
|
-
inputs: InputSource[],
|
|
24
|
-
{ config, logger, req, yamlToMomoa }: LoadResolverOptions,
|
|
25
|
-
): Promise<{ resolver: Resolver | undefined; tokens: TokenNormalizedSet; sources: InputSourceWithDocument[] }> {
|
|
26
|
-
let resolverDoc: momoa.DocumentNode | undefined;
|
|
27
|
-
let tokens: TokenNormalizedSet = {};
|
|
28
|
-
const entry = {
|
|
29
|
-
group: 'parser',
|
|
30
|
-
label: 'init',
|
|
31
|
-
} as const;
|
|
32
|
-
|
|
33
|
-
for (const input of inputs) {
|
|
34
|
-
let document: momoa.DocumentNode | undefined;
|
|
35
|
-
if (typeof input.src === 'string') {
|
|
36
|
-
if (maybeRawJSON(input.src)) {
|
|
37
|
-
document = toMomoa(input.src);
|
|
38
|
-
} else if (yamlToMomoa) {
|
|
39
|
-
document = yamlToMomoa(input.src);
|
|
40
|
-
} else {
|
|
41
|
-
logger.error({
|
|
42
|
-
...entry,
|
|
43
|
-
message: `Install yaml-to-momoa package to parse YAML, and pass in as option, e.g.:
|
|
44
|
-
|
|
45
|
-
import { bundle } from '@terrazzo/json-schema-tools';
|
|
46
|
-
import yamlToMomoa from 'yaml-to-momoa';
|
|
47
|
-
|
|
48
|
-
bundle(yamlString, { yamlToMomoa });`,
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
} else if (input.src && typeof input.src === 'object') {
|
|
52
|
-
document = toMomoa(JSON.stringify(input.src, undefined, 2));
|
|
53
|
-
} else {
|
|
54
|
-
logger.error({ ...entry, message: `Could not parse ${input.filename}. Is this valid JSON or YAML?` });
|
|
55
|
-
}
|
|
56
|
-
if (!document || !isLikelyResolver(document)) {
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
if (inputs.length > 1) {
|
|
60
|
-
logger.error({ ...entry, message: `Resolver must be the only input, found ${inputs.length} sources.` });
|
|
61
|
-
}
|
|
62
|
-
resolverDoc = document;
|
|
63
|
-
break;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
let resolver: Resolver | undefined;
|
|
67
|
-
if (resolverDoc) {
|
|
68
|
-
validateResolver(resolverDoc, { logger, src: inputs[0]!.src });
|
|
69
|
-
const normalized = await normalizeResolver(resolverDoc, {
|
|
70
|
-
filename: inputs[0]!.filename!,
|
|
71
|
-
logger,
|
|
72
|
-
req,
|
|
73
|
-
src: inputs[0]!.src,
|
|
74
|
-
yamlToMomoa,
|
|
75
|
-
});
|
|
76
|
-
resolver = createResolver(normalized, { config, logger, sources: [{ ...inputs[0]!, document: resolverDoc }] });
|
|
77
|
-
|
|
78
|
-
// If a resolver is present, load a single permutation to get a base token set.
|
|
79
|
-
const firstInput: Record<string, string> = {};
|
|
80
|
-
for (const m of resolver.source.resolutionOrder) {
|
|
81
|
-
if (m.type !== 'modifier') {
|
|
82
|
-
continue;
|
|
83
|
-
}
|
|
84
|
-
firstInput[m.name] = typeof m.default === 'string' ? m.default : Object.keys(m.contexts)[0]!;
|
|
85
|
-
}
|
|
86
|
-
tokens = resolver.apply(firstInput);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return {
|
|
90
|
-
resolver,
|
|
91
|
-
tokens,
|
|
92
|
-
sources: [{ ...inputs[0]!, document: resolverDoc! }],
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export interface CreateResolverOptions {
|
|
97
|
-
config: ConfigInit;
|
|
98
|
-
logger: Logger;
|
|
99
|
-
sources: InputSourceWithDocument[];
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/** Create an interface to resolve permutations */
|
|
103
|
-
export function createResolver(
|
|
104
|
-
resolverSource: ResolverSourceNormalized,
|
|
105
|
-
{ config, logger, sources }: CreateResolverOptions,
|
|
106
|
-
): Resolver {
|
|
107
|
-
const inputDefaults: Record<string, string> = {};
|
|
108
|
-
const validContexts: Record<string, string[]> = {};
|
|
109
|
-
const allPermutations: Record<string, string>[] = [];
|
|
110
|
-
|
|
111
|
-
const resolverCache: Record<string, any> = {};
|
|
112
|
-
|
|
113
|
-
for (const m of resolverSource.resolutionOrder) {
|
|
114
|
-
if (m.type === 'modifier') {
|
|
115
|
-
if (typeof m.default === 'string') {
|
|
116
|
-
inputDefaults[m.name] = m.default!;
|
|
117
|
-
}
|
|
118
|
-
validContexts[m.name] = Object.keys(m.contexts);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return {
|
|
123
|
-
apply(inputRaw): TokenNormalizedSet {
|
|
124
|
-
let tokensRaw: TokenNormalizedSet = {};
|
|
125
|
-
const input = { ...inputDefaults, ...inputRaw };
|
|
126
|
-
const inputKey = makeInputKey(input);
|
|
127
|
-
|
|
128
|
-
if (resolverCache[inputKey]) {
|
|
129
|
-
return resolverCache[inputKey];
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
for (const item of resolverSource.resolutionOrder) {
|
|
133
|
-
switch (item.type) {
|
|
134
|
-
case 'set': {
|
|
135
|
-
for (const s of item.sources) {
|
|
136
|
-
tokensRaw = merge(tokensRaw, s) as TokenNormalizedSet;
|
|
137
|
-
}
|
|
138
|
-
break;
|
|
139
|
-
}
|
|
140
|
-
case 'modifier': {
|
|
141
|
-
const context = input[item.name]!;
|
|
142
|
-
const sources = item.contexts[context];
|
|
143
|
-
if (!sources) {
|
|
144
|
-
logger.error({
|
|
145
|
-
group: 'parser',
|
|
146
|
-
label: 'resolver',
|
|
147
|
-
message: `Modifier ${item.name} has no context ${JSON.stringify(context)}.`,
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
for (const s of sources ?? []) {
|
|
151
|
-
tokensRaw = merge(tokensRaw, s) as TokenNormalizedSet;
|
|
152
|
-
}
|
|
153
|
-
break;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const src = JSON.stringify(tokensRaw, undefined, 2);
|
|
159
|
-
const rootSource = { filename: resolverSource._source.filename!, document: toMomoa(src), src };
|
|
160
|
-
const tokens = processTokens(rootSource, {
|
|
161
|
-
config,
|
|
162
|
-
logger,
|
|
163
|
-
sourceByFilename: { [resolverSource._source.filename!.href]: rootSource },
|
|
164
|
-
sources,
|
|
165
|
-
});
|
|
166
|
-
resolverCache[inputKey] = tokens;
|
|
167
|
-
return tokens;
|
|
168
|
-
},
|
|
169
|
-
source: resolverSource,
|
|
170
|
-
listPermutations() {
|
|
171
|
-
// only do work on first call, then cache subsequent work. this could be thousands of possible values!
|
|
172
|
-
if (!allPermutations.length) {
|
|
173
|
-
allPermutations.push(...calculatePermutations(Object.entries(validContexts)));
|
|
174
|
-
}
|
|
175
|
-
return allPermutations;
|
|
176
|
-
},
|
|
177
|
-
isValidInput(input: Record<string, string>) {
|
|
178
|
-
if (!input || typeof input !== 'object') {
|
|
179
|
-
logger.error({ group: 'parser', label: 'resolver', message: `Invalid input: ${JSON.stringify(input)}.` });
|
|
180
|
-
}
|
|
181
|
-
if (!Object.keys(input).every((k) => k in validContexts)) {
|
|
182
|
-
return false; // 1. invalid if unknown modifier name
|
|
183
|
-
}
|
|
184
|
-
for (const [name, contexts] of Object.entries(validContexts)) {
|
|
185
|
-
// Note: empty strings are valid! Don’t check for truthiness.
|
|
186
|
-
if (name in input) {
|
|
187
|
-
if (!contexts.includes(input[name]!)) {
|
|
188
|
-
return false; // 2. invalid if unknown context
|
|
189
|
-
}
|
|
190
|
-
} else if (!(name in inputDefaults)) {
|
|
191
|
-
return false; // 3. invalid if omitted, and no default
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
return true;
|
|
195
|
-
},
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/** Calculate all permutations */
|
|
200
|
-
export function calculatePermutations(options: [string, string[]][]) {
|
|
201
|
-
const permutationCount = [1];
|
|
202
|
-
for (const [_name, contexts] of options) {
|
|
203
|
-
permutationCount.push(contexts.length * (permutationCount.at(-1) || 1));
|
|
204
|
-
}
|
|
205
|
-
const permutations: Record<string, string>[] = [];
|
|
206
|
-
for (let i = 0; i < permutationCount.at(-1)!; i++) {
|
|
207
|
-
const input: Record<string, string> = {};
|
|
208
|
-
for (let j = 0; j < options.length; j++) {
|
|
209
|
-
const [name, contexts] = options[j]!;
|
|
210
|
-
input[name] = contexts[Math.floor(i / permutationCount[j]!) % contexts.length]!;
|
|
211
|
-
}
|
|
212
|
-
permutations.push(input);
|
|
213
|
-
}
|
|
214
|
-
return permutations.length ? permutations : [{}];
|
|
215
|
-
}
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
import * as momoa from '@humanwhocodes/momoa';
|
|
2
|
-
import { bundle, encodeFragment, parseRef, replaceNode } from '@terrazzo/json-schema-tools';
|
|
3
|
-
import type yamlToMomoa from 'yaml-to-momoa';
|
|
4
|
-
import type Logger from '../logger.js';
|
|
5
|
-
import type { Group, ReferenceObject, ResolverSourceNormalized } from '../types.js';
|
|
6
|
-
|
|
7
|
-
export interface NormalizeResolverOptions {
|
|
8
|
-
logger: Logger;
|
|
9
|
-
yamlToMomoa?: typeof yamlToMomoa;
|
|
10
|
-
filename: URL;
|
|
11
|
-
req: (url: URL, origin: URL) => Promise<string>;
|
|
12
|
-
src?: any;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/** Normalize resolver (assuming it’s been validated) */
|
|
16
|
-
export async function normalizeResolver(
|
|
17
|
-
document: momoa.DocumentNode,
|
|
18
|
-
{ logger, filename, req, src, yamlToMomoa }: NormalizeResolverOptions,
|
|
19
|
-
): Promise<ResolverSourceNormalized> {
|
|
20
|
-
// Important note: think about sets, modifiers, and resolutionOrder all
|
|
21
|
-
// containing their own partial tokens documents. Now think about JSON $refs
|
|
22
|
-
// inside those. Because we want to treat them all as one _eventual_ document,
|
|
23
|
-
// we defer resolving $refs until the very last step. In most setups, this has
|
|
24
|
-
// no effect on the final result, however, in the scenario where remote
|
|
25
|
-
// documents are loaded and they conflict in unexpected ways, resolving too
|
|
26
|
-
// early will produce incorrect results.
|
|
27
|
-
//
|
|
28
|
-
// To prevent this, we bundle ONCE at the very top level, with the `$defs` at
|
|
29
|
-
// the top level now containing all partial documents (as opposed to bundling
|
|
30
|
-
// every sub document individually). So all that said, we are deciding to
|
|
31
|
-
// choose the “all-in-one“ method for closer support with DTCG aliases, but at
|
|
32
|
-
// the expense of some edge cases of $refs behaving unexpectedly.
|
|
33
|
-
const resolverBundle = await bundle([{ filename, src }], { req, yamlToMomoa });
|
|
34
|
-
const resolverSource = momoa.evaluate(resolverBundle.document) as unknown as ResolverSourceNormalized;
|
|
35
|
-
|
|
36
|
-
// Resolve $refs, but in a very different way than everywhere else These are
|
|
37
|
-
// all _evaluated_, meaning initialized in JS memory. Unlike in the AST, when
|
|
38
|
-
// we resolve these they’ll share memory points (which isn’t possible in the
|
|
39
|
-
// AST—values must be duplicated). This code is unique because it’s the only
|
|
40
|
-
// place where we’re dealing with shared, initialized JS memory.
|
|
41
|
-
replaceNode(document, resolverBundle.document); // inject $defs into the root document
|
|
42
|
-
for (const set of Object.values(resolverSource.sets ?? {})) {
|
|
43
|
-
for (const source of set.sources) {
|
|
44
|
-
resolvePartials(source, { resolver: resolverSource, logger });
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
for (const modifier of Object.values(resolverSource.modifiers ?? {})) {
|
|
48
|
-
for (const context of Object.values(modifier.contexts)) {
|
|
49
|
-
for (const source of context) {
|
|
50
|
-
resolvePartials(source, { resolver: resolverSource, logger });
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
for (const item of resolverSource.resolutionOrder ?? []) {
|
|
55
|
-
resolvePartials(item, { resolver: resolverSource, logger });
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return {
|
|
59
|
-
name: resolverSource.name,
|
|
60
|
-
version: resolverSource.version,
|
|
61
|
-
description: resolverSource.description,
|
|
62
|
-
sets: resolverSource.sets,
|
|
63
|
-
modifiers: resolverSource.modifiers,
|
|
64
|
-
resolutionOrder: resolverSource.resolutionOrder,
|
|
65
|
-
_source: {
|
|
66
|
-
filename,
|
|
67
|
-
document,
|
|
68
|
-
},
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/** Resolve $refs for already-initialized JS */
|
|
73
|
-
function resolvePartials(
|
|
74
|
-
source: Group | ReferenceObject,
|
|
75
|
-
{
|
|
76
|
-
resolver,
|
|
77
|
-
logger,
|
|
78
|
-
}: {
|
|
79
|
-
resolver: any;
|
|
80
|
-
logger: Logger;
|
|
81
|
-
},
|
|
82
|
-
) {
|
|
83
|
-
if (!source) {
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
const entry = { group: 'parser' as const, label: 'resolver' };
|
|
87
|
-
if (Array.isArray(source)) {
|
|
88
|
-
for (const item of source) {
|
|
89
|
-
resolvePartials(item, { resolver, logger });
|
|
90
|
-
}
|
|
91
|
-
} else if (typeof source === 'object') {
|
|
92
|
-
for (const k of Object.keys(source)) {
|
|
93
|
-
if (k === '$ref') {
|
|
94
|
-
const $ref = (source as any)[k] as string;
|
|
95
|
-
const { url, subpath = [] } = parseRef($ref);
|
|
96
|
-
if (url !== '.' || !subpath.length) {
|
|
97
|
-
logger.error({ ...entry, message: `Could not load $ref ${JSON.stringify($ref)}` });
|
|
98
|
-
}
|
|
99
|
-
const found = findObject(resolver, subpath ?? [], logger);
|
|
100
|
-
if (subpath[0] === 'sets' || subpath[0] === 'modifiers') {
|
|
101
|
-
found.type = subpath[0].replace(/s$/, '');
|
|
102
|
-
found.name = subpath[1];
|
|
103
|
-
}
|
|
104
|
-
if (found) {
|
|
105
|
-
for (const k2 of Object.keys(found)) {
|
|
106
|
-
(source as any)[k2] = found[k2];
|
|
107
|
-
}
|
|
108
|
-
delete (source as any).$ref;
|
|
109
|
-
} else {
|
|
110
|
-
logger.error({ ...entry, message: `Could not find ${JSON.stringify($ref)}` });
|
|
111
|
-
}
|
|
112
|
-
} else {
|
|
113
|
-
resolvePartials((source as any)[k], { resolver, logger });
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function findObject(dict: Record<string, any>, path: string[], logger: Logger): any {
|
|
120
|
-
let node = dict;
|
|
121
|
-
for (const idRaw of path) {
|
|
122
|
-
const id = idRaw.replace(/~/g, '~0').replace(/\//g, '~1');
|
|
123
|
-
if (!(id in node)) {
|
|
124
|
-
logger.error({
|
|
125
|
-
group: 'parser',
|
|
126
|
-
label: 'resolver',
|
|
127
|
-
message: `Could not load $ref ${encodeFragment(path)}`,
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
node = node[id];
|
|
131
|
-
}
|
|
132
|
-
return node;
|
|
133
|
-
}
|