@swiftuijs/twill 0.1.1 → 0.2.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 +48 -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-CuCGz02_.js +172 -0
- package/dist/compilation-cache-CuCGz02_.js.map +1 -0
- package/dist/compilation-cache.d.ts +21 -0
- package/dist/{compiler-Bi9TNOhX.js → compiler-DY1N07np.js} +530 -50
- package/dist/compiler-DY1N07np.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-CTJjSu_h.js +139 -0
- package/dist/configuration-CTJjSu_h.js.map +1 -0
- package/dist/configuration.d.ts +12 -0
- package/dist/defer-helper.d.ts +1 -0
- package/dist/defer.d.ts +1 -1
- package/dist/doctor.js +10 -6
- 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-DIw9aw5n.js +19 -0
- package/dist/loader-compiler-DIw9aw5n.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-Bks2hxCJ.js} +41 -12
- package/dist/plugin-Bks2hxCJ.js.map +1 -0
- package/dist/project.js +27 -3
- package/dist/project.js.map +1 -1
- 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-DG81yfVs.js} +2 -2
- package/dist/{transpile-DwVekVFM.js.map → transpile-DG81yfVs.js.map} +1 -1
- package/dist/typescript-plugin.cjs +718 -60
- package/dist/typescript-plugin.cjs.map +1 -1
- package/dist/vite.js +1 -1
- package/dist/webpack.js +1 -1
- package/package.json +9 -5
- 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,6 +175,16 @@ 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];
|
|
@@ -183,8 +194,55 @@ function parserFor(language) {
|
|
|
183
194
|
lineStarts = null;
|
|
184
195
|
classSuperDepth = null;
|
|
185
196
|
subscriptDepth = 0;
|
|
197
|
+
conditionDepth = null;
|
|
186
198
|
derivedClassElement = false;
|
|
187
199
|
implicitClosure = null;
|
|
200
|
+
tsParseEnumDeclaration(node, modifiers = {}) {
|
|
201
|
+
if (!associatedEnumAt(this.input, this.end)) return super.tsParseEnumDeclaration(node, modifiers);
|
|
202
|
+
if (modifiers.const || modifiers.declare || this.isAmbientContext) this.raise(node.start, "Associated-value enums cannot be const or ambient declarations.");
|
|
203
|
+
this.expectContextual("enum");
|
|
204
|
+
node.id = this.parseIdent();
|
|
205
|
+
this.checkLValSimple(node.id, 2);
|
|
206
|
+
node.typeParameters = this.tsTryParseTypeParameters(() => {
|
|
207
|
+
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.");
|
|
208
|
+
});
|
|
209
|
+
const constant = node.typeParameters?.params.find((parameter) => parameter.const);
|
|
210
|
+
if (constant) this.raise(constant.start, "Associated-value enum type parameters cannot have const/in/out modifiers.");
|
|
211
|
+
node.braceStart = this.start;
|
|
212
|
+
this.expect(tokTypes.braceL);
|
|
213
|
+
node.cases = [];
|
|
214
|
+
const names = /* @__PURE__ */ new Set();
|
|
215
|
+
while (this.type !== tokTypes.braceR) {
|
|
216
|
+
const branch = this.startNode();
|
|
217
|
+
this.expect(tokTypes._case);
|
|
218
|
+
branch.id = this.parseIdent();
|
|
219
|
+
if (names.has(branch.id.name)) this.raise(branch.id.start, "Duplicate enum case.");
|
|
220
|
+
names.add(branch.id.name);
|
|
221
|
+
branch.params = [];
|
|
222
|
+
branch.hasParens = this.eat(tokTypes.parenL);
|
|
223
|
+
if (branch.hasParens) {
|
|
224
|
+
const fields = /* @__PURE__ */ new Set();
|
|
225
|
+
while (this.type !== tokTypes.parenR) {
|
|
226
|
+
const field = this.parseIdent();
|
|
227
|
+
this.checkLValSimple(field);
|
|
228
|
+
if (field.name === "kind") this.raise(field.start, "Enum payload fields cannot use the discriminator name kind.");
|
|
229
|
+
if (fields.has(field.name)) this.raise(field.start, "Duplicate enum payload field.");
|
|
230
|
+
fields.add(field.name);
|
|
231
|
+
if (this.type !== tokTypes.colon) this.raise(this.start, "Enum payload fields require a name and type; optional/default/rest fields are unsupported.");
|
|
232
|
+
field.typeAnnotation = this.tsParseTypeAnnotation();
|
|
233
|
+
branch.params.push(field);
|
|
234
|
+
if (!this.eat(tokTypes.comma)) break;
|
|
235
|
+
}
|
|
236
|
+
this.expect(tokTypes.parenR);
|
|
237
|
+
}
|
|
238
|
+
branch.valueEnd = this.lastTokEnd;
|
|
239
|
+
this.semicolon();
|
|
240
|
+
node.cases.push(this.finishNode(branch, "TwillEnumCase"));
|
|
241
|
+
}
|
|
242
|
+
if (!node.cases.length) this.raise(node.start, "An associated-value enum needs at least one case.");
|
|
243
|
+
this.next();
|
|
244
|
+
return this.finishNode(node, "TwillEnumDeclaration");
|
|
245
|
+
}
|
|
188
246
|
locate(offset) {
|
|
189
247
|
if (!this.lineStarts) {
|
|
190
248
|
this.lineStarts = [0];
|
|
@@ -223,7 +281,11 @@ function parserFor(language) {
|
|
|
223
281
|
node.cleanup = cleanup;
|
|
224
282
|
return this.finishNode(node, "DeferStatement");
|
|
225
283
|
}
|
|
226
|
-
if (!this.isContextual("guard") || !guardAt(this.input, this.end, !!this.implicitClosure))
|
|
284
|
+
if (!this.isContextual("guard") || !guardAt(this.input, this.end, !!this.implicitClosure)) {
|
|
285
|
+
const statement = super.parseStatement(context, ...args);
|
|
286
|
+
if (context && statement.type === "TwillEnumDeclaration") this.raise(statement.start, "An associated-value enum requires a block; add braces around this body.");
|
|
287
|
+
return statement;
|
|
288
|
+
}
|
|
227
289
|
const node = this.startNode();
|
|
228
290
|
node.singleStatement = !!context;
|
|
229
291
|
this.next();
|
|
@@ -243,6 +305,36 @@ function parserFor(language) {
|
|
|
243
305
|
if (!exits(node.failure)) this.raise(node.failure.start, "Every guard else path must exit with return, throw, break or continue.");
|
|
244
306
|
return this.finishNode(node, "GuardStatement");
|
|
245
307
|
}
|
|
308
|
+
parseIfStatement(node) {
|
|
309
|
+
if (/^\s*\(/.test(this.input.slice(this.end))) return super.parseIfStatement(node);
|
|
310
|
+
let binding;
|
|
311
|
+
try {
|
|
312
|
+
binding = Parser.tokenizer(this.input.slice(this.end), options).getToken().type;
|
|
313
|
+
} catch {}
|
|
314
|
+
if (binding !== tokTypes._const) return super.parseIfStatement(node);
|
|
315
|
+
this.next();
|
|
316
|
+
const declaration = this.startNode();
|
|
317
|
+
this.next();
|
|
318
|
+
this.enterScope(0);
|
|
319
|
+
const previous = this.conditionDepth;
|
|
320
|
+
this.conditionDepth = this.subscriptDepth;
|
|
321
|
+
try {
|
|
322
|
+
this.parseVar(declaration, false, "const");
|
|
323
|
+
} finally {
|
|
324
|
+
this.conditionDepth = previous;
|
|
325
|
+
}
|
|
326
|
+
node.binding = this.finishNode(declaration, "VariableDeclaration");
|
|
327
|
+
if (node.binding.declarations.length !== 1) this.raise(node.start, "if const requires one binding declaration.");
|
|
328
|
+
if (this.type !== tokTypes.braceL) this.raise(this.start, "if const requires a braced success branch.");
|
|
329
|
+
node.consequent = this.parseBlock(false);
|
|
330
|
+
this.exitScope();
|
|
331
|
+
node.alternate = null;
|
|
332
|
+
if (this.eat(tokTypes._else)) {
|
|
333
|
+
if (this.type !== tokTypes.braceL && this.type !== tokTypes._if) this.raise(this.start, "if const else requires a block or another if.");
|
|
334
|
+
node.alternate = this.parseStatement("if");
|
|
335
|
+
}
|
|
336
|
+
return this.finishNode(node, "TwillIfBinding");
|
|
337
|
+
}
|
|
246
338
|
tsParseTypeParameter(...args) {
|
|
247
339
|
const start = this.start;
|
|
248
340
|
const loc = this.startLoc;
|
|
@@ -314,12 +406,21 @@ function parserFor(language) {
|
|
|
314
406
|
}
|
|
315
407
|
return super.parseExprAtom(...args);
|
|
316
408
|
}
|
|
317
|
-
parseSwitchExpression() {
|
|
318
|
-
const node = this.startNode();
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
409
|
+
parseSwitchExpression(call) {
|
|
410
|
+
const node = call ? this.startNodeAt(call.start, call.loc.start) : this.startNode();
|
|
411
|
+
node.keyword = call ? "match" : "switch";
|
|
412
|
+
if (call) {
|
|
413
|
+
if (call.arguments.length !== 1 || call.arguments[0].type === "SpreadElement") this.raise(call.start, "A match expression requires one subject; parenthesize comma expressions.");
|
|
414
|
+
const opening = Parser.tokenizer(this.input.slice(call.callee.end), options).getToken();
|
|
415
|
+
node.parenStart = call.callee.end + opening.start;
|
|
416
|
+
node.discriminant = call.arguments[0];
|
|
417
|
+
node.parenEnd = call.end - 1;
|
|
418
|
+
} else {
|
|
419
|
+
this.next();
|
|
420
|
+
node.parenStart = this.start;
|
|
421
|
+
node.discriminant = this.parseParenExpression();
|
|
422
|
+
node.parenEnd = this.lastTokStart;
|
|
423
|
+
}
|
|
323
424
|
this.expect(tokTypes.braceL);
|
|
324
425
|
node.cases = [];
|
|
325
426
|
let mode, discriminator, sawDefault = false;
|
|
@@ -333,6 +434,34 @@ function parserFor(language) {
|
|
|
333
434
|
if (sawDefault) this.raise(branch.start, "Multiple default clauses.");
|
|
334
435
|
sawDefault = true;
|
|
335
436
|
branch.test = null;
|
|
437
|
+
} else if (call && this.type !== tokTypes.braceL || !call && this.type.label === "name" && this.value === "enum" && !this.containsEsc) {
|
|
438
|
+
if (mode === "value") this.raise(branch.start, "Cannot mix enum patterns and value cases.");
|
|
439
|
+
if (discriminator !== void 0 && discriminator !== "kind") this.raise(branch.start, "Enum patterns require the kind discriminator.");
|
|
440
|
+
mode = "pattern";
|
|
441
|
+
discriminator = "kind";
|
|
442
|
+
const pattern = this.startNode();
|
|
443
|
+
pattern.explicitKeyword = !call;
|
|
444
|
+
if (!call) this.next();
|
|
445
|
+
let reference = this.parseIdent();
|
|
446
|
+
while (this.eat(tokTypes.dot)) {
|
|
447
|
+
const member = this.startNodeAt(reference.start, reference.loc.start);
|
|
448
|
+
member.object = reference;
|
|
449
|
+
member.property = this.parseIdent(true);
|
|
450
|
+
member.computed = false;
|
|
451
|
+
member.optional = false;
|
|
452
|
+
reference = this.finishNode(member, "MemberExpression");
|
|
453
|
+
}
|
|
454
|
+
if (reference.type !== "MemberExpression") this.raise(reference.start, "An enum pattern requires a qualified case reference.");
|
|
455
|
+
pattern.reference = reference;
|
|
456
|
+
pattern.parenStart = this.start;
|
|
457
|
+
this.expect(tokTypes.parenL);
|
|
458
|
+
if (this.type === tokTypes.braceL) {
|
|
459
|
+
pattern.binding = this.parseObj(true);
|
|
460
|
+
this.checkLValPattern(pattern.binding, 2, false);
|
|
461
|
+
}
|
|
462
|
+
pattern.parenEnd = this.start;
|
|
463
|
+
this.expect(tokTypes.parenR);
|
|
464
|
+
branch.enumPattern = this.finishNode(pattern, "TwillEnumPattern");
|
|
336
465
|
} else if (this.type === tokTypes.braceL) {
|
|
337
466
|
if (mode === "value") this.raise(branch.start, "Cannot mix object patterns and value cases.");
|
|
338
467
|
mode = "pattern";
|
|
@@ -378,7 +507,7 @@ function parserFor(language) {
|
|
|
378
507
|
node.cases.push(this.finishNode(branch, "TwillSwitchCase"));
|
|
379
508
|
}
|
|
380
509
|
this.next();
|
|
381
|
-
if (!node.cases.length) this.raise(node.start,
|
|
510
|
+
if (!node.cases.length) this.raise(node.start, `A ${node.keyword} expression requires at least one arm.`);
|
|
382
511
|
node.discriminator = discriminator;
|
|
383
512
|
return this.finishNode(node, "TwillSwitchExpression");
|
|
384
513
|
}
|
|
@@ -441,6 +570,13 @@ function parserFor(language) {
|
|
|
441
570
|
}
|
|
442
571
|
parseSubscript(base, startPos, startLoc, noCalls, maybeAsyncArrow, optionalChained, forInit) {
|
|
443
572
|
const classBody = this.classSuperDepth !== null && this.subscriptDepth === this.classSuperDepth + 1;
|
|
573
|
+
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") {
|
|
574
|
+
let first;
|
|
575
|
+
try {
|
|
576
|
+
first = Parser.tokenizer(this.input.slice(this.end), options).getToken().type;
|
|
577
|
+
} catch {}
|
|
578
|
+
if (first === tokTypes._case || first === tokTypes._default) return this.parseSwitchExpression(base);
|
|
579
|
+
}
|
|
444
580
|
if (!noCalls && !classBody && this.tsMatchLeftRelational?.() && this.start === base.end) {
|
|
445
581
|
const parameters = this.tsTryParseAndCatch(() => {
|
|
446
582
|
const parsed = this.tsParseTypeArgumentsInExpression();
|
|
@@ -459,6 +595,7 @@ function parserFor(language) {
|
|
|
459
595
|
"MemberExpression",
|
|
460
596
|
"TSInstantiationExpression"
|
|
461
597
|
].includes(base.type);
|
|
598
|
+
if (this.conditionDepth !== null && this.subscriptDepth === this.conditionDepth + 1 && this.type === tokTypes.braceL) return base;
|
|
462
599
|
if (!noCalls && supported && this.type === tokTypes.braceL && !classBody) {
|
|
463
600
|
if (base.trailing) this.raise(this.start, "Additional trailing closures require labels (for example completion: { ... }).");
|
|
464
601
|
if (base.type !== "CallExpression" && /[\r\n]/.test(this.input.slice(base.end, this.start))) return base;
|
|
@@ -512,15 +649,19 @@ function parse(source, language = "ts", sourceType = "module") {
|
|
|
512
649
|
const live = /* @__PURE__ */ new Set();
|
|
513
650
|
const calls = [];
|
|
514
651
|
const guards = [];
|
|
652
|
+
const ifBindings = [];
|
|
515
653
|
const defers = [];
|
|
516
654
|
const switches = [];
|
|
655
|
+
const enums = [];
|
|
517
656
|
const visit = (node) => {
|
|
518
657
|
if (!node || typeof node !== "object") return;
|
|
519
658
|
if (node.type === "ArrowFunctionExpression" || node.type === "Identifier" && node.start === node.end && node.name === "__twillImplicit") live.add(node);
|
|
520
659
|
if (node.trailing) calls.push(node);
|
|
521
660
|
if (node.type === "GuardStatement") guards.push(node);
|
|
661
|
+
if (node.type === "TwillIfBinding") ifBindings.push(node);
|
|
522
662
|
if (node.type === "DeferStatement") defers.push(node);
|
|
523
663
|
if (node.type === "TwillSwitchExpression") switches.push(node);
|
|
664
|
+
if (node.type === "TwillEnumDeclaration") enums.push(node);
|
|
524
665
|
for (const [key, value] of Object.entries(node)) {
|
|
525
666
|
if (key === "trailing" || key === "loc") continue;
|
|
526
667
|
if (Array.isArray(value)) value.forEach(visit);
|
|
@@ -532,8 +673,10 @@ function parse(source, language = "ts", sourceType = "module") {
|
|
|
532
673
|
ast,
|
|
533
674
|
calls,
|
|
534
675
|
guards,
|
|
676
|
+
ifBindings,
|
|
535
677
|
defers,
|
|
536
678
|
switches,
|
|
679
|
+
enums,
|
|
537
680
|
comments,
|
|
538
681
|
closures: parser.closures.filter((closure) => live.has(closure.node)).map((closure) => ({
|
|
539
682
|
...closure,
|
|
@@ -542,6 +685,11 @@ function parse(source, language = "ts", sourceType = "module") {
|
|
|
542
685
|
};
|
|
543
686
|
}
|
|
544
687
|
//#endregion
|
|
688
|
+
//#region src/defer-helper.ts
|
|
689
|
+
function syncDeferBody(names) {
|
|
690
|
+
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";
|
|
691
|
+
}
|
|
692
|
+
//#endregion
|
|
545
693
|
//#region src/defer.ts
|
|
546
694
|
var functions = /* @__PURE__ */ new Set([
|
|
547
695
|
"FunctionDeclaration",
|
|
@@ -558,7 +706,7 @@ function children(node, visit) {
|
|
|
558
706
|
}
|
|
559
707
|
}
|
|
560
708
|
/** Scope-local cleanup callbacks; dynamic registrations use lazy stacks. */
|
|
561
|
-
function lowerDefers(source, ast, code, language, usedNames, contentStarts, comments, fail) {
|
|
709
|
+
function lowerDefers(source, ast, code, language, usedNames, contentStarts, comments, runtime, sourceType, fail) {
|
|
562
710
|
const scopes = [];
|
|
563
711
|
const visit = (node, scope, parent) => {
|
|
564
712
|
if (functions.has(node.type) || [
|
|
@@ -607,6 +755,7 @@ function lowerDefers(source, ast, code, language, usedNames, contentStarts, comm
|
|
|
607
755
|
}
|
|
608
756
|
return start;
|
|
609
757
|
};
|
|
758
|
+
let sharedHelper;
|
|
610
759
|
for (const scope of scopes) {
|
|
611
760
|
if (!scope.defers.length) continue;
|
|
612
761
|
const { block } = scope;
|
|
@@ -631,7 +780,7 @@ function lowerDefers(source, ast, code, language, usedNames, contentStarts, comm
|
|
|
631
780
|
const declarations = scope.functionBody ? ordinary.filter((node) => node.type === "FunctionDeclaration") : [];
|
|
632
781
|
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
782
|
const names = [...new Set(declarations.map((node) => node.id.name))];
|
|
634
|
-
code.
|
|
783
|
+
code.prependLeft(start, `;${jsdoc}let ${stack}${annotation};${names.length ? `var ${names.join(",")};` : ""}try {\n`);
|
|
635
784
|
let anchor = start;
|
|
636
785
|
const initial = /* @__PURE__ */ new Set();
|
|
637
786
|
for (const statement of ordinary) {
|
|
@@ -655,11 +804,37 @@ function lowerDefers(source, ast, code, language, usedNames, contentStarts, comm
|
|
|
655
804
|
}
|
|
656
805
|
if (single) {
|
|
657
806
|
const call = allAsync ? `if (${stack}) await ${stack}();` : `${stack}?.();`;
|
|
658
|
-
code.
|
|
807
|
+
code.appendLeft(block.end - 1, `\n} finally { ${call} }\n`);
|
|
808
|
+
continue;
|
|
809
|
+
}
|
|
810
|
+
if (!anyAsync) {
|
|
811
|
+
let body;
|
|
812
|
+
if (runtime === "external") {
|
|
813
|
+
if (sourceType !== "module") fail(scope.defers[0], "External runtime helpers require module sourceType; use runtime: inline for scripts.");
|
|
814
|
+
sharedHelper ??= fresh("RunDefers");
|
|
815
|
+
body = `${sharedHelper}(${stack});`;
|
|
816
|
+
} else body = syncDeferBody({
|
|
817
|
+
stack,
|
|
818
|
+
failed,
|
|
819
|
+
failure,
|
|
820
|
+
cleanup: callback,
|
|
821
|
+
error
|
|
822
|
+
});
|
|
823
|
+
code.appendLeft(block.end - 1, `} finally { ${body} }`);
|
|
659
824
|
continue;
|
|
660
825
|
}
|
|
661
826
|
const call = mixed ? `if (${callback}.async) await ${callback}.run(); else ${callback}.run();` : `${allAsync ? "await " : ""}${callback}();`;
|
|
662
|
-
code.
|
|
827
|
+
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}; }`);
|
|
828
|
+
}
|
|
829
|
+
if (sharedHelper) {
|
|
830
|
+
let start = ast.body.find((node) => !node.directive).start;
|
|
831
|
+
for (const comment of sortedComments) {
|
|
832
|
+
if (comment.end > start) continue;
|
|
833
|
+
if (/\S/.test(source.slice(comment.end, start))) break;
|
|
834
|
+
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;
|
|
835
|
+
else break;
|
|
836
|
+
}
|
|
837
|
+
code.prependLeft(start, `\nimport { runDefers as ${sharedHelper} } from '@swiftuijs/twill-runtime/helpers/v1';\n`);
|
|
663
838
|
}
|
|
664
839
|
}
|
|
665
840
|
//#endregion
|
|
@@ -683,22 +858,68 @@ function suspension(node) {
|
|
|
683
858
|
}
|
|
684
859
|
}
|
|
685
860
|
}
|
|
686
|
-
/** Native switches with per-arm scopes
|
|
687
|
-
*
|
|
688
|
-
function lowerSwitchExpressions(source, ast, switches, code, language, usedNames, fail) {
|
|
861
|
+
/** Native switches with per-arm scopes. Direct returns and standalone variable
|
|
862
|
+
* initializers need no extra function; other expressions keep a lexical IIFE. */
|
|
863
|
+
function lowerSwitchExpressions(source, ast, switches, code, language, usedNames, comments, fail) {
|
|
689
864
|
const returns = /* @__PURE__ */ new Set();
|
|
690
|
-
const
|
|
865
|
+
const initializers = /* @__PURE__ */ new Map();
|
|
866
|
+
const leadingDoc = (start) => {
|
|
867
|
+
let low = 0, high = comments.length;
|
|
868
|
+
while (low < high) {
|
|
869
|
+
const middle = low + high >>> 1;
|
|
870
|
+
if (comments[middle].end <= start) low = middle + 1;
|
|
871
|
+
else high = middle;
|
|
872
|
+
}
|
|
873
|
+
const comment = comments[low - 1];
|
|
874
|
+
return comment && /^\s*$/.test(source.slice(comment.end, start)) ? comment : void 0;
|
|
875
|
+
};
|
|
876
|
+
const directEval = (node) => {
|
|
877
|
+
if ([
|
|
878
|
+
"FunctionDeclaration",
|
|
879
|
+
"FunctionExpression",
|
|
880
|
+
"ArrowFunctionExpression"
|
|
881
|
+
].includes(node.type)) return false;
|
|
882
|
+
if (node.type === "CallExpression" && node.callee.type === "Identifier" && node.callee.name === "eval" && !node.optional) return true;
|
|
883
|
+
return Object.entries(node).some(([key, value]) => ![
|
|
884
|
+
"loc",
|
|
885
|
+
"trailing",
|
|
886
|
+
"tag"
|
|
887
|
+
].includes(key) && (Array.isArray(value) ? value : [value]).some((child) => child?.type && directEval(child)));
|
|
888
|
+
};
|
|
889
|
+
const grouping = (start, end, initializer = false) => {
|
|
890
|
+
const parentheses = [];
|
|
891
|
+
const tokens = Parser.tokenizer(source.slice(start, end), { ecmaVersion: "latest" });
|
|
892
|
+
for (;;) {
|
|
893
|
+
const token = tokens.getToken();
|
|
894
|
+
if (token.type === tokTypes.eof) break;
|
|
895
|
+
if (initializer && token.type === tokTypes.eq) parentheses.length = 0;
|
|
896
|
+
if (token.type === tokTypes.parenL || token.type === tokTypes.parenR) parentheses.push(token);
|
|
897
|
+
}
|
|
898
|
+
for (const token of parentheses) code.remove(start + token.start, start + token.end);
|
|
899
|
+
};
|
|
900
|
+
const visit = (node, parent) => {
|
|
901
|
+
if (node.type === "VariableDeclaration" && node.declarations.length === 1) {
|
|
902
|
+
const declaration = node.declarations[0];
|
|
903
|
+
if (declaration.id.type === "Identifier" && declaration.init?.type === "TwillSwitchExpression" && [
|
|
904
|
+
"Program",
|
|
905
|
+
"BlockStatement",
|
|
906
|
+
"SwitchCase",
|
|
907
|
+
"ExportNamedDeclaration"
|
|
908
|
+
].includes(parent?.type ?? "")) {
|
|
909
|
+
const statement = parent?.type === "ExportNamedDeclaration" ? parent : node;
|
|
910
|
+
const comment = leadingDoc(statement.start);
|
|
911
|
+
const prefixStart = comment && source.startsWith("/**", comment.start) ? comment.start : statement.start;
|
|
912
|
+
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, {
|
|
913
|
+
declaration,
|
|
914
|
+
statement,
|
|
915
|
+
prefixStart
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
}
|
|
691
919
|
if (node.type === "ReturnStatement" && node.argument?.type === "TwillSwitchExpression") {
|
|
692
920
|
returns.add(node.argument);
|
|
693
921
|
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
|
-
}
|
|
922
|
+
for (const [start, end] of [[node.start + 6, node.argument.start], [node.argument.end, node.end]]) grouping(start, end);
|
|
702
923
|
}
|
|
703
924
|
for (const [key, value] of Object.entries(node)) {
|
|
704
925
|
if ([
|
|
@@ -706,7 +927,7 @@ function lowerSwitchExpressions(source, ast, switches, code, language, usedNames
|
|
|
706
927
|
"trailing",
|
|
707
928
|
"tag"
|
|
708
929
|
].includes(key)) continue;
|
|
709
|
-
for (const child of Array.isArray(value) ? value : [value]) if (child?.type) visit(child);
|
|
930
|
+
for (const child of Array.isArray(value) ? value : [value]) if (child?.type) visit(child, node);
|
|
710
931
|
}
|
|
711
932
|
};
|
|
712
933
|
visit(ast);
|
|
@@ -722,34 +943,268 @@ function lowerSwitchExpressions(source, ast, switches, code, language, usedNames
|
|
|
722
943
|
for (const node of switches) {
|
|
723
944
|
const direct = returns.has(node);
|
|
724
945
|
const suspended = !direct && suspension(node);
|
|
725
|
-
if (suspended) fail(suspended,
|
|
946
|
+
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.`);
|
|
947
|
+
const initializer = initializers.get(node);
|
|
948
|
+
const result = initializer ? fresh("Result") : void 0;
|
|
949
|
+
const exit = initializer ? fresh("Exit") : void 0;
|
|
726
950
|
const subject = fresh("Subject");
|
|
727
|
-
|
|
951
|
+
if (initializer) {
|
|
952
|
+
const { declaration, statement, prefixStart } = initializer;
|
|
953
|
+
const annotation = language.startsWith("ts") && declaration.id.typeAnnotation ? `: typeof ${declaration.id.name}` : "";
|
|
954
|
+
grouping(declaration.id.end, node.start, true);
|
|
955
|
+
grouping(node.end, statement.end);
|
|
956
|
+
code.appendLeft(prefixStart, `let ${result}${annotation}; ${exit}: {const ${subject} = `);
|
|
957
|
+
if (prefixStart < statement.start) code.prependRight(prefixStart, "\n");
|
|
958
|
+
code.move(prefixStart, node.start, node.end);
|
|
959
|
+
code.appendRight(node.end, ` ${result}`);
|
|
960
|
+
}
|
|
961
|
+
const valuePrefix = result ? `${result} = (` : "return ";
|
|
962
|
+
const valueSuffix = result ? `); break ${exit};` : ";";
|
|
963
|
+
const kind = node.cases.some((branch) => branch.enumPattern) ? fresh("Kind") : void 0;
|
|
964
|
+
if (!initializer) code.appendLeft(node.start, `${direct ? "{" : "(() => {"}const ${subject} = `);
|
|
728
965
|
code.remove(node.parenStart, node.parenStart + 1);
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
966
|
+
if (node.keyword === "match") {
|
|
967
|
+
const tokens = Parser.tokenizer(source.slice(node.discriminant.end, node.parenEnd), { ecmaVersion: "latest" });
|
|
968
|
+
for (;;) {
|
|
969
|
+
const token = tokens.getToken();
|
|
970
|
+
if (token.type === tokTypes.eof) break;
|
|
971
|
+
if (token.type === tokTypes.comma) code.remove(node.discriminant.end + token.start, node.discriminant.end + token.end);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
const keywordEnd = node.start + (node.keyword === "match" ? 5 : 6);
|
|
975
|
+
if (node.keyword === "match") code.overwrite(node.start, keywordEnd, "switch");
|
|
976
|
+
code.move(node.start, keywordEnd, node.parenEnd);
|
|
977
|
+
code.appendLeft(node.parenEnd, `; ${kind ? `const ${kind} = ${subject}["kind"]; ` : ""}`);
|
|
978
|
+
code.overwrite(node.parenEnd, node.parenEnd + 1, ` (${kind ?? subject}${kind || node.discriminator === void 0 ? "" : `[${JSON.stringify(node.discriminator)}]`})`);
|
|
979
|
+
for (const branch of node.cases) if (branch.enumPattern) {
|
|
980
|
+
const { reference, binding, start, parenStart, parenEnd, explicitKeyword } = branch.enumPattern;
|
|
981
|
+
const tag = JSON.stringify(reference.property.name);
|
|
982
|
+
if (language.startsWith("ts")) {
|
|
983
|
+
const witness = `(${tag} satisfies (typeof `;
|
|
984
|
+
if (explicitKeyword) code.overwrite(start, start + 4, witness);
|
|
985
|
+
else code.appendLeft(start, witness);
|
|
986
|
+
code.appendLeft(reference.end, ` extends (...args: never[]) => { readonly kind: ${tag} } ? ${tag} : never))`);
|
|
987
|
+
} else if (explicitKeyword) {
|
|
988
|
+
code.overwrite(start, start + 4, tag);
|
|
989
|
+
code.remove(reference.start, reference.end);
|
|
990
|
+
} else code.overwrite(reference.start, reference.end, tag);
|
|
991
|
+
code.remove(parenStart, parenStart + 1);
|
|
992
|
+
code.remove(parenEnd, parenEnd + 1);
|
|
993
|
+
if (binding) {
|
|
994
|
+
code.appendLeft(binding.start, ": { const ");
|
|
995
|
+
code.overwrite(branch.colonStart, branch.colonStart + 1, ` = ${subject}; ${branch.throw ? "" : valuePrefix}`);
|
|
996
|
+
code.appendLeft(branch.valueEnd, `${branch.throw ? ";" : valueSuffix} }`);
|
|
997
|
+
if (source[branch.end - 1] === ";") code.remove(branch.end - 1, branch.end);
|
|
998
|
+
} else if (!branch.throw) {
|
|
999
|
+
code.appendLeft(branch.colonStart + 1, ` ${valuePrefix}`);
|
|
1000
|
+
if (result) {
|
|
1001
|
+
code.appendLeft(branch.valueEnd, valueSuffix);
|
|
1002
|
+
if (source[branch.end - 1] === ";") code.remove(branch.end - 1, branch.end);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
} else if (branch.pattern) {
|
|
733
1006
|
const { pattern, tag } = branch;
|
|
734
1007
|
code.appendLeft(tag.value.start, fresh("Tag"));
|
|
735
1008
|
code.move(tag.value.start, tag.value.end, pattern.start);
|
|
736
1009
|
code.appendLeft(tag.value.end, ": { const ");
|
|
737
|
-
code.overwrite(branch.colonStart, branch.colonStart + 1, ` = ${subject}; ${branch.throw ? "" :
|
|
738
|
-
code.appendLeft(branch.valueEnd, "; }
|
|
1010
|
+
code.overwrite(branch.colonStart, branch.colonStart + 1, ` = ${subject}; ${branch.throw ? "" : valuePrefix}`);
|
|
1011
|
+
code.appendLeft(branch.valueEnd, `${branch.throw ? ";" : valueSuffix} }`);
|
|
739
1012
|
if (source[branch.end - 1] === ";") code.remove(branch.end - 1, branch.end);
|
|
740
|
-
} else if (!branch.throw)
|
|
741
|
-
|
|
742
|
-
|
|
1013
|
+
} else if (!branch.throw) {
|
|
1014
|
+
code.appendLeft(branch.colonStart + 1, ` ${valuePrefix}`);
|
|
1015
|
+
if (result) {
|
|
1016
|
+
code.appendLeft(branch.valueEnd, valueSuffix);
|
|
1017
|
+
if (source[branch.end - 1] === ";") code.remove(branch.end - 1, branch.end);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
const hasDefault = node.cases.some((branch) => !branch.pattern && !branch.enumPattern && branch.test === null);
|
|
1021
|
+
const check = !hasDefault && language.startsWith("ts") ? ` ${kind ? `(0 as unknown as typeof ${kind})` : subject} satisfies never;` : "";
|
|
743
1022
|
const failure = hasDefault ? "" : `${check} throw new TypeError('Non-exhaustive switch expression');`;
|
|
744
|
-
code.overwrite(node.end - 1, node.end, `}${failure}${direct ? "}" : "})()"}
|
|
1023
|
+
code.overwrite(node.end - 1, node.end, `}${failure}${direct || initializer ? "}" : "})()"}`, { contentOnly: true });
|
|
745
1024
|
}
|
|
746
1025
|
}
|
|
747
1026
|
//#endregion
|
|
748
|
-
//#region src/
|
|
749
|
-
|
|
750
|
-
function
|
|
751
|
-
|
|
1027
|
+
//#region src/enums.ts
|
|
1028
|
+
/** Find free type references, respecting generic function/mapped/infer binders. */
|
|
1029
|
+
function references(node, names, found = /* @__PURE__ */ new Set(), bound = /* @__PURE__ */ new Set()) {
|
|
1030
|
+
const local = new Set(bound);
|
|
1031
|
+
for (const parameter of node.typeParameters?.params ?? []) local.add(parameter.name);
|
|
1032
|
+
if (node.type === "TSMappedType") local.add(node.typeParameter.name);
|
|
1033
|
+
if (node.type === "TSInferType") local.add(node.typeParameter.name);
|
|
1034
|
+
if (node.type === "TSTypeReference" && node.typeName.type === "Identifier") {
|
|
1035
|
+
const name = node.typeName.name;
|
|
1036
|
+
if (names.has(name) && !local.has(name)) found.add(name);
|
|
1037
|
+
}
|
|
1038
|
+
for (const [key, value] of Object.entries(node)) {
|
|
1039
|
+
if (["loc", "typeName"].includes(key)) continue;
|
|
1040
|
+
for (const child of Array.isArray(value) ? value : [value]) {
|
|
1041
|
+
if (!child?.type) continue;
|
|
1042
|
+
const childBound = new Set(local);
|
|
1043
|
+
if (node.type === "TSConditionalType" && key === "trueType") {
|
|
1044
|
+
const bind = (part) => {
|
|
1045
|
+
if (part.type === "TSInferType") childBound.add(part.typeParameter.name);
|
|
1046
|
+
for (const [property, nested] of Object.entries(part)) {
|
|
1047
|
+
if (property === "loc") continue;
|
|
1048
|
+
if (part.type === "TSConditionalType" && property === "extendsType") continue;
|
|
1049
|
+
for (const item of Array.isArray(nested) ? nested : [nested]) if (item?.type) bind(item);
|
|
1050
|
+
}
|
|
1051
|
+
};
|
|
1052
|
+
bind(node.extendsType);
|
|
1053
|
+
}
|
|
1054
|
+
references(child, names, found, childBound);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
return found;
|
|
1058
|
+
}
|
|
1059
|
+
function enumTypeParameters(node, branch) {
|
|
1060
|
+
const parameters = node.typeParameters?.params ?? [];
|
|
1061
|
+
const names = new Set(parameters.map((parameter) => parameter.name));
|
|
1062
|
+
const used = /* @__PURE__ */ new Set();
|
|
1063
|
+
for (const field of branch.params) references(field.typeAnnotation, names, used);
|
|
1064
|
+
for (let size = -1; size !== used.size;) {
|
|
1065
|
+
size = used.size;
|
|
1066
|
+
for (const parameter of parameters) {
|
|
1067
|
+
if (!used.has(parameter.name)) continue;
|
|
1068
|
+
if (parameter.constraint) references(parameter.constraint, names, used);
|
|
1069
|
+
if (parameter.default) references(parameter.default, names, used);
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
return parameters.filter((parameter) => used.has(parameter.name));
|
|
752
1073
|
}
|
|
1074
|
+
/** Tagged records and precise factories; no application runtime is introduced. */
|
|
1075
|
+
function lowerEnums(source, ast, enums, code) {
|
|
1076
|
+
const insertions = [];
|
|
1077
|
+
const exported = /* @__PURE__ */ new Set();
|
|
1078
|
+
const visit = (node) => {
|
|
1079
|
+
if (node.type === "ExportNamedDeclaration") exported.add(node.declaration);
|
|
1080
|
+
for (const [key, value] of Object.entries(node)) {
|
|
1081
|
+
if (["loc", "trailing"].includes(key)) continue;
|
|
1082
|
+
for (const child of Array.isArray(value) ? value : [value]) if (child?.type) visit(child);
|
|
1083
|
+
}
|
|
1084
|
+
};
|
|
1085
|
+
visit(ast);
|
|
1086
|
+
for (const node of enums) {
|
|
1087
|
+
const replacement = {
|
|
1088
|
+
anchor: node.braceStart,
|
|
1089
|
+
before: false,
|
|
1090
|
+
text: "= ",
|
|
1091
|
+
copies: []
|
|
1092
|
+
};
|
|
1093
|
+
for (const [index, branch] of node.cases.entries()) {
|
|
1094
|
+
if (index) replacement.text += " | ";
|
|
1095
|
+
replacement.text += `{ readonly kind: ${JSON.stringify(branch.id.name)}; `;
|
|
1096
|
+
for (const field of branch.params) {
|
|
1097
|
+
replacement.text += "readonly ";
|
|
1098
|
+
const text = source.slice(field.start, field.typeAnnotation.end);
|
|
1099
|
+
replacement.copies.push({
|
|
1100
|
+
start: replacement.text.length,
|
|
1101
|
+
sourceStart: field.start,
|
|
1102
|
+
length: text.length
|
|
1103
|
+
});
|
|
1104
|
+
replacement.text += text + "; ";
|
|
1105
|
+
}
|
|
1106
|
+
replacement.text += "}";
|
|
1107
|
+
}
|
|
1108
|
+
replacement.text += `; ${exported.has(node) ? "export " : ""}const `;
|
|
1109
|
+
replacement.copies.push({
|
|
1110
|
+
start: replacement.text.length,
|
|
1111
|
+
sourceStart: node.id.start,
|
|
1112
|
+
length: node.id.end - node.id.start
|
|
1113
|
+
});
|
|
1114
|
+
replacement.text += source.slice(node.id.start, node.id.end) + " = {";
|
|
1115
|
+
insertions.push(replacement);
|
|
1116
|
+
code.overwrite(node.start, node.start + 4, "type");
|
|
1117
|
+
code.overwrite(node.braceStart, node.braceStart + 1, replacement.text);
|
|
1118
|
+
for (const branch of node.cases) {
|
|
1119
|
+
code.remove(branch.start, branch.start + 4);
|
|
1120
|
+
const parameters = enumTypeParameters(node, branch);
|
|
1121
|
+
const copied = {
|
|
1122
|
+
anchor: branch.id.end,
|
|
1123
|
+
before: true,
|
|
1124
|
+
text: parameters.length ? "<" : "",
|
|
1125
|
+
copies: []
|
|
1126
|
+
};
|
|
1127
|
+
for (const [index, parameter] of parameters.entries()) {
|
|
1128
|
+
if (index) copied.text += ", ";
|
|
1129
|
+
const text = source.slice(parameter.start, parameter.end);
|
|
1130
|
+
copied.copies.push({
|
|
1131
|
+
start: copied.text.length,
|
|
1132
|
+
sourceStart: parameter.start,
|
|
1133
|
+
length: text.length
|
|
1134
|
+
});
|
|
1135
|
+
copied.text += text;
|
|
1136
|
+
}
|
|
1137
|
+
if (parameters.length) copied.text += ">";
|
|
1138
|
+
const generic = copied.text;
|
|
1139
|
+
if (generic) insertions.push(copied);
|
|
1140
|
+
code.appendLeft(branch.id.end, generic + (branch.hasParens ? "" : "()"));
|
|
1141
|
+
const fields = branch.params.map((field) => field.name);
|
|
1142
|
+
code.appendLeft(branch.valueEnd, `{ return { kind: ${JSON.stringify(branch.id.name)}${fields.length ? ", " + fields.join(", ") : ""} } as const; },`);
|
|
1143
|
+
if (source[branch.end - 1] === ";") code.remove(branch.end - 1, branch.end);
|
|
1144
|
+
}
|
|
1145
|
+
code.appendLeft(node.end, ";");
|
|
1146
|
+
}
|
|
1147
|
+
return insertions;
|
|
1148
|
+
}
|
|
1149
|
+
/** Generated type copies map to their actual source tokens, including .d.ts maps. */
|
|
1150
|
+
function mapEnumCopies(source, generated, map, insertions) {
|
|
1151
|
+
const starts = (text) => {
|
|
1152
|
+
const result = [0];
|
|
1153
|
+
for (let index = 0; index < text.length; index++) if (text[index] === "\n") result.push(index + 1);
|
|
1154
|
+
return result;
|
|
1155
|
+
};
|
|
1156
|
+
const originalLines = starts(source), generatedLines = starts(generated);
|
|
1157
|
+
const position = (lines, offset) => {
|
|
1158
|
+
let line = 0;
|
|
1159
|
+
while (line + 1 < lines.length && lines[line + 1] <= offset) line++;
|
|
1160
|
+
return {
|
|
1161
|
+
line,
|
|
1162
|
+
column: offset - lines[line]
|
|
1163
|
+
};
|
|
1164
|
+
};
|
|
1165
|
+
const trace = new TraceMap(map);
|
|
1166
|
+
const decoded = decodedMappings(trace);
|
|
1167
|
+
const copies = [];
|
|
1168
|
+
for (const insertion of insertions) {
|
|
1169
|
+
const anchor = position(originalLines, insertion.anchor);
|
|
1170
|
+
const location = generatedPositionFor(trace, {
|
|
1171
|
+
source: map.sources[0],
|
|
1172
|
+
line: anchor.line + 1,
|
|
1173
|
+
column: anchor.column
|
|
1174
|
+
});
|
|
1175
|
+
const offset = generatedLines[location.line - 1] + location.column - (insertion.before ? insertion.text.length : 0);
|
|
1176
|
+
if (generated.slice(offset, offset + insertion.text.length) !== insertion.text) throw new Error("Enum mapping anchor does not match generated text.");
|
|
1177
|
+
for (const copy of insertion.copies) {
|
|
1178
|
+
copies.push({
|
|
1179
|
+
generatedStart: offset + copy.start,
|
|
1180
|
+
sourceStart: copy.sourceStart,
|
|
1181
|
+
length: copy.length
|
|
1182
|
+
});
|
|
1183
|
+
for (let index = 0; index < copy.length; index++) {
|
|
1184
|
+
const target = position(generatedLines, offset + copy.start + index);
|
|
1185
|
+
const original = position(originalLines, copy.sourceStart + index);
|
|
1186
|
+
const line = decoded[target.line];
|
|
1187
|
+
const entry = [
|
|
1188
|
+
target.column,
|
|
1189
|
+
0,
|
|
1190
|
+
original.line,
|
|
1191
|
+
original.column
|
|
1192
|
+
];
|
|
1193
|
+
const existing = line.findIndex((segment) => segment[0] === target.column);
|
|
1194
|
+
if (existing >= 0) line[existing] = entry;
|
|
1195
|
+
else line.push(entry);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
for (const line of decoded) line.sort((left, right) => left[0] - right[0]);
|
|
1200
|
+
map.mappings = encodedMappings(new TraceMap({
|
|
1201
|
+
...JSON.parse(map.toString()),
|
|
1202
|
+
mappings: decoded
|
|
1203
|
+
}));
|
|
1204
|
+
return copies;
|
|
1205
|
+
}
|
|
1206
|
+
//#endregion
|
|
1207
|
+
//#region src/compiler.ts
|
|
753
1208
|
function inferLanguage(filename) {
|
|
754
1209
|
if (/\.(?:twillx|tsx)$/.test(filename)) return "tsx";
|
|
755
1210
|
if (/\.jsx$/.test(filename)) return "jsx";
|
|
@@ -792,6 +1247,7 @@ function calleeName(node) {
|
|
|
792
1247
|
}
|
|
793
1248
|
}
|
|
794
1249
|
function transform(source, options = {}) {
|
|
1250
|
+
if (options.runtime !== void 0 && !["inline", "external"].includes(options.runtime)) throw new TypeError("runtime must be inline or external");
|
|
795
1251
|
const filename = options.filename ?? "input.twill";
|
|
796
1252
|
let parsed;
|
|
797
1253
|
try {
|
|
@@ -800,11 +1256,28 @@ function transform(source, options = {}) {
|
|
|
800
1256
|
throw new TwillSyntaxError(source, filename, error);
|
|
801
1257
|
}
|
|
802
1258
|
const code = new MagicString(source);
|
|
1259
|
+
const enumInsertions = parsed.enums.length ? lowerEnums(source, parsed.ast, parsed.enums, code) : [];
|
|
803
1260
|
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);
|
|
1261
|
+
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) => {
|
|
1262
|
+
if (node.type === "Identifier" || node.type === "JSXIdentifier") usedNames.add(node.name);
|
|
806
1263
|
});
|
|
807
1264
|
let guardCounter = 0;
|
|
1265
|
+
const ifBindingStarts = new Set(parsed.ifBindings.map((node) => node.consequent.start + 1));
|
|
1266
|
+
for (const statement of parsed.ifBindings) {
|
|
1267
|
+
const binding = statement.binding.declarations[0].id;
|
|
1268
|
+
let temporary;
|
|
1269
|
+
do
|
|
1270
|
+
temporary = `__twillIf${guardCounter++}`;
|
|
1271
|
+
while (usedNames.has(temporary));
|
|
1272
|
+
usedNames.add(temporary);
|
|
1273
|
+
const patternEnd = binding.typeAnnotation?.start ?? binding.end;
|
|
1274
|
+
code.overwrite(statement.start, binding.start, `{const ${temporary}`);
|
|
1275
|
+
code.move(binding.start, patternEnd, statement.consequent.start + 1);
|
|
1276
|
+
code.prependRight(binding.start, "const ");
|
|
1277
|
+
code.appendLeft(patternEnd, ` = ${temporary};`);
|
|
1278
|
+
code.prependLeft(statement.consequent.start, `; if (${temporary} !== null && ${temporary} !== void 0) `);
|
|
1279
|
+
code.appendLeft(statement.end, "}");
|
|
1280
|
+
}
|
|
808
1281
|
for (const guard of parsed.guards) {
|
|
809
1282
|
if (guard.singleStatement) {
|
|
810
1283
|
code.prependLeft(guard.start, "{");
|
|
@@ -1011,13 +1484,15 @@ function transform(source, options = {}) {
|
|
|
1011
1484
|
const collect = (statement) => {
|
|
1012
1485
|
switch (statement.type) {
|
|
1013
1486
|
case "ExpressionStatement":
|
|
1014
|
-
code.
|
|
1487
|
+
if (ifBindingStarts.has(statement.start)) code.appendRight(statement.start, `${collector}.push(`);
|
|
1488
|
+
else code.prependLeft(statement.start, `${collector}.push(`);
|
|
1015
1489
|
code.appendLeft(source[statement.end - 1] === ";" ? statement.end - 1 : statement.end, ")");
|
|
1016
1490
|
break;
|
|
1017
1491
|
case "BlockStatement":
|
|
1018
1492
|
statement.body.forEach(collect);
|
|
1019
1493
|
break;
|
|
1020
1494
|
case "IfStatement":
|
|
1495
|
+
case "TwillIfBinding":
|
|
1021
1496
|
collect(statement.consequent);
|
|
1022
1497
|
if (statement.alternate) collect(statement.alternate);
|
|
1023
1498
|
break;
|
|
@@ -1061,7 +1536,7 @@ function transform(source, options = {}) {
|
|
|
1061
1536
|
if (component) code.prependLeft(closures.at(-1).end, (vue ? "})}" : directChild || metadataByNode.get(closures[0]).header ? ")}" : ")()}") + `</${calleeName(node.callee)}>)`);
|
|
1062
1537
|
else code.appendLeft(closures.at(-1).end, ")");
|
|
1063
1538
|
}
|
|
1064
|
-
if (parsed.switches.length) lowerSwitchExpressions(source, parsed.ast, parsed.switches, code, options.language ?? inferLanguage(filename), usedNames, (node, message) => {
|
|
1539
|
+
if (parsed.switches.length) lowerSwitchExpressions(source, parsed.ast, parsed.switches, code, options.language ?? inferLanguage(filename), usedNames, parsed.comments, (node, message) => {
|
|
1065
1540
|
throw new TwillSyntaxError(source, filename, {
|
|
1066
1541
|
message,
|
|
1067
1542
|
pos: node.start,
|
|
@@ -1072,7 +1547,7 @@ function transform(source, options = {}) {
|
|
|
1072
1547
|
if (parsed.defers.length) {
|
|
1073
1548
|
const starts = /* @__PURE__ */ new Map();
|
|
1074
1549
|
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) => {
|
|
1550
|
+
lowerDefers(source, parsed.ast, code, options.language ?? inferLanguage(filename), usedNames, starts, parsed.comments, options.runtime ?? "inline", options.sourceType ?? "module", (node, message) => {
|
|
1076
1551
|
throw new TwillSyntaxError(source, filename, {
|
|
1077
1552
|
message,
|
|
1078
1553
|
pos: node.start,
|
|
@@ -1086,14 +1561,19 @@ function transform(source, options = {}) {
|
|
|
1086
1561
|
includeContent: true,
|
|
1087
1562
|
hires: true
|
|
1088
1563
|
});
|
|
1564
|
+
const enumCopies = enumInsertions.length ? mapEnumCopies(source, code.toString(), map, enumInsertions) : [];
|
|
1089
1565
|
return {
|
|
1090
1566
|
code: code.toString(),
|
|
1091
1567
|
map,
|
|
1092
1568
|
changed: code.hasChanged(),
|
|
1093
1569
|
closures: parsed.closures.length,
|
|
1094
1570
|
guards: parsed.guards.length,
|
|
1571
|
+
ifBindings: parsed.ifBindings.length,
|
|
1095
1572
|
defers: parsed.defers.length,
|
|
1096
1573
|
switches: parsed.switches.length,
|
|
1574
|
+
associatedEnums: parsed.enums,
|
|
1575
|
+
enumPatterns: parsed.switches.flatMap((node) => node.cases.flatMap((branch) => branch.enumPattern ? [branch.enumPattern] : [])),
|
|
1576
|
+
enumCopies,
|
|
1097
1577
|
componentProps
|
|
1098
1578
|
};
|
|
1099
1579
|
}
|
|
@@ -1122,6 +1602,6 @@ function generatedPosition(result, line, column) {
|
|
|
1122
1602
|
});
|
|
1123
1603
|
}
|
|
1124
1604
|
//#endregion
|
|
1125
|
-
export {
|
|
1605
|
+
export { transform as a, originalPosition as i, generatedPosition as n, parse as o, inferLanguage as r, TwillSyntaxError as t };
|
|
1126
1606
|
|
|
1127
|
-
//# sourceMappingURL=compiler-
|
|
1607
|
+
//# sourceMappingURL=compiler-DY1N07np.js.map
|