jsxpression 0.1.28 → 0.1.31

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 (141) hide show
  1. package/README.md +155 -188
  2. package/lib/analyze/analysis-report.d.ts +2 -2
  3. package/lib/analyze/analysis-report.d.ts.map +1 -1
  4. package/lib/analyze/analysis-report.js +4 -0
  5. package/lib/analyze/analysis-report.js.map +1 -1
  6. package/lib/analyze/data/data-access.d.ts.map +1 -1
  7. package/lib/analyze/data/data-access.js +45 -82
  8. package/lib/analyze/data/data-access.js.map +1 -1
  9. package/lib/analyze/data/method-calls.d.ts.map +1 -1
  10. package/lib/analyze/data/method-calls.js +4 -3
  11. package/lib/analyze/data/method-calls.js.map +1 -1
  12. package/lib/analyze/data/utils.d.ts +2 -3
  13. package/lib/analyze/data/utils.d.ts.map +1 -1
  14. package/lib/analyze/data/utils.js +10 -36
  15. package/lib/analyze/data/utils.js.map +1 -1
  16. package/lib/analyze/jsx/element-attributes.d.ts +1 -1
  17. package/lib/analyze/jsx/element-attributes.d.ts.map +1 -1
  18. package/lib/analyze/jsx/element-attributes.js +13 -3
  19. package/lib/analyze/jsx/element-attributes.js.map +1 -1
  20. package/lib/analyze/jsx/element-children.d.ts +1 -1
  21. package/lib/analyze/jsx/element-children.d.ts.map +1 -1
  22. package/lib/analyze/jsx/element-children.js +16 -5
  23. package/lib/analyze/jsx/element-children.js.map +1 -1
  24. package/lib/analyze/jsx/element-tags.d.ts +1 -1
  25. package/lib/analyze/jsx/element-tags.d.ts.map +1 -1
  26. package/lib/analyze/jsx/element-tags.js +12 -2
  27. package/lib/analyze/jsx/element-tags.js.map +1 -1
  28. package/lib/analyze/security/analyze.d.ts.map +1 -1
  29. package/lib/analyze/security/analyze.js +2 -4
  30. package/lib/analyze/security/analyze.js.map +1 -1
  31. package/lib/analyze/security/call-expressions.d.ts +2 -2
  32. package/lib/analyze/security/call-expressions.d.ts.map +1 -1
  33. package/lib/analyze/security/call-expressions.js +21 -5
  34. package/lib/analyze/security/call-expressions.js.map +1 -1
  35. package/lib/analyze/security/declarations.d.ts +1 -1
  36. package/lib/analyze/security/declarations.d.ts.map +1 -1
  37. package/lib/analyze/security/declarations.js +22 -2
  38. package/lib/analyze/security/declarations.js.map +1 -1
  39. package/lib/analyze/security/expressions.d.ts.map +1 -1
  40. package/lib/analyze/security/expressions.js +10 -2
  41. package/lib/analyze/security/expressions.js.map +1 -1
  42. package/lib/analyze/security/identifiers.d.ts +2 -2
  43. package/lib/analyze/security/identifiers.d.ts.map +1 -1
  44. package/lib/analyze/security/identifiers.js +159 -19
  45. package/lib/analyze/security/identifiers.js.map +1 -1
  46. package/lib/builtins.d.ts.map +1 -1
  47. package/lib/builtins.js +5 -0
  48. package/lib/builtins.js.map +1 -1
  49. package/lib/codegen/builtins/builtin-types.d.ts.map +1 -1
  50. package/lib/codegen/builtins/builtin-types.js +7 -1
  51. package/lib/codegen/builtins/builtin-types.js.map +1 -1
  52. package/lib/compile/compile.d.ts.map +1 -1
  53. package/lib/compile/compile.js +131 -61
  54. package/lib/compile/compile.js.map +1 -1
  55. package/lib/evaluate/evaluate.d.ts +3 -3
  56. package/lib/evaluate/evaluate.d.ts.map +1 -1
  57. package/lib/evaluate/evaluate.js +28 -7
  58. package/lib/evaluate/evaluate.js.map +1 -1
  59. package/lib/jsx.d.ts +2 -19
  60. package/lib/jsx.d.ts.map +1 -1
  61. package/lib/jsx.js +0 -6
  62. package/lib/jsx.js.map +1 -1
  63. package/lib/parse/parse.d.ts.map +1 -1
  64. package/lib/parse/parse.js +4 -3
  65. package/lib/parse/parse.js.map +1 -1
  66. package/lib/render.d.ts +4 -4
  67. package/lib/render.d.ts.map +1 -1
  68. package/lib/render.js +7 -9
  69. package/lib/render.js.map +1 -1
  70. package/lib/schema.d.ts +2 -7
  71. package/lib/schema.d.ts.map +1 -1
  72. package/lib/schema.js +0 -43
  73. package/lib/schema.js.map +1 -1
  74. package/lib/traverse.d.ts +4 -4
  75. package/lib/traverse.d.ts.map +1 -1
  76. package/lib/traverse.js +118 -40
  77. package/lib/traverse.js.map +1 -1
  78. package/lib/validate.d.ts +1 -1
  79. package/lib/validate.js +1 -1
  80. package/package.json +4 -3
  81. package/src/analyze/analysis-report.ts +6 -2
  82. package/src/analyze/analyze.test.ts +108 -69
  83. package/src/analyze/data/data-access.ts +45 -90
  84. package/src/analyze/data/method-calls.ts +4 -3
  85. package/src/analyze/data/utils.ts +11 -40
  86. package/src/analyze/jsx/element-attributes.ts +24 -9
  87. package/src/analyze/jsx/element-children.ts +20 -6
  88. package/src/analyze/jsx/element-tags.ts +15 -3
  89. package/src/analyze/security/analyze.ts +1 -3
  90. package/src/analyze/security/call-expressions.ts +37 -17
  91. package/src/analyze/security/declarations.ts +35 -9
  92. package/src/analyze/security/expressions.ts +17 -3
  93. package/src/analyze/security/identifiers.ts +172 -28
  94. package/src/builtins.ts +6 -0
  95. package/src/codegen/builtins/builtin-types.ts +7 -1
  96. package/src/codegen/generate-typescript-definitions.test.ts +16 -23
  97. package/src/compile/compile.test.ts +153 -30
  98. package/src/compile/compile.ts +163 -73
  99. package/src/evaluate/evaluate.test.ts +276 -14
  100. package/src/evaluate/evaluate.ts +34 -10
  101. package/src/jsx.ts +2 -31
  102. package/src/parse/parse.test.ts +4 -4
  103. package/src/parse/parse.ts +4 -3
  104. package/src/render.test.ts +476 -27
  105. package/src/render.ts +7 -14
  106. package/src/schema.ts +1 -55
  107. package/src/traverse.ts +135 -49
  108. package/src/validate.ts +1 -1
  109. package/LICENSE +0 -21
  110. package/lib/analyze/analyze.test.d.ts +0 -2
  111. package/lib/analyze/analyze.test.d.ts.map +0 -1
  112. package/lib/analyze/analyze.test.js +0 -763
  113. package/lib/analyze/analyze.test.js.map +0 -1
  114. package/lib/analyze/security/member-access.d.ts +0 -6
  115. package/lib/codegen/generate-typescript-definitions.test.d.ts +0 -2
  116. package/lib/codegen/generate-typescript-definitions.test.d.ts.map +0 -1
  117. package/lib/codegen/generate-typescript-definitions.test.js +0 -649
  118. package/lib/codegen/generate-typescript-definitions.test.js.map +0 -1
  119. package/lib/compile/compile.test.d.ts +0 -2
  120. package/lib/compile/compile.test.d.ts.map +0 -1
  121. package/lib/compile/compile.test.js +0 -146
  122. package/lib/compile/compile.test.js.map +0 -1
  123. package/lib/evaluate/evaluate.test.d.ts +0 -2
  124. package/lib/evaluate/evaluate.test.d.ts.map +0 -1
  125. package/lib/evaluate/evaluate.test.js +0 -260
  126. package/lib/evaluate/evaluate.test.js.map +0 -1
  127. package/lib/parse/parse.test.d.ts +0 -2
  128. package/lib/parse/parse.test.d.ts.map +0 -1
  129. package/lib/parse/parse.test.js +0 -80
  130. package/lib/parse/parse.test.js.map +0 -1
  131. package/lib/render.test.d.ts +0 -2
  132. package/lib/render.test.d.ts.map +0 -1
  133. package/lib/render.test.js +0 -301
  134. package/lib/render.test.js.map +0 -1
  135. package/lib/traverse.test.d.ts +0 -2
  136. package/lib/traverse.test.d.ts.map +0 -1
  137. package/lib/traverse.test.js +0 -84
  138. package/lib/traverse.test.js.map +0 -1
  139. package/lib/types.d.ts +0 -12
  140. package/src/analyze/security/member-access.ts +0 -35
  141. package/src/types.ts +0 -17
@@ -50,27 +50,27 @@ describe("compile", () => {
50
50
  });
51
51
 
52
52
  it("should compile bare expression containers", () => {
53
- const ast = parse("{data.user.name}");
53
+ const ast = parse("{user.name}");
54
54
  const result = compile(ast);
55
- expect(result).toBe('"use strict";return data.user.name;');
55
+ expect(result).toBe('"use strict";return user.name;');
56
56
  });
57
57
 
58
58
  it("should compile bare mathematical expressions", () => {
59
- const ast = parse("{data.count * 2}");
59
+ const ast = parse("{count * 2}");
60
60
  const result = compile(ast);
61
- expect(result).toBe('"use strict";return (data.count * 2);');
61
+ expect(result).toBe('"use strict";return (count * 2);');
62
62
  });
63
63
 
64
64
  it("should compile bare method calls", () => {
65
- const ast = parse("{data.text.toUpperCase()}");
65
+ const ast = parse("{text.toUpperCase()}");
66
66
  const result = compile(ast);
67
- expect(result).toBe('"use strict";return data.text.toUpperCase();');
67
+ expect(result).toBe('"use strict";return text.toUpperCase();');
68
68
  });
69
69
 
70
70
  it("should compile bare complex expressions", () => {
71
- const ast = parse("{data.items.filter(x => x.length > 2).length}");
71
+ const ast = parse("{items.filter(x => x.length > 2).length}");
72
72
  const result = compile(ast);
73
- expect(result).toBe('"use strict";return data.items.filter((x) => (x.length > 2)).length;');
73
+ expect(result).toBe('"use strict";return items.filter((x) => (x.length > 2)).length;');
74
74
  });
75
75
 
76
76
  describe("JSX empty expressions", () => {
@@ -94,14 +94,14 @@ describe("compile", () => {
94
94
  });
95
95
 
96
96
  it("should handle mixed empty and real expressions", () => {
97
- const ast = parse("<Text>Hello {}{data.x}{} World</Text>");
97
+ const ast = parse("<Text>Hello {}{x}{} World</Text>");
98
98
  const compiled = compile(ast);
99
99
 
100
100
  expect(compiled).toContain("return");
101
101
  expect(compiled).toContain("Text");
102
102
  expect(compiled).toContain("Hello");
103
103
  expect(compiled).toContain("World");
104
- expect(compiled).toContain("data.x");
104
+ expect(compiled).toContain("x");
105
105
  });
106
106
 
107
107
  it("should generate valid JavaScript code structure", () => {
@@ -115,59 +115,182 @@ describe("compile", () => {
115
115
 
116
116
  describe("spread elements", () => {
117
117
  it("should compile spread in arrays", () => {
118
- const ast = parse("<div>{[...data.items]}</div>");
118
+ const ast = parse("<div>{[...items]}</div>");
119
119
  const result = compile(ast);
120
- expect(result).toBe('"use strict";return h("div", null, [...data.items]);');
120
+ expect(result).toBe('"use strict";return h("div", null, [...items]);');
121
121
  });
122
122
 
123
123
  it("should compile spread in objects", () => {
124
- const ast = parse("<div style={{ ...data.styles }}>Text</div>");
124
+ const ast = parse("<div style={{ ...styles }}>Text</div>");
125
125
  const result = compile(ast);
126
- expect(result).toBe('"use strict";return h("div", { style: { ...data.styles } }, "Text");');
126
+ expect(result).toBe('"use strict";return h("div", { style: { ...styles } }, "Text");');
127
127
  });
128
128
 
129
129
  it("should compile mixed spread and regular elements in arrays", () => {
130
- const ast = parse("<div>{[1, ...data.items, 2]}</div>");
130
+ const ast = parse("<div>{[1, ...items, 2]}</div>");
131
131
  const result = compile(ast);
132
- expect(result).toBe('"use strict";return h("div", null, [1, ...data.items, 2]);');
132
+ expect(result).toBe('"use strict";return h("div", null, [1, ...items, 2]);');
133
133
  });
134
134
 
135
135
  it("should compile mixed spread and regular properties in objects", () => {
136
- const ast = parse("<div style={{ color: 'red', ...data.styles, width: 100 }}>Text</div>");
136
+ const ast = parse("<div style={{ color: 'red', ...styles, width: 100 }}>Text</div>");
137
137
  const result = compile(ast);
138
- expect(result).toBe(
139
- '"use strict";return h("div", { style: { color: "red", ...data.styles, width: 100 } }, "Text");'
140
- );
138
+ expect(result).toBe('"use strict";return h("div", { style: { color: "red", ...styles, width: 100 } }, "Text");');
141
139
  });
142
140
 
143
141
  it("should compile multiple spreads in arrays", () => {
144
- const ast = parse("<div>{[...data.first, ...data.second]}</div>");
142
+ const ast = parse("<div>{[...first, ...second]}</div>");
145
143
  const result = compile(ast);
146
- expect(result).toBe('"use strict";return h("div", null, [...data.first, ...data.second]);');
144
+ expect(result).toBe('"use strict";return h("div", null, [...first, ...second]);');
147
145
  });
148
146
 
149
147
  it("should compile multiple spreads in objects", () => {
150
- const ast = parse("<div style={{ ...data.base, ...data.override }}>Text</div>");
148
+ const ast = parse("<div style={{ ...base, ...override }}>Text</div>");
151
149
  const result = compile(ast);
152
- expect(result).toBe('"use strict";return h("div", { style: { ...data.base, ...data.override } }, "Text");');
150
+ expect(result).toBe('"use strict";return h("div", { style: { ...base, ...override } }, "Text");');
153
151
  });
154
152
 
155
153
  it("should compile spread in function call arguments", () => {
156
- const ast = parse("<div>{Math.max(...data.numbers)}</div>");
154
+ const ast = parse("<div>{Math.max(...numbers)}</div>");
157
155
  const result = compile(ast);
158
- expect(result).toBe('"use strict";return h("div", null, Math.max(...data.numbers));');
156
+ expect(result).toBe('"use strict";return h("div", null, Math.max(...numbers));');
159
157
  });
160
158
 
161
159
  it("should compile spread with map in function call", () => {
162
- const ast = parse("<div>{Math.min(...data.items.map(x => x.price))}</div>");
160
+ const ast = parse("<div>{Math.min(...items.map(x => x.price))}</div>");
163
161
  const result = compile(ast);
164
- expect(result).toBe('"use strict";return h("div", null, Math.min(...data.items.map((x) => x.price)));');
162
+ expect(result).toBe('"use strict";return h("div", null, Math.min(...items.map((x) => x.price)));');
165
163
  });
166
164
 
167
165
  it("should compile mixed spread and regular arguments", () => {
168
- const ast = parse("<div>{Math.max(0, ...data.numbers, 100)}</div>");
166
+ const ast = parse("<div>{Math.max(0, ...numbers, 100)}</div>");
167
+ const result = compile(ast);
168
+ expect(result).toBe('"use strict";return h("div", null, Math.max(0, ...numbers, 100));');
169
+ });
170
+ });
171
+
172
+ describe("const declarations", () => {
173
+ it("should compile const with literal value", () => {
174
+ const ast = parse("const x = 42;\nreturn <div>{x}</div>");
175
+ const result = compile(ast);
176
+ expect(result).toBe('"use strict";const x = 42;return h("div", null, x);');
177
+ });
178
+
179
+ it("should compile const with expression value", () => {
180
+ const ast = parse("const label = name.toUpperCase();\nreturn <div>{label}</div>");
181
+ const result = compile(ast);
182
+ expect(result).toBe('"use strict";const label = name.toUpperCase();return h("div", null, label);');
183
+ });
184
+
185
+ it("should compile multiple const declarations", () => {
186
+ const ast = parse('const a = 1;\nconst b = "hello";\nreturn <div>{a}{b}</div>');
187
+ const result = compile(ast);
188
+ expect(result).toBe('"use strict";const a = 1;const b = "hello";return h("div", null, a, b);');
189
+ });
190
+
191
+ it("should compile const with object value", () => {
192
+ const ast = parse("const style = { color: 'red' };\nreturn <div style={style}>Text</div>");
193
+ const result = compile(ast);
194
+ expect(result).toBe('"use strict";const style = { color: "red" };return h("div", { style: style }, "Text");');
195
+ });
196
+
197
+ it("should compile const with array value", () => {
198
+ const ast = parse("const nums = [1, 2, 3];\nreturn <div>{nums.join(', ')}</div>");
199
+ const result = compile(ast);
200
+ expect(result).toBe('"use strict";const nums = [1, 2, 3];return h("div", null, nums.join(", "));');
201
+ });
202
+
203
+ it("should not auto-wrap bare JSX when declarations present", () => {
204
+ const ast = parse("const x = 1;\n<div>{x}</div>");
205
+ const result = compile(ast);
206
+ expect(result).toBe('"use strict";const x = 1;');
207
+ });
208
+ });
209
+
210
+ describe("function declarations", () => {
211
+ it("should compile simple function declaration", () => {
212
+ const ast = parse("function Badge() {\n return <span>badge</span>\n}\nreturn <Badge />");
213
+ const result = compile(ast);
214
+ expect(result).toBe('"use strict";function Badge() { return h("span", null, "badge"); }return h(Badge, null);');
215
+ });
216
+
217
+ it("should compile function with props parameter", () => {
218
+ const ast = parse('function Badge({ label }) {\n return <span>{label}</span>\n}\nreturn <Badge label="hi" />');
219
+ const result = compile(ast);
220
+ expect(result).toBe(
221
+ '"use strict";function Badge({ label }) { return h("span", null, label); }return h(Badge, { label: "hi" });'
222
+ );
223
+ });
224
+
225
+ it("should compile function with default parameter", () => {
226
+ const ast = parse("function Badge({ label = 'default' }) {\n return <span>{label}</span>\n}\nreturn <Badge />");
227
+ const result = compile(ast);
228
+ expect(result).toBe(
229
+ '"use strict";function Badge({ label = "default" }) { return h("span", null, label); }return h(Badge, null);'
230
+ );
231
+ });
232
+
233
+ it("should compile function with const inside body", () => {
234
+ const ast = parse(
235
+ "function Badge({ value }) {\n const doubled = value * 2;\n return <span>{doubled}</span>\n}\nreturn <Badge value={5} />"
236
+ );
237
+ const result = compile(ast);
238
+ expect(result).toContain("const doubled = (value * 2);");
239
+ expect(result).toContain('return h("span", null, doubled);');
240
+ });
241
+
242
+ it("should compile function used as child element", () => {
243
+ const ast = parse("function Item() {\n return <span>item</span>\n}\nreturn <div><Item /></div>");
244
+ const result = compile(ast);
245
+ expect(result).toContain("function Item()");
246
+ expect(result).toContain("h(Item, null)");
247
+ expect(result).toContain('h("div", null, h(Item, null))');
248
+ });
249
+
250
+ it("should compile multiple function declarations", () => {
251
+ const ast = parse(
252
+ "function A() {\n return <span>a</span>\n}\nfunction B() {\n return <span>b</span>\n}\nreturn <div><A /><B /></div>"
253
+ );
254
+ const result = compile(ast);
255
+ expect(result).toContain("function A()");
256
+ expect(result).toContain("function B()");
257
+ expect(result).toContain("h(A, null)");
258
+ expect(result).toContain("h(B, null)");
259
+ });
260
+
261
+ it("should compile function calling another function", () => {
262
+ const ast = parse(
263
+ "function Inner() {\n return <span>inner</span>\n}\nfunction Outer() {\n return <div><Inner /></div>\n}\nreturn <Outer />"
264
+ );
265
+ const result = compile(ast);
266
+ expect(result).toContain("function Inner()");
267
+ expect(result).toContain("function Outer()");
268
+ expect(result).toContain("h(Inner, null)");
269
+ expect(result).toContain("h(Outer, null)");
270
+ });
271
+
272
+ it("should emit local function names as references not strings in JSX", () => {
273
+ const ast = parse("function Badge() {\n return <span>b</span>\n}\nreturn <Badge />");
274
+ const result = compile(ast);
275
+ // Badge should be a reference, not a string
276
+ expect(result).toContain("h(Badge, null)");
277
+ expect(result).not.toContain('h("Badge"');
278
+ });
279
+ });
280
+
281
+ describe("return statements", () => {
282
+ it("should compile explicit return with JSX", () => {
283
+ const ast = parse("return <div>hello</div>");
284
+ const result = compile(ast);
285
+ expect(result).toBe('"use strict";return h("div", null, "hello");');
286
+ });
287
+
288
+ it("should compile return after const and function", () => {
289
+ const ast = parse("const x = 1;\nfunction F() {\n return <span>f</span>\n}\nreturn <div><F />{x}</div>");
169
290
  const result = compile(ast);
170
- expect(result).toBe('"use strict";return h("div", null, Math.max(0, ...data.numbers, 100));');
291
+ expect(result).toContain("const x = 1;");
292
+ expect(result).toContain("function F()");
293
+ expect(result).toMatch(/return h\("div"/);
171
294
  });
172
295
  });
173
296
 
@@ -1,72 +1,87 @@
1
- import type { Expression, PrivateIdentifier, Program, Super, Identifier, Literal } from "acorn";
1
+ import type {
2
+ BlockStatement,
3
+ Expression,
4
+ FunctionDeclaration,
5
+ Identifier,
6
+ Literal,
7
+ Pattern,
8
+ PrivateIdentifier,
9
+ Program,
10
+ Statement,
11
+ Super,
12
+ VariableDeclaration,
13
+ } from "acorn";
2
14
 
3
15
  import {
4
16
  isJsxElement,
17
+ isJsxEmptyExpression,
18
+ isJsxExpressionContainer,
5
19
  isJsxFragment,
6
20
  isJsxRoot,
7
21
  isJsxText,
8
- isJsxExpressionContainer,
9
- isJsxEmptyExpression,
10
- type JSXRoot,
22
+ type JSXAttribute,
11
23
  type JSXElement,
12
- type JSXFragment,
13
24
  type JSXEmptyExpression,
25
+ type JSXFragment,
26
+ type JSXNameNode,
14
27
  type JSXNode,
15
- type JSXAttribute,
28
+ type JSXRoot,
16
29
  type JSXSpreadAttribute,
17
- type JSXNameNode,
18
30
  } from "../jsx.js";
19
31
  import { CompilationError } from "./compilation-error.js";
20
32
 
21
33
  type CompilableExpression = Expression | PrivateIdentifier | Super | JSXElement | JSXFragment | JSXEmptyExpression;
22
34
 
23
35
  export function compile(ast: Program): string {
24
- const root = getRoot(ast);
25
- const code = emitJsxRoot(root);
26
-
27
- return `"use strict";return ${code};`;
28
- }
29
-
30
- function getRoot(ast: Program): JSXRoot {
31
- for (const declaration of ast.body) {
32
- if (declaration.type === "ExpressionStatement" && isJsxRoot(declaration.expression)) {
33
- return declaration.expression;
34
- }
35
- if (declaration.type === "ReturnStatement" && declaration.argument && isJsxRoot(declaration.argument)) {
36
- return declaration.argument;
36
+ const localFunctions = new Set<string>();
37
+ for (const stmt of ast.body) {
38
+ if (stmt.type === "FunctionDeclaration" && stmt.id) {
39
+ localFunctions.add(stmt.id.name);
37
40
  }
38
- // Handle bare expressions like {data.user.name} which parse as BlockStatement
39
- if (declaration.type === "BlockStatement" && declaration.body.length === 1) {
40
- const innerStatement = declaration.body[0];
41
+ }
42
+
43
+ const parts: string[] = ['"use strict";'];
44
+
45
+ const hasDeclarations = ast.body.some((s) => s.type === "VariableDeclaration" || s.type === "FunctionDeclaration");
46
+
47
+ for (const statement of ast.body) {
48
+ if (statement.type === "VariableDeclaration") {
49
+ parts.push(emitVariableDeclaration(statement, localFunctions));
50
+ } else if (statement.type === "FunctionDeclaration") {
51
+ parts.push(emitFunctionDeclaration(statement as FunctionDeclaration, localFunctions));
52
+ } else if (statement.type === "ReturnStatement" && statement.argument && isJsxRoot(statement.argument)) {
53
+ parts.push(`return ${emitJsxRoot(statement.argument, localFunctions)};`);
54
+ } else if (!hasDeclarations && statement.type === "ExpressionStatement" && isJsxRoot(statement.expression)) {
55
+ parts.push(`return ${emitJsxRoot(statement.expression, localFunctions)};`);
56
+ } else if (!hasDeclarations && statement.type === "BlockStatement" && statement.body.length === 1) {
57
+ const innerStatement = statement.body[0];
41
58
  if (innerStatement.type === "ExpressionStatement") {
42
- // Create a synthetic JSXExpressionContainer for the bare expression
43
59
  const syntheticContainer = {
44
60
  type: "JSXExpressionContainer" as const,
45
61
  expression: innerStatement.expression as CompilableExpression,
46
62
  };
47
- return syntheticContainer as JSXRoot;
63
+ parts.push(`return ${emitJsxRoot(syntheticContainer as JSXRoot, localFunctions)};`);
48
64
  }
49
65
  }
50
66
  }
51
67
 
52
- throw CompilationError.fromNode("No root JSX element/fragment/expression found", ast);
68
+ return parts.join("");
53
69
  }
54
70
 
55
- function emitJsxRoot(node: JSXRoot): string {
71
+ function emitJsxRoot(node: JSXRoot, localFunctions: Set<string>): string {
56
72
  if (isJsxElement(node)) {
57
- return emitJsxElement(node);
73
+ return emitJsxElement(node, localFunctions);
58
74
  }
59
75
  if (isJsxFragment(node)) {
60
- return emitJsxFragment(node);
76
+ return emitJsxFragment(node, localFunctions);
61
77
  }
62
- // Handle bare expression containers like {data.user.name}
63
- return emitExpression(node.expression);
78
+ return emitExpression(node.expression, localFunctions);
64
79
  }
65
80
 
66
- function emitJsxElement(node: JSXElement): string {
67
- const name = emitJsxName(node.openingElement.name as JSXNameNode);
68
- const attributes = emitAttributes(node.openingElement.attributes);
69
- const children = emitJsxChildren(node.children);
81
+ function emitJsxElement(node: JSXElement, localFunctions: Set<string>): string {
82
+ const name = emitJsxName(node.openingElement.name as JSXNameNode, localFunctions);
83
+ const attributes = emitAttributes(node.openingElement.attributes, localFunctions);
84
+ const children = emitJsxChildren(node.children, localFunctions);
70
85
 
71
86
  if (children.length > 0) {
72
87
  return `h(${name}, ${attributes}, ${children.join(", ")})`;
@@ -75,18 +90,21 @@ function emitJsxElement(node: JSXElement): string {
75
90
  return `h(${name}, ${attributes})`;
76
91
  }
77
92
 
78
- function emitJsxFragment(node: JSXFragment): string {
79
- const children = emitJsxChildren(node.children);
93
+ function emitJsxFragment(node: JSXFragment, localFunctions: Set<string>): string {
94
+ const children = emitJsxChildren(node.children, localFunctions);
80
95
  return `[${children.join(", ")}]`;
81
96
  }
82
97
 
83
- function emitJsxName(nameNode: JSXNameNode): string {
98
+ function emitJsxName(nameNode: JSXNameNode, localFunctions: Set<string>): string {
84
99
  switch (nameNode.type) {
85
100
  case "JSXIdentifier":
101
+ if (localFunctions.has(nameNode.name)) {
102
+ return nameNode.name;
103
+ }
86
104
  return JSON.stringify(nameNode.name);
87
105
  case "JSXMemberExpression": {
88
- const left = emitJsxName(nameNode.object);
89
- const right = emitJsxName(nameNode.property);
106
+ const left = emitJsxName(nameNode.object, localFunctions);
107
+ const right = emitJsxName(nameNode.property, localFunctions);
90
108
  return JSON.stringify(JSON.parse(left) + "." + JSON.parse(right));
91
109
  }
92
110
  default:
@@ -94,7 +112,7 @@ function emitJsxName(nameNode: JSXNameNode): string {
94
112
  }
95
113
  }
96
114
 
97
- function emitAttributes(attributes: (JSXAttribute | JSXSpreadAttribute)[]): string {
115
+ function emitAttributes(attributes: (JSXAttribute | JSXSpreadAttribute)[], localFunctions: Set<string>): string {
98
116
  const parts: string[] = [];
99
117
  let currentObj: string[] = [];
100
118
 
@@ -108,7 +126,7 @@ function emitAttributes(attributes: (JSXAttribute | JSXSpreadAttribute)[]): stri
108
126
  for (const attribute of attributes) {
109
127
  if (attribute.type === "JSXSpreadAttribute") {
110
128
  flushCurrent();
111
- parts.push(`(${emitExpression(attribute.argument as CompilableExpression)})`);
129
+ parts.push(`(${emitExpression(attribute.argument as CompilableExpression, localFunctions)})`);
112
130
  continue;
113
131
  }
114
132
 
@@ -124,7 +142,7 @@ function emitAttributes(attributes: (JSXAttribute | JSXSpreadAttribute)[]): stri
124
142
  } else if (attribute.value.type === "Literal") {
125
143
  value = JSON.stringify(attribute.value.value);
126
144
  } else if (attribute.value.type === "JSXExpressionContainer") {
127
- value = emitExpression(attribute.value.expression as CompilableExpression);
145
+ value = emitExpression(attribute.value.expression as CompilableExpression, localFunctions);
128
146
  } else {
129
147
  throw CompilationError.fromNode("Unsupported attribute value", attribute.value);
130
148
  }
@@ -145,7 +163,7 @@ function emitAttributes(attributes: (JSXAttribute | JSXSpreadAttribute)[]): stri
145
163
  return `Object.assign({}, ${parts.join(", ")})`;
146
164
  }
147
165
 
148
- function emitJsxChildren(children: JSXNode[]): string[] {
166
+ function emitJsxChildren(children: JSXNode[], localFunctions: Set<string>): string[] {
149
167
  const result: string[] = [];
150
168
 
151
169
  for (const child of children) {
@@ -155,14 +173,13 @@ function emitJsxChildren(children: JSXNode[]): string[] {
155
173
  result.push(JSON.stringify(text));
156
174
  }
157
175
  } else if (isJsxExpressionContainer(child)) {
158
- // Skip empty expressions {} - they don't contribute any content
159
176
  if (!isJsxEmptyExpression(child.expression)) {
160
- result.push(emitExpression(child.expression as CompilableExpression));
177
+ result.push(emitExpression(child.expression as CompilableExpression, localFunctions));
161
178
  }
162
179
  } else if (isJsxElement(child)) {
163
- result.push(emitJsxElement(child));
180
+ result.push(emitJsxElement(child, localFunctions));
164
181
  } else if (isJsxFragment(child)) {
165
- result.push(emitJsxFragment(child));
182
+ result.push(emitJsxFragment(child, localFunctions));
166
183
  }
167
184
  }
168
185
 
@@ -171,7 +188,7 @@ function emitJsxChildren(children: JSXNode[]): string[] {
171
188
 
172
189
  const TRAILING_PLUS_RX = /\+\s*\+\s*$/;
173
190
 
174
- function emitExpression(node: CompilableExpression): string {
191
+ function emitExpression(node: CompilableExpression, localFunctions: Set<string>): string {
175
192
  switch (node.type) {
176
193
  case "Literal":
177
194
  return JSON.stringify(node.value);
@@ -181,23 +198,27 @@ function emitExpression(node: CompilableExpression): string {
181
198
  const bits: string[] = [];
182
199
  node.quasis.forEach((quasi, index) => {
183
200
  bits.push(JSON.stringify(quasi.value.cooked ?? ""));
184
-
185
201
  if (node.expressions.length > index) {
186
- bits.push(`+(${emitExpression(node.expressions[index])})+`);
202
+ bits.push(`+(${emitExpression(node.expressions[index], localFunctions)})+`);
187
203
  }
188
204
  });
189
-
190
205
  return bits.join("").replace(TRAILING_PLUS_RX, "") || '""';
191
206
  }
192
207
  case "ParenthesizedExpression":
193
- return `(${emitExpression(node.expression)})`;
208
+ return `(${emitExpression(node.expression, localFunctions)})`;
194
209
  case "UnaryExpression":
195
- return `${node.operator}${emitExpression(node.argument)}`;
210
+ return `${node.operator}${emitExpression(node.argument, localFunctions)}`;
196
211
  case "BinaryExpression":
197
212
  case "LogicalExpression":
198
- return `(${emitExpression(node.left)} ${node.operator} ${emitExpression(node.right)})`;
213
+ return `(${emitExpression(node.left, localFunctions)} ${node.operator} ${emitExpression(
214
+ node.right,
215
+ localFunctions
216
+ )})`;
199
217
  case "ConditionalExpression":
200
- return `(${emitExpression(node.test)}?${emitExpression(node.consequent)}:${emitExpression(node.alternate)})`;
218
+ return `(${emitExpression(node.test, localFunctions)}?${emitExpression(
219
+ node.consequent,
220
+ localFunctions
221
+ )}:${emitExpression(node.alternate, localFunctions)})`;
201
222
  case "ArrayExpression":
202
223
  return `[${node.elements
203
224
  .map((element) => {
@@ -205,56 +226,56 @@ function emitExpression(node: CompilableExpression): string {
205
226
  return "undefined";
206
227
  }
207
228
  if (element.type === "SpreadElement") {
208
- return `...${emitExpression(element.argument as CompilableExpression)}`;
229
+ return `...${emitExpression(element.argument as CompilableExpression, localFunctions)}`;
209
230
  }
210
- return emitExpression(element as CompilableExpression);
231
+ return emitExpression(element as CompilableExpression, localFunctions);
211
232
  })
212
233
  .join(", ")}]`;
213
234
  case "ObjectExpression": {
214
235
  const keyValuePairs = node.properties
215
236
  .map((property) => {
216
237
  if (property.type === "SpreadElement") {
217
- return `...${emitExpression(property.argument as CompilableExpression)}`;
238
+ return `...${emitExpression(property.argument as CompilableExpression, localFunctions)}`;
218
239
  }
219
-
220
240
  const key = getAstKeyString(property.key as Identifier | Literal);
221
- return `${key}: ${emitExpression(property.value)}`;
241
+ return `${key}: ${emitExpression(property.value, localFunctions)}`;
222
242
  })
223
243
  .join(", ");
224
244
  return `{ ${keyValuePairs} }`;
225
245
  }
226
246
  case "MemberExpression": {
227
- const obj = emitExpression(node.object);
247
+ const obj = emitExpression(node.object, localFunctions);
228
248
  let prop: string;
229
249
  if (node.computed) {
230
- prop = `[${emitExpression(node.property)}]`;
250
+ prop = `[${emitExpression(node.property, localFunctions)}]`;
231
251
  } else {
232
252
  prop = `.${getAstKeyString(node.property as Identifier | Literal)}`;
233
253
  }
234
254
  return `${obj}${prop}`;
235
255
  }
236
256
  case "CallExpression": {
237
- const callee = emitExpression(node.callee);
257
+ const callee = emitExpression(node.callee, localFunctions);
238
258
  const args = node.arguments
239
259
  .map((argument) => {
240
260
  if (argument.type === "SpreadElement") {
241
- return `...${emitExpression(argument.argument as CompilableExpression)}`;
261
+ return `...${emitExpression(argument.argument as CompilableExpression, localFunctions)}`;
242
262
  }
243
- return emitExpression(argument as CompilableExpression);
263
+ return emitExpression(argument as CompilableExpression, localFunctions);
244
264
  })
245
265
  .join(", ");
246
266
  return `${callee}(${args})`;
247
267
  }
248
268
  case "ArrowFunctionExpression": {
249
- const params = node.params.map((param) => emitExpression(param as CompilableExpression)).join(", ");
250
- // analyze() ensures this is not a BlockStatement
251
- const body = emitExpression(node.body as CompilableExpression);
269
+ const params = node.params
270
+ .map((param) => emitExpression(param as CompilableExpression, localFunctions))
271
+ .join(", ");
272
+ const body = emitExpression(node.body as CompilableExpression, localFunctions);
252
273
  return `(${params}) => ${body}`;
253
274
  }
254
275
  case "JSXElement":
255
- return emitJsxElement(node);
276
+ return emitJsxElement(node, localFunctions);
256
277
  case "JSXFragment":
257
- return emitJsxFragment(node);
278
+ return emitJsxFragment(node, localFunctions);
258
279
  case "JSXEmptyExpression":
259
280
  return "null";
260
281
  default:
@@ -262,14 +283,83 @@ function emitExpression(node: CompilableExpression): string {
262
283
  }
263
284
  }
264
285
 
286
+ function emitVariableDeclaration(node: VariableDeclaration, localFunctions: Set<string>): string {
287
+ const decls = node.declarations
288
+ .map((d) => {
289
+ const name = (d.id as Identifier).name;
290
+ const init = d.init ? emitExpression(d.init as CompilableExpression, localFunctions) : "undefined";
291
+ return `${name} = ${init}`;
292
+ })
293
+ .join(", ");
294
+ return `const ${decls};`;
295
+ }
296
+
297
+ function emitFunctionDeclaration(node: FunctionDeclaration, localFunctions: Set<string>): string {
298
+ const name = node.id!.name;
299
+ const params = node.params.map((p) => emitParam(p, localFunctions)).join(", ");
300
+ const body = emitFunctionBody(node.body as BlockStatement, localFunctions);
301
+ return `function ${name}(${params}) ${body}`;
302
+ }
303
+
304
+ function emitFunctionBody(body: BlockStatement, localFunctions: Set<string>): string {
305
+ const statements = body.body.map((s) => emitStatement(s, localFunctions));
306
+ return `{ ${statements.join(" ")} }`;
307
+ }
308
+
309
+ function emitStatement(statement: Statement, localFunctions: Set<string>): string {
310
+ switch (statement.type) {
311
+ case "ReturnStatement":
312
+ if (!statement.argument) {
313
+ return "return;";
314
+ }
315
+ return `return ${emitExpression(statement.argument as CompilableExpression, localFunctions)};`;
316
+ case "VariableDeclaration":
317
+ return emitVariableDeclaration(statement, localFunctions);
318
+ case "ExpressionStatement":
319
+ return `${emitExpression(statement.expression as CompilableExpression, localFunctions)};`;
320
+ default:
321
+ throw CompilationError.fromNode(`Unsupported statement: ${statement.type}`, statement);
322
+ }
323
+ }
324
+
325
+ function emitParam(param: Pattern, localFunctions: Set<string>): string {
326
+ switch (param.type) {
327
+ case "Identifier":
328
+ return (param as Identifier).name;
329
+ case "ObjectPattern": {
330
+ const props = (param as any).properties.map((p: any) => {
331
+ if (p.type === "RestElement") {
332
+ return `...${emitParam(p.argument, localFunctions)}`;
333
+ }
334
+ const key = (p.key as Identifier).name;
335
+ if (p.value.type === "AssignmentPattern") {
336
+ return `${key} = ${emitExpression(p.value.right as CompilableExpression, localFunctions)}`;
337
+ }
338
+ if (p.value !== p.key && p.value.type === "Identifier" && p.value.name !== key) {
339
+ return `${key}: ${emitParam(p.value, localFunctions)}`;
340
+ }
341
+ return key;
342
+ });
343
+ return `{ ${props.join(", ")} }`;
344
+ }
345
+ case "AssignmentPattern":
346
+ return `${emitParam((param as any).left, localFunctions)} = ${emitExpression(
347
+ (param as any).right as CompilableExpression,
348
+ localFunctions
349
+ )}`;
350
+ case "RestElement":
351
+ return `...${emitParam((param as any).argument, localFunctions)}`;
352
+ default:
353
+ throw CompilationError.fromNode(`Unsupported parameter: ${param.type}`, param);
354
+ }
355
+ }
356
+
265
357
  function getAstKeyString(node: Identifier | Literal): string {
266
358
  if (node.type === "Identifier") {
267
359
  return node.name;
268
360
  } else if (node.type === "Literal") {
269
361
  return JSON.stringify(node.value);
270
362
  } else {
271
- // This should never happen due to type constraints but
272
- // if JSX ever changes this could be possible!
273
363
  throw CompilationError.fromNode("Unsupported key type", node);
274
364
  }
275
365
  }