jsxpression 0.1.41 → 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 +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 +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 +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 +76 -92
- 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 +1 -1
- 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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
|
+
import type { ElementSchema, Schema } from "../schema.js";
|
|
2
3
|
import { generateTypeScriptDefinitions } from "./generate-typescript-definitions.js";
|
|
3
|
-
import type { Schema } from "../schema.js";
|
|
4
4
|
|
|
5
5
|
describe("generateTypeScriptDefinitions", () => {
|
|
6
6
|
describe("basic structure", () => {
|
|
@@ -230,10 +230,11 @@ describe("generateTypeScriptDefinitions", () => {
|
|
|
230
230
|
const schema: Schema = {
|
|
231
231
|
data: {},
|
|
232
232
|
elements: {
|
|
233
|
+
// `content` isn't part of ElementSchema; this intersection type documents that it's a legacy/unmodeled field being tested here.
|
|
233
234
|
Input: {
|
|
234
235
|
props: {},
|
|
235
236
|
content: false,
|
|
236
|
-
} as
|
|
237
|
+
} as ElementSchema & { content: boolean },
|
|
237
238
|
},
|
|
238
239
|
};
|
|
239
240
|
|
|
@@ -525,9 +526,7 @@ describe("generateTypeScriptDefinitions", () => {
|
|
|
525
526
|
};
|
|
526
527
|
|
|
527
528
|
const result = generateTypeScriptDefinitions(schema);
|
|
528
|
-
expect(result).toContain(
|
|
529
|
-
`id: "TypeA";\n valueA: string;\n } | {\n id: "TypeB";\n valueB: number;\n };`
|
|
530
|
-
);
|
|
529
|
+
expect(result).toContain(`id: "TypeA";\n valueA: string;\n } | {\n id: "TypeB";\n valueB: number;\n };`);
|
|
531
530
|
});
|
|
532
531
|
|
|
533
532
|
it("should generate functions", () => {
|
|
@@ -608,8 +607,9 @@ describe("generateTypeScriptDefinitions", () => {
|
|
|
608
607
|
shape: { type: "string" },
|
|
609
608
|
},
|
|
610
609
|
},
|
|
610
|
+
// `content` isn't part of ElementSchema; this intersection type documents that it's a legacy/unmodeled field being tested here.
|
|
611
611
|
content: false,
|
|
612
|
-
} as
|
|
612
|
+
} as ElementSchema & { content: boolean },
|
|
613
613
|
},
|
|
614
614
|
};
|
|
615
615
|
|
|
@@ -6,35 +6,31 @@ import { declareComponentTypes, declareDataTypes, declareFunctionTypes } from ".
|
|
|
6
6
|
* Generates TypeScript definitions from a JSXExpression schema.
|
|
7
7
|
*
|
|
8
8
|
* The generated definitions enable code editors to provide:
|
|
9
|
+
*
|
|
9
10
|
* - IntelliSense autocomplete for components and data properties
|
|
10
11
|
* - Type checking for JSX expressions
|
|
11
12
|
* - Hover documentation from schema descriptions
|
|
12
13
|
*
|
|
13
|
-
* @param schema - Schema defining allowed data properties, JSX elements, and their types
|
|
14
|
-
* @returns Complete TypeScript definition file as a string
|
|
15
|
-
*
|
|
16
14
|
* @example
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
15
|
+
* ```typescript
|
|
16
|
+
* const schema = {
|
|
17
|
+
* elements: {
|
|
18
|
+
* Button: {
|
|
19
|
+
* props: { size: { type: "string", enum: ["sm", "md", "lg"] } },
|
|
20
|
+
* },
|
|
21
|
+
* },
|
|
22
|
+
* data: {
|
|
23
|
+
* user: { type: "object", shape: { name: { type: "string" } } },
|
|
24
|
+
* },
|
|
25
|
+
* };
|
|
28
26
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
27
|
+
* const definitions = generateTypeScriptDefinitions(schema);
|
|
28
|
+
* // Use with Monaco, VS Code, or any TypeScript-enabled editor
|
|
29
|
+
* ```;
|
|
30
|
+
*
|
|
31
|
+
* @param schema - Schema defining allowed data properties, JSX elements, and their types
|
|
32
|
+
* @returns Complete TypeScript definition file as a string
|
|
32
33
|
*/
|
|
33
34
|
export function generateTypeScriptDefinitions(schema: Schema): string {
|
|
34
|
-
return [
|
|
35
|
-
declareBuiltinTypes(),
|
|
36
|
-
declareComponentTypes(schema),
|
|
37
|
-
declareDataTypes(schema),
|
|
38
|
-
declareFunctionTypes(schema),
|
|
39
|
-
].join("\n");
|
|
35
|
+
return [declareBuiltinTypes(), declareComponentTypes(schema), declareDataTypes(schema), declareFunctionTypes(schema)].join("\n");
|
|
40
36
|
}
|
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
import type { Schema } from "../../schema.js";
|
|
1
|
+
import type { ElementSchema, PropertySchema, Schema } from "../../schema.js";
|
|
2
2
|
import { mapSchemaTypeToTypeScript } from "./utils.js";
|
|
3
3
|
|
|
4
|
+
// ElementSchema fields the generator supports that aren't (yet) part of the shared schema type.
|
|
5
|
+
type ElementSchemaExtras = ElementSchema & {
|
|
6
|
+
content?: boolean;
|
|
7
|
+
doc?: string;
|
|
8
|
+
examples?: ReadonlyArray<string>;
|
|
9
|
+
};
|
|
10
|
+
|
|
4
11
|
export function declareComponentTypes(schema: Schema): string {
|
|
5
12
|
let output = `declare global {\n`;
|
|
6
13
|
|
|
@@ -29,10 +36,10 @@ export function declareComponentTypes(schema: Schema): string {
|
|
|
29
36
|
return output;
|
|
30
37
|
}
|
|
31
38
|
|
|
32
|
-
function generatePropsInterface(interfaceName: string, element:
|
|
39
|
+
function generatePropsInterface(interfaceName: string, element: ElementSchemaExtras, indent: string = ""): string {
|
|
33
40
|
let output = `${indent}interface ${interfaceName} {\n`;
|
|
34
41
|
|
|
35
|
-
Object.entries(element.props
|
|
42
|
+
Object.entries(element.props ?? {}).forEach(([propName, prop]) => {
|
|
36
43
|
const optional = prop.required ? "" : "?";
|
|
37
44
|
const type = mapSchemaTypeToTypeScript(prop);
|
|
38
45
|
const comment = generatePropComment(prop, `${indent} `);
|
|
@@ -51,8 +58,8 @@ function generatePropsInterface(interfaceName: string, element: any, indent: str
|
|
|
51
58
|
return output;
|
|
52
59
|
}
|
|
53
60
|
|
|
54
|
-
function generateComponentComment(elementName: string, element:
|
|
55
|
-
const comments: string
|
|
61
|
+
function generateComponentComment(elementName: string, element: ElementSchemaExtras, indent: string = ""): string {
|
|
62
|
+
const comments: Array<string> = [];
|
|
56
63
|
|
|
57
64
|
if (element.description) {
|
|
58
65
|
comments.push(element.description);
|
|
@@ -66,7 +73,7 @@ function generateComponentComment(elementName: string, element: any, indent: str
|
|
|
66
73
|
comments.push("");
|
|
67
74
|
comments.push(`@param props Component props`);
|
|
68
75
|
|
|
69
|
-
Object.entries(element.props).forEach(([propName, prop]
|
|
76
|
+
Object.entries(element.props).forEach(([propName, prop]) => {
|
|
70
77
|
if (prop.description) {
|
|
71
78
|
comments.push(`@param props.${propName} ${prop.description}`);
|
|
72
79
|
}
|
|
@@ -79,7 +86,7 @@ function generateComponentComment(elementName: string, element: any, indent: str
|
|
|
79
86
|
if (element.examples && element.examples.length > 0) {
|
|
80
87
|
comments.push("");
|
|
81
88
|
comments.push("@example");
|
|
82
|
-
element.examples.forEach((example
|
|
89
|
+
element.examples.forEach((example) => {
|
|
83
90
|
comments.push(example);
|
|
84
91
|
});
|
|
85
92
|
}
|
|
@@ -107,19 +114,22 @@ function generateComponentComment(elementName: string, element: any, indent: str
|
|
|
107
114
|
return output;
|
|
108
115
|
}
|
|
109
116
|
|
|
110
|
-
function generatePropComment(prop:
|
|
111
|
-
const comments: string
|
|
117
|
+
function generatePropComment(prop: PropertySchema, indent: string): string {
|
|
118
|
+
const comments: Array<string> = [];
|
|
112
119
|
|
|
113
120
|
if (prop.description) {
|
|
114
121
|
comments.push(prop.description);
|
|
115
122
|
}
|
|
116
123
|
|
|
117
|
-
if (prop.default !== undefined) {
|
|
124
|
+
if ((prop.type === "string" || prop.type === "number" || prop.type === "boolean") && prop.default !== undefined) {
|
|
118
125
|
comments.push(`@default ${JSON.stringify(prop.default)}`);
|
|
119
126
|
}
|
|
120
127
|
|
|
121
|
-
|
|
122
|
-
|
|
128
|
+
// oxlint-disable-next-line typescript/no-explicit-any -- "enum" isn't a PropertySchema.type variant; kept as-is rather than deleted by this type-only lint pass
|
|
129
|
+
const propAsAny = prop as any;
|
|
130
|
+
if (propAsAny.type === "enum" && propAsAny.values) {
|
|
131
|
+
// oxlint-disable-next-line typescript/no-explicit-any -- propAsAny.values is untyped ("enum" isn't a PropertySchema.type variant)
|
|
132
|
+
comments.push(`@values ${propAsAny.values.map((v: any) => `"${v}"`).join(", ")}`);
|
|
123
133
|
}
|
|
124
134
|
|
|
125
135
|
if (comments.length === 0) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Schema } from "../../schema.js";
|
|
1
|
+
import type { PropertySchema, Schema } from "../../schema.js";
|
|
2
2
|
import { mapSchemaTypeToTypeScript } from "./utils.js";
|
|
3
3
|
|
|
4
4
|
export function declareDataTypes(schema: Schema): string {
|
|
@@ -21,8 +21,8 @@ export function declareDataTypes(schema: Schema): string {
|
|
|
21
21
|
return output;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
function generateNestedPropertyJSDoc(propertyName: string, propertySchema:
|
|
25
|
-
const comments: string
|
|
24
|
+
function generateNestedPropertyJSDoc(propertyName: string, propertySchema: PropertySchema, indent: string = ""): string {
|
|
25
|
+
const comments: Array<string> = [];
|
|
26
26
|
|
|
27
27
|
if (propertySchema.description) {
|
|
28
28
|
comments.push(propertySchema.description);
|
|
@@ -39,15 +39,15 @@ function generateNestedPropertyJSDoc(propertyName: string, propertySchema: any,
|
|
|
39
39
|
let hasNestedProperties = false;
|
|
40
40
|
if (propertySchema.type === "object" && propertySchema.shape) {
|
|
41
41
|
comments.push("");
|
|
42
|
-
Object.entries(propertySchema.shape).forEach(([key, value]
|
|
42
|
+
Object.entries(propertySchema.shape).forEach(([key, value]) => {
|
|
43
43
|
if (value.description) {
|
|
44
44
|
comments.push(`@property ${key} ${value.description}`);
|
|
45
45
|
hasNestedProperties = true;
|
|
46
46
|
}
|
|
47
47
|
});
|
|
48
|
-
} else if (propertySchema.type === "array" && propertySchema.shape
|
|
48
|
+
} else if (propertySchema.type === "array" && propertySchema.shape.type === "object" && propertySchema.shape.shape) {
|
|
49
49
|
comments.push("");
|
|
50
|
-
Object.entries(propertySchema.shape.shape).forEach(([key, value]
|
|
50
|
+
Object.entries(propertySchema.shape.shape).forEach(([key, value]) => {
|
|
51
51
|
if (value.description) {
|
|
52
52
|
comments.push(`@property ${key} ${value.description}`);
|
|
53
53
|
hasNestedProperties = true;
|
|
@@ -6,9 +6,7 @@ export function declareFunctionTypes(schema: Schema): string {
|
|
|
6
6
|
|
|
7
7
|
if (schema.functions) {
|
|
8
8
|
Object.entries(schema.functions).forEach(([funcName, funcSchema]) => {
|
|
9
|
-
const args = funcSchema.args
|
|
10
|
-
? funcSchema.args.map((arg) => `${arg.name}: ${mapSchemaTypeToTypeScript(arg.property)}`).join(", ")
|
|
11
|
-
: "";
|
|
9
|
+
const args = funcSchema.args ? funcSchema.args.map((arg) => `${arg.name}: ${mapSchemaTypeToTypeScript(arg.property)}`).join(", ") : "";
|
|
12
10
|
const ret = funcSchema.ret ? mapSchemaTypeToTypeScript(funcSchema.ret) : "void";
|
|
13
11
|
output += `function ${funcName}(${args}): ${ret};\n`;
|
|
14
12
|
});
|
|
@@ -1,50 +1,56 @@
|
|
|
1
|
-
|
|
1
|
+
import type { PropertySchema } from "../../schema.js";
|
|
2
|
+
|
|
3
|
+
export function mapSchemaTypeToTypeScript(prop: PropertySchema, depth: number = 0): string {
|
|
2
4
|
switch (prop.type) {
|
|
3
5
|
case "string":
|
|
4
6
|
if (prop.enum && Array.isArray(prop.enum)) {
|
|
5
|
-
return prop.enum.map((value
|
|
7
|
+
return prop.enum.map((value) => `"${value}"`).join(" | ");
|
|
6
8
|
}
|
|
7
9
|
return "string";
|
|
8
10
|
case "number":
|
|
9
11
|
if (prop.enum && Array.isArray(prop.enum)) {
|
|
10
|
-
return prop.enum.map((value
|
|
12
|
+
return prop.enum.map((value) => String(value)).join(" | ");
|
|
11
13
|
}
|
|
12
14
|
return "number";
|
|
13
15
|
case "boolean":
|
|
14
16
|
if (prop.enum && Array.isArray(prop.enum)) {
|
|
15
|
-
return prop.enum.map((value
|
|
17
|
+
return prop.enum.map((value) => String(value)).join(" | ");
|
|
16
18
|
}
|
|
17
19
|
return "boolean";
|
|
18
|
-
case "array":
|
|
20
|
+
case "array": {
|
|
19
21
|
const itemType = prop.shape ? mapSchemaTypeToTypeScript(prop.shape, depth) : "any";
|
|
20
22
|
return `${itemType}[]`;
|
|
21
|
-
|
|
23
|
+
}
|
|
24
|
+
case "object": {
|
|
22
25
|
if (prop.shape && typeof prop.shape === "object") {
|
|
23
26
|
return generateObjectTypeWithJSDoc(prop.shape, depth);
|
|
24
27
|
}
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
// oxlint-disable-next-line typescript/no-explicit-any -- "properties" isn't an ObjectPropertySchema field; kept as a legacy fallback rather than removed by this type-only pass
|
|
29
|
+
const propAsAny = prop as any;
|
|
30
|
+
if (propAsAny.properties && typeof propAsAny.properties === "object") {
|
|
31
|
+
return generateObjectTypeWithJSDoc(propAsAny.properties, depth);
|
|
27
32
|
}
|
|
28
33
|
return "object";
|
|
29
|
-
|
|
34
|
+
}
|
|
35
|
+
case "record": {
|
|
30
36
|
const valueType = prop.shape ? mapSchemaTypeToTypeScript(prop.shape) : "any";
|
|
31
37
|
return `Record<string, ${valueType}>`;
|
|
38
|
+
}
|
|
32
39
|
case "function":
|
|
33
40
|
return "(...args: any[]) => any";
|
|
34
41
|
case "union": {
|
|
35
|
-
return
|
|
42
|
+
return prop.shape.map((p) => mapSchemaTypeToTypeScript(p)).join(" | ");
|
|
36
43
|
}
|
|
37
44
|
default:
|
|
38
|
-
console.warn(`Unknown schema type: ${prop.type}`);
|
|
39
45
|
return "any";
|
|
40
46
|
}
|
|
41
47
|
}
|
|
42
48
|
|
|
43
|
-
export function generateObjectTypeWithJSDoc(properties:
|
|
49
|
+
export function generateObjectTypeWithJSDoc(properties: Record<string, PropertySchema>, depth: number = 0): string {
|
|
44
50
|
let result = "{\n";
|
|
45
51
|
|
|
46
52
|
// Generate each property with inline JSDoc
|
|
47
|
-
Object.entries(properties).forEach(([key, value]
|
|
53
|
+
Object.entries(properties).forEach(([key, value]) => {
|
|
48
54
|
// Generate comprehensive JSDoc for this property
|
|
49
55
|
const propertyJSDoc = generatePropertyJSDoc(key, value, " ", depth);
|
|
50
56
|
result += propertyJSDoc;
|
|
@@ -59,13 +65,8 @@ export function generateObjectTypeWithJSDoc(properties: any, depth: number = 0):
|
|
|
59
65
|
return result;
|
|
60
66
|
}
|
|
61
67
|
|
|
62
|
-
export function generatePropertyJSDoc(
|
|
63
|
-
|
|
64
|
-
propertySchema: any,
|
|
65
|
-
indent: string = "",
|
|
66
|
-
_depth: number = 0
|
|
67
|
-
): string {
|
|
68
|
-
const comments: string[] = [];
|
|
68
|
+
export function generatePropertyJSDoc(_propertyName: string, propertySchema: PropertySchema, indent: string = "", _depth: number = 0): string {
|
|
69
|
+
const comments: Array<string> = [];
|
|
69
70
|
|
|
70
71
|
// Main description
|
|
71
72
|
if (propertySchema.description) {
|
|
@@ -76,16 +77,16 @@ export function generatePropertyJSDoc(
|
|
|
76
77
|
let hasNestedProperties = false;
|
|
77
78
|
if (propertySchema.type === "object" && propertySchema.shape) {
|
|
78
79
|
comments.push("");
|
|
79
|
-
Object.entries(propertySchema.shape).forEach(([key, value]
|
|
80
|
+
Object.entries(propertySchema.shape).forEach(([key, value]) => {
|
|
80
81
|
if (value.description) {
|
|
81
82
|
comments.push(`@property ${key} ${value.description}`);
|
|
82
83
|
hasNestedProperties = true;
|
|
83
84
|
}
|
|
84
85
|
});
|
|
85
|
-
} else if (propertySchema.type === "array" && propertySchema.shape
|
|
86
|
+
} else if (propertySchema.type === "array" && propertySchema.shape.type === "object" && propertySchema.shape.shape) {
|
|
86
87
|
// For arrays of objects, document the object structure
|
|
87
88
|
comments.push("");
|
|
88
|
-
Object.entries(propertySchema.shape.shape).forEach(([key, value]
|
|
89
|
+
Object.entries(propertySchema.shape.shape).forEach(([key, value]) => {
|
|
89
90
|
if (value.description) {
|
|
90
91
|
comments.push(`@property ${key} ${value.description}`);
|
|
91
92
|
hasNestedProperties = true;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { Node } from "acorn";
|
|
2
|
+
|
|
1
3
|
type Loc = {
|
|
2
4
|
start: {
|
|
3
5
|
line: number;
|
|
@@ -9,8 +11,10 @@ type Loc = {
|
|
|
9
11
|
};
|
|
10
12
|
};
|
|
11
13
|
|
|
14
|
+
// oxlint-disable-next-line functional/no-classes -- extends Error, which requires a class
|
|
12
15
|
export class CompilationError extends Error {
|
|
13
|
-
static fromNode(message: string,
|
|
16
|
+
static fromNode(message: string, nodeParam: unknown): CompilationError {
|
|
17
|
+
const node = nodeParam as Node;
|
|
14
18
|
const loc = node.loc
|
|
15
19
|
? {
|
|
16
20
|
start: {
|
|
@@ -30,9 +30,7 @@ describe("compile", () => {
|
|
|
30
30
|
it("should compile nested ternary expressions", () => {
|
|
31
31
|
const ast = parse("<div>{props.level > 2 ? 'high' : props.level > 1 ? 'medium' : 'low'}</div>");
|
|
32
32
|
const result = compile(ast);
|
|
33
|
-
expect(result).toBe(
|
|
34
|
-
'"use strict";return h("div", null, ((props.level > 2)?"high":((props.level > 1)?"medium":"low")));'
|
|
35
|
-
);
|
|
33
|
+
expect(result).toBe('"use strict";return h("div", null, ((props.level > 2)?"high":((props.level > 1)?"medium":"low")));');
|
|
36
34
|
});
|
|
37
35
|
|
|
38
36
|
it("should compile nested JSX elements", () => {
|
|
@@ -44,9 +42,7 @@ describe("compile", () => {
|
|
|
44
42
|
it("should compile nested array operations", () => {
|
|
45
43
|
const ast = parse("<div>{props.items.map(x => x.split(',').map(y => y.trim()).join(' ')).join(' | ')}</div>");
|
|
46
44
|
const result = compile(ast);
|
|
47
|
-
expect(result).toBe(
|
|
48
|
-
'"use strict";return h("div", null, props.items.map((x) => x.split(",").map((y) => y.trim()).join(" ")).join(" | "));'
|
|
49
|
-
);
|
|
45
|
+
expect(result).toBe('"use strict";return h("div", null, props.items.map((x) => x.split(",").map((y) => y.trim()).join(" ")).join(" | "));');
|
|
50
46
|
});
|
|
51
47
|
|
|
52
48
|
it("should compile bare expression containers", () => {
|
|
@@ -217,23 +213,17 @@ describe("compile", () => {
|
|
|
217
213
|
it("should compile function with props parameter", () => {
|
|
218
214
|
const ast = parse('function Badge({ label }) {\n return <span>{label}</span>\n}\nreturn <Badge label="hi" />');
|
|
219
215
|
const result = compile(ast);
|
|
220
|
-
expect(result).toBe(
|
|
221
|
-
'"use strict";function Badge({ label }) { return h("span", null, label); }return h(Badge, { label: "hi" });'
|
|
222
|
-
);
|
|
216
|
+
expect(result).toBe('"use strict";function Badge({ label }) { return h("span", null, label); }return h(Badge, { label: "hi" });');
|
|
223
217
|
});
|
|
224
218
|
|
|
225
219
|
it("should compile function with default parameter", () => {
|
|
226
220
|
const ast = parse("function Badge({ label = 'default' }) {\n return <span>{label}</span>\n}\nreturn <Badge />");
|
|
227
221
|
const result = compile(ast);
|
|
228
|
-
expect(result).toBe(
|
|
229
|
-
'"use strict";function Badge({ label = "default" }) { return h("span", null, label); }return h(Badge, null);'
|
|
230
|
-
);
|
|
222
|
+
expect(result).toBe('"use strict";function Badge({ label = "default" }) { return h("span", null, label); }return h(Badge, null);');
|
|
231
223
|
});
|
|
232
224
|
|
|
233
225
|
it("should compile function with const inside body", () => {
|
|
234
|
-
const ast = parse(
|
|
235
|
-
"function Badge({ value }) {\n const doubled = value * 2;\n return <span>{doubled}</span>\n}\nreturn <Badge value={5} />"
|
|
236
|
-
);
|
|
226
|
+
const ast = parse("function Badge({ value }) {\n const doubled = value * 2;\n return <span>{doubled}</span>\n}\nreturn <Badge value={5} />");
|
|
237
227
|
const result = compile(ast);
|
|
238
228
|
expect(result).toContain("const doubled = (value * 2);");
|
|
239
229
|
expect(result).toContain('return h("span", null, doubled);');
|
|
@@ -248,9 +238,7 @@ describe("compile", () => {
|
|
|
248
238
|
});
|
|
249
239
|
|
|
250
240
|
it("should compile multiple function declarations", () => {
|
|
251
|
-
const ast = parse(
|
|
252
|
-
"function A() {\n return <span>a</span>\n}\nfunction B() {\n return <span>b</span>\n}\nreturn <div><A /><B /></div>"
|
|
253
|
-
);
|
|
241
|
+
const ast = parse("function A() {\n return <span>a</span>\n}\nfunction B() {\n return <span>b</span>\n}\nreturn <div><A /><B /></div>");
|
|
254
242
|
const result = compile(ast);
|
|
255
243
|
expect(result).toContain("function A()");
|
|
256
244
|
expect(result).toContain("function B()");
|
|
@@ -259,9 +247,7 @@ describe("compile", () => {
|
|
|
259
247
|
});
|
|
260
248
|
|
|
261
249
|
it("should compile function calling another function", () => {
|
|
262
|
-
const ast = parse(
|
|
263
|
-
"function Inner() {\n return <span>inner</span>\n}\nfunction Outer() {\n return <div><Inner /></div>\n}\nreturn <Outer />"
|
|
264
|
-
);
|
|
250
|
+
const ast = parse("function Inner() {\n return <span>inner</span>\n}\nfunction Outer() {\n return <div><Inner /></div>\n}\nreturn <Outer />");
|
|
265
251
|
const result = compile(ast);
|
|
266
252
|
expect(result).toContain("function Inner()");
|
|
267
253
|
expect(result).toContain("function Outer()");
|
package/src/compile/compile.ts
CHANGED
|
@@ -11,7 +11,6 @@ import type {
|
|
|
11
11
|
Super,
|
|
12
12
|
VariableDeclaration,
|
|
13
13
|
} from "acorn";
|
|
14
|
-
|
|
15
14
|
import {
|
|
16
15
|
isJsxElement,
|
|
17
16
|
isJsxEmptyExpression,
|
|
@@ -40,7 +39,7 @@ export function compile(ast: Program): string {
|
|
|
40
39
|
}
|
|
41
40
|
}
|
|
42
41
|
|
|
43
|
-
const parts: string
|
|
42
|
+
const parts: Array<string> = ['"use strict";'];
|
|
44
43
|
|
|
45
44
|
const hasDeclarations = ast.body.some((s) => s.type === "VariableDeclaration" || s.type === "FunctionDeclaration");
|
|
46
45
|
|
|
@@ -108,13 +107,13 @@ function emitJsxName(nameNode: JSXNameNode, localFunctions: Set<string>): string
|
|
|
108
107
|
return JSON.stringify(JSON.parse(left) + "." + JSON.parse(right));
|
|
109
108
|
}
|
|
110
109
|
default:
|
|
111
|
-
throw CompilationError.fromNode(`Unsupported JSX name: ${(nameNode as
|
|
110
|
+
throw CompilationError.fromNode(`Unsupported JSX name: ${(nameNode as { type: string }).type}`, nameNode);
|
|
112
111
|
}
|
|
113
112
|
}
|
|
114
113
|
|
|
115
|
-
function emitAttributes(attributes:
|
|
116
|
-
const parts: string
|
|
117
|
-
let currentObj: string
|
|
114
|
+
function emitAttributes(attributes: Array<JSXAttribute | JSXSpreadAttribute>, localFunctions: Set<string>): string {
|
|
115
|
+
const parts: Array<string> = [];
|
|
116
|
+
let currentObj: Array<string> = [];
|
|
118
117
|
|
|
119
118
|
const flushCurrent = (): void => {
|
|
120
119
|
if (currentObj.length) {
|
|
@@ -136,7 +135,7 @@ function emitAttributes(attributes: (JSXAttribute | JSXSpreadAttribute)[], local
|
|
|
136
135
|
|
|
137
136
|
const key = attribute.name?.name;
|
|
138
137
|
|
|
139
|
-
let value: string;
|
|
138
|
+
let value: string = "";
|
|
140
139
|
if (!attribute.value) {
|
|
141
140
|
value = "true";
|
|
142
141
|
} else if (attribute.value.type === "Literal") {
|
|
@@ -163,8 +162,8 @@ function emitAttributes(attributes: (JSXAttribute | JSXSpreadAttribute)[], local
|
|
|
163
162
|
return `Object.assign({}, ${parts.join(", ")})`;
|
|
164
163
|
}
|
|
165
164
|
|
|
166
|
-
function emitJsxChildren(children: JSXNode
|
|
167
|
-
const result: string
|
|
165
|
+
function emitJsxChildren(children: Array<JSXNode>, localFunctions: Set<string>): Array<string> {
|
|
166
|
+
const result: Array<string> = [];
|
|
168
167
|
|
|
169
168
|
for (const child of children) {
|
|
170
169
|
if (isJsxText(child)) {
|
|
@@ -195,7 +194,7 @@ function emitExpression(node: CompilableExpression, localFunctions: Set<string>)
|
|
|
195
194
|
case "Identifier":
|
|
196
195
|
return node.name;
|
|
197
196
|
case "TemplateLiteral": {
|
|
198
|
-
const bits: string
|
|
197
|
+
const bits: Array<string> = [];
|
|
199
198
|
node.quasis.forEach((quasi, index) => {
|
|
200
199
|
bits.push(JSON.stringify(quasi.value.cooked ?? ""));
|
|
201
200
|
if (node.expressions.length > index) {
|
|
@@ -210,10 +209,7 @@ function emitExpression(node: CompilableExpression, localFunctions: Set<string>)
|
|
|
210
209
|
return `${node.operator}${emitExpression(node.argument, localFunctions)}`;
|
|
211
210
|
case "BinaryExpression":
|
|
212
211
|
case "LogicalExpression":
|
|
213
|
-
return `(${emitExpression(node.left, localFunctions)} ${node.operator} ${emitExpression(
|
|
214
|
-
node.right,
|
|
215
|
-
localFunctions
|
|
216
|
-
)})`;
|
|
212
|
+
return `(${emitExpression(node.left, localFunctions)} ${node.operator} ${emitExpression(node.right, localFunctions)})`;
|
|
217
213
|
case "ConditionalExpression":
|
|
218
214
|
return `(${emitExpression(node.test, localFunctions)}?${emitExpression(
|
|
219
215
|
node.consequent,
|
|
@@ -245,12 +241,9 @@ function emitExpression(node: CompilableExpression, localFunctions: Set<string>)
|
|
|
245
241
|
}
|
|
246
242
|
case "MemberExpression": {
|
|
247
243
|
const obj = emitExpression(node.object, localFunctions);
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
} else {
|
|
252
|
-
prop = `.${getAstKeyString(node.property as Identifier | Literal)}`;
|
|
253
|
-
}
|
|
244
|
+
const prop: string = node.computed
|
|
245
|
+
? `[${emitExpression(node.property, localFunctions)}]`
|
|
246
|
+
: `.${getAstKeyString(node.property as Identifier | Literal)}`;
|
|
254
247
|
return `${obj}${prop}`;
|
|
255
248
|
}
|
|
256
249
|
case "CallExpression": {
|
|
@@ -266,9 +259,7 @@ function emitExpression(node: CompilableExpression, localFunctions: Set<string>)
|
|
|
266
259
|
return `${callee}(${args})`;
|
|
267
260
|
}
|
|
268
261
|
case "ArrowFunctionExpression": {
|
|
269
|
-
const params = node.params
|
|
270
|
-
.map((param) => emitExpression(param as CompilableExpression, localFunctions))
|
|
271
|
-
.join(", ");
|
|
262
|
+
const params = node.params.map((param) => emitExpression(param as CompilableExpression, localFunctions)).join(", ");
|
|
272
263
|
const body = emitExpression(node.body as CompilableExpression, localFunctions);
|
|
273
264
|
return `(${params}) => ${body}`;
|
|
274
265
|
}
|
|
@@ -327,13 +318,13 @@ function emitParam(param: Pattern, localFunctions: Set<string>): string {
|
|
|
327
318
|
case "Identifier":
|
|
328
319
|
return param.name;
|
|
329
320
|
case "ObjectPattern": {
|
|
330
|
-
const props =
|
|
321
|
+
const props = param.properties.map((p) => {
|
|
331
322
|
if (p.type === "RestElement") {
|
|
332
323
|
return `...${emitParam(p.argument, localFunctions)}`;
|
|
333
324
|
}
|
|
334
325
|
const key = (p.key as Identifier).name;
|
|
335
326
|
if (p.value.type === "AssignmentPattern") {
|
|
336
|
-
return `${key} = ${emitExpression(p.value.right
|
|
327
|
+
return `${key} = ${emitExpression(p.value.right, localFunctions)}`;
|
|
337
328
|
}
|
|
338
329
|
if (p.value !== p.key && p.value.type === "Identifier" && p.value.name !== key) {
|
|
339
330
|
return `${key}: ${emitParam(p.value, localFunctions)}`;
|
|
@@ -343,12 +334,9 @@ function emitParam(param: Pattern, localFunctions: Set<string>): string {
|
|
|
343
334
|
return `{ ${props.join(", ")} }`;
|
|
344
335
|
}
|
|
345
336
|
case "AssignmentPattern":
|
|
346
|
-
return `${emitParam(
|
|
347
|
-
(param as any).right as CompilableExpression,
|
|
348
|
-
localFunctions
|
|
349
|
-
)}`;
|
|
337
|
+
return `${emitParam(param.left, localFunctions)} = ${emitExpression(param.right, localFunctions)}`;
|
|
350
338
|
case "RestElement":
|
|
351
|
-
return `...${emitParam(
|
|
339
|
+
return `...${emitParam(param.argument, localFunctions)}`;
|
|
352
340
|
default:
|
|
353
341
|
throw CompilationError.fromNode(`Unsupported parameter: ${param.type}`, param);
|
|
354
342
|
}
|