gofront 1.7.0 → 1.9.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 (194) hide show
  1. package/README.md +14 -14
  2. package/package.json +10 -38
  3. package/src/backend/js/expressions.js +2060 -389
  4. package/src/backend/js/index.js +586 -174
  5. package/src/backend/js/natives.js +383 -0
  6. package/src/backend/js/runtime.js +936 -1644
  7. package/src/backend/js/statements.js +1349 -132
  8. package/src/backend/js/stdlib/index.js +51 -94
  9. package/src/backend/js/templ.js +67 -118
  10. package/src/backend/js/type-info.js +436 -0
  11. package/src/backend/shared/fixed-float.js +20 -0
  12. package/src/backend/shared/fmt-types.js +80 -0
  13. package/src/backend/shared/host-unicode.js +36 -0
  14. package/src/backend/wasm/boundary.js +753 -872
  15. package/src/backend/wasm/emit-builtins.js +914 -237
  16. package/src/backend/wasm/emit-exprs.js +1456 -195
  17. package/src/backend/wasm/emit-maps.js +78 -380
  18. package/src/backend/wasm/emit-stmts.js +1659 -214
  19. package/src/backend/wasm/emit.js +614 -88
  20. package/src/backend/wasm/encode.js +43 -13
  21. package/src/backend/wasm/globals.js +491 -0
  22. package/src/backend/wasm/glue.js +2 -4
  23. package/src/backend/wasm/index.js +406 -3195
  24. package/src/backend/wasm/method-expr.js +392 -0
  25. package/src/backend/wasm/module-emitter.js +1615 -0
  26. package/src/backend/wasm/monomorph.js +164 -9
  27. package/src/backend/wasm/natives.js +596 -0
  28. package/src/backend/wasm/prescan.js +249 -0
  29. package/src/backend/wasm/prune-funcs.js +89 -0
  30. package/src/backend/wasm/stdlib/index.js +2 -37
  31. package/src/backend/wasm/stdlib/simd.js +293 -28
  32. package/src/backend/wasm/types.js +260 -27
  33. package/src/backend/wasm/variadic.js +93 -0
  34. package/src/backend/wasm/wat.js +47 -20
  35. package/src/{cli-core.js → cli/cli-core.js} +241 -11
  36. package/src/{test-runner.js → cli/test-runner.js} +188 -122
  37. package/src/{vendor.js → cli/vendor.js} +3 -4
  38. package/src/compiler.js +202 -386
  39. package/src/index.js +15 -169
  40. package/src/{lexer.js → lexer/lexer.js} +203 -62
  41. package/src/{templ-lexer.js → lexer/templ-lexer.js} +61 -18
  42. package/src/{tokens.js → lexer/tokens.js} +2 -0
  43. package/src/link/sourcemap.js +97 -0
  44. package/src/link/stdlib.js +737 -0
  45. package/src/link/type-names.js +148 -0
  46. package/src/link/wasm.js +318 -0
  47. package/src/lower/bounds.js +547 -0
  48. package/src/lower/boxing.js +1 -1
  49. package/src/lower/captures.js +267 -64
  50. package/src/lower/embedding.js +32 -3
  51. package/src/lower/errors-as.js +196 -0
  52. package/src/lower/fmt-formatters.js +905 -0
  53. package/src/lower/functions.js +2 -0
  54. package/src/lower/gen-types.js +274 -0
  55. package/src/lower/index.js +33 -19
  56. package/src/lower/json-codecs.js +1379 -0
  57. package/src/lower/ownership.js +47 -8
  58. package/src/lower/range.js +86 -1
  59. package/src/lower/reachability.js +150 -0
  60. package/src/lower/walk.js +12 -0
  61. package/src/parser/declarations.js +83 -15
  62. package/src/parser/expressions.js +110 -18
  63. package/src/parser/index.js +2 -1
  64. package/src/parser/statements.js +38 -22
  65. package/src/{templ-parser.js → parser/templ.js} +179 -17
  66. package/src/parser/types.js +70 -9
  67. package/src/resolver.js +21 -1
  68. package/src/typechecker/assignability.js +291 -19
  69. package/src/typechecker/const-fold.js +399 -0
  70. package/src/typechecker/constants.js +282 -0
  71. package/src/typechecker/expressions.js +1220 -183
  72. package/src/typechecker/index.js +659 -92
  73. package/src/typechecker/init-order.js +426 -0
  74. package/src/typechecker/operand-text.js +40 -0
  75. package/src/typechecker/resolve.js +613 -62
  76. package/src/typechecker/statements.js +212 -20
  77. package/src/typechecker/stdlib/excluded.js +902 -0
  78. package/src/typechecker/stdlib/index.js +38 -0
  79. package/src/typechecker/stdlib/simd.js +65 -15
  80. package/src/typechecker/termination.js +11 -2
  81. package/src/typechecker/types.js +268 -514
  82. package/stdlib/bytes/buffer.go +479 -0
  83. package/stdlib/bytes/bytes.go +1010 -0
  84. package/stdlib/bytes/reader.go +161 -0
  85. package/stdlib/cmp/cmp.go +65 -0
  86. package/stdlib/context/context.go +115 -0
  87. package/stdlib/crypto/rand/rand.go +34 -0
  88. package/stdlib/crypto/sha256/sha256.go +249 -0
  89. package/stdlib/encoding/base64/base64.go +331 -0
  90. package/stdlib/encoding/binary/binary.go +332 -0
  91. package/stdlib/encoding/hex/hex.go +140 -0
  92. package/stdlib/encoding/json/errors.go +31 -0
  93. package/stdlib/encoding/json/indent.go +71 -0
  94. package/stdlib/encoding/json/json.go +62 -0
  95. package/stdlib/encoding/json/number.go +12 -0
  96. package/stdlib/encoding/json/stream.go +173 -0
  97. package/stdlib/errors/errors.go +174 -0
  98. package/stdlib/fmt/errors.go +110 -0
  99. package/stdlib/fmt/format.go +732 -0
  100. package/stdlib/fmt/print.go +1232 -0
  101. package/stdlib/gom/gom.go +293 -0
  102. package/stdlib/hash/hash.go +53 -0
  103. package/stdlib/html/html.go +251 -0
  104. package/stdlib/internal/fmtrt/fmtrt.go +163 -0
  105. package/stdlib/internal/jsonrt/dec.go +871 -0
  106. package/stdlib/internal/jsonrt/enc.go +516 -0
  107. package/stdlib/internal/jsonrt/indent.go +135 -0
  108. package/stdlib/internal/jsonrt/number.go +27 -0
  109. package/stdlib/internal/jsonrt/reader.go +73 -0
  110. package/stdlib/internal/jsonrt/scanner.go +658 -0
  111. package/stdlib/io/io.go +502 -0
  112. package/stdlib/iter/iter.go +18 -0
  113. package/stdlib/maps/maps.go +131 -0
  114. package/stdlib/math/bits/bits.go +507 -0
  115. package/stdlib/math/extra.go +272 -0
  116. package/stdlib/math/math.go +439 -0
  117. package/stdlib/math/rand/rand.go +297 -0
  118. package/stdlib/os/os.go +93 -0
  119. package/stdlib/path/filepath/filepath.go +47 -0
  120. package/stdlib/path/path.go +394 -0
  121. package/stdlib/regexp/regexp.go +231 -0
  122. package/stdlib/slices/slices.go +526 -0
  123. package/stdlib/slices/sort.go +209 -0
  124. package/stdlib/slices/zsort_cmp.go +480 -0
  125. package/stdlib/slices/zsort_ordered.go +482 -0
  126. package/stdlib/sort/sort.go +500 -0
  127. package/stdlib/sort/zsort_func.go +480 -0
  128. package/stdlib/sort/zsort_iface.go +480 -0
  129. package/stdlib/strconv/float.go +1699 -0
  130. package/stdlib/strconv/isprint.go +752 -0
  131. package/stdlib/strconv/quote.go +526 -0
  132. package/stdlib/strconv/strconv.go +616 -0
  133. package/stdlib/strings/builder.go +95 -0
  134. package/stdlib/strings/reader.go +163 -0
  135. package/stdlib/strings/replacer.go +102 -0
  136. package/stdlib/strings/strings.go +807 -0
  137. package/stdlib/sync/sync.go +268 -0
  138. package/stdlib/testing/async_js.go +61 -0
  139. package/stdlib/testing/benchmark.go +297 -0
  140. package/stdlib/testing/testing.go +523 -0
  141. package/stdlib/time/duration.go +233 -0
  142. package/stdlib/time/format.go +745 -0
  143. package/stdlib/time/parse.go +790 -0
  144. package/stdlib/time/sleep_js.go +63 -0
  145. package/stdlib/time/time.go +834 -0
  146. package/stdlib/unicode/unicode.go +250 -0
  147. package/stdlib/unicode/utf8/utf8.go +464 -0
  148. package/src/backend/js/stdlib/base64.js +0 -12
  149. package/src/backend/js/stdlib/binary.js +0 -102
  150. package/src/backend/js/stdlib/bits.js +0 -46
  151. package/src/backend/js/stdlib/builder.js +0 -63
  152. package/src/backend/js/stdlib/bytes.js +0 -122
  153. package/src/backend/js/stdlib/errors.js +0 -44
  154. package/src/backend/js/stdlib/fmt.js +0 -178
  155. package/src/backend/js/stdlib/gom.js +0 -155
  156. package/src/backend/js/stdlib/hex.js +0 -17
  157. package/src/backend/js/stdlib/html.js +0 -20
  158. package/src/backend/js/stdlib/io.js +0 -137
  159. package/src/backend/js/stdlib/json.js +0 -972
  160. package/src/backend/js/stdlib/maps.js +0 -45
  161. package/src/backend/js/stdlib/math.js +0 -69
  162. package/src/backend/js/stdlib/os.js +0 -13
  163. package/src/backend/js/stdlib/path.js +0 -48
  164. package/src/backend/js/stdlib/rand.js +0 -32
  165. package/src/backend/js/stdlib/regexp.js +0 -54
  166. package/src/backend/js/stdlib/sha256.js +0 -12
  167. package/src/backend/js/stdlib/shared.js +0 -17
  168. package/src/backend/js/stdlib/simd.js +0 -24
  169. package/src/backend/js/stdlib/slices.js +0 -77
  170. package/src/backend/js/stdlib/sort.js +0 -30
  171. package/src/backend/js/stdlib/strconv.js +0 -74
  172. package/src/backend/js/stdlib/strings.js +0 -222
  173. package/src/backend/js/stdlib/sync.js +0 -13
  174. package/src/backend/js/stdlib/testing.js +0 -34
  175. package/src/backend/js/stdlib/time.js +0 -128
  176. package/src/backend/js/stdlib/unicode.js +0 -25
  177. package/src/backend/js/stdlib/utf8.js +0 -26
  178. package/src/backend/wasm/const-fold.js +0 -205
  179. package/src/backend/wasm/emit-stdlib.js +0 -13
  180. package/src/backend/wasm/stdlib/bytes.js +0 -5039
  181. package/src/backend/wasm/stdlib/core.js +0 -8903
  182. package/src/backend/wasm/stdlib/io.js +0 -2161
  183. package/src/backend/wasm/stdlib/packages.js +0 -2400
  184. package/src/backend/wasm/stdlib/pure.js +0 -720
  185. package/src/backend/wasm/stdlib/time.js +0 -1200
  186. package/src/typechecker/stdlib/core.js +0 -801
  187. package/src/typechecker/stdlib/extended.js +0 -914
  188. package/src/typechecker/stdlib.js +0 -22
  189. /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
  190. /package/src/{colors.js → cli/colors.js} +0 -0
  191. /package/src/{dev-server.js → cli/dev-server.js} +0 -0
  192. /package/src/{minifier.js → cli/minifier.js} +0 -0
  193. /package/src/{project-config.js → cli/project-config.js} +0 -0
  194. /package/src/{pwa.js → cli/pwa.js} +0 -0
@@ -1,74 +1,56 @@
1
1
  // CodeGen stdlib call generation — installed as a mixin on CodeGen.prototype.
2
- // One file per Go package under ./stdlib/. This index merges them.
2
+ // Dispatches `gofront/shared` and `gofront/simd` calls to inline JS.
3
3
 
4
- import { base64Methods } from "./base64.js";
5
- import { binaryMethods } from "./binary.js";
6
- import { bitsMethods } from "./bits.js";
7
- import { builderMethods } from "./builder.js";
8
- import { bytesMethods } from "./bytes.js";
9
- import { errorsMethods } from "./errors.js";
10
- import { fmtMethods } from "./fmt.js";
11
- import { gomMethods } from "./gom.js";
12
- import { hexMethods } from "./hex.js";
13
- import { htmlMethods } from "./html.js";
14
- import { ioMethods } from "./io.js";
15
- import { jsonMethods } from "./json.js";
16
- import { mapsMethods } from "./maps.js";
17
- import { mathMethods } from "./math.js";
18
- import { osMethods } from "./os.js";
19
- import { pathMethods } from "./path.js";
20
- import { randMethods } from "./rand.js";
21
- import { regexpMethods } from "./regexp.js";
22
- import { sha256Methods } from "./sha256.js";
23
- import { sharedMethods } from "./shared.js";
24
- import { simdMethods } from "./simd.js";
25
- import { slicesMethods } from "./slices.js";
26
- import { sortMethods } from "./sort.js";
27
- import { strconvMethods } from "./strconv.js";
28
- import { stringsMethods } from "./strings.js";
29
- import { syncMethods } from "./sync.js";
30
- import { testingMethods } from "./testing.js";
31
- import { timeMethods } from "./time.js";
32
- import { unicodeMethods } from "./unicode.js";
33
- import { utf8Methods } from "./utf8.js";
4
+ import { SHARED_KINDS } from "../../../typechecker/stdlib/shared.js";
34
5
 
35
6
  /** @typedef {import('../index.js').CodeGen} CodeGen */
36
7
 
37
8
  // Stdlib namespace → handler method name. Used by _genStdlibCall to dispatch
38
9
  // dynamically without creating static call edges.
39
10
  const STDLIB_METHOD_MAP = {
40
- fmt: "_genFmt",
41
- strings: "_genStrings",
42
- bytes: "_genBytes",
43
- strconv: "_genStrconv",
44
- sort: "_genSort",
45
- math: "_genMath",
46
- bits: "_genBits",
47
- unicode: "_genUnicode",
48
- os: "_genOs",
49
- errors: "_genErrors",
50
- time: "_genTime",
51
- regexp: "_genRegexp",
52
- slices: "_genSlices",
53
- maps: "_genMaps",
54
- html: "_genHtml",
55
- io: "_genIo",
56
- rand: "_genRand",
57
- utf8: "_genUtf8",
58
- path: "_genPath",
59
- filepath: "_genPath",
60
- testing: "_genTesting",
61
11
  shared: "_genShared",
62
- sync: "_genSync",
63
- sha256: "_genSha256",
64
- binary: "_genBinary",
65
- hex: "_genHex",
66
- base64: "_genBase64",
67
- json: "_genJson",
68
12
  simd: "_genSimd",
69
13
  };
70
14
 
71
15
  const dispatchMethods = {
16
+ // `gofront/shared`: in js packages a shared buffer is just a TypedArray (no
17
+ // linear memory is involved); in wasm packages the facade hands out
18
+ // zero-copy views onto the module memory.
19
+ _genShared(fn, a) {
20
+ if (!fn.startsWith("New")) return undefined;
21
+ const info = SHARED_KINDS[fn.slice(3)];
22
+ if (!info) return undefined;
23
+ return `new ${info.ctor}(${a()[0]})`;
24
+ },
25
+
26
+ // `gofront/simd` in JS packages.
27
+ _genSimd(fn, a) {
28
+ this.useHelper("simd");
29
+ const args = a();
30
+ switch (fn) {
31
+ case "Splat":
32
+ return `__simd_splat(${args[0]})`;
33
+ case "NewF32x4":
34
+ return `__simd_f32x4(${args[0]}, ${args[1]}, ${args[2]}, ${args[3]})`;
35
+ case "Select":
36
+ return `__simd_select(${args[0]}, ${args[1]}, ${args[2]})`;
37
+ case "Shuffle":
38
+ return `__simd_shuffle(${args.join(", ")})`;
39
+ case "LoadF32x4":
40
+ return `__simd_load(${args[0]}, ${args[1]})`;
41
+ case "StoreF32x4":
42
+ return `__simd_store(${args[0]}, ${args[1]}, ${args[2]})`;
43
+ case "LoadF32x4Slice":
44
+ return `__simd_load_slice(${args[0]}, ${args[1]})`;
45
+ case "StoreF32x4Slice":
46
+ return `__simd_store_slice(${args[0]}, ${args[1]}, ${args[2]})`;
47
+ case "Pad4":
48
+ return `(Math.floor((${args[0]} + 3) / 4) * 4)`;
49
+ default:
50
+ return undefined;
51
+ }
52
+ },
53
+
72
54
  typeComment(typeNode) {
73
55
  if (!typeNode) return "unknown";
74
56
  switch (typeNode.kind) {
@@ -86,50 +68,25 @@ const dispatchMethods = {
86
68
  // Dispatch table for stdlib namespace calls (pkg.Func → inline JS).
87
69
  // Returns the generated JS string, or undefined if not handled.
88
70
  _genStdlibCall(ns, fn, expr) {
89
- // Stdlib shims take raw values; only fmt and errors use the interface box
90
- // of a named value (Error() dispatch, error comparisons).
71
+ const method = STDLIB_METHOD_MAP[ns];
72
+ if (!method) return undefined;
73
+ // A member written in Go is an ordinary call of its mangled name.
74
+ if (expr.func?._stdName) return undefined;
75
+ // Stdlib shims take raw values, not the interface box of a named value.
91
76
  const isNamespace = expr.func?.expr?._type?.kind === "namespace";
92
- if (isNamespace && ns !== "fmt" && ns !== "errors") {
77
+ if (isNamespace) {
93
78
  for (const e of expr.args) if (e) e._noNamedBox = true;
94
79
  }
95
80
  const a = () => expr.args.map((e) => this.genExpr(e));
96
- if (ns === "gom") return this._genGom(fn, expr);
97
- const method = STDLIB_METHOD_MAP[ns];
98
- return method ? this[method](fn, a, expr) : undefined;
81
+ const res = this[method](fn, a, expr);
82
+ if (res === undefined && isNamespace) {
83
+ for (const e of expr.args) if (e) delete e._noNamedBox;
84
+ }
85
+ return res;
99
86
  },
100
87
  };
101
88
 
102
89
  /** @type {ThisType<CodeGen>} */
103
90
  export const stdlibGenMethods = {
104
- ...bitsMethods,
105
- ...builderMethods,
106
- ...bytesMethods,
107
- ...errorsMethods,
108
- ...fmtMethods,
109
- ...gomMethods,
110
- ...htmlMethods,
111
- ...ioMethods,
112
- ...mapsMethods,
113
- ...mathMethods,
114
- ...osMethods,
115
- ...pathMethods,
116
- ...randMethods,
117
- ...regexpMethods,
118
- ...sharedMethods,
119
- ...simdMethods,
120
- ...slicesMethods,
121
- ...sortMethods,
122
- ...strconvMethods,
123
- ...stringsMethods,
124
- ...timeMethods,
125
- ...unicodeMethods,
126
- ...utf8Methods,
127
- ...testingMethods,
128
- ...syncMethods,
129
- ...sha256Methods,
130
- ...binaryMethods,
131
- ...hexMethods,
132
- ...base64Methods,
133
- ...jsonMethods,
134
91
  ...dispatchMethods,
135
92
  };
@@ -2,9 +2,6 @@
2
2
  // Mounts an HTML/expression tree onto a parent DOM node by emitting plain
3
3
  // document.createElement / appendChild / setAttribute calls.
4
4
 
5
- import { Parser } from "../../parser/index.js";
6
- import { T, Token } from "../../tokens.js";
7
-
8
5
  /** @typedef {import('./index.js').CodeGen} CodeGen */
9
6
 
10
7
  /** @type {ThisType<CodeGen>} */
@@ -16,13 +13,22 @@ export const templGenMethods = {
16
13
  )
17
14
  .join(", ");
18
15
  this.line(`function ${decl.name}(${params}) {`);
16
+ const prevBoxed = this._boxedVars;
17
+ this._boxedVars = new Set(this._boxedGlobals);
19
18
  this.indented(() => {
19
+ // `@W(xs...)` passes `{ __sp: xs }` like any Go variadic call.
20
+ const last = decl.params[decl.params.length - 1];
21
+ if (last?.variadic && last.name && last.name !== "_") {
22
+ this.useHelper("va");
23
+ this.line(`${last.name} = __va(${last.name});`);
24
+ }
20
25
  this.line(`return {Mount(___p, ___refs) {`);
21
26
  this.indented(() => {
22
27
  this._genTemplNodes(decl.body, "___p");
23
28
  });
24
29
  this.line(`}};`);
25
30
  });
31
+ this._boxedVars = prevBoxed;
26
32
  this.line("}");
27
33
  },
28
34
 
@@ -79,15 +85,21 @@ export const templGenMethods = {
79
85
  );
80
86
  case "TemplExpr":
81
87
  return this.line(
82
- `${p}.appendChild(document.createTextNode(String(${this._genTemplTokenExpr(node.tokens)})));`,
88
+ `${p}.appendChild(document.createTextNode(String(${this._genTemplExpr(node, '""')})));`,
83
89
  );
84
90
  case "TemplComponent":
85
91
  return this._genTemplComponentMount(node, p);
86
- case "TemplChildren":
87
- // children is a variadic gom.Node param — mount each child
92
+ case "TemplChildren": {
93
+ // `{ name... }`: mount each node of a variadic gom.Node parameter
94
+ // (nil mounts nothing). `children` may also be absent altogether.
95
+ const slot =
96
+ node.name === "children"
97
+ ? `(typeof children!=="undefined"?children:null)`
98
+ : this._genTemplExpr(node, "null");
88
99
  return this.line(
89
- `if(typeof children!=="undefined")(Array.isArray(children)?children:[...children]).forEach(___c=>___c?.Mount?.(${p}));`,
100
+ `for (const ___c of ${slot} ?? []) ___c?.Mount?.(${p});`,
90
101
  );
102
+ }
91
103
  case "TemplIf":
92
104
  return this._genTemplIf(node, p);
93
105
  case "TemplFor":
@@ -101,20 +113,12 @@ export const templGenMethods = {
101
113
 
102
114
  _genTemplComponentMount(node, p) {
103
115
  // Special case: @templ.Raw(expr) → insertAdjacentHTML
104
- const toks = node.tokens;
105
- if (
106
- toks.length >= 4 &&
107
- toks[0]?.value === "templ" &&
108
- toks[1]?.type === T.DOT &&
109
- toks[2]?.value === "Raw" &&
110
- toks[3]?.type === T.LPAREN
111
- ) {
112
- const argTokens = toks.slice(4, toks.length - 1);
113
- const argJs = this._genTemplTokenExpr(argTokens);
116
+ if (node.raw) {
117
+ const argJs = this._genTemplExpr(node, '""');
114
118
  return this.line(`${p}.insertAdjacentHTML("beforeend", ${argJs});`);
115
119
  }
116
120
  return this.line(
117
- `(${this._genTemplTokenCallExpr(node.tokens)}).Mount(${p}, ___refs);`,
121
+ `(${this._genTemplExpr(node, "null")}).Mount(${p}, ___refs);`,
118
122
  );
119
123
  },
120
124
 
@@ -160,7 +164,7 @@ export const templGenMethods = {
160
164
  );
161
165
  }
162
166
  } else if (attr.kind === "expr") {
163
- const exprJs = this._genTemplTokenExpr(attr.tokens);
167
+ const exprJs = this._genTemplExpr(attr, '""');
164
168
  if (name === "class") {
165
169
  this.line(setClass(exprJs));
166
170
  } else {
@@ -171,7 +175,7 @@ export const templGenMethods = {
171
175
  } else if (attr.kind === "bool") {
172
176
  this.line(`${el}.setAttribute(${JSON.stringify(name)}, "");`);
173
177
  } else if (attr.kind === "cond-bool") {
174
- const exprJs = this._genTemplTokenExpr(attr.tokens);
178
+ const exprJs = this._genTemplExpr(attr, '""');
175
179
  this.line(
176
180
  `if(${exprJs})${el}.setAttribute(${JSON.stringify(name)}, "");`,
177
181
  );
@@ -179,7 +183,7 @@ export const templGenMethods = {
179
183
  },
180
184
 
181
185
  _genTemplIf(node, p) {
182
- const condJs = this._genTemplCondTokens(node.condTokens);
186
+ const condJs = this._genTemplExpr(node, "false");
183
187
  this.line(`if (${condJs}) {`);
184
188
  this.indented(() => this._genTemplNodes(node.then, p));
185
189
  this._genTemplIfElse(node.else_, p);
@@ -192,7 +196,7 @@ export const templGenMethods = {
192
196
  }
193
197
  if (else_.length === 1 && else_[0].kind === "TemplIf") {
194
198
  const elseIf = else_[0];
195
- this.line(`} else if (${this._genTemplCondTokens(elseIf.condTokens)}) {`);
199
+ this.line(`} else if (${this._genTemplExpr(elseIf, "false")}) {`);
196
200
  this.indented(() => this._genTemplNodes(elseIf.then, p));
197
201
  this._genTemplIfElse(elseIf.else_, p);
198
202
  } else {
@@ -203,17 +207,20 @@ export const templGenMethods = {
203
207
  },
204
208
 
205
209
  _genTemplSwitch(node, p) {
206
- const { exprTokens, cases } = node;
210
+ const { cases } = node;
207
211
  if (cases.length === 0) return;
208
- const exprJs = this._genTemplTokenExpr(exprTokens);
212
+ // `switch { case cond: }` compares each condition with `true`.
213
+ const exprJs = this._genTemplExpr(node, "true");
209
214
  this.line(`switch (${exprJs}) {`);
210
215
  this.indented(() => {
211
216
  for (const c of cases) {
212
- const label =
213
- c.caseTokens === null
214
- ? "default"
215
- : `case ${this._genTemplTokenExpr(c.caseTokens)}`;
216
- this.line(`${label}: {`);
217
+ if (c.caseTokens === null) this.line("default: {");
218
+ else {
219
+ // One JS label per value of `case a, b:`.
220
+ if (c.exprError) throw c.exprError;
221
+ const labels = (c.exprs ?? []).map((e) => `case ${this.genExpr(e)}:`);
222
+ this.line(`${labels.join(" ") || 'case "":'} {`);
223
+ }
217
224
  this.indented(() => this._genTemplNodes(c.body, p));
218
225
  this.line(`break; }`);
219
226
  }
@@ -221,106 +228,48 @@ export const templGenMethods = {
221
228
  this.line(`}`);
222
229
  },
223
230
 
231
+ // The header is a parsed Go `for` statement, so it goes through the regular
232
+ // loop generator; its body is a statement that mounts the templ nodes.
224
233
  _genTemplFor(node, p) {
225
- // stmtTokens include the `for` keyword plus the range clause (no body).
226
- // Strip any trailing semicolons (inserted by the Go lexer on newlines),
227
- // then add a fake empty body `{ }` so the parser can complete the ForStmt.
228
- let tokens = node.stmtTokens;
229
- while (tokens.length > 0 && tokens[tokens.length - 1].type === T.SEMICOLON)
230
- tokens = tokens.slice(0, -1);
231
- const eofTok = new Token(T.EOF, "", 1, 1);
232
- const lbrace = new Token(T.LBRACE, "{", 1, 1);
233
- const rbrace = new Token(T.RBRACE, "}", 1, 1);
234
- const semi = new Token(T.SEMICOLON, ";", 1, 1);
235
- const withFor =
236
- tokens[0]?.type === T.FOR
237
- ? [...tokens, lbrace, rbrace, semi, eofTok]
238
- : [
239
- new Token(T.FOR, "for", 1, 1),
240
- ...tokens,
241
- lbrace,
242
- rbrace,
243
- semi,
244
- eofTok,
245
- ];
246
- try {
247
- const p2 = new Parser(withFor);
248
- const forStmt = p2.parseFor();
249
- this._genTemplRangeFor(forStmt, node.body, p);
250
- } catch {
251
- // Fallback for non-range loops: emit a comment + skip
252
- this.line(`/* templ for: ${this._tokensToSource(tokens)} */`);
253
- }
254
- },
255
-
256
- _genTemplRangeFor(forStmt, templBody, mountParent) {
257
- const init = forStmt.init;
258
- if (init?.rhs?.[0]?.kind !== "RangeExpr") {
259
- // Plain for loop (cond-only or three-clause)
260
- const condJs = forStmt.cond ? this.genExpr(forStmt.cond) : "true";
261
- this.line(`while (${condJs}) {`);
262
- this.indented(() => this._genTemplNodes(templBody, mountParent));
263
- this.line("}");
264
- return;
265
- }
266
- // Range for: for key, val := range iter
267
- const range = init.rhs[0];
268
- const iterJs = this.genExpr(range.expr);
269
- const lhs = init.lhs.map((e) => e.name ?? "_");
270
- const keyName = lhs[0] ?? "_";
271
- const valName = lhs[1] ?? null;
272
-
273
- const loopHeader = this._genTemplRangeHeader(keyName, valName, iterJs);
274
- this.line(loopHeader);
275
- this.indented(() => this._genTemplNodes(templBody, mountParent));
276
- this.line("}");
234
+ if (node.forError) throw node.forError;
235
+ if (!node.forStmt)
236
+ throw new Error("templ for header was not parsed by TemplParser");
237
+ const mount = { kind: "TemplMount", parent: p };
238
+ // Hidden from the generic AST walkers: they know no templ node kinds.
239
+ Object.defineProperty(mount, "nodes", { value: node.body });
240
+ this.genFor({
241
+ ...node.forStmt,
242
+ body: { kind: "Block", stmts: [mount] },
243
+ });
277
244
  },
278
245
 
279
- _genTemplRangeHeader(keyName, valName, iterJs) {
280
- if (valName === null || valName === undefined) {
281
- return `for (const ${keyName === "_" ? "_$" : keyName} of ${iterJs}) {`;
282
- }
283
- if (keyName === "_" && valName === "_")
284
- return `for (const _$ of ${iterJs}) {`;
285
- if (keyName === "_") return `for (const ${valName} of ${iterJs}) {`;
286
- if (valName === "_")
287
- return `for (const [${keyName}] of Object.entries(${iterJs})) {`;
288
- // Default to .entries() for arrays, Object.entries for objects
289
- this.useHelper("sliceGuard");
290
- return `for (const [${keyName}, ${valName}] of __s(${iterJs}).entries()) {`;
246
+ _genTemplMountStmt(stmt) {
247
+ this._genTemplNodes(stmt.nodes, stmt.parent);
291
248
  },
292
249
 
293
- // Parse a token array as a Go expression and emit JS.
294
- _genTemplTokenExpr(tokens) {
295
- if (!tokens || tokens.length === 0) return '""';
296
- const eofTok = new Token(T.EOF, "", 1, 1);
297
- const p = new Parser([...tokens, eofTok]);
298
- const ast = p.parseExpr();
299
- return this.genExpr(ast);
250
+ // Emit JS for the Go expression TemplParser parsed onto `node`. `emptyJs` is
251
+ // the value when the node holds no tokens. A parse error is raised here,
252
+ // not in the parser, so it surfaces at the same point as it always did.
253
+ _genTemplExpr(node, emptyJs) {
254
+ if (node.exprError) throw node.exprError;
255
+ if (node.expr === undefined)
256
+ throw new Error("templ expression was not parsed by TemplParser");
257
+ return node.expr ? this.genExpr(node.expr) : emptyJs;
300
258
  },
301
259
 
302
- // Parse a token array as a Go call expression and emit JS.
303
- _genTemplTokenCallExpr(tokens) {
304
- if (!tokens || tokens.length === 0) return "null";
305
- const eofTok = new Token(T.EOF, "", 1, 1);
306
- const p = new Parser([...tokens, eofTok]);
307
- const ast = p.parseExpr();
308
- return this.genExpr(ast);
260
+ // Rename handlers for `_renameReservedIdents`: templ bodies keep their Go
261
+ // expressions in non-enumerable props (see parser/templ.js), which its
262
+ // generic walk does not visit, so `class` in `{ class }` would stay unsafe.
263
+ _renameTemplExprNode(node) {
264
+ this._renameReservedIdents(node.expr);
309
265
  },
310
266
 
311
- // Extract condition JS from condTokens (which may start with `if`).
312
- _genTemplCondTokens(tokens) {
313
- if (!tokens || tokens.length === 0) return "false";
314
- let toks = tokens;
315
- if (toks[0]?.type === T.IF) toks = toks.slice(1); // skip `if` keyword
316
- const eofTok = new Token(T.EOF, "", 1, 1);
317
- const p = new Parser([...toks, eofTok]);
318
- const ast = p.parseExpr();
319
- return this.genExpr(ast);
267
+ _renameTemplForNode(node) {
268
+ this._renameReservedIdents(node.forStmt);
320
269
  },
321
270
 
322
- _tokensToSource(tokens) {
323
- return tokens.map((t) => t.value).join(" ");
271
+ _renameTemplElementNode(node) {
272
+ for (const attr of node.attrs ?? []) this._renameReservedIdents(attr.expr);
324
273
  },
325
274
 
326
275
  // Fresh variable name counter