@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.
Files changed (60) hide show
  1. package/README.md +57 -14
  2. package/bin/run.mjs +15 -0
  3. package/bin/twill.mjs +34 -11
  4. package/dist/branches.d.ts +6 -3
  5. package/dist/cli.js +13 -6
  6. package/dist/cli.js.map +1 -1
  7. package/dist/compilation-cache-7SugqJ4n.js +172 -0
  8. package/dist/compilation-cache-7SugqJ4n.js.map +1 -0
  9. package/dist/compilation-cache.d.ts +21 -0
  10. package/dist/{compiler-Bi9TNOhX.js → compiler-CbNSBUI2.js} +583 -64
  11. package/dist/compiler-CbNSBUI2.js.map +1 -0
  12. package/dist/compiler.d.ts +17 -2
  13. package/dist/config.d.ts +1 -6
  14. package/dist/config.js +1 -54
  15. package/dist/configuration-BVMf6Q_j.js +149 -0
  16. package/dist/configuration-BVMf6Q_j.js.map +1 -0
  17. package/dist/configuration.d.ts +12 -0
  18. package/dist/defer-helper.d.ts +1 -0
  19. package/dist/defer.d.ts +2 -2
  20. package/dist/doctor.js +9 -5
  21. package/dist/doctor.js.map +1 -1
  22. package/dist/editor.d.ts +2 -0
  23. package/dist/editor.js +75 -4
  24. package/dist/editor.js.map +1 -1
  25. package/dist/enums.d.ts +27 -0
  26. package/dist/esbuild.js +1 -1
  27. package/dist/extensions-B1xTkoYP.js +9 -0
  28. package/dist/extensions-B1xTkoYP.js.map +1 -0
  29. package/dist/extensions.d.ts +2 -0
  30. package/dist/{files-D1mERoxc.js → files-BW9geU0_.js} +2 -2
  31. package/dist/files-BW9geU0_.js.map +1 -0
  32. package/dist/index.js +2 -1
  33. package/dist/loader-compiler-D1teTrXw.js +19 -0
  34. package/dist/loader-compiler-D1teTrXw.js.map +1 -0
  35. package/dist/loader-compiler.d.ts +4 -0
  36. package/dist/loader.d.ts +4 -0
  37. package/dist/loader.js +29 -22
  38. package/dist/loader.js.map +1 -1
  39. package/dist/parser.d.ts +2 -0
  40. package/dist/{plugin-BGabvyT6.js → plugin-BHfiQ2L-.js} +41 -12
  41. package/dist/plugin-BHfiQ2L-.js.map +1 -0
  42. package/dist/project.js +39 -11
  43. package/dist/project.js.map +1 -1
  44. package/dist/recovery.d.ts +2 -2
  45. package/dist/rollup.js +1 -1
  46. package/dist/rspack.js +1 -1
  47. package/dist/syntax.d.ts +2 -0
  48. package/dist/syntax.js +1 -1
  49. package/dist/{transpile-DwVekVFM.js → transpile-D8P8Lsbx.js} +2 -2
  50. package/dist/{transpile-DwVekVFM.js.map → transpile-D8P8Lsbx.js.map} +1 -1
  51. package/dist/typescript-plugin.cjs +783 -82
  52. package/dist/typescript-plugin.cjs.map +1 -1
  53. package/dist/vite.js +1 -1
  54. package/dist/webpack.js +1 -1
  55. package/package.json +8 -4
  56. package/schemas/twill.schema.json +6 -0
  57. package/dist/compiler-Bi9TNOhX.js.map +0 -1
  58. package/dist/config.js.map +0 -1
  59. package/dist/files-D1mERoxc.js.map +0 -1
  60. 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": return !!node.alternate && exits(node.consequent) && exits(node.alternate);
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)) return super.parseStatement(context, ...args);
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
- this.next();
320
- node.parenStart = this.start;
321
- node.discriminant = this.parseParenExpression();
322
- node.parenEnd = this.lastTokStart;
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, "A switch expression requires at least one arm.");
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
- for (const [key, value] of Object.entries(node)) {
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
- if (Array.isArray(value)) value.forEach(visit);
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 [key, value] of Object.entries(node)) {
710
+ for (const key of Object.keys(node)) {
553
711
  if (key === "loc" || key === "trailing") continue;
554
- if (Array.isArray(value)) value.forEach((child) => {
555
- if (child?.type) visit(child);
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
- /** Scope-local cleanup callbacks; dynamic registrations use lazy stacks. */
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.appendLeft(start, `;${jsdoc}let ${stack}${annotation};${names.length ? `var ${names.join(",")};` : ""}try {\n`);
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.prependLeft(block.end - 1, `\n} finally { ${call} }\n`);
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.prependLeft(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}; }`);
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; expression contexts use a synchronous
687
- * lexical IIFE. Direct returns need no extra function or scheduling. */
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 visit = (node) => {
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, "await/yield inside a switch expression requires a direct return. Bind the subject first, or await the whole result explicitly.");
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
- code.appendLeft(node.start, `${direct ? "{" : "(() => {"}const ${subject} = `);
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
- code.move(node.start, node.start + 6, node.parenEnd);
730
- code.appendLeft(node.parenEnd, "; ");
731
- code.overwrite(node.parenEnd, node.parenEnd + 1, ` (${subject}${node.discriminator === void 0 ? "" : `[${JSON.stringify(node.discriminator)}]`})`);
732
- for (const branch of node.cases) if (branch.pattern) {
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 ? "" : "return "}`);
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) code.appendLeft(branch.colonStart + 1, " return ");
741
- const hasDefault = node.cases.some((branch) => !branch.pattern && branch.test === null);
742
- const check = !hasDefault && language.startsWith("ts") ? ` ${subject} satisfies never;` : "";
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/compiler.ts
749
- var extensions = [".twill", ".twillx"];
750
- function isTwillFile(id) {
751
- return extensions.some((extension) => id.split(/[?#]/, 1)[0].endsWith(extension));
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 [key, value] of Object.entries(node)) {
780
- if (["loc", "trailing"].includes(key)) continue;
781
- if (Array.isArray(value)) value.forEach((child) => {
782
- if (child?.type) walk(child, visit);
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.prependLeft(statement.start, `${collector}.push(`);
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 { isTwillFile as a, parse as c, inferLanguage as i, extensions as n, originalPosition as o, generatedPosition as r, transform as s, TwillSyntaxError as t };
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-Bi9TNOhX.js.map
1646
+ //# sourceMappingURL=compiler-CbNSBUI2.js.map