@swiftuijs/twill 0.1.2 → 0.3.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/README.md +57 -14
- package/bin/run.mjs +15 -0
- package/bin/twill.mjs +34 -11
- package/dist/branches.d.ts +6 -3
- package/dist/cli.js +13 -6
- package/dist/cli.js.map +1 -1
- package/dist/compilation-cache-7SugqJ4n.js +172 -0
- package/dist/compilation-cache-7SugqJ4n.js.map +1 -0
- package/dist/compilation-cache.d.ts +21 -0
- package/dist/{compiler-Bi9TNOhX.js → compiler-CbNSBUI2.js} +583 -64
- package/dist/compiler-CbNSBUI2.js.map +1 -0
- package/dist/compiler.d.ts +17 -2
- package/dist/config.d.ts +1 -6
- package/dist/config.js +1 -54
- package/dist/configuration-BVMf6Q_j.js +149 -0
- package/dist/configuration-BVMf6Q_j.js.map +1 -0
- package/dist/configuration.d.ts +12 -0
- package/dist/defer-helper.d.ts +1 -0
- package/dist/defer.d.ts +2 -2
- package/dist/doctor.js +9 -5
- package/dist/doctor.js.map +1 -1
- package/dist/editor.d.ts +2 -0
- package/dist/editor.js +75 -4
- package/dist/editor.js.map +1 -1
- package/dist/enums.d.ts +27 -0
- package/dist/esbuild.js +1 -1
- package/dist/extensions-B1xTkoYP.js +9 -0
- package/dist/extensions-B1xTkoYP.js.map +1 -0
- package/dist/extensions.d.ts +2 -0
- package/dist/{files-D1mERoxc.js → files-BW9geU0_.js} +2 -2
- package/dist/files-BW9geU0_.js.map +1 -0
- package/dist/index.js +2 -1
- package/dist/loader-compiler-D1teTrXw.js +19 -0
- package/dist/loader-compiler-D1teTrXw.js.map +1 -0
- package/dist/loader-compiler.d.ts +4 -0
- package/dist/loader.d.ts +4 -0
- package/dist/loader.js +29 -22
- package/dist/loader.js.map +1 -1
- package/dist/parser.d.ts +2 -0
- package/dist/{plugin-BGabvyT6.js → plugin-BHfiQ2L-.js} +41 -12
- package/dist/plugin-BHfiQ2L-.js.map +1 -0
- package/dist/project.js +39 -11
- package/dist/project.js.map +1 -1
- package/dist/recovery.d.ts +2 -2
- package/dist/rollup.js +1 -1
- package/dist/rspack.js +1 -1
- package/dist/syntax.d.ts +2 -0
- package/dist/syntax.js +1 -1
- package/dist/{transpile-DwVekVFM.js → transpile-D8P8Lsbx.js} +2 -2
- package/dist/{transpile-DwVekVFM.js.map → transpile-D8P8Lsbx.js.map} +1 -1
- package/dist/typescript-plugin.cjs +783 -82
- package/dist/typescript-plugin.cjs.map +1 -1
- package/dist/vite.js +1 -1
- package/dist/webpack.js +1 -1
- package/package.json +8 -4
- package/schemas/twill.schema.json +6 -0
- package/dist/compiler-Bi9TNOhX.js.map +0 -1
- package/dist/config.js.map +0 -1
- package/dist/files-D1mERoxc.js.map +0 -1
- package/dist/plugin-BGabvyT6.js.map +0 -1
|
@@ -2,7 +2,7 @@ import MagicString from "magic-string";
|
|
|
2
2
|
import { Parser, tokTypes } from "acorn";
|
|
3
3
|
import { tsPlugin } from "acorn-typescript";
|
|
4
4
|
import jsx from "acorn-jsx";
|
|
5
|
-
import { GREATEST_LOWER_BOUND, TraceMap, generatedPositionFor, originalPositionFor } from "@jridgewell/trace-mapping";
|
|
5
|
+
import { GREATEST_LOWER_BOUND, TraceMap, decodedMappings, encodedMappings, generatedPositionFor, originalPositionFor } from "@jridgewell/trace-mapping";
|
|
6
6
|
//#region src/parser.js
|
|
7
7
|
var options = {
|
|
8
8
|
ecmaVersion: "latest",
|
|
@@ -80,7 +80,8 @@ function exits(node) {
|
|
|
80
80
|
case "BreakStatement":
|
|
81
81
|
case "ContinueStatement": return true;
|
|
82
82
|
case "BlockStatement": return node.body.some(exits);
|
|
83
|
-
case "IfStatement":
|
|
83
|
+
case "IfStatement":
|
|
84
|
+
case "TwillIfBinding": return !!node.alternate && exits(node.consequent) && exits(node.alternate);
|
|
84
85
|
default: return false;
|
|
85
86
|
}
|
|
86
87
|
}
|
|
@@ -174,17 +175,83 @@ function moveNodes(node, delta, locate) {
|
|
|
174
175
|
}
|
|
175
176
|
}
|
|
176
177
|
var parsers = /* @__PURE__ */ new Map();
|
|
178
|
+
function associatedEnumAt(input, offset) {
|
|
179
|
+
try {
|
|
180
|
+
const tokens = Parser.tokenizer(input.slice(offset), { ecmaVersion: "latest" });
|
|
181
|
+
tokens.getToken();
|
|
182
|
+
const next = tokens.getToken();
|
|
183
|
+
return next.value === "<" || next.type === tokTypes.braceL && tokens.getToken().type === tokTypes._case && tokens.getToken().type === tokTypes.name;
|
|
184
|
+
} catch {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
177
188
|
function parserFor(language) {
|
|
178
189
|
if (parsers.has(language)) return parsers.get(language);
|
|
179
190
|
const Base = bases[language];
|
|
180
191
|
class TwillParser extends Base {
|
|
192
|
+
declareName(name, bindingType, position) {
|
|
193
|
+
if (bindingType === 6 || bindingType === 7) {
|
|
194
|
+
const scope = this.currentScope();
|
|
195
|
+
(scope.types ??= []).push(name);
|
|
196
|
+
if (this.inModule && scope.flags & 1) delete this.undefinedExports[name];
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
super.declareName(name, bindingType, position);
|
|
200
|
+
}
|
|
181
201
|
skipHeader = null;
|
|
182
202
|
closures = [];
|
|
183
203
|
lineStarts = null;
|
|
184
204
|
classSuperDepth = null;
|
|
185
205
|
subscriptDepth = 0;
|
|
206
|
+
conditionDepth = null;
|
|
186
207
|
derivedClassElement = false;
|
|
187
208
|
implicitClosure = null;
|
|
209
|
+
tsParseEnumDeclaration(node, modifiers = {}) {
|
|
210
|
+
if (!associatedEnumAt(this.input, this.end)) return super.tsParseEnumDeclaration(node, modifiers);
|
|
211
|
+
if (modifiers.const || modifiers.declare || this.isAmbientContext) this.raise(node.start, "Associated-value enums cannot be const or ambient declarations.");
|
|
212
|
+
this.expectContextual("enum");
|
|
213
|
+
node.id = this.parseIdent();
|
|
214
|
+
this.checkLValSimple(node.id, 2);
|
|
215
|
+
node.typeParameters = this.tsTryParseTypeParameters(() => {
|
|
216
|
+
if (this.type === tokTypes._in || this.isContextual("out") && this.lookahead().type === tokTypes.name) this.raise(this.start, "Associated-value enum type parameters cannot have const/in/out modifiers.");
|
|
217
|
+
});
|
|
218
|
+
const constant = node.typeParameters?.params.find((parameter) => parameter.const);
|
|
219
|
+
if (constant) this.raise(constant.start, "Associated-value enum type parameters cannot have const/in/out modifiers.");
|
|
220
|
+
node.braceStart = this.start;
|
|
221
|
+
this.expect(tokTypes.braceL);
|
|
222
|
+
node.cases = [];
|
|
223
|
+
const names = /* @__PURE__ */ new Set();
|
|
224
|
+
while (this.type !== tokTypes.braceR) {
|
|
225
|
+
const branch = this.startNode();
|
|
226
|
+
this.expect(tokTypes._case);
|
|
227
|
+
branch.id = this.parseIdent();
|
|
228
|
+
if (names.has(branch.id.name)) this.raise(branch.id.start, "Duplicate enum case.");
|
|
229
|
+
names.add(branch.id.name);
|
|
230
|
+
branch.params = [];
|
|
231
|
+
branch.hasParens = this.eat(tokTypes.parenL);
|
|
232
|
+
if (branch.hasParens) {
|
|
233
|
+
const fields = /* @__PURE__ */ new Set();
|
|
234
|
+
while (this.type !== tokTypes.parenR) {
|
|
235
|
+
const field = this.parseIdent();
|
|
236
|
+
this.checkLValSimple(field);
|
|
237
|
+
if (field.name === "kind") this.raise(field.start, "Enum payload fields cannot use the discriminator name kind.");
|
|
238
|
+
if (fields.has(field.name)) this.raise(field.start, "Duplicate enum payload field.");
|
|
239
|
+
fields.add(field.name);
|
|
240
|
+
if (this.type !== tokTypes.colon) this.raise(this.start, "Enum payload fields require a name and type; optional/default/rest fields are unsupported.");
|
|
241
|
+
field.typeAnnotation = this.tsParseTypeAnnotation();
|
|
242
|
+
branch.params.push(field);
|
|
243
|
+
if (!this.eat(tokTypes.comma)) break;
|
|
244
|
+
}
|
|
245
|
+
this.expect(tokTypes.parenR);
|
|
246
|
+
}
|
|
247
|
+
branch.valueEnd = this.lastTokEnd;
|
|
248
|
+
this.semicolon();
|
|
249
|
+
node.cases.push(this.finishNode(branch, "TwillEnumCase"));
|
|
250
|
+
}
|
|
251
|
+
if (!node.cases.length) this.raise(node.start, "An associated-value enum needs at least one case.");
|
|
252
|
+
this.next();
|
|
253
|
+
return this.finishNode(node, "TwillEnumDeclaration");
|
|
254
|
+
}
|
|
188
255
|
locate(offset) {
|
|
189
256
|
if (!this.lineStarts) {
|
|
190
257
|
this.lineStarts = [0];
|
|
@@ -223,7 +290,11 @@ function parserFor(language) {
|
|
|
223
290
|
node.cleanup = cleanup;
|
|
224
291
|
return this.finishNode(node, "DeferStatement");
|
|
225
292
|
}
|
|
226
|
-
if (!this.isContextual("guard") || !guardAt(this.input, this.end, !!this.implicitClosure))
|
|
293
|
+
if (!this.isContextual("guard") || !guardAt(this.input, this.end, !!this.implicitClosure)) {
|
|
294
|
+
const statement = super.parseStatement(context, ...args);
|
|
295
|
+
if (context && statement.type === "TwillEnumDeclaration") this.raise(statement.start, "An associated-value enum requires a block; add braces around this body.");
|
|
296
|
+
return statement;
|
|
297
|
+
}
|
|
227
298
|
const node = this.startNode();
|
|
228
299
|
node.singleStatement = !!context;
|
|
229
300
|
this.next();
|
|
@@ -243,6 +314,36 @@ function parserFor(language) {
|
|
|
243
314
|
if (!exits(node.failure)) this.raise(node.failure.start, "Every guard else path must exit with return, throw, break or continue.");
|
|
244
315
|
return this.finishNode(node, "GuardStatement");
|
|
245
316
|
}
|
|
317
|
+
parseIfStatement(node) {
|
|
318
|
+
if (/^\s*\(/.test(this.input.slice(this.end))) return super.parseIfStatement(node);
|
|
319
|
+
let binding;
|
|
320
|
+
try {
|
|
321
|
+
binding = Parser.tokenizer(this.input.slice(this.end), options).getToken().type;
|
|
322
|
+
} catch {}
|
|
323
|
+
if (binding !== tokTypes._const) return super.parseIfStatement(node);
|
|
324
|
+
this.next();
|
|
325
|
+
const declaration = this.startNode();
|
|
326
|
+
this.next();
|
|
327
|
+
this.enterScope(0);
|
|
328
|
+
const previous = this.conditionDepth;
|
|
329
|
+
this.conditionDepth = this.subscriptDepth;
|
|
330
|
+
try {
|
|
331
|
+
this.parseVar(declaration, false, "const");
|
|
332
|
+
} finally {
|
|
333
|
+
this.conditionDepth = previous;
|
|
334
|
+
}
|
|
335
|
+
node.binding = this.finishNode(declaration, "VariableDeclaration");
|
|
336
|
+
if (node.binding.declarations.length !== 1) this.raise(node.start, "if const requires one binding declaration.");
|
|
337
|
+
if (this.type !== tokTypes.braceL) this.raise(this.start, "if const requires a braced success branch.");
|
|
338
|
+
node.consequent = this.parseBlock(false);
|
|
339
|
+
this.exitScope();
|
|
340
|
+
node.alternate = null;
|
|
341
|
+
if (this.eat(tokTypes._else)) {
|
|
342
|
+
if (this.type !== tokTypes.braceL && this.type !== tokTypes._if) this.raise(this.start, "if const else requires a block or another if.");
|
|
343
|
+
node.alternate = this.parseStatement("if");
|
|
344
|
+
}
|
|
345
|
+
return this.finishNode(node, "TwillIfBinding");
|
|
346
|
+
}
|
|
246
347
|
tsParseTypeParameter(...args) {
|
|
247
348
|
const start = this.start;
|
|
248
349
|
const loc = this.startLoc;
|
|
@@ -314,12 +415,21 @@ function parserFor(language) {
|
|
|
314
415
|
}
|
|
315
416
|
return super.parseExprAtom(...args);
|
|
316
417
|
}
|
|
317
|
-
parseSwitchExpression() {
|
|
318
|
-
const node = this.startNode();
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
418
|
+
parseSwitchExpression(call) {
|
|
419
|
+
const node = call ? this.startNodeAt(call.start, call.loc.start) : this.startNode();
|
|
420
|
+
node.keyword = call ? "match" : "switch";
|
|
421
|
+
if (call) {
|
|
422
|
+
if (call.arguments.length !== 1 || call.arguments[0].type === "SpreadElement") this.raise(call.start, "A match expression requires one subject; parenthesize comma expressions.");
|
|
423
|
+
const opening = Parser.tokenizer(this.input.slice(call.callee.end), options).getToken();
|
|
424
|
+
node.parenStart = call.callee.end + opening.start;
|
|
425
|
+
node.discriminant = call.arguments[0];
|
|
426
|
+
node.parenEnd = call.end - 1;
|
|
427
|
+
} else {
|
|
428
|
+
this.next();
|
|
429
|
+
node.parenStart = this.start;
|
|
430
|
+
node.discriminant = this.parseParenExpression();
|
|
431
|
+
node.parenEnd = this.lastTokStart;
|
|
432
|
+
}
|
|
323
433
|
this.expect(tokTypes.braceL);
|
|
324
434
|
node.cases = [];
|
|
325
435
|
let mode, discriminator, sawDefault = false;
|
|
@@ -333,6 +443,34 @@ function parserFor(language) {
|
|
|
333
443
|
if (sawDefault) this.raise(branch.start, "Multiple default clauses.");
|
|
334
444
|
sawDefault = true;
|
|
335
445
|
branch.test = null;
|
|
446
|
+
} else if (call && this.type !== tokTypes.braceL || !call && this.type.label === "name" && this.value === "enum" && !this.containsEsc) {
|
|
447
|
+
if (mode === "value") this.raise(branch.start, "Cannot mix enum patterns and value cases.");
|
|
448
|
+
if (discriminator !== void 0 && discriminator !== "kind") this.raise(branch.start, "Enum patterns require the kind discriminator.");
|
|
449
|
+
mode = "pattern";
|
|
450
|
+
discriminator = "kind";
|
|
451
|
+
const pattern = this.startNode();
|
|
452
|
+
pattern.explicitKeyword = !call;
|
|
453
|
+
if (!call) this.next();
|
|
454
|
+
let reference = this.parseIdent();
|
|
455
|
+
while (this.eat(tokTypes.dot)) {
|
|
456
|
+
const member = this.startNodeAt(reference.start, reference.loc.start);
|
|
457
|
+
member.object = reference;
|
|
458
|
+
member.property = this.parseIdent(true);
|
|
459
|
+
member.computed = false;
|
|
460
|
+
member.optional = false;
|
|
461
|
+
reference = this.finishNode(member, "MemberExpression");
|
|
462
|
+
}
|
|
463
|
+
if (reference.type !== "MemberExpression") this.raise(reference.start, "An enum pattern requires a qualified case reference.");
|
|
464
|
+
pattern.reference = reference;
|
|
465
|
+
pattern.parenStart = this.start;
|
|
466
|
+
this.expect(tokTypes.parenL);
|
|
467
|
+
if (this.type === tokTypes.braceL) {
|
|
468
|
+
pattern.binding = this.parseObj(true);
|
|
469
|
+
this.checkLValPattern(pattern.binding, 2, false);
|
|
470
|
+
}
|
|
471
|
+
pattern.parenEnd = this.start;
|
|
472
|
+
this.expect(tokTypes.parenR);
|
|
473
|
+
branch.enumPattern = this.finishNode(pattern, "TwillEnumPattern");
|
|
336
474
|
} else if (this.type === tokTypes.braceL) {
|
|
337
475
|
if (mode === "value") this.raise(branch.start, "Cannot mix object patterns and value cases.");
|
|
338
476
|
mode = "pattern";
|
|
@@ -378,7 +516,7 @@ function parserFor(language) {
|
|
|
378
516
|
node.cases.push(this.finishNode(branch, "TwillSwitchCase"));
|
|
379
517
|
}
|
|
380
518
|
this.next();
|
|
381
|
-
if (!node.cases.length) this.raise(node.start,
|
|
519
|
+
if (!node.cases.length) this.raise(node.start, `A ${node.keyword} expression requires at least one arm.`);
|
|
382
520
|
node.discriminator = discriminator;
|
|
383
521
|
return this.finishNode(node, "TwillSwitchExpression");
|
|
384
522
|
}
|
|
@@ -441,6 +579,13 @@ function parserFor(language) {
|
|
|
441
579
|
}
|
|
442
580
|
parseSubscript(base, startPos, startLoc, noCalls, maybeAsyncArrow, optionalChained, forInit) {
|
|
443
581
|
const classBody = this.classSuperDepth !== null && this.subscriptDepth === this.classSuperDepth + 1;
|
|
582
|
+
if (!noCalls && !classBody && this.type === tokTypes.braceL && base.type === "CallExpression" && !base.optional && !base.typeParameters && base.callee.type === "Identifier" && base.callee.name === "match" && base.callee.start === base.start && this.input.slice(base.callee.start, base.callee.end) === "match") {
|
|
583
|
+
let first;
|
|
584
|
+
try {
|
|
585
|
+
first = Parser.tokenizer(this.input.slice(this.end), options).getToken().type;
|
|
586
|
+
} catch {}
|
|
587
|
+
if (first === tokTypes._case || first === tokTypes._default) return this.parseSwitchExpression(base);
|
|
588
|
+
}
|
|
444
589
|
if (!noCalls && !classBody && this.tsMatchLeftRelational?.() && this.start === base.end) {
|
|
445
590
|
const parameters = this.tsTryParseAndCatch(() => {
|
|
446
591
|
const parsed = this.tsParseTypeArgumentsInExpression();
|
|
@@ -459,6 +604,7 @@ function parserFor(language) {
|
|
|
459
604
|
"MemberExpression",
|
|
460
605
|
"TSInstantiationExpression"
|
|
461
606
|
].includes(base.type);
|
|
607
|
+
if (this.conditionDepth !== null && this.subscriptDepth === this.conditionDepth + 1 && this.type === tokTypes.braceL) return base;
|
|
462
608
|
if (!noCalls && supported && this.type === tokTypes.braceL && !classBody) {
|
|
463
609
|
if (base.trailing) this.raise(this.start, "Additional trailing closures require labels (for example completion: { ... }).");
|
|
464
610
|
if (base.type !== "CallExpression" && /[\r\n]/.test(this.input.slice(base.end, this.start))) return base;
|
|
@@ -512,18 +658,23 @@ function parse(source, language = "ts", sourceType = "module") {
|
|
|
512
658
|
const live = /* @__PURE__ */ new Set();
|
|
513
659
|
const calls = [];
|
|
514
660
|
const guards = [];
|
|
661
|
+
const ifBindings = [];
|
|
515
662
|
const defers = [];
|
|
516
663
|
const switches = [];
|
|
664
|
+
const enums = [];
|
|
517
665
|
const visit = (node) => {
|
|
518
666
|
if (!node || typeof node !== "object") return;
|
|
519
667
|
if (node.type === "ArrowFunctionExpression" || node.type === "Identifier" && node.start === node.end && node.name === "__twillImplicit") live.add(node);
|
|
520
668
|
if (node.trailing) calls.push(node);
|
|
521
669
|
if (node.type === "GuardStatement") guards.push(node);
|
|
670
|
+
if (node.type === "TwillIfBinding") ifBindings.push(node);
|
|
522
671
|
if (node.type === "DeferStatement") defers.push(node);
|
|
523
672
|
if (node.type === "TwillSwitchExpression") switches.push(node);
|
|
524
|
-
|
|
673
|
+
if (node.type === "TwillEnumDeclaration") enums.push(node);
|
|
674
|
+
for (const key of Object.keys(node)) {
|
|
525
675
|
if (key === "trailing" || key === "loc") continue;
|
|
526
|
-
|
|
676
|
+
const value = node[key];
|
|
677
|
+
if (Array.isArray(value)) for (const child of value) visit(child);
|
|
527
678
|
else if (value?.type) visit(value);
|
|
528
679
|
}
|
|
529
680
|
};
|
|
@@ -532,8 +683,10 @@ function parse(source, language = "ts", sourceType = "module") {
|
|
|
532
683
|
ast,
|
|
533
684
|
calls,
|
|
534
685
|
guards,
|
|
686
|
+
ifBindings,
|
|
535
687
|
defers,
|
|
536
688
|
switches,
|
|
689
|
+
enums,
|
|
537
690
|
comments,
|
|
538
691
|
closures: parser.closures.filter((closure) => live.has(closure.node)).map((closure) => ({
|
|
539
692
|
...closure,
|
|
@@ -542,6 +695,11 @@ function parse(source, language = "ts", sourceType = "module") {
|
|
|
542
695
|
};
|
|
543
696
|
}
|
|
544
697
|
//#endregion
|
|
698
|
+
//#region src/defer-helper.ts
|
|
699
|
+
function syncDeferBody(names) {
|
|
700
|
+
return "\n let " + names.failed + " = false,\n " + names.failure + ";\n while (" + names.stack + "?.length) {\n const " + names.cleanup + " = " + names.stack + ".pop();\n if (" + names.cleanup + ") {\n try {\n " + names.cleanup + "();\n } catch (" + names.error + ") {\n " + names.failed + " = true;\n " + names.failure + " = " + names.error + ";\n }\n }\n }\n if (" + names.failed + ") throw " + names.failure + ";\n";
|
|
701
|
+
}
|
|
702
|
+
//#endregion
|
|
545
703
|
//#region src/defer.ts
|
|
546
704
|
var functions = /* @__PURE__ */ new Set([
|
|
547
705
|
"FunctionDeclaration",
|
|
@@ -549,16 +707,16 @@ var functions = /* @__PURE__ */ new Set([
|
|
|
549
707
|
"ArrowFunctionExpression"
|
|
550
708
|
]);
|
|
551
709
|
function children(node, visit) {
|
|
552
|
-
for (const
|
|
710
|
+
for (const key of Object.keys(node)) {
|
|
553
711
|
if (key === "loc" || key === "trailing") continue;
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
else if (value?.type) visit(value);
|
|
712
|
+
const value = node[key];
|
|
713
|
+
if (Array.isArray(value)) {
|
|
714
|
+
for (const child of value) if (child?.type) visit(child);
|
|
715
|
+
} else if (value?.type) visit(value);
|
|
558
716
|
}
|
|
559
717
|
}
|
|
560
|
-
/**
|
|
561
|
-
function lowerDefers(source, ast, code, language, usedNames, contentStarts, comments, fail) {
|
|
718
|
+
/** Native finally where scope permits; callbacks retain dynamic registration semantics. */
|
|
719
|
+
function lowerDefers(source, ast, code, language, usedNames, contentStarts, comments, runtime, sourceType, fail) {
|
|
562
720
|
const scopes = [];
|
|
563
721
|
const visit = (node, scope, parent) => {
|
|
564
722
|
if (functions.has(node.type) || [
|
|
@@ -607,10 +765,40 @@ function lowerDefers(source, ast, code, language, usedNames, contentStarts, comm
|
|
|
607
765
|
}
|
|
608
766
|
return start;
|
|
609
767
|
};
|
|
768
|
+
let sharedHelper;
|
|
610
769
|
for (const scope of scopes) {
|
|
611
770
|
if (!scope.defers.length) continue;
|
|
612
771
|
const { block } = scope;
|
|
613
772
|
const statements = block.body;
|
|
773
|
+
const first = statements.indexOf(scope.defers[0]);
|
|
774
|
+
let native = first >= 0 && scope.defers.every((node) => !node.awaited && statements.includes(node));
|
|
775
|
+
if (native) {
|
|
776
|
+
native = !statements.some((node, index) => node.type === "FunctionDeclaration" || node.type === "TSDeclareFunction" || node.type === "VariableDeclaration" && node.kind.endsWith("using") || index > first && (node.type === "VariableDeclaration" && node.kind !== "var" || node.type.endsWith("Declaration") && node.type !== "VariableDeclaration" || node.type === "GuardStatement" && node.binding));
|
|
777
|
+
const check = (node) => {
|
|
778
|
+
if (node.type === "WithStatement" || node.type === "ThisExpression" || node.type === "VariableDeclarator" && !node.init || node.type === "VariableDeclaration" && node.kind === "var" && (!statements.includes(node) || node.start > scope.defers[0].start) || node.type === "CallExpression" && node.callee.type === "Identifier" && node.callee.name === "eval") native = false;
|
|
779
|
+
children(node, check);
|
|
780
|
+
};
|
|
781
|
+
if (native) check(block);
|
|
782
|
+
const cleanupScope = (node) => {
|
|
783
|
+
if (node.type === "FunctionDeclaration" || node.directive || node.type === "VariableDeclaration" && node.kind === "var") native = false;
|
|
784
|
+
if (!functions.has(node.type)) children(node, cleanupScope);
|
|
785
|
+
};
|
|
786
|
+
if (native) for (const node of scope.defers) cleanupScope(node.cleanup.body);
|
|
787
|
+
}
|
|
788
|
+
if (native) {
|
|
789
|
+
for (const node of [...scope.defers].reverse()) {
|
|
790
|
+
code.overwrite(node.start, node.cleanup.body.start, ";try {");
|
|
791
|
+
if (!node.cleanup.body.body.length) {
|
|
792
|
+
code.remove(node.cleanup.body.start, node.cleanup.body.end);
|
|
793
|
+
code.appendLeft(block.end - 1, "\n} finally " + source.slice(node.cleanup.body.start, node.cleanup.body.end) + "\n");
|
|
794
|
+
continue;
|
|
795
|
+
}
|
|
796
|
+
code.prependRight(node.cleanup.body.start, "\n} finally ");
|
|
797
|
+
code.appendLeft(node.cleanup.body.end, "\n");
|
|
798
|
+
if (node.cleanup.body.end !== block.end - 1) code.move(node.cleanup.body.start, node.cleanup.body.end, block.end - 1);
|
|
799
|
+
}
|
|
800
|
+
continue;
|
|
801
|
+
}
|
|
614
802
|
const single = scope.defers.length === 1 && statements.includes(scope.defers[0]);
|
|
615
803
|
const stack = fresh(single ? "Cleanup" : "Defers");
|
|
616
804
|
const failure = single ? "" : fresh("Failure");
|
|
@@ -631,7 +819,7 @@ function lowerDefers(source, ast, code, language, usedNames, contentStarts, comm
|
|
|
631
819
|
const declarations = scope.functionBody ? ordinary.filter((node) => node.type === "FunctionDeclaration") : [];
|
|
632
820
|
if (scope.functionBody && ordinary.some((node) => node.type === "TSDeclareFunction")) fail(ordinary.find((node) => node.type === "TSDeclareFunction"), "Function overloads/ambient function declarations in a defer scope are not supported; move them to a separate scope.");
|
|
633
821
|
const names = [...new Set(declarations.map((node) => node.id.name))];
|
|
634
|
-
code.
|
|
822
|
+
code.prependLeft(start, `;${jsdoc}let ${stack}${annotation};${names.length ? `var ${names.join(",")};` : ""}try {\n`);
|
|
635
823
|
let anchor = start;
|
|
636
824
|
const initial = /* @__PURE__ */ new Set();
|
|
637
825
|
for (const statement of ordinary) {
|
|
@@ -655,11 +843,37 @@ function lowerDefers(source, ast, code, language, usedNames, contentStarts, comm
|
|
|
655
843
|
}
|
|
656
844
|
if (single) {
|
|
657
845
|
const call = allAsync ? `if (${stack}) await ${stack}();` : `${stack}?.();`;
|
|
658
|
-
code.
|
|
846
|
+
code.appendLeft(block.end - 1, `\n} finally { ${call} }\n`);
|
|
847
|
+
continue;
|
|
848
|
+
}
|
|
849
|
+
if (!anyAsync) {
|
|
850
|
+
let body;
|
|
851
|
+
if (runtime === "external") {
|
|
852
|
+
if (sourceType !== "module") fail(scope.defers[0], "External runtime helpers require module sourceType; use runtime: inline for scripts.");
|
|
853
|
+
sharedHelper ??= fresh("RunDefers");
|
|
854
|
+
body = `${sharedHelper}(${stack});`;
|
|
855
|
+
} else body = syncDeferBody({
|
|
856
|
+
stack,
|
|
857
|
+
failed,
|
|
858
|
+
failure,
|
|
859
|
+
cleanup: callback,
|
|
860
|
+
error
|
|
861
|
+
});
|
|
862
|
+
code.appendLeft(block.end - 1, `} finally { ${body} }`);
|
|
659
863
|
continue;
|
|
660
864
|
}
|
|
661
865
|
const call = mixed ? `if (${callback}.async) await ${callback}.run(); else ${callback}.run();` : `${allAsync ? "await " : ""}${callback}();`;
|
|
662
|
-
code.
|
|
866
|
+
code.appendLeft(block.end - 1, `} finally { let ${failed} = false, ${failure}; while (${stack}?.length) { const ${callback} = ${stack}.pop(); if (${callback}) { try { ${call} } catch (${error}) { ${failed} = true; ${failure} = ${error}; } } } if (${failed}) throw ${failure}; }`);
|
|
867
|
+
}
|
|
868
|
+
if (sharedHelper) {
|
|
869
|
+
let start = ast.body.find((node) => !node.directive).start;
|
|
870
|
+
for (const comment of sortedComments) {
|
|
871
|
+
if (comment.end > start) continue;
|
|
872
|
+
if (/\S/.test(source.slice(comment.end, start))) break;
|
|
873
|
+
if ((source.startsWith("/**", comment.start) || /@ts-(?:ignore|expect-error)\b/.test(comment.value)) && !/@(?:ts-(?:check|nocheck)|jsxImportSource|jsxRuntime)\b/.test(comment.value)) start = comment.start;
|
|
874
|
+
else break;
|
|
875
|
+
}
|
|
876
|
+
code.prependLeft(start, `\nimport { runDefers as ${sharedHelper} } from '@swiftuijs/twill-runtime/helpers/v1';\n`);
|
|
663
877
|
}
|
|
664
878
|
}
|
|
665
879
|
//#endregion
|
|
@@ -683,22 +897,68 @@ function suspension(node) {
|
|
|
683
897
|
}
|
|
684
898
|
}
|
|
685
899
|
}
|
|
686
|
-
/** Native switches with per-arm scopes
|
|
687
|
-
*
|
|
688
|
-
function lowerSwitchExpressions(source, ast, switches, code, language, usedNames, fail) {
|
|
900
|
+
/** Native switches with per-arm scopes. Direct returns and standalone variable
|
|
901
|
+
* initializers need no extra function; other expressions keep a lexical IIFE. */
|
|
902
|
+
function lowerSwitchExpressions(source, ast, switches, code, language, usedNames, comments, fail) {
|
|
689
903
|
const returns = /* @__PURE__ */ new Set();
|
|
690
|
-
const
|
|
904
|
+
const initializers = /* @__PURE__ */ new Map();
|
|
905
|
+
const leadingDoc = (start) => {
|
|
906
|
+
let low = 0, high = comments.length;
|
|
907
|
+
while (low < high) {
|
|
908
|
+
const middle = low + high >>> 1;
|
|
909
|
+
if (comments[middle].end <= start) low = middle + 1;
|
|
910
|
+
else high = middle;
|
|
911
|
+
}
|
|
912
|
+
const comment = comments[low - 1];
|
|
913
|
+
return comment && /^\s*$/.test(source.slice(comment.end, start)) ? comment : void 0;
|
|
914
|
+
};
|
|
915
|
+
const directEval = (node) => {
|
|
916
|
+
if ([
|
|
917
|
+
"FunctionDeclaration",
|
|
918
|
+
"FunctionExpression",
|
|
919
|
+
"ArrowFunctionExpression"
|
|
920
|
+
].includes(node.type)) return false;
|
|
921
|
+
if (node.type === "CallExpression" && node.callee.type === "Identifier" && node.callee.name === "eval" && !node.optional) return true;
|
|
922
|
+
return Object.entries(node).some(([key, value]) => ![
|
|
923
|
+
"loc",
|
|
924
|
+
"trailing",
|
|
925
|
+
"tag"
|
|
926
|
+
].includes(key) && (Array.isArray(value) ? value : [value]).some((child) => child?.type && directEval(child)));
|
|
927
|
+
};
|
|
928
|
+
const grouping = (start, end, initializer = false) => {
|
|
929
|
+
const parentheses = [];
|
|
930
|
+
const tokens = Parser.tokenizer(source.slice(start, end), { ecmaVersion: "latest" });
|
|
931
|
+
for (;;) {
|
|
932
|
+
const token = tokens.getToken();
|
|
933
|
+
if (token.type === tokTypes.eof) break;
|
|
934
|
+
if (initializer && token.type === tokTypes.eq) parentheses.length = 0;
|
|
935
|
+
if (token.type === tokTypes.parenL || token.type === tokTypes.parenR) parentheses.push(token);
|
|
936
|
+
}
|
|
937
|
+
for (const token of parentheses) code.remove(start + token.start, start + token.end);
|
|
938
|
+
};
|
|
939
|
+
const visit = (node, parent) => {
|
|
940
|
+
if (node.type === "VariableDeclaration" && node.declarations.length === 1) {
|
|
941
|
+
const declaration = node.declarations[0];
|
|
942
|
+
if (declaration.id.type === "Identifier" && declaration.init?.type === "TwillSwitchExpression" && [
|
|
943
|
+
"Program",
|
|
944
|
+
"BlockStatement",
|
|
945
|
+
"SwitchCase",
|
|
946
|
+
"ExportNamedDeclaration"
|
|
947
|
+
].includes(parent?.type ?? "")) {
|
|
948
|
+
const statement = parent?.type === "ExportNamedDeclaration" ? parent : node;
|
|
949
|
+
const comment = leadingDoc(statement.start);
|
|
950
|
+
const prefixStart = comment && source.startsWith("/**", comment.start) ? comment.start : statement.start;
|
|
951
|
+
if (!(comment && /@ts-(?:ignore|expect-error|nocheck|check)\b/.test(source.slice(comment.start, comment.end))) && !directEval(declaration.init) && (language.startsWith("ts") || prefixStart === statement.start)) initializers.set(declaration.init, {
|
|
952
|
+
declaration,
|
|
953
|
+
statement,
|
|
954
|
+
prefixStart
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
}
|
|
691
958
|
if (node.type === "ReturnStatement" && node.argument?.type === "TwillSwitchExpression") {
|
|
692
959
|
returns.add(node.argument);
|
|
693
960
|
code.remove(node.start, node.start + 6);
|
|
694
|
-
for (const [start, end] of [[node.start + 6, node.argument.start], [node.argument.end, node.end]])
|
|
695
|
-
const tokens = Parser.tokenizer(source.slice(start, end), { ecmaVersion: "latest" });
|
|
696
|
-
for (;;) {
|
|
697
|
-
const token = tokens.getToken();
|
|
698
|
-
if (token.type === tokTypes.eof) break;
|
|
699
|
-
if (token.type === tokTypes.parenL || token.type === tokTypes.parenR) code.remove(start + token.start, start + token.end);
|
|
700
|
-
}
|
|
701
|
-
}
|
|
961
|
+
for (const [start, end] of [[node.start + 6, node.argument.start], [node.argument.end, node.end]]) grouping(start, end);
|
|
702
962
|
}
|
|
703
963
|
for (const [key, value] of Object.entries(node)) {
|
|
704
964
|
if ([
|
|
@@ -706,7 +966,7 @@ function lowerSwitchExpressions(source, ast, switches, code, language, usedNames
|
|
|
706
966
|
"trailing",
|
|
707
967
|
"tag"
|
|
708
968
|
].includes(key)) continue;
|
|
709
|
-
for (const child of Array.isArray(value) ? value : [value]) if (child?.type) visit(child);
|
|
969
|
+
for (const child of Array.isArray(value) ? value : [value]) if (child?.type) visit(child, node);
|
|
710
970
|
}
|
|
711
971
|
};
|
|
712
972
|
visit(ast);
|
|
@@ -722,34 +982,268 @@ function lowerSwitchExpressions(source, ast, switches, code, language, usedNames
|
|
|
722
982
|
for (const node of switches) {
|
|
723
983
|
const direct = returns.has(node);
|
|
724
984
|
const suspended = !direct && suspension(node);
|
|
725
|
-
if (suspended) fail(suspended,
|
|
985
|
+
if (suspended) fail(suspended, `await/yield inside a ${node.keyword} expression requires a direct return. Bind the subject first, or await the whole result explicitly.`);
|
|
986
|
+
const initializer = initializers.get(node);
|
|
987
|
+
const result = initializer ? fresh("Result") : void 0;
|
|
988
|
+
const exit = initializer ? fresh("Exit") : void 0;
|
|
726
989
|
const subject = fresh("Subject");
|
|
727
|
-
|
|
990
|
+
if (initializer) {
|
|
991
|
+
const { declaration, statement, prefixStart } = initializer;
|
|
992
|
+
const annotation = language.startsWith("ts") && declaration.id.typeAnnotation ? `: typeof ${declaration.id.name}` : "";
|
|
993
|
+
grouping(declaration.id.end, node.start, true);
|
|
994
|
+
grouping(node.end, statement.end);
|
|
995
|
+
code.appendLeft(prefixStart, `let ${result}${annotation}; ${exit}: {const ${subject} = `);
|
|
996
|
+
if (prefixStart < statement.start) code.prependRight(prefixStart, "\n");
|
|
997
|
+
code.move(prefixStart, node.start, node.end);
|
|
998
|
+
code.appendRight(node.end, ` ${result}`);
|
|
999
|
+
}
|
|
1000
|
+
const valuePrefix = result ? `${result} = (` : "return ";
|
|
1001
|
+
const valueSuffix = result ? `); break ${exit};` : ";";
|
|
1002
|
+
const kind = node.cases.some((branch) => branch.enumPattern) ? fresh("Kind") : void 0;
|
|
1003
|
+
if (!initializer) code.appendLeft(node.start, `${direct ? "{" : "(() => {"}const ${subject} = `);
|
|
728
1004
|
code.remove(node.parenStart, node.parenStart + 1);
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
1005
|
+
if (node.keyword === "match") {
|
|
1006
|
+
const tokens = Parser.tokenizer(source.slice(node.discriminant.end, node.parenEnd), { ecmaVersion: "latest" });
|
|
1007
|
+
for (;;) {
|
|
1008
|
+
const token = tokens.getToken();
|
|
1009
|
+
if (token.type === tokTypes.eof) break;
|
|
1010
|
+
if (token.type === tokTypes.comma) code.remove(node.discriminant.end + token.start, node.discriminant.end + token.end);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
const keywordEnd = node.start + (node.keyword === "match" ? 5 : 6);
|
|
1014
|
+
if (node.keyword === "match") code.overwrite(node.start, keywordEnd, "switch");
|
|
1015
|
+
code.move(node.start, keywordEnd, node.parenEnd);
|
|
1016
|
+
code.appendLeft(node.parenEnd, `; ${kind ? `const ${kind} = ${subject}["kind"]; ` : ""}`);
|
|
1017
|
+
code.overwrite(node.parenEnd, node.parenEnd + 1, ` (${kind ?? subject}${kind || node.discriminator === void 0 ? "" : `[${JSON.stringify(node.discriminator)}]`})`);
|
|
1018
|
+
for (const branch of node.cases) if (branch.enumPattern) {
|
|
1019
|
+
const { reference, binding, start, parenStart, parenEnd, explicitKeyword } = branch.enumPattern;
|
|
1020
|
+
const tag = JSON.stringify(reference.property.name);
|
|
1021
|
+
if (language.startsWith("ts")) {
|
|
1022
|
+
const witness = `(${tag} satisfies (typeof `;
|
|
1023
|
+
if (explicitKeyword) code.overwrite(start, start + 4, witness);
|
|
1024
|
+
else code.appendLeft(start, witness);
|
|
1025
|
+
code.appendLeft(reference.end, ` extends (...args: never[]) => { readonly kind: ${tag} } ? ${tag} : never))`);
|
|
1026
|
+
} else if (explicitKeyword) {
|
|
1027
|
+
code.overwrite(start, start + 4, tag);
|
|
1028
|
+
code.remove(reference.start, reference.end);
|
|
1029
|
+
} else code.overwrite(reference.start, reference.end, tag);
|
|
1030
|
+
code.remove(parenStart, parenStart + 1);
|
|
1031
|
+
code.remove(parenEnd, parenEnd + 1);
|
|
1032
|
+
if (binding) {
|
|
1033
|
+
code.appendLeft(binding.start, ": { const ");
|
|
1034
|
+
code.overwrite(branch.colonStart, branch.colonStart + 1, ` = ${subject}; ${branch.throw ? "" : valuePrefix}`);
|
|
1035
|
+
code.appendLeft(branch.valueEnd, `${branch.throw ? ";" : valueSuffix} }`);
|
|
1036
|
+
if (source[branch.end - 1] === ";") code.remove(branch.end - 1, branch.end);
|
|
1037
|
+
} else if (!branch.throw) {
|
|
1038
|
+
code.appendLeft(branch.colonStart + 1, ` ${valuePrefix}`);
|
|
1039
|
+
if (result) {
|
|
1040
|
+
code.appendLeft(branch.valueEnd, valueSuffix);
|
|
1041
|
+
if (source[branch.end - 1] === ";") code.remove(branch.end - 1, branch.end);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
} else if (branch.pattern) {
|
|
733
1045
|
const { pattern, tag } = branch;
|
|
734
1046
|
code.appendLeft(tag.value.start, fresh("Tag"));
|
|
735
1047
|
code.move(tag.value.start, tag.value.end, pattern.start);
|
|
736
1048
|
code.appendLeft(tag.value.end, ": { const ");
|
|
737
|
-
code.overwrite(branch.colonStart, branch.colonStart + 1, ` = ${subject}; ${branch.throw ? "" :
|
|
738
|
-
code.appendLeft(branch.valueEnd, "; }
|
|
1049
|
+
code.overwrite(branch.colonStart, branch.colonStart + 1, ` = ${subject}; ${branch.throw ? "" : valuePrefix}`);
|
|
1050
|
+
code.appendLeft(branch.valueEnd, `${branch.throw ? ";" : valueSuffix} }`);
|
|
739
1051
|
if (source[branch.end - 1] === ";") code.remove(branch.end - 1, branch.end);
|
|
740
|
-
} else if (!branch.throw)
|
|
741
|
-
|
|
742
|
-
|
|
1052
|
+
} else if (!branch.throw) {
|
|
1053
|
+
code.appendLeft(branch.colonStart + 1, ` ${valuePrefix}`);
|
|
1054
|
+
if (result) {
|
|
1055
|
+
code.appendLeft(branch.valueEnd, valueSuffix);
|
|
1056
|
+
if (source[branch.end - 1] === ";") code.remove(branch.end - 1, branch.end);
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
const hasDefault = node.cases.some((branch) => !branch.pattern && !branch.enumPattern && branch.test === null);
|
|
1060
|
+
const check = !hasDefault && language.startsWith("ts") ? ` ${kind ? `(0 as unknown as typeof ${kind})` : subject} satisfies never;` : "";
|
|
743
1061
|
const failure = hasDefault ? "" : `${check} throw new TypeError('Non-exhaustive switch expression');`;
|
|
744
|
-
code.overwrite(node.end - 1, node.end, `}${failure}${direct ? "}" : "})()"}
|
|
1062
|
+
code.overwrite(node.end - 1, node.end, `}${failure}${direct || initializer ? "}" : "})()"}`, { contentOnly: true });
|
|
745
1063
|
}
|
|
746
1064
|
}
|
|
747
1065
|
//#endregion
|
|
748
|
-
//#region src/
|
|
749
|
-
|
|
750
|
-
function
|
|
751
|
-
|
|
1066
|
+
//#region src/enums.ts
|
|
1067
|
+
/** Find free type references, respecting generic function/mapped/infer binders. */
|
|
1068
|
+
function references(node, names, found = /* @__PURE__ */ new Set(), bound = /* @__PURE__ */ new Set()) {
|
|
1069
|
+
const local = new Set(bound);
|
|
1070
|
+
for (const parameter of node.typeParameters?.params ?? []) local.add(parameter.name);
|
|
1071
|
+
if (node.type === "TSMappedType") local.add(node.typeParameter.name);
|
|
1072
|
+
if (node.type === "TSInferType") local.add(node.typeParameter.name);
|
|
1073
|
+
if (node.type === "TSTypeReference" && node.typeName.type === "Identifier") {
|
|
1074
|
+
const name = node.typeName.name;
|
|
1075
|
+
if (names.has(name) && !local.has(name)) found.add(name);
|
|
1076
|
+
}
|
|
1077
|
+
for (const [key, value] of Object.entries(node)) {
|
|
1078
|
+
if (["loc", "typeName"].includes(key)) continue;
|
|
1079
|
+
for (const child of Array.isArray(value) ? value : [value]) {
|
|
1080
|
+
if (!child?.type) continue;
|
|
1081
|
+
const childBound = new Set(local);
|
|
1082
|
+
if (node.type === "TSConditionalType" && key === "trueType") {
|
|
1083
|
+
const bind = (part) => {
|
|
1084
|
+
if (part.type === "TSInferType") childBound.add(part.typeParameter.name);
|
|
1085
|
+
for (const [property, nested] of Object.entries(part)) {
|
|
1086
|
+
if (property === "loc") continue;
|
|
1087
|
+
if (part.type === "TSConditionalType" && property === "extendsType") continue;
|
|
1088
|
+
for (const item of Array.isArray(nested) ? nested : [nested]) if (item?.type) bind(item);
|
|
1089
|
+
}
|
|
1090
|
+
};
|
|
1091
|
+
bind(node.extendsType);
|
|
1092
|
+
}
|
|
1093
|
+
references(child, names, found, childBound);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
return found;
|
|
1097
|
+
}
|
|
1098
|
+
function enumTypeParameters(node, branch) {
|
|
1099
|
+
const parameters = node.typeParameters?.params ?? [];
|
|
1100
|
+
const names = new Set(parameters.map((parameter) => parameter.name));
|
|
1101
|
+
const used = /* @__PURE__ */ new Set();
|
|
1102
|
+
for (const field of branch.params) references(field.typeAnnotation, names, used);
|
|
1103
|
+
for (let size = -1; size !== used.size;) {
|
|
1104
|
+
size = used.size;
|
|
1105
|
+
for (const parameter of parameters) {
|
|
1106
|
+
if (!used.has(parameter.name)) continue;
|
|
1107
|
+
if (parameter.constraint) references(parameter.constraint, names, used);
|
|
1108
|
+
if (parameter.default) references(parameter.default, names, used);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
return parameters.filter((parameter) => used.has(parameter.name));
|
|
752
1112
|
}
|
|
1113
|
+
/** Tagged records and precise factories; no application runtime is introduced. */
|
|
1114
|
+
function lowerEnums(source, ast, enums, code) {
|
|
1115
|
+
const insertions = [];
|
|
1116
|
+
const exported = /* @__PURE__ */ new Set();
|
|
1117
|
+
const visit = (node) => {
|
|
1118
|
+
if (node.type === "ExportNamedDeclaration") exported.add(node.declaration);
|
|
1119
|
+
for (const [key, value] of Object.entries(node)) {
|
|
1120
|
+
if (["loc", "trailing"].includes(key)) continue;
|
|
1121
|
+
for (const child of Array.isArray(value) ? value : [value]) if (child?.type) visit(child);
|
|
1122
|
+
}
|
|
1123
|
+
};
|
|
1124
|
+
visit(ast);
|
|
1125
|
+
for (const node of enums) {
|
|
1126
|
+
const replacement = {
|
|
1127
|
+
anchor: node.braceStart,
|
|
1128
|
+
before: false,
|
|
1129
|
+
text: "= ",
|
|
1130
|
+
copies: []
|
|
1131
|
+
};
|
|
1132
|
+
for (const [index, branch] of node.cases.entries()) {
|
|
1133
|
+
if (index) replacement.text += " | ";
|
|
1134
|
+
replacement.text += `{ readonly kind: ${JSON.stringify(branch.id.name)}; `;
|
|
1135
|
+
for (const field of branch.params) {
|
|
1136
|
+
replacement.text += "readonly ";
|
|
1137
|
+
const text = source.slice(field.start, field.typeAnnotation.end);
|
|
1138
|
+
replacement.copies.push({
|
|
1139
|
+
start: replacement.text.length,
|
|
1140
|
+
sourceStart: field.start,
|
|
1141
|
+
length: text.length
|
|
1142
|
+
});
|
|
1143
|
+
replacement.text += text + "; ";
|
|
1144
|
+
}
|
|
1145
|
+
replacement.text += "}";
|
|
1146
|
+
}
|
|
1147
|
+
replacement.text += `; ${exported.has(node) ? "export " : ""}const `;
|
|
1148
|
+
replacement.copies.push({
|
|
1149
|
+
start: replacement.text.length,
|
|
1150
|
+
sourceStart: node.id.start,
|
|
1151
|
+
length: node.id.end - node.id.start
|
|
1152
|
+
});
|
|
1153
|
+
replacement.text += source.slice(node.id.start, node.id.end) + " = {";
|
|
1154
|
+
insertions.push(replacement);
|
|
1155
|
+
code.overwrite(node.start, node.start + 4, "type");
|
|
1156
|
+
code.overwrite(node.braceStart, node.braceStart + 1, replacement.text);
|
|
1157
|
+
for (const branch of node.cases) {
|
|
1158
|
+
code.remove(branch.start, branch.start + 4);
|
|
1159
|
+
const parameters = enumTypeParameters(node, branch);
|
|
1160
|
+
const copied = {
|
|
1161
|
+
anchor: branch.id.end,
|
|
1162
|
+
before: true,
|
|
1163
|
+
text: parameters.length ? "<" : "",
|
|
1164
|
+
copies: []
|
|
1165
|
+
};
|
|
1166
|
+
for (const [index, parameter] of parameters.entries()) {
|
|
1167
|
+
if (index) copied.text += ", ";
|
|
1168
|
+
const text = source.slice(parameter.start, parameter.end);
|
|
1169
|
+
copied.copies.push({
|
|
1170
|
+
start: copied.text.length,
|
|
1171
|
+
sourceStart: parameter.start,
|
|
1172
|
+
length: text.length
|
|
1173
|
+
});
|
|
1174
|
+
copied.text += text;
|
|
1175
|
+
}
|
|
1176
|
+
if (parameters.length) copied.text += ">";
|
|
1177
|
+
const generic = copied.text;
|
|
1178
|
+
if (generic) insertions.push(copied);
|
|
1179
|
+
code.appendLeft(branch.id.end, generic + (branch.hasParens ? "" : "()"));
|
|
1180
|
+
const fields = branch.params.map((field) => field.name);
|
|
1181
|
+
code.appendLeft(branch.valueEnd, `{ return { kind: ${JSON.stringify(branch.id.name)}${fields.length ? ", " + fields.join(", ") : ""} } as const; },`);
|
|
1182
|
+
if (source[branch.end - 1] === ";") code.remove(branch.end - 1, branch.end);
|
|
1183
|
+
}
|
|
1184
|
+
code.appendLeft(node.end, ";");
|
|
1185
|
+
}
|
|
1186
|
+
return insertions;
|
|
1187
|
+
}
|
|
1188
|
+
/** Generated type copies map to their actual source tokens, including .d.ts maps. */
|
|
1189
|
+
function mapEnumCopies(source, generated, map, insertions) {
|
|
1190
|
+
const starts = (text) => {
|
|
1191
|
+
const result = [0];
|
|
1192
|
+
for (let index = 0; index < text.length; index++) if (text[index] === "\n") result.push(index + 1);
|
|
1193
|
+
return result;
|
|
1194
|
+
};
|
|
1195
|
+
const originalLines = starts(source), generatedLines = starts(generated);
|
|
1196
|
+
const position = (lines, offset) => {
|
|
1197
|
+
let line = 0;
|
|
1198
|
+
while (line + 1 < lines.length && lines[line + 1] <= offset) line++;
|
|
1199
|
+
return {
|
|
1200
|
+
line,
|
|
1201
|
+
column: offset - lines[line]
|
|
1202
|
+
};
|
|
1203
|
+
};
|
|
1204
|
+
const trace = new TraceMap(map);
|
|
1205
|
+
const decoded = decodedMappings(trace);
|
|
1206
|
+
const copies = [];
|
|
1207
|
+
for (const insertion of insertions) {
|
|
1208
|
+
const anchor = position(originalLines, insertion.anchor);
|
|
1209
|
+
const location = generatedPositionFor(trace, {
|
|
1210
|
+
source: map.sources[0],
|
|
1211
|
+
line: anchor.line + 1,
|
|
1212
|
+
column: anchor.column
|
|
1213
|
+
});
|
|
1214
|
+
const offset = generatedLines[location.line - 1] + location.column - (insertion.before ? insertion.text.length : 0);
|
|
1215
|
+
if (generated.slice(offset, offset + insertion.text.length) !== insertion.text) throw new Error("Enum mapping anchor does not match generated text.");
|
|
1216
|
+
for (const copy of insertion.copies) {
|
|
1217
|
+
copies.push({
|
|
1218
|
+
generatedStart: offset + copy.start,
|
|
1219
|
+
sourceStart: copy.sourceStart,
|
|
1220
|
+
length: copy.length
|
|
1221
|
+
});
|
|
1222
|
+
for (let index = 0; index < copy.length; index++) {
|
|
1223
|
+
const target = position(generatedLines, offset + copy.start + index);
|
|
1224
|
+
const original = position(originalLines, copy.sourceStart + index);
|
|
1225
|
+
const line = decoded[target.line];
|
|
1226
|
+
const entry = [
|
|
1227
|
+
target.column,
|
|
1228
|
+
0,
|
|
1229
|
+
original.line,
|
|
1230
|
+
original.column
|
|
1231
|
+
];
|
|
1232
|
+
const existing = line.findIndex((segment) => segment[0] === target.column);
|
|
1233
|
+
if (existing >= 0) line[existing] = entry;
|
|
1234
|
+
else line.push(entry);
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
for (const line of decoded) line.sort((left, right) => left[0] - right[0]);
|
|
1239
|
+
map.mappings = encodedMappings(new TraceMap({
|
|
1240
|
+
...JSON.parse(map.toString()),
|
|
1241
|
+
mappings: decoded
|
|
1242
|
+
}));
|
|
1243
|
+
return copies;
|
|
1244
|
+
}
|
|
1245
|
+
//#endregion
|
|
1246
|
+
//#region src/compiler.ts
|
|
753
1247
|
function inferLanguage(filename) {
|
|
754
1248
|
if (/\.(?:twillx|tsx)$/.test(filename)) return "tsx";
|
|
755
1249
|
if (/\.jsx$/.test(filename)) return "jsx";
|
|
@@ -776,12 +1270,12 @@ var TwillSyntaxError = class extends SyntaxError {
|
|
|
776
1270
|
};
|
|
777
1271
|
function walk(node, visit) {
|
|
778
1272
|
visit(node);
|
|
779
|
-
for (const
|
|
780
|
-
if (
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
else if (value?.type) walk(value, visit);
|
|
1273
|
+
for (const key of Object.keys(node)) {
|
|
1274
|
+
if (key === "loc" || key === "trailing") continue;
|
|
1275
|
+
const value = node[key];
|
|
1276
|
+
if (Array.isArray(value)) {
|
|
1277
|
+
for (const child of value) if (child?.type) walk(child, visit);
|
|
1278
|
+
} else if (value?.type) walk(value, visit);
|
|
785
1279
|
}
|
|
786
1280
|
}
|
|
787
1281
|
function calleeName(node) {
|
|
@@ -792,6 +1286,7 @@ function calleeName(node) {
|
|
|
792
1286
|
}
|
|
793
1287
|
}
|
|
794
1288
|
function transform(source, options = {}) {
|
|
1289
|
+
if (options.runtime !== void 0 && !["inline", "external"].includes(options.runtime)) throw new TypeError("runtime must be inline or external");
|
|
795
1290
|
const filename = options.filename ?? "input.twill";
|
|
796
1291
|
let parsed;
|
|
797
1292
|
try {
|
|
@@ -800,11 +1295,28 @@ function transform(source, options = {}) {
|
|
|
800
1295
|
throw new TwillSyntaxError(source, filename, error);
|
|
801
1296
|
}
|
|
802
1297
|
const code = new MagicString(source);
|
|
1298
|
+
const enumInsertions = parsed.enums.length ? lowerEnums(source, parsed.ast, parsed.enums, code) : [];
|
|
803
1299
|
const usedNames = /* @__PURE__ */ new Set();
|
|
804
|
-
if (parsed.guards.filter((guard) => guard.binding && guard.binding.declarations[0].id.type !== "Identifier").length || parsed.switches.length || parsed.defers.length || parsed.closures.some((closure) => closure.implicitMembers.length) || /\.twillx$/.test(filename.split(/[?#]/, 1)[0])) walk(parsed.ast, (node) => {
|
|
805
|
-
if (node.type === "Identifier") usedNames.add(node.name);
|
|
1300
|
+
if (parsed.guards.filter((guard) => guard.binding && guard.binding.declarations[0].id.type !== "Identifier").length || parsed.ifBindings.length || parsed.switches.length || parsed.defers.length || parsed.closures.some((closure) => closure.implicitMembers.length) || /\.twillx$/.test(filename.split(/[?#]/, 1)[0])) walk(parsed.ast, (node) => {
|
|
1301
|
+
if (node.type === "Identifier" || node.type === "JSXIdentifier") usedNames.add(node.name);
|
|
806
1302
|
});
|
|
807
1303
|
let guardCounter = 0;
|
|
1304
|
+
const ifBindingStarts = new Set(parsed.ifBindings.map((node) => node.consequent.start + 1));
|
|
1305
|
+
for (const statement of parsed.ifBindings) {
|
|
1306
|
+
const binding = statement.binding.declarations[0].id;
|
|
1307
|
+
let temporary;
|
|
1308
|
+
do
|
|
1309
|
+
temporary = `__twillIf${guardCounter++}`;
|
|
1310
|
+
while (usedNames.has(temporary));
|
|
1311
|
+
usedNames.add(temporary);
|
|
1312
|
+
const patternEnd = binding.typeAnnotation?.start ?? binding.end;
|
|
1313
|
+
code.overwrite(statement.start, binding.start, `{const ${temporary}`);
|
|
1314
|
+
code.move(binding.start, patternEnd, statement.consequent.start + 1);
|
|
1315
|
+
code.prependRight(binding.start, "const ");
|
|
1316
|
+
code.appendLeft(patternEnd, ` = ${temporary};`);
|
|
1317
|
+
code.prependLeft(statement.consequent.start, `; if (${temporary} !== null && ${temporary} !== void 0) `);
|
|
1318
|
+
code.appendLeft(statement.end, "}");
|
|
1319
|
+
}
|
|
808
1320
|
for (const guard of parsed.guards) {
|
|
809
1321
|
if (guard.singleStatement) {
|
|
810
1322
|
code.prependLeft(guard.start, "{");
|
|
@@ -1011,13 +1523,15 @@ function transform(source, options = {}) {
|
|
|
1011
1523
|
const collect = (statement) => {
|
|
1012
1524
|
switch (statement.type) {
|
|
1013
1525
|
case "ExpressionStatement":
|
|
1014
|
-
code.
|
|
1526
|
+
if (ifBindingStarts.has(statement.start)) code.appendRight(statement.start, `${collector}.push(`);
|
|
1527
|
+
else code.prependLeft(statement.start, `${collector}.push(`);
|
|
1015
1528
|
code.appendLeft(source[statement.end - 1] === ";" ? statement.end - 1 : statement.end, ")");
|
|
1016
1529
|
break;
|
|
1017
1530
|
case "BlockStatement":
|
|
1018
1531
|
statement.body.forEach(collect);
|
|
1019
1532
|
break;
|
|
1020
1533
|
case "IfStatement":
|
|
1534
|
+
case "TwillIfBinding":
|
|
1021
1535
|
collect(statement.consequent);
|
|
1022
1536
|
if (statement.alternate) collect(statement.alternate);
|
|
1023
1537
|
break;
|
|
@@ -1061,7 +1575,7 @@ function transform(source, options = {}) {
|
|
|
1061
1575
|
if (component) code.prependLeft(closures.at(-1).end, (vue ? "})}" : directChild || metadataByNode.get(closures[0]).header ? ")}" : ")()}") + `</${calleeName(node.callee)}>)`);
|
|
1062
1576
|
else code.appendLeft(closures.at(-1).end, ")");
|
|
1063
1577
|
}
|
|
1064
|
-
if (parsed.switches.length) lowerSwitchExpressions(source, parsed.ast, parsed.switches, code, options.language ?? inferLanguage(filename), usedNames, (node, message) => {
|
|
1578
|
+
if (parsed.switches.length) lowerSwitchExpressions(source, parsed.ast, parsed.switches, code, options.language ?? inferLanguage(filename), usedNames, parsed.comments, (node, message) => {
|
|
1065
1579
|
throw new TwillSyntaxError(source, filename, {
|
|
1066
1580
|
message,
|
|
1067
1581
|
pos: node.start,
|
|
@@ -1072,7 +1586,7 @@ function transform(source, options = {}) {
|
|
|
1072
1586
|
if (parsed.defers.length) {
|
|
1073
1587
|
const starts = /* @__PURE__ */ new Map();
|
|
1074
1588
|
for (const metadata of parsed.closures) if (metadata.header) starts.set(metadata.node.body, metadata.header.end);
|
|
1075
|
-
lowerDefers(source, parsed.ast, code, options.language ?? inferLanguage(filename), usedNames, starts, parsed.comments, (node, message) => {
|
|
1589
|
+
lowerDefers(source, parsed.ast, code, options.language ?? inferLanguage(filename), usedNames, starts, parsed.comments, options.runtime ?? "inline", options.sourceType ?? "module", (node, message) => {
|
|
1076
1590
|
throw new TwillSyntaxError(source, filename, {
|
|
1077
1591
|
message,
|
|
1078
1592
|
pos: node.start,
|
|
@@ -1086,14 +1600,19 @@ function transform(source, options = {}) {
|
|
|
1086
1600
|
includeContent: true,
|
|
1087
1601
|
hires: true
|
|
1088
1602
|
});
|
|
1603
|
+
const enumCopies = enumInsertions.length ? mapEnumCopies(source, code.toString(), map, enumInsertions) : [];
|
|
1089
1604
|
return {
|
|
1090
1605
|
code: code.toString(),
|
|
1091
1606
|
map,
|
|
1092
1607
|
changed: code.hasChanged(),
|
|
1093
1608
|
closures: parsed.closures.length,
|
|
1094
1609
|
guards: parsed.guards.length,
|
|
1610
|
+
ifBindings: parsed.ifBindings.length,
|
|
1095
1611
|
defers: parsed.defers.length,
|
|
1096
1612
|
switches: parsed.switches.length,
|
|
1613
|
+
associatedEnums: parsed.enums,
|
|
1614
|
+
enumPatterns: parsed.switches.flatMap((node) => node.cases.flatMap((branch) => branch.enumPattern ? [branch.enumPattern] : [])),
|
|
1615
|
+
enumCopies,
|
|
1097
1616
|
componentProps
|
|
1098
1617
|
};
|
|
1099
1618
|
}
|
|
@@ -1122,6 +1641,6 @@ function generatedPosition(result, line, column) {
|
|
|
1122
1641
|
});
|
|
1123
1642
|
}
|
|
1124
1643
|
//#endregion
|
|
1125
|
-
export {
|
|
1644
|
+
export { transform as a, originalPosition as i, generatedPosition as n, parse as o, inferLanguage as r, TwillSyntaxError as t };
|
|
1126
1645
|
|
|
1127
|
-
//# sourceMappingURL=compiler-
|
|
1646
|
+
//# sourceMappingURL=compiler-CbNSBUI2.js.map
|