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.
Files changed (165) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +368 -0
  3. package/lib/analyze/analysis-error.d.ts.map +1 -0
  4. package/lib/analyze/analysis-report.d.ts +33 -0
  5. package/lib/analyze/analysis-report.d.ts.map +1 -0
  6. package/lib/analyze/analysis-report.js +194 -0
  7. package/lib/analyze/analysis-report.js.map +1 -0
  8. package/lib/analyze/analyze.d.ts.map +1 -0
  9. package/lib/analyze/analyze.test.d.ts.map +1 -0
  10. package/lib/analyze/analyze.test.js +765 -0
  11. package/lib/analyze/analyze.test.js.map +1 -0
  12. package/lib/analyze/data/analyze.d.ts.map +1 -0
  13. package/lib/analyze/data/data-access.d.ts.map +1 -0
  14. package/lib/analyze/data/index.d.ts.map +1 -0
  15. package/lib/analyze/data/method-calls.d.ts.map +1 -0
  16. package/lib/analyze/data/utils.d.ts.map +1 -0
  17. package/lib/analyze/index.d.ts.map +1 -0
  18. package/lib/analyze/jsx/analyze.d.ts.map +1 -0
  19. package/lib/analyze/jsx/element-attributes.d.ts.map +1 -0
  20. package/lib/analyze/jsx/element-attributes.js +89 -0
  21. package/lib/analyze/jsx/element-attributes.js.map +1 -0
  22. package/lib/analyze/jsx/element-children.d.ts.map +1 -0
  23. package/lib/analyze/jsx/element-tags.d.ts.map +1 -0
  24. package/lib/analyze/jsx/index.d.ts.map +1 -0
  25. package/lib/analyze/security/analyze.d.ts.map +1 -0
  26. package/lib/analyze/security/call-expressions.d.ts.map +1 -0
  27. package/lib/analyze/security/declarations.d.ts.map +1 -0
  28. package/lib/analyze/security/expressions.d.ts.map +1 -0
  29. package/lib/analyze/security/identifiers.d.ts.map +1 -0
  30. package/lib/analyze/security/index.d.ts.map +1 -0
  31. package/lib/analyze/security/member-access.d.ts.map +1 -0
  32. package/lib/builtins.d.ts +50 -0
  33. package/lib/builtins.d.ts.map +1 -0
  34. package/lib/builtins.js +1065 -0
  35. package/lib/builtins.js.map +1 -0
  36. package/lib/codegen/builtin-types.d.ts +11 -0
  37. package/lib/codegen/builtin-types.d.ts.map +1 -0
  38. package/lib/codegen/builtin-types.js +151 -0
  39. package/lib/codegen/builtin-types.js.map +1 -0
  40. package/lib/codegen/builtins/builtin-types.d.ts +2 -0
  41. package/lib/codegen/builtins/builtin-types.d.ts.map +1 -0
  42. package/lib/codegen/builtins/builtin-types.js +143 -0
  43. package/lib/codegen/builtins/builtin-types.js.map +1 -0
  44. package/lib/codegen/builtins/index.d.ts +2 -0
  45. package/lib/codegen/builtins/index.d.ts.map +1 -0
  46. package/lib/codegen/builtins/index.js +6 -0
  47. package/lib/codegen/builtins/index.js.map +1 -0
  48. package/lib/codegen/declare-types.d.ts +20 -0
  49. package/lib/codegen/declare-types.d.ts.map +1 -0
  50. package/lib/codegen/declare-types.js +299 -0
  51. package/lib/codegen/declare-types.js.map +1 -0
  52. package/lib/codegen/generate-typescript-definitions.d.ts +31 -0
  53. package/lib/codegen/generate-typescript-definitions.d.ts.map +1 -0
  54. package/lib/codegen/generate-typescript-definitions.js +37 -0
  55. package/lib/codegen/generate-typescript-definitions.js.map +1 -0
  56. package/lib/codegen/generate-typescript-definitions.test.d.ts +2 -0
  57. package/lib/codegen/generate-typescript-definitions.test.d.ts.map +1 -0
  58. package/lib/codegen/generate-typescript-definitions.test.js +587 -0
  59. package/lib/codegen/generate-typescript-definitions.test.js.map +1 -0
  60. package/lib/codegen/index.d.ts +2 -0
  61. package/lib/codegen/index.d.ts.map +1 -0
  62. package/lib/codegen/index.js +6 -0
  63. package/lib/codegen/index.js.map +1 -0
  64. package/lib/codegen/schema/component-types.d.ts +3 -0
  65. package/lib/codegen/schema/component-types.d.ts.map +1 -0
  66. package/lib/codegen/schema/component-types.js +118 -0
  67. package/lib/codegen/schema/component-types.js.map +1 -0
  68. package/lib/codegen/schema/data-types.d.ts +3 -0
  69. package/lib/codegen/schema/data-types.d.ts.map +1 -0
  70. package/lib/codegen/schema/data-types.js +98 -0
  71. package/lib/codegen/schema/data-types.js.map +1 -0
  72. package/lib/codegen/schema/index.d.ts +3 -0
  73. package/lib/codegen/schema/index.d.ts.map +1 -0
  74. package/lib/codegen/schema/index.js +8 -0
  75. package/lib/codegen/schema/index.js.map +1 -0
  76. package/lib/codegen/schema/utils.d.ts +4 -0
  77. package/lib/codegen/schema/utils.d.ts.map +1 -0
  78. package/lib/codegen/schema/utils.js +103 -0
  79. package/lib/codegen/schema/utils.js.map +1 -0
  80. package/lib/codegen/schema-to-types.d.ts +14 -0
  81. package/lib/codegen/schema-to-types.d.ts.map +1 -0
  82. package/lib/codegen/schema-to-types.js +203 -0
  83. package/lib/codegen/schema-to-types.js.map +1 -0
  84. package/lib/compile/compile.d.ts.map +1 -0
  85. package/lib/compile/compile.js +245 -0
  86. package/lib/compile/compile.js.map +1 -0
  87. package/lib/compile/compile.test.d.ts.map +1 -0
  88. package/lib/compile/compile.test.js +148 -0
  89. package/lib/compile/compile.test.js.map +1 -0
  90. package/lib/evaluate/evaluate.d.ts +26 -0
  91. package/lib/evaluate/evaluate.d.ts.map +1 -0
  92. package/lib/evaluate/evaluate.js +40 -0
  93. package/lib/evaluate/evaluation-error.d.ts +4 -0
  94. package/lib/evaluate/evaluation-error.d.ts.map +1 -0
  95. package/lib/evaluate/evaluation-error.js +6 -0
  96. package/lib/index.d.ts +8 -0
  97. package/lib/index.d.ts.map +1 -0
  98. package/lib/index.js +16 -0
  99. package/lib/index.js.map +1 -0
  100. package/lib/render.d.ts +58 -0
  101. package/lib/render.d.ts.map +1 -0
  102. package/lib/render.js +52 -0
  103. package/lib/render.test.d.ts.map +1 -0
  104. package/lib/schema.d.ts +68 -0
  105. package/lib/schema.d.ts.map +1 -0
  106. package/lib/schema.js +106 -0
  107. package/lib/validate.d.ts +41 -0
  108. package/lib/validate.d.ts.map +1 -0
  109. package/lib/validate.js +42 -0
  110. package/package.json +27 -0
  111. package/src/analyze/analysis-error.ts +26 -0
  112. package/src/analyze/analysis-report.ts +228 -0
  113. package/src/analyze/analyze.test.ts +829 -0
  114. package/src/analyze/analyze.ts +10 -0
  115. package/src/analyze/data/analyze.ts +15 -0
  116. package/src/analyze/data/data-access.ts +349 -0
  117. package/src/analyze/data/index.ts +1 -0
  118. package/src/analyze/data/method-calls.ts +96 -0
  119. package/src/analyze/data/utils.ts +329 -0
  120. package/src/analyze/index.ts +3 -0
  121. package/src/analyze/jsx/analyze.ts +17 -0
  122. package/src/analyze/jsx/element-attributes.ts +143 -0
  123. package/src/analyze/jsx/element-children.ts +97 -0
  124. package/src/analyze/jsx/element-tags.ts +55 -0
  125. package/src/analyze/jsx/index.ts +1 -0
  126. package/src/analyze/security/analyze.ts +21 -0
  127. package/src/analyze/security/call-expressions.ts +41 -0
  128. package/src/analyze/security/declarations.ts +67 -0
  129. package/src/analyze/security/expressions.ts +100 -0
  130. package/src/analyze/security/identifiers.ts +113 -0
  131. package/src/analyze/security/index.ts +1 -0
  132. package/src/analyze/security/member-access.ts +35 -0
  133. package/src/analyze/utils.ts +118 -0
  134. package/src/analyze/validation-context.ts +32 -0
  135. package/src/builtins.ts +1149 -0
  136. package/src/codegen/builtins/builtin-types.ts +174 -0
  137. package/src/codegen/builtins/index.ts +1 -0
  138. package/src/codegen/generate-typescript-definitions.test.ts +676 -0
  139. package/src/codegen/generate-typescript-definitions.ts +35 -0
  140. package/src/codegen/index.ts +1 -0
  141. package/src/codegen/schema/component-types.ts +140 -0
  142. package/src/codegen/schema/data-types.ts +109 -0
  143. package/src/codegen/schema/index.ts +2 -0
  144. package/src/codegen/schema/utils.ts +111 -0
  145. package/src/compile/compilation-error.ts +37 -0
  146. package/src/compile/compile.test.ts +180 -0
  147. package/src/compile/compile.ts +282 -0
  148. package/src/compile/index.ts +2 -0
  149. package/src/evaluate/evaluate.test.ts +316 -0
  150. package/src/evaluate/evaluate.ts +81 -0
  151. package/src/evaluate/evaluation-error.ts +6 -0
  152. package/src/evaluate/index.ts +2 -0
  153. package/src/index.ts +10 -0
  154. package/src/jsx.ts +161 -0
  155. package/src/parse/index.ts +2 -0
  156. package/src/parse/parse-error.ts +14 -0
  157. package/src/parse/parse.test.ts +99 -0
  158. package/src/parse/parse.ts +40 -0
  159. package/src/render.test.ts +425 -0
  160. package/src/render.ts +75 -0
  161. package/src/schema.ts +195 -0
  162. package/src/traverse.test.ts +104 -0
  163. package/src/traverse.ts +78 -0
  164. package/src/types.ts +17 -0
  165. 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,2 @@
1
+ export { declareComponentTypes } from "./component-types.js";
2
+ export { declareDataTypes } from "./data-types.js";
@@ -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
+ });