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.
- package/README.md +155 -188
- package/lib/analyze/analysis-report.d.ts +2 -2
- package/lib/analyze/analysis-report.d.ts.map +1 -1
- package/lib/analyze/analysis-report.js +4 -0
- package/lib/analyze/analysis-report.js.map +1 -1
- package/lib/analyze/data/data-access.d.ts.map +1 -1
- package/lib/analyze/data/data-access.js +45 -82
- package/lib/analyze/data/data-access.js.map +1 -1
- package/lib/analyze/data/method-calls.d.ts.map +1 -1
- package/lib/analyze/data/method-calls.js +4 -3
- package/lib/analyze/data/method-calls.js.map +1 -1
- package/lib/analyze/data/utils.d.ts +2 -3
- package/lib/analyze/data/utils.d.ts.map +1 -1
- package/lib/analyze/data/utils.js +10 -36
- package/lib/analyze/data/utils.js.map +1 -1
- package/lib/analyze/jsx/element-attributes.d.ts +1 -1
- package/lib/analyze/jsx/element-attributes.d.ts.map +1 -1
- package/lib/analyze/jsx/element-attributes.js +13 -3
- package/lib/analyze/jsx/element-attributes.js.map +1 -1
- package/lib/analyze/jsx/element-children.d.ts +1 -1
- package/lib/analyze/jsx/element-children.d.ts.map +1 -1
- package/lib/analyze/jsx/element-children.js +16 -5
- package/lib/analyze/jsx/element-children.js.map +1 -1
- package/lib/analyze/jsx/element-tags.d.ts +1 -1
- package/lib/analyze/jsx/element-tags.d.ts.map +1 -1
- package/lib/analyze/jsx/element-tags.js +12 -2
- package/lib/analyze/jsx/element-tags.js.map +1 -1
- package/lib/analyze/security/analyze.d.ts.map +1 -1
- package/lib/analyze/security/analyze.js +2 -4
- package/lib/analyze/security/analyze.js.map +1 -1
- package/lib/analyze/security/call-expressions.d.ts +2 -2
- package/lib/analyze/security/call-expressions.d.ts.map +1 -1
- package/lib/analyze/security/call-expressions.js +21 -5
- package/lib/analyze/security/call-expressions.js.map +1 -1
- package/lib/analyze/security/declarations.d.ts +1 -1
- package/lib/analyze/security/declarations.d.ts.map +1 -1
- package/lib/analyze/security/declarations.js +22 -2
- package/lib/analyze/security/declarations.js.map +1 -1
- package/lib/analyze/security/expressions.d.ts.map +1 -1
- package/lib/analyze/security/expressions.js +10 -2
- package/lib/analyze/security/expressions.js.map +1 -1
- package/lib/analyze/security/identifiers.d.ts +2 -2
- package/lib/analyze/security/identifiers.d.ts.map +1 -1
- package/lib/analyze/security/identifiers.js +159 -19
- package/lib/analyze/security/identifiers.js.map +1 -1
- package/lib/builtins.d.ts.map +1 -1
- package/lib/builtins.js +5 -0
- package/lib/builtins.js.map +1 -1
- package/lib/codegen/builtins/builtin-types.d.ts.map +1 -1
- package/lib/codegen/builtins/builtin-types.js +7 -1
- package/lib/codegen/builtins/builtin-types.js.map +1 -1
- package/lib/compile/compile.d.ts.map +1 -1
- package/lib/compile/compile.js +131 -61
- package/lib/compile/compile.js.map +1 -1
- package/lib/evaluate/evaluate.d.ts +3 -3
- package/lib/evaluate/evaluate.d.ts.map +1 -1
- package/lib/evaluate/evaluate.js +28 -7
- package/lib/evaluate/evaluate.js.map +1 -1
- package/lib/jsx.d.ts +2 -19
- package/lib/jsx.d.ts.map +1 -1
- package/lib/jsx.js +0 -6
- package/lib/jsx.js.map +1 -1
- package/lib/parse/parse.d.ts.map +1 -1
- package/lib/parse/parse.js +4 -3
- package/lib/parse/parse.js.map +1 -1
- package/lib/render.d.ts +4 -4
- package/lib/render.d.ts.map +1 -1
- package/lib/render.js +7 -9
- package/lib/render.js.map +1 -1
- package/lib/schema.d.ts +2 -7
- package/lib/schema.d.ts.map +1 -1
- package/lib/schema.js +0 -43
- package/lib/schema.js.map +1 -1
- package/lib/traverse.d.ts +4 -4
- package/lib/traverse.d.ts.map +1 -1
- package/lib/traverse.js +118 -40
- package/lib/traverse.js.map +1 -1
- package/lib/validate.d.ts +1 -1
- package/lib/validate.js +1 -1
- package/package.json +4 -3
- package/src/analyze/analysis-report.ts +6 -2
- package/src/analyze/analyze.test.ts +108 -69
- package/src/analyze/data/data-access.ts +45 -90
- package/src/analyze/data/method-calls.ts +4 -3
- package/src/analyze/data/utils.ts +11 -40
- package/src/analyze/jsx/element-attributes.ts +24 -9
- package/src/analyze/jsx/element-children.ts +20 -6
- package/src/analyze/jsx/element-tags.ts +15 -3
- package/src/analyze/security/analyze.ts +1 -3
- package/src/analyze/security/call-expressions.ts +37 -17
- package/src/analyze/security/declarations.ts +35 -9
- package/src/analyze/security/expressions.ts +17 -3
- package/src/analyze/security/identifiers.ts +172 -28
- package/src/builtins.ts +6 -0
- package/src/codegen/builtins/builtin-types.ts +7 -1
- package/src/codegen/generate-typescript-definitions.test.ts +16 -23
- package/src/compile/compile.test.ts +153 -30
- package/src/compile/compile.ts +163 -73
- package/src/evaluate/evaluate.test.ts +276 -14
- package/src/evaluate/evaluate.ts +34 -10
- package/src/jsx.ts +2 -31
- package/src/parse/parse.test.ts +4 -4
- package/src/parse/parse.ts +4 -3
- package/src/render.test.ts +476 -27
- package/src/render.ts +7 -14
- package/src/schema.ts +1 -55
- package/src/traverse.ts +135 -49
- package/src/validate.ts +1 -1
- package/LICENSE +0 -21
- package/lib/analyze/analyze.test.d.ts +0 -2
- package/lib/analyze/analyze.test.d.ts.map +0 -1
- package/lib/analyze/analyze.test.js +0 -763
- package/lib/analyze/analyze.test.js.map +0 -1
- package/lib/analyze/security/member-access.d.ts +0 -6
- package/lib/codegen/generate-typescript-definitions.test.d.ts +0 -2
- package/lib/codegen/generate-typescript-definitions.test.d.ts.map +0 -1
- package/lib/codegen/generate-typescript-definitions.test.js +0 -649
- package/lib/codegen/generate-typescript-definitions.test.js.map +0 -1
- package/lib/compile/compile.test.d.ts +0 -2
- package/lib/compile/compile.test.d.ts.map +0 -1
- package/lib/compile/compile.test.js +0 -146
- package/lib/compile/compile.test.js.map +0 -1
- package/lib/evaluate/evaluate.test.d.ts +0 -2
- package/lib/evaluate/evaluate.test.d.ts.map +0 -1
- package/lib/evaluate/evaluate.test.js +0 -260
- package/lib/evaluate/evaluate.test.js.map +0 -1
- package/lib/parse/parse.test.d.ts +0 -2
- package/lib/parse/parse.test.d.ts.map +0 -1
- package/lib/parse/parse.test.js +0 -80
- package/lib/parse/parse.test.js.map +0 -1
- package/lib/render.test.d.ts +0 -2
- package/lib/render.test.d.ts.map +0 -1
- package/lib/render.test.js +0 -301
- package/lib/render.test.js.map +0 -1
- package/lib/traverse.test.d.ts +0 -2
- package/lib/traverse.test.d.ts.map +0 -1
- package/lib/traverse.test.js +0 -84
- package/lib/traverse.test.js.map +0 -1
- package/lib/types.d.ts +0 -12
- package/src/analyze/security/member-access.ts +0 -35
- 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("{
|
|
53
|
+
const ast = parse("{user.name}");
|
|
54
54
|
const result = compile(ast);
|
|
55
|
-
expect(result).toBe('"use strict";return
|
|
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("{
|
|
59
|
+
const ast = parse("{count * 2}");
|
|
60
60
|
const result = compile(ast);
|
|
61
|
-
expect(result).toBe('"use strict";return (
|
|
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("{
|
|
65
|
+
const ast = parse("{text.toUpperCase()}");
|
|
66
66
|
const result = compile(ast);
|
|
67
|
-
expect(result).toBe('"use strict";return
|
|
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("{
|
|
71
|
+
const ast = parse("{items.filter(x => x.length > 2).length}");
|
|
72
72
|
const result = compile(ast);
|
|
73
|
-
expect(result).toBe('"use strict";return
|
|
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 {}{
|
|
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("
|
|
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>{[...
|
|
118
|
+
const ast = parse("<div>{[...items]}</div>");
|
|
119
119
|
const result = compile(ast);
|
|
120
|
-
expect(result).toBe('"use strict";return h("div", null, [...
|
|
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={{ ...
|
|
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: { ...
|
|
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, ...
|
|
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, ...
|
|
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', ...
|
|
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>{[...
|
|
142
|
+
const ast = parse("<div>{[...first, ...second]}</div>");
|
|
145
143
|
const result = compile(ast);
|
|
146
|
-
expect(result).toBe('"use strict";return h("div", null, [...
|
|
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={{ ...
|
|
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: { ...
|
|
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(...
|
|
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(...
|
|
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(...
|
|
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(...
|
|
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, ...
|
|
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).
|
|
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
|
|
package/src/compile/compile.ts
CHANGED
|
@@ -1,72 +1,87 @@
|
|
|
1
|
-
import type {
|
|
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
|
-
|
|
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
|
|
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
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
|
250
|
-
|
|
251
|
-
|
|
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
|
}
|