jsxpression 0.1.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/LICENSE +21 -0
- package/README.md +368 -0
- package/lib/analyze/analysis-error.d.ts.map +1 -0
- package/lib/analyze/analysis-report.d.ts +33 -0
- package/lib/analyze/analysis-report.d.ts.map +1 -0
- package/lib/analyze/analysis-report.js +194 -0
- package/lib/analyze/analysis-report.js.map +1 -0
- package/lib/analyze/analyze.d.ts.map +1 -0
- package/lib/analyze/analyze.test.d.ts.map +1 -0
- package/lib/analyze/analyze.test.js +765 -0
- package/lib/analyze/analyze.test.js.map +1 -0
- package/lib/analyze/data/analyze.d.ts.map +1 -0
- package/lib/analyze/data/data-access.d.ts.map +1 -0
- package/lib/analyze/data/index.d.ts.map +1 -0
- package/lib/analyze/data/method-calls.d.ts.map +1 -0
- package/lib/analyze/data/utils.d.ts.map +1 -0
- package/lib/analyze/index.d.ts.map +1 -0
- package/lib/analyze/jsx/analyze.d.ts.map +1 -0
- package/lib/analyze/jsx/element-attributes.d.ts.map +1 -0
- package/lib/analyze/jsx/element-attributes.js +89 -0
- package/lib/analyze/jsx/element-attributes.js.map +1 -0
- package/lib/analyze/jsx/element-children.d.ts.map +1 -0
- package/lib/analyze/jsx/element-tags.d.ts.map +1 -0
- package/lib/analyze/jsx/index.d.ts.map +1 -0
- package/lib/analyze/security/analyze.d.ts.map +1 -0
- package/lib/analyze/security/call-expressions.d.ts.map +1 -0
- package/lib/analyze/security/declarations.d.ts.map +1 -0
- package/lib/analyze/security/expressions.d.ts.map +1 -0
- package/lib/analyze/security/identifiers.d.ts.map +1 -0
- package/lib/analyze/security/index.d.ts.map +1 -0
- package/lib/analyze/security/member-access.d.ts.map +1 -0
- package/lib/builtins.d.ts +50 -0
- package/lib/builtins.d.ts.map +1 -0
- package/lib/builtins.js +1065 -0
- package/lib/builtins.js.map +1 -0
- package/lib/codegen/builtin-types.d.ts +11 -0
- package/lib/codegen/builtin-types.d.ts.map +1 -0
- package/lib/codegen/builtin-types.js +151 -0
- package/lib/codegen/builtin-types.js.map +1 -0
- package/lib/codegen/builtins/builtin-types.d.ts +2 -0
- package/lib/codegen/builtins/builtin-types.d.ts.map +1 -0
- package/lib/codegen/builtins/builtin-types.js +143 -0
- package/lib/codegen/builtins/builtin-types.js.map +1 -0
- package/lib/codegen/builtins/index.d.ts +2 -0
- package/lib/codegen/builtins/index.d.ts.map +1 -0
- package/lib/codegen/builtins/index.js +6 -0
- package/lib/codegen/builtins/index.js.map +1 -0
- package/lib/codegen/declare-types.d.ts +20 -0
- package/lib/codegen/declare-types.d.ts.map +1 -0
- package/lib/codegen/declare-types.js +299 -0
- package/lib/codegen/declare-types.js.map +1 -0
- package/lib/codegen/generate-typescript-definitions.d.ts +31 -0
- package/lib/codegen/generate-typescript-definitions.d.ts.map +1 -0
- package/lib/codegen/generate-typescript-definitions.js +37 -0
- package/lib/codegen/generate-typescript-definitions.js.map +1 -0
- package/lib/codegen/generate-typescript-definitions.test.d.ts +2 -0
- package/lib/codegen/generate-typescript-definitions.test.d.ts.map +1 -0
- package/lib/codegen/generate-typescript-definitions.test.js +587 -0
- package/lib/codegen/generate-typescript-definitions.test.js.map +1 -0
- package/lib/codegen/index.d.ts +2 -0
- package/lib/codegen/index.d.ts.map +1 -0
- package/lib/codegen/index.js +6 -0
- package/lib/codegen/index.js.map +1 -0
- package/lib/codegen/schema/component-types.d.ts +3 -0
- package/lib/codegen/schema/component-types.d.ts.map +1 -0
- package/lib/codegen/schema/component-types.js +118 -0
- package/lib/codegen/schema/component-types.js.map +1 -0
- package/lib/codegen/schema/data-types.d.ts +3 -0
- package/lib/codegen/schema/data-types.d.ts.map +1 -0
- package/lib/codegen/schema/data-types.js +98 -0
- package/lib/codegen/schema/data-types.js.map +1 -0
- package/lib/codegen/schema/index.d.ts +3 -0
- package/lib/codegen/schema/index.d.ts.map +1 -0
- package/lib/codegen/schema/index.js +8 -0
- package/lib/codegen/schema/index.js.map +1 -0
- package/lib/codegen/schema/utils.d.ts +4 -0
- package/lib/codegen/schema/utils.d.ts.map +1 -0
- package/lib/codegen/schema/utils.js +103 -0
- package/lib/codegen/schema/utils.js.map +1 -0
- package/lib/codegen/schema-to-types.d.ts +14 -0
- package/lib/codegen/schema-to-types.d.ts.map +1 -0
- package/lib/codegen/schema-to-types.js +203 -0
- package/lib/codegen/schema-to-types.js.map +1 -0
- package/lib/compile/compile.d.ts.map +1 -0
- package/lib/compile/compile.js +245 -0
- package/lib/compile/compile.js.map +1 -0
- package/lib/compile/compile.test.d.ts.map +1 -0
- package/lib/compile/compile.test.js +148 -0
- package/lib/compile/compile.test.js.map +1 -0
- package/lib/evaluate/evaluate.d.ts +26 -0
- package/lib/evaluate/evaluate.d.ts.map +1 -0
- package/lib/evaluate/evaluate.js +40 -0
- package/lib/evaluate/evaluation-error.d.ts +4 -0
- package/lib/evaluate/evaluation-error.d.ts.map +1 -0
- package/lib/evaluate/evaluation-error.js +6 -0
- package/lib/index.d.ts +8 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +16 -0
- package/lib/index.js.map +1 -0
- package/lib/render.d.ts +58 -0
- package/lib/render.d.ts.map +1 -0
- package/lib/render.js +52 -0
- package/lib/render.test.d.ts.map +1 -0
- package/lib/schema.d.ts +68 -0
- package/lib/schema.d.ts.map +1 -0
- package/lib/schema.js +106 -0
- package/lib/validate.d.ts +41 -0
- package/lib/validate.d.ts.map +1 -0
- package/lib/validate.js +42 -0
- package/package.json +27 -0
- package/src/analyze/analysis-error.ts +26 -0
- package/src/analyze/analysis-report.ts +228 -0
- package/src/analyze/analyze.test.ts +829 -0
- package/src/analyze/analyze.ts +10 -0
- package/src/analyze/data/analyze.ts +15 -0
- package/src/analyze/data/data-access.ts +349 -0
- package/src/analyze/data/index.ts +1 -0
- package/src/analyze/data/method-calls.ts +96 -0
- package/src/analyze/data/utils.ts +329 -0
- package/src/analyze/index.ts +3 -0
- package/src/analyze/jsx/analyze.ts +17 -0
- package/src/analyze/jsx/element-attributes.ts +143 -0
- package/src/analyze/jsx/element-children.ts +97 -0
- package/src/analyze/jsx/element-tags.ts +55 -0
- package/src/analyze/jsx/index.ts +1 -0
- package/src/analyze/security/analyze.ts +21 -0
- package/src/analyze/security/call-expressions.ts +41 -0
- package/src/analyze/security/declarations.ts +67 -0
- package/src/analyze/security/expressions.ts +100 -0
- package/src/analyze/security/identifiers.ts +113 -0
- package/src/analyze/security/index.ts +1 -0
- package/src/analyze/security/member-access.ts +35 -0
- package/src/analyze/utils.ts +118 -0
- package/src/analyze/validation-context.ts +32 -0
- package/src/builtins.ts +1149 -0
- package/src/codegen/builtins/builtin-types.ts +174 -0
- package/src/codegen/builtins/index.ts +1 -0
- package/src/codegen/generate-typescript-definitions.test.ts +676 -0
- package/src/codegen/generate-typescript-definitions.ts +35 -0
- package/src/codegen/index.ts +1 -0
- package/src/codegen/schema/component-types.ts +140 -0
- package/src/codegen/schema/data-types.ts +109 -0
- package/src/codegen/schema/index.ts +2 -0
- package/src/codegen/schema/utils.ts +111 -0
- package/src/compile/compilation-error.ts +37 -0
- package/src/compile/compile.test.ts +180 -0
- package/src/compile/compile.ts +282 -0
- package/src/compile/index.ts +2 -0
- package/src/evaluate/evaluate.test.ts +316 -0
- package/src/evaluate/evaluate.ts +81 -0
- package/src/evaluate/evaluation-error.ts +6 -0
- package/src/evaluate/index.ts +2 -0
- package/src/index.ts +10 -0
- package/src/jsx.ts +161 -0
- package/src/parse/index.ts +2 -0
- package/src/parse/parse-error.ts +14 -0
- package/src/parse/parse.test.ts +99 -0
- package/src/parse/parse.ts +40 -0
- package/src/render.test.ts +425 -0
- package/src/render.ts +75 -0
- package/src/schema.ts +195 -0
- package/src/traverse.test.ts +104 -0
- package/src/traverse.ts +78 -0
- package/src/types.ts +17 -0
- package/src/validate.ts +59 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { traverse } from "./traverse";
|
|
3
|
+
import { parse } from "./parse";
|
|
4
|
+
|
|
5
|
+
describe("traverse", () => {
|
|
6
|
+
it("should call JSXElement visitor", () => {
|
|
7
|
+
const ast = parse("<Text>Hello</Text>");
|
|
8
|
+
const visits: string[] = [];
|
|
9
|
+
|
|
10
|
+
traverse(ast, {
|
|
11
|
+
JSXElement() {
|
|
12
|
+
visits.push("JSXElement");
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
expect(visits).toContain("JSXElement");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("should call JSXFragment visitor", () => {
|
|
20
|
+
const ast = parse("<>Fragment</>");
|
|
21
|
+
const visits: string[] = [];
|
|
22
|
+
|
|
23
|
+
traverse(ast, {
|
|
24
|
+
JSXFragment() {
|
|
25
|
+
visits.push("JSXFragment");
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
expect(visits).toContain("JSXFragment");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("should call Identifier visitor for expressions", () => {
|
|
33
|
+
const ast = parse("<Text>{props.value}</Text>");
|
|
34
|
+
const identifiers: string[] = [];
|
|
35
|
+
|
|
36
|
+
traverse(ast, {
|
|
37
|
+
Identifier(node) {
|
|
38
|
+
identifiers.push(node.name);
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
expect(identifiers).toContain("props");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("should call JSXAttribute visitor", () => {
|
|
46
|
+
const ast = parse("<Text x={10} y={20}>Hello</Text>");
|
|
47
|
+
let attributeCount = 0;
|
|
48
|
+
|
|
49
|
+
traverse(ast, {
|
|
50
|
+
JSXAttribute() {
|
|
51
|
+
attributeCount++;
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
expect(attributeCount).toBe(2);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("should call JSXExpressionContainer visitor", () => {
|
|
59
|
+
const ast = parse("<Text>{props.value}</Text>");
|
|
60
|
+
const visits: string[] = [];
|
|
61
|
+
|
|
62
|
+
traverse(ast, {
|
|
63
|
+
JSXExpressionContainer() {
|
|
64
|
+
visits.push("JSXExpressionContainer");
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
expect(visits).toContain("JSXExpressionContainer");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("should traverse nested JSX elements", () => {
|
|
72
|
+
const ast = parse("<Outer><Inner>Text</Inner></Outer>");
|
|
73
|
+
let jsxCount = 0;
|
|
74
|
+
|
|
75
|
+
traverse(ast, {
|
|
76
|
+
JSXElement() {
|
|
77
|
+
jsxCount++;
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
expect(jsxCount).toBe(2);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("should handle multiple visitor types simultaneously", () => {
|
|
85
|
+
const ast = parse("<Text x={props.value}>Hello</Text>");
|
|
86
|
+
const visits: string[] = [];
|
|
87
|
+
|
|
88
|
+
traverse(ast, {
|
|
89
|
+
JSXElement() {
|
|
90
|
+
visits.push("JSXElement");
|
|
91
|
+
},
|
|
92
|
+
JSXAttribute() {
|
|
93
|
+
visits.push("JSXAttribute");
|
|
94
|
+
},
|
|
95
|
+
Identifier(node) {
|
|
96
|
+
visits.push("Identifier:" + node.name);
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
expect(visits).toContain("JSXElement");
|
|
101
|
+
expect(visits).toContain("JSXAttribute");
|
|
102
|
+
expect(visits).toContain("Identifier:props");
|
|
103
|
+
});
|
|
104
|
+
});
|
package/src/traverse.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Program } from "acorn";
|
|
2
|
+
import { simple, base, SimpleVisitors, RecursiveVisitors } from "acorn-walk";
|
|
3
|
+
import {
|
|
4
|
+
JSXAttribute,
|
|
5
|
+
JSXElement,
|
|
6
|
+
JSXExpressionContainer,
|
|
7
|
+
JSXFragment,
|
|
8
|
+
JSXOpeningElement,
|
|
9
|
+
JSXSpreadAttribute,
|
|
10
|
+
isJsxText,
|
|
11
|
+
isJsxEmptyExpression,
|
|
12
|
+
} from "./jsx";
|
|
13
|
+
|
|
14
|
+
type Visit = (node: unknown, state: unknown) => void;
|
|
15
|
+
type RecursiveVisitor<T> = (node: T, state: unknown, visit: Visit) => void;
|
|
16
|
+
type SimpleVisitor<T> = (node: T, state: unknown) => void;
|
|
17
|
+
|
|
18
|
+
type ExtendedRecursiveVisitors = RecursiveVisitors<unknown> & {
|
|
19
|
+
JSXAttribute?: RecursiveVisitor<JSXAttribute>;
|
|
20
|
+
JSXElement?: RecursiveVisitor<JSXElement>;
|
|
21
|
+
JSXExpressionContainer?: RecursiveVisitor<JSXExpressionContainer>;
|
|
22
|
+
JSXFragment?: RecursiveVisitor<JSXFragment>;
|
|
23
|
+
JSXOpeningElement?: RecursiveVisitor<JSXOpeningElement>;
|
|
24
|
+
JSXSpreadAttribute?: RecursiveVisitor<JSXSpreadAttribute>;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type ExtendedSimpleVisitors = SimpleVisitors<unknown> & {
|
|
28
|
+
JSXAttribute?: SimpleVisitor<JSXAttribute>;
|
|
29
|
+
JSXElement?: SimpleVisitor<JSXElement>;
|
|
30
|
+
JSXExpressionContainer?: SimpleVisitor<JSXExpressionContainer>;
|
|
31
|
+
JSXFragment?: SimpleVisitor<JSXFragment>;
|
|
32
|
+
JSXOpeningElement?: SimpleVisitor<JSXOpeningElement>;
|
|
33
|
+
JSXSpreadAttribute?: SimpleVisitor<JSXSpreadAttribute>;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export function traverse(ast: Program, visitors: Partial<ExtendedSimpleVisitors>): void {
|
|
37
|
+
simple(ast, visitors, {
|
|
38
|
+
...base,
|
|
39
|
+
JSXAttribute(node, state, visit) {
|
|
40
|
+
if (node.value) {
|
|
41
|
+
visit(node.value, state);
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
JSXElement(node, state, visit) {
|
|
45
|
+
const { openingElement } = node;
|
|
46
|
+
if (openingElement && Array.isArray(openingElement.attributes)) {
|
|
47
|
+
for (const attribute of openingElement.attributes) {
|
|
48
|
+
visit(attribute, state);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
for (const child of node.children) {
|
|
52
|
+
if (!isJsxText(child)) {
|
|
53
|
+
visit(child, state);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
JSXExpressionContainer(node, state, visit) {
|
|
58
|
+
if (!isJsxEmptyExpression(node.expression)) {
|
|
59
|
+
visit(node.expression, state);
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
JSXFragment(node, state, visit) {
|
|
63
|
+
for (const child of node.children) {
|
|
64
|
+
if (!isJsxText(child)) {
|
|
65
|
+
visit(child, state);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
JSXOpeningElement(node, state, visit) {
|
|
70
|
+
for (const attribute of node.attributes) {
|
|
71
|
+
visit(attribute, state);
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
JSXSpreadAttribute(node, state, visit) {
|
|
75
|
+
visit(node.argument, state);
|
|
76
|
+
},
|
|
77
|
+
} as ExtendedRecursiveVisitors);
|
|
78
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type PropsDict = Record<string, unknown>;
|
|
2
|
+
|
|
3
|
+
export type DataDict = Record<string, unknown>;
|
|
4
|
+
|
|
5
|
+
export type ComponentDict = Record<string, Component>;
|
|
6
|
+
|
|
7
|
+
export type CreateElement<T = any> = (type: Component, props?: PropsDict, ...children: any[]) => T;
|
|
8
|
+
|
|
9
|
+
export type Node = {
|
|
10
|
+
type: string;
|
|
11
|
+
props: PropsDict;
|
|
12
|
+
children: Node[];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type H = (type: string, props?: PropsDict, ...children: Node[]) => Node;
|
|
16
|
+
|
|
17
|
+
export type Component = (...args: any[]) => any;
|
package/src/validate.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { analyze, AnalysisError } from "./analyze";
|
|
2
|
+
import { parse, ParseError } from "./parse";
|
|
3
|
+
import { Schema } from "./schema";
|
|
4
|
+
|
|
5
|
+
export type ValidateResult = { ok: true } | { ok: false; error: AnalysisError | ParseError };
|
|
6
|
+
|
|
7
|
+
export interface ValidateOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Minimum severity level that counts as a failure: 1=info, 2=warning, 3=error.
|
|
10
|
+
*/
|
|
11
|
+
minSeverity?: 1 | 2 | 3;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Validates JSX expression syntax and schema compliance without execution.
|
|
15
|
+
*
|
|
16
|
+
* Performs static analysis to check if JSX source code is valid according to the schema.
|
|
17
|
+
* This includes syntax validation, security constraints, JSX element/attribute validation,
|
|
18
|
+
* and data access pattern validation - all without needing actual data or components.
|
|
19
|
+
*
|
|
20
|
+
* This function parses the source code and returns a simple ok/error result.
|
|
21
|
+
*
|
|
22
|
+
* @param source - JSX expression source code to validate
|
|
23
|
+
* @param schema - Schema defining allowed data properties, JSX elements, and hierarchy rules
|
|
24
|
+
* @returns Validation result indicating success or failure with parse or analysis error
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* const result = validate("<Text>{data.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
|
+
*/
|
|
37
|
+
export function validate(
|
|
38
|
+
source: string,
|
|
39
|
+
schema: Schema,
|
|
40
|
+
|
|
41
|
+
{ minSeverity = 3 }: ValidateOptions = {}
|
|
42
|
+
): ValidateResult {
|
|
43
|
+
try {
|
|
44
|
+
const ast = parse(source);
|
|
45
|
+
const report = analyze(ast, schema);
|
|
46
|
+
|
|
47
|
+
if (report.hasIssues(minSeverity)) {
|
|
48
|
+
return { ok: false, error: AnalysisError.fromReport(report) };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return { ok: true };
|
|
52
|
+
} catch (error: unknown) {
|
|
53
|
+
if (error instanceof ParseError) {
|
|
54
|
+
return { ok: false, error };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
}
|