@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.
- package/ATOM-PIPELINE.md +144 -0
- package/README.md +88 -40
- package/dist/babel/index.js +2814 -277
- package/dist/babel/index.mjs +2 -2
- package/dist/chunk-3USIFFE4.mjs +2190 -0
- package/dist/chunk-45YMGEVT.mjs +186 -0
- package/dist/chunk-4FN2AISW.mjs +148 -0
- package/dist/chunk-4OPI5L7G.mjs +2593 -0
- package/dist/chunk-4RYTKOOJ.mjs +186 -0
- package/dist/chunk-52XHYD2V.mjs +214 -0
- package/dist/chunk-5GUFFFGL.mjs +148 -0
- package/dist/chunk-5RKTOVR5.mjs +244 -0
- package/dist/chunk-5YDMOO4X.mjs +214 -0
- package/dist/chunk-64ZWEMLJ.mjs +148 -0
- package/dist/chunk-6XP4KSWQ.mjs +2190 -0
- package/dist/chunk-72QWL54I.mjs +175 -0
- package/dist/chunk-7B4TRI7C.mjs +4835 -0
- package/dist/chunk-7ZKGHTNB.mjs +4952 -0
- package/dist/chunk-CIESM3BP.mjs +33 -0
- package/dist/chunk-DE3ZGQAC.mjs +148 -0
- package/dist/chunk-DMCY3BBG.mjs +1933 -0
- package/dist/chunk-DPIK3PJS.mjs +244 -0
- package/dist/chunk-E5IVH4RE.mjs +186 -0
- package/dist/chunk-E6FZNUR5.mjs +4953 -0
- package/dist/chunk-EJRBDQDP.mjs +2607 -0
- package/dist/chunk-ELO4TXJL.mjs +186 -0
- package/dist/chunk-EO6SYNCG.mjs +175 -0
- package/dist/chunk-FKRO52XH.mjs +3446 -0
- package/dist/chunk-FL4YAKU6.mjs +4941 -0
- package/dist/chunk-FYT47UBU.mjs +5076 -0
- package/dist/chunk-GCLGPOJZ.mjs +148 -0
- package/dist/chunk-GXB4JOP7.mjs +5072 -0
- package/dist/chunk-HFXOUMTD.mjs +175 -0
- package/dist/chunk-HWIZ47US.mjs +214 -0
- package/dist/chunk-IB7MNPQL.mjs +4953 -0
- package/dist/chunk-ICSIHQCG.mjs +148 -0
- package/dist/chunk-J7JUAHS4.mjs +186 -0
- package/dist/chunk-JLA5VNQ3.mjs +186 -0
- package/dist/chunk-JQLWFCTM.mjs +214 -0
- package/dist/chunk-KFJJCQAL.mjs +148 -0
- package/dist/chunk-KJUIIEQE.mjs +186 -0
- package/dist/chunk-KNWTHRVQ.mjs +175 -0
- package/dist/chunk-KSG4XSZF.mjs +175 -0
- package/dist/chunk-LF5N6DOU.mjs +175 -0
- package/dist/chunk-LJQCM2IM.mjs +214 -0
- package/dist/chunk-NTB7OEX2.mjs +2918 -0
- package/dist/chunk-NW6555WJ.mjs +186 -0
- package/dist/chunk-OMZE6VLQ.mjs +214 -0
- package/dist/chunk-OPJKP747.mjs +7506 -0
- package/dist/chunk-P4BR7WVO.mjs +2190 -0
- package/dist/chunk-QQHVYH2X.mjs +244 -0
- package/dist/chunk-S5QLWLLT.mjs +186 -0
- package/dist/chunk-SCWGT2FY.mjs +2190 -0
- package/dist/chunk-SMKJUSB3.mjs +2190 -0
- package/dist/chunk-THFYE5ZX.mjs +244 -0
- package/dist/chunk-VCAY2KGM.mjs +175 -0
- package/dist/chunk-WBYMW4NQ.mjs +3450 -0
- package/dist/chunk-WECAV6QB.mjs +148 -0
- package/dist/chunk-WMKBXUCE.mjs +3228 -0
- package/dist/chunk-XAJ5BKKL.mjs +4947 -0
- package/dist/chunk-XG2X7AEA.mjs +175 -0
- package/dist/chunk-XG7Z23NQ.mjs +148 -0
- package/dist/chunk-XWZAOCQ7.mjs +2607 -0
- package/dist/chunk-Y6MA7ULW.mjs +148 -0
- package/dist/chunk-YMS7Q7LG.mjs +214 -0
- package/dist/chunk-ZA37XTGA.mjs +175 -0
- package/dist/cli/index.js +13189 -6838
- package/dist/cli/index.mjs +140 -22
- package/dist/codemod/cli.mjs +1 -1
- package/dist/codemod/index.mjs +1 -1
- package/dist/config-PL24KEWL.mjs +219 -0
- package/dist/dev-server-RmGHIntF.d.mts +113 -0
- package/dist/dev-server-RmGHIntF.d.ts +113 -0
- package/dist/dev-server.d.mts +1 -1
- package/dist/dev-server.d.ts +1 -1
- package/dist/dev-server.js +4135 -440
- package/dist/dev-server.mjs +5 -5
- package/dist/envelope.js +2812 -275
- package/dist/envelope.mjs +3 -3
- package/dist/index.d.mts +161 -2
- package/dist/index.d.ts +161 -2
- package/dist/index.js +4429 -428
- package/dist/index.mjs +217 -9
- package/{src/cli/init.ts → dist/init-7JQMAAXS.mjs} +70 -95
- package/dist/init-DQDX3QK6.mjs +369 -0
- package/dist/init-EHO4VQ22.mjs +369 -0
- package/dist/init-UC3FWPIW.mjs +367 -0
- package/dist/init-UNSMVKIK.mjs +366 -0
- package/dist/init-UNV5XIDE.mjs +367 -0
- package/dist/project-compiler-2P4N4DR7.mjs +10 -0
- package/dist/project-compiler-D2LCC27O.mjs +10 -0
- package/dist/project-compiler-EJ3GANJE.mjs +10 -0
- package/dist/project-compiler-LOQKVRZJ.mjs +10 -0
- package/dist/project-compiler-OP2VVGJQ.mjs +10 -0
- package/dist/project-compiler-RQ6OQKRM.mjs +10 -0
- package/dist/project-compiler-VWNNCHGO.mjs +10 -0
- package/dist/project-compiler-XVAAU4C5.mjs +10 -0
- package/dist/project-compiler-YES5FGMD.mjs +10 -0
- package/dist/project-compiler-ZKMQDLGU.mjs +10 -0
- package/dist/project-decompiler-FLXCEJHS.mjs +7 -0
- package/dist/project-decompiler-US7GAVIC.mjs +7 -0
- package/dist/project-decompiler-VLPR22QF.mjs +7 -0
- package/dist/pull-FUS5QYZS.mjs +109 -0
- package/dist/pull-LD5ENLGY.mjs +109 -0
- package/dist/pull-P44LDRWB.mjs +109 -0
- package/dist/testing/index.js +2822 -285
- package/dist/testing/index.mjs +2 -2
- package/dist/verify-SEIXUGN4.mjs +1833 -0
- package/dist/vite/index.js +2815 -278
- package/dist/vite/index.mjs +3 -3
- package/examples/uber-app/app/admin/fleet.tsx +19 -19
- package/package.json +16 -6
- package/compile-blueprint-chat.mjs +0 -99
- package/compile-blueprint-glass-console.mjs +0 -98
- package/compile-chat-defs.mjs +0 -92
- package/examples/uber-app/tests/payment.test.tsx +0 -129
- package/examples/uber-app/tests/ride-flow.test.tsx +0 -123
- package/package.json.backup +0 -86
- package/scripts/decompile.ts +0 -226
- package/scripts/seed-auth.ts +0 -267
- package/scripts/seed-uber.ts +0 -248
- package/scripts/validate-uber.ts +0 -119
- package/seed-blueprint-chat.mjs +0 -444
- package/seed-blueprint-glass-console.mjs +0 -445
- package/seed-compiled.mjs +0 -318
- package/src/RoundTripValidator.ts +0 -400
- package/src/__tests__/atom-rendering-coverage.test.ts +0 -680
- package/src/__tests__/auth-module-compilation.test.ts +0 -247
- package/src/__tests__/auth-template-compilation.test.ts +0 -589
- package/src/__tests__/change-extractor.test.ts +0 -142
- package/src/__tests__/cli-pull.test.ts +0 -73
- package/src/__tests__/cli-test.test.ts +0 -72
- package/src/__tests__/component-extractor.test.ts +0 -331
- package/src/__tests__/context-extractor.test.ts +0 -145
- package/src/__tests__/decompiler.test.ts +0 -718
- package/src/__tests__/define-blueprint.test.ts +0 -133
- package/src/__tests__/definition-validator.test.ts +0 -519
- package/src/__tests__/during-extractor.test.ts +0 -152
- package/src/__tests__/effect-extractor.test.ts +0 -107
- package/src/__tests__/event-emission.test.ts +0 -127
- package/src/__tests__/examples.test.ts +0 -236
- package/src/__tests__/full-blueprint-coverage.test.ts +0 -1221
- package/src/__tests__/golden-suite.test.ts +0 -403
- package/src/__tests__/grammar-island-extractor.test.ts +0 -289
- package/src/__tests__/instance-key.test.ts +0 -82
- package/src/__tests__/ir-migration.test.ts +0 -255
- package/src/__tests__/lock-file.test.ts +0 -117
- package/src/__tests__/model-extractor.test.ts +0 -195
- package/src/__tests__/model-field-acl.test.ts +0 -237
- package/src/__tests__/model-hooks.test.ts +0 -130
- package/src/__tests__/model-ref-resolution.test.ts +0 -268
- package/src/__tests__/model-roundtrip.test.ts +0 -502
- package/src/__tests__/model-runtime.test.ts +0 -112
- package/src/__tests__/model-transitions.test.ts +0 -183
- package/src/__tests__/nrt-action-trace.test.ts +0 -391
- package/src/__tests__/pipeline-hardening.test.ts +0 -413
- package/src/__tests__/project-compiler.test.ts +0 -546
- package/src/__tests__/project-decompiler.test.ts +0 -343
- package/src/__tests__/query-compilation.test.ts +0 -145
- package/src/__tests__/round-trip/PLAN.md +0 -158
- package/src/__tests__/round-trip/README.md +0 -52
- package/src/__tests__/round-trip/RESULTS.md +0 -86
- package/src/__tests__/round-trip/fixtures/data-heavy/main.workflow.tsx +0 -55
- package/src/__tests__/round-trip/fixtures/data-heavy/mm.config.ts +0 -11
- package/src/__tests__/round-trip/fixtures/data-heavy/models/contact.ts +0 -54
- package/src/__tests__/round-trip/fixtures/full-workflow/main.workflow.tsx +0 -79
- package/src/__tests__/round-trip/fixtures/full-workflow/mm.config.ts +0 -12
- package/src/__tests__/round-trip/fixtures/full-workflow/models/order.ts +0 -50
- package/src/__tests__/round-trip/fixtures/simple-crud/main.workflow.tsx +0 -25
- package/src/__tests__/round-trip/fixtures/simple-crud/mm.config.ts +0 -11
- package/src/__tests__/round-trip/fixtures/simple-crud/models/task.ts +0 -32
- package/src/__tests__/round-trip/fixtures/view-heavy/main.workflow.tsx +0 -79
- package/src/__tests__/round-trip/fixtures/view-heavy/mm.config.ts +0 -10
- package/src/__tests__/round-trip/round-trip.test.ts +0 -2598
- package/src/__tests__/round-trip-ir.test.ts +0 -300
- package/src/__tests__/round-trip.test.ts +0 -1212
- package/src/__tests__/route-merging.test.ts +0 -372
- package/src/__tests__/router-composition.test.ts +0 -489
- package/src/__tests__/router-extractor.test.ts +0 -176
- package/src/__tests__/server-action-extractor.test.ts +0 -128
- package/src/__tests__/smart-type-inference.test.ts +0 -365
- package/src/__tests__/source-envelope.test.ts +0 -284
- package/src/__tests__/source-fidelity.test.ts +0 -516
- package/src/__tests__/state-extractor.test.ts +0 -115
- package/src/__tests__/strict-mode.test.ts +0 -227
- package/src/__tests__/transition-effect-extractor.test.ts +0 -119
- package/src/__tests__/transition-extractor.test.ts +0 -68
- package/src/__tests__/ts-to-expression.test.ts +0 -462
- package/src/__tests__/type-generator.test.ts +0 -201
- package/src/__tests__/uber-validation.test.ts +0 -502
- package/src/action-compiler.ts +0 -361
- package/src/babel/emitters/experience-transform.ts +0 -199
- package/src/babel/emitters/ir-to-tsx-emitter.ts +0 -110
- package/src/babel/emitters/pure-form-emitter.ts +0 -1023
- package/src/babel/emitters/runtime-glue-emitter.ts +0 -39
- package/src/babel/extractors/change-extractor.ts +0 -199
- package/src/babel/extractors/component-extractor.ts +0 -907
- package/src/babel/extractors/computed-extractor.ts +0 -262
- package/src/babel/extractors/context-extractor.ts +0 -277
- package/src/babel/extractors/during-extractor.ts +0 -295
- package/src/babel/extractors/effect-extractor.ts +0 -340
- package/src/babel/extractors/event-extractor.ts +0 -235
- package/src/babel/extractors/grammar-island-extractor.ts +0 -302
- package/src/babel/extractors/model-extractor.ts +0 -1018
- package/src/babel/extractors/router-extractor.ts +0 -303
- package/src/babel/extractors/server-action-extractor.ts +0 -173
- package/src/babel/extractors/server-action-hook-extractor.ts +0 -72
- package/src/babel/extractors/server-state-extractor.ts +0 -88
- package/src/babel/extractors/state-extractor.ts +0 -214
- package/src/babel/extractors/transition-effect-extractor.ts +0 -176
- package/src/babel/extractors/transition-extractor.ts +0 -143
- package/src/babel/index.ts +0 -24
- package/src/babel/transpilers/ts-to-expression.ts +0 -674
- package/src/babel/visitor.ts +0 -807
- package/src/cli/auth.ts +0 -255
- package/src/cli/build.ts +0 -288
- package/src/cli/deploy.ts +0 -206
- package/src/cli/index.ts +0 -328
- package/src/cli/installer.ts +0 -261
- package/src/cli/lock-file.ts +0 -94
- package/src/cli/mmrc.ts +0 -22
- package/src/cli/pull.ts +0 -172
- package/src/cli/registry-client.ts +0 -175
- package/src/cli/test.ts +0 -397
- package/src/cli/type-generator.ts +0 -243
- package/src/codemod/__tests__/forward.test.ts +0 -239
- package/src/codemod/__tests__/reverse.test.ts +0 -145
- package/src/codemod/__tests__/round-trip.test.ts +0 -137
- package/src/codemod/annotation.ts +0 -97
- package/src/codemod/classify.ts +0 -197
- package/src/codemod/cli.ts +0 -207
- package/src/codemod/control-flow.ts +0 -409
- package/src/codemod/forward.ts +0 -244
- package/src/codemod/import-manager.ts +0 -171
- package/src/codemod/index.ts +0 -120
- package/src/codemod/reverse.ts +0 -197
- package/src/codemod/rules.ts +0 -174
- package/src/codemod/state-transform.ts +0 -126
- package/src/decompiler/ast-builder.ts +0 -538
- package/src/decompiler/config-generator.ts +0 -151
- package/src/decompiler/index.ts +0 -315
- package/src/decompiler/project-decompiler.ts +0 -1776
- package/src/decompiler/project.ts +0 -862
- package/src/decompiler/split-strategy.ts +0 -140
- package/src/decompiler/state-emitter.ts +0 -1053
- package/src/decompiler/sx-emitter.ts +0 -318
- package/src/decompiler/workspace-hydrator.ts +0 -189
- package/src/dev-server.ts +0 -238
- package/src/envelope/fs-tree.ts +0 -217
- package/src/envelope/source-envelope.ts +0 -264
- package/src/envelope.ts +0 -315
- package/src/incremental-compiler.ts +0 -401
- package/src/index.ts +0 -99
- package/src/model-compiler.ts +0 -277
- package/src/project-compiler.ts +0 -1629
- package/src/route-extractor.ts +0 -333
- package/src/testing/index.ts +0 -32
- package/src/testing/snapshot.ts +0 -252
- package/src/testing/test-utils.ts +0 -226
- package/src/types.ts +0 -68
- package/src/vite/index.ts +0 -288
- package/test-compile.mjs +0 -142
- package/tsconfig.json +0 -25
- package/tsup.config.ts +0 -23
- 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
|
-
}
|