@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.
Files changed (59) hide show
  1. package/README.md +48 -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-CuCGz02_.js +172 -0
  8. package/dist/compilation-cache-CuCGz02_.js.map +1 -0
  9. package/dist/compilation-cache.d.ts +21 -0
  10. package/dist/{compiler-Bi9TNOhX.js → compiler-DY1N07np.js} +530 -50
  11. package/dist/compiler-DY1N07np.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-CTJjSu_h.js +139 -0
  16. package/dist/configuration-CTJjSu_h.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 +1 -1
  20. package/dist/doctor.js +10 -6
  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-DIw9aw5n.js +19 -0
  34. package/dist/loader-compiler-DIw9aw5n.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-Bks2hxCJ.js} +41 -12
  41. package/dist/plugin-Bks2hxCJ.js.map +1 -0
  42. package/dist/project.js +27 -3
  43. package/dist/project.js.map +1 -1
  44. package/dist/rollup.js +1 -1
  45. package/dist/rspack.js +1 -1
  46. package/dist/syntax.d.ts +2 -0
  47. package/dist/syntax.js +1 -1
  48. package/dist/{transpile-DwVekVFM.js → transpile-DG81yfVs.js} +2 -2
  49. package/dist/{transpile-DwVekVFM.js.map → transpile-DG81yfVs.js.map} +1 -1
  50. package/dist/typescript-plugin.cjs +718 -60
  51. package/dist/typescript-plugin.cjs.map +1 -1
  52. package/dist/vite.js +1 -1
  53. package/dist/webpack.js +1 -1
  54. package/package.json +9 -5
  55. package/schemas/twill.schema.json +6 -0
  56. package/dist/compiler-Bi9TNOhX.js.map +0 -1
  57. package/dist/config.js.map +0 -1
  58. package/dist/files-D1mERoxc.js.map +0 -1
  59. package/dist/plugin-BGabvyT6.js.map +0 -1
@@ -38,6 +38,7 @@ let node_fs = require("node:fs");
38
38
  let node_path = require("node:path");
39
39
  let typescript = require("typescript");
40
40
  typescript = __toESM(typescript, 1);
41
+ let node_crypto = require("node:crypto");
41
42
  //#region ../../node_modules/.pnpm/@jridgewell+sourcemap-codec@1.6.0/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.mjs
42
43
  var comma = ",".charCodeAt(0);
43
44
  var semicolon = ";".charCodeAt(0);
@@ -15049,7 +15050,8 @@ function exits(node) {
15049
15050
  case "BreakStatement":
15050
15051
  case "ContinueStatement": return true;
15051
15052
  case "BlockStatement": return node.body.some(exits);
15052
- case "IfStatement": return !!node.alternate && exits(node.consequent) && exits(node.alternate);
15053
+ case "IfStatement":
15054
+ case "TwillIfBinding": return !!node.alternate && exits(node.consequent) && exits(node.alternate);
15053
15055
  default: return false;
15054
15056
  }
15055
15057
  }
@@ -15143,6 +15145,16 @@ function moveNodes(node, delta, locate) {
15143
15145
  }
15144
15146
  }
15145
15147
  var parsers = /* @__PURE__ */ new Map();
15148
+ function associatedEnumAt(input, offset) {
15149
+ try {
15150
+ const tokens = Parser.tokenizer(input.slice(offset), { ecmaVersion: "latest" });
15151
+ tokens.getToken();
15152
+ const next = tokens.getToken();
15153
+ return next.value === "<" || next.type === types$1.braceL && tokens.getToken().type === types$1._case && tokens.getToken().type === types$1.name;
15154
+ } catch {
15155
+ return false;
15156
+ }
15157
+ }
15146
15158
  function parserFor(language) {
15147
15159
  if (parsers.has(language)) return parsers.get(language);
15148
15160
  const Base = bases[language];
@@ -15152,8 +15164,55 @@ function parserFor(language) {
15152
15164
  lineStarts = null;
15153
15165
  classSuperDepth = null;
15154
15166
  subscriptDepth = 0;
15167
+ conditionDepth = null;
15155
15168
  derivedClassElement = false;
15156
15169
  implicitClosure = null;
15170
+ tsParseEnumDeclaration(node, modifiers = {}) {
15171
+ if (!associatedEnumAt(this.input, this.end)) return super.tsParseEnumDeclaration(node, modifiers);
15172
+ if (modifiers.const || modifiers.declare || this.isAmbientContext) this.raise(node.start, "Associated-value enums cannot be const or ambient declarations.");
15173
+ this.expectContextual("enum");
15174
+ node.id = this.parseIdent();
15175
+ this.checkLValSimple(node.id, 2);
15176
+ node.typeParameters = this.tsTryParseTypeParameters(() => {
15177
+ if (this.type === types$1._in || this.isContextual("out") && this.lookahead().type === types$1.name) this.raise(this.start, "Associated-value enum type parameters cannot have const/in/out modifiers.");
15178
+ });
15179
+ const constant = node.typeParameters?.params.find((parameter) => parameter.const);
15180
+ if (constant) this.raise(constant.start, "Associated-value enum type parameters cannot have const/in/out modifiers.");
15181
+ node.braceStart = this.start;
15182
+ this.expect(types$1.braceL);
15183
+ node.cases = [];
15184
+ const names = /* @__PURE__ */ new Set();
15185
+ while (this.type !== types$1.braceR) {
15186
+ const branch = this.startNode();
15187
+ this.expect(types$1._case);
15188
+ branch.id = this.parseIdent();
15189
+ if (names.has(branch.id.name)) this.raise(branch.id.start, "Duplicate enum case.");
15190
+ names.add(branch.id.name);
15191
+ branch.params = [];
15192
+ branch.hasParens = this.eat(types$1.parenL);
15193
+ if (branch.hasParens) {
15194
+ const fields = /* @__PURE__ */ new Set();
15195
+ while (this.type !== types$1.parenR) {
15196
+ const field = this.parseIdent();
15197
+ this.checkLValSimple(field);
15198
+ if (field.name === "kind") this.raise(field.start, "Enum payload fields cannot use the discriminator name kind.");
15199
+ if (fields.has(field.name)) this.raise(field.start, "Duplicate enum payload field.");
15200
+ fields.add(field.name);
15201
+ if (this.type !== types$1.colon) this.raise(this.start, "Enum payload fields require a name and type; optional/default/rest fields are unsupported.");
15202
+ field.typeAnnotation = this.tsParseTypeAnnotation();
15203
+ branch.params.push(field);
15204
+ if (!this.eat(types$1.comma)) break;
15205
+ }
15206
+ this.expect(types$1.parenR);
15207
+ }
15208
+ branch.valueEnd = this.lastTokEnd;
15209
+ this.semicolon();
15210
+ node.cases.push(this.finishNode(branch, "TwillEnumCase"));
15211
+ }
15212
+ if (!node.cases.length) this.raise(node.start, "An associated-value enum needs at least one case.");
15213
+ this.next();
15214
+ return this.finishNode(node, "TwillEnumDeclaration");
15215
+ }
15157
15216
  locate(offset) {
15158
15217
  if (!this.lineStarts) {
15159
15218
  this.lineStarts = [0];
@@ -15192,7 +15251,11 @@ function parserFor(language) {
15192
15251
  node.cleanup = cleanup;
15193
15252
  return this.finishNode(node, "DeferStatement");
15194
15253
  }
15195
- if (!this.isContextual("guard") || !guardAt(this.input, this.end, !!this.implicitClosure)) return super.parseStatement(context, ...args);
15254
+ if (!this.isContextual("guard") || !guardAt(this.input, this.end, !!this.implicitClosure)) {
15255
+ const statement = super.parseStatement(context, ...args);
15256
+ if (context && statement.type === "TwillEnumDeclaration") this.raise(statement.start, "An associated-value enum requires a block; add braces around this body.");
15257
+ return statement;
15258
+ }
15196
15259
  const node = this.startNode();
15197
15260
  node.singleStatement = !!context;
15198
15261
  this.next();
@@ -15212,6 +15275,36 @@ function parserFor(language) {
15212
15275
  if (!exits(node.failure)) this.raise(node.failure.start, "Every guard else path must exit with return, throw, break or continue.");
15213
15276
  return this.finishNode(node, "GuardStatement");
15214
15277
  }
15278
+ parseIfStatement(node) {
15279
+ if (/^\s*\(/.test(this.input.slice(this.end))) return super.parseIfStatement(node);
15280
+ let binding;
15281
+ try {
15282
+ binding = Parser.tokenizer(this.input.slice(this.end), options).getToken().type;
15283
+ } catch {}
15284
+ if (binding !== types$1._const) return super.parseIfStatement(node);
15285
+ this.next();
15286
+ const declaration = this.startNode();
15287
+ this.next();
15288
+ this.enterScope(0);
15289
+ const previous = this.conditionDepth;
15290
+ this.conditionDepth = this.subscriptDepth;
15291
+ try {
15292
+ this.parseVar(declaration, false, "const");
15293
+ } finally {
15294
+ this.conditionDepth = previous;
15295
+ }
15296
+ node.binding = this.finishNode(declaration, "VariableDeclaration");
15297
+ if (node.binding.declarations.length !== 1) this.raise(node.start, "if const requires one binding declaration.");
15298
+ if (this.type !== types$1.braceL) this.raise(this.start, "if const requires a braced success branch.");
15299
+ node.consequent = this.parseBlock(false);
15300
+ this.exitScope();
15301
+ node.alternate = null;
15302
+ if (this.eat(types$1._else)) {
15303
+ if (this.type !== types$1.braceL && this.type !== types$1._if) this.raise(this.start, "if const else requires a block or another if.");
15304
+ node.alternate = this.parseStatement("if");
15305
+ }
15306
+ return this.finishNode(node, "TwillIfBinding");
15307
+ }
15215
15308
  tsParseTypeParameter(...args) {
15216
15309
  const start = this.start;
15217
15310
  const loc = this.startLoc;
@@ -15283,12 +15376,21 @@ function parserFor(language) {
15283
15376
  }
15284
15377
  return super.parseExprAtom(...args);
15285
15378
  }
15286
- parseSwitchExpression() {
15287
- const node = this.startNode();
15288
- this.next();
15289
- node.parenStart = this.start;
15290
- node.discriminant = this.parseParenExpression();
15291
- node.parenEnd = this.lastTokStart;
15379
+ parseSwitchExpression(call) {
15380
+ const node = call ? this.startNodeAt(call.start, call.loc.start) : this.startNode();
15381
+ node.keyword = call ? "match" : "switch";
15382
+ if (call) {
15383
+ if (call.arguments.length !== 1 || call.arguments[0].type === "SpreadElement") this.raise(call.start, "A match expression requires one subject; parenthesize comma expressions.");
15384
+ const opening = Parser.tokenizer(this.input.slice(call.callee.end), options).getToken();
15385
+ node.parenStart = call.callee.end + opening.start;
15386
+ node.discriminant = call.arguments[0];
15387
+ node.parenEnd = call.end - 1;
15388
+ } else {
15389
+ this.next();
15390
+ node.parenStart = this.start;
15391
+ node.discriminant = this.parseParenExpression();
15392
+ node.parenEnd = this.lastTokStart;
15393
+ }
15292
15394
  this.expect(types$1.braceL);
15293
15395
  node.cases = [];
15294
15396
  let mode, discriminator, sawDefault = false;
@@ -15302,6 +15404,34 @@ function parserFor(language) {
15302
15404
  if (sawDefault) this.raise(branch.start, "Multiple default clauses.");
15303
15405
  sawDefault = true;
15304
15406
  branch.test = null;
15407
+ } else if (call && this.type !== types$1.braceL || !call && this.type.label === "name" && this.value === "enum" && !this.containsEsc) {
15408
+ if (mode === "value") this.raise(branch.start, "Cannot mix enum patterns and value cases.");
15409
+ if (discriminator !== void 0 && discriminator !== "kind") this.raise(branch.start, "Enum patterns require the kind discriminator.");
15410
+ mode = "pattern";
15411
+ discriminator = "kind";
15412
+ const pattern = this.startNode();
15413
+ pattern.explicitKeyword = !call;
15414
+ if (!call) this.next();
15415
+ let reference = this.parseIdent();
15416
+ while (this.eat(types$1.dot)) {
15417
+ const member = this.startNodeAt(reference.start, reference.loc.start);
15418
+ member.object = reference;
15419
+ member.property = this.parseIdent(true);
15420
+ member.computed = false;
15421
+ member.optional = false;
15422
+ reference = this.finishNode(member, "MemberExpression");
15423
+ }
15424
+ if (reference.type !== "MemberExpression") this.raise(reference.start, "An enum pattern requires a qualified case reference.");
15425
+ pattern.reference = reference;
15426
+ pattern.parenStart = this.start;
15427
+ this.expect(types$1.parenL);
15428
+ if (this.type === types$1.braceL) {
15429
+ pattern.binding = this.parseObj(true);
15430
+ this.checkLValPattern(pattern.binding, 2, false);
15431
+ }
15432
+ pattern.parenEnd = this.start;
15433
+ this.expect(types$1.parenR);
15434
+ branch.enumPattern = this.finishNode(pattern, "TwillEnumPattern");
15305
15435
  } else if (this.type === types$1.braceL) {
15306
15436
  if (mode === "value") this.raise(branch.start, "Cannot mix object patterns and value cases.");
15307
15437
  mode = "pattern";
@@ -15347,7 +15477,7 @@ function parserFor(language) {
15347
15477
  node.cases.push(this.finishNode(branch, "TwillSwitchCase"));
15348
15478
  }
15349
15479
  this.next();
15350
- if (!node.cases.length) this.raise(node.start, "A switch expression requires at least one arm.");
15480
+ if (!node.cases.length) this.raise(node.start, `A ${node.keyword} expression requires at least one arm.`);
15351
15481
  node.discriminator = discriminator;
15352
15482
  return this.finishNode(node, "TwillSwitchExpression");
15353
15483
  }
@@ -15410,6 +15540,13 @@ function parserFor(language) {
15410
15540
  }
15411
15541
  parseSubscript(base, startPos, startLoc, noCalls, maybeAsyncArrow, optionalChained, forInit) {
15412
15542
  const classBody = this.classSuperDepth !== null && this.subscriptDepth === this.classSuperDepth + 1;
15543
+ if (!noCalls && !classBody && this.type === types$1.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") {
15544
+ let first;
15545
+ try {
15546
+ first = Parser.tokenizer(this.input.slice(this.end), options).getToken().type;
15547
+ } catch {}
15548
+ if (first === types$1._case || first === types$1._default) return this.parseSwitchExpression(base);
15549
+ }
15413
15550
  if (!noCalls && !classBody && this.tsMatchLeftRelational?.() && this.start === base.end) {
15414
15551
  const parameters = this.tsTryParseAndCatch(() => {
15415
15552
  const parsed = this.tsParseTypeArgumentsInExpression();
@@ -15428,6 +15565,7 @@ function parserFor(language) {
15428
15565
  "MemberExpression",
15429
15566
  "TSInstantiationExpression"
15430
15567
  ].includes(base.type);
15568
+ if (this.conditionDepth !== null && this.subscriptDepth === this.conditionDepth + 1 && this.type === types$1.braceL) return base;
15431
15569
  if (!noCalls && supported && this.type === types$1.braceL && !classBody) {
15432
15570
  if (base.trailing) this.raise(this.start, "Additional trailing closures require labels (for example completion: { ... }).");
15433
15571
  if (base.type !== "CallExpression" && /[\r\n]/.test(this.input.slice(base.end, this.start))) return base;
@@ -15481,15 +15619,19 @@ function parse$1(source, language = "ts", sourceType = "module") {
15481
15619
  const live = /* @__PURE__ */ new Set();
15482
15620
  const calls = [];
15483
15621
  const guards = [];
15622
+ const ifBindings = [];
15484
15623
  const defers = [];
15485
15624
  const switches = [];
15625
+ const enums = [];
15486
15626
  const visit = (node) => {
15487
15627
  if (!node || typeof node !== "object") return;
15488
15628
  if (node.type === "ArrowFunctionExpression" || node.type === "Identifier" && node.start === node.end && node.name === "__twillImplicit") live.add(node);
15489
15629
  if (node.trailing) calls.push(node);
15490
15630
  if (node.type === "GuardStatement") guards.push(node);
15631
+ if (node.type === "TwillIfBinding") ifBindings.push(node);
15491
15632
  if (node.type === "DeferStatement") defers.push(node);
15492
15633
  if (node.type === "TwillSwitchExpression") switches.push(node);
15634
+ if (node.type === "TwillEnumDeclaration") enums.push(node);
15493
15635
  for (const [key, value] of Object.entries(node)) {
15494
15636
  if (key === "trailing" || key === "loc") continue;
15495
15637
  if (Array.isArray(value)) value.forEach(visit);
@@ -15501,8 +15643,10 @@ function parse$1(source, language = "ts", sourceType = "module") {
15501
15643
  ast,
15502
15644
  calls,
15503
15645
  guards,
15646
+ ifBindings,
15504
15647
  defers,
15505
15648
  switches,
15649
+ enums,
15506
15650
  comments,
15507
15651
  closures: parser.closures.filter((closure) => live.has(closure.node)).map((closure) => ({
15508
15652
  ...closure,
@@ -15511,6 +15655,11 @@ function parse$1(source, language = "ts", sourceType = "module") {
15511
15655
  };
15512
15656
  }
15513
15657
  //#endregion
15658
+ //#region src/defer-helper.ts
15659
+ function syncDeferBody(names) {
15660
+ 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";
15661
+ }
15662
+ //#endregion
15514
15663
  //#region src/defer.ts
15515
15664
  var functions = /* @__PURE__ */ new Set([
15516
15665
  "FunctionDeclaration",
@@ -15527,7 +15676,7 @@ function children(node, visit) {
15527
15676
  }
15528
15677
  }
15529
15678
  /** Scope-local cleanup callbacks; dynamic registrations use lazy stacks. */
15530
- function lowerDefers(source, ast, code, language, usedNames, contentStarts, comments, fail) {
15679
+ function lowerDefers(source, ast, code, language, usedNames, contentStarts, comments, runtime, sourceType, fail) {
15531
15680
  const scopes = [];
15532
15681
  const visit = (node, scope, parent) => {
15533
15682
  if (functions.has(node.type) || [
@@ -15576,6 +15725,7 @@ function lowerDefers(source, ast, code, language, usedNames, contentStarts, comm
15576
15725
  }
15577
15726
  return start;
15578
15727
  };
15728
+ let sharedHelper;
15579
15729
  for (const scope of scopes) {
15580
15730
  if (!scope.defers.length) continue;
15581
15731
  const { block } = scope;
@@ -15600,7 +15750,7 @@ function lowerDefers(source, ast, code, language, usedNames, contentStarts, comm
15600
15750
  const declarations = scope.functionBody ? ordinary.filter((node) => node.type === "FunctionDeclaration") : [];
15601
15751
  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.");
15602
15752
  const names = [...new Set(declarations.map((node) => node.id.name))];
15603
- code.appendLeft(start, `;${jsdoc}let ${stack}${annotation};${names.length ? `var ${names.join(",")};` : ""}try {\n`);
15753
+ code.prependLeft(start, `;${jsdoc}let ${stack}${annotation};${names.length ? `var ${names.join(",")};` : ""}try {\n`);
15604
15754
  let anchor = start;
15605
15755
  const initial = /* @__PURE__ */ new Set();
15606
15756
  for (const statement of ordinary) {
@@ -15624,11 +15774,37 @@ function lowerDefers(source, ast, code, language, usedNames, contentStarts, comm
15624
15774
  }
15625
15775
  if (single) {
15626
15776
  const call = allAsync ? `if (${stack}) await ${stack}();` : `${stack}?.();`;
15627
- code.prependLeft(block.end - 1, `\n} finally { ${call} }\n`);
15777
+ code.appendLeft(block.end - 1, `\n} finally { ${call} }\n`);
15778
+ continue;
15779
+ }
15780
+ if (!anyAsync) {
15781
+ let body;
15782
+ if (runtime === "external") {
15783
+ if (sourceType !== "module") fail(scope.defers[0], "External runtime helpers require module sourceType; use runtime: inline for scripts.");
15784
+ sharedHelper ??= fresh("RunDefers");
15785
+ body = `${sharedHelper}(${stack});`;
15786
+ } else body = syncDeferBody({
15787
+ stack,
15788
+ failed,
15789
+ failure,
15790
+ cleanup: callback,
15791
+ error
15792
+ });
15793
+ code.appendLeft(block.end - 1, `} finally { ${body} }`);
15628
15794
  continue;
15629
15795
  }
15630
15796
  const call = mixed ? `if (${callback}.async) await ${callback}.run(); else ${callback}.run();` : `${allAsync ? "await " : ""}${callback}();`;
15631
- 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}; }`);
15797
+ 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}; }`);
15798
+ }
15799
+ if (sharedHelper) {
15800
+ let start = ast.body.find((node) => !node.directive).start;
15801
+ for (const comment of sortedComments) {
15802
+ if (comment.end > start) continue;
15803
+ if (/\S/.test(source.slice(comment.end, start))) break;
15804
+ 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;
15805
+ else break;
15806
+ }
15807
+ code.prependLeft(start, `\nimport { runDefers as ${sharedHelper} } from '@swiftuijs/twill-runtime/helpers/v1';\n`);
15632
15808
  }
15633
15809
  }
15634
15810
  //#endregion
@@ -15652,22 +15828,68 @@ function suspension(node) {
15652
15828
  }
15653
15829
  }
15654
15830
  }
15655
- /** Native switches with per-arm scopes; expression contexts use a synchronous
15656
- * lexical IIFE. Direct returns need no extra function or scheduling. */
15657
- function lowerSwitchExpressions(source, ast, switches, code, language, usedNames, fail) {
15831
+ /** Native switches with per-arm scopes. Direct returns and standalone variable
15832
+ * initializers need no extra function; other expressions keep a lexical IIFE. */
15833
+ function lowerSwitchExpressions(source, ast, switches, code, language, usedNames, comments, fail) {
15658
15834
  const returns = /* @__PURE__ */ new Set();
15659
- const visit = (node) => {
15835
+ const initializers = /* @__PURE__ */ new Map();
15836
+ const leadingDoc = (start) => {
15837
+ let low = 0, high = comments.length;
15838
+ while (low < high) {
15839
+ const middle = low + high >>> 1;
15840
+ if (comments[middle].end <= start) low = middle + 1;
15841
+ else high = middle;
15842
+ }
15843
+ const comment = comments[low - 1];
15844
+ return comment && /^\s*$/.test(source.slice(comment.end, start)) ? comment : void 0;
15845
+ };
15846
+ const directEval = (node) => {
15847
+ if ([
15848
+ "FunctionDeclaration",
15849
+ "FunctionExpression",
15850
+ "ArrowFunctionExpression"
15851
+ ].includes(node.type)) return false;
15852
+ if (node.type === "CallExpression" && node.callee.type === "Identifier" && node.callee.name === "eval" && !node.optional) return true;
15853
+ return Object.entries(node).some(([key, value]) => ![
15854
+ "loc",
15855
+ "trailing",
15856
+ "tag"
15857
+ ].includes(key) && (Array.isArray(value) ? value : [value]).some((child) => child?.type && directEval(child)));
15858
+ };
15859
+ const grouping = (start, end, initializer = false) => {
15860
+ const parentheses = [];
15861
+ const tokens = Parser.tokenizer(source.slice(start, end), { ecmaVersion: "latest" });
15862
+ for (;;) {
15863
+ const token = tokens.getToken();
15864
+ if (token.type === types$1.eof) break;
15865
+ if (initializer && token.type === types$1.eq) parentheses.length = 0;
15866
+ if (token.type === types$1.parenL || token.type === types$1.parenR) parentheses.push(token);
15867
+ }
15868
+ for (const token of parentheses) code.remove(start + token.start, start + token.end);
15869
+ };
15870
+ const visit = (node, parent) => {
15871
+ if (node.type === "VariableDeclaration" && node.declarations.length === 1) {
15872
+ const declaration = node.declarations[0];
15873
+ if (declaration.id.type === "Identifier" && declaration.init?.type === "TwillSwitchExpression" && [
15874
+ "Program",
15875
+ "BlockStatement",
15876
+ "SwitchCase",
15877
+ "ExportNamedDeclaration"
15878
+ ].includes(parent?.type ?? "")) {
15879
+ const statement = parent?.type === "ExportNamedDeclaration" ? parent : node;
15880
+ const comment = leadingDoc(statement.start);
15881
+ const prefixStart = comment && source.startsWith("/**", comment.start) ? comment.start : statement.start;
15882
+ 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, {
15883
+ declaration,
15884
+ statement,
15885
+ prefixStart
15886
+ });
15887
+ }
15888
+ }
15660
15889
  if (node.type === "ReturnStatement" && node.argument?.type === "TwillSwitchExpression") {
15661
15890
  returns.add(node.argument);
15662
15891
  code.remove(node.start, node.start + 6);
15663
- for (const [start, end] of [[node.start + 6, node.argument.start], [node.argument.end, node.end]]) {
15664
- const tokens = Parser.tokenizer(source.slice(start, end), { ecmaVersion: "latest" });
15665
- for (;;) {
15666
- const token = tokens.getToken();
15667
- if (token.type === types$1.eof) break;
15668
- if (token.type === types$1.parenL || token.type === types$1.parenR) code.remove(start + token.start, start + token.end);
15669
- }
15670
- }
15892
+ for (const [start, end] of [[node.start + 6, node.argument.start], [node.argument.end, node.end]]) grouping(start, end);
15671
15893
  }
15672
15894
  for (const [key, value] of Object.entries(node)) {
15673
15895
  if ([
@@ -15675,7 +15897,7 @@ function lowerSwitchExpressions(source, ast, switches, code, language, usedNames
15675
15897
  "trailing",
15676
15898
  "tag"
15677
15899
  ].includes(key)) continue;
15678
- for (const child of Array.isArray(value) ? value : [value]) if (child?.type) visit(child);
15900
+ for (const child of Array.isArray(value) ? value : [value]) if (child?.type) visit(child, node);
15679
15901
  }
15680
15902
  };
15681
15903
  visit(ast);
@@ -15691,26 +15913,84 @@ function lowerSwitchExpressions(source, ast, switches, code, language, usedNames
15691
15913
  for (const node of switches) {
15692
15914
  const direct = returns.has(node);
15693
15915
  const suspended = !direct && suspension(node);
15694
- if (suspended) fail(suspended, "await/yield inside a switch expression requires a direct return. Bind the subject first, or await the whole result explicitly.");
15916
+ 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.`);
15917
+ const initializer = initializers.get(node);
15918
+ const result = initializer ? fresh("Result") : void 0;
15919
+ const exit = initializer ? fresh("Exit") : void 0;
15695
15920
  const subject = fresh("Subject");
15696
- code.appendLeft(node.start, `${direct ? "{" : "(() => {"}const ${subject} = `);
15921
+ if (initializer) {
15922
+ const { declaration, statement, prefixStart } = initializer;
15923
+ const annotation = language.startsWith("ts") && declaration.id.typeAnnotation ? `: typeof ${declaration.id.name}` : "";
15924
+ grouping(declaration.id.end, node.start, true);
15925
+ grouping(node.end, statement.end);
15926
+ code.appendLeft(prefixStart, `let ${result}${annotation}; ${exit}: {const ${subject} = `);
15927
+ if (prefixStart < statement.start) code.prependRight(prefixStart, "\n");
15928
+ code.move(prefixStart, node.start, node.end);
15929
+ code.appendRight(node.end, ` ${result}`);
15930
+ }
15931
+ const valuePrefix = result ? `${result} = (` : "return ";
15932
+ const valueSuffix = result ? `); break ${exit};` : ";";
15933
+ const kind = node.cases.some((branch) => branch.enumPattern) ? fresh("Kind") : void 0;
15934
+ if (!initializer) code.appendLeft(node.start, `${direct ? "{" : "(() => {"}const ${subject} = `);
15697
15935
  code.remove(node.parenStart, node.parenStart + 1);
15698
- code.move(node.start, node.start + 6, node.parenEnd);
15699
- code.appendLeft(node.parenEnd, "; ");
15700
- code.overwrite(node.parenEnd, node.parenEnd + 1, ` (${subject}${node.discriminator === void 0 ? "" : `[${JSON.stringify(node.discriminator)}]`})`);
15701
- for (const branch of node.cases) if (branch.pattern) {
15936
+ if (node.keyword === "match") {
15937
+ const tokens = Parser.tokenizer(source.slice(node.discriminant.end, node.parenEnd), { ecmaVersion: "latest" });
15938
+ for (;;) {
15939
+ const token = tokens.getToken();
15940
+ if (token.type === types$1.eof) break;
15941
+ if (token.type === types$1.comma) code.remove(node.discriminant.end + token.start, node.discriminant.end + token.end);
15942
+ }
15943
+ }
15944
+ const keywordEnd = node.start + (node.keyword === "match" ? 5 : 6);
15945
+ if (node.keyword === "match") code.overwrite(node.start, keywordEnd, "switch");
15946
+ code.move(node.start, keywordEnd, node.parenEnd);
15947
+ code.appendLeft(node.parenEnd, `; ${kind ? `const ${kind} = ${subject}["kind"]; ` : ""}`);
15948
+ code.overwrite(node.parenEnd, node.parenEnd + 1, ` (${kind ?? subject}${kind || node.discriminator === void 0 ? "" : `[${JSON.stringify(node.discriminator)}]`})`);
15949
+ for (const branch of node.cases) if (branch.enumPattern) {
15950
+ const { reference, binding, start, parenStart, parenEnd, explicitKeyword } = branch.enumPattern;
15951
+ const tag = JSON.stringify(reference.property.name);
15952
+ if (language.startsWith("ts")) {
15953
+ const witness = `(${tag} satisfies (typeof `;
15954
+ if (explicitKeyword) code.overwrite(start, start + 4, witness);
15955
+ else code.appendLeft(start, witness);
15956
+ code.appendLeft(reference.end, ` extends (...args: never[]) => { readonly kind: ${tag} } ? ${tag} : never))`);
15957
+ } else if (explicitKeyword) {
15958
+ code.overwrite(start, start + 4, tag);
15959
+ code.remove(reference.start, reference.end);
15960
+ } else code.overwrite(reference.start, reference.end, tag);
15961
+ code.remove(parenStart, parenStart + 1);
15962
+ code.remove(parenEnd, parenEnd + 1);
15963
+ if (binding) {
15964
+ code.appendLeft(binding.start, ": { const ");
15965
+ code.overwrite(branch.colonStart, branch.colonStart + 1, ` = ${subject}; ${branch.throw ? "" : valuePrefix}`);
15966
+ code.appendLeft(branch.valueEnd, `${branch.throw ? ";" : valueSuffix} }`);
15967
+ if (source[branch.end - 1] === ";") code.remove(branch.end - 1, branch.end);
15968
+ } else if (!branch.throw) {
15969
+ code.appendLeft(branch.colonStart + 1, ` ${valuePrefix}`);
15970
+ if (result) {
15971
+ code.appendLeft(branch.valueEnd, valueSuffix);
15972
+ if (source[branch.end - 1] === ";") code.remove(branch.end - 1, branch.end);
15973
+ }
15974
+ }
15975
+ } else if (branch.pattern) {
15702
15976
  const { pattern, tag } = branch;
15703
15977
  code.appendLeft(tag.value.start, fresh("Tag"));
15704
15978
  code.move(tag.value.start, tag.value.end, pattern.start);
15705
15979
  code.appendLeft(tag.value.end, ": { const ");
15706
- code.overwrite(branch.colonStart, branch.colonStart + 1, ` = ${subject}; ${branch.throw ? "" : "return "}`);
15707
- code.appendLeft(branch.valueEnd, "; }");
15980
+ code.overwrite(branch.colonStart, branch.colonStart + 1, ` = ${subject}; ${branch.throw ? "" : valuePrefix}`);
15981
+ code.appendLeft(branch.valueEnd, `${branch.throw ? ";" : valueSuffix} }`);
15708
15982
  if (source[branch.end - 1] === ";") code.remove(branch.end - 1, branch.end);
15709
- } else if (!branch.throw) code.appendLeft(branch.colonStart + 1, " return ");
15710
- const hasDefault = node.cases.some((branch) => !branch.pattern && branch.test === null);
15711
- const check = !hasDefault && language.startsWith("ts") ? ` ${subject} satisfies never;` : "";
15983
+ } else if (!branch.throw) {
15984
+ code.appendLeft(branch.colonStart + 1, ` ${valuePrefix}`);
15985
+ if (result) {
15986
+ code.appendLeft(branch.valueEnd, valueSuffix);
15987
+ if (source[branch.end - 1] === ";") code.remove(branch.end - 1, branch.end);
15988
+ }
15989
+ }
15990
+ const hasDefault = node.cases.some((branch) => !branch.pattern && !branch.enumPattern && branch.test === null);
15991
+ const check = !hasDefault && language.startsWith("ts") ? ` ${kind ? `(0 as unknown as typeof ${kind})` : subject} satisfies never;` : "";
15712
15992
  const failure = hasDefault ? "" : `${check} throw new TypeError('Non-exhaustive switch expression');`;
15713
- code.overwrite(node.end - 1, node.end, `}${failure}${direct ? "}" : "})()"}`);
15993
+ code.overwrite(node.end - 1, node.end, `}${failure}${direct || initializer ? "}" : "})()"}`, { contentOnly: true });
15714
15994
  }
15715
15995
  }
15716
15996
  //#endregion
@@ -15842,7 +16122,7 @@ function normalizePath(url, type) {
15842
16122
  /**
15843
16123
  * Attempts to resolve `input` URL/path relative to `base`.
15844
16124
  */
15845
- function resolve$3(input, base) {
16125
+ function resolve$4(input, base) {
15846
16126
  if (!input && !base) return "";
15847
16127
  const url = parseUrl(input);
15848
16128
  let inputType = url.type;
@@ -15887,7 +16167,7 @@ function stripFilename(path) {
15887
16167
  function resolver(mapUrl, sourceRoot) {
15888
16168
  const from = stripFilename(mapUrl);
15889
16169
  const prefix = sourceRoot ? sourceRoot + "/" : "";
15890
- return (source) => resolve$3(prefix + (source || ""), from);
16170
+ return (source) => resolve$4(prefix + (source || ""), from);
15891
16171
  }
15892
16172
  var COLUMN$1 = 0;
15893
16173
  var SOURCES_INDEX$1 = 1;
@@ -16028,6 +16308,10 @@ var TraceMap = class {
16028
16308
  function cast$1(map) {
16029
16309
  return map;
16030
16310
  }
16311
+ function encodedMappings(map) {
16312
+ var _a, _b;
16313
+ return (_b = (_a = cast$1(map))._encoded) != null ? _b : _a._encoded = encode(cast$1(map)._decoded);
16314
+ }
16031
16315
  function decodedMappings(map) {
16032
16316
  var _a;
16033
16317
  return (_a = cast$1(map))._decoded || (_a._decoded = decode(cast$1(map)._encoded));
@@ -16113,11 +16397,193 @@ function generatedPosition$1(map, source, line, column, bias, all) {
16113
16397
  return GMapping(segment[REV_GENERATED_LINE] + 1, segment[REV_GENERATED_COLUMN]);
16114
16398
  }
16115
16399
  //#endregion
16116
- //#region src/compiler.ts
16400
+ //#region src/enums.ts
16401
+ /** Find free type references, respecting generic function/mapped/infer binders. */
16402
+ function references(node, names, found = /* @__PURE__ */ new Set(), bound = /* @__PURE__ */ new Set()) {
16403
+ const local = new Set(bound);
16404
+ for (const parameter of node.typeParameters?.params ?? []) local.add(parameter.name);
16405
+ if (node.type === "TSMappedType") local.add(node.typeParameter.name);
16406
+ if (node.type === "TSInferType") local.add(node.typeParameter.name);
16407
+ if (node.type === "TSTypeReference" && node.typeName.type === "Identifier") {
16408
+ const name = node.typeName.name;
16409
+ if (names.has(name) && !local.has(name)) found.add(name);
16410
+ }
16411
+ for (const [key, value] of Object.entries(node)) {
16412
+ if (["loc", "typeName"].includes(key)) continue;
16413
+ for (const child of Array.isArray(value) ? value : [value]) {
16414
+ if (!child?.type) continue;
16415
+ const childBound = new Set(local);
16416
+ if (node.type === "TSConditionalType" && key === "trueType") {
16417
+ const bind = (part) => {
16418
+ if (part.type === "TSInferType") childBound.add(part.typeParameter.name);
16419
+ for (const [property, nested] of Object.entries(part)) {
16420
+ if (property === "loc") continue;
16421
+ if (part.type === "TSConditionalType" && property === "extendsType") continue;
16422
+ for (const item of Array.isArray(nested) ? nested : [nested]) if (item?.type) bind(item);
16423
+ }
16424
+ };
16425
+ bind(node.extendsType);
16426
+ }
16427
+ references(child, names, found, childBound);
16428
+ }
16429
+ }
16430
+ return found;
16431
+ }
16432
+ function enumTypeParameters(node, branch) {
16433
+ const parameters = node.typeParameters?.params ?? [];
16434
+ const names = new Set(parameters.map((parameter) => parameter.name));
16435
+ const used = /* @__PURE__ */ new Set();
16436
+ for (const field of branch.params) references(field.typeAnnotation, names, used);
16437
+ for (let size = -1; size !== used.size;) {
16438
+ size = used.size;
16439
+ for (const parameter of parameters) {
16440
+ if (!used.has(parameter.name)) continue;
16441
+ if (parameter.constraint) references(parameter.constraint, names, used);
16442
+ if (parameter.default) references(parameter.default, names, used);
16443
+ }
16444
+ }
16445
+ return parameters.filter((parameter) => used.has(parameter.name));
16446
+ }
16447
+ /** Tagged records and precise factories; no application runtime is introduced. */
16448
+ function lowerEnums(source, ast, enums, code) {
16449
+ const insertions = [];
16450
+ const exported = /* @__PURE__ */ new Set();
16451
+ const visit = (node) => {
16452
+ if (node.type === "ExportNamedDeclaration") exported.add(node.declaration);
16453
+ for (const [key, value] of Object.entries(node)) {
16454
+ if (["loc", "trailing"].includes(key)) continue;
16455
+ for (const child of Array.isArray(value) ? value : [value]) if (child?.type) visit(child);
16456
+ }
16457
+ };
16458
+ visit(ast);
16459
+ for (const node of enums) {
16460
+ const replacement = {
16461
+ anchor: node.braceStart,
16462
+ before: false,
16463
+ text: "= ",
16464
+ copies: []
16465
+ };
16466
+ for (const [index, branch] of node.cases.entries()) {
16467
+ if (index) replacement.text += " | ";
16468
+ replacement.text += `{ readonly kind: ${JSON.stringify(branch.id.name)}; `;
16469
+ for (const field of branch.params) {
16470
+ replacement.text += "readonly ";
16471
+ const text = source.slice(field.start, field.typeAnnotation.end);
16472
+ replacement.copies.push({
16473
+ start: replacement.text.length,
16474
+ sourceStart: field.start,
16475
+ length: text.length
16476
+ });
16477
+ replacement.text += text + "; ";
16478
+ }
16479
+ replacement.text += "}";
16480
+ }
16481
+ replacement.text += `; ${exported.has(node) ? "export " : ""}const `;
16482
+ replacement.copies.push({
16483
+ start: replacement.text.length,
16484
+ sourceStart: node.id.start,
16485
+ length: node.id.end - node.id.start
16486
+ });
16487
+ replacement.text += source.slice(node.id.start, node.id.end) + " = {";
16488
+ insertions.push(replacement);
16489
+ code.overwrite(node.start, node.start + 4, "type");
16490
+ code.overwrite(node.braceStart, node.braceStart + 1, replacement.text);
16491
+ for (const branch of node.cases) {
16492
+ code.remove(branch.start, branch.start + 4);
16493
+ const parameters = enumTypeParameters(node, branch);
16494
+ const copied = {
16495
+ anchor: branch.id.end,
16496
+ before: true,
16497
+ text: parameters.length ? "<" : "",
16498
+ copies: []
16499
+ };
16500
+ for (const [index, parameter] of parameters.entries()) {
16501
+ if (index) copied.text += ", ";
16502
+ const text = source.slice(parameter.start, parameter.end);
16503
+ copied.copies.push({
16504
+ start: copied.text.length,
16505
+ sourceStart: parameter.start,
16506
+ length: text.length
16507
+ });
16508
+ copied.text += text;
16509
+ }
16510
+ if (parameters.length) copied.text += ">";
16511
+ const generic = copied.text;
16512
+ if (generic) insertions.push(copied);
16513
+ code.appendLeft(branch.id.end, generic + (branch.hasParens ? "" : "()"));
16514
+ const fields = branch.params.map((field) => field.name);
16515
+ code.appendLeft(branch.valueEnd, `{ return { kind: ${JSON.stringify(branch.id.name)}${fields.length ? ", " + fields.join(", ") : ""} } as const; },`);
16516
+ if (source[branch.end - 1] === ";") code.remove(branch.end - 1, branch.end);
16517
+ }
16518
+ code.appendLeft(node.end, ";");
16519
+ }
16520
+ return insertions;
16521
+ }
16522
+ /** Generated type copies map to their actual source tokens, including .d.ts maps. */
16523
+ function mapEnumCopies(source, generated, map, insertions) {
16524
+ const starts = (text) => {
16525
+ const result = [0];
16526
+ for (let index = 0; index < text.length; index++) if (text[index] === "\n") result.push(index + 1);
16527
+ return result;
16528
+ };
16529
+ const originalLines = starts(source), generatedLines = starts(generated);
16530
+ const position = (lines, offset) => {
16531
+ let line = 0;
16532
+ while (line + 1 < lines.length && lines[line + 1] <= offset) line++;
16533
+ return {
16534
+ line,
16535
+ column: offset - lines[line]
16536
+ };
16537
+ };
16538
+ const trace = new TraceMap(map);
16539
+ const decoded = decodedMappings(trace);
16540
+ const copies = [];
16541
+ for (const insertion of insertions) {
16542
+ const anchor = position(originalLines, insertion.anchor);
16543
+ const location = generatedPositionFor(trace, {
16544
+ source: map.sources[0],
16545
+ line: anchor.line + 1,
16546
+ column: anchor.column
16547
+ });
16548
+ const offset = generatedLines[location.line - 1] + location.column - (insertion.before ? insertion.text.length : 0);
16549
+ if (generated.slice(offset, offset + insertion.text.length) !== insertion.text) throw new Error("Enum mapping anchor does not match generated text.");
16550
+ for (const copy of insertion.copies) {
16551
+ copies.push({
16552
+ generatedStart: offset + copy.start,
16553
+ sourceStart: copy.sourceStart,
16554
+ length: copy.length
16555
+ });
16556
+ for (let index = 0; index < copy.length; index++) {
16557
+ const target = position(generatedLines, offset + copy.start + index);
16558
+ const original = position(originalLines, copy.sourceStart + index);
16559
+ const line = decoded[target.line];
16560
+ const entry = [
16561
+ target.column,
16562
+ 0,
16563
+ original.line,
16564
+ original.column
16565
+ ];
16566
+ const existing = line.findIndex((segment) => segment[0] === target.column);
16567
+ if (existing >= 0) line[existing] = entry;
16568
+ else line.push(entry);
16569
+ }
16570
+ }
16571
+ }
16572
+ for (const line of decoded) line.sort((left, right) => left[0] - right[0]);
16573
+ map.mappings = encodedMappings(new TraceMap({
16574
+ ...JSON.parse(map.toString()),
16575
+ mappings: decoded
16576
+ }));
16577
+ return copies;
16578
+ }
16579
+ //#endregion
16580
+ //#region src/extensions.ts
16117
16581
  var extensions = [".twill", ".twillx"];
16118
16582
  function isTwillFile(id) {
16119
16583
  return extensions.some((extension) => id.split(/[?#]/, 1)[0].endsWith(extension));
16120
16584
  }
16585
+ //#endregion
16586
+ //#region src/compiler.ts
16121
16587
  function inferLanguage(filename) {
16122
16588
  if (/\.(?:twillx|tsx)$/.test(filename)) return "tsx";
16123
16589
  if (/\.jsx$/.test(filename)) return "jsx";
@@ -16160,6 +16626,7 @@ function calleeName(node) {
16160
16626
  }
16161
16627
  }
16162
16628
  function transform(source, options = {}) {
16629
+ if (options.runtime !== void 0 && !["inline", "external"].includes(options.runtime)) throw new TypeError("runtime must be inline or external");
16163
16630
  const filename = options.filename ?? "input.twill";
16164
16631
  let parsed;
16165
16632
  try {
@@ -16168,11 +16635,28 @@ function transform(source, options = {}) {
16168
16635
  throw new TwillSyntaxError(source, filename, error);
16169
16636
  }
16170
16637
  const code = new MagicString(source);
16638
+ const enumInsertions = parsed.enums.length ? lowerEnums(source, parsed.ast, parsed.enums, code) : [];
16171
16639
  const usedNames = /* @__PURE__ */ new Set();
16172
- 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) => {
16173
- if (node.type === "Identifier") usedNames.add(node.name);
16640
+ 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) => {
16641
+ if (node.type === "Identifier" || node.type === "JSXIdentifier") usedNames.add(node.name);
16174
16642
  });
16175
16643
  let guardCounter = 0;
16644
+ const ifBindingStarts = new Set(parsed.ifBindings.map((node) => node.consequent.start + 1));
16645
+ for (const statement of parsed.ifBindings) {
16646
+ const binding = statement.binding.declarations[0].id;
16647
+ let temporary;
16648
+ do
16649
+ temporary = `__twillIf${guardCounter++}`;
16650
+ while (usedNames.has(temporary));
16651
+ usedNames.add(temporary);
16652
+ const patternEnd = binding.typeAnnotation?.start ?? binding.end;
16653
+ code.overwrite(statement.start, binding.start, `{const ${temporary}`);
16654
+ code.move(binding.start, patternEnd, statement.consequent.start + 1);
16655
+ code.prependRight(binding.start, "const ");
16656
+ code.appendLeft(patternEnd, ` = ${temporary};`);
16657
+ code.prependLeft(statement.consequent.start, `; if (${temporary} !== null && ${temporary} !== void 0) `);
16658
+ code.appendLeft(statement.end, "}");
16659
+ }
16176
16660
  for (const guard of parsed.guards) {
16177
16661
  if (guard.singleStatement) {
16178
16662
  code.prependLeft(guard.start, "{");
@@ -16379,13 +16863,15 @@ function transform(source, options = {}) {
16379
16863
  const collect = (statement) => {
16380
16864
  switch (statement.type) {
16381
16865
  case "ExpressionStatement":
16382
- code.prependLeft(statement.start, `${collector}.push(`);
16866
+ if (ifBindingStarts.has(statement.start)) code.appendRight(statement.start, `${collector}.push(`);
16867
+ else code.prependLeft(statement.start, `${collector}.push(`);
16383
16868
  code.appendLeft(source[statement.end - 1] === ";" ? statement.end - 1 : statement.end, ")");
16384
16869
  break;
16385
16870
  case "BlockStatement":
16386
16871
  statement.body.forEach(collect);
16387
16872
  break;
16388
16873
  case "IfStatement":
16874
+ case "TwillIfBinding":
16389
16875
  collect(statement.consequent);
16390
16876
  if (statement.alternate) collect(statement.alternate);
16391
16877
  break;
@@ -16429,7 +16915,7 @@ function transform(source, options = {}) {
16429
16915
  if (component) code.prependLeft(closures.at(-1).end, (vue ? "})}" : directChild || metadataByNode.get(closures[0]).header ? ")}" : ")()}") + `</${calleeName(node.callee)}>)`);
16430
16916
  else code.appendLeft(closures.at(-1).end, ")");
16431
16917
  }
16432
- if (parsed.switches.length) lowerSwitchExpressions(source, parsed.ast, parsed.switches, code, options.language ?? inferLanguage(filename), usedNames, (node, message) => {
16918
+ if (parsed.switches.length) lowerSwitchExpressions(source, parsed.ast, parsed.switches, code, options.language ?? inferLanguage(filename), usedNames, parsed.comments, (node, message) => {
16433
16919
  throw new TwillSyntaxError(source, filename, {
16434
16920
  message,
16435
16921
  pos: node.start,
@@ -16440,7 +16926,7 @@ function transform(source, options = {}) {
16440
16926
  if (parsed.defers.length) {
16441
16927
  const starts = /* @__PURE__ */ new Map();
16442
16928
  for (const metadata of parsed.closures) if (metadata.header) starts.set(metadata.node.body, metadata.header.end);
16443
- lowerDefers(source, parsed.ast, code, options.language ?? inferLanguage(filename), usedNames, starts, parsed.comments, (node, message) => {
16929
+ lowerDefers(source, parsed.ast, code, options.language ?? inferLanguage(filename), usedNames, starts, parsed.comments, options.runtime ?? "inline", options.sourceType ?? "module", (node, message) => {
16444
16930
  throw new TwillSyntaxError(source, filename, {
16445
16931
  message,
16446
16932
  pos: node.start,
@@ -16454,14 +16940,19 @@ function transform(source, options = {}) {
16454
16940
  includeContent: true,
16455
16941
  hires: true
16456
16942
  });
16943
+ const enumCopies = enumInsertions.length ? mapEnumCopies(source, code.toString(), map, enumInsertions) : [];
16457
16944
  return {
16458
16945
  code: code.toString(),
16459
16946
  map,
16460
16947
  changed: code.hasChanged(),
16461
16948
  closures: parsed.closures.length,
16462
16949
  guards: parsed.guards.length,
16950
+ ifBindings: parsed.ifBindings.length,
16463
16951
  defers: parsed.defers.length,
16464
16952
  switches: parsed.switches.length,
16953
+ associatedEnums: parsed.enums,
16954
+ enumPatterns: parsed.switches.flatMap((node) => node.cases.flatMap((branch) => branch.enumPattern ? [branch.enumPattern] : [])),
16955
+ enumCopies,
16465
16956
  componentProps
16466
16957
  };
16467
16958
  }
@@ -16490,21 +16981,78 @@ function generatedPosition(result, line, column) {
16490
16981
  });
16491
16982
  }
16492
16983
  //#endregion
16493
- //#region src/config.ts
16984
+ //#region src/compilation-cache.ts
16985
+ var digest = (source) => (0, node_crypto.createHash)("sha256").update(source).digest("hex");
16986
+ function observe(path, kind) {
16987
+ try {
16988
+ if (kind === "directory") return (0, node_fs.statSync)(path).isDirectory() ? "directory" : "absent";
16989
+ return digest((0, node_fs.readFileSync)(path));
16990
+ } catch (error) {
16991
+ return ["ENOENT", "ENOTDIR"].includes(error.code ?? "") ? "absent" : "unavailable";
16992
+ }
16993
+ }
16994
+ //#endregion
16995
+ //#region src/configuration.ts
16494
16996
  var jsxConfigs = /* @__PURE__ */ new Map();
16997
+ function record(observations, path, kind) {
16998
+ path = (0, node_path.resolve)(path);
16999
+ observations.set(kind + path, {
17000
+ path,
17001
+ kind,
17002
+ value: observe(path, kind)
17003
+ });
17004
+ }
16495
17005
  /** Use the project's standard JSX setting, including extended tsconfigs. */
16496
- function jsxSource(root, configFile) {
16497
- const filename = configFile ?? typescript.default.findConfigFile(root, typescript.default.sys.fileExists);
17006
+ function jsxSource(root, configFile, observations) {
17007
+ const system = observations ? {
17008
+ ...typescript.default.sys,
17009
+ fileExists(path) {
17010
+ record(observations, path, "file");
17011
+ return typescript.default.sys.fileExists(path);
17012
+ },
17013
+ directoryExists(path) {
17014
+ record(observations, path, "directory");
17015
+ return typescript.default.sys.directoryExists(path);
17016
+ }
17017
+ } : typescript.default.sys;
17018
+ const filename = configFile ?? typescript.default.findConfigFile(root, system.fileExists);
16498
17019
  if (!filename) return void 0;
16499
17020
  const cached = jsxConfigs.get(filename);
16500
- if (cached && [...cached.files].every(([file, time]) => ((0, node_fs.existsSync)(file) ? (0, node_fs.statSync)(file).mtimeMs : -1) === time)) return cached.source;
17021
+ if (!observations && cached && [...cached.files].every(([file, time]) => ((0, node_fs.existsSync)(file) ? (0, node_fs.statSync)(file).mtimeMs : -1) === time)) return cached.source;
16501
17022
  const files = /* @__PURE__ */ new Map();
16502
17023
  const source = typescript.default.getParsedCommandLineOfConfigFile(filename, {}, {
16503
- ...typescript.default.sys,
17024
+ ...system,
16504
17025
  readDirectory: () => [],
16505
17026
  readFile(file) {
16506
17027
  files.set(file, (0, node_fs.existsSync)(file) ? (0, node_fs.statSync)(file).mtimeMs : -1);
16507
- return typescript.default.sys.readFile(file);
17028
+ if (!observations) return typescript.default.sys.readFile(file);
17029
+ const path = (0, node_path.resolve)(file);
17030
+ let bytes;
17031
+ try {
17032
+ bytes = (0, node_fs.readFileSync)(file);
17033
+ } catch {
17034
+ observations.set("file" + path, {
17035
+ path,
17036
+ kind: "file",
17037
+ value: "unavailable"
17038
+ });
17039
+ return;
17040
+ }
17041
+ observations.set("file" + path, {
17042
+ path,
17043
+ kind: "file",
17044
+ value: digest(bytes)
17045
+ });
17046
+ if (bytes[0] === 254 && bytes[1] === 255) {
17047
+ for (let i = 0; i < (bytes.length & -2); i += 2) {
17048
+ const byte = bytes[i];
17049
+ bytes[i] = bytes[i + 1];
17050
+ bytes[i + 1] = byte;
17051
+ }
17052
+ return bytes.toString("utf16le", 2);
17053
+ }
17054
+ if (bytes[0] === 255 && bytes[1] === 254) return bytes.toString("utf16le", 2);
17055
+ return bytes.toString("utf8", bytes[0] === 239 && bytes[1] === 187 && bytes[2] === 191 ? 3 : 0);
16508
17056
  },
16509
17057
  getCurrentDirectory: () => root,
16510
17058
  onUnRecoverableConfigFileDiagnostic() {}
@@ -16515,19 +17063,35 @@ function jsxSource(root, configFile) {
16515
17063
  });
16516
17064
  return source;
16517
17065
  }
16518
- function loadConfig(root = process.cwd(), configFile) {
17066
+ function readConfig(root, configFile, observations) {
16519
17067
  const filename = (0, node_path.resolve)(root, "twill.config.json");
16520
- const jsxImportSource = jsxSource(root, configFile);
17068
+ const jsxImportSource = jsxSource(root, configFile, observations);
17069
+ if (observations) record(observations, filename, "file");
16521
17070
  if (!(0, node_fs.existsSync)(filename)) return { jsxImportSource };
16522
- const value = JSON.parse((0, node_fs.readFileSync)(filename, "utf8"));
17071
+ const text = (0, node_fs.readFileSync)(filename, "utf8");
17072
+ if (observations) observations.set("file" + filename, {
17073
+ path: filename,
17074
+ kind: "file",
17075
+ value: digest(text)
17076
+ });
17077
+ const value = JSON.parse(text);
16523
17078
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${filename}: expected an object`);
16524
17079
  if (value.implicitReturn !== void 0 && typeof value.implicitReturn !== "boolean") throw new Error(`${filename}: implicitReturn must be a boolean`);
16525
- for (const key of Object.keys(value)) if (!["implicitReturn", "$schema"].includes(key)) throw new Error(`${filename}: unknown option ${key}`);
17080
+ if (value.runtime !== void 0 && !["inline", "external"].includes(value.runtime)) throw new Error(`${filename}: runtime must be inline or external`);
17081
+ for (const key of Object.keys(value)) if (![
17082
+ "implicitReturn",
17083
+ "runtime",
17084
+ "$schema"
17085
+ ].includes(key)) throw new Error(`${filename}: unknown option ${key}`);
16526
17086
  return {
16527
17087
  implicitReturn: value.implicitReturn,
16528
- jsxImportSource
17088
+ jsxImportSource,
17089
+ ...value.runtime === void 0 ? {} : { runtime: value.runtime }
16529
17090
  };
16530
17091
  }
17092
+ function loadConfig(root = process.cwd(), configFile) {
17093
+ return readConfig(root, configFile);
17094
+ }
16531
17095
  //#endregion
16532
17096
  //#region ../../node_modules/.pnpm/@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/gen-mapping/dist/gen-mapping.mjs
16533
17097
  var SetArray = class {
@@ -16839,9 +17403,32 @@ function recoverTransform(source, options) {
16839
17403
  includeContent: true,
16840
17404
  hires: true
16841
17405
  });
17406
+ const repairedResult = {
17407
+ ...result,
17408
+ map: repairMap
17409
+ };
17410
+ const starts = [0];
17411
+ for (const match of source.matchAll(/\n/g)) starts.push(match.index + 1);
17412
+ const sourceOffset = (offset) => {
17413
+ const prefix = text.slice(0, offset).split("\n");
17414
+ const point = originalPosition(repairedResult, prefix.length, prefix.at(-1).length);
17415
+ return starts[point.line - 1] + point.column;
17416
+ };
17417
+ const enumPatterns = result.enumPatterns.map((pattern) => ({
17418
+ ...pattern,
17419
+ reference: {
17420
+ ...pattern.reference,
17421
+ property: {
17422
+ ...pattern.reference.property,
17423
+ start: sourceOffset(pattern.reference.property.start),
17424
+ end: sourceOffset(pattern.reference.property.end)
17425
+ }
17426
+ }
17427
+ }));
16842
17428
  const map = remapping([JSON.parse(result.map.toString()), JSON.parse(repairMap.toString())], () => null);
16843
17429
  return {
16844
17430
  ...result,
17431
+ enumPatterns,
16845
17432
  map
16846
17433
  };
16847
17434
  } catch (error) {
@@ -17241,6 +17828,7 @@ var TwillProject = class {
17241
17828
  var TwillEditor = class {
17242
17829
  project;
17243
17830
  attributeCache = /* @__PURE__ */ new WeakMap();
17831
+ descriptorCache = /* @__PURE__ */ new WeakMap();
17244
17832
  constructor(project) {
17245
17833
  this.project = project;
17246
17834
  }
@@ -17309,10 +17897,11 @@ var TwillEditor = class {
17309
17897
  const source = this.project.text(filename);
17310
17898
  if (source === void 0) return void 0;
17311
17899
  if (!isTwillFile(filename)) return span.start >= 0 && span.start + span.length <= source.length ? span : void 0;
17312
- const generated = this.project.transformed(filename)?.code;
17900
+ const transformed = this.project.transformed(filename);
17901
+ const generated = transformed?.code;
17313
17902
  if (generated === void 0) return void 0;
17314
17903
  const start = this.project.toOriginalOffset(filename, span.start);
17315
- if (start < 0 || start + span.length > source.length || this.project.toGeneratedOffset(filename, start) !== span.start || source.slice(start, start + span.length) !== generated.slice(span.start, span.start + span.length)) return void 0;
17904
+ if (start < 0 || start + span.length > source.length || this.project.toGeneratedOffset(filename, start) !== span.start && !transformed.enumCopies.some((copy) => span.start >= copy.generatedStart && span.start + span.length <= copy.generatedStart + copy.length && start === copy.sourceStart + span.start - copy.generatedStart || start >= copy.sourceStart && start + span.length <= copy.sourceStart + copy.length && this.project.toOriginalOffset(filename, span.start + span.length - 1) === start + span.length - 1) || source.slice(start, start + span.length) !== generated.slice(span.start, span.start + span.length)) return void 0;
17316
17905
  return {
17317
17906
  start,
17318
17907
  length: span.length
@@ -17400,8 +17989,77 @@ var TwillEditor = class {
17400
17989
  references(filename, position) {
17401
17990
  return this.referenceGroups(filename, position)?.flatMap((group) => group.references);
17402
17991
  }
17992
+ descriptorSymbols(program) {
17993
+ const cached = this.descriptorCache.get(program);
17994
+ if (cached) return cached;
17995
+ const symbols = /* @__PURE__ */ new Set();
17996
+ const checker = program.getTypeChecker();
17997
+ for (const file of program.getSourceFiles()) {
17998
+ const original = sourceFilename(file.fileName);
17999
+ if (!isTwillFile(original)) continue;
18000
+ let patterns;
18001
+ try {
18002
+ patterns = this.project.transformed(original)?.enumPatterns;
18003
+ } catch {
18004
+ continue;
18005
+ }
18006
+ if (!patterns?.length) continue;
18007
+ const offsets = new Set(patterns.map((pattern) => this.project.toGeneratedOffset(original, pattern.reference.property.start)));
18008
+ const visit = (node) => {
18009
+ if (typescript.default.isIdentifier(node) && offsets.has(node.getStart())) {
18010
+ const symbol = checker.getSymbolAtLocation(node);
18011
+ if (symbol) for (const root of checker.getRootSymbols(symbol)) symbols.add(root);
18012
+ }
18013
+ typescript.default.forEachChild(node, visit);
18014
+ };
18015
+ visit(file);
18016
+ }
18017
+ this.descriptorCache.set(program, symbols);
18018
+ return symbols;
18019
+ }
17403
18020
  renameInfo(filename, position) {
17404
- const info = this.project.service.getRenameInfo(virtualFilename(filename), this.project.toGeneratedOffset(filename, position), { allowRenameOfImportPath: false });
18021
+ const offset = this.project.toGeneratedOffset(filename, position);
18022
+ const definitions = this.project.service.getDefinitionAtPosition(virtualFilename(filename), offset);
18023
+ const program = this.project.service.getProgram();
18024
+ const descriptors = this.descriptorSymbols(program);
18025
+ for (const definition of definitions ?? []) {
18026
+ const file = sourceFilename(definition.fileName);
18027
+ const definitionFile = this.sourceFile(file);
18028
+ let descriptor = false;
18029
+ const visitDefinition = (node) => {
18030
+ if (definition.textSpan.start < node.getStart() || definition.textSpan.start >= node.end) return;
18031
+ if (typescript.default.isIdentifier(node) || typescript.default.isStringLiteral(node)) {
18032
+ const symbol = program.getTypeChecker().getSymbolAtLocation(node);
18033
+ descriptor = !!symbol && program.getTypeChecker().getRootSymbols(symbol).some((root) => descriptors.has(root));
18034
+ } else typescript.default.forEachChild(node, visitDefinition);
18035
+ };
18036
+ if (descriptors.size && definitionFile) visitDefinition(definitionFile);
18037
+ if (descriptor) return {
18038
+ canRename: false,
18039
+ localizedErrorMessage: "Renaming an enum-pattern case descriptor requires linked discriminator edits and is not supported yet."
18040
+ };
18041
+ const declarations = this.project.transformed(file)?.associatedEnums;
18042
+ if (!declarations?.length) continue;
18043
+ const sourceFile = this.sourceFile(file);
18044
+ let generatedEnum = false;
18045
+ const visit = (node) => {
18046
+ if (definition.textSpan.start < node.getStart() || definition.textSpan.start >= node.end) return;
18047
+ if ((typescript.default.isTypeAliasDeclaration(node) || typescript.default.isVariableDeclaration(node)) && typescript.default.isIdentifier(node.name)) {
18048
+ const start = this.project.toOriginalOffset(file, node.name.getStart());
18049
+ if (declarations.some((declaration) => declaration.id.start === start)) {
18050
+ generatedEnum = true;
18051
+ return;
18052
+ }
18053
+ }
18054
+ typescript.default.forEachChild(node, visit);
18055
+ };
18056
+ if (sourceFile) visit(sourceFile);
18057
+ if (generatedEnum) return {
18058
+ canRename: false,
18059
+ localizedErrorMessage: "Renaming associated-value enum declarations, cases, payload fields and type parameters is not supported yet."
18060
+ };
18061
+ }
18062
+ const info = this.project.service.getRenameInfo(virtualFilename(filename), offset, { allowRenameOfImportPath: false });
17405
18063
  if (!info.canRename) return info;
17406
18064
  const span = this.mapSpan(filename, info.triggerSpan);
17407
18065
  return span ? {