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
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
51
|
// oxlint-disable-next-line typescript/no-implied-eval, no-new-func
|
|
52
|
-
return new Function("h", "Math", ...dataKeys, ...functionKeys, source)(
|
|
53
|
-
h,
|
|
54
|
-
Math,
|
|
55
|
-
...Object.values(frozen),
|
|
56
|
-
...Object.values(functions)
|
|
57
|
-
) as T;
|
|
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";
|
package/src/jsx.ts
CHANGED
|
@@ -19,9 +19,7 @@ export type JSXLiteral = {
|
|
|
19
19
|
|
|
20
20
|
export type JSXAttributeValue = JSXLiteral | JSXExpressionContainer;
|
|
21
21
|
|
|
22
|
-
export type JSXNameNode =
|
|
23
|
-
| { type: "JSXIdentifier"; name: string }
|
|
24
|
-
| { type: "JSXMemberExpression"; object: JSXNameNode; property: JSXNameNode };
|
|
22
|
+
export type JSXNameNode = { type: "JSXIdentifier"; name: string } | { type: "JSXMemberExpression"; object: JSXNameNode; property: JSXNameNode };
|
|
25
23
|
|
|
26
24
|
export type JSXAttribute = {
|
|
27
25
|
type: "JSXAttribute";
|
|
@@ -41,7 +39,7 @@ export type JSXOpeningElement = {
|
|
|
41
39
|
name: {
|
|
42
40
|
name: string;
|
|
43
41
|
};
|
|
44
|
-
attributes:
|
|
42
|
+
attributes: Array<JSXAttribute | JSXSpreadAttribute>;
|
|
45
43
|
};
|
|
46
44
|
|
|
47
45
|
export type JSXClosingElement = {
|
|
@@ -53,12 +51,12 @@ export type JSXElement = {
|
|
|
53
51
|
type: "JSXElement";
|
|
54
52
|
openingElement: JSXOpeningElement;
|
|
55
53
|
closingElement?: JSXClosingElement;
|
|
56
|
-
children: JSXNode
|
|
54
|
+
children: Array<JSXNode>;
|
|
57
55
|
};
|
|
58
56
|
|
|
59
57
|
export type JSXFragment = {
|
|
60
58
|
type: "JSXFragment";
|
|
61
|
-
children: JSXNode
|
|
59
|
+
children: Array<JSXNode>;
|
|
62
60
|
};
|
|
63
61
|
|
|
64
62
|
export type JSXText = {
|
package/src/parse/parse-error.ts
CHANGED
package/src/parse/parse.test.ts
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { parse } from "./parse.js";
|
|
3
2
|
import { ParseError } from "./parse-error.js";
|
|
3
|
+
import { parse } from "./parse.js";
|
|
4
|
+
|
|
5
|
+
function getThrownError(fn: () => unknown): unknown {
|
|
6
|
+
try {
|
|
7
|
+
fn();
|
|
8
|
+
} catch (error) {
|
|
9
|
+
return error;
|
|
10
|
+
}
|
|
11
|
+
throw new Error("Expected function to throw");
|
|
12
|
+
}
|
|
4
13
|
|
|
5
14
|
describe("parse", () => {
|
|
6
15
|
it("should parse valid JSX element", () => {
|
|
@@ -20,12 +29,6 @@ describe("parse", () => {
|
|
|
20
29
|
|
|
21
30
|
it("should throw ParseError with position for syntax error", () => {
|
|
22
31
|
expect(() => parse("<Text>Unclosed")).toThrow(ParseError);
|
|
23
|
-
|
|
24
|
-
try {
|
|
25
|
-
parse("<Text>Unclosed");
|
|
26
|
-
} catch (error) {
|
|
27
|
-
expect(error).toBeInstanceOf(ParseError);
|
|
28
|
-
}
|
|
29
32
|
});
|
|
30
33
|
|
|
31
34
|
it("should parse nested JSX", () => {
|
|
@@ -78,17 +81,12 @@ describe("parse", () => {
|
|
|
78
81
|
});
|
|
79
82
|
|
|
80
83
|
it("should preserve location information in errors", () => {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
expect(error.loc).toBeDefined();
|
|
88
|
-
expect(error.loc.line).toBe(1);
|
|
89
|
-
expect(error.loc.column).toBe(6);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
84
|
+
const error = getThrownError(() => parse("<Text>Unclosed"));
|
|
85
|
+
|
|
86
|
+
expect(error).toBeInstanceOf(ParseError);
|
|
87
|
+
expect((error as ParseError).loc).toBeDefined();
|
|
88
|
+
expect((error as ParseError).loc?.line).toBe(1);
|
|
89
|
+
expect((error as ParseError).loc?.column).toBe(6);
|
|
92
90
|
});
|
|
93
91
|
|
|
94
92
|
it("should handle invalid JSX syntax", () => {
|
package/src/render.test.ts
CHANGED
|
@@ -7,12 +7,12 @@ import { render } from "./render.js";
|
|
|
7
7
|
type TestNode = {
|
|
8
8
|
type: string;
|
|
9
9
|
props: Record<string, unknown>;
|
|
10
|
-
children: unknown
|
|
10
|
+
children: Array<unknown>;
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
describe("render", () => {
|
|
14
14
|
it("should render complete JSX pipeline successfully", () => {
|
|
15
|
-
const TestComponent = ({ children, ...props }: { x: number; y: number; children?:
|
|
15
|
+
const TestComponent = ({ children, ...props }: { x: number; y: number; children?: Array<unknown> }): TestNode => ({
|
|
16
16
|
type: "TestComponent",
|
|
17
17
|
props,
|
|
18
18
|
children: children ?? [],
|
|
@@ -76,13 +76,13 @@ describe("render", () => {
|
|
|
76
76
|
});
|
|
77
77
|
|
|
78
78
|
it("should render nested JSX elements", () => {
|
|
79
|
-
const OuterComponent = ({ children, ...props }: { x: number; children?:
|
|
79
|
+
const OuterComponent = ({ children, ...props }: { x: number; children?: Array<unknown> }): TestNode => ({
|
|
80
80
|
type: "OuterComponent",
|
|
81
81
|
props,
|
|
82
82
|
children: children ?? [],
|
|
83
83
|
});
|
|
84
84
|
|
|
85
|
-
const InnerComponent = ({ children, ...props }: { y: number; children?:
|
|
85
|
+
const InnerComponent = ({ children, ...props }: { y: number; children?: Array<unknown> }): TestNode => ({
|
|
86
86
|
type: "InnerComponent",
|
|
87
87
|
props,
|
|
88
88
|
children: children ?? [],
|
package/src/render.ts
CHANGED
|
@@ -1,30 +1,45 @@
|
|
|
1
|
+
import { analyze, AnalysisError } from "./analyze/index.js";
|
|
1
2
|
import { compile as compileInternal } from "./compile/index.js";
|
|
2
3
|
import type { EvaluateOptions } from "./evaluate/evaluate.js";
|
|
3
4
|
import { evaluate } from "./evaluate/evaluate.js";
|
|
4
5
|
import { parse } from "./parse/index.js";
|
|
5
6
|
import type { Schema } from "./schema.js";
|
|
6
|
-
import { analyze, AnalysisError } from "./analyze/index.js";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Configuration options for rendering JSX expressions.
|
|
10
10
|
*
|
|
11
|
-
* Extends EvaluateOptions with render-specific functionality, including control over
|
|
12
|
-
* the minimum severity threshold that stops rendering.
|
|
11
|
+
* Extends EvaluateOptions with render-specific functionality, including control over the minimum severity threshold that stops rendering.
|
|
13
12
|
*
|
|
14
13
|
* @template T - The return type of createElement function
|
|
15
14
|
*/
|
|
16
|
-
export interface RenderOptions<T =
|
|
17
|
-
/**
|
|
18
|
-
* Minimum severity level that counts as a failure: 1=info, 2=warning, 3=error.
|
|
19
|
-
*/
|
|
15
|
+
export interface RenderOptions<T = unknown> extends EvaluateOptions<T> {
|
|
16
|
+
/** Minimum severity level that counts as a failure: 1=info, 2=warning, 3=error. */
|
|
20
17
|
minSeverity?: 1 | 2 | 3;
|
|
21
18
|
}
|
|
22
19
|
|
|
23
20
|
/**
|
|
24
21
|
* Renders a JSX expression with data validation and security constraints.
|
|
25
22
|
*
|
|
26
|
-
* Parses JSX source code, validates it against a schema for security and type safety,
|
|
27
|
-
*
|
|
23
|
+
* Parses JSX source code, validates it against a schema for security and type safety, compiles it to executable code, and evaluates it with the
|
|
24
|
+
* provided data and components.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* const schema: Schema = {
|
|
29
|
+
* data: {
|
|
30
|
+
* user: { type: "object", shape: { name: { type: "string" } } },
|
|
31
|
+
* },
|
|
32
|
+
* elements: {
|
|
33
|
+
* Text: { props: { children: { type: "string" } } },
|
|
34
|
+
* },
|
|
35
|
+
* };
|
|
36
|
+
*
|
|
37
|
+
* const result = render("<Text>Hello {user.name}!</Text>", schema, {
|
|
38
|
+
* data: { user: { name: "World" } },
|
|
39
|
+
* components: { Text: ({ children }) => children },
|
|
40
|
+
* createElement: (type, props, ...children) => ({ type, props, children }),
|
|
41
|
+
* });
|
|
42
|
+
* ```;
|
|
28
43
|
*
|
|
29
44
|
* @template T - The return type of the rendered JSX (e.g., ReactElement, VNode, string)
|
|
30
45
|
* @param source - JSX expression source code to render (e.g., `"<Text x={user.name}>Hello</Text>"`)
|
|
@@ -35,36 +50,9 @@ export interface RenderOptions<T = any> extends EvaluateOptions<T> {
|
|
|
35
50
|
* @param options.createElement - Function to create elements (e.g., React.createElement)
|
|
36
51
|
* @param options.minSeverity - Minimum severity (1=info, 2=warning, 3=error) required to abort rendering; defaults to `3`
|
|
37
52
|
* @returns The rendered JSX element of type T
|
|
38
|
-
*
|
|
39
53
|
* @throws {AnalysisError} When JSX contains security violations, invalid data access, or schema mismatches
|
|
40
|
-
*
|
|
41
|
-
* @example
|
|
42
|
-
* ```typescript
|
|
43
|
-
* const schema: Schema = {
|
|
44
|
-
* data: {
|
|
45
|
-
* user: { type: "object", shape: { name: { type: "string" } } }
|
|
46
|
-
* },
|
|
47
|
-
* elements: {
|
|
48
|
-
* Text: { props: { children: { type: "string" } } }
|
|
49
|
-
* }
|
|
50
|
-
* };
|
|
51
|
-
*
|
|
52
|
-
* const result = render(
|
|
53
|
-
* "<Text>Hello {user.name}!</Text>",
|
|
54
|
-
* schema,
|
|
55
|
-
* {
|
|
56
|
-
* data: { user: { name: "World" } },
|
|
57
|
-
* components: { Text: ({ children }) => children },
|
|
58
|
-
* createElement: (type, props, ...children) => ({ type, props, children })
|
|
59
|
-
* }
|
|
60
|
-
* );
|
|
61
|
-
* ```
|
|
62
54
|
*/
|
|
63
|
-
export function render<T =
|
|
64
|
-
source: string,
|
|
65
|
-
schema: Schema,
|
|
66
|
-
{ minSeverity = 3, ...options }: RenderOptions<T> = {}
|
|
67
|
-
): T {
|
|
55
|
+
export function render<T = unknown>(source: string, schema: Schema, { minSeverity = 3, ...options }: RenderOptions<T> = {}): T {
|
|
68
56
|
const ast = parse(source);
|
|
69
57
|
const report = analyze(ast, schema);
|
|
70
58
|
|
|
@@ -75,7 +63,7 @@ export function render<T = any>(
|
|
|
75
63
|
return evaluate(compileInternal(ast), schema, options);
|
|
76
64
|
}
|
|
77
65
|
|
|
78
|
-
export function compile<T =
|
|
66
|
+
export function compile<T = unknown>(source: string, schema: Schema, { minSeverity = 3 }: RenderOptions<T> = {}): string {
|
|
79
67
|
const ast = parse(source);
|
|
80
68
|
const report = analyze(ast, schema);
|
|
81
69
|
if (report.hasIssues(minSeverity)) {
|
package/src/schema.ts
CHANGED
|
@@ -6,7 +6,7 @@ export type StringPropertySchema = {
|
|
|
6
6
|
description?: string;
|
|
7
7
|
required?: boolean;
|
|
8
8
|
default?: string;
|
|
9
|
-
enum?:
|
|
9
|
+
enum?: ReadonlyArray<string>;
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
export type NumberPropertySchema = {
|
|
@@ -14,7 +14,7 @@ export type NumberPropertySchema = {
|
|
|
14
14
|
description?: string;
|
|
15
15
|
required?: boolean;
|
|
16
16
|
default?: number;
|
|
17
|
-
enum?:
|
|
17
|
+
enum?: ReadonlyArray<number>;
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
export type BooleanPropertySchema = {
|
|
@@ -22,7 +22,7 @@ export type BooleanPropertySchema = {
|
|
|
22
22
|
description?: string;
|
|
23
23
|
required?: boolean;
|
|
24
24
|
default?: boolean;
|
|
25
|
-
enum?:
|
|
25
|
+
enum?: ReadonlyArray<boolean>;
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
export type FunctionPropertySchema = {
|
|
@@ -72,7 +72,7 @@ export type PropertySchema =
|
|
|
72
72
|
export type ElementSchema = {
|
|
73
73
|
description?: string;
|
|
74
74
|
props?: Record<string, PropertySchema>;
|
|
75
|
-
allowedChildren?: string
|
|
75
|
+
allowedChildren?: Array<string>;
|
|
76
76
|
};
|
|
77
77
|
|
|
78
78
|
export type ArgumentSchema = {
|
|
@@ -175,7 +175,7 @@ export function getElementSchema(schema: Schema, tagName: string): ElementSchema
|
|
|
175
175
|
return schema.elements?.[tagName];
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
export function getAllowedAttributes(schema: Schema, tagName: string): string
|
|
178
|
+
export function getAllowedAttributes(schema: Schema, tagName: string): Array<string> {
|
|
179
179
|
const elementConfig = getElementSchema(schema, tagName);
|
|
180
180
|
return elementConfig?.props ? Object.keys(elementConfig.props) : [];
|
|
181
181
|
}
|
|
@@ -185,7 +185,7 @@ export function isAttributeAllowed(schema: Schema, tagName: string, attributeNam
|
|
|
185
185
|
return allowedAttributes.includes(attributeName);
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
-
export function getAllowedChildren(schema: Schema, tagName: string): string
|
|
188
|
+
export function getAllowedChildren(schema: Schema, tagName: string): Array<string> | undefined {
|
|
189
189
|
const elementConfig = getElementSchema(schema, tagName);
|
|
190
190
|
return elementConfig?.allowedChildren;
|
|
191
191
|
}
|
|
@@ -197,7 +197,7 @@ export function canHaveChildren(schema: Schema, tagName: string): boolean {
|
|
|
197
197
|
|
|
198
198
|
export function isSelfClosing(schema: Schema, tagName: string): boolean {
|
|
199
199
|
const allowedChildren = getAllowedChildren(schema, tagName);
|
|
200
|
-
return allowedChildren
|
|
200
|
+
return allowedChildren?.length === 0;
|
|
201
201
|
}
|
|
202
202
|
|
|
203
203
|
export function isChildAllowed(schema: Schema, parentTag: string, childTag: string): boolean {
|
|
@@ -206,7 +206,7 @@ export function isChildAllowed(schema: Schema, parentTag: string, childTag: stri
|
|
|
206
206
|
}
|
|
207
207
|
|
|
208
208
|
// New schema utilities for enhanced validation
|
|
209
|
-
export function getAllElements(schema: Schema): string
|
|
209
|
+
export function getAllElements(schema: Schema): Array<string> {
|
|
210
210
|
return schema.elements ? Object.keys(schema.elements) : [];
|
|
211
211
|
}
|
|
212
212
|
|
|
@@ -215,13 +215,13 @@ export function getAttributeSchema(schema: Schema, tagName: string, attributeNam
|
|
|
215
215
|
return elementSchema?.props?.[attributeName];
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
-
export function getRequiredAttributes(schema: Schema, tagName: string): string
|
|
218
|
+
export function getRequiredAttributes(schema: Schema, tagName: string): Array<string> {
|
|
219
219
|
const elementSchema = getElementSchema(schema, tagName);
|
|
220
220
|
if (!elementSchema?.props) {
|
|
221
221
|
return [];
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
-
const required: string
|
|
224
|
+
const required: Array<string> = [];
|
|
225
225
|
for (const [attrName, attrSchema] of Object.entries(elementSchema.props)) {
|
|
226
226
|
if (attrSchema.required === true) {
|
|
227
227
|
required.push(attrName);
|
|
@@ -233,7 +233,7 @@ export function getRequiredAttributes(schema: Schema, tagName: string): string[]
|
|
|
233
233
|
|
|
234
234
|
type PropertyEnumValue = string | number | boolean;
|
|
235
235
|
|
|
236
|
-
export function getEnumValues(property: PropertySchema):
|
|
236
|
+
export function getEnumValues(property: PropertySchema): ReadonlyArray<PropertyEnumValue> | undefined {
|
|
237
237
|
switch (property.type) {
|
|
238
238
|
case "string":
|
|
239
239
|
return property.enum;
|
package/src/traverse.test.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { traverse } from "./traverse.js";
|
|
3
2
|
import { parse } from "./parse/index.js";
|
|
3
|
+
import { traverse } from "./traverse.js";
|
|
4
4
|
|
|
5
5
|
describe("traverse", () => {
|
|
6
6
|
it("should call JSXElement visitor", () => {
|
|
7
7
|
const ast = parse("<Text>Hello</Text>");
|
|
8
|
-
const visits: string
|
|
8
|
+
const visits: Array<string> = [];
|
|
9
9
|
|
|
10
10
|
traverse(ast, {
|
|
11
11
|
JSXElement() {
|
|
@@ -18,7 +18,7 @@ describe("traverse", () => {
|
|
|
18
18
|
|
|
19
19
|
it("should call JSXFragment visitor", () => {
|
|
20
20
|
const ast = parse("<>Fragment</>");
|
|
21
|
-
const visits: string
|
|
21
|
+
const visits: Array<string> = [];
|
|
22
22
|
|
|
23
23
|
traverse(ast, {
|
|
24
24
|
JSXFragment() {
|
|
@@ -31,7 +31,7 @@ describe("traverse", () => {
|
|
|
31
31
|
|
|
32
32
|
it("should call Identifier visitor for expressions", () => {
|
|
33
33
|
const ast = parse("<Text>{props.value}</Text>");
|
|
34
|
-
const identifiers: string
|
|
34
|
+
const identifiers: Array<string> = [];
|
|
35
35
|
|
|
36
36
|
traverse(ast, {
|
|
37
37
|
Identifier(node) {
|
|
@@ -57,7 +57,7 @@ describe("traverse", () => {
|
|
|
57
57
|
|
|
58
58
|
it("should call JSXExpressionContainer visitor", () => {
|
|
59
59
|
const ast = parse("<Text>{props.value}</Text>");
|
|
60
|
-
const visits: string
|
|
60
|
+
const visits: Array<string> = [];
|
|
61
61
|
|
|
62
62
|
traverse(ast, {
|
|
63
63
|
JSXExpressionContainer() {
|
|
@@ -83,7 +83,7 @@ describe("traverse", () => {
|
|
|
83
83
|
|
|
84
84
|
it("should handle multiple visitor types simultaneously", () => {
|
|
85
85
|
const ast = parse("<Text x={props.value}>Hello</Text>");
|
|
86
|
-
const visits: string
|
|
86
|
+
const visits: Array<string> = [];
|
|
87
87
|
|
|
88
88
|
traverse(ast, {
|
|
89
89
|
JSXElement() {
|
package/src/traverse.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Program } from "acorn";
|
|
2
|
-
import { base, type RecursiveVisitors, type SimpleVisitors, simple } from "acorn-walk";
|
|
1
|
+
import type { Node, Program } from "acorn";
|
|
2
|
+
import { base, type RecursiveVisitors, type SimpleVisitors, simple, type WalkerCallback } from "acorn-walk";
|
|
3
3
|
import {
|
|
4
4
|
isJsxEmptyExpression,
|
|
5
5
|
isJsxText,
|
|
@@ -101,7 +101,7 @@ const tsNodeTypes = [
|
|
|
101
101
|
"TSThisType",
|
|
102
102
|
] as const;
|
|
103
103
|
|
|
104
|
-
const tsWalkers: Record<string, (node:
|
|
104
|
+
const tsWalkers: Record<string, (node: Node, state: unknown, callback: WalkerCallback<unknown>) => void> = {};
|
|
105
105
|
for (const nodeType of tsNodeTypes) {
|
|
106
106
|
tsWalkers[nodeType] = () => {};
|
|
107
107
|
}
|
package/src/validate.ts
CHANGED
|
@@ -5,34 +5,31 @@ import type { Schema } from "./schema.js";
|
|
|
5
5
|
export type ValidateResult = { ok: true } | { ok: false; error: AnalysisError | ParseError };
|
|
6
6
|
|
|
7
7
|
export interface ValidateOptions {
|
|
8
|
-
/**
|
|
9
|
-
* Minimum severity level that counts as a failure: 1=info, 2=warning, 3=error.
|
|
10
|
-
*/
|
|
8
|
+
/** Minimum severity level that counts as a failure: 1=info, 2=warning, 3=error. */
|
|
11
9
|
minSeverity?: 1 | 2 | 3;
|
|
12
10
|
}
|
|
13
11
|
/**
|
|
14
12
|
* Validates JSX expression syntax and schema compliance without execution.
|
|
15
13
|
*
|
|
16
|
-
* Performs static analysis to check if JSX source code is valid according to the schema.
|
|
17
|
-
*
|
|
18
|
-
* and data access pattern validation - all without needing actual data or components.
|
|
14
|
+
* Performs static analysis to check if JSX source code is valid according to the schema. This includes syntax validation, security constraints, JSX
|
|
15
|
+
* element/attribute validation, and data access pattern validation - all without needing actual data or components.
|
|
19
16
|
*
|
|
20
17
|
* This function parses the source code and returns a simple ok/error result.
|
|
21
18
|
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* const result = validate("<Text>{user.name}</Text>", schema);
|
|
22
|
+
* if (result.ok) {
|
|
23
|
+
* console.log("JSX is valid!");
|
|
24
|
+
* } else {
|
|
25
|
+
* console.error("Validation failed with", result.report.errors.length, "errors");
|
|
26
|
+
* result.report.errors.forEach((err) => console.error(err.message));
|
|
27
|
+
* }
|
|
28
|
+
* ```;
|
|
29
|
+
*
|
|
22
30
|
* @param source - JSX expression source code to validate
|
|
23
31
|
* @param schema - Schema defining allowed data properties, JSX elements, and hierarchy rules
|
|
24
32
|
* @returns Validation result indicating success or failure with parse or analysis error
|
|
25
|
-
*
|
|
26
|
-
* @example
|
|
27
|
-
* ```typescript
|
|
28
|
-
* const result = validate("<Text>{user.name}</Text>", schema);
|
|
29
|
-
* if (result.ok) {
|
|
30
|
-
* console.log("JSX is valid!");
|
|
31
|
-
* } else {
|
|
32
|
-
* console.error("Validation failed with", result.report.errors.length, "errors");
|
|
33
|
-
* result.report.errors.forEach(err => console.error(err.message));
|
|
34
|
-
* }
|
|
35
|
-
* ```
|
|
36
33
|
*/
|
|
37
34
|
export function validate(
|
|
38
35
|
source: string,
|
package/CHANGELOG.md
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
# Change Log
|
|
2
|
-
|
|
3
|
-
All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/).
|
|
4
|
-
|
|
5
|
-
## [0.1.39] - 2026-09-05
|
|
6
|
-
|
|
7
|
-
- Wired the package's test suite into the shared workspace test runner.
|
|
8
|
-
- Internal: adopt `verbatimModuleSyntax` and stricter compiler/lint settings. No other functional changes.
|