jsxpression 0.1.41 → 0.2.1
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/lib/analyze/analysis-error.d.ts.map +1 -1
- package/lib/analyze/analysis-error.js +2 -7
- package/lib/analyze/analysis-error.js.map +1 -1
- package/lib/analyze/analysis-report.d.ts +13 -13
- package/lib/analyze/analysis-report.d.ts.map +1 -1
- package/lib/analyze/analysis-report.js +48 -45
- package/lib/analyze/analysis-report.js.map +1 -1
- package/lib/analyze/analyze.d.ts +1 -1
- package/lib/analyze/analyze.d.ts.map +1 -1
- package/lib/analyze/analyze.js +4 -4
- package/lib/analyze/analyze.js.map +1 -1
- package/lib/analyze/data/analyze.d.ts +1 -1
- package/lib/analyze/data/analyze.d.ts.map +1 -1
- package/lib/analyze/data/analyze.js +5 -5
- package/lib/analyze/data/analyze.js.map +1 -1
- package/lib/analyze/data/data-access.d.ts +1 -1
- package/lib/analyze/data/data-access.d.ts.map +1 -1
- package/lib/analyze/data/data-access.js +19 -15
- package/lib/analyze/data/data-access.js.map +1 -1
- package/lib/analyze/data/method-calls.d.ts +1 -1
- package/lib/analyze/data/method-calls.d.ts.map +1 -1
- package/lib/analyze/data/method-calls.js +5 -5
- package/lib/analyze/data/method-calls.js.map +1 -1
- package/lib/analyze/data/utils.d.ts +3 -3
- package/lib/analyze/data/utils.d.ts.map +1 -1
- package/lib/analyze/data/utils.js +6 -5
- package/lib/analyze/data/utils.js.map +1 -1
- package/lib/analyze/index.d.ts +1 -1
- package/lib/analyze/index.d.ts.map +1 -1
- package/lib/analyze/index.js +1 -1
- package/lib/analyze/index.js.map +1 -1
- package/lib/analyze/jsx/analyze.d.ts +1 -1
- package/lib/analyze/jsx/analyze.d.ts.map +1 -1
- package/lib/analyze/jsx/analyze.js +4 -4
- package/lib/analyze/jsx/analyze.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 +3 -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 +3 -3
- 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 +2 -2
- package/lib/analyze/jsx/element-tags.js.map +1 -1
- package/lib/analyze/security/analyze.d.ts +1 -1
- package/lib/analyze/security/analyze.d.ts.map +1 -1
- package/lib/analyze/security/analyze.js +4 -4
- package/lib/analyze/security/analyze.js.map +1 -1
- package/lib/analyze/security/call-expressions.d.ts +1 -1
- package/lib/analyze/security/call-expressions.d.ts.map +1 -1
- package/lib/analyze/security/call-expressions.js +3 -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 +6 -6
- package/lib/analyze/security/declarations.js.map +1 -1
- package/lib/analyze/security/expressions.d.ts +1 -1
- package/lib/analyze/security/expressions.d.ts.map +1 -1
- package/lib/analyze/security/expressions.js +3 -6
- package/lib/analyze/security/expressions.js.map +1 -1
- package/lib/analyze/security/identifiers.d.ts +1 -1
- package/lib/analyze/security/identifiers.d.ts.map +1 -1
- package/lib/analyze/security/identifiers.js +19 -22
- package/lib/analyze/security/identifiers.js.map +1 -1
- package/lib/analyze/utils.d.ts +4 -4
- package/lib/analyze/utils.d.ts.map +1 -1
- package/lib/analyze/utils.js +5 -3
- package/lib/analyze/utils.js.map +1 -1
- package/lib/analyze/validation-context.d.ts +8 -8
- package/lib/analyze/validation-context.d.ts.map +1 -1
- package/lib/analyze/validation-context.js +23 -21
- package/lib/analyze/validation-context.js.map +1 -1
- package/lib/builtins.d.ts +6 -6
- package/lib/builtins.d.ts.map +1 -1
- package/lib/builtins.js +9 -8
- 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 +9 -13
- package/lib/codegen/builtins/builtin-types.js.map +1 -1
- package/lib/codegen/generate-typescript-definitions.d.ts +18 -17
- package/lib/codegen/generate-typescript-definitions.d.ts.map +1 -1
- package/lib/codegen/generate-typescript-definitions.js +19 -23
- package/lib/codegen/generate-typescript-definitions.js.map +1 -1
- package/lib/codegen/schema/component-types.d.ts.map +1 -1
- package/lib/codegen/schema/component-types.js +7 -4
- package/lib/codegen/schema/component-types.js.map +1 -1
- package/lib/codegen/schema/data-types.d.ts.map +1 -1
- package/lib/codegen/schema/data-types.js +1 -1
- package/lib/codegen/schema/data-types.js.map +1 -1
- package/lib/codegen/schema/function-types.d.ts.map +1 -1
- package/lib/codegen/schema/function-types.js +1 -3
- package/lib/codegen/schema/function-types.js.map +1 -1
- package/lib/codegen/schema/utils.d.ts +4 -3
- package/lib/codegen/schema/utils.d.ts.map +1 -1
- package/lib/codegen/schema/utils.js +12 -8
- package/lib/codegen/schema/utils.js.map +1 -1
- package/lib/compile/compilation-error.d.ts +1 -1
- package/lib/compile/compilation-error.d.ts.map +1 -1
- package/lib/compile/compilation-error.js +3 -1
- package/lib/compile/compilation-error.js.map +1 -1
- package/lib/compile/compile.d.ts.map +1 -1
- package/lib/compile/compile.js +5 -11
- package/lib/compile/compile.js.map +1 -1
- package/lib/evaluate/evaluate.d.ts +6 -6
- package/lib/evaluate/evaluate.d.ts.map +1 -1
- package/lib/evaluate/evaluate.js +1 -1
- package/lib/evaluate/evaluate.js.map +1 -1
- package/lib/evaluate/evaluation-error.d.ts.map +1 -1
- package/lib/evaluate/evaluation-error.js +1 -0
- package/lib/evaluate/evaluation-error.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/jsx.d.ts +3 -3
- package/lib/jsx.d.ts.map +1 -1
- package/lib/jsx.js.map +1 -1
- package/lib/parse/parse-error.d.ts.map +1 -1
- package/lib/parse/parse-error.js +1 -0
- package/lib/parse/parse-error.js.map +1 -1
- package/lib/render.d.ts +25 -33
- package/lib/render.d.ts.map +1 -1
- package/lib/render.js +21 -26
- package/lib/render.js.map +1 -1
- package/lib/schema.d.ts +9 -9
- package/lib/schema.d.ts.map +1 -1
- package/lib/schema.js +1 -1
- package/lib/schema.js.map +1 -1
- package/lib/traverse.d.ts.map +1 -1
- package/lib/traverse.js.map +1 -1
- package/lib/validate.d.ts +14 -17
- package/lib/validate.d.ts.map +1 -1
- package/lib/validate.js +13 -14
- package/lib/validate.js.map +1 -1
- package/package.json +12 -12
- package/src/analyze/analysis-error.ts +2 -6
- package/src/analyze/analysis-report.ts +73 -62
- package/src/analyze/analyze.test.ts +60 -60
- package/src/analyze/analyze.ts +4 -4
- package/src/analyze/data/analyze.ts +5 -8
- package/src/analyze/data/data-access.ts +49 -46
- package/src/analyze/data/method-calls.ts +9 -18
- package/src/analyze/data/utils.ts +15 -19
- package/src/analyze/index.ts +1 -1
- package/src/analyze/jsx/analyze.ts +4 -4
- package/src/analyze/jsx/element-attributes.ts +5 -18
- package/src/analyze/jsx/element-children.ts +4 -8
- package/src/analyze/jsx/element-tags.ts +2 -2
- package/src/analyze/security/analyze.ts +4 -4
- package/src/analyze/security/call-expressions.ts +5 -18
- package/src/analyze/security/declarations.ts +14 -43
- package/src/analyze/security/expressions.ts +12 -49
- package/src/analyze/security/identifiers.ts +37 -39
- package/src/analyze/utils.ts +25 -19
- package/src/analyze/validation-context.ts +32 -23
- package/src/builtins.ts +27 -38
- package/src/codegen/builtins/builtin-types.ts +13 -20
- package/src/codegen/generate-typescript-definitions.test.ts +6 -6
- package/src/codegen/generate-typescript-definitions.ts +19 -23
- package/src/codegen/schema/component-types.ts +22 -12
- package/src/codegen/schema/data-types.ts +6 -6
- package/src/codegen/schema/function-types.ts +1 -3
- package/src/codegen/schema/utils.ts +24 -23
- package/src/compile/compilation-error.ts +5 -1
- package/src/compile/compile.test.ts +7 -21
- package/src/compile/compile.ts +18 -30
- package/src/evaluate/evaluate.test.ts +78 -94
- package/src/evaluate/evaluate.ts +15 -20
- package/src/evaluate/evaluation-error.ts +1 -0
- package/src/index.ts +1 -7
- package/src/jsx.ts +4 -6
- package/src/parse/parse-error.ts +1 -0
- package/src/parse/parse.test.ts +16 -18
- package/src/render.test.ts +4 -4
- package/src/render.ts +26 -38
- package/src/schema.ts +11 -11
- package/src/traverse.test.ts +6 -6
- package/src/traverse.ts +3 -3
- package/src/validate.ts +14 -17
- package/CHANGELOG.md +0 -8
|
@@ -30,9 +30,7 @@ describe("compile", () => {
|
|
|
30
30
|
it("should compile nested ternary expressions", () => {
|
|
31
31
|
const ast = parse("<div>{props.level > 2 ? 'high' : props.level > 1 ? 'medium' : 'low'}</div>");
|
|
32
32
|
const result = compile(ast);
|
|
33
|
-
expect(result).toBe(
|
|
34
|
-
'"use strict";return h("div", null, ((props.level > 2)?"high":((props.level > 1)?"medium":"low")));'
|
|
35
|
-
);
|
|
33
|
+
expect(result).toBe('"use strict";return h("div", null, ((props.level > 2)?"high":((props.level > 1)?"medium":"low")));');
|
|
36
34
|
});
|
|
37
35
|
|
|
38
36
|
it("should compile nested JSX elements", () => {
|
|
@@ -44,9 +42,7 @@ describe("compile", () => {
|
|
|
44
42
|
it("should compile nested array operations", () => {
|
|
45
43
|
const ast = parse("<div>{props.items.map(x => x.split(',').map(y => y.trim()).join(' ')).join(' | ')}</div>");
|
|
46
44
|
const result = compile(ast);
|
|
47
|
-
expect(result).toBe(
|
|
48
|
-
'"use strict";return h("div", null, props.items.map((x) => x.split(",").map((y) => y.trim()).join(" ")).join(" | "));'
|
|
49
|
-
);
|
|
45
|
+
expect(result).toBe('"use strict";return h("div", null, props.items.map((x) => x.split(",").map((y) => y.trim()).join(" ")).join(" | "));');
|
|
50
46
|
});
|
|
51
47
|
|
|
52
48
|
it("should compile bare expression containers", () => {
|
|
@@ -217,23 +213,17 @@ describe("compile", () => {
|
|
|
217
213
|
it("should compile function with props parameter", () => {
|
|
218
214
|
const ast = parse('function Badge({ label }) {\n return <span>{label}</span>\n}\nreturn <Badge label="hi" />');
|
|
219
215
|
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
|
-
);
|
|
216
|
+
expect(result).toBe('"use strict";function Badge({ label }) { return h("span", null, label); }return h(Badge, { label: "hi" });');
|
|
223
217
|
});
|
|
224
218
|
|
|
225
219
|
it("should compile function with default parameter", () => {
|
|
226
220
|
const ast = parse("function Badge({ label = 'default' }) {\n return <span>{label}</span>\n}\nreturn <Badge />");
|
|
227
221
|
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
|
-
);
|
|
222
|
+
expect(result).toBe('"use strict";function Badge({ label = "default" }) { return h("span", null, label); }return h(Badge, null);');
|
|
231
223
|
});
|
|
232
224
|
|
|
233
225
|
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
|
-
);
|
|
226
|
+
const ast = parse("function Badge({ value }) {\n const doubled = value * 2;\n return <span>{doubled}</span>\n}\nreturn <Badge value={5} />");
|
|
237
227
|
const result = compile(ast);
|
|
238
228
|
expect(result).toContain("const doubled = (value * 2);");
|
|
239
229
|
expect(result).toContain('return h("span", null, doubled);');
|
|
@@ -248,9 +238,7 @@ describe("compile", () => {
|
|
|
248
238
|
});
|
|
249
239
|
|
|
250
240
|
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
|
-
);
|
|
241
|
+
const ast = parse("function A() {\n return <span>a</span>\n}\nfunction B() {\n return <span>b</span>\n}\nreturn <div><A /><B /></div>");
|
|
254
242
|
const result = compile(ast);
|
|
255
243
|
expect(result).toContain("function A()");
|
|
256
244
|
expect(result).toContain("function B()");
|
|
@@ -259,9 +247,7 @@ describe("compile", () => {
|
|
|
259
247
|
});
|
|
260
248
|
|
|
261
249
|
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
|
-
);
|
|
250
|
+
const ast = parse("function Inner() {\n return <span>inner</span>\n}\nfunction Outer() {\n return <div><Inner /></div>\n}\nreturn <Outer />");
|
|
265
251
|
const result = compile(ast);
|
|
266
252
|
expect(result).toContain("function Inner()");
|
|
267
253
|
expect(result).toContain("function Outer()");
|
package/src/compile/compile.ts
CHANGED
|
@@ -11,7 +11,6 @@ import type {
|
|
|
11
11
|
Super,
|
|
12
12
|
VariableDeclaration,
|
|
13
13
|
} from "acorn";
|
|
14
|
-
|
|
15
14
|
import {
|
|
16
15
|
isJsxElement,
|
|
17
16
|
isJsxEmptyExpression,
|
|
@@ -40,7 +39,7 @@ export function compile(ast: Program): string {
|
|
|
40
39
|
}
|
|
41
40
|
}
|
|
42
41
|
|
|
43
|
-
const parts: string
|
|
42
|
+
const parts: Array<string> = ['"use strict";'];
|
|
44
43
|
|
|
45
44
|
const hasDeclarations = ast.body.some((s) => s.type === "VariableDeclaration" || s.type === "FunctionDeclaration");
|
|
46
45
|
|
|
@@ -108,13 +107,13 @@ function emitJsxName(nameNode: JSXNameNode, localFunctions: Set<string>): string
|
|
|
108
107
|
return JSON.stringify(JSON.parse(left) + "." + JSON.parse(right));
|
|
109
108
|
}
|
|
110
109
|
default:
|
|
111
|
-
throw CompilationError.fromNode(`Unsupported JSX name: ${(nameNode as
|
|
110
|
+
throw CompilationError.fromNode(`Unsupported JSX name: ${(nameNode as { type: string }).type}`, nameNode);
|
|
112
111
|
}
|
|
113
112
|
}
|
|
114
113
|
|
|
115
|
-
function emitAttributes(attributes:
|
|
116
|
-
const parts: string
|
|
117
|
-
let currentObj: string
|
|
114
|
+
function emitAttributes(attributes: Array<JSXAttribute | JSXSpreadAttribute>, localFunctions: Set<string>): string {
|
|
115
|
+
const parts: Array<string> = [];
|
|
116
|
+
let currentObj: Array<string> = [];
|
|
118
117
|
|
|
119
118
|
const flushCurrent = (): void => {
|
|
120
119
|
if (currentObj.length) {
|
|
@@ -136,7 +135,7 @@ function emitAttributes(attributes: (JSXAttribute | JSXSpreadAttribute)[], local
|
|
|
136
135
|
|
|
137
136
|
const key = attribute.name?.name;
|
|
138
137
|
|
|
139
|
-
let value: string;
|
|
138
|
+
let value: string = "";
|
|
140
139
|
if (!attribute.value) {
|
|
141
140
|
value = "true";
|
|
142
141
|
} else if (attribute.value.type === "Literal") {
|
|
@@ -163,8 +162,8 @@ function emitAttributes(attributes: (JSXAttribute | JSXSpreadAttribute)[], local
|
|
|
163
162
|
return `Object.assign({}, ${parts.join(", ")})`;
|
|
164
163
|
}
|
|
165
164
|
|
|
166
|
-
function emitJsxChildren(children: JSXNode
|
|
167
|
-
const result: string
|
|
165
|
+
function emitJsxChildren(children: Array<JSXNode>, localFunctions: Set<string>): Array<string> {
|
|
166
|
+
const result: Array<string> = [];
|
|
168
167
|
|
|
169
168
|
for (const child of children) {
|
|
170
169
|
if (isJsxText(child)) {
|
|
@@ -195,7 +194,7 @@ function emitExpression(node: CompilableExpression, localFunctions: Set<string>)
|
|
|
195
194
|
case "Identifier":
|
|
196
195
|
return node.name;
|
|
197
196
|
case "TemplateLiteral": {
|
|
198
|
-
const bits: string
|
|
197
|
+
const bits: Array<string> = [];
|
|
199
198
|
node.quasis.forEach((quasi, index) => {
|
|
200
199
|
bits.push(JSON.stringify(quasi.value.cooked ?? ""));
|
|
201
200
|
if (node.expressions.length > index) {
|
|
@@ -210,10 +209,7 @@ function emitExpression(node: CompilableExpression, localFunctions: Set<string>)
|
|
|
210
209
|
return `${node.operator}${emitExpression(node.argument, localFunctions)}`;
|
|
211
210
|
case "BinaryExpression":
|
|
212
211
|
case "LogicalExpression":
|
|
213
|
-
return `(${emitExpression(node.left, localFunctions)} ${node.operator} ${emitExpression(
|
|
214
|
-
node.right,
|
|
215
|
-
localFunctions
|
|
216
|
-
)})`;
|
|
212
|
+
return `(${emitExpression(node.left, localFunctions)} ${node.operator} ${emitExpression(node.right, localFunctions)})`;
|
|
217
213
|
case "ConditionalExpression":
|
|
218
214
|
return `(${emitExpression(node.test, localFunctions)}?${emitExpression(
|
|
219
215
|
node.consequent,
|
|
@@ -245,12 +241,9 @@ function emitExpression(node: CompilableExpression, localFunctions: Set<string>)
|
|
|
245
241
|
}
|
|
246
242
|
case "MemberExpression": {
|
|
247
243
|
const obj = emitExpression(node.object, localFunctions);
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
} else {
|
|
252
|
-
prop = `.${getAstKeyString(node.property as Identifier | Literal)}`;
|
|
253
|
-
}
|
|
244
|
+
const prop: string = node.computed
|
|
245
|
+
? `[${emitExpression(node.property, localFunctions)}]`
|
|
246
|
+
: `.${getAstKeyString(node.property as Identifier | Literal)}`;
|
|
254
247
|
return `${obj}${prop}`;
|
|
255
248
|
}
|
|
256
249
|
case "CallExpression": {
|
|
@@ -266,9 +259,7 @@ function emitExpression(node: CompilableExpression, localFunctions: Set<string>)
|
|
|
266
259
|
return `${callee}(${args})`;
|
|
267
260
|
}
|
|
268
261
|
case "ArrowFunctionExpression": {
|
|
269
|
-
const params = node.params
|
|
270
|
-
.map((param) => emitExpression(param as CompilableExpression, localFunctions))
|
|
271
|
-
.join(", ");
|
|
262
|
+
const params = node.params.map((param) => emitExpression(param as CompilableExpression, localFunctions)).join(", ");
|
|
272
263
|
const body = emitExpression(node.body as CompilableExpression, localFunctions);
|
|
273
264
|
return `(${params}) => ${body}`;
|
|
274
265
|
}
|
|
@@ -327,13 +318,13 @@ function emitParam(param: Pattern, localFunctions: Set<string>): string {
|
|
|
327
318
|
case "Identifier":
|
|
328
319
|
return param.name;
|
|
329
320
|
case "ObjectPattern": {
|
|
330
|
-
const props =
|
|
321
|
+
const props = param.properties.map((p) => {
|
|
331
322
|
if (p.type === "RestElement") {
|
|
332
323
|
return `...${emitParam(p.argument, localFunctions)}`;
|
|
333
324
|
}
|
|
334
325
|
const key = (p.key as Identifier).name;
|
|
335
326
|
if (p.value.type === "AssignmentPattern") {
|
|
336
|
-
return `${key} = ${emitExpression(p.value.right
|
|
327
|
+
return `${key} = ${emitExpression(p.value.right, localFunctions)}`;
|
|
337
328
|
}
|
|
338
329
|
if (p.value !== p.key && p.value.type === "Identifier" && p.value.name !== key) {
|
|
339
330
|
return `${key}: ${emitParam(p.value, localFunctions)}`;
|
|
@@ -343,12 +334,9 @@ function emitParam(param: Pattern, localFunctions: Set<string>): string {
|
|
|
343
334
|
return `{ ${props.join(", ")} }`;
|
|
344
335
|
}
|
|
345
336
|
case "AssignmentPattern":
|
|
346
|
-
return `${emitParam(
|
|
347
|
-
(param as any).right as CompilableExpression,
|
|
348
|
-
localFunctions
|
|
349
|
-
)}`;
|
|
337
|
+
return `${emitParam(param.left, localFunctions)} = ${emitExpression(param.right, localFunctions)}`;
|
|
350
338
|
case "RestElement":
|
|
351
|
-
return `...${emitParam(
|
|
339
|
+
return `...${emitParam(param.argument, localFunctions)}`;
|
|
352
340
|
default:
|
|
353
341
|
throw CompilationError.fromNode(`Unsupported parameter: ${param.type}`, param);
|
|
354
342
|
}
|
|
@@ -1,35 +1,35 @@
|
|
|
1
1
|
import { describe, it, expect, vi } from "vitest";
|
|
2
|
-
import { evaluate, type EvaluateOptions } from "./evaluate.js";
|
|
3
2
|
import { compile } from "../compile/index.js";
|
|
4
3
|
import { parse } from "../parse/index.js";
|
|
5
4
|
import type { Schema } from "../schema.js";
|
|
6
|
-
|
|
5
|
+
import { evaluate, type Component, type CreateElement, type EvaluateOptions, type Node as EvalNode, type PropsDict } from "./evaluate.js";
|
|
7
6
|
import { EvaluationError } from "./evaluation-error.js";
|
|
8
7
|
|
|
9
8
|
type Node = {
|
|
10
9
|
type: string;
|
|
11
10
|
props: Record<string, unknown>;
|
|
12
|
-
children: Node
|
|
11
|
+
children: Array<Node>;
|
|
13
12
|
};
|
|
14
13
|
|
|
15
|
-
function createPermissiveSchema(componentNames: string
|
|
14
|
+
function createPermissiveSchema(componentNames: Array<string>): Schema {
|
|
16
15
|
return {
|
|
17
16
|
elements: Object.fromEntries(componentNames.map((name) => [name, {}])),
|
|
18
17
|
};
|
|
19
18
|
}
|
|
20
19
|
|
|
21
20
|
describe("evaluate", () => {
|
|
22
|
-
const compileAndEvaluate = (source: string, options: EvaluateOptions = {}): Node => {
|
|
23
|
-
const componentNames = Object.keys(options.components
|
|
21
|
+
const compileAndEvaluate = (source: string, options: EvaluateOptions<Node> = {}): Node => {
|
|
22
|
+
const componentNames = Object.keys(options.components ?? {});
|
|
24
23
|
const schema = createPermissiveSchema(componentNames);
|
|
25
24
|
return evaluate(compile(parse(source)), schema, options);
|
|
26
25
|
};
|
|
27
26
|
|
|
28
27
|
it("should evaluate JSX with props", () => {
|
|
29
|
-
const TestComponent = (props:
|
|
28
|
+
const TestComponent = (props: PropsDict): Node => ({
|
|
30
29
|
type: "TestComponent",
|
|
31
30
|
props: { x: props.x, y: props.y },
|
|
32
|
-
|
|
31
|
+
// props.children is genuinely dynamic (mixed strings/numbers/nested Node results); this fixture assumes the Node shape its own assertions expect.
|
|
32
|
+
children: props.children as Array<Node>,
|
|
33
33
|
});
|
|
34
34
|
|
|
35
35
|
const result = compileAndEvaluate("<Test x={x} y={y}>Hello</Test>", {
|
|
@@ -47,7 +47,7 @@ describe("evaluate", () => {
|
|
|
47
47
|
});
|
|
48
48
|
|
|
49
49
|
it("should evaluate JSX with Math operations", () => {
|
|
50
|
-
const TestComponent = (props:
|
|
50
|
+
const TestComponent = (props: PropsDict): Node => ({
|
|
51
51
|
type: "TestComponent",
|
|
52
52
|
props,
|
|
53
53
|
children: [],
|
|
@@ -68,16 +68,15 @@ describe("evaluate", () => {
|
|
|
68
68
|
const emptySchema = createPermissiveSchema([]);
|
|
69
69
|
|
|
70
70
|
expect(() => evaluate(compiled, emptySchema, { components: {} })).toThrow(EvaluationError);
|
|
71
|
-
expect(() => evaluate(compiled, emptySchema, { components: {} })).toThrow(
|
|
72
|
-
'component "UnknownComponent" is not allowed'
|
|
73
|
-
);
|
|
71
|
+
expect(() => evaluate(compiled, emptySchema, { components: {} })).toThrow('component "UnknownComponent" is not allowed');
|
|
74
72
|
});
|
|
75
73
|
|
|
76
74
|
it("should deep-freeze props to prevent mutations", () => {
|
|
77
|
-
const TestComponent = (props:
|
|
75
|
+
const TestComponent = (props: PropsDict): Node => {
|
|
78
76
|
expect(() => {
|
|
79
|
-
props.nested
|
|
80
|
-
|
|
77
|
+
// props.nested's shape isn't known statically; this test only cares that mutating it throws once frozen.
|
|
78
|
+
(props.nested as Record<string, unknown>).value = 999;
|
|
79
|
+
}).toThrow(TypeError);
|
|
81
80
|
return { type: "Test", props, children: [] };
|
|
82
81
|
};
|
|
83
82
|
|
|
@@ -93,16 +92,16 @@ describe("evaluate", () => {
|
|
|
93
92
|
});
|
|
94
93
|
|
|
95
94
|
it("should evaluate nested elements", () => {
|
|
96
|
-
const OuterComponent = (props:
|
|
95
|
+
const OuterComponent = (props: PropsDict): Node => ({
|
|
97
96
|
type: "Outer",
|
|
98
97
|
props: { x: props.x },
|
|
99
|
-
children: props.children
|
|
98
|
+
children: props.children as Array<Node>,
|
|
100
99
|
});
|
|
101
100
|
|
|
102
|
-
const InnerComponent = (props:
|
|
101
|
+
const InnerComponent = (props: PropsDict): Node => ({
|
|
103
102
|
type: "Inner",
|
|
104
103
|
props: { y: props.y },
|
|
105
|
-
children: props.children
|
|
104
|
+
children: props.children as Array<Node>,
|
|
106
105
|
});
|
|
107
106
|
|
|
108
107
|
const result = compileAndEvaluate("<Outer x={x}><Inner y={y}>Text</Inner></Outer>", {
|
|
@@ -123,10 +122,10 @@ describe("evaluate", () => {
|
|
|
123
122
|
});
|
|
124
123
|
|
|
125
124
|
it("should use custom createElement when provided", () => {
|
|
126
|
-
const customCreateElement = (type:
|
|
127
|
-
type: type.name
|
|
125
|
+
const customCreateElement = (type: Component, props: PropsDict | undefined, ...children: Array<unknown>): Node => ({
|
|
126
|
+
type: type.name,
|
|
128
127
|
props: { ...props, custom: true },
|
|
129
|
-
children
|
|
128
|
+
children: children as Array<Node>,
|
|
130
129
|
});
|
|
131
130
|
|
|
132
131
|
const TestComponent = (): Node => ({ type: "Test", props: {}, children: [] });
|
|
@@ -146,7 +145,7 @@ describe("evaluate", () => {
|
|
|
146
145
|
});
|
|
147
146
|
|
|
148
147
|
it("should evaluate conditional expressions with props", () => {
|
|
149
|
-
const TestComponent = (props:
|
|
148
|
+
const TestComponent = (props: PropsDict): Node => ({
|
|
150
149
|
type: "TestComponent",
|
|
151
150
|
props,
|
|
152
151
|
children: [],
|
|
@@ -163,7 +162,7 @@ describe("evaluate", () => {
|
|
|
163
162
|
});
|
|
164
163
|
|
|
165
164
|
it("should evaluate array methods on props", () => {
|
|
166
|
-
const TestComponent = (props:
|
|
165
|
+
const TestComponent = (props: PropsDict): Node => ({
|
|
167
166
|
type: "TestComponent",
|
|
168
167
|
props,
|
|
169
168
|
children: [],
|
|
@@ -195,7 +194,7 @@ describe("evaluate", () => {
|
|
|
195
194
|
});
|
|
196
195
|
|
|
197
196
|
it("should evaluate logical operators with props", () => {
|
|
198
|
-
const TestComponent = (props:
|
|
197
|
+
const TestComponent = (props: PropsDict): Node => ({
|
|
199
198
|
type: "TestComponent",
|
|
200
199
|
props,
|
|
201
200
|
children: [],
|
|
@@ -212,10 +211,10 @@ describe("evaluate", () => {
|
|
|
212
211
|
});
|
|
213
212
|
|
|
214
213
|
it("should flatten and filter children correctly", () => {
|
|
215
|
-
const TestComponent = (props:
|
|
214
|
+
const TestComponent = (props: PropsDict): Node => ({
|
|
216
215
|
type: "Test",
|
|
217
216
|
props: {},
|
|
218
|
-
children: props.children
|
|
217
|
+
children: props.children as Array<Node>,
|
|
219
218
|
});
|
|
220
219
|
|
|
221
220
|
const result = compileAndEvaluate("<Test>{show && 'Visible'}{false}{'Always'}</Test>", {
|
|
@@ -240,10 +239,10 @@ describe("evaluate", () => {
|
|
|
240
239
|
});
|
|
241
240
|
|
|
242
241
|
it("should handle empty JSX expressions", () => {
|
|
243
|
-
const TestComponent = (props:
|
|
242
|
+
const TestComponent = (props: PropsDict): Node => ({
|
|
244
243
|
type: "Test",
|
|
245
244
|
props: {},
|
|
246
|
-
children: props.children
|
|
245
|
+
children: props.children as Array<Node>,
|
|
247
246
|
});
|
|
248
247
|
|
|
249
248
|
const result = compileAndEvaluate("<Test>{}{value}{}</Test>", {
|
|
@@ -255,25 +254,22 @@ describe("evaluate", () => {
|
|
|
255
254
|
});
|
|
256
255
|
|
|
257
256
|
it("should evaluate complex nested expressions", () => {
|
|
258
|
-
const TestComponent = (props:
|
|
257
|
+
const TestComponent = (props: PropsDict): Node => ({
|
|
259
258
|
type: "Test",
|
|
260
259
|
props,
|
|
261
260
|
children: [],
|
|
262
261
|
});
|
|
263
262
|
|
|
264
|
-
const result = compileAndEvaluate(
|
|
265
|
-
|
|
266
|
-
{
|
|
267
|
-
|
|
268
|
-
components: { Test: TestComponent },
|
|
269
|
-
}
|
|
270
|
-
);
|
|
263
|
+
const result = compileAndEvaluate("<Test values={items.filter(x => x > 2).map(x => x * 2).join(', ')}>Text</Test>", {
|
|
264
|
+
data: { items: [1, 2, 3, 4, 5] },
|
|
265
|
+
components: { Test: TestComponent },
|
|
266
|
+
});
|
|
271
267
|
|
|
272
268
|
expect(result.props.values).toBe("6, 8, 10");
|
|
273
269
|
});
|
|
274
270
|
|
|
275
271
|
it("should pass correct parameters to custom createElement", () => {
|
|
276
|
-
const mockCreateElement = vi.fn();
|
|
272
|
+
const mockCreateElement = vi.fn<CreateElement<EvalNode>>();
|
|
277
273
|
|
|
278
274
|
compileAndEvaluate("<Test x={10}>Hello</Test>", {
|
|
279
275
|
components: { Test: () => ({ type: "Test", props: {}, children: [] }) },
|
|
@@ -284,7 +280,7 @@ describe("evaluate", () => {
|
|
|
284
280
|
});
|
|
285
281
|
|
|
286
282
|
it("should handle string literals in expressions", () => {
|
|
287
|
-
const TestComponent = (props:
|
|
283
|
+
const TestComponent = (props: PropsDict): Node => ({
|
|
288
284
|
type: "Test",
|
|
289
285
|
props,
|
|
290
286
|
children: [],
|
|
@@ -299,7 +295,7 @@ describe("evaluate", () => {
|
|
|
299
295
|
});
|
|
300
296
|
|
|
301
297
|
it("should handle numeric operations", () => {
|
|
302
|
-
const TestComponent = (props:
|
|
298
|
+
const TestComponent = (props: PropsDict): Node => ({
|
|
303
299
|
type: "Test",
|
|
304
300
|
props,
|
|
305
301
|
children: [],
|
|
@@ -314,10 +310,10 @@ describe("evaluate", () => {
|
|
|
314
310
|
});
|
|
315
311
|
|
|
316
312
|
it("should handle data access in children", () => {
|
|
317
|
-
const TestComponent = (props:
|
|
313
|
+
const TestComponent = (props: PropsDict): Node => ({
|
|
318
314
|
type: "Test",
|
|
319
315
|
props: {},
|
|
320
|
-
children: props.children
|
|
316
|
+
children: props.children as Array<Node>,
|
|
321
317
|
});
|
|
322
318
|
|
|
323
319
|
const result = compileAndEvaluate("<Test>Hello {name}!</Test>", {
|
|
@@ -330,7 +326,7 @@ describe("evaluate", () => {
|
|
|
330
326
|
|
|
331
327
|
describe("const declarations", () => {
|
|
332
328
|
it("should evaluate const with literal value", () => {
|
|
333
|
-
const TestComponent = (props:
|
|
329
|
+
const TestComponent = (props: PropsDict): Node => ({
|
|
334
330
|
type: "Test",
|
|
335
331
|
props,
|
|
336
332
|
children: [],
|
|
@@ -344,7 +340,7 @@ describe("evaluate", () => {
|
|
|
344
340
|
});
|
|
345
341
|
|
|
346
342
|
it("should evaluate const with computed expression", () => {
|
|
347
|
-
const TestComponent = (props:
|
|
343
|
+
const TestComponent = (props: PropsDict): Node => ({
|
|
348
344
|
type: "Test",
|
|
349
345
|
props,
|
|
350
346
|
children: [],
|
|
@@ -359,7 +355,7 @@ describe("evaluate", () => {
|
|
|
359
355
|
});
|
|
360
356
|
|
|
361
357
|
it("should evaluate const referencing data", () => {
|
|
362
|
-
const TestComponent = (props:
|
|
358
|
+
const TestComponent = (props: PropsDict): Node => ({
|
|
363
359
|
type: "Test",
|
|
364
360
|
props,
|
|
365
361
|
children: [],
|
|
@@ -374,19 +370,16 @@ describe("evaluate", () => {
|
|
|
374
370
|
});
|
|
375
371
|
|
|
376
372
|
it("should evaluate const with computed array", () => {
|
|
377
|
-
const TestComponent = (props:
|
|
373
|
+
const TestComponent = (props: PropsDict): Node => ({
|
|
378
374
|
type: "Test",
|
|
379
375
|
props,
|
|
380
376
|
children: [],
|
|
381
377
|
});
|
|
382
378
|
|
|
383
|
-
const result = compileAndEvaluate(
|
|
384
|
-
|
|
385
|
-
{
|
|
386
|
-
|
|
387
|
-
components: { Test: TestComponent },
|
|
388
|
-
}
|
|
389
|
-
);
|
|
379
|
+
const result = compileAndEvaluate("const filtered = items.filter(x => x > 2);\nreturn <Test count={filtered.length}>Text</Test>", {
|
|
380
|
+
data: { items: [1, 2, 3, 4, 5] },
|
|
381
|
+
components: { Test: TestComponent },
|
|
382
|
+
});
|
|
390
383
|
|
|
391
384
|
expect(result.props.count).toBe(3);
|
|
392
385
|
});
|
|
@@ -394,25 +387,22 @@ describe("evaluate", () => {
|
|
|
394
387
|
|
|
395
388
|
describe("function declarations", () => {
|
|
396
389
|
it("should evaluate user-defined component", () => {
|
|
397
|
-
const Span = (props:
|
|
390
|
+
const Span = (props: PropsDict): Node => ({
|
|
398
391
|
type: "Span",
|
|
399
392
|
props: {},
|
|
400
|
-
children: props.children
|
|
393
|
+
children: props.children as Array<Node>,
|
|
401
394
|
});
|
|
402
395
|
|
|
403
|
-
const result = compileAndEvaluate(
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
components: { Span },
|
|
407
|
-
}
|
|
408
|
-
);
|
|
396
|
+
const result = compileAndEvaluate('function Badge({ label }) {\n return <Span>{label}</Span>\n}\nreturn <Badge label="hello" />', {
|
|
397
|
+
components: { Span },
|
|
398
|
+
});
|
|
409
399
|
|
|
410
400
|
expect(result.type).toBe("Span");
|
|
411
401
|
expect(result.children).toEqual(["hello"]);
|
|
412
402
|
});
|
|
413
403
|
|
|
414
404
|
it("should evaluate function with const inside", () => {
|
|
415
|
-
const Span = (props:
|
|
405
|
+
const Span = (props: PropsDict): Node => ({
|
|
416
406
|
type: "Span",
|
|
417
407
|
props,
|
|
418
408
|
children: [],
|
|
@@ -429,16 +419,16 @@ describe("evaluate", () => {
|
|
|
429
419
|
});
|
|
430
420
|
|
|
431
421
|
it("should evaluate nested user-defined components", () => {
|
|
432
|
-
const Span = (props:
|
|
422
|
+
const Span = (props: PropsDict): Node => ({
|
|
433
423
|
type: "Span",
|
|
434
424
|
props: {},
|
|
435
|
-
children: props.children
|
|
425
|
+
children: props.children as Array<Node>,
|
|
436
426
|
});
|
|
437
427
|
|
|
438
|
-
const Div = (props:
|
|
428
|
+
const Div = (props: PropsDict): Node => ({
|
|
439
429
|
type: "Div",
|
|
440
430
|
props: {},
|
|
441
|
-
children: props.children
|
|
431
|
+
children: props.children as Array<Node>,
|
|
442
432
|
});
|
|
443
433
|
|
|
444
434
|
const result = compileAndEvaluate(
|
|
@@ -455,16 +445,16 @@ describe("evaluate", () => {
|
|
|
455
445
|
});
|
|
456
446
|
|
|
457
447
|
it("should evaluate function with children", () => {
|
|
458
|
-
const Div = (props:
|
|
448
|
+
const Div = (props: PropsDict): Node => ({
|
|
459
449
|
type: "Div",
|
|
460
450
|
props: {},
|
|
461
|
-
children: props.children
|
|
451
|
+
children: props.children as Array<Node>,
|
|
462
452
|
});
|
|
463
453
|
|
|
464
|
-
const Span = (props:
|
|
454
|
+
const Span = (props: PropsDict): Node => ({
|
|
465
455
|
type: "Span",
|
|
466
456
|
props: {},
|
|
467
|
-
children: props.children
|
|
457
|
+
children: props.children as Array<Node>,
|
|
468
458
|
});
|
|
469
459
|
|
|
470
460
|
const result = compileAndEvaluate(
|
|
@@ -480,36 +470,30 @@ describe("evaluate", () => {
|
|
|
480
470
|
});
|
|
481
471
|
|
|
482
472
|
it("should evaluate function with default parameter values", () => {
|
|
483
|
-
const Span = (props:
|
|
473
|
+
const Span = (props: PropsDict): Node => ({
|
|
484
474
|
type: "Span",
|
|
485
475
|
props: {},
|
|
486
|
-
children: props.children
|
|
476
|
+
children: props.children as Array<Node>,
|
|
487
477
|
});
|
|
488
478
|
|
|
489
|
-
const result = compileAndEvaluate(
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
components: { Span },
|
|
493
|
-
}
|
|
494
|
-
);
|
|
479
|
+
const result = compileAndEvaluate("function Badge({ label = 'default' }) {\n return <Span>{label}</Span>\n}\nreturn <Badge />", {
|
|
480
|
+
components: { Span },
|
|
481
|
+
});
|
|
495
482
|
|
|
496
483
|
expect(result.children).toEqual(["default"]);
|
|
497
484
|
});
|
|
498
485
|
|
|
499
486
|
it("should evaluate function with data access", () => {
|
|
500
|
-
const Span = (props:
|
|
487
|
+
const Span = (props: PropsDict): Node => ({
|
|
501
488
|
type: "Span",
|
|
502
489
|
props: {},
|
|
503
|
-
children: props.children
|
|
490
|
+
children: props.children as Array<Node>,
|
|
504
491
|
});
|
|
505
492
|
|
|
506
|
-
const result = compileAndEvaluate(
|
|
507
|
-
|
|
508
|
-
{
|
|
509
|
-
|
|
510
|
-
components: { Span },
|
|
511
|
-
}
|
|
512
|
-
);
|
|
493
|
+
const result = compileAndEvaluate("function Greeting() {\n return <Span>Hello {name}</Span>\n}\nreturn <Greeting />", {
|
|
494
|
+
data: { name: "World" },
|
|
495
|
+
components: { Span },
|
|
496
|
+
});
|
|
513
497
|
|
|
514
498
|
expect(result.children).toEqual(["Hello", "World"]);
|
|
515
499
|
});
|
|
@@ -517,7 +501,7 @@ describe("evaluate", () => {
|
|
|
517
501
|
|
|
518
502
|
describe("const and function combined", () => {
|
|
519
503
|
it("should evaluate const used in function body", () => {
|
|
520
|
-
const Span = (props:
|
|
504
|
+
const Span = (props: PropsDict): Node => ({
|
|
521
505
|
type: "Span",
|
|
522
506
|
props,
|
|
523
507
|
children: [],
|
|
@@ -534,16 +518,16 @@ describe("evaluate", () => {
|
|
|
534
518
|
});
|
|
535
519
|
|
|
536
520
|
it("should evaluate function used in map with const", () => {
|
|
537
|
-
const Div = (props:
|
|
521
|
+
const Div = (props: PropsDict): Node => ({
|
|
538
522
|
type: "Div",
|
|
539
523
|
props: {},
|
|
540
|
-
children: props.children
|
|
524
|
+
children: props.children as Array<Node>,
|
|
541
525
|
});
|
|
542
526
|
|
|
543
|
-
const Span = (props:
|
|
527
|
+
const Span = (props: PropsDict): Node => ({
|
|
544
528
|
type: "Span",
|
|
545
529
|
props: {},
|
|
546
|
-
children: props.children
|
|
530
|
+
children: props.children as Array<Node>,
|
|
547
531
|
});
|
|
548
532
|
|
|
549
533
|
const result = compileAndEvaluate(
|
|
@@ -561,7 +545,7 @@ describe("evaluate", () => {
|
|
|
561
545
|
|
|
562
546
|
describe("String builtin", () => {
|
|
563
547
|
it("should evaluate String() conversion", () => {
|
|
564
|
-
const TestComponent = (props:
|
|
548
|
+
const TestComponent = (props: PropsDict): Node => ({
|
|
565
549
|
type: "Test",
|
|
566
550
|
props,
|
|
567
551
|
children: [],
|
|
@@ -575,7 +559,7 @@ describe("evaluate", () => {
|
|
|
575
559
|
});
|
|
576
560
|
|
|
577
561
|
it("should evaluate String() with boolean", () => {
|
|
578
|
-
const TestComponent = (props:
|
|
562
|
+
const TestComponent = (props: PropsDict): Node => ({
|
|
579
563
|
type: "Test",
|
|
580
564
|
props,
|
|
581
565
|
children: [],
|