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

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 (346) hide show
  1. package/ATOM-PIPELINE.md +144 -0
  2. package/README.md +88 -40
  3. package/dist/auth-3UK75242.mjs +17 -0
  4. package/dist/babel/index.d.mts +2 -2
  5. package/dist/babel/index.d.ts +2 -2
  6. package/dist/babel/index.js +2816 -279
  7. package/dist/babel/index.mjs +2 -2
  8. package/dist/chunk-3USIFFE4.mjs +2190 -0
  9. package/dist/chunk-45YMGEVT.mjs +186 -0
  10. package/dist/chunk-4FN2AISW.mjs +148 -0
  11. package/dist/chunk-4OPI5L7G.mjs +2593 -0
  12. package/dist/chunk-4RYTKOOJ.mjs +186 -0
  13. package/dist/chunk-52XHYD2V.mjs +214 -0
  14. package/dist/chunk-5FTDWKHH.mjs +244 -0
  15. package/dist/chunk-5GUFFFGL.mjs +148 -0
  16. package/dist/chunk-5RKTOVR5.mjs +244 -0
  17. package/dist/chunk-5YDMOO4X.mjs +214 -0
  18. package/dist/chunk-64ZWEMLJ.mjs +148 -0
  19. package/dist/chunk-6XP4KSWQ.mjs +2190 -0
  20. package/dist/chunk-72QWL54I.mjs +175 -0
  21. package/dist/chunk-7B4TRI7C.mjs +4835 -0
  22. package/dist/chunk-7JRAEFRB.mjs +7510 -0
  23. package/dist/chunk-7T6Q5KAA.mjs +7506 -0
  24. package/dist/chunk-7ZKGHTNB.mjs +4952 -0
  25. package/dist/chunk-ABYPKRSB.mjs +215 -0
  26. package/dist/chunk-BZEXUPDH.mjs +175 -0
  27. package/dist/chunk-CIESM3BP.mjs +33 -0
  28. package/dist/chunk-DE3ZGQAC.mjs +148 -0
  29. package/dist/chunk-DMCY3BBG.mjs +1933 -0
  30. package/dist/chunk-DPIK3PJS.mjs +244 -0
  31. package/dist/chunk-E5IVH4RE.mjs +186 -0
  32. package/dist/chunk-E6FZNUR5.mjs +4953 -0
  33. package/dist/chunk-EJRBDQDP.mjs +2607 -0
  34. package/dist/chunk-ELO4TXJL.mjs +186 -0
  35. package/dist/chunk-EO6SYNCG.mjs +175 -0
  36. package/dist/chunk-FKRO52XH.mjs +3446 -0
  37. package/dist/chunk-FL4YAKU6.mjs +4941 -0
  38. package/dist/chunk-FYT47UBU.mjs +5076 -0
  39. package/dist/chunk-GCLGPOJZ.mjs +148 -0
  40. package/dist/chunk-GXB4JOP7.mjs +5072 -0
  41. package/dist/chunk-HFXOUMTD.mjs +175 -0
  42. package/dist/chunk-HRYR54PT.mjs +175 -0
  43. package/dist/chunk-HWIZ47US.mjs +214 -0
  44. package/dist/chunk-IB7MNPQL.mjs +4953 -0
  45. package/dist/chunk-ICSIHQCG.mjs +148 -0
  46. package/dist/chunk-J3M4GUS7.mjs +161 -0
  47. package/dist/chunk-J7JUAHS4.mjs +186 -0
  48. package/dist/chunk-JLA5VNQ3.mjs +186 -0
  49. package/dist/chunk-JQLWFCTM.mjs +214 -0
  50. package/dist/chunk-JRGFBWTN.mjs +2918 -0
  51. package/dist/chunk-KFJJCQAL.mjs +148 -0
  52. package/dist/chunk-KJUIIEQE.mjs +186 -0
  53. package/dist/chunk-KNWTHRVQ.mjs +175 -0
  54. package/dist/chunk-KSG4XSZF.mjs +175 -0
  55. package/dist/chunk-LF5N6DOU.mjs +175 -0
  56. package/dist/chunk-LJQCM2IM.mjs +214 -0
  57. package/dist/chunk-NTB7OEX2.mjs +2918 -0
  58. package/dist/chunk-NW6555WJ.mjs +186 -0
  59. package/dist/chunk-O4AUS7EU.mjs +148 -0
  60. package/dist/chunk-OMZE6VLQ.mjs +214 -0
  61. package/dist/chunk-OPJKP747.mjs +7506 -0
  62. package/dist/chunk-P4BR7WVO.mjs +2190 -0
  63. package/dist/chunk-QQHVYH2X.mjs +244 -0
  64. package/dist/chunk-R2DD5GTY.mjs +186 -0
  65. package/dist/chunk-S5QLWLLT.mjs +186 -0
  66. package/dist/chunk-SCWGT2FY.mjs +2190 -0
  67. package/dist/chunk-SMKJUSB3.mjs +2190 -0
  68. package/dist/chunk-THFYE5ZX.mjs +244 -0
  69. package/dist/chunk-UDDTWG5J.mjs +734 -0
  70. package/dist/chunk-VCAY2KGM.mjs +175 -0
  71. package/dist/chunk-VLTKQDJ3.mjs +244 -0
  72. package/dist/chunk-WBYMW4NQ.mjs +3450 -0
  73. package/dist/chunk-WECAV6QB.mjs +148 -0
  74. package/dist/chunk-WMKBXUCE.mjs +3228 -0
  75. package/dist/chunk-WVYY32LD.mjs +939 -0
  76. package/dist/chunk-XAJ5BKKL.mjs +4947 -0
  77. package/dist/chunk-XDVM4YHX.mjs +3450 -0
  78. package/dist/chunk-XG2X7AEA.mjs +175 -0
  79. package/dist/chunk-XG7Z23NQ.mjs +148 -0
  80. package/dist/chunk-XWZAOCQ7.mjs +2607 -0
  81. package/dist/chunk-Y6MA7ULW.mjs +148 -0
  82. package/dist/chunk-YMS7Q7LG.mjs +214 -0
  83. package/dist/chunk-Z2G5RZ4H.mjs +186 -0
  84. package/dist/chunk-ZA37XTGA.mjs +175 -0
  85. package/dist/chunk-ZE3KCHBM.mjs +2918 -0
  86. package/dist/cli/index.js +14720 -7199
  87. package/dist/cli/index.mjs +224 -183
  88. package/dist/codemod/cli.js +1 -1
  89. package/dist/codemod/cli.mjs +2 -2
  90. package/dist/codemod/index.d.mts +3 -3
  91. package/dist/codemod/index.d.ts +3 -3
  92. package/dist/codemod/index.js +1 -1
  93. package/dist/codemod/index.mjs +2 -2
  94. package/dist/config-PL24KEWL.mjs +219 -0
  95. package/dist/deploy-YAJGW6II.mjs +9 -0
  96. package/dist/dev-server-CrQ041KP.d.mts +79 -0
  97. package/dist/dev-server-CrQ041KP.d.ts +79 -0
  98. package/dist/dev-server-RmGHIntF.d.mts +113 -0
  99. package/dist/dev-server-RmGHIntF.d.ts +113 -0
  100. package/dist/dev-server.d.mts +2 -2
  101. package/dist/dev-server.d.ts +2 -2
  102. package/dist/dev-server.js +6424 -1597
  103. package/dist/dev-server.mjs +5 -5
  104. package/dist/envelope-ChEkuHij.d.mts +265 -0
  105. package/dist/envelope-ChEkuHij.d.ts +265 -0
  106. package/dist/envelope.d.mts +2 -2
  107. package/dist/envelope.d.ts +2 -2
  108. package/dist/envelope.js +2814 -277
  109. package/dist/envelope.mjs +3 -3
  110. package/dist/index-CEKyyazf.d.mts +104 -0
  111. package/dist/index-CEKyyazf.d.ts +104 -0
  112. package/dist/index.d.mts +168 -9
  113. package/dist/index.d.ts +168 -9
  114. package/dist/index.js +5606 -681
  115. package/dist/index.mjs +217 -9
  116. package/dist/init-7FJENUDK.mjs +407 -0
  117. package/{src/cli/init.ts → dist/init-7JQMAAXS.mjs} +70 -95
  118. package/dist/init-DQDX3QK6.mjs +369 -0
  119. package/dist/init-EHO4VQ22.mjs +369 -0
  120. package/dist/init-UC3FWPIW.mjs +367 -0
  121. package/dist/init-UNSMVKIK.mjs +366 -0
  122. package/dist/init-UNV5XIDE.mjs +367 -0
  123. package/dist/project-compiler-2P4N4DR7.mjs +10 -0
  124. package/dist/project-compiler-D2LCC27O.mjs +10 -0
  125. package/dist/project-compiler-EJ3GANJE.mjs +10 -0
  126. package/dist/project-compiler-LOQKVRZJ.mjs +10 -0
  127. package/dist/project-compiler-NNK32MPG.mjs +10 -0
  128. package/dist/project-compiler-OP2VVGJQ.mjs +10 -0
  129. package/dist/project-compiler-RQ6OQKRM.mjs +10 -0
  130. package/dist/project-compiler-VWNNCHGO.mjs +10 -0
  131. package/dist/project-compiler-XVAAU4C5.mjs +10 -0
  132. package/dist/project-compiler-YES5FGMD.mjs +10 -0
  133. package/dist/project-compiler-ZB4RUYVL.mjs +10 -0
  134. package/dist/project-compiler-ZKMQDLGU.mjs +10 -0
  135. package/dist/project-decompiler-FLXCEJHS.mjs +7 -0
  136. package/dist/project-decompiler-U55HQUHW.mjs +7 -0
  137. package/dist/project-decompiler-US7GAVIC.mjs +7 -0
  138. package/dist/project-decompiler-VLPR22QF.mjs +7 -0
  139. package/dist/pull-FUS5QYZS.mjs +109 -0
  140. package/dist/pull-KOL2QAYQ.mjs +109 -0
  141. package/dist/pull-LD5ENLGY.mjs +109 -0
  142. package/dist/pull-P44LDRWB.mjs +109 -0
  143. package/dist/seed-KOGEPGOJ.mjs +154 -0
  144. package/dist/server-VW6UPCHO.mjs +277 -0
  145. package/dist/testing/index.d.mts +8 -8
  146. package/dist/testing/index.d.ts +8 -8
  147. package/dist/testing/index.js +2824 -287
  148. package/dist/testing/index.mjs +2 -2
  149. package/dist/verify-BYHUKARQ.mjs +1833 -0
  150. package/dist/verify-OQDEQYMS.mjs +1833 -0
  151. package/dist/verify-SEIXUGN4.mjs +1833 -0
  152. package/dist/vite/index.d.mts +1 -1
  153. package/dist/vite/index.d.ts +1 -1
  154. package/dist/vite/index.js +2817 -280
  155. package/dist/vite/index.mjs +3 -3
  156. package/examples/authentication/main.workflow.tsx +1 -1
  157. package/examples/authentication/mm.config.ts +1 -1
  158. package/examples/authentication/pages/LoginPage.tsx +2 -2
  159. package/examples/authentication/pages/SignupPage.tsx +2 -2
  160. package/examples/counter.workflow.tsx +1 -1
  161. package/examples/dashboard.workflow.tsx +1 -1
  162. package/examples/invoice-approval/actions/invoice.server.ts +1 -1
  163. package/examples/invoice-approval/main.workflow.tsx +1 -1
  164. package/examples/invoice-approval/mm.config.ts +1 -1
  165. package/examples/invoice-approval/pages/InvoiceDetailPage.tsx +1 -1
  166. package/examples/invoice-approval/pages/InvoiceFormPage.tsx +1 -1
  167. package/examples/invoice-approval/pages/InvoiceListPage.tsx +1 -1
  168. package/examples/todo-app.workflow.tsx +1 -1
  169. package/examples/uber-app/actions/matching.server.ts +1 -1
  170. package/examples/uber-app/actions/notifications.server.ts +1 -1
  171. package/examples/uber-app/actions/payments.server.ts +1 -1
  172. package/examples/uber-app/actions/pricing.server.ts +1 -1
  173. package/examples/uber-app/app/admin/analytics.tsx +2 -2
  174. package/examples/uber-app/app/admin/fleet.tsx +21 -21
  175. package/examples/uber-app/app/admin/surge-pricing.tsx +2 -2
  176. package/examples/uber-app/app/driver/dashboard.tsx +2 -2
  177. package/examples/uber-app/app/driver/earnings.tsx +2 -2
  178. package/examples/uber-app/app/driver/navigation.tsx +2 -2
  179. package/examples/uber-app/app/driver/ride-acceptance.tsx +2 -2
  180. package/examples/uber-app/app/rider/home.tsx +2 -2
  181. package/examples/uber-app/app/rider/payment-methods.tsx +2 -2
  182. package/examples/uber-app/app/rider/ride-history.tsx +2 -2
  183. package/examples/uber-app/app/rider/ride-tracking.tsx +2 -2
  184. package/examples/uber-app/components/DriverCard.tsx +1 -1
  185. package/examples/uber-app/components/MapView.tsx +3 -3
  186. package/examples/uber-app/components/RatingStars.tsx +2 -2
  187. package/examples/uber-app/components/RideCard.tsx +1 -1
  188. package/examples/uber-app/mm.config.ts +1 -1
  189. package/examples/uber-app/workflows/dispute-resolution.workflow.tsx +2 -2
  190. package/examples/uber-app/workflows/driver-onboarding.workflow.tsx +2 -2
  191. package/examples/uber-app/workflows/payment-processing.workflow.tsx +2 -2
  192. package/examples/uber-app/workflows/ride-request.workflow.tsx +2 -2
  193. package/package.json +10 -4
  194. package/compile-blueprint-chat.mjs +0 -99
  195. package/compile-blueprint-glass-console.mjs +0 -98
  196. package/compile-chat-defs.mjs +0 -92
  197. package/examples/uber-app/tests/payment.test.tsx +0 -129
  198. package/examples/uber-app/tests/ride-flow.test.tsx +0 -123
  199. package/package.json.backup +0 -86
  200. package/scripts/decompile.ts +0 -226
  201. package/scripts/seed-auth.ts +0 -267
  202. package/scripts/seed-uber.ts +0 -248
  203. package/scripts/validate-uber.ts +0 -119
  204. package/seed-blueprint-chat.mjs +0 -444
  205. package/seed-blueprint-glass-console.mjs +0 -445
  206. package/seed-compiled.mjs +0 -318
  207. package/src/RoundTripValidator.ts +0 -400
  208. package/src/__tests__/atom-rendering-coverage.test.ts +0 -680
  209. package/src/__tests__/auth-module-compilation.test.ts +0 -247
  210. package/src/__tests__/auth-template-compilation.test.ts +0 -589
  211. package/src/__tests__/change-extractor.test.ts +0 -142
  212. package/src/__tests__/cli-pull.test.ts +0 -73
  213. package/src/__tests__/cli-test.test.ts +0 -72
  214. package/src/__tests__/component-extractor.test.ts +0 -331
  215. package/src/__tests__/context-extractor.test.ts +0 -145
  216. package/src/__tests__/decompiler.test.ts +0 -718
  217. package/src/__tests__/define-blueprint.test.ts +0 -133
  218. package/src/__tests__/definition-validator.test.ts +0 -519
  219. package/src/__tests__/during-extractor.test.ts +0 -152
  220. package/src/__tests__/effect-extractor.test.ts +0 -107
  221. package/src/__tests__/event-emission.test.ts +0 -127
  222. package/src/__tests__/examples.test.ts +0 -236
  223. package/src/__tests__/full-blueprint-coverage.test.ts +0 -1221
  224. package/src/__tests__/golden-suite.test.ts +0 -403
  225. package/src/__tests__/grammar-island-extractor.test.ts +0 -289
  226. package/src/__tests__/instance-key.test.ts +0 -82
  227. package/src/__tests__/ir-migration.test.ts +0 -255
  228. package/src/__tests__/lock-file.test.ts +0 -117
  229. package/src/__tests__/model-extractor.test.ts +0 -195
  230. package/src/__tests__/model-field-acl.test.ts +0 -237
  231. package/src/__tests__/model-hooks.test.ts +0 -130
  232. package/src/__tests__/model-ref-resolution.test.ts +0 -268
  233. package/src/__tests__/model-roundtrip.test.ts +0 -502
  234. package/src/__tests__/model-runtime.test.ts +0 -112
  235. package/src/__tests__/model-transitions.test.ts +0 -183
  236. package/src/__tests__/nrt-action-trace.test.ts +0 -391
  237. package/src/__tests__/pipeline-hardening.test.ts +0 -413
  238. package/src/__tests__/project-compiler.test.ts +0 -546
  239. package/src/__tests__/project-decompiler.test.ts +0 -343
  240. package/src/__tests__/query-compilation.test.ts +0 -145
  241. package/src/__tests__/round-trip/PLAN.md +0 -158
  242. package/src/__tests__/round-trip/README.md +0 -52
  243. package/src/__tests__/round-trip/RESULTS.md +0 -86
  244. package/src/__tests__/round-trip/fixtures/data-heavy/main.workflow.tsx +0 -55
  245. package/src/__tests__/round-trip/fixtures/data-heavy/mm.config.ts +0 -11
  246. package/src/__tests__/round-trip/fixtures/data-heavy/models/contact.ts +0 -54
  247. package/src/__tests__/round-trip/fixtures/full-workflow/main.workflow.tsx +0 -79
  248. package/src/__tests__/round-trip/fixtures/full-workflow/mm.config.ts +0 -12
  249. package/src/__tests__/round-trip/fixtures/full-workflow/models/order.ts +0 -50
  250. package/src/__tests__/round-trip/fixtures/simple-crud/main.workflow.tsx +0 -25
  251. package/src/__tests__/round-trip/fixtures/simple-crud/mm.config.ts +0 -11
  252. package/src/__tests__/round-trip/fixtures/simple-crud/models/task.ts +0 -32
  253. package/src/__tests__/round-trip/fixtures/view-heavy/main.workflow.tsx +0 -79
  254. package/src/__tests__/round-trip/fixtures/view-heavy/mm.config.ts +0 -10
  255. package/src/__tests__/round-trip/round-trip.test.ts +0 -2598
  256. package/src/__tests__/round-trip-ir.test.ts +0 -300
  257. package/src/__tests__/round-trip.test.ts +0 -1212
  258. package/src/__tests__/route-merging.test.ts +0 -372
  259. package/src/__tests__/router-composition.test.ts +0 -489
  260. package/src/__tests__/router-extractor.test.ts +0 -176
  261. package/src/__tests__/server-action-extractor.test.ts +0 -128
  262. package/src/__tests__/smart-type-inference.test.ts +0 -365
  263. package/src/__tests__/source-envelope.test.ts +0 -284
  264. package/src/__tests__/source-fidelity.test.ts +0 -516
  265. package/src/__tests__/state-extractor.test.ts +0 -115
  266. package/src/__tests__/strict-mode.test.ts +0 -227
  267. package/src/__tests__/transition-effect-extractor.test.ts +0 -119
  268. package/src/__tests__/transition-extractor.test.ts +0 -68
  269. package/src/__tests__/ts-to-expression.test.ts +0 -462
  270. package/src/__tests__/type-generator.test.ts +0 -201
  271. package/src/__tests__/uber-validation.test.ts +0 -502
  272. package/src/action-compiler.ts +0 -361
  273. package/src/babel/emitters/experience-transform.ts +0 -199
  274. package/src/babel/emitters/ir-to-tsx-emitter.ts +0 -110
  275. package/src/babel/emitters/pure-form-emitter.ts +0 -1023
  276. package/src/babel/emitters/runtime-glue-emitter.ts +0 -39
  277. package/src/babel/extractors/change-extractor.ts +0 -199
  278. package/src/babel/extractors/component-extractor.ts +0 -907
  279. package/src/babel/extractors/computed-extractor.ts +0 -262
  280. package/src/babel/extractors/context-extractor.ts +0 -277
  281. package/src/babel/extractors/during-extractor.ts +0 -295
  282. package/src/babel/extractors/effect-extractor.ts +0 -340
  283. package/src/babel/extractors/event-extractor.ts +0 -235
  284. package/src/babel/extractors/grammar-island-extractor.ts +0 -302
  285. package/src/babel/extractors/model-extractor.ts +0 -1018
  286. package/src/babel/extractors/router-extractor.ts +0 -303
  287. package/src/babel/extractors/server-action-extractor.ts +0 -173
  288. package/src/babel/extractors/server-action-hook-extractor.ts +0 -72
  289. package/src/babel/extractors/server-state-extractor.ts +0 -88
  290. package/src/babel/extractors/state-extractor.ts +0 -214
  291. package/src/babel/extractors/transition-effect-extractor.ts +0 -176
  292. package/src/babel/extractors/transition-extractor.ts +0 -143
  293. package/src/babel/index.ts +0 -24
  294. package/src/babel/transpilers/ts-to-expression.ts +0 -674
  295. package/src/babel/visitor.ts +0 -807
  296. package/src/cli/auth.ts +0 -255
  297. package/src/cli/build.ts +0 -288
  298. package/src/cli/deploy.ts +0 -206
  299. package/src/cli/index.ts +0 -328
  300. package/src/cli/installer.ts +0 -261
  301. package/src/cli/lock-file.ts +0 -94
  302. package/src/cli/mmrc.ts +0 -22
  303. package/src/cli/pull.ts +0 -172
  304. package/src/cli/registry-client.ts +0 -175
  305. package/src/cli/test.ts +0 -397
  306. package/src/cli/type-generator.ts +0 -243
  307. package/src/codemod/__tests__/forward.test.ts +0 -239
  308. package/src/codemod/__tests__/reverse.test.ts +0 -145
  309. package/src/codemod/__tests__/round-trip.test.ts +0 -137
  310. package/src/codemod/annotation.ts +0 -97
  311. package/src/codemod/classify.ts +0 -197
  312. package/src/codemod/cli.ts +0 -207
  313. package/src/codemod/control-flow.ts +0 -409
  314. package/src/codemod/forward.ts +0 -244
  315. package/src/codemod/import-manager.ts +0 -171
  316. package/src/codemod/index.ts +0 -120
  317. package/src/codemod/reverse.ts +0 -197
  318. package/src/codemod/rules.ts +0 -174
  319. package/src/codemod/state-transform.ts +0 -126
  320. package/src/decompiler/ast-builder.ts +0 -538
  321. package/src/decompiler/config-generator.ts +0 -151
  322. package/src/decompiler/index.ts +0 -315
  323. package/src/decompiler/project-decompiler.ts +0 -1776
  324. package/src/decompiler/project.ts +0 -862
  325. package/src/decompiler/split-strategy.ts +0 -140
  326. package/src/decompiler/state-emitter.ts +0 -1053
  327. package/src/decompiler/sx-emitter.ts +0 -318
  328. package/src/decompiler/workspace-hydrator.ts +0 -189
  329. package/src/dev-server.ts +0 -238
  330. package/src/envelope/fs-tree.ts +0 -217
  331. package/src/envelope/source-envelope.ts +0 -264
  332. package/src/envelope.ts +0 -315
  333. package/src/incremental-compiler.ts +0 -401
  334. package/src/index.ts +0 -99
  335. package/src/model-compiler.ts +0 -277
  336. package/src/project-compiler.ts +0 -1629
  337. package/src/route-extractor.ts +0 -333
  338. package/src/testing/index.ts +0 -32
  339. package/src/testing/snapshot.ts +0 -252
  340. package/src/testing/test-utils.ts +0 -226
  341. package/src/types.ts +0 -68
  342. package/src/vite/index.ts +0 -288
  343. package/test-compile.mjs +0 -142
  344. package/tsconfig.json +0 -25
  345. package/tsup.config.ts +0 -23
  346. 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
- }