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