@teacss/engine 0.8.5-alpha.3 → 0.9.0-alpha.3
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/binding-header.d.ts +20 -0
- package/binding.d.ts +19 -1
- package/binding.js +54 -54
- package/contract.d.ts +246 -0
- package/dist/facade/config-view.d.ts +27 -0
- package/dist/facade/config-view.js +96 -0
- package/dist/facade/constants.d.ts +22 -0
- package/{src/facade/constants.mjs → dist/facade/constants.js} +10 -11
- package/dist/facade/countable-set.d.ts +43 -0
- package/dist/facade/countable-set.js +149 -0
- package/dist/facade/errors.d.ts +11 -0
- package/dist/facade/errors.js +18 -0
- package/dist/facade/generator.d.ts +153 -0
- package/dist/facade/generator.js +777 -0
- package/dist/facade/index.d.ts +6 -0
- package/dist/facade/index.js +9 -0
- package/dist/facade/result.d.ts +68 -0
- package/dist/facade/result.js +134 -0
- package/dist/facade/select.d.ts +175 -0
- package/dist/facade/select.js +680 -0
- package/dist/facade/transformer.d.ts +44 -0
- package/dist/facade/transformer.js +59 -0
- package/dist/facade/ts-fallback.d.ts +32 -0
- package/dist/facade/ts-fallback.js +78 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/index.d.ts +1 -387
- package/index.js +1 -1
- package/package.json +42 -23
- package/src/facade/config-view.mjs +0 -82
- package/src/facade/countable-set.mjs +0 -179
- package/src/facade/errors.mjs +0 -22
- package/src/facade/generator.mjs +0 -812
- package/src/facade/index.mjs +0 -19
- package/src/facade/result.mjs +0 -157
- package/src/facade/select.mjs +0 -635
- package/src/facade/transformer.mjs +0 -56
- package/src/facade/ts-fallback.mjs +0 -88
|
@@ -0,0 +1,777 @@
|
|
|
1
|
+
// `FacadeGenerator`: the object `createGenerator` returns, shaped like the 0.8.2 `TeacssGenerator`
|
|
2
|
+
// (v02.9 §3.2) and backed by the Rust engine or, after a fallback, a TypeScript generator.
|
|
3
|
+
// The tooling members (`makeContext`, `parseUtil`, ...) forward to a TypeScript generator and
|
|
4
|
+
// return its objects: a `RuleContext.generator` there is that generator, not the facade.
|
|
5
|
+
import { Engine } from "../../binding.js";
|
|
6
|
+
import { createConfigView } from "./config-view.js";
|
|
7
|
+
import { ENGINE_INSTANCE, ENGINE_MATERIALIZE, ENGINE_PACKAGE_VERSION, ENGINE_STATS } from "./constants.js";
|
|
8
|
+
import { BoundedSet, BoundedTokenCache, CountableSet, copyCountableSet, expandInputGroups, isCountableSet } from "./countable-set.js";
|
|
9
|
+
import { tsNotReadyError } from "./errors.js";
|
|
10
|
+
import { buildMatched, createFacadeResult, describeUnmatched, fallbackIsTokenShaped, formatUnmatchedError, toTuple } from "./result.js";
|
|
11
|
+
import { translateConfig } from "./select.js";
|
|
12
|
+
import { createCompileClassTransformer } from "./transformer.js";
|
|
13
|
+
import { createTsFallback } from "./ts-fallback.js";
|
|
14
|
+
/** Port of core/utils/events.ts:84-112. */
|
|
15
|
+
export function createNanoEvents() {
|
|
16
|
+
return {
|
|
17
|
+
events: Object.create(null),
|
|
18
|
+
emit(event, ...args) {
|
|
19
|
+
const listeners = Object.hasOwn(this.events, event) && Array.isArray(this.events[event]) ? [...this.events[event]] : [];
|
|
20
|
+
for (const listener of listeners)
|
|
21
|
+
listener(...args);
|
|
22
|
+
},
|
|
23
|
+
on(event, cb) {
|
|
24
|
+
const existing = Object.hasOwn(this.events, event) ? this.events[event] : undefined;
|
|
25
|
+
const listeners = Array.isArray(existing) ? existing : [];
|
|
26
|
+
if (listeners !== existing)
|
|
27
|
+
this.events[event] = listeners;
|
|
28
|
+
listeners.push(cb);
|
|
29
|
+
let subscribed = true;
|
|
30
|
+
return () => {
|
|
31
|
+
if (!subscribed)
|
|
32
|
+
return;
|
|
33
|
+
const current = Object.hasOwn(this.events, event) && Array.isArray(this.events[event]) ? this.events[event] : [];
|
|
34
|
+
this.events[event] = current.filter((listener) => listener !== cb);
|
|
35
|
+
subscribed = false;
|
|
36
|
+
};
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** Port of core/utils/escape.ts:10-72 (CSSOM "serialize an identifier"). */
|
|
41
|
+
export function escapeSelector(str) {
|
|
42
|
+
const length = str.length;
|
|
43
|
+
const firstCodeUnit = str.charCodeAt(0);
|
|
44
|
+
let result = "";
|
|
45
|
+
for (let index = 0; index < length; index++) {
|
|
46
|
+
const codeUnit = str.charCodeAt(index);
|
|
47
|
+
if (codeUnit === 0x0000) {
|
|
48
|
+
result += "\uFFFD";
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if ((codeUnit >= 0x0001 && codeUnit <= 0x001f) || codeUnit === 0x007f || (index === 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039) || (index === 1 && codeUnit >= 0x0030 && codeUnit <= 0x0039 && firstCodeUnit === 0x002d)) {
|
|
52
|
+
result += `\\${codeUnit.toString(16)} `;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (index === 0 && length === 1 && codeUnit === 0x002d) {
|
|
56
|
+
result += `\\${str.charAt(index)}`;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (codeUnit >= 0x0080 || codeUnit === 0x002d || codeUnit === 0x005f || (codeUnit >= 0x0030 && codeUnit <= 0x0039) || (codeUnit >= 0x0041 && codeUnit <= 0x005a) || (codeUnit >= 0x0061 && codeUnit <= 0x007a)) {
|
|
60
|
+
result += str.charAt(index);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
result += `\\${str.charAt(index)}`;
|
|
64
|
+
}
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
const attributifyRe = /^\[(.+?)(~?=)"(.*)"\]$/;
|
|
68
|
+
/** generator.ts:3722. */
|
|
69
|
+
export function toEscapedSelector(raw) {
|
|
70
|
+
if (attributifyRe.test(raw))
|
|
71
|
+
return raw.replace(attributifyRe, (_, name, sign, value) => `[${escapeSelector(name)}${sign}"${escapeSelector(value)}"]`);
|
|
72
|
+
return `.${escapeSelector(raw)}`;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* generator.ts:393-440: the selector with each whole occurrence of the token's own selector
|
|
76
|
+
* replaced by `&`, skipping strings, comments, escapes and attribute brackets.
|
|
77
|
+
*/
|
|
78
|
+
function normalizedSelector(selector, raw, escape) {
|
|
79
|
+
if (selector === undefined)
|
|
80
|
+
return undefined;
|
|
81
|
+
const subject = escape(raw);
|
|
82
|
+
const attributeSubject = subject.startsWith("[");
|
|
83
|
+
let brackets = 0;
|
|
84
|
+
let quote;
|
|
85
|
+
let cursor = 0;
|
|
86
|
+
let normalized = "";
|
|
87
|
+
for (let index = 0; index < selector.length; index++) {
|
|
88
|
+
const char = selector[index];
|
|
89
|
+
if (quote) {
|
|
90
|
+
if (char === "\\")
|
|
91
|
+
index++;
|
|
92
|
+
else if (char === quote)
|
|
93
|
+
quote = undefined;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (char === "/" && selector[index + 1] === "*") {
|
|
97
|
+
const end = selector.indexOf("*/", index + 2);
|
|
98
|
+
if (end === -1)
|
|
99
|
+
break;
|
|
100
|
+
index = end + 1;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (char === "\\") {
|
|
104
|
+
index++;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (char === '"' || char === "'") {
|
|
108
|
+
quote = char;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (brackets === 0 && char === subject[0] && selector.startsWith(subject, index)) {
|
|
112
|
+
const next = selector[index + subject.length];
|
|
113
|
+
const continued = next !== undefined && (/[\w-]/.test(next) || next === "\\" || next.charCodeAt(0) >= 0x80);
|
|
114
|
+
if (attributeSubject || !continued) {
|
|
115
|
+
normalized += `${selector.slice(cursor, index)}&`;
|
|
116
|
+
cursor = index + subject.length;
|
|
117
|
+
index = cursor - 1;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (char === "[")
|
|
122
|
+
brackets++;
|
|
123
|
+
else if (char === "]" && brackets > 0)
|
|
124
|
+
brackets--;
|
|
125
|
+
}
|
|
126
|
+
return normalized + selector.slice(cursor);
|
|
127
|
+
}
|
|
128
|
+
/** generator.ts:462. */
|
|
129
|
+
const isStringifiedUtilTuple = (value) => Array.isArray(value) && [0, 1, 2, 3, 4, 5, 6].every((index) => Object.hasOwn(value, index));
|
|
130
|
+
const metaValue = (meta, key) => (meta && typeof meta === "object" ? meta[key] : undefined);
|
|
131
|
+
/**
|
|
132
|
+
* generator.ts:441-460: same bodies, selectors (relative to each token), parents and layer,
|
|
133
|
+
* sort, noScope and noMerge.
|
|
134
|
+
*/
|
|
135
|
+
function sameStringifiedUtils(left, leftRaw, right, rightRaw, escape) {
|
|
136
|
+
const leftItems = left.filter(isStringifiedUtilTuple);
|
|
137
|
+
const rightItems = right.filter(isStringifiedUtilTuple);
|
|
138
|
+
if (leftItems.length !== rightItems.length)
|
|
139
|
+
return false;
|
|
140
|
+
for (let index = 0; index < leftItems.length; index++) {
|
|
141
|
+
const item = leftItems[index];
|
|
142
|
+
const other = rightItems[index];
|
|
143
|
+
if (item[2] !== other[2] ||
|
|
144
|
+
normalizedSelector(item[1], leftRaw, escape) !== normalizedSelector(other[1], rightRaw, escape) ||
|
|
145
|
+
item[3] !== other[3] ||
|
|
146
|
+
metaValue(item[4], "layer") !== metaValue(other[4], "layer") ||
|
|
147
|
+
metaValue(item[4], "sort") !== metaValue(other[4], "sort") ||
|
|
148
|
+
!!metaValue(item[4], "noScope") !== !!metaValue(other[4], "noScope") ||
|
|
149
|
+
!!(item[6] ?? metaValue(item[4], "noMerge")) !== !!(other[6] ?? metaValue(other[4], "noMerge")))
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
/** A fresh copy of each tuple, as the oracle's `copyValidatedStringifiedUtils` returns. */
|
|
155
|
+
const copyTuples = (tuples) => tuples.map((tuple) => [...tuple]);
|
|
156
|
+
/**
|
|
157
|
+
* core/config.ts:26-36: the first entry of each name wins.
|
|
158
|
+
*/
|
|
159
|
+
function uniqueNamed(entries) {
|
|
160
|
+
const names = new Set();
|
|
161
|
+
const unique = [];
|
|
162
|
+
for (const entry of entries) {
|
|
163
|
+
if (names.has(entry.name))
|
|
164
|
+
continue;
|
|
165
|
+
names.add(entry.name);
|
|
166
|
+
unique.push(entry);
|
|
167
|
+
}
|
|
168
|
+
return unique;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* v02.9 §8.1: the process-wide counters, the same object the teacss-ts hook updates.
|
|
172
|
+
*/
|
|
173
|
+
export function engineStats() {
|
|
174
|
+
const scope = globalThis;
|
|
175
|
+
return (scope[ENGINE_STATS] ??= { rust: 0, ts: 0, fallbacks: Object.create(null) });
|
|
176
|
+
}
|
|
177
|
+
const reported = new Set();
|
|
178
|
+
/**
|
|
179
|
+
* v02.9 §9: each reason and path prints once per process; the counter grows on every call. A
|
|
180
|
+
* host `report(reason, path, level)` (the hook's consola) replaces the stderr line.
|
|
181
|
+
*/
|
|
182
|
+
export function reportFallback(reason, path, host = {}, level = "info") {
|
|
183
|
+
const stats = engineStats();
|
|
184
|
+
stats.fallbacks[reason] = (stats.fallbacks[reason] ?? 0) + 1;
|
|
185
|
+
const key = `${reason}\0${path}`;
|
|
186
|
+
if (reported.has(key))
|
|
187
|
+
return;
|
|
188
|
+
reported.add(key);
|
|
189
|
+
if (typeof host.report === "function")
|
|
190
|
+
host.report(reason, path, level);
|
|
191
|
+
else
|
|
192
|
+
process.stderr.write(`[teacss] engine: falling back to the TypeScript engine (${reason} at ${path})\n`);
|
|
193
|
+
}
|
|
194
|
+
/** array-internal.ts:7-13: ownership is rechecked before each read. */
|
|
195
|
+
function* ownArrayValues(array) {
|
|
196
|
+
if (!Array.isArray(array))
|
|
197
|
+
return;
|
|
198
|
+
for (let index = 0, length = array.length; index < length; index++) {
|
|
199
|
+
if (Object.hasOwn(array, index))
|
|
200
|
+
yield array[index];
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/** generator.ts:363-372: `then` is read once and called on the original value. */
|
|
204
|
+
function snapshotThenable(value) {
|
|
205
|
+
if (value === null || (typeof value !== "object" && typeof value !== "function"))
|
|
206
|
+
return undefined;
|
|
207
|
+
const then = Reflect.get(value, "then");
|
|
208
|
+
if (typeof then !== "function")
|
|
209
|
+
return undefined;
|
|
210
|
+
return { then: (onFulfilled, onRejected) => Reflect.apply(then, value, [onFulfilled, onRejected]) };
|
|
211
|
+
}
|
|
212
|
+
const FORWARDED_OPTIONS = ["id", "scope", "preflights", "safelist", "minify", "extendedInfo", "tokenConcurrency"];
|
|
213
|
+
/**
|
|
214
|
+
* Generate options whose own `isInputCurrent` was read once; the other own options are still
|
|
215
|
+
* read on every access, as the oracle reads them on every retry (generator.ts:2366-2372).
|
|
216
|
+
*/
|
|
217
|
+
function captureInputCheck(options) {
|
|
218
|
+
if (options === null || typeof options !== "object")
|
|
219
|
+
return options;
|
|
220
|
+
const captured = {};
|
|
221
|
+
if (Object.hasOwn(options, "isInputCurrent"))
|
|
222
|
+
captured.isInputCurrent = options.isInputCurrent;
|
|
223
|
+
for (const key of FORWARDED_OPTIONS) {
|
|
224
|
+
if (Object.hasOwn(options, key))
|
|
225
|
+
Object.defineProperty(captured, key, { enumerable: true, get: () => options[key] });
|
|
226
|
+
}
|
|
227
|
+
return captured;
|
|
228
|
+
}
|
|
229
|
+
function disposeEngine(engine) {
|
|
230
|
+
try {
|
|
231
|
+
engine.dispose();
|
|
232
|
+
}
|
|
233
|
+
catch { }
|
|
234
|
+
}
|
|
235
|
+
/** The `extract` each host `extractorSplit` had when the facade first saw it. */
|
|
236
|
+
const originalExtract = new WeakMap();
|
|
237
|
+
function replacedExtract(split) {
|
|
238
|
+
if (!originalExtract.has(split))
|
|
239
|
+
originalExtract.set(split, split.extract);
|
|
240
|
+
return originalExtract.get(split) !== split.extract;
|
|
241
|
+
}
|
|
242
|
+
export class FacadeGenerator {
|
|
243
|
+
/** An uncommitted candidate's Engine is disposed once the candidate is collected. */
|
|
244
|
+
static #registry = new FinalizationRegistry((held) => {
|
|
245
|
+
if (held.kind === "rust" && !held.committed)
|
|
246
|
+
disposeEngine(held.engine);
|
|
247
|
+
});
|
|
248
|
+
#backend;
|
|
249
|
+
#held = null;
|
|
250
|
+
#host;
|
|
251
|
+
#tsFallback;
|
|
252
|
+
/** The compile-class transformer: created once and handed to every candidate (v02.9 §5.2). */
|
|
253
|
+
#transformer;
|
|
254
|
+
#cache = new BoundedTokenCache();
|
|
255
|
+
#blocked = new BoundedSet();
|
|
256
|
+
#parentOrders = new Map();
|
|
257
|
+
#activatedRules = new Set();
|
|
258
|
+
// Declared for the method cards (F7.15-F7.19).
|
|
259
|
+
#warnedUnmatched = new BoundedSet();
|
|
260
|
+
#allTokens = null;
|
|
261
|
+
#configVersion = 0;
|
|
262
|
+
/** Weak, so a prepared config nobody commits does not keep its candidate (and Engine) alive. */
|
|
263
|
+
#prepared = new WeakSet();
|
|
264
|
+
/** The facade's own emitter; a TS backend's `events` are not forwarded. */
|
|
265
|
+
events = createNanoEvents();
|
|
266
|
+
/**
|
|
267
|
+
* An `engine` gives a Rust backend and `ts` a TypeScript one.
|
|
268
|
+
*/
|
|
269
|
+
constructor({ engine, request = {}, presets = [], transformers = [], ts, userConfig = {}, defaults = {}, host = {}, transformer = null, }) {
|
|
270
|
+
this.#host = host;
|
|
271
|
+
if (host.core?.extractorSplit)
|
|
272
|
+
replacedExtract(host.core.extractorSplit);
|
|
273
|
+
this.userConfig = userConfig;
|
|
274
|
+
this.defaults = defaults;
|
|
275
|
+
this.#transformer = transformer;
|
|
276
|
+
this.#tsFallback = createTsFallback(host, { userConfig, defaults });
|
|
277
|
+
if (ts) {
|
|
278
|
+
this.#backend = { kind: "ts", generator: ts };
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (!engine)
|
|
282
|
+
throw new TypeError("[@teacss/engine] FacadeGenerator needs engine or ts");
|
|
283
|
+
this.#transformer = transformer ?? createCompileClassTransformer();
|
|
284
|
+
const view = engine.configView();
|
|
285
|
+
const own = view.standard?.compileClass === true ? [this.#transformer] : [];
|
|
286
|
+
const list = uniqueNamed([...own, ...transformers]);
|
|
287
|
+
const config = createConfigView({ view, request, presets, userConfig, transformers: list, host, fallback: this.#tsFallback, engine: own.length ? engine : undefined });
|
|
288
|
+
this.#backend = { kind: "rust", engine, request, presets, config, transformer: this.#transformer };
|
|
289
|
+
}
|
|
290
|
+
get version() {
|
|
291
|
+
return this.#host.core?.version ?? ENGINE_PACKAGE_VERSION;
|
|
292
|
+
}
|
|
293
|
+
get config() {
|
|
294
|
+
return this.#backend.kind === "rust" ? this.#backend.config : this.#backend.generator.config;
|
|
295
|
+
}
|
|
296
|
+
get cache() {
|
|
297
|
+
return this.#backend.kind === "rust" ? this.#cache : this.#backend.generator.cache;
|
|
298
|
+
}
|
|
299
|
+
get blocked() {
|
|
300
|
+
return this.#backend.kind === "rust" ? this.#blocked : this.#backend.generator.blocked;
|
|
301
|
+
}
|
|
302
|
+
get parentOrders() {
|
|
303
|
+
return this.#backend.kind === "rust" ? this.#parentOrders : this.#backend.generator.parentOrders;
|
|
304
|
+
}
|
|
305
|
+
get activatedRules() {
|
|
306
|
+
return this.#backend.kind === "rust" ? this.#activatedRules : this.#backend.generator.activatedRules;
|
|
307
|
+
}
|
|
308
|
+
// The runtime gives a detached reload candidate its own copies (transformer-context.ts:421-424),
|
|
309
|
+
// as it may on the oracle's public fields; a Rust backend keeps its bounded containers.
|
|
310
|
+
set cache(value) {
|
|
311
|
+
if (this.#backend.kind === "ts") {
|
|
312
|
+
this.#backend.generator.cache = value;
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
if (value instanceof BoundedTokenCache) {
|
|
316
|
+
this.#cache = value;
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
const cache = new BoundedTokenCache();
|
|
320
|
+
for (const [token, utils] of value)
|
|
321
|
+
cache.set(token, utils);
|
|
322
|
+
this.#cache = cache;
|
|
323
|
+
}
|
|
324
|
+
set blocked(value) {
|
|
325
|
+
if (this.#backend.kind === "ts")
|
|
326
|
+
this.#backend.generator.blocked = value;
|
|
327
|
+
else
|
|
328
|
+
this.#blocked = value instanceof BoundedSet ? value : new BoundedSet(undefined, value);
|
|
329
|
+
}
|
|
330
|
+
set parentOrders(value) {
|
|
331
|
+
if (this.#backend.kind === "ts")
|
|
332
|
+
this.#backend.generator.parentOrders = value;
|
|
333
|
+
else
|
|
334
|
+
this.#parentOrders = value;
|
|
335
|
+
}
|
|
336
|
+
set activatedRules(value) {
|
|
337
|
+
if (this.#backend.kind === "ts")
|
|
338
|
+
this.#backend.generator.activatedRules = value;
|
|
339
|
+
else
|
|
340
|
+
this.#activatedRules = value;
|
|
341
|
+
}
|
|
342
|
+
get [ENGINE_INSTANCE]() {
|
|
343
|
+
return this.#backend.kind === "rust" ? this.#backend.engine : null;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* The two overloads of generator.ts:2037-2040. With an `id` the Engine records the module's
|
|
347
|
+
* tokens; a counted set adds each token's occurrences.
|
|
348
|
+
*/
|
|
349
|
+
async applyExtractors(code, id, extracted = new Set()) {
|
|
350
|
+
await Promise.resolve();
|
|
351
|
+
const backend = this.#backend;
|
|
352
|
+
if (backend.kind === "ts")
|
|
353
|
+
return backend.generator.applyExtractors(code, id, extracted);
|
|
354
|
+
const split = this.#host.core?.extractorSplit;
|
|
355
|
+
if (split && replacedExtract(split)) {
|
|
356
|
+
// The host's default extractor was swapped at runtime (runtime context.ts `usesDefaultExtractor`
|
|
357
|
+
// checks the same): run it as generator.ts:2037 does instead of the native splitter.
|
|
358
|
+
const result = await split.extract({ code, id, extracted, envMode: this.config.envMode });
|
|
359
|
+
for (const token of result ?? []) {
|
|
360
|
+
if (isCountableSet(extracted))
|
|
361
|
+
extracted.setCount(token, extracted.getCount(token) + 1);
|
|
362
|
+
else
|
|
363
|
+
extracted.add(token);
|
|
364
|
+
}
|
|
365
|
+
return extracted;
|
|
366
|
+
}
|
|
367
|
+
const engine = backend.engine;
|
|
368
|
+
if (isCountableSet(extracted)) {
|
|
369
|
+
const counted = id === undefined ? engine.extractCounted(code) : engine.extractCounted(code, id);
|
|
370
|
+
if (id !== undefined)
|
|
371
|
+
this.#updateModule(id, code);
|
|
372
|
+
for (const { token, count } of counted)
|
|
373
|
+
extracted.setCount(token, extracted.getCount(token) + count);
|
|
374
|
+
return extracted;
|
|
375
|
+
}
|
|
376
|
+
const tokens = id === undefined ? engine.extract(code) : this.#updateModule(id, code);
|
|
377
|
+
for (const token of tokens)
|
|
378
|
+
extracted.add(token);
|
|
379
|
+
return extracted;
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* The overloads of generator.ts:2316-2318. String input is extracted here, as Rust's
|
|
383
|
+
* `{ kind: "code" }` would, so its tokens reach the cache and the unmatched check (the oracle
|
|
384
|
+
* runs `applyExtractors` on it, generator.ts:2378).
|
|
385
|
+
*/
|
|
386
|
+
async generate(input, options = {}) {
|
|
387
|
+
const backend = this.#backend;
|
|
388
|
+
if (backend.kind === "ts") {
|
|
389
|
+
// A rule may commit a new configuration through this facade while the TS backend runs; the
|
|
390
|
+
// oracle then retries on the new configuration (generator.ts:2356), reading the input
|
|
391
|
+
// check once (generator.ts:2324).
|
|
392
|
+
const delegated = captureInputCheck(options);
|
|
393
|
+
const result = await backend.generator.generate(input, delegated);
|
|
394
|
+
return this.#backend === backend ? result : this.generate(input, delegated);
|
|
395
|
+
}
|
|
396
|
+
const own = (key) => (Object.hasOwn(options, key) ? options[key] : undefined);
|
|
397
|
+
const isInputCurrent = own("isInputCurrent");
|
|
398
|
+
if (isInputCurrent !== undefined && typeof isInputCurrent !== "function")
|
|
399
|
+
throw new TypeError("[@teacss/core] isInputCurrent must be a function.");
|
|
400
|
+
const checkInput = () => {
|
|
401
|
+
if (!isInputCurrent)
|
|
402
|
+
return;
|
|
403
|
+
const current = isInputCurrent();
|
|
404
|
+
if (typeof current !== "boolean") {
|
|
405
|
+
void Promise.resolve(current).catch(() => { });
|
|
406
|
+
throw new TypeError("[@teacss/core] isInputCurrent must return a boolean synchronously.");
|
|
407
|
+
}
|
|
408
|
+
if (!current)
|
|
409
|
+
throw new Error("[@teacss/core] Generation input is no longer current.");
|
|
410
|
+
};
|
|
411
|
+
await Promise.resolve();
|
|
412
|
+
checkInput();
|
|
413
|
+
const tokenConcurrency = own("tokenConcurrency");
|
|
414
|
+
if (tokenConcurrency !== undefined && (!Number.isSafeInteger(tokenConcurrency) || tokenConcurrency < 1))
|
|
415
|
+
throw new RangeError("tokenConcurrency must be a positive integer");
|
|
416
|
+
const preflights = own("preflights") ?? true;
|
|
417
|
+
const safelist = own("safelist") ?? true;
|
|
418
|
+
const minify = own("minify") ?? false;
|
|
419
|
+
const extendedInfo = own("extendedInfo") ?? false;
|
|
420
|
+
const { rustInput, countOf, collection, tokens } = this.#normalizeInput(input, own("id"), extendedInfo);
|
|
421
|
+
// Payloads are always requested: the cache stores them even when the caller did not ask.
|
|
422
|
+
const rust = backend.engine.generate(rustInput, { id: own("id"), preflights, safelist, minify, extendedInfo: true, scope: own("scope") });
|
|
423
|
+
const config = backend.config;
|
|
424
|
+
const matched = buildMatched({ rust: extendedInfo ? rust : { ...rust, matchedInfo: null }, extendedInfo, countOf });
|
|
425
|
+
const candidates = this.#candidates(tokens, safelist ? config.safelist : []);
|
|
426
|
+
this.#recordGeneration(rust, candidates, collection);
|
|
427
|
+
const unmatchedTokens = this.#collectUnmatched(rust, candidates, config);
|
|
428
|
+
let resolved;
|
|
429
|
+
const resolveUnmatched = () => (resolved ??= unmatchedTokens.length ? backend.engine.diagnoseUnmatched(unmatchedTokens) : []);
|
|
430
|
+
if (unmatchedTokens.length && config.unmatched === "error") {
|
|
431
|
+
const diagnosed = resolveUnmatched();
|
|
432
|
+
if (diagnosed.length)
|
|
433
|
+
throw new Error(formatUnmatchedError(diagnosed));
|
|
434
|
+
}
|
|
435
|
+
const result = createFacadeResult({ rust, matched, minify, outputToCssLayers: config.outputToCssLayers, configuredLayers: config.layers, resolveUnmatched });
|
|
436
|
+
if (unmatchedTokens.length && config.unmatched === "warn" && config.warn) {
|
|
437
|
+
for (const entry of resolveUnmatched()) {
|
|
438
|
+
if (this.#warnedUnmatched.has(entry.token))
|
|
439
|
+
continue;
|
|
440
|
+
console.warn("[Teacss]", `unmatched token ${describeUnmatched(entry)}`);
|
|
441
|
+
this.#warnedUnmatched.add(entry.token);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
checkInput();
|
|
445
|
+
return result;
|
|
446
|
+
}
|
|
447
|
+
#normalizeInput(input, id, extendedInfo) {
|
|
448
|
+
const engine = this.#backend.engine;
|
|
449
|
+
if (typeof input === "string") {
|
|
450
|
+
const counted = engine.extractCounted(input, id);
|
|
451
|
+
const tokens = new CountableSet();
|
|
452
|
+
for (const { token, count } of counted)
|
|
453
|
+
tokens.setCount(token, count);
|
|
454
|
+
return { rustInput: { kind: "tokens", tokens: [...tokens] }, countOf: extendedInfo ? (token) => tokens.getCount(token) : undefined, collection: false, tokens };
|
|
455
|
+
}
|
|
456
|
+
const counted = input instanceof CountableSet || this.#host.core?.isCountableSet?.(input);
|
|
457
|
+
if (counted) {
|
|
458
|
+
const source = input;
|
|
459
|
+
const copy = new CountableSet();
|
|
460
|
+
for (const token of source)
|
|
461
|
+
copy.setCount(token, source.getCount(token));
|
|
462
|
+
const expanded = expandInputGroups(copyCountableSet(copy));
|
|
463
|
+
return { rustInput: { kind: "tokens", tokens: [...expanded] }, countOf: (token) => expanded.getCount(token), collection: true, tokens: expanded };
|
|
464
|
+
}
|
|
465
|
+
if (Array.isArray(input) || input instanceof Set) {
|
|
466
|
+
const tokens = expandInputGroups(new Set(input));
|
|
467
|
+
return { rustInput: this.#isAllTokens(tokens) ? { kind: "all" } : { kind: "tokens", tokens: [...tokens] }, countOf: () => -1, collection: true, tokens };
|
|
468
|
+
}
|
|
469
|
+
throw new TypeError("[@teacss/engine] generate input must be a string, array, Set or CountableSet");
|
|
470
|
+
}
|
|
471
|
+
/** v02.9 §7.3: a set equal to every module token is sent as `{ kind: "all" }`. */
|
|
472
|
+
#isAllTokens(tokens) {
|
|
473
|
+
const engine = this.#backend.engine;
|
|
474
|
+
this.#allTokens ??= new Set(engine.allTokens());
|
|
475
|
+
const all = this.#allTokens;
|
|
476
|
+
return tokens.size === all.size && [...tokens].every((token) => all.has(token));
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* The tokens generation visits, in order: the input, then the safelist members it lacks
|
|
480
|
+
* (generator.ts:2390-2410, which adds them without counting).
|
|
481
|
+
*/
|
|
482
|
+
#candidates(tokens, configuredSafelist) {
|
|
483
|
+
const candidates = [...tokens];
|
|
484
|
+
const seen = new Set(candidates);
|
|
485
|
+
for (const entry of Array.isArray(configuredSafelist) ? configuredSafelist : []) {
|
|
486
|
+
if (typeof entry !== "string")
|
|
487
|
+
continue;
|
|
488
|
+
const trimmed = entry.trim();
|
|
489
|
+
if (!trimmed)
|
|
490
|
+
continue;
|
|
491
|
+
for (const member of trimmed.includes("{") ? expandInputGroups(new Set([trimmed])) : [trimmed]) {
|
|
492
|
+
if (seen.has(member))
|
|
493
|
+
continue;
|
|
494
|
+
seen.add(member);
|
|
495
|
+
candidates.push(member);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return candidates;
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* v02.9 §3.7: every visited token is cached in order (a miss as `null`, a blocked one also in
|
|
502
|
+
* `blocked`); a collection's matches then become the retained entries (generator.ts:2626-2630).
|
|
503
|
+
* `parentOrders` is not written: the Rust utils carry no parent order, and the oracle's
|
|
504
|
+
* Standard rules record none.
|
|
505
|
+
*/
|
|
506
|
+
#recordGeneration(rust, candidates, collection) {
|
|
507
|
+
const engine = this.#backend.engine;
|
|
508
|
+
const payloads = new Map((rust.matchedInfo ?? []).map((info) => [info.token, info.data.map(toTuple)]));
|
|
509
|
+
for (const token of candidates) {
|
|
510
|
+
const payload = payloads.get(token);
|
|
511
|
+
if (payload) {
|
|
512
|
+
if (!collection)
|
|
513
|
+
this.#cache.set(token, payload);
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
516
|
+
if (engine.isBlocked(token))
|
|
517
|
+
this.#blocked.add(token);
|
|
518
|
+
this.#cache.set(token, null);
|
|
519
|
+
}
|
|
520
|
+
if (collection)
|
|
521
|
+
this.#cache.replaceRetained(rust.matched.map((token) => [token, payloads.get(token) ?? []]));
|
|
522
|
+
}
|
|
523
|
+
/** Token-shaped misses that no blocklist explains, in visiting order. */
|
|
524
|
+
#collectUnmatched(rust, candidates, config) {
|
|
525
|
+
if (config.unmatched !== "warn" && config.unmatched !== "error")
|
|
526
|
+
return [];
|
|
527
|
+
const engine = this.#backend.engine;
|
|
528
|
+
const matched = new Set(rust.matched);
|
|
529
|
+
const isTokenShaped = this.#host.core?.isTokenShaped ?? fallbackIsTokenShaped;
|
|
530
|
+
return candidates.filter((token) => !matched.has(token) && isTokenShaped(token) && !this.#blocked.has(token) && !engine.isBlocked(token));
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* generator.ts:2113-2286. A tuple array, or `undefined` for a miss or a blocked token; a miss
|
|
534
|
+
* already cached reads back as `null`. With an `alias` nothing is cached.
|
|
535
|
+
*/
|
|
536
|
+
async parseToken(raw, alias) {
|
|
537
|
+
await Promise.resolve();
|
|
538
|
+
const backend = this.#backend;
|
|
539
|
+
if (backend.kind === "ts")
|
|
540
|
+
return backend.generator.parseToken(raw, alias);
|
|
541
|
+
if (this.#blocked.has(raw))
|
|
542
|
+
return undefined;
|
|
543
|
+
const shared = !alias;
|
|
544
|
+
if (shared && this.#cache.has(raw)) {
|
|
545
|
+
const cached = this.#cache.get(raw);
|
|
546
|
+
if (!cached)
|
|
547
|
+
return cached;
|
|
548
|
+
return cached.length === 0 ? undefined : copyTuples(cached);
|
|
549
|
+
}
|
|
550
|
+
const utils = backend.engine.parseToken(raw, alias);
|
|
551
|
+
if (utils === null) {
|
|
552
|
+
if (backend.engine.isBlocked(raw)) {
|
|
553
|
+
if (shared) {
|
|
554
|
+
this.#blocked.add(raw);
|
|
555
|
+
this.#cache.set(raw, null);
|
|
556
|
+
}
|
|
557
|
+
return undefined;
|
|
558
|
+
}
|
|
559
|
+
if (shared)
|
|
560
|
+
this.#cache.set(raw, null);
|
|
561
|
+
return undefined;
|
|
562
|
+
}
|
|
563
|
+
const tuples = utils.map(toTuple);
|
|
564
|
+
if (shared)
|
|
565
|
+
this.#cache.set(raw, tuples);
|
|
566
|
+
// An empty static match is cached but reads as no match (generator.ts:2116).
|
|
567
|
+
return tuples.length === 0 ? undefined : copyTuples(tuples);
|
|
568
|
+
}
|
|
569
|
+
/** generator.ts:3611. */
|
|
570
|
+
isBlocked(raw) {
|
|
571
|
+
return this.#backend.kind === "ts" ? this.#backend.generator.isBlocked(raw) : this.#backend.engine.isBlocked(raw);
|
|
572
|
+
}
|
|
573
|
+
/** generator.ts:3623; the blocklist holds strings only (anything else falls back). */
|
|
574
|
+
getBlocked(raw) {
|
|
575
|
+
if (this.#backend.kind === "ts")
|
|
576
|
+
return this.#backend.generator.getBlocked(raw);
|
|
577
|
+
for (const value of this.#backend.config.blocklist)
|
|
578
|
+
if (value === raw)
|
|
579
|
+
return [value, undefined];
|
|
580
|
+
return undefined;
|
|
581
|
+
}
|
|
582
|
+
/** generator.ts:2304-2314: cached tokens that emit the same output as `raw`. */
|
|
583
|
+
getCachedEquivalentTokens(raw) {
|
|
584
|
+
if (this.#backend.kind === "ts")
|
|
585
|
+
return this.#backend.generator.getCachedEquivalentTokens(raw);
|
|
586
|
+
const target = this.#cache.get(raw);
|
|
587
|
+
if (!target?.length)
|
|
588
|
+
return [raw];
|
|
589
|
+
const escape = this.#host.core?.toEscapedSelector ?? toEscapedSelector;
|
|
590
|
+
const names = new Set([raw]);
|
|
591
|
+
for (const [name, utils] of this.#cache) {
|
|
592
|
+
if (utils && !name.endsWith(" -") && sameStringifiedUtils(utils, name, target, raw, escape))
|
|
593
|
+
names.add(name);
|
|
594
|
+
}
|
|
595
|
+
return [...names];
|
|
596
|
+
}
|
|
597
|
+
/** The TypeScript generator behind the tooling members, created on first call; a TS backend returns itself. */
|
|
598
|
+
async [ENGINE_MATERIALIZE]() {
|
|
599
|
+
if (this.#backend.kind === "ts")
|
|
600
|
+
return this.#backend.generator;
|
|
601
|
+
return this.#tsFallback.generator("materialize");
|
|
602
|
+
}
|
|
603
|
+
#tsSync(member) {
|
|
604
|
+
if (this.#backend.kind === "ts")
|
|
605
|
+
return this.#backend.generator;
|
|
606
|
+
const ts = this.#tsFallback.peek();
|
|
607
|
+
if (!ts)
|
|
608
|
+
throw tsNotReadyError(member);
|
|
609
|
+
return ts;
|
|
610
|
+
}
|
|
611
|
+
async #tsAsync(member) {
|
|
612
|
+
if (this.#backend.kind === "ts")
|
|
613
|
+
return this.#backend.generator;
|
|
614
|
+
return this.#tsFallback.generator(member);
|
|
615
|
+
}
|
|
616
|
+
/** generator.ts:2105. */
|
|
617
|
+
makeContext(raw, applied) {
|
|
618
|
+
return this.#tsSync("makeContext").makeContext(raw, applied);
|
|
619
|
+
}
|
|
620
|
+
/** generator.ts:2809. */
|
|
621
|
+
constructCustomCSS(context, body, overrideSelector) {
|
|
622
|
+
return this.#tsSync("constructCustomCSS").constructCustomCSS(context, body, overrideSelector);
|
|
623
|
+
}
|
|
624
|
+
/** generator.ts:3160. */
|
|
625
|
+
stringifyUtil(parsed, context) {
|
|
626
|
+
return this.#tsSync("stringifyUtil").stringifyUtil(parsed, context);
|
|
627
|
+
}
|
|
628
|
+
/** generator.ts:2635. */
|
|
629
|
+
async matchVariants(raw, current) {
|
|
630
|
+
return (await this.#tsAsync("matchVariants")).matchVariants(raw, current);
|
|
631
|
+
}
|
|
632
|
+
/** generator.ts:2866. */
|
|
633
|
+
async parseUtil(input, context, internal = false, shortcutPrefix) {
|
|
634
|
+
return (await this.#tsAsync("parseUtil")).parseUtil(input, context, internal, shortcutPrefix);
|
|
635
|
+
}
|
|
636
|
+
/** generator.ts:3208. */
|
|
637
|
+
async expandShortcut(input, context, depth = 5) {
|
|
638
|
+
return (await this.#tsAsync("expandShortcut")).expandShortcut(input, context, depth);
|
|
639
|
+
}
|
|
640
|
+
/** generator.ts:3409. */
|
|
641
|
+
async stringifyShortcuts(parent, context, expanded, meta, matchedPrefixes) {
|
|
642
|
+
return (await this.#tsAsync("stringifyShortcuts")).stringifyShortcuts(parent, context, expanded, meta, matchedPrefixes);
|
|
643
|
+
}
|
|
644
|
+
/** generator.ts:1976-1980: a falsy config keeps the current one. */
|
|
645
|
+
async setConfig(userConfig, defaults) {
|
|
646
|
+
if (userConfig)
|
|
647
|
+
this.commitConfig(await this.prepareConfig(userConfig, defaults));
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* generator.ts:1983-2008: a candidate generator for the configuration, without touching this
|
|
651
|
+
* one. A configuration Rust cannot serve gets a TypeScript candidate.
|
|
652
|
+
*/
|
|
653
|
+
async prepareConfig(userConfig, defaults) {
|
|
654
|
+
await Promise.resolve();
|
|
655
|
+
const version = ++this.#configVersion;
|
|
656
|
+
const nextDefaults = defaults ?? this.defaults;
|
|
657
|
+
const translated = await translateConfig(userConfig, nextDefaults);
|
|
658
|
+
let candidate;
|
|
659
|
+
if (translated.ok) {
|
|
660
|
+
const engine = new Engine();
|
|
661
|
+
engine.loadProject(translated.request);
|
|
662
|
+
candidate = new FacadeGenerator({ engine, request: translated.request, presets: translated.presets, transformers: translated.transformers, userConfig, defaults: nextDefaults, host: this.#host, transformer: this.#transformer });
|
|
663
|
+
engineStats().rust++;
|
|
664
|
+
}
|
|
665
|
+
else {
|
|
666
|
+
this.#reportFallback(translated);
|
|
667
|
+
const ts = await this.#tsFallback.create(userConfig, nextDefaults);
|
|
668
|
+
candidate = new FacadeGenerator({ ts, userConfig, defaults: nextDefaults, host: this.#host });
|
|
669
|
+
}
|
|
670
|
+
const prepared = Object.freeze({ config: candidate.config, defaults: nextDefaults, generator: candidate, userConfig, version });
|
|
671
|
+
this.#prepared.add(prepared);
|
|
672
|
+
const held = candidate.#backend.kind === "rust" ? { kind: "rust", engine: candidate.#backend.engine, committed: false } : { kind: "ts", committed: false };
|
|
673
|
+
candidate.#held = held;
|
|
674
|
+
FacadeGenerator.#registry.register(candidate, held);
|
|
675
|
+
return prepared;
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* generator.ts:2010-2035: switch to the prepared backend if no newer request superseded it. A
|
|
679
|
+
* superseded Rust candidate is disposed; so is the previous Engine once it is replaced.
|
|
680
|
+
*/
|
|
681
|
+
commitConfig(prepared) {
|
|
682
|
+
if (!this.#prepared.delete(prepared))
|
|
683
|
+
return false;
|
|
684
|
+
const candidate = prepared.generator;
|
|
685
|
+
if (prepared.version !== this.#configVersion) {
|
|
686
|
+
candidate.#disposeIfRust();
|
|
687
|
+
return false;
|
|
688
|
+
}
|
|
689
|
+
if (candidate.config !== prepared.config)
|
|
690
|
+
return false;
|
|
691
|
+
const previous = this.#backend;
|
|
692
|
+
this.defaults = prepared.defaults;
|
|
693
|
+
this.userConfig = prepared.userConfig;
|
|
694
|
+
this.#clearRuntimeCaches();
|
|
695
|
+
this.#backend = candidate.#backend;
|
|
696
|
+
if (candidate.#held)
|
|
697
|
+
candidate.#held.committed = true;
|
|
698
|
+
this.#allTokens = null;
|
|
699
|
+
this.#tsFallback.invalidate(prepared.userConfig, prepared.defaults);
|
|
700
|
+
if (previous.kind === "rust" && (this.#backend.kind !== "rust" || previous.engine !== this.#backend.engine))
|
|
701
|
+
disposeEngine(previous.engine);
|
|
702
|
+
this.#dispatchConfig(this.config);
|
|
703
|
+
return true;
|
|
704
|
+
}
|
|
705
|
+
/** An explicit `engine: "rust"` asked for Rust, so its fallback is a warning. */
|
|
706
|
+
#reportFallback(failure) {
|
|
707
|
+
const explicit = this.defaults?.engine === "rust" || this.userConfig?.engine === "rust";
|
|
708
|
+
reportFallback(failure.reason, failure.path, this.#host, explicit ? "warn" : "info");
|
|
709
|
+
engineStats().ts++;
|
|
710
|
+
}
|
|
711
|
+
#disposeIfRust() {
|
|
712
|
+
if (this.#backend.kind === "rust")
|
|
713
|
+
disposeEngine(this.#backend.engine);
|
|
714
|
+
if (this.#held)
|
|
715
|
+
this.#held.committed = true;
|
|
716
|
+
}
|
|
717
|
+
/** generator.ts:1671-1680. */
|
|
718
|
+
#clearRuntimeCaches() {
|
|
719
|
+
this.#cache.clear();
|
|
720
|
+
this.#blocked.clear();
|
|
721
|
+
this.#parentOrders.clear();
|
|
722
|
+
this.#activatedRules.clear();
|
|
723
|
+
this.#warnedUnmatched.clear();
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* generator.ts:1649-1671: only own listener slots are called, a result's `then` is read once,
|
|
727
|
+
* and a throwing store or observer is reported, never rethrown.
|
|
728
|
+
*/
|
|
729
|
+
#dispatchConfig(config) {
|
|
730
|
+
const errors = [];
|
|
731
|
+
const append = (error) => Object.defineProperty(errors, errors.length, { configurable: true, enumerable: true, value: error, writable: true });
|
|
732
|
+
try {
|
|
733
|
+
const store = this.events.events;
|
|
734
|
+
const listeners = Array.from(ownArrayValues(Object.hasOwn(store, "config") ? store.config : undefined));
|
|
735
|
+
for (const listener of listeners) {
|
|
736
|
+
try {
|
|
737
|
+
const pending = snapshotThenable(listener(config));
|
|
738
|
+
if (pending)
|
|
739
|
+
void Promise.resolve(pending).catch((error) => this.#reportObserverErrors([error]));
|
|
740
|
+
}
|
|
741
|
+
catch (error) {
|
|
742
|
+
append(error);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
catch (error) {
|
|
747
|
+
append(error);
|
|
748
|
+
}
|
|
749
|
+
if (errors.length)
|
|
750
|
+
this.#reportObserverErrors(errors);
|
|
751
|
+
}
|
|
752
|
+
/** generator.ts:1638-1643. */
|
|
753
|
+
#reportObserverErrors(errors) {
|
|
754
|
+
try {
|
|
755
|
+
console.error("[@teacss/core] Configuration observer failed:", new AggregateError(errors, "Configuration observers failed"));
|
|
756
|
+
}
|
|
757
|
+
catch { }
|
|
758
|
+
}
|
|
759
|
+
/** A changed module invalidates the `allTokens` snapshot. */
|
|
760
|
+
#updateModule(id, code) {
|
|
761
|
+
if (this.#backend.kind !== "rust")
|
|
762
|
+
return [];
|
|
763
|
+
const result = this.#backend.engine.updateModule(id, code);
|
|
764
|
+
if (result.changed)
|
|
765
|
+
this.#allTokens = null;
|
|
766
|
+
return result.tokens;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
export async function createGenerator(config = {}, defaults = {}, host = {}) {
|
|
770
|
+
const translated = await translateConfig(config, defaults);
|
|
771
|
+
if (!translated.ok)
|
|
772
|
+
return { ok: false, reason: translated.reason, path: translated.path };
|
|
773
|
+
const engine = new Engine();
|
|
774
|
+
engine.loadProject(translated.request);
|
|
775
|
+
return { ok: true, generator: new FacadeGenerator({ engine, request: translated.request, presets: translated.presets, transformers: translated.transformers, userConfig: config, defaults, host }) };
|
|
776
|
+
}
|
|
777
|
+
//# sourceMappingURL=generator.js.map
|