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,140 @@
|
|
|
1
|
+
import type { Schema } from "../../schema.js";
|
|
2
|
+
import { mapSchemaTypeToTypeScript } from "./utils.js";
|
|
3
|
+
|
|
4
|
+
export function declareComponentTypes(schema: Schema): string {
|
|
5
|
+
let output = `declare global {\n`;
|
|
6
|
+
|
|
7
|
+
if (schema.elements) {
|
|
8
|
+
Object.entries(schema.elements).forEach(([elementName, element]) => {
|
|
9
|
+
const interfaceName = `${elementName}Props`;
|
|
10
|
+
output += generatePropsInterface(interfaceName, element, " ");
|
|
11
|
+
output += "\n";
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (schema.elements) {
|
|
16
|
+
Object.entries(schema.elements).forEach(([elementName, element]) => {
|
|
17
|
+
const interfaceName = `${elementName}Props`;
|
|
18
|
+
const componentComment = generateComponentComment(elementName, element, " ");
|
|
19
|
+
if (componentComment) {
|
|
20
|
+
output += componentComment;
|
|
21
|
+
}
|
|
22
|
+
output += ` const ${elementName}: (props: ${interfaceName}) => JSX.Element;\n\n`;
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
output += `}\n\n`;
|
|
27
|
+
output += `export {};\n`;
|
|
28
|
+
|
|
29
|
+
return output;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function generatePropsInterface(interfaceName: string, element: any, indent: string = ""): string {
|
|
33
|
+
let output = `${indent}interface ${interfaceName} {\n`;
|
|
34
|
+
|
|
35
|
+
Object.entries(element.props || {}).forEach(([propName, prop]: [string, any]) => {
|
|
36
|
+
const optional = prop.required ? "" : "?";
|
|
37
|
+
const type = mapSchemaTypeToTypeScript(prop);
|
|
38
|
+
const comment = generatePropComment(prop, `${indent} `);
|
|
39
|
+
|
|
40
|
+
if (comment) {
|
|
41
|
+
output += comment;
|
|
42
|
+
}
|
|
43
|
+
output += `${indent} ${propName}${optional}: ${type};\n`;
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (element.content !== false) {
|
|
47
|
+
output += `${indent} children?: JSX.Node;\n`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
output += `${indent}}\n`;
|
|
51
|
+
return output;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function generateComponentComment(elementName: string, element: any, indent: string = ""): string {
|
|
55
|
+
const comments: string[] = [];
|
|
56
|
+
|
|
57
|
+
if (element.description) {
|
|
58
|
+
comments.push(element.description);
|
|
59
|
+
} else if (element.doc) {
|
|
60
|
+
comments.push(element.doc);
|
|
61
|
+
} else {
|
|
62
|
+
comments.push(`${elementName} component`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (element.props && Object.keys(element.props).length > 0) {
|
|
66
|
+
comments.push("");
|
|
67
|
+
comments.push(`@param props Component props`);
|
|
68
|
+
|
|
69
|
+
Object.entries(element.props).forEach(([propName, prop]: [string, any]) => {
|
|
70
|
+
if (prop.description) {
|
|
71
|
+
comments.push(`@param props.${propName} ${prop.description}`);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
comments.push("");
|
|
77
|
+
comments.push(`@returns JSX.Element`);
|
|
78
|
+
|
|
79
|
+
if (element.examples && element.examples.length > 0) {
|
|
80
|
+
comments.push("");
|
|
81
|
+
comments.push("@example");
|
|
82
|
+
element.examples.forEach((example: string) => {
|
|
83
|
+
comments.push(example);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (comments.length === 0) {
|
|
88
|
+
return "";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (comments.length === 1 && comments[0] && !comments[0].includes("\n")) {
|
|
92
|
+
return `${indent}/** ${comments[0]} */\n`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let output = `${indent}/**\n`;
|
|
96
|
+
comments.forEach((comment) => {
|
|
97
|
+
if (comment === "") {
|
|
98
|
+
output += `${indent} *\n`;
|
|
99
|
+
} else {
|
|
100
|
+
comment.split("\n").forEach((line) => {
|
|
101
|
+
output += `${indent} * ${line}\n`;
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
output += `${indent} */\n`;
|
|
106
|
+
|
|
107
|
+
return output;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function generatePropComment(prop: any, indent: string): string {
|
|
111
|
+
const comments: string[] = [];
|
|
112
|
+
|
|
113
|
+
if (prop.description) {
|
|
114
|
+
comments.push(prop.description);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (prop.default !== undefined) {
|
|
118
|
+
comments.push(`@default ${JSON.stringify(prop.default)}`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (prop.type === "enum" && prop.values) {
|
|
122
|
+
comments.push(`@values ${prop.values.map((v: any) => `"${v}"`).join(", ")}`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (comments.length === 0) {
|
|
126
|
+
return "";
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (comments.length === 1) {
|
|
130
|
+
return `${indent}/** ${comments[0]} */\n`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
let output = `${indent}/**\n`;
|
|
134
|
+
comments.forEach((comment) => {
|
|
135
|
+
output += `${indent} * ${comment}\n`;
|
|
136
|
+
});
|
|
137
|
+
output += `${indent} */\n`;
|
|
138
|
+
|
|
139
|
+
return output;
|
|
140
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { Schema } from "../../schema.js";
|
|
2
|
+
import { mapSchemaTypeToTypeScript } from "./utils.js";
|
|
3
|
+
|
|
4
|
+
export function declareDataTypes(schema: Schema): string {
|
|
5
|
+
let output = `declare global {\n`;
|
|
6
|
+
|
|
7
|
+
if (schema.data) {
|
|
8
|
+
output += generateDataObjectComment(schema.data, " ");
|
|
9
|
+
output += ` const data: {\n`;
|
|
10
|
+
Object.entries(schema.data).forEach(([dataKey, dataSchema]) => {
|
|
11
|
+
output += generateNestedPropertyJSDoc(dataKey, dataSchema, " ");
|
|
12
|
+
|
|
13
|
+
const type = mapSchemaTypeToTypeScript(dataSchema);
|
|
14
|
+
|
|
15
|
+
const indentedType = type.includes("\n") ? type.replace(/\n/g, "\n ").replace(/\n {4}$/, "\n ") : type;
|
|
16
|
+
output += ` ${dataKey}: ${indentedType};\n`;
|
|
17
|
+
});
|
|
18
|
+
output += ` };\n`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
output += `}\n\n`;
|
|
22
|
+
output += `export {};\n`;
|
|
23
|
+
|
|
24
|
+
return output;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function generateNestedPropertyJSDoc(propertyName: string, propertySchema: any, indent: string = ""): string {
|
|
28
|
+
const comments: string[] = [];
|
|
29
|
+
|
|
30
|
+
if (propertySchema.description) {
|
|
31
|
+
comments.push(propertySchema.description);
|
|
32
|
+
} else {
|
|
33
|
+
const desc =
|
|
34
|
+
propertyName.charAt(0).toUpperCase() +
|
|
35
|
+
propertyName
|
|
36
|
+
.slice(1)
|
|
37
|
+
.replace(/([A-Z])/g, " $1")
|
|
38
|
+
.toLowerCase();
|
|
39
|
+
comments.push(desc);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let hasNestedProperties = false;
|
|
43
|
+
if (propertySchema.type === "object" && propertySchema.shape) {
|
|
44
|
+
comments.push("");
|
|
45
|
+
Object.entries(propertySchema.shape).forEach(([key, value]: [string, any]) => {
|
|
46
|
+
if (value.description) {
|
|
47
|
+
comments.push(`@property ${key} ${value.description}`);
|
|
48
|
+
hasNestedProperties = true;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
} else if (propertySchema.type === "array" && propertySchema.shape?.type === "object" && propertySchema.shape.shape) {
|
|
52
|
+
comments.push("");
|
|
53
|
+
Object.entries(propertySchema.shape.shape).forEach(([key, value]: [string, any]) => {
|
|
54
|
+
if (value.description) {
|
|
55
|
+
comments.push(`@property ${key} ${value.description}`);
|
|
56
|
+
hasNestedProperties = true;
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (comments.length === 1 && !hasNestedProperties) {
|
|
62
|
+
return `${indent}/** ${comments[0]} */\n`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let output = `${indent}/**\n`;
|
|
66
|
+
comments.forEach((comment) => {
|
|
67
|
+
if (comment === "") {
|
|
68
|
+
output += `${indent} *\n`;
|
|
69
|
+
} else {
|
|
70
|
+
output += `${indent} * ${comment}\n`;
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
output += `${indent} */\n`;
|
|
74
|
+
|
|
75
|
+
return output;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function generateDataObjectComment(dataSchema: any, indent: string = ""): string {
|
|
79
|
+
const comments: string[] = [];
|
|
80
|
+
|
|
81
|
+
comments.push("Data object for JSX expressions");
|
|
82
|
+
comments.push("");
|
|
83
|
+
|
|
84
|
+
Object.entries(dataSchema).forEach(([dataKey, schema]: [string, any]) => {
|
|
85
|
+
if (schema.description) {
|
|
86
|
+
comments.push(`@property ${dataKey} ${schema.description}`);
|
|
87
|
+
} else {
|
|
88
|
+
const desc =
|
|
89
|
+
dataKey.charAt(0).toUpperCase() +
|
|
90
|
+
dataKey
|
|
91
|
+
.slice(1)
|
|
92
|
+
.replace(/([A-Z])/g, " $1")
|
|
93
|
+
.toLowerCase();
|
|
94
|
+
comments.push(`@property ${dataKey} ${desc}`);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
let output = `${indent}/**\n`;
|
|
99
|
+
comments.forEach((comment) => {
|
|
100
|
+
if (comment === "") {
|
|
101
|
+
output += `${indent} *\n`;
|
|
102
|
+
} else {
|
|
103
|
+
output += `${indent} * ${comment}\n`;
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
output += `${indent} */\n`;
|
|
107
|
+
|
|
108
|
+
return output;
|
|
109
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
export function mapSchemaTypeToTypeScript(prop: any, depth: number = 0): string {
|
|
2
|
+
switch (prop.type) {
|
|
3
|
+
case "string":
|
|
4
|
+
if (prop.enum && Array.isArray(prop.enum)) {
|
|
5
|
+
return prop.enum.map((value: any) => `"${value}"`).join(" | ");
|
|
6
|
+
}
|
|
7
|
+
return "string";
|
|
8
|
+
case "number":
|
|
9
|
+
if (prop.enum && Array.isArray(prop.enum)) {
|
|
10
|
+
return prop.enum.map((value: any) => String(value)).join(" | ");
|
|
11
|
+
}
|
|
12
|
+
return "number";
|
|
13
|
+
case "boolean":
|
|
14
|
+
if (prop.enum && Array.isArray(prop.enum)) {
|
|
15
|
+
return prop.enum.map((value: any) => String(value)).join(" | ");
|
|
16
|
+
}
|
|
17
|
+
return "boolean";
|
|
18
|
+
case "array":
|
|
19
|
+
const itemType = prop.shape ? mapSchemaTypeToTypeScript(prop.shape, depth) : "any";
|
|
20
|
+
return `${itemType}[]`;
|
|
21
|
+
case "object":
|
|
22
|
+
if (prop.shape && typeof prop.shape === "object") {
|
|
23
|
+
return generateObjectTypeWithJSDoc(prop.shape, depth);
|
|
24
|
+
}
|
|
25
|
+
if (prop.properties && typeof prop.properties === "object") {
|
|
26
|
+
return generateObjectTypeWithJSDoc(prop.properties, depth);
|
|
27
|
+
}
|
|
28
|
+
return "object";
|
|
29
|
+
case "function":
|
|
30
|
+
return "(...args: any[]) => any";
|
|
31
|
+
default:
|
|
32
|
+
console.warn(`Unknown schema type: ${prop.type}`);
|
|
33
|
+
return "any";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function generateObjectTypeWithJSDoc(properties: any, depth: number = 0): string {
|
|
38
|
+
let result = "{\n";
|
|
39
|
+
|
|
40
|
+
// Generate each property with inline JSDoc
|
|
41
|
+
Object.entries(properties).forEach(([key, value]: [string, any]) => {
|
|
42
|
+
// Generate comprehensive JSDoc for this property
|
|
43
|
+
const propertyJSDoc = generatePropertyJSDoc(key, value, " ", depth);
|
|
44
|
+
result += propertyJSDoc;
|
|
45
|
+
|
|
46
|
+
const nestedType = mapSchemaTypeToTypeScript(value, depth + 1);
|
|
47
|
+
const required = value.required !== false ? "" : "?";
|
|
48
|
+
result += ` ${key}${required}: ${nestedType};\n`;
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
result += " }";
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function generatePropertyJSDoc(
|
|
56
|
+
propertyName: string,
|
|
57
|
+
propertySchema: any,
|
|
58
|
+
indent: string = "",
|
|
59
|
+
depth: number = 0
|
|
60
|
+
): string {
|
|
61
|
+
const comments: string[] = [];
|
|
62
|
+
|
|
63
|
+
// Main description
|
|
64
|
+
if (propertySchema.description) {
|
|
65
|
+
comments.push(propertySchema.description);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Add @property tags for nested object properties (recursive!)
|
|
69
|
+
let hasNestedProperties = false;
|
|
70
|
+
if (propertySchema.type === "object" && propertySchema.shape) {
|
|
71
|
+
comments.push("");
|
|
72
|
+
Object.entries(propertySchema.shape).forEach(([key, value]: [string, any]) => {
|
|
73
|
+
if (value.description) {
|
|
74
|
+
comments.push(`@property ${key} ${value.description}`);
|
|
75
|
+
hasNestedProperties = true;
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
} else if (propertySchema.type === "array" && propertySchema.shape?.type === "object" && propertySchema.shape.shape) {
|
|
79
|
+
// For arrays of objects, document the object structure
|
|
80
|
+
comments.push("");
|
|
81
|
+
Object.entries(propertySchema.shape.shape).forEach(([key, value]: [string, any]) => {
|
|
82
|
+
if (value.description) {
|
|
83
|
+
comments.push(`@property ${key} ${value.description}`);
|
|
84
|
+
hasNestedProperties = true;
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// No comments needed
|
|
90
|
+
if (comments.length === 0) {
|
|
91
|
+
return "";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Single-line JSDoc
|
|
95
|
+
if (comments.length === 1 && !hasNestedProperties) {
|
|
96
|
+
return `${indent}/** ${comments[0]} */\n`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Multi-line JSDoc with @property tags
|
|
100
|
+
let output = `${indent}/**\n`;
|
|
101
|
+
comments.forEach((comment) => {
|
|
102
|
+
if (comment === "") {
|
|
103
|
+
output += `${indent} *\n`;
|
|
104
|
+
} else {
|
|
105
|
+
output += `${indent} * ${comment}\n`;
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
output += `${indent} */\n`;
|
|
109
|
+
|
|
110
|
+
return output;
|
|
111
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
type Loc = {
|
|
2
|
+
start: {
|
|
3
|
+
line: number;
|
|
4
|
+
column: number;
|
|
5
|
+
};
|
|
6
|
+
end: {
|
|
7
|
+
line: number;
|
|
8
|
+
column: number;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export class CompilationError extends Error {
|
|
13
|
+
static fromNode(message: string, node: any): CompilationError {
|
|
14
|
+
const loc = node.loc
|
|
15
|
+
? {
|
|
16
|
+
start: {
|
|
17
|
+
line: node.loc.start.line,
|
|
18
|
+
column: node.loc.start.column,
|
|
19
|
+
},
|
|
20
|
+
end: {
|
|
21
|
+
line: node.loc.end.line,
|
|
22
|
+
column: node.loc.end.column,
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
: undefined;
|
|
26
|
+
|
|
27
|
+
return new CompilationError(message, loc);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
readonly loc?: Loc;
|
|
31
|
+
|
|
32
|
+
constructor(message: string, loc?: Loc) {
|
|
33
|
+
super(message);
|
|
34
|
+
this.name = "CompilationError";
|
|
35
|
+
this.loc = loc;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { compile } from "../compile";
|
|
3
|
+
import { parse } from "../parse";
|
|
4
|
+
|
|
5
|
+
describe("compile", () => {
|
|
6
|
+
it("should compile ternary expression", () => {
|
|
7
|
+
const ast = parse("<div>{props.show ? 'Yes' : 'No'}</div>");
|
|
8
|
+
const result = compile(ast);
|
|
9
|
+
expect(result).toBe('"use strict";return h("div", null, (props.show?"Yes":"No"));');
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("should compile logical OR expression", () => {
|
|
13
|
+
const ast = parse("<div>{props.value || 'default'}</div>");
|
|
14
|
+
const result = compile(ast);
|
|
15
|
+
expect(result).toBe('"use strict";return h("div", null, (props.value || "default"));');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("should compile array operations", () => {
|
|
19
|
+
const ast = parse("<div>{props.items.filter(x => x.length > 2).join(', ')}</div>");
|
|
20
|
+
const result = compile(ast);
|
|
21
|
+
expect(result).toBe('"use strict";return h("div", null, props.items.filter((x) => (x.length > 2)).join(", "));');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("should compile object literals in props", () => {
|
|
25
|
+
const ast = parse("<div style={{ color: 'red' }}>Text</div>");
|
|
26
|
+
const result = compile(ast);
|
|
27
|
+
expect(result).toBe('"use strict";return h("div", { style: { color: "red" } }, "Text");');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("should compile nested ternary expressions", () => {
|
|
31
|
+
const ast = parse("<div>{props.level > 2 ? 'high' : props.level > 1 ? 'medium' : 'low'}</div>");
|
|
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
|
+
);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("should compile nested JSX elements", () => {
|
|
39
|
+
const ast = parse("<div><span><strong>Nested</strong></span></div>");
|
|
40
|
+
const result = compile(ast);
|
|
41
|
+
expect(result).toBe('"use strict";return h("div", null, h("span", null, h("strong", null, "Nested")));');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("should compile nested array operations", () => {
|
|
45
|
+
const ast = parse("<div>{props.items.map(x => x.split(',').map(y => y.trim()).join(' ')).join(' | ')}</div>");
|
|
46
|
+
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
|
+
);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("should compile bare expression containers", () => {
|
|
53
|
+
const ast = parse("{data.user.name}");
|
|
54
|
+
const result = compile(ast);
|
|
55
|
+
expect(result).toBe('"use strict";return data.user.name;');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("should compile bare mathematical expressions", () => {
|
|
59
|
+
const ast = parse("{data.count * 2}");
|
|
60
|
+
const result = compile(ast);
|
|
61
|
+
expect(result).toBe('"use strict";return (data.count * 2);');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("should compile bare method calls", () => {
|
|
65
|
+
const ast = parse("{data.text.toUpperCase()}");
|
|
66
|
+
const result = compile(ast);
|
|
67
|
+
expect(result).toBe('"use strict";return data.text.toUpperCase();');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("should compile bare complex expressions", () => {
|
|
71
|
+
const ast = parse("{data.items.filter(x => x.length > 2).length}");
|
|
72
|
+
const result = compile(ast);
|
|
73
|
+
expect(result).toBe('"use strict";return data.items.filter((x) => (x.length > 2)).length;');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe("JSX empty expressions", () => {
|
|
77
|
+
it("should handle empty expressions in children", () => {
|
|
78
|
+
const ast = parse("<Text>{}</Text>");
|
|
79
|
+
const compiled = compile(ast);
|
|
80
|
+
|
|
81
|
+
expect(compiled).toContain("return");
|
|
82
|
+
expect(compiled).toContain("Text");
|
|
83
|
+
// Empty expressions should be skipped, so no children array should be empty or minimal
|
|
84
|
+
expect(compiled).toBeDefined();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("should handle multiple empty expressions", () => {
|
|
88
|
+
const ast = parse("<Text>{}{}{}</Text>");
|
|
89
|
+
const compiled = compile(ast);
|
|
90
|
+
|
|
91
|
+
expect(compiled).toContain("return");
|
|
92
|
+
expect(compiled).toContain("Text");
|
|
93
|
+
expect(compiled).toBeDefined();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("should handle mixed empty and real expressions", () => {
|
|
97
|
+
const ast = parse("<Text>Hello {}{data.x}{} World</Text>");
|
|
98
|
+
const compiled = compile(ast);
|
|
99
|
+
|
|
100
|
+
expect(compiled).toContain("return");
|
|
101
|
+
expect(compiled).toContain("Text");
|
|
102
|
+
expect(compiled).toContain("Hello");
|
|
103
|
+
expect(compiled).toContain("World");
|
|
104
|
+
expect(compiled).toContain("data.x");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("should generate valid JavaScript code structure", () => {
|
|
108
|
+
const ast = parse("<Text>{}</Text>");
|
|
109
|
+
const compiled = compile(ast);
|
|
110
|
+
|
|
111
|
+
// The compiled code should have expected structure
|
|
112
|
+
expect(compiled).toMatch(/^"use strict";return .+;$/);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe("spread elements", () => {
|
|
117
|
+
it("should compile spread in arrays", () => {
|
|
118
|
+
const ast = parse("<div>{[...data.items]}</div>");
|
|
119
|
+
const result = compile(ast);
|
|
120
|
+
expect(result).toBe('"use strict";return h("div", null, [...data.items]);');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("should compile spread in objects", () => {
|
|
124
|
+
const ast = parse("<div style={{ ...data.styles }}>Text</div>");
|
|
125
|
+
const result = compile(ast);
|
|
126
|
+
expect(result).toBe('"use strict";return h("div", { style: { ...data.styles } }, "Text");');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("should compile mixed spread and regular elements in arrays", () => {
|
|
130
|
+
const ast = parse("<div>{[1, ...data.items, 2]}</div>");
|
|
131
|
+
const result = compile(ast);
|
|
132
|
+
expect(result).toBe('"use strict";return h("div", null, [1, ...data.items, 2]);');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("should compile mixed spread and regular properties in objects", () => {
|
|
136
|
+
const ast = parse("<div style={{ color: 'red', ...data.styles, width: 100 }}>Text</div>");
|
|
137
|
+
const result = compile(ast);
|
|
138
|
+
expect(result).toBe(
|
|
139
|
+
'"use strict";return h("div", { style: { color: "red", ...data.styles, width: 100 } }, "Text");'
|
|
140
|
+
);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("should compile multiple spreads in arrays", () => {
|
|
144
|
+
const ast = parse("<div>{[...data.first, ...data.second]}</div>");
|
|
145
|
+
const result = compile(ast);
|
|
146
|
+
expect(result).toBe('"use strict";return h("div", null, [...data.first, ...data.second]);');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("should compile multiple spreads in objects", () => {
|
|
150
|
+
const ast = parse("<div style={{ ...data.base, ...data.override }}>Text</div>");
|
|
151
|
+
const result = compile(ast);
|
|
152
|
+
expect(result).toBe('"use strict";return h("div", { style: { ...data.base, ...data.override } }, "Text");');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("should compile spread in function call arguments", () => {
|
|
156
|
+
const ast = parse("<div>{Math.max(...data.numbers)}</div>");
|
|
157
|
+
const result = compile(ast);
|
|
158
|
+
expect(result).toBe('"use strict";return h("div", null, Math.max(...data.numbers));');
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("should compile spread with map in function call", () => {
|
|
162
|
+
const ast = parse("<div>{Math.min(...data.items.map(x => x.price))}</div>");
|
|
163
|
+
const result = compile(ast);
|
|
164
|
+
expect(result).toBe('"use strict";return h("div", null, Math.min(...data.items.map((x) => x.price)));');
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("should compile mixed spread and regular arguments", () => {
|
|
168
|
+
const ast = parse("<div>{Math.max(0, ...data.numbers, 100)}</div>");
|
|
169
|
+
const result = compile(ast);
|
|
170
|
+
expect(result).toBe('"use strict";return h("div", null, Math.max(0, ...data.numbers, 100));');
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// TODO: Add ChainExpression support for optional chaining (?.)
|
|
175
|
+
// it("should compile nested object access with conditionals", () => {
|
|
176
|
+
// const ast = parse("<div>{props.user?.profile?.name || 'Anonymous'}</div>");
|
|
177
|
+
// const result = compile(ast);
|
|
178
|
+
// expect(result).toBe('"use strict";return h("div", null, ((props.user?.profile?.name) || "Anonymous"));');
|
|
179
|
+
// });
|
|
180
|
+
});
|