jsxpression 0.1.40 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +8 -9
- 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.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 +2 -2
- 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.map +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 +0 -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 +14 -14
- package/src/analyze/analysis-error.ts +2 -6
- package/src/analyze/analysis-report.ts +73 -62
- package/src/analyze/analyze.test.ts +14 -18
- 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 +6 -19
- package/src/analyze/jsx/element-children.ts +9 -13
- 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 +39 -41
- package/src/analyze/utils.ts +25 -19
- package/src/analyze/validation-context.ts +32 -23
- package/src/builtins.ts +28 -39
- package/src/codegen/builtins/builtin-types.ts +13 -20
- package/src/codegen/generate-typescript-definitions.test.ts +29 -29
- 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 -1
- 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 +33 -45
- package/src/evaluate/evaluate.test.ts +95 -111
- package/src/evaluate/evaluate.ts +16 -21
- 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 +1 -1
- package/src/render.test.ts +4 -4
- package/src/render.ts +27 -39
- package/src/schema.ts +12 -12
- package/src/traverse.test.ts +6 -6
- package/src/traverse.ts +15 -16
- package/src/validate.ts +14 -17
- package/CHANGELOG.md +0 -8
|
@@ -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 EvaluateOptions, 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: [],
|
|
@@ -60,7 +60,7 @@ describe("evaluate", () => {
|
|
|
60
60
|
},
|
|
61
61
|
});
|
|
62
62
|
|
|
63
|
-
expect(result.props
|
|
63
|
+
expect(result.props.value).toBe(15); // floor(5 * 3.14159...) = 15
|
|
64
64
|
});
|
|
65
65
|
|
|
66
66
|
it("should throw EvaluationError for unknown component", () => {
|
|
@@ -68,15 +68,14 @@ 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
|
|
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;
|
|
80
79
|
}).toThrow();
|
|
81
80
|
return { type: "Test", props, children: [] };
|
|
82
81
|
};
|
|
@@ -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>", {
|
|
@@ -116,17 +115,17 @@ describe("evaluate", () => {
|
|
|
116
115
|
expect(result.type).toBe("Outer");
|
|
117
116
|
expect(result.props).toEqual({ x: 10 });
|
|
118
117
|
expect(result.children).toHaveLength(1);
|
|
119
|
-
const innerNode = result.children[0]
|
|
118
|
+
const innerNode = result.children[0];
|
|
120
119
|
expect(innerNode.type).toBe("Inner");
|
|
121
120
|
expect(innerNode.props).toEqual({ y: 20 });
|
|
122
121
|
expect(innerNode.children).toEqual(["Text"]);
|
|
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: [],
|
|
@@ -159,11 +158,11 @@ describe("evaluate", () => {
|
|
|
159
158
|
},
|
|
160
159
|
});
|
|
161
160
|
|
|
162
|
-
expect(result.props
|
|
161
|
+
expect(result.props.msg).toBe("visible");
|
|
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: [],
|
|
@@ -176,7 +175,7 @@ describe("evaluate", () => {
|
|
|
176
175
|
},
|
|
177
176
|
});
|
|
178
177
|
|
|
179
|
-
expect(result.props
|
|
178
|
+
expect(result.props.values).toEqual([6, 8, 10]);
|
|
180
179
|
});
|
|
181
180
|
|
|
182
181
|
it("should handle runtime errors gracefully", () => {
|
|
@@ -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: [],
|
|
@@ -208,14 +207,14 @@ describe("evaluate", () => {
|
|
|
208
207
|
},
|
|
209
208
|
});
|
|
210
209
|
|
|
211
|
-
expect(result.props
|
|
210
|
+
expect(result.props.msg).toBe("Anonymous");
|
|
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,21 +254,18 @@ 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
|
-
expect(result.props
|
|
268
|
+
expect(result.props.values).toBe("6, 8, 10");
|
|
273
269
|
});
|
|
274
270
|
|
|
275
271
|
it("should pass correct parameters to custom createElement", () => {
|
|
@@ -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: [],
|
|
@@ -295,11 +291,11 @@ describe("evaluate", () => {
|
|
|
295
291
|
components: { Test: TestComponent },
|
|
296
292
|
});
|
|
297
293
|
|
|
298
|
-
expect(result.props
|
|
294
|
+
expect(result.props.msg).toBe("Hello World");
|
|
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: [],
|
|
@@ -310,14 +306,14 @@ describe("evaluate", () => {
|
|
|
310
306
|
components: { Test: TestComponent },
|
|
311
307
|
});
|
|
312
308
|
|
|
313
|
-
expect(result.props
|
|
309
|
+
expect(result.props.value).toBe(11); // 5 + (3 * 2)
|
|
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: [],
|
|
@@ -340,11 +336,11 @@ describe("evaluate", () => {
|
|
|
340
336
|
components: { Test: TestComponent },
|
|
341
337
|
});
|
|
342
338
|
|
|
343
|
-
expect(result.props
|
|
339
|
+
expect(result.props.value).toBe(42);
|
|
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: [],
|
|
@@ -355,11 +351,11 @@ describe("evaluate", () => {
|
|
|
355
351
|
components: { Test: TestComponent },
|
|
356
352
|
});
|
|
357
353
|
|
|
358
|
-
expect(result.props
|
|
354
|
+
expect(result.props.value).toBe(8); // (3+1)*2
|
|
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: [],
|
|
@@ -370,49 +366,43 @@ describe("evaluate", () => {
|
|
|
370
366
|
components: { Test: TestComponent },
|
|
371
367
|
});
|
|
372
368
|
|
|
373
|
-
expect(result.props
|
|
369
|
+
expect(result.props.msg).toBe("Hello World");
|
|
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
|
-
expect(result.props
|
|
384
|
+
expect(result.props.count).toBe(3);
|
|
392
385
|
});
|
|
393
386
|
});
|
|
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: [],
|
|
@@ -425,20 +415,20 @@ describe("evaluate", () => {
|
|
|
425
415
|
}
|
|
426
416
|
);
|
|
427
417
|
|
|
428
|
-
expect(result.props
|
|
418
|
+
expect(result.props.result).toBe(10);
|
|
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(
|
|
@@ -450,21 +440,21 @@ describe("evaluate", () => {
|
|
|
450
440
|
|
|
451
441
|
expect(result.type).toBe("Div");
|
|
452
442
|
expect(result.children).toHaveLength(1);
|
|
453
|
-
expect(
|
|
454
|
-
expect(
|
|
443
|
+
expect(result.children[0].type).toBe("Span");
|
|
444
|
+
expect(result.children[0].children).toEqual(["hi"]);
|
|
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(
|
|
@@ -476,40 +466,34 @@ describe("evaluate", () => {
|
|
|
476
466
|
|
|
477
467
|
expect(result.type).toBe("Div");
|
|
478
468
|
expect(result.children).toHaveLength(1);
|
|
479
|
-
expect(
|
|
469
|
+
expect(result.children[0].type).toBe("Span");
|
|
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: [],
|
|
@@ -530,20 +514,20 @@ describe("evaluate", () => {
|
|
|
530
514
|
}
|
|
531
515
|
);
|
|
532
516
|
|
|
533
|
-
expect(result.props
|
|
517
|
+
expect(result.props.result).toBe(12);
|
|
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: [],
|
|
@@ -571,11 +555,11 @@ describe("evaluate", () => {
|
|
|
571
555
|
components: { Test: TestComponent },
|
|
572
556
|
});
|
|
573
557
|
|
|
574
|
-
expect(result.props
|
|
558
|
+
expect(result.props.value).toBe("42");
|
|
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: [],
|
|
@@ -586,7 +570,7 @@ describe("evaluate", () => {
|
|
|
586
570
|
components: { Test: TestComponent },
|
|
587
571
|
});
|
|
588
572
|
|
|
589
|
-
expect(result.props
|
|
573
|
+
expect(result.props.value).toBe("true");
|
|
590
574
|
});
|
|
591
575
|
});
|
|
592
576
|
});
|
package/src/evaluate/evaluate.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { EvaluationError } from "./evaluation-error.js";
|
|
|
3
3
|
|
|
4
4
|
export type ComponentDict = Record<string, Component>;
|
|
5
5
|
|
|
6
|
-
export type CreateElement<T =
|
|
6
|
+
export type CreateElement<T = unknown> = (type: Component, props?: PropsDict, ...children: Array<unknown>) => T;
|
|
7
7
|
|
|
8
8
|
export type PropsDict = Record<string, unknown>;
|
|
9
9
|
|
|
@@ -12,19 +12,19 @@ export type DataDict = Record<string, unknown>;
|
|
|
12
12
|
export type Node = {
|
|
13
13
|
type: string;
|
|
14
14
|
props: PropsDict;
|
|
15
|
-
children: Node
|
|
15
|
+
children: Array<Node>;
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
-
export type H = (type: string | Function, props?: PropsDict, ...children: Node
|
|
18
|
+
export type H = (type: string | Function, props?: PropsDict, ...children: Array<Node>) => Node;
|
|
19
19
|
|
|
20
|
-
export type Component = (...args:
|
|
20
|
+
export type Component = (...args: Array<never>) => unknown;
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Configuration options for evaluating JSX expressions.
|
|
24
24
|
*
|
|
25
25
|
* @template T - The return type of createElement function
|
|
26
26
|
*/
|
|
27
|
-
export interface EvaluateOptions<T =
|
|
27
|
+
export interface EvaluateOptions<T = unknown> {
|
|
28
28
|
/** Data object whose keys become top-level variables in JSX expressions */
|
|
29
29
|
data?: DataDict;
|
|
30
30
|
/** Map of component names to component functions used in JSX */
|
|
@@ -38,23 +38,18 @@ export interface EvaluateOptions<T = any> {
|
|
|
38
38
|
const RESERVED_PARAMS = new Set(["h", "Math"]);
|
|
39
39
|
const VALID_IDENTIFIER = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
40
40
|
|
|
41
|
-
export function evaluate<T =
|
|
41
|
+
export function evaluate<T = unknown>(source: string, schema: Schema, options: EvaluateOptions<T>): T {
|
|
42
42
|
const { data = {}, components = {}, functions = {}, createElement } = options;
|
|
43
43
|
|
|
44
44
|
const dataKeys = Object.keys(data);
|
|
45
45
|
const functionKeys = Object.keys(functions);
|
|
46
46
|
validateParamKeys(dataKeys, functionKeys);
|
|
47
47
|
|
|
48
|
-
const h = createH(components, createElement
|
|
48
|
+
const h = createH(components, createElement ?? defaultCreateElement, schema);
|
|
49
49
|
const frozen = deepFreezeData(structuredClone(data));
|
|
50
50
|
try {
|
|
51
|
-
//
|
|
52
|
-
return new Function("h", "Math", ...dataKeys, ...functionKeys, source)(
|
|
53
|
-
h,
|
|
54
|
-
Math,
|
|
55
|
-
...Object.values(frozen),
|
|
56
|
-
...Object.values(functions)
|
|
57
|
-
) as T;
|
|
51
|
+
// oxlint-disable-next-line typescript/no-implied-eval, no-new-func
|
|
52
|
+
return new Function("h", "Math", ...dataKeys, ...functionKeys, source)(h, Math, ...Object.values(frozen), ...Object.values(functions)) as T;
|
|
58
53
|
} catch (error: unknown) {
|
|
59
54
|
// We may have thrown an EvaluationError from createH()
|
|
60
55
|
if (error instanceof EvaluationError) {
|
|
@@ -65,7 +60,7 @@ export function evaluate<T = any>(source: string, schema: Schema, options: Evalu
|
|
|
65
60
|
}
|
|
66
61
|
}
|
|
67
62
|
|
|
68
|
-
function validateParamKeys(dataKeys: string
|
|
63
|
+
function validateParamKeys(dataKeys: Array<string>, functionKeys: Array<string>): void {
|
|
69
64
|
const seen = new Set<string>(RESERVED_PARAMS);
|
|
70
65
|
for (const key of [...dataKeys, ...functionKeys]) {
|
|
71
66
|
if (!VALID_IDENTIFIER.test(key)) {
|
|
@@ -81,22 +76,22 @@ function validateParamKeys(dataKeys: string[], functionKeys: string[]): void {
|
|
|
81
76
|
function createH(components: ComponentDict, createElement: CreateElement, schema: Schema): H {
|
|
82
77
|
return function h(type, props, ...children) {
|
|
83
78
|
if (typeof type === "function") {
|
|
84
|
-
return createElement(type as Component, props, ...children.flat().filter(Boolean));
|
|
79
|
+
return createElement(type as Component, props, ...children.flat().filter(Boolean)) as Node;
|
|
85
80
|
}
|
|
86
81
|
|
|
87
82
|
const Component = components[type];
|
|
88
83
|
|
|
89
84
|
if (!Component) {
|
|
90
85
|
if (isElementAllowed(schema, type)) {
|
|
91
|
-
const defaultComponent = (props: PropsDict):
|
|
92
|
-
return createElement(defaultComponent, props, ...children.flat().filter(Boolean));
|
|
86
|
+
const defaultComponent = (props: PropsDict): unknown => (canHaveChildren(schema, type) ? props["children"] : null);
|
|
87
|
+
return createElement(defaultComponent, props, ...children.flat().filter(Boolean)) as Node;
|
|
93
88
|
}
|
|
94
89
|
|
|
95
90
|
// Component not in schema - throw error
|
|
96
91
|
throw new EvaluationError(`component "${type}" is not allowed`);
|
|
97
92
|
}
|
|
98
93
|
|
|
99
|
-
return createElement(Component, props, ...children.flat().filter(Boolean));
|
|
94
|
+
return createElement(Component, props, ...children.flat().filter(Boolean)) as Node;
|
|
100
95
|
};
|
|
101
96
|
}
|
|
102
97
|
|
|
@@ -110,6 +105,6 @@ function deepFreezeData(obj: Record<string, unknown>): Record<string, unknown> {
|
|
|
110
105
|
return obj;
|
|
111
106
|
}
|
|
112
107
|
|
|
113
|
-
function defaultCreateElement(Component: Component, props: PropsDict = {}, ...children:
|
|
114
|
-
return Component({ ...props, children });
|
|
108
|
+
function defaultCreateElement(Component: Component, props: PropsDict = {}, ...children: Array<unknown>): Node {
|
|
109
|
+
return (Component as (props: PropsDict & { children: Array<unknown> }) => Node)({ ...props, children });
|
|
115
110
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
export {
|
|
2
|
-
type Schema,
|
|
3
|
-
type PropertySchema,
|
|
4
|
-
type FunctionSchema,
|
|
5
|
-
serializePropertySchemaToJson,
|
|
6
|
-
deserializePropertySchemaFromJson
|
|
7
|
-
} from "./schema.js";
|
|
1
|
+
export { type Schema, type PropertySchema, type FunctionSchema, serializePropertySchemaToJson, deserializePropertySchemaFromJson } from "./schema.js";
|
|
8
2
|
|
|
9
3
|
export { ParseError } from "./parse/index.js";
|
|
10
4
|
export { CompilationError } from "./compile/index.js";
|