@mmapp/react-compiler 0.1.0-alpha.1 → 0.1.0-alpha.5

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 (265) hide show
  1. package/ATOM-PIPELINE.md +144 -0
  2. package/README.md +88 -40
  3. package/dist/babel/index.js +2814 -277
  4. package/dist/babel/index.mjs +2 -2
  5. package/dist/chunk-3USIFFE4.mjs +2190 -0
  6. package/dist/chunk-45YMGEVT.mjs +186 -0
  7. package/dist/chunk-4FN2AISW.mjs +148 -0
  8. package/dist/chunk-4OPI5L7G.mjs +2593 -0
  9. package/dist/chunk-4RYTKOOJ.mjs +186 -0
  10. package/dist/chunk-52XHYD2V.mjs +214 -0
  11. package/dist/chunk-5GUFFFGL.mjs +148 -0
  12. package/dist/chunk-5RKTOVR5.mjs +244 -0
  13. package/dist/chunk-5YDMOO4X.mjs +214 -0
  14. package/dist/chunk-64ZWEMLJ.mjs +148 -0
  15. package/dist/chunk-6XP4KSWQ.mjs +2190 -0
  16. package/dist/chunk-72QWL54I.mjs +175 -0
  17. package/dist/chunk-7B4TRI7C.mjs +4835 -0
  18. package/dist/chunk-7ZKGHTNB.mjs +4952 -0
  19. package/dist/chunk-CIESM3BP.mjs +33 -0
  20. package/dist/chunk-DE3ZGQAC.mjs +148 -0
  21. package/dist/chunk-DMCY3BBG.mjs +1933 -0
  22. package/dist/chunk-DPIK3PJS.mjs +244 -0
  23. package/dist/chunk-E5IVH4RE.mjs +186 -0
  24. package/dist/chunk-E6FZNUR5.mjs +4953 -0
  25. package/dist/chunk-EJRBDQDP.mjs +2607 -0
  26. package/dist/chunk-ELO4TXJL.mjs +186 -0
  27. package/dist/chunk-EO6SYNCG.mjs +175 -0
  28. package/dist/chunk-FKRO52XH.mjs +3446 -0
  29. package/dist/chunk-FL4YAKU6.mjs +4941 -0
  30. package/dist/chunk-FYT47UBU.mjs +5076 -0
  31. package/dist/chunk-GCLGPOJZ.mjs +148 -0
  32. package/dist/chunk-GXB4JOP7.mjs +5072 -0
  33. package/dist/chunk-HFXOUMTD.mjs +175 -0
  34. package/dist/chunk-HWIZ47US.mjs +214 -0
  35. package/dist/chunk-IB7MNPQL.mjs +4953 -0
  36. package/dist/chunk-ICSIHQCG.mjs +148 -0
  37. package/dist/chunk-J7JUAHS4.mjs +186 -0
  38. package/dist/chunk-JLA5VNQ3.mjs +186 -0
  39. package/dist/chunk-JQLWFCTM.mjs +214 -0
  40. package/dist/chunk-KFJJCQAL.mjs +148 -0
  41. package/dist/chunk-KJUIIEQE.mjs +186 -0
  42. package/dist/chunk-KNWTHRVQ.mjs +175 -0
  43. package/dist/chunk-KSG4XSZF.mjs +175 -0
  44. package/dist/chunk-LF5N6DOU.mjs +175 -0
  45. package/dist/chunk-LJQCM2IM.mjs +214 -0
  46. package/dist/chunk-NTB7OEX2.mjs +2918 -0
  47. package/dist/chunk-NW6555WJ.mjs +186 -0
  48. package/dist/chunk-OMZE6VLQ.mjs +214 -0
  49. package/dist/chunk-OPJKP747.mjs +7506 -0
  50. package/dist/chunk-P4BR7WVO.mjs +2190 -0
  51. package/dist/chunk-QQHVYH2X.mjs +244 -0
  52. package/dist/chunk-S5QLWLLT.mjs +186 -0
  53. package/dist/chunk-SCWGT2FY.mjs +2190 -0
  54. package/dist/chunk-SMKJUSB3.mjs +2190 -0
  55. package/dist/chunk-THFYE5ZX.mjs +244 -0
  56. package/dist/chunk-VCAY2KGM.mjs +175 -0
  57. package/dist/chunk-WBYMW4NQ.mjs +3450 -0
  58. package/dist/chunk-WECAV6QB.mjs +148 -0
  59. package/dist/chunk-WMKBXUCE.mjs +3228 -0
  60. package/dist/chunk-XAJ5BKKL.mjs +4947 -0
  61. package/dist/chunk-XG2X7AEA.mjs +175 -0
  62. package/dist/chunk-XG7Z23NQ.mjs +148 -0
  63. package/dist/chunk-XWZAOCQ7.mjs +2607 -0
  64. package/dist/chunk-Y6MA7ULW.mjs +148 -0
  65. package/dist/chunk-YMS7Q7LG.mjs +214 -0
  66. package/dist/chunk-ZA37XTGA.mjs +175 -0
  67. package/dist/cli/index.js +13189 -6838
  68. package/dist/cli/index.mjs +140 -22
  69. package/dist/codemod/cli.mjs +1 -1
  70. package/dist/codemod/index.mjs +1 -1
  71. package/dist/config-PL24KEWL.mjs +219 -0
  72. package/dist/dev-server-RmGHIntF.d.mts +113 -0
  73. package/dist/dev-server-RmGHIntF.d.ts +113 -0
  74. package/dist/dev-server.d.mts +1 -1
  75. package/dist/dev-server.d.ts +1 -1
  76. package/dist/dev-server.js +4135 -440
  77. package/dist/dev-server.mjs +5 -5
  78. package/dist/envelope.js +2812 -275
  79. package/dist/envelope.mjs +3 -3
  80. package/dist/index.d.mts +161 -2
  81. package/dist/index.d.ts +161 -2
  82. package/dist/index.js +4429 -428
  83. package/dist/index.mjs +217 -9
  84. package/{src/cli/init.ts → dist/init-7JQMAAXS.mjs} +70 -95
  85. package/dist/init-DQDX3QK6.mjs +369 -0
  86. package/dist/init-EHO4VQ22.mjs +369 -0
  87. package/dist/init-UC3FWPIW.mjs +367 -0
  88. package/dist/init-UNSMVKIK.mjs +366 -0
  89. package/dist/init-UNV5XIDE.mjs +367 -0
  90. package/dist/project-compiler-2P4N4DR7.mjs +10 -0
  91. package/dist/project-compiler-D2LCC27O.mjs +10 -0
  92. package/dist/project-compiler-EJ3GANJE.mjs +10 -0
  93. package/dist/project-compiler-LOQKVRZJ.mjs +10 -0
  94. package/dist/project-compiler-OP2VVGJQ.mjs +10 -0
  95. package/dist/project-compiler-RQ6OQKRM.mjs +10 -0
  96. package/dist/project-compiler-VWNNCHGO.mjs +10 -0
  97. package/dist/project-compiler-XVAAU4C5.mjs +10 -0
  98. package/dist/project-compiler-YES5FGMD.mjs +10 -0
  99. package/dist/project-compiler-ZKMQDLGU.mjs +10 -0
  100. package/dist/project-decompiler-FLXCEJHS.mjs +7 -0
  101. package/dist/project-decompiler-US7GAVIC.mjs +7 -0
  102. package/dist/project-decompiler-VLPR22QF.mjs +7 -0
  103. package/dist/pull-FUS5QYZS.mjs +109 -0
  104. package/dist/pull-LD5ENLGY.mjs +109 -0
  105. package/dist/pull-P44LDRWB.mjs +109 -0
  106. package/dist/testing/index.js +2822 -285
  107. package/dist/testing/index.mjs +2 -2
  108. package/dist/verify-SEIXUGN4.mjs +1833 -0
  109. package/dist/vite/index.js +2815 -278
  110. package/dist/vite/index.mjs +3 -3
  111. package/examples/uber-app/app/admin/fleet.tsx +19 -19
  112. package/package.json +16 -6
  113. package/compile-blueprint-chat.mjs +0 -99
  114. package/compile-blueprint-glass-console.mjs +0 -98
  115. package/compile-chat-defs.mjs +0 -92
  116. package/examples/uber-app/tests/payment.test.tsx +0 -129
  117. package/examples/uber-app/tests/ride-flow.test.tsx +0 -123
  118. package/package.json.backup +0 -86
  119. package/scripts/decompile.ts +0 -226
  120. package/scripts/seed-auth.ts +0 -267
  121. package/scripts/seed-uber.ts +0 -248
  122. package/scripts/validate-uber.ts +0 -119
  123. package/seed-blueprint-chat.mjs +0 -444
  124. package/seed-blueprint-glass-console.mjs +0 -445
  125. package/seed-compiled.mjs +0 -318
  126. package/src/RoundTripValidator.ts +0 -400
  127. package/src/__tests__/atom-rendering-coverage.test.ts +0 -680
  128. package/src/__tests__/auth-module-compilation.test.ts +0 -247
  129. package/src/__tests__/auth-template-compilation.test.ts +0 -589
  130. package/src/__tests__/change-extractor.test.ts +0 -142
  131. package/src/__tests__/cli-pull.test.ts +0 -73
  132. package/src/__tests__/cli-test.test.ts +0 -72
  133. package/src/__tests__/component-extractor.test.ts +0 -331
  134. package/src/__tests__/context-extractor.test.ts +0 -145
  135. package/src/__tests__/decompiler.test.ts +0 -718
  136. package/src/__tests__/define-blueprint.test.ts +0 -133
  137. package/src/__tests__/definition-validator.test.ts +0 -519
  138. package/src/__tests__/during-extractor.test.ts +0 -152
  139. package/src/__tests__/effect-extractor.test.ts +0 -107
  140. package/src/__tests__/event-emission.test.ts +0 -127
  141. package/src/__tests__/examples.test.ts +0 -236
  142. package/src/__tests__/full-blueprint-coverage.test.ts +0 -1221
  143. package/src/__tests__/golden-suite.test.ts +0 -403
  144. package/src/__tests__/grammar-island-extractor.test.ts +0 -289
  145. package/src/__tests__/instance-key.test.ts +0 -82
  146. package/src/__tests__/ir-migration.test.ts +0 -255
  147. package/src/__tests__/lock-file.test.ts +0 -117
  148. package/src/__tests__/model-extractor.test.ts +0 -195
  149. package/src/__tests__/model-field-acl.test.ts +0 -237
  150. package/src/__tests__/model-hooks.test.ts +0 -130
  151. package/src/__tests__/model-ref-resolution.test.ts +0 -268
  152. package/src/__tests__/model-roundtrip.test.ts +0 -502
  153. package/src/__tests__/model-runtime.test.ts +0 -112
  154. package/src/__tests__/model-transitions.test.ts +0 -183
  155. package/src/__tests__/nrt-action-trace.test.ts +0 -391
  156. package/src/__tests__/pipeline-hardening.test.ts +0 -413
  157. package/src/__tests__/project-compiler.test.ts +0 -546
  158. package/src/__tests__/project-decompiler.test.ts +0 -343
  159. package/src/__tests__/query-compilation.test.ts +0 -145
  160. package/src/__tests__/round-trip/PLAN.md +0 -158
  161. package/src/__tests__/round-trip/README.md +0 -52
  162. package/src/__tests__/round-trip/RESULTS.md +0 -86
  163. package/src/__tests__/round-trip/fixtures/data-heavy/main.workflow.tsx +0 -55
  164. package/src/__tests__/round-trip/fixtures/data-heavy/mm.config.ts +0 -11
  165. package/src/__tests__/round-trip/fixtures/data-heavy/models/contact.ts +0 -54
  166. package/src/__tests__/round-trip/fixtures/full-workflow/main.workflow.tsx +0 -79
  167. package/src/__tests__/round-trip/fixtures/full-workflow/mm.config.ts +0 -12
  168. package/src/__tests__/round-trip/fixtures/full-workflow/models/order.ts +0 -50
  169. package/src/__tests__/round-trip/fixtures/simple-crud/main.workflow.tsx +0 -25
  170. package/src/__tests__/round-trip/fixtures/simple-crud/mm.config.ts +0 -11
  171. package/src/__tests__/round-trip/fixtures/simple-crud/models/task.ts +0 -32
  172. package/src/__tests__/round-trip/fixtures/view-heavy/main.workflow.tsx +0 -79
  173. package/src/__tests__/round-trip/fixtures/view-heavy/mm.config.ts +0 -10
  174. package/src/__tests__/round-trip/round-trip.test.ts +0 -2598
  175. package/src/__tests__/round-trip-ir.test.ts +0 -300
  176. package/src/__tests__/round-trip.test.ts +0 -1212
  177. package/src/__tests__/route-merging.test.ts +0 -372
  178. package/src/__tests__/router-composition.test.ts +0 -489
  179. package/src/__tests__/router-extractor.test.ts +0 -176
  180. package/src/__tests__/server-action-extractor.test.ts +0 -128
  181. package/src/__tests__/smart-type-inference.test.ts +0 -365
  182. package/src/__tests__/source-envelope.test.ts +0 -284
  183. package/src/__tests__/source-fidelity.test.ts +0 -516
  184. package/src/__tests__/state-extractor.test.ts +0 -115
  185. package/src/__tests__/strict-mode.test.ts +0 -227
  186. package/src/__tests__/transition-effect-extractor.test.ts +0 -119
  187. package/src/__tests__/transition-extractor.test.ts +0 -68
  188. package/src/__tests__/ts-to-expression.test.ts +0 -462
  189. package/src/__tests__/type-generator.test.ts +0 -201
  190. package/src/__tests__/uber-validation.test.ts +0 -502
  191. package/src/action-compiler.ts +0 -361
  192. package/src/babel/emitters/experience-transform.ts +0 -199
  193. package/src/babel/emitters/ir-to-tsx-emitter.ts +0 -110
  194. package/src/babel/emitters/pure-form-emitter.ts +0 -1023
  195. package/src/babel/emitters/runtime-glue-emitter.ts +0 -39
  196. package/src/babel/extractors/change-extractor.ts +0 -199
  197. package/src/babel/extractors/component-extractor.ts +0 -907
  198. package/src/babel/extractors/computed-extractor.ts +0 -262
  199. package/src/babel/extractors/context-extractor.ts +0 -277
  200. package/src/babel/extractors/during-extractor.ts +0 -295
  201. package/src/babel/extractors/effect-extractor.ts +0 -340
  202. package/src/babel/extractors/event-extractor.ts +0 -235
  203. package/src/babel/extractors/grammar-island-extractor.ts +0 -302
  204. package/src/babel/extractors/model-extractor.ts +0 -1018
  205. package/src/babel/extractors/router-extractor.ts +0 -303
  206. package/src/babel/extractors/server-action-extractor.ts +0 -173
  207. package/src/babel/extractors/server-action-hook-extractor.ts +0 -72
  208. package/src/babel/extractors/server-state-extractor.ts +0 -88
  209. package/src/babel/extractors/state-extractor.ts +0 -214
  210. package/src/babel/extractors/transition-effect-extractor.ts +0 -176
  211. package/src/babel/extractors/transition-extractor.ts +0 -143
  212. package/src/babel/index.ts +0 -24
  213. package/src/babel/transpilers/ts-to-expression.ts +0 -674
  214. package/src/babel/visitor.ts +0 -807
  215. package/src/cli/auth.ts +0 -255
  216. package/src/cli/build.ts +0 -288
  217. package/src/cli/deploy.ts +0 -206
  218. package/src/cli/index.ts +0 -328
  219. package/src/cli/installer.ts +0 -261
  220. package/src/cli/lock-file.ts +0 -94
  221. package/src/cli/mmrc.ts +0 -22
  222. package/src/cli/pull.ts +0 -172
  223. package/src/cli/registry-client.ts +0 -175
  224. package/src/cli/test.ts +0 -397
  225. package/src/cli/type-generator.ts +0 -243
  226. package/src/codemod/__tests__/forward.test.ts +0 -239
  227. package/src/codemod/__tests__/reverse.test.ts +0 -145
  228. package/src/codemod/__tests__/round-trip.test.ts +0 -137
  229. package/src/codemod/annotation.ts +0 -97
  230. package/src/codemod/classify.ts +0 -197
  231. package/src/codemod/cli.ts +0 -207
  232. package/src/codemod/control-flow.ts +0 -409
  233. package/src/codemod/forward.ts +0 -244
  234. package/src/codemod/import-manager.ts +0 -171
  235. package/src/codemod/index.ts +0 -120
  236. package/src/codemod/reverse.ts +0 -197
  237. package/src/codemod/rules.ts +0 -174
  238. package/src/codemod/state-transform.ts +0 -126
  239. package/src/decompiler/ast-builder.ts +0 -538
  240. package/src/decompiler/config-generator.ts +0 -151
  241. package/src/decompiler/index.ts +0 -315
  242. package/src/decompiler/project-decompiler.ts +0 -1776
  243. package/src/decompiler/project.ts +0 -862
  244. package/src/decompiler/split-strategy.ts +0 -140
  245. package/src/decompiler/state-emitter.ts +0 -1053
  246. package/src/decompiler/sx-emitter.ts +0 -318
  247. package/src/decompiler/workspace-hydrator.ts +0 -189
  248. package/src/dev-server.ts +0 -238
  249. package/src/envelope/fs-tree.ts +0 -217
  250. package/src/envelope/source-envelope.ts +0 -264
  251. package/src/envelope.ts +0 -315
  252. package/src/incremental-compiler.ts +0 -401
  253. package/src/index.ts +0 -99
  254. package/src/model-compiler.ts +0 -277
  255. package/src/project-compiler.ts +0 -1629
  256. package/src/route-extractor.ts +0 -333
  257. package/src/testing/index.ts +0 -32
  258. package/src/testing/snapshot.ts +0 -252
  259. package/src/testing/test-utils.ts +0 -226
  260. package/src/types.ts +0 -68
  261. package/src/vite/index.ts +0 -288
  262. package/test-compile.mjs +0 -142
  263. package/tsconfig.json +0 -25
  264. package/tsup.config.ts +0 -23
  265. package/vitest.config.ts +0 -9
@@ -1,907 +0,0 @@
1
- /**
2
- * Component Extractor — extracts JSX return value into IRExperienceNode tree.
3
- *
4
- * Handles:
5
- * - JSX elements with identifier and member expression names
6
- * - Fragments (anonymous containers)
7
- * - Text content as Text nodes
8
- * - Expression children ({count}, {status})
9
- * - Conditional rendering ({show && <X/>}, {cond ? <A/> : <B/>})
10
- * - List rendering ({items.map(i => <Item/>)})
11
- * - Spread attributes
12
- * - visible_when, data-slot special attributes
13
- */
14
-
15
- import type { NodePath } from '@babel/traverse';
16
- import * as t from '@babel/types';
17
- import type { IRExperienceNode, IRFieldDefinition } from '@mindmatrix/player-core';
18
- import type { CompilerState } from '../../types';
19
- import { transpileExpression } from '../transpilers/ts-to-expression';
20
-
21
- /**
22
- * Per-compilation node ID counter. Reset via resetNodeIdCounter().
23
- */
24
- let nodeIdCounter = 0;
25
-
26
- /**
27
- * Maps camelCase useState field names → snake_case names for $local bindings.
28
- * e.g. "sidebarOpen" → "sidebar_open"
29
- */
30
- let localFieldMap: Map<string, string> = new Map();
31
-
32
- /**
33
- * Maps setter function names → snake_case field names.
34
- * e.g. "setSidebarOpen" → "sidebar_open"
35
- */
36
- let setterToFieldMap: Map<string, string> = new Map();
37
-
38
- /**
39
- * Maps derived/computed variable names → their AST expressions.
40
- * e.g. "hasContent" → BinaryExpression(text.trim().length > 0 || files.length > 0)
41
- * When generateExpression encounters these, it inlines the expression
42
- * (recursively resolving $local references).
43
- */
44
- let derivedVarMap: Map<string, t.Expression> = new Map();
45
-
46
- /**
47
- * Converts camelCase to snake_case.
48
- */
49
- function toSnakeCase(str: string): string {
50
- return str.replace(/([A-Z])/g, '_$1').toLowerCase().replace(/^_/, '');
51
- }
52
-
53
- /**
54
- * Sentinel value for tryStaticEval failure (distinguishes from `undefined` as a valid result).
55
- */
56
- const STATIC_EVAL_FAIL = Symbol('STATIC_EVAL_FAIL');
57
-
58
- /**
59
- * Attempts to statically evaluate an AST expression into a plain JS value.
60
- * Returns the value for pure literals (arrays, objects, strings, numbers, booleans, null, undefined).
61
- * Returns STATIC_EVAL_FAIL if the expression contains dynamic references.
62
- */
63
- function tryStaticEval(node: t.Expression): unknown {
64
- if (t.isStringLiteral(node)) return node.value;
65
- if (t.isNumericLiteral(node)) return node.value;
66
- if (t.isBooleanLiteral(node)) return node.value;
67
- if (t.isNullLiteral(node)) return null;
68
- if (t.isIdentifier(node) && node.name === 'undefined') return undefined;
69
-
70
- if (t.isUnaryExpression(node) && node.operator === '-' && t.isNumericLiteral(node.argument)) {
71
- return -node.argument.value;
72
- }
73
-
74
- if (t.isArrayExpression(node)) {
75
- const result: unknown[] = [];
76
- for (const el of node.elements) {
77
- if (el === null) { result.push(null); continue; }
78
- if (t.isSpreadElement(el)) return STATIC_EVAL_FAIL;
79
- const val = tryStaticEval(el);
80
- if (val === STATIC_EVAL_FAIL) return STATIC_EVAL_FAIL;
81
- result.push(val);
82
- }
83
- return result;
84
- }
85
-
86
- if (t.isObjectExpression(node)) {
87
- const result: Record<string, unknown> = {};
88
- for (const prop of node.properties) {
89
- if (t.isSpreadElement(prop) || t.isObjectMethod(prop)) return STATIC_EVAL_FAIL;
90
- if (!t.isObjectProperty(prop)) return STATIC_EVAL_FAIL;
91
- let key: string;
92
- if (t.isIdentifier(prop.key)) key = prop.key.name;
93
- else if (t.isStringLiteral(prop.key)) key = prop.key.value;
94
- else if (t.isNumericLiteral(prop.key)) key = String(prop.key.value);
95
- else return STATIC_EVAL_FAIL;
96
- if (!t.isExpression(prop.value)) return STATIC_EVAL_FAIL;
97
- const val = tryStaticEval(prop.value);
98
- if (val === STATIC_EVAL_FAIL) return STATIC_EVAL_FAIL;
99
- result[key] = val;
100
- }
101
- return result;
102
- }
103
-
104
- // Template literal with no expressions: `hello world`
105
- if (t.isTemplateLiteral(node) && node.expressions.length === 0) {
106
- return node.quasis[0].value.cooked ?? node.quasis[0].value.raw;
107
- }
108
-
109
- return STATIC_EVAL_FAIL;
110
- }
111
-
112
- /**
113
- * Initializes field maps from extracted useState fields.
114
- * Must be called before JSX extraction.
115
- */
116
- function initLocalFieldMaps(fields: IRFieldDefinition[]): void {
117
- localFieldMap.clear();
118
- setterToFieldMap.clear();
119
- for (const field of fields) {
120
- const camelName = field.name;
121
- const snakeName = toSnakeCase(camelName);
122
- localFieldMap.set(camelName, snakeName);
123
- // Build setter name: "sidebarOpen" → "setSidebarOpen"
124
- const setterName = 'set' + camelName.charAt(0).toUpperCase() + camelName.slice(1);
125
- setterToFieldMap.set(setterName, snakeName);
126
- }
127
- }
128
-
129
- /**
130
- * Register a derived variable (const declaration) for inline expansion.
131
- * When the variable is referenced in JSX expressions, its initializer
132
- * expression is inlined with $local substitutions applied.
133
- */
134
- export function registerDerivedVar(name: string, init: t.Expression): void {
135
- derivedVarMap.set(name, init);
136
- }
137
-
138
- /**
139
- * Resets the node ID counter. MUST be called between file compilations.
140
- */
141
- export function resetNodeIdCounter(): void {
142
- nodeIdCounter = 0;
143
- derivedVarMap.clear();
144
- }
145
-
146
- /**
147
- * Converts JSX element to IRExperienceNode.
148
- */
149
- function jsxToExperienceNode(node: t.JSXElement | t.JSXFragment): IRExperienceNode {
150
- if (t.isJSXFragment(node)) {
151
- return {
152
- id: `fragment_${++nodeIdCounter}`,
153
- children: extractChildren(node.children),
154
- };
155
- }
156
-
157
- const element = node.openingElement;
158
- const componentName = resolveComponentName(element.name);
159
- const id = generateNodeId(componentName);
160
- const config: Record<string, unknown> = {};
161
- const bindings: Record<string, string> = {};
162
- let visibleWhen: string | undefined;
163
- let layout: string | undefined;
164
- let slot: string | undefined;
165
- let className: string | undefined;
166
- let displayName: string | undefined;
167
-
168
- // Infer layout from component type
169
- const layoutMap: Record<string, string> = {
170
- Stack: 'stack', Row: 'row', Grid: 'grid', Tabs: 'tabs', Column: 'column',
171
- };
172
- if (layoutMap[componentName]) {
173
- layout = layoutMap[componentName];
174
- }
175
-
176
- // Extract attributes
177
- for (const attr of element.attributes) {
178
- if (t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name)) {
179
- const attrName = attr.name.name;
180
- const attrValue = attr.value;
181
-
182
- if (attrName === 'visible_when' && t.isStringLiteral(attrValue)) {
183
- visibleWhen = attrValue.value;
184
- } else if (attrName === 'data-slot' && t.isStringLiteral(attrValue)) {
185
- slot = attrValue.value;
186
- } else if (attrName === 'className' && t.isStringLiteral(attrValue)) {
187
- className = attrValue.value;
188
- } else if (attrName === 'displayName' && t.isStringLiteral(attrValue)) {
189
- displayName = attrValue.value;
190
- } else if (t.isJSXExpressionContainer(attrValue)) {
191
- const expr = attrValue.expression;
192
- if (t.isJSXEmptyExpression(expr)) continue;
193
- // Literals go to config, dynamic expressions go to bindings
194
- if (t.isNumericLiteral(expr)) {
195
- config[attrName] = expr.value;
196
- } else if (t.isBooleanLiteral(expr)) {
197
- config[attrName] = expr.value;
198
- } else if (t.isStringLiteral(expr)) {
199
- // String literals that look like binding expressions (containing $instance,
200
- // $local, or function calls) should be stored as bindings for round-trip
201
- // stability. Plain strings go to config.
202
- if (expr.value.includes('$instance') || expr.value.includes('$local') || /^[a-zA-Z_]+\(/.test(expr.value)) {
203
- bindings[attrName] = expr.value;
204
- } else {
205
- config[attrName] = expr.value;
206
- }
207
- } else if (t.isIdentifier(expr)) {
208
- const snakeName = localFieldMap.get(expr.name);
209
- bindings[attrName] = snakeName ? `$local.${snakeName}` : `$instance.${expr.name}`;
210
- } else if (
211
- isEventHandlerProp(attrName) &&
212
- (t.isArrowFunctionExpression(expr) || t.isFunctionExpression(expr))
213
- ) {
214
- // Decompose event handlers into action sequences
215
- const decomposed = decomposeHandlerToSeq(expr);
216
- if (decomposed) {
217
- bindings[attrName] = decomposed;
218
- } else {
219
- // Fallback: wrap raw JS in $expr() so IR consumers can identify
220
- // opaque JavaScript expressions that don't map to mm-compute syntax.
221
- // The binding resolver strips $expr() at evaluation time.
222
- bindings[attrName] = `$expr(${generateExpression(expr)})`;
223
- }
224
- } else if (t.isExpression(expr)) {
225
- // Try to statically evaluate pure-literal expressions (arrays/objects of primitives)
226
- // into config values instead of putting them in bindings as strings
227
- const staticVal = tryStaticEval(expr);
228
- if (staticVal !== STATIC_EVAL_FAIL) {
229
- config[attrName] = staticVal;
230
- } else {
231
- bindings[attrName] = generateExpression(expr);
232
- }
233
- }
234
- } else if (t.isStringLiteral(attrValue)) {
235
- config[attrName] = attrValue.value;
236
- } else if (attrValue === null) {
237
- // Boolean attribute: <Button disabled />
238
- config[attrName] = true;
239
- }
240
- } else if (t.isJSXSpreadAttribute(attr)) {
241
- // Spread attributes: {...props} → store the identifier in bindings
242
- if (t.isIdentifier(attr.argument)) {
243
- bindings['...' + attr.argument.name] = `$instance.${attr.argument.name}`;
244
- }
245
- }
246
- }
247
-
248
- const children = extractChildren(node.children);
249
-
250
- const experienceNode: IRExperienceNode = {
251
- id,
252
- ...(displayName && { displayName }),
253
- component: componentName,
254
- ...(slot && { slot }),
255
- ...(layout && { layout }),
256
- ...(className && { className }),
257
- ...(Object.keys(bindings).length > 0 && { bindings }),
258
- ...(Object.keys(config).length > 0 && { config }),
259
- ...(visibleWhen && { visible_when: visibleWhen }),
260
- ...(children.length > 0 && { children }),
261
- };
262
-
263
- return experienceNode;
264
- }
265
-
266
- /**
267
- * Resolves component name from JSX element name node.
268
- * Handles JSXIdentifier and JSXMemberExpression.
269
- */
270
- function resolveComponentName(name: t.JSXIdentifier | t.JSXMemberExpression | t.JSXNamespacedName): string {
271
- if (t.isJSXIdentifier(name)) return name.name;
272
- if (t.isJSXMemberExpression(name)) {
273
- const obj = resolveComponentName(name.object);
274
- return `${obj}.${name.property.name}`;
275
- }
276
- if (t.isJSXNamespacedName(name)) {
277
- return `${name.namespace.name}:${name.name.name}`;
278
- }
279
- return 'div';
280
- }
281
-
282
- /**
283
- * Extracts children from JSX, handling text, expressions, conditionals, and lists.
284
- */
285
- function extractChildren(
286
- children: Array<t.JSXText | t.JSXExpressionContainer | t.JSXElement | t.JSXFragment | t.JSXSpreadChild>
287
- ): IRExperienceNode[] {
288
- const nodes: IRExperienceNode[] = [];
289
-
290
- for (const child of children) {
291
- if (t.isJSXElement(child)) {
292
- nodes.push(jsxToExperienceNode(child));
293
- } else if (t.isJSXFragment(child)) {
294
- nodes.push(jsxToExperienceNode(child));
295
- } else if (t.isJSXText(child)) {
296
- const text = child.value.trim();
297
- if (text) {
298
- nodes.push({
299
- id: `text_${++nodeIdCounter}`,
300
- component: 'Text',
301
- config: { value: text },
302
- });
303
- }
304
- } else if (t.isJSXExpressionContainer(child)) {
305
- const expr = child.expression;
306
- if (t.isJSXEmptyExpression(expr)) continue;
307
-
308
- // Conditional: {cond ? <A/> : <B/>}
309
- if (t.isConditionalExpression(expr)) {
310
- const condExpr = generateExpression(expr.test);
311
- if (t.isJSXElement(expr.consequent) || t.isJSXFragment(expr.consequent)) {
312
- const consequent = jsxToExperienceNode(expr.consequent as t.JSXElement | t.JSXFragment);
313
- consequent.visible_when = condExpr;
314
- nodes.push(consequent);
315
- }
316
- if (t.isJSXElement(expr.alternate) || t.isJSXFragment(expr.alternate)) {
317
- const alternate = jsxToExperienceNode(expr.alternate as t.JSXElement | t.JSXFragment);
318
- alternate.visible_when = `not(${condExpr})`;
319
- nodes.push(alternate);
320
- }
321
- }
322
- // Logical AND: {show && <Component/>}
323
- else if (t.isLogicalExpression(expr) && expr.operator === '&&') {
324
- const condExpr = generateExpression(expr.left);
325
- if (t.isJSXElement(expr.right) || t.isJSXFragment(expr.right)) {
326
- const element = jsxToExperienceNode(expr.right as t.JSXElement | t.JSXFragment);
327
- element.visible_when = condExpr;
328
- nodes.push(element);
329
- }
330
- }
331
- // List: {items.map(item => <Item key={...} />)}
332
- else if (
333
- t.isCallExpression(expr) &&
334
- t.isMemberExpression(expr.callee) &&
335
- t.isIdentifier(expr.callee.property) &&
336
- expr.callee.property.name === 'map' &&
337
- expr.arguments.length > 0
338
- ) {
339
- const listSource = generateExpression(expr.callee.object as t.Expression);
340
- const mapFn = expr.arguments[0];
341
- let itemTemplate: IRExperienceNode | null = null;
342
- let itemAlias = 'item';
343
-
344
- if (t.isArrowFunctionExpression(mapFn) || t.isFunctionExpression(mapFn)) {
345
- // Get the item parameter name
346
- if (mapFn.params.length > 0 && t.isIdentifier(mapFn.params[0])) {
347
- itemAlias = mapFn.params[0].name;
348
- }
349
- // Get the returned JSX
350
- const body = mapFn.body;
351
- if (t.isJSXElement(body) || t.isJSXFragment(body)) {
352
- itemTemplate = jsxToExperienceNode(body as t.JSXElement | t.JSXFragment);
353
- } else if (t.isBlockStatement(body)) {
354
- for (const stmt of body.body) {
355
- if (t.isReturnStatement(stmt) && stmt.argument) {
356
- if (t.isJSXElement(stmt.argument) || t.isJSXFragment(stmt.argument)) {
357
- itemTemplate = jsxToExperienceNode(stmt.argument as t.JSXElement | t.JSXFragment);
358
- }
359
- break;
360
- }
361
- }
362
- }
363
- }
364
-
365
- nodes.push({
366
- id: `each_${++nodeIdCounter}`,
367
- component: 'Each',
368
- bindings: { items: listSource.startsWith('$') ? listSource : `$instance.${listSource}` },
369
- config: { as: itemAlias },
370
- ...(itemTemplate ? { children: [itemTemplate] } : {}),
371
- });
372
- }
373
- // Render callback: {(item) => <Component/>} or {(item, index) => { return <Component/>; }}
374
- // This is the children-as-function pattern used by Each, Show, etc.
375
- else if (
376
- (t.isArrowFunctionExpression(expr) || t.isFunctionExpression(expr)) &&
377
- extractJSXFromCallback(expr)
378
- ) {
379
- const jsxNode = extractJSXFromCallback(expr)!;
380
- nodes.push(jsxToExperienceNode(jsxNode));
381
- }
382
- // String literal in expression: {' '}, {"hello"} → static Text config
383
- else if (t.isStringLiteral(expr)) {
384
- nodes.push({
385
- id: `text_${++nodeIdCounter}`,
386
- component: 'Text',
387
- config: { value: expr.value },
388
- });
389
- }
390
- // Simple expression: {count}, {status}
391
- else if (t.isIdentifier(expr)) {
392
- const snakeName = localFieldMap.get(expr.name);
393
- const bindingPath = snakeName ? `$local.${snakeName}` : `$instance.${expr.name}`;
394
- nodes.push({
395
- id: `text_${++nodeIdCounter}`,
396
- component: 'Text',
397
- bindings: { value: bindingPath },
398
- });
399
- }
400
- // Complex expression: {a + b}, {fn(x)}
401
- else if (t.isExpression(expr)) {
402
- const exprStr = generateExpression(expr);
403
- if (exprStr !== '[Expression]') {
404
- nodes.push({
405
- id: `text_${++nodeIdCounter}`,
406
- component: 'Text',
407
- bindings: { value: exprStr },
408
- });
409
- }
410
- }
411
- }
412
- }
413
-
414
- return nodes;
415
- }
416
-
417
- /**
418
- * Generates a unique node ID from component name.
419
- */
420
- function generateNodeId(componentName: string): string {
421
- const id = componentName
422
- .replace(/\./g, '-')
423
- .replace(/([A-Z])/g, '-$1')
424
- .toLowerCase()
425
- .replace(/^-/, '');
426
- return `${id}_${++nodeIdCounter}`;
427
- }
428
-
429
- /**
430
- * Generates expression string from AST node.
431
- *
432
- * The output must be valid JS that the binding resolver can evaluate
433
- * via `new Function(...)`. All $-prefix paths are resolved at runtime
434
- * from the binding context ($instance, $local, $fn, $action, etc.).
435
- */
436
- function generateExpression(node: t.Expression): string {
437
- if (t.isIdentifier(node)) {
438
- // Map useState fields to $local.snake_case
439
- const snakeName = localFieldMap.get(node.name);
440
- if (snakeName) return `$local.${snakeName}`;
441
- // Inline derived variables: expand to their defining expression
442
- const derivedInit = derivedVarMap.get(node.name);
443
- if (derivedInit) return `(${generateExpression(derivedInit)})`;
444
- return node.name;
445
- }
446
- if (t.isStringLiteral(node)) return `"${node.value.replace(/"/g, '\\"')}"`;
447
- if (t.isNumericLiteral(node)) return String(node.value);
448
- if (t.isBooleanLiteral(node)) return String(node.value);
449
- if (t.isNullLiteral(node)) return 'null';
450
-
451
- // Member expression: a.b, a?.b, a[0], a["key"]
452
- if (t.isMemberExpression(node) || t.isOptionalMemberExpression(node)) {
453
- const obj = t.isExpression(node.object) ? generateExpression(node.object) : '[object]';
454
- const optional = (node as any).optional ? '?.' : '.';
455
- if (node.computed) {
456
- const prop = t.isExpression(node.property) ? generateExpression(node.property as t.Expression) : '0';
457
- return `${obj}[${prop}]`;
458
- }
459
- const prop = t.isIdentifier(node.property) ? node.property.name : '[property]';
460
- return `${obj}${optional}${prop}`;
461
- }
462
-
463
- // Binary: a + b, a === b, a !== b, etc.
464
- if (t.isBinaryExpression(node)) {
465
- return `(${generateExpression(node.left as t.Expression)} ${node.operator} ${generateExpression(node.right as t.Expression)})`;
466
- }
467
-
468
- // Logical: a && b, a || b, a ?? b
469
- if (t.isLogicalExpression(node)) {
470
- return `(${generateExpression(node.left)} ${node.operator} ${generateExpression(node.right)})`;
471
- }
472
-
473
- // Conditional (ternary): a ? b : c
474
- if (t.isConditionalExpression(node)) {
475
- return `(${generateExpression(node.test)} ? ${generateExpression(node.consequent)} : ${generateExpression(node.alternate)})`;
476
- }
477
-
478
- // Unary: !a, -a, typeof a
479
- if (t.isUnaryExpression(node) && node.prefix) {
480
- const space = node.operator === 'typeof' || node.operator === 'void' || node.operator === 'delete' ? ' ' : '';
481
- return `${node.operator}${space}${generateExpression(node.argument)}`;
482
- }
483
-
484
- // Call expression: fn(args) or obj.method(args)
485
- if (t.isCallExpression(node) || t.isOptionalCallExpression(node)) {
486
- if (t.isIdentifier(node.callee)) {
487
- // Detect useState setter calls: setSomething(value) → $action.setLocal("field")(value)
488
- const setterFieldName = setterToFieldMap.get(node.callee.name);
489
- if (setterFieldName) {
490
- if (node.arguments.length >= 1) {
491
- const arg = node.arguments[0];
492
- // Functional update: setSomething(prev => expr) → $action.setLocal("field")(expr with prev→$local.field)
493
- if (
494
- (t.isArrowFunctionExpression(arg) || t.isFunctionExpression(arg)) &&
495
- arg.params.length >= 1 &&
496
- t.isIdentifier(arg.params[0])
497
- ) {
498
- const paramName = arg.params[0].name;
499
- const bodyExpr = t.isExpression(arg.body) ? generateExpression(arg.body) : 'undefined';
500
- // Replace the callback param with the actual $local field reference
501
- const resolved = bodyExpr.replace(
502
- new RegExp(`(?<![.$\\w])${paramName}(?![\\w])`, 'g'),
503
- `$local.${setterFieldName}`,
504
- );
505
- return `$action.setLocal("${setterFieldName}")(${resolved})`;
506
- }
507
- // Direct value: setSomething(value) → $action.setLocal("field")(value)
508
- const argStr = t.isExpression(arg) ? generateExpression(arg) : 'undefined';
509
- return `$action.setLocal("${setterFieldName}")(${argStr})`;
510
- }
511
- return `$action.setLocal("${setterFieldName}")`;
512
- }
513
- const args = node.arguments.map((a) => t.isExpression(a) ? generateExpression(a) : t.isSpreadElement(a) ? `...${generateExpression(a.argument)}` : 'undefined').join(', ');
514
- return `${node.callee.name}(${args})`;
515
- }
516
- if (t.isMemberExpression(node.callee) || t.isOptionalMemberExpression(node.callee)) {
517
- // Try mm-compute transpilation for recognized method patterns
518
- // (Math.*, JSON.*, Date.*, string/array methods)
519
- const mmResult = transpileExpression(node, {
520
- localFieldMap,
521
- derivedVarMap,
522
- setterToFieldMap,
523
- });
524
- if (mmResult.pure) return mmResult.expression;
525
-
526
- // Fallback: raw JS method call syntax
527
- const obj = generateExpression(node.callee.object as t.Expression);
528
- const optional = (node.callee as any).optional ? '?.' : '.';
529
- const prop = t.isIdentifier(node.callee.property) ? node.callee.property.name : '[method]';
530
- const args = node.arguments.map((a) => t.isExpression(a) ? generateExpression(a) : t.isSpreadElement(a) ? `...${generateExpression(a.argument)}` : 'undefined').join(', ');
531
- return `${obj}${optional}${prop}(${args})`;
532
- }
533
- // Callee is a more complex expression (e.g., (fn || fallback)(args))
534
- if (t.isExpression(node.callee)) {
535
- const callee = generateExpression(node.callee);
536
- const args = node.arguments.map((a) => t.isExpression(a) ? generateExpression(a) : 'undefined').join(', ');
537
- return `(${callee})(${args})`;
538
- }
539
- }
540
-
541
- // Template literal: `hello ${name}`
542
- if (t.isTemplateLiteral(node)) {
543
- const parts = node.quasis.map((q, i) => {
544
- const raw = q.value.raw;
545
- if (i < node.expressions.length) {
546
- return raw + '${' + generateExpression(node.expressions[i] as t.Expression) + '}';
547
- }
548
- return raw;
549
- }).join('');
550
- return '`' + parts + '`';
551
- }
552
-
553
- // Arrow function: () => expr, (a) => expr, (a) => { ... }
554
- if (t.isArrowFunctionExpression(node)) {
555
- const params = node.params.map((p) => {
556
- if (t.isIdentifier(p)) return p.name;
557
- if (t.isRestElement(p) && t.isIdentifier(p.argument)) return `...${p.argument.name}`;
558
- if (t.isAssignmentPattern(p) && t.isIdentifier(p.left)) {
559
- return `${p.left.name} = ${t.isExpression(p.right) ? generateExpression(p.right) : 'undefined'}`;
560
- }
561
- return '_';
562
- }).join(', ');
563
- if (t.isExpression(node.body)) {
564
- return `(${params}) => ${generateExpression(node.body)}`;
565
- }
566
- // Block body — generate the statements
567
- if (t.isBlockStatement(node.body)) {
568
- const bodyStr = generateBlockStatement(node.body);
569
- return `(${params}) => { ${bodyStr} }`;
570
- }
571
- }
572
-
573
- // Array expression: [1, 2, 3]
574
- if (t.isArrayExpression(node)) {
575
- const elements = node.elements.map((el) => {
576
- if (el === null) return 'undefined';
577
- if (t.isSpreadElement(el)) return `...${generateExpression(el.argument)}`;
578
- return t.isExpression(el) ? generateExpression(el) : 'undefined';
579
- }).join(', ');
580
- return `[${elements}]`;
581
- }
582
-
583
- // Object expression: { key: value }
584
- if (t.isObjectExpression(node)) {
585
- const props = node.properties.map((prop) => {
586
- if (t.isSpreadElement(prop)) return `...${generateExpression(prop.argument)}`;
587
- if (t.isObjectProperty(prop)) {
588
- const key = t.isIdentifier(prop.key) ? prop.key.name
589
- : t.isStringLiteral(prop.key) ? `"${prop.key.value}"`
590
- : t.isNumericLiteral(prop.key) ? String(prop.key.value)
591
- : '[key]';
592
- if (prop.shorthand && t.isIdentifier(prop.value)) return key;
593
- const value = t.isExpression(prop.value) ? generateExpression(prop.value) : 'undefined';
594
- return `${key}: ${value}`;
595
- }
596
- if (t.isObjectMethod(prop)) {
597
- const key = t.isIdentifier(prop.key) ? prop.key.name : '[method]';
598
- const params = prop.params.map((p) => t.isIdentifier(p) ? p.name : '_').join(', ');
599
- const bodyStr = generateBlockStatement(prop.body);
600
- return `${key}(${params}) { ${bodyStr} }`;
601
- }
602
- return '';
603
- }).filter(Boolean).join(', ');
604
- return `{ ${props} }`;
605
- }
606
-
607
- // Parenthesized expression
608
- if (t.isParenthesizedExpression(node)) {
609
- return `(${generateExpression(node.expression)})`;
610
- }
611
-
612
- // TS type assertions: x as Type, x!
613
- if (t.isTSAsExpression(node)) return generateExpression(node.expression);
614
- if (t.isTSNonNullExpression(node)) return generateExpression(node.expression);
615
- if (t.isTSTypeAssertion(node)) return generateExpression(node.expression);
616
-
617
- // Sequence expression: (a, b, c) — returns last value
618
- if (t.isSequenceExpression(node)) {
619
- return `(${node.expressions.map(generateExpression).join(', ')})`;
620
- }
621
-
622
- // Assignment expression: a = b
623
- if (t.isAssignmentExpression(node)) {
624
- const left = t.isExpression(node.left) ? generateExpression(node.left as t.Expression) : '[target]';
625
- return `(${left} ${node.operator} ${generateExpression(node.right)})`;
626
- }
627
-
628
- // Update expression: a++, --b
629
- if (t.isUpdateExpression(node)) {
630
- const arg = generateExpression(node.argument);
631
- return node.prefix ? `${node.operator}${arg}` : `${arg}${node.operator}`;
632
- }
633
-
634
- // Tagged template: tag`string`
635
- if (t.isTaggedTemplateExpression(node)) {
636
- return generateExpression(node.quasi);
637
- }
638
-
639
- // Spread in non-array/object context (shouldn't happen but safety)
640
- if (t.isSpreadElement(node as any)) {
641
- return `...${generateExpression((node as any).argument)}`;
642
- }
643
-
644
- // Yield: yield expr (generators — unlikely in views but handle gracefully)
645
- if (t.isYieldExpression(node)) {
646
- return node.argument ? `yield ${generateExpression(node.argument)}` : 'yield';
647
- }
648
-
649
- // Await: await expr
650
- if (t.isAwaitExpression(node)) {
651
- return `await ${generateExpression(node.argument)}`;
652
- }
653
-
654
- // New expression: new Foo(args)
655
- if (t.isNewExpression(node)) {
656
- const callee = t.isExpression(node.callee) ? generateExpression(node.callee) : 'Object';
657
- const args = node.arguments.map((a) => t.isExpression(a) ? generateExpression(a) : 'undefined').join(', ');
658
- return `new ${callee}(${args})`;
659
- }
660
-
661
- // This expression
662
- if (t.isThisExpression(node)) return 'this';
663
-
664
- // Fallback — generate from source if possible using Babel's generator
665
- console.warn(`[component-extractor] Unhandled expression type: ${node.type}`);
666
- return '[Expression]';
667
- }
668
-
669
- /**
670
- * Generates a block statement body as a string.
671
- */
672
- function generateBlockStatement(block: t.BlockStatement): string {
673
- return block.body.map((stmt) => {
674
- if (t.isReturnStatement(stmt)) {
675
- return stmt.argument ? `return ${generateExpression(stmt.argument)};` : 'return;';
676
- }
677
- if (t.isExpressionStatement(stmt)) {
678
- return `${generateExpression(stmt.expression)};`;
679
- }
680
- if (t.isVariableDeclaration(stmt)) {
681
- const decls = stmt.declarations.map((d) => {
682
- const id = t.isIdentifier(d.id) ? d.id.name : '_';
683
- const init = d.init && t.isExpression(d.init) ? generateExpression(d.init) : 'undefined';
684
- return `${id} = ${init}`;
685
- }).join(', ');
686
- return `${stmt.kind} ${decls};`;
687
- }
688
- if (t.isIfStatement(stmt)) {
689
- const test = generateExpression(stmt.test);
690
- const consequent = t.isBlockStatement(stmt.consequent)
691
- ? `{ ${generateBlockStatement(stmt.consequent)} }`
692
- : t.isExpressionStatement(stmt.consequent)
693
- ? `{ ${generateExpression(stmt.consequent.expression)}; }`
694
- : '{}';
695
- const alternate = stmt.alternate
696
- ? t.isBlockStatement(stmt.alternate)
697
- ? ` else { ${generateBlockStatement(stmt.alternate)} }`
698
- : t.isIfStatement(stmt.alternate)
699
- ? ` else ${generateBlockStatement(t.blockStatement([stmt.alternate]))}`
700
- : ''
701
- : '';
702
- return `if (${test}) ${consequent}${alternate}`;
703
- }
704
- // Fallback for other statement types
705
- return '/* [Statement] */';
706
- }).join(' ');
707
- }
708
-
709
- /**
710
- * Extracts JSX element from a render callback function.
711
- * Handles: (item) => <JSX/>, (item) => (<JSX/>), (item) => { return <JSX/>; }
712
- */
713
- function extractJSXFromCallback(
714
- node: t.ArrowFunctionExpression | t.FunctionExpression
715
- ): t.JSXElement | t.JSXFragment | null {
716
- const body = node.body;
717
- if (t.isJSXElement(body) || t.isJSXFragment(body)) {
718
- return body as t.JSXElement | t.JSXFragment;
719
- }
720
- // Parenthesized: (item) => (<JSX/>)
721
- if (t.isParenthesizedExpression(body)) {
722
- const inner = body.expression;
723
- if (t.isJSXElement(inner) || t.isJSXFragment(inner)) {
724
- return inner as t.JSXElement | t.JSXFragment;
725
- }
726
- }
727
- // Block body: (item) => { return <JSX/>; }
728
- if (t.isBlockStatement(body)) {
729
- for (const stmt of body.body) {
730
- if (t.isReturnStatement(stmt) && stmt.argument) {
731
- if (t.isJSXElement(stmt.argument) || t.isJSXFragment(stmt.argument)) {
732
- return stmt.argument as t.JSXElement | t.JSXFragment;
733
- }
734
- // Parenthesized return: return (<JSX/>)
735
- if (t.isParenthesizedExpression(stmt.argument)) {
736
- const inner = stmt.argument.expression;
737
- if (t.isJSXElement(inner) || t.isJSXFragment(inner)) {
738
- return inner as t.JSXElement | t.JSXFragment;
739
- }
740
- }
741
- }
742
- }
743
- }
744
- return null;
745
- }
746
-
747
- // ============================================================================
748
- // EVENT HANDLER DECOMPOSITION
749
- // ============================================================================
750
-
751
- /**
752
- * Returns true for event handler prop names (onClick, onPress, onChange, etc.)
753
- */
754
- function isEventHandlerProp(name: string): boolean {
755
- return /^on[A-Z]/.test(name);
756
- }
757
-
758
- /**
759
- * Converts a single expression (statement body) into an atomic action string.
760
- * Returns null if the expression cannot be decomposed.
761
- */
762
- function expressionToActionAtom(expr: t.Expression): string | null {
763
- // Call expression: setter, transition, navigate, toast, etc.
764
- if (t.isCallExpression(expr) || t.isOptionalCallExpression(expr)) {
765
- // Direct setter: setFoo(value)
766
- if (t.isIdentifier(expr.callee)) {
767
- const setterFieldName = setterToFieldMap.get(expr.callee.name);
768
- if (setterFieldName && expr.arguments.length >= 1) {
769
- const arg = expr.arguments[0];
770
- // Functional update: setFoo(prev => expr)
771
- if (
772
- (t.isArrowFunctionExpression(arg) || t.isFunctionExpression(arg)) &&
773
- arg.params.length >= 1 &&
774
- t.isIdentifier(arg.params[0])
775
- ) {
776
- const paramName = arg.params[0].name;
777
- const bodyExpr = t.isExpression(arg.body) ? generateExpression(arg.body) : 'undefined';
778
- const resolved = bodyExpr.replace(
779
- new RegExp(`(?<![.$\\w])${paramName}(?![\\w])`, 'g'),
780
- `$local.${setterFieldName}`,
781
- );
782
- return `$action.setLocal("${setterFieldName}", ${resolved})`;
783
- }
784
- // Direct value: setFoo(value)
785
- const argStr = t.isExpression(arg) ? generateExpression(arg) : 'undefined';
786
- return `$action.setLocal("${setterFieldName}", ${argStr})`;
787
- }
788
- // Other function calls — not decomposable into framework actions
789
- return null;
790
- }
791
-
792
- // Member call: $action-like calls from chained setLocal etc.
793
- // e.g., $action.setLocal("key")(value) — already handled above via setter detection
794
- // For now, try to generate as expression and check if it's an $action call
795
- if (t.isMemberExpression(expr.callee) || t.isOptionalMemberExpression(expr.callee)) {
796
- const generated = generateExpression(expr);
797
- // If it looks like an $action call, wrap it as a 2-arg form
798
- if (generated.startsWith('$action.')) return generated;
799
- }
800
-
801
- return null;
802
- }
803
-
804
- return null;
805
- }
806
-
807
- /**
808
- * Converts a statement into an action atom string.
809
- * Returns null for unsupported statement types.
810
- */
811
- function statementToAction(stmt: t.Statement): string | null {
812
- // ExpressionStatement: the most common case
813
- if (t.isExpressionStatement(stmt)) {
814
- return expressionToActionAtom(stmt.expression);
815
- }
816
-
817
- // IfStatement: convert to $action.when(condition, thenAction, elseAction?)
818
- if (t.isIfStatement(stmt)) {
819
- const condition = generateExpression(stmt.test);
820
-
821
- // Consequent
822
- let thenAction: string | null = null;
823
- if (t.isBlockStatement(stmt.consequent)) {
824
- const actions = stmt.consequent.body.map(s => statementToAction(s)).filter(Boolean) as string[];
825
- if (actions.length === 0 || actions.length !== stmt.consequent.body.length) return null;
826
- thenAction = actions.length === 1 ? actions[0] : `$action.seq(${actions.join(', ')})`;
827
- } else if (t.isExpressionStatement(stmt.consequent)) {
828
- thenAction = expressionToActionAtom(stmt.consequent.expression);
829
- }
830
- if (!thenAction) return null;
831
-
832
- // Alternate (optional)
833
- let elseAction: string | null = null;
834
- if (stmt.alternate) {
835
- if (t.isBlockStatement(stmt.alternate)) {
836
- const actions = stmt.alternate.body.map(s => statementToAction(s)).filter(Boolean) as string[];
837
- if (actions.length !== stmt.alternate.body.length) return null;
838
- elseAction = actions.length === 1 ? actions[0] : `$action.seq(${actions.join(', ')})`;
839
- } else if (t.isIfStatement(stmt.alternate)) {
840
- elseAction = statementToAction(stmt.alternate);
841
- } else if (t.isExpressionStatement(stmt.alternate)) {
842
- elseAction = expressionToActionAtom(stmt.alternate.expression);
843
- }
844
- if (!elseAction) return null;
845
- }
846
-
847
- return elseAction
848
- ? `$action.when(${condition}, ${thenAction}, ${elseAction})`
849
- : `$action.when(${condition}, ${thenAction})`;
850
- }
851
-
852
- return null;
853
- }
854
-
855
- /**
856
- * Decomposes an arrow/function expression handler into a $action.seq() string.
857
- * For single-expression arrows, returns the single action atom.
858
- * Returns null if decomposition fails (fallback to generateExpression).
859
- */
860
- function decomposeHandlerToSeq(node: t.ArrowFunctionExpression | t.FunctionExpression): string | null {
861
- // Expression body: () => setFoo(value) — single action
862
- if (t.isArrowFunctionExpression(node) && t.isExpression(node.body)) {
863
- return expressionToActionAtom(node.body);
864
- }
865
-
866
- // Block body: () => { stmt1; stmt2; ... }
867
- const body = node.body;
868
- if (!t.isBlockStatement(body)) return null;
869
-
870
- const actions: string[] = [];
871
- for (const stmt of body.body) {
872
- const action = statementToAction(stmt);
873
- if (!action) return null; // Can't decompose — bail
874
- actions.push(action);
875
- }
876
-
877
- if (actions.length === 0) return null;
878
- if (actions.length === 1) return actions[0];
879
- return `$action.seq(${actions.join(', ')})`;
880
- }
881
-
882
- /**
883
- * Main extractor function called from the visitor.
884
- */
885
- export function extractComponents(path: NodePath<t.ReturnStatement>, state: any): void {
886
- const arg = path.node.argument;
887
- if (!arg) return;
888
-
889
- // Initialize local field maps from extracted useState fields BEFORE processing JSX
890
- const compilerState = state as CompilerState;
891
-
892
- if (compilerState.fields && compilerState.fields.length > 0) {
893
- initLocalFieldMaps(compilerState.fields);
894
- }
895
-
896
- let experienceNode: IRExperienceNode | null = null;
897
-
898
- if (t.isJSXElement(arg)) {
899
- experienceNode = jsxToExperienceNode(arg);
900
- } else if (t.isJSXFragment(arg)) {
901
- experienceNode = jsxToExperienceNode(arg);
902
- }
903
-
904
- if (experienceNode) {
905
- compilerState.experience = experienceNode;
906
- }
907
- }