sinwan-compiler 0.0.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +27 -0
- package/dist/analyze.d.ts +103 -0
- package/dist/chunk-ba0546ea.js +1848 -0
- package/dist/cli.d.ts +10 -0
- package/dist/cli.js +8 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +329 -0
- package/dist/reactive-wrap.d.ts +80 -0
- package/dist/transform.d.ts +23 -0
- package/docs/README.md +26 -0
- package/docs/analyzer.md +97 -0
- package/docs/api.md +84 -0
- package/docs/architecture.md +75 -0
- package/docs/cli.md +46 -0
- package/docs/plugins.md +60 -0
- package/docs/transform.md +64 -0
- package/package.json +44 -2
|
@@ -0,0 +1,1848 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/cli.ts
|
|
3
|
+
import * as path3 from "path";
|
|
4
|
+
|
|
5
|
+
// src/analyze.ts
|
|
6
|
+
import { parse } from "@babel/parser";
|
|
7
|
+
import * as t2 from "@babel/types";
|
|
8
|
+
import _traverse2 from "@babel/traverse";
|
|
9
|
+
import * as fs from "fs";
|
|
10
|
+
import * as path2 from "path";
|
|
11
|
+
|
|
12
|
+
// src/reactive-wrap.ts
|
|
13
|
+
import * as t from "@babel/types";
|
|
14
|
+
import _traverse from "@babel/traverse";
|
|
15
|
+
import * as path from "path";
|
|
16
|
+
var traverse = typeof _traverse === "function" ? _traverse : _traverse.default ?? _traverse;
|
|
17
|
+
var REACTIVE_SOURCE_MODULES = {
|
|
18
|
+
"sinwan/store": new Set(["createMutable", "createStore"]),
|
|
19
|
+
"sinwan/reactivity": new Set(["signal", "computed"]),
|
|
20
|
+
"sinwan/react-client": new Set(["useState"])
|
|
21
|
+
};
|
|
22
|
+
var COMPONENT_FACTORY_MODULES = {
|
|
23
|
+
sinwan: new Set(["cc"]),
|
|
24
|
+
"sinwan/component": new Set(["cc"])
|
|
25
|
+
};
|
|
26
|
+
var BUILTIN_REACTIVE_PROPS = new Map([
|
|
27
|
+
["For", { reactiveProps: new Set(["each"]) }],
|
|
28
|
+
["Show", { reactiveProps: new Set(["when"]) }],
|
|
29
|
+
["Switch", { reactiveProps: new Set(["when"]) }],
|
|
30
|
+
["Match", { reactiveProps: new Set(["when"]) }],
|
|
31
|
+
["Index", { reactiveProps: new Set(["each"]) }],
|
|
32
|
+
["Key", { reactiveProps: new Set(["when"]) }],
|
|
33
|
+
["Dynamic", { reactiveProps: new Set(["component"]) }],
|
|
34
|
+
["Visible", { reactiveProps: new Set(["when", "style"]) }],
|
|
35
|
+
["Portal", { reactiveProps: new Set(["mount"]) }],
|
|
36
|
+
["Virtual", { reactiveProps: new Set(["each"]) }],
|
|
37
|
+
["Activity", { reactiveProps: new Set(["mode"]) }]
|
|
38
|
+
]);
|
|
39
|
+
function isBuiltinReactiveProp(componentName, attrName) {
|
|
40
|
+
const entry = BUILTIN_REACTIVE_PROPS.get(componentName);
|
|
41
|
+
if (!entry)
|
|
42
|
+
return false;
|
|
43
|
+
return entry.reactiveProps.has(attrName);
|
|
44
|
+
}
|
|
45
|
+
function trackReactiveImports(ast) {
|
|
46
|
+
const names = {
|
|
47
|
+
createMutable: new Set,
|
|
48
|
+
createStore: new Set,
|
|
49
|
+
signal: new Set,
|
|
50
|
+
computed: new Set,
|
|
51
|
+
useState: new Set,
|
|
52
|
+
cc: new Set
|
|
53
|
+
};
|
|
54
|
+
traverse(ast, {
|
|
55
|
+
ImportDeclaration(path2) {
|
|
56
|
+
const source = path2.node.source.value;
|
|
57
|
+
const reactiveAllowed = REACTIVE_SOURCE_MODULES[source];
|
|
58
|
+
const componentAllowed = COMPONENT_FACTORY_MODULES[source];
|
|
59
|
+
if (!reactiveAllowed && !componentAllowed)
|
|
60
|
+
return;
|
|
61
|
+
for (const spec of path2.node.specifiers) {
|
|
62
|
+
if (!t.isImportSpecifier(spec))
|
|
63
|
+
continue;
|
|
64
|
+
const imported = t.isIdentifier(spec.imported) ? spec.imported.name : spec.imported.value;
|
|
65
|
+
if (reactiveAllowed?.has(imported)) {
|
|
66
|
+
const local = spec.local.name;
|
|
67
|
+
names[imported].add(local);
|
|
68
|
+
} else if (componentAllowed?.has(imported)) {
|
|
69
|
+
names.cc.add(spec.local.name);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
return names;
|
|
75
|
+
}
|
|
76
|
+
function isReactiveSourceCall(expr, names) {
|
|
77
|
+
if (!t.isCallExpression(expr))
|
|
78
|
+
return null;
|
|
79
|
+
const callee = expr.callee;
|
|
80
|
+
if (!t.isIdentifier(callee))
|
|
81
|
+
return null;
|
|
82
|
+
if (names.createMutable.has(callee.name)) {
|
|
83
|
+
return { kind: "mutable", isArray: false };
|
|
84
|
+
}
|
|
85
|
+
if (names.createStore.has(callee.name)) {
|
|
86
|
+
return { kind: "mutable", isArray: true };
|
|
87
|
+
}
|
|
88
|
+
if (names.signal.has(callee.name)) {
|
|
89
|
+
return { kind: "signal", isArray: false };
|
|
90
|
+
}
|
|
91
|
+
if (names.computed.has(callee.name)) {
|
|
92
|
+
return { kind: "computed", isArray: false };
|
|
93
|
+
}
|
|
94
|
+
if (names.useState.has(callee.name)) {
|
|
95
|
+
return { kind: "getter", isArray: true };
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
function trackLocalScopeBindings(path2, names, scope) {
|
|
100
|
+
path2.traverse({
|
|
101
|
+
VariableDeclarator(p) {
|
|
102
|
+
const id = p.node.id;
|
|
103
|
+
const init = p.node.init;
|
|
104
|
+
if (!init)
|
|
105
|
+
return;
|
|
106
|
+
const source = isReactiveSourceCall(init, names);
|
|
107
|
+
if (!source)
|
|
108
|
+
return;
|
|
109
|
+
if (source.isArray) {
|
|
110
|
+
if (t.isArrayPattern(id) && id.elements[0]) {
|
|
111
|
+
const first = id.elements[0];
|
|
112
|
+
if (t.isIdentifier(first)) {
|
|
113
|
+
scope.bindings.set(first.name, { kind: source.kind });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (t.isIdentifier(id)) {
|
|
119
|
+
if (source.kind === "mutable") {
|
|
120
|
+
scope.bindings.set(id.name, {
|
|
121
|
+
kind: "mutable",
|
|
122
|
+
root: id.name,
|
|
123
|
+
path: []
|
|
124
|
+
});
|
|
125
|
+
} else {
|
|
126
|
+
scope.bindings.set(id.name, { kind: source.kind });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
path2.traverse({
|
|
132
|
+
VariableDeclarator(p) {
|
|
133
|
+
const id = p.node.id;
|
|
134
|
+
const init = p.node.init;
|
|
135
|
+
if (!init || !t.isIdentifier(init))
|
|
136
|
+
return;
|
|
137
|
+
const initBinding = scope.bindings.get(init.name);
|
|
138
|
+
if (!initBinding || initBinding.kind !== "mutable")
|
|
139
|
+
return;
|
|
140
|
+
if (t.isObjectPattern(id)) {
|
|
141
|
+
for (const prop of id.properties) {
|
|
142
|
+
if (!t.isObjectProperty(prop))
|
|
143
|
+
continue;
|
|
144
|
+
const key = prop.key;
|
|
145
|
+
const value = prop.value;
|
|
146
|
+
if (!t.isIdentifier(key) || !t.isIdentifier(value))
|
|
147
|
+
continue;
|
|
148
|
+
scope.bindings.set(value.name, {
|
|
149
|
+
kind: "mutable",
|
|
150
|
+
root: initBinding.root,
|
|
151
|
+
path: [...initBinding.path, key.name]
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
path2.traverse({
|
|
158
|
+
"FunctionDeclaration|VariableDeclarator"(p) {
|
|
159
|
+
const node = p.node;
|
|
160
|
+
let fn = null;
|
|
161
|
+
let name = null;
|
|
162
|
+
if (t.isFunctionDeclaration(node)) {
|
|
163
|
+
fn = node;
|
|
164
|
+
name = node.id?.name ?? null;
|
|
165
|
+
} else if (t.isVariableDeclarator(node)) {
|
|
166
|
+
const init = node.init;
|
|
167
|
+
if (init && (t.isArrowFunctionExpression(init) || t.isFunctionExpression(init))) {
|
|
168
|
+
fn = init;
|
|
169
|
+
}
|
|
170
|
+
if (t.isIdentifier(node.id)) {
|
|
171
|
+
name = node.id.name;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (!fn || !name || scope.bindings.has(name))
|
|
175
|
+
return;
|
|
176
|
+
const body = fn.body;
|
|
177
|
+
if (body && containsReactiveRead(body, scope)) {
|
|
178
|
+
scope.bindings.set(name, { kind: "getter" });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
function trackPropBindings(scope, params, reactiveProps) {
|
|
184
|
+
if (!params || reactiveProps.size === 0)
|
|
185
|
+
return;
|
|
186
|
+
if (t.isIdentifier(params)) {
|
|
187
|
+
scope.bindings.set(params.name, { kind: "prop" });
|
|
188
|
+
} else if (t.isObjectPattern(params)) {
|
|
189
|
+
for (const prop of params.properties) {
|
|
190
|
+
if (t.isObjectProperty(prop) && t.isIdentifier(prop.value)) {
|
|
191
|
+
const key = prop.key;
|
|
192
|
+
if (t.isIdentifier(key) && reactiveProps.has(key.name)) {
|
|
193
|
+
scope.bindings.set(prop.value.name, { kind: "prop" });
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function getAllPropNames(params) {
|
|
200
|
+
if (!params)
|
|
201
|
+
return new Set;
|
|
202
|
+
if (t.isIdentifier(params))
|
|
203
|
+
return new Set(["*"]);
|
|
204
|
+
if (t.isObjectPattern(params)) {
|
|
205
|
+
const names = new Set;
|
|
206
|
+
for (const prop of params.properties) {
|
|
207
|
+
if (t.isObjectProperty(prop) && t.isIdentifier(prop.key)) {
|
|
208
|
+
names.add(prop.key.name);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
names.add("children");
|
|
212
|
+
return names;
|
|
213
|
+
}
|
|
214
|
+
return new Set;
|
|
215
|
+
}
|
|
216
|
+
function resolveLocalObjectLiteral(fn, name) {
|
|
217
|
+
const body = fn.body;
|
|
218
|
+
if (!t.isBlockStatement(body))
|
|
219
|
+
return null;
|
|
220
|
+
for (const stmt of body.body) {
|
|
221
|
+
if (!t.isVariableDeclaration(stmt))
|
|
222
|
+
continue;
|
|
223
|
+
for (const decl of stmt.declarations) {
|
|
224
|
+
if (!t.isIdentifier(decl.id) || decl.id.name !== name)
|
|
225
|
+
continue;
|
|
226
|
+
if (decl.init && t.isObjectExpression(decl.init)) {
|
|
227
|
+
return decl.init;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
function analyzeObjectLiteralSpread(spread, fullScope, calleeReactive) {
|
|
234
|
+
for (const prop of spread.properties) {
|
|
235
|
+
if (t.isObjectProperty(prop) && !prop.computed) {
|
|
236
|
+
const key = prop.key;
|
|
237
|
+
const name = t.isIdentifier(key) ? key.name : t.isStringLiteral(key) ? key.value : null;
|
|
238
|
+
if (name && containsReactiveValue(prop.value, fullScope)) {
|
|
239
|
+
calleeReactive.add(name);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
function getMemberExpressionRootAndPath(node) {
|
|
245
|
+
const path2 = [];
|
|
246
|
+
let current = node;
|
|
247
|
+
while (t.isMemberExpression(current)) {
|
|
248
|
+
if (current.computed)
|
|
249
|
+
return null;
|
|
250
|
+
if (!t.isIdentifier(current.property))
|
|
251
|
+
return null;
|
|
252
|
+
path2.push(current.property.name);
|
|
253
|
+
current = current.object;
|
|
254
|
+
}
|
|
255
|
+
if (t.isIdentifier(current)) {
|
|
256
|
+
return { root: current.name, path: path2.reverse() };
|
|
257
|
+
}
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
function isReactiveRead(node, scope) {
|
|
261
|
+
if (t.isIdentifier(node)) {
|
|
262
|
+
const binding2 = scope.bindings.get(node.name);
|
|
263
|
+
if (!binding2)
|
|
264
|
+
return false;
|
|
265
|
+
if (binding2.kind === "prop") {
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
if (binding2.kind === "mutable" && binding2.path.length > 0) {
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
const rootPath = getMemberExpressionRootAndPath(node);
|
|
274
|
+
if (!rootPath)
|
|
275
|
+
return false;
|
|
276
|
+
const binding = scope.bindings.get(rootPath.root);
|
|
277
|
+
if (!binding)
|
|
278
|
+
return false;
|
|
279
|
+
if (binding.kind === "prop") {
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
if (binding.kind === "mutable") {
|
|
283
|
+
return true;
|
|
284
|
+
}
|
|
285
|
+
if (binding.kind === "signal" || binding.kind === "computed") {
|
|
286
|
+
return rootPath.path[0] === "value";
|
|
287
|
+
}
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
function isGetterCall(node, scope) {
|
|
291
|
+
const callee = node.callee;
|
|
292
|
+
if (!t.isIdentifier(callee))
|
|
293
|
+
return false;
|
|
294
|
+
const binding = scope.bindings.get(callee.name);
|
|
295
|
+
return binding?.kind === "getter";
|
|
296
|
+
}
|
|
297
|
+
function isReactiveValue(node, scope) {
|
|
298
|
+
if (t.isIdentifier(node)) {
|
|
299
|
+
const binding2 = scope.bindings.get(node.name);
|
|
300
|
+
if (!binding2)
|
|
301
|
+
return false;
|
|
302
|
+
return binding2.kind === "prop" || binding2.kind === "signal" || binding2.kind === "computed" || binding2.kind === "mutable";
|
|
303
|
+
}
|
|
304
|
+
const rootPath = getMemberExpressionRootAndPath(node);
|
|
305
|
+
if (!rootPath)
|
|
306
|
+
return false;
|
|
307
|
+
const binding = scope.bindings.get(rootPath.root);
|
|
308
|
+
if (!binding)
|
|
309
|
+
return false;
|
|
310
|
+
if (binding.kind === "prop" || binding.kind === "mutable") {
|
|
311
|
+
return true;
|
|
312
|
+
}
|
|
313
|
+
if (binding.kind === "signal" || binding.kind === "computed") {
|
|
314
|
+
return rootPath.path[0] === "value";
|
|
315
|
+
}
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
function containsReactiveValue(expr, scope) {
|
|
319
|
+
let found = false;
|
|
320
|
+
function visit(node) {
|
|
321
|
+
if (found)
|
|
322
|
+
return;
|
|
323
|
+
if (!node || typeof node !== "object")
|
|
324
|
+
return;
|
|
325
|
+
if (t.isMemberExpression(node) || t.isIdentifier(node)) {
|
|
326
|
+
if (isReactiveValue(node, scope)) {
|
|
327
|
+
found = true;
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (t.isCallExpression(node) && isGetterCall(node, scope)) {
|
|
332
|
+
found = true;
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
if (t.isArrowFunctionExpression(node) || t.isFunctionExpression(node)) {
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
for (const key of Object.keys(node)) {
|
|
339
|
+
if (key === "loc" || key === "start" || key === "end" || key === "leadingComments" || key === "trailingComments")
|
|
340
|
+
continue;
|
|
341
|
+
const value = node[key];
|
|
342
|
+
if (Array.isArray(value)) {
|
|
343
|
+
for (const item of value) {
|
|
344
|
+
if (item && typeof item === "object")
|
|
345
|
+
visit(item);
|
|
346
|
+
}
|
|
347
|
+
} else if (value && typeof value === "object") {
|
|
348
|
+
visit(value);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
visit(expr);
|
|
353
|
+
return found;
|
|
354
|
+
}
|
|
355
|
+
function containsReactiveRead(expr, scope) {
|
|
356
|
+
let found = false;
|
|
357
|
+
function visit(node) {
|
|
358
|
+
if (found)
|
|
359
|
+
return;
|
|
360
|
+
if (!node || typeof node !== "object")
|
|
361
|
+
return;
|
|
362
|
+
if (t.isMemberExpression(node) || t.isIdentifier(node)) {
|
|
363
|
+
if (isReactiveRead(node, scope)) {
|
|
364
|
+
found = true;
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
if (t.isCallExpression(node) && isGetterCall(node, scope)) {
|
|
369
|
+
found = true;
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
if (t.isArrowFunctionExpression(node) || t.isFunctionExpression(node)) {
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
for (const key of Object.keys(node)) {
|
|
376
|
+
if (key === "loc" || key === "start" || key === "end" || key === "leadingComments" || key === "trailingComments")
|
|
377
|
+
continue;
|
|
378
|
+
const value = node[key];
|
|
379
|
+
if (Array.isArray(value)) {
|
|
380
|
+
for (const item of value) {
|
|
381
|
+
if (item && typeof item === "object")
|
|
382
|
+
visit(item);
|
|
383
|
+
}
|
|
384
|
+
} else if (value && typeof value === "object") {
|
|
385
|
+
visit(value);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
visit(expr);
|
|
390
|
+
return found;
|
|
391
|
+
}
|
|
392
|
+
function getComponentExpressionInfo(exprPath) {
|
|
393
|
+
const parent = exprPath.parentPath;
|
|
394
|
+
if (!parent) {
|
|
395
|
+
return { isComponent: false, componentName: null, attributeName: null };
|
|
396
|
+
}
|
|
397
|
+
let elementPath = null;
|
|
398
|
+
let attributeName = null;
|
|
399
|
+
if (parent.isJSXAttribute && parent.isJSXAttribute()) {
|
|
400
|
+
const attrName = parent.node.name;
|
|
401
|
+
if (t.isJSXIdentifier(attrName)) {
|
|
402
|
+
attributeName = attrName.name;
|
|
403
|
+
}
|
|
404
|
+
const openingElementPath = parent.parentPath;
|
|
405
|
+
elementPath = openingElementPath?.parentPath;
|
|
406
|
+
} else if (parent.isJSXElement && parent.isJSXElement()) {
|
|
407
|
+
elementPath = parent;
|
|
408
|
+
}
|
|
409
|
+
if (!elementPath || !elementPath.isJSXElement || !elementPath.isJSXElement()) {
|
|
410
|
+
return { isComponent: false, componentName: null, attributeName: null };
|
|
411
|
+
}
|
|
412
|
+
const name = elementPath.node.openingElement.name;
|
|
413
|
+
let firstChar = null;
|
|
414
|
+
let componentName = null;
|
|
415
|
+
if (t.isJSXIdentifier(name)) {
|
|
416
|
+
componentName = name.name;
|
|
417
|
+
firstChar = name.name?.[0] ?? null;
|
|
418
|
+
} else if (t.isJSXMemberExpression(name)) {
|
|
419
|
+
let current = name;
|
|
420
|
+
while (t.isJSXMemberExpression(current)) {
|
|
421
|
+
current = current.object;
|
|
422
|
+
}
|
|
423
|
+
if (t.isJSXIdentifier(current)) {
|
|
424
|
+
componentName = current.name;
|
|
425
|
+
firstChar = current.name?.[0] ?? null;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
if (!firstChar || firstChar !== firstChar.toUpperCase() || !/[A-Z]/.test(firstChar)) {
|
|
429
|
+
return { isComponent: false, componentName: null, attributeName: null };
|
|
430
|
+
}
|
|
431
|
+
return { isComponent: true, componentName, attributeName };
|
|
432
|
+
}
|
|
433
|
+
function isReactiveComponentProp(info, componentNames, reactiveProps) {
|
|
434
|
+
if (!info.isComponent || !info.componentName || !info.attributeName) {
|
|
435
|
+
return false;
|
|
436
|
+
}
|
|
437
|
+
if (isBuiltinReactiveProp(info.componentName, info.attributeName)) {
|
|
438
|
+
return true;
|
|
439
|
+
}
|
|
440
|
+
const componentFn = componentNames.get(info.componentName);
|
|
441
|
+
if (!componentFn)
|
|
442
|
+
return false;
|
|
443
|
+
const props = reactiveProps.get(componentFn);
|
|
444
|
+
if (!props)
|
|
445
|
+
return false;
|
|
446
|
+
return props.has(info.attributeName);
|
|
447
|
+
}
|
|
448
|
+
function shouldWrap(expr, scope) {
|
|
449
|
+
if (t.isArrowFunctionExpression(expr) || t.isFunctionExpression(expr))
|
|
450
|
+
return false;
|
|
451
|
+
if (t.isJSXEmptyExpression(expr))
|
|
452
|
+
return false;
|
|
453
|
+
return containsReactiveRead(expr, scope);
|
|
454
|
+
}
|
|
455
|
+
function wrapExpression(expr) {
|
|
456
|
+
return t.arrowFunctionExpression([], expr);
|
|
457
|
+
}
|
|
458
|
+
function createBindingDescriptor(expr, exprPath, options) {
|
|
459
|
+
if (!options.explicitBindings) {
|
|
460
|
+
return wrapExpression(expr);
|
|
461
|
+
}
|
|
462
|
+
const parent = exprPath.parentPath;
|
|
463
|
+
if (parent && parent.isJSXAttribute && parent.isJSXAttribute()) {
|
|
464
|
+
const attrName = parent.node.name.name;
|
|
465
|
+
if (attrName === "style") {
|
|
466
|
+
return t.callExpression(t.identifier("_$bindStyle"), [
|
|
467
|
+
wrapExpression(expr)
|
|
468
|
+
]);
|
|
469
|
+
}
|
|
470
|
+
if (attrName === "class") {
|
|
471
|
+
return t.callExpression(t.identifier("_$bindClass"), [
|
|
472
|
+
wrapExpression(expr)
|
|
473
|
+
]);
|
|
474
|
+
}
|
|
475
|
+
return t.callExpression(t.identifier("_$bindAttr"), [
|
|
476
|
+
t.stringLiteral(attrName),
|
|
477
|
+
wrapExpression(expr)
|
|
478
|
+
]);
|
|
479
|
+
}
|
|
480
|
+
return t.callExpression(t.identifier("_$bindText"), [wrapExpression(expr)]);
|
|
481
|
+
}
|
|
482
|
+
function collectComponentFunctions(ast, ccNames) {
|
|
483
|
+
const functions = new Set;
|
|
484
|
+
const names = new Map;
|
|
485
|
+
const exported = new Set;
|
|
486
|
+
if (ccNames.size === 0)
|
|
487
|
+
return { functions, names, exported };
|
|
488
|
+
traverse(ast, {
|
|
489
|
+
CallExpression(path2) {
|
|
490
|
+
const callee = path2.node.callee;
|
|
491
|
+
if (!t.isIdentifier(callee) || !ccNames.has(callee.name))
|
|
492
|
+
return;
|
|
493
|
+
const firstArg = path2.node.arguments[0];
|
|
494
|
+
if (!firstArg || !t.isFunction(firstArg))
|
|
495
|
+
return;
|
|
496
|
+
const componentFn = firstArg;
|
|
497
|
+
functions.add(componentFn);
|
|
498
|
+
const parent = path2.parentPath;
|
|
499
|
+
if (parent && parent.isVariableDeclarator && parent.isVariableDeclarator()) {
|
|
500
|
+
const id = parent.node.id;
|
|
501
|
+
if (t.isIdentifier(id)) {
|
|
502
|
+
names.set(id.name, componentFn);
|
|
503
|
+
}
|
|
504
|
+
} else if (parent && parent.isExportNamedDeclaration && parent.isExportNamedDeclaration()) {
|
|
505
|
+
const declaration = parent.node.declaration;
|
|
506
|
+
if (t.isVariableDeclaration(declaration)) {
|
|
507
|
+
for (const decl of declaration.declarations) {
|
|
508
|
+
if (t.isIdentifier(decl.id)) {
|
|
509
|
+
names.set(decl.id.name, componentFn);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
traverse(ast, {
|
|
517
|
+
ExportNamedDeclaration(path2) {
|
|
518
|
+
const declaration = path2.node.declaration;
|
|
519
|
+
if (!t.isVariableDeclaration(declaration))
|
|
520
|
+
return;
|
|
521
|
+
for (const decl of declaration.declarations) {
|
|
522
|
+
const init = decl.init;
|
|
523
|
+
if (!init || !t.isCallExpression(init))
|
|
524
|
+
continue;
|
|
525
|
+
const callee = init.callee;
|
|
526
|
+
if (!t.isIdentifier(callee) || !ccNames.has(callee.name))
|
|
527
|
+
continue;
|
|
528
|
+
const firstArg = init.arguments[0];
|
|
529
|
+
if (!firstArg || !t.isFunction(firstArg))
|
|
530
|
+
continue;
|
|
531
|
+
exported.add(firstArg);
|
|
532
|
+
}
|
|
533
|
+
},
|
|
534
|
+
ExportDefaultDeclaration(path2) {
|
|
535
|
+
const decl = path2.node.declaration;
|
|
536
|
+
if (t.isIdentifier(decl)) {
|
|
537
|
+
const componentFn = names.get(decl.name);
|
|
538
|
+
if (componentFn) {
|
|
539
|
+
exported.add(componentFn);
|
|
540
|
+
}
|
|
541
|
+
} else if (t.isCallExpression(decl)) {
|
|
542
|
+
const callee = decl.callee;
|
|
543
|
+
if (t.isIdentifier(callee) && ccNames.has(callee.name)) {
|
|
544
|
+
const firstArg = decl.arguments[0];
|
|
545
|
+
if (firstArg && t.isFunction(firstArg)) {
|
|
546
|
+
exported.add(firstArg);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
return { functions, names, exported };
|
|
553
|
+
}
|
|
554
|
+
function computeLocalScopes(ast, names) {
|
|
555
|
+
const scopes = new Map;
|
|
556
|
+
traverse(ast, {
|
|
557
|
+
Function(path2) {
|
|
558
|
+
const scope = { bindings: new Map };
|
|
559
|
+
trackLocalScopeBindings(path2, names, scope);
|
|
560
|
+
scopes.set(path2.node, scope);
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
return scopes;
|
|
564
|
+
}
|
|
565
|
+
function collectComponentCallGraph(ast, componentNames) {
|
|
566
|
+
const callGraph = new Map;
|
|
567
|
+
if (componentNames.size === 0)
|
|
568
|
+
return callGraph;
|
|
569
|
+
traverse(ast, {
|
|
570
|
+
JSXElement(path2) {
|
|
571
|
+
const name = path2.node.openingElement.name;
|
|
572
|
+
if (!t.isJSXIdentifier(name))
|
|
573
|
+
return;
|
|
574
|
+
const callee = componentNames.get(name.name);
|
|
575
|
+
if (!callee)
|
|
576
|
+
return;
|
|
577
|
+
const callerPath = path2.findParent((p) => p.isFunction());
|
|
578
|
+
const callerFn = callerPath ? callerPath.node : null;
|
|
579
|
+
if (!callerFn)
|
|
580
|
+
return;
|
|
581
|
+
const props = [];
|
|
582
|
+
const spreads = [];
|
|
583
|
+
for (const attr of path2.node.openingElement.attributes) {
|
|
584
|
+
if (t.isJSXSpreadAttribute(attr)) {
|
|
585
|
+
if (attr.argument) {
|
|
586
|
+
spreads.push(attr.argument);
|
|
587
|
+
}
|
|
588
|
+
continue;
|
|
589
|
+
}
|
|
590
|
+
if (t.isJSXAttribute(attr)) {
|
|
591
|
+
const attrName = attr.name.name;
|
|
592
|
+
if (!attrName)
|
|
593
|
+
continue;
|
|
594
|
+
if (t.isJSXExpressionContainer(attr.value)) {
|
|
595
|
+
const expr = attr.value.expression;
|
|
596
|
+
if (!expr || t.isJSXEmptyExpression(expr))
|
|
597
|
+
continue;
|
|
598
|
+
props.push({ name: attrName, value: expr });
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
const childExprs = [];
|
|
603
|
+
for (const child of path2.node.children) {
|
|
604
|
+
if (t.isJSXExpressionContainer(child)) {
|
|
605
|
+
const expr = child.expression;
|
|
606
|
+
if (!expr || t.isJSXEmptyExpression(expr))
|
|
607
|
+
continue;
|
|
608
|
+
childExprs.push(expr);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
if (childExprs.length === 1) {
|
|
612
|
+
props.push({ name: "children", value: childExprs[0] });
|
|
613
|
+
} else if (childExprs.length > 1) {
|
|
614
|
+
props.push({ name: "children", value: t.arrayExpression(childExprs) });
|
|
615
|
+
}
|
|
616
|
+
const sites = callGraph.get(callerFn) ?? [];
|
|
617
|
+
sites.push({ callee, props, spreads });
|
|
618
|
+
callGraph.set(callerFn, sites);
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
return callGraph;
|
|
622
|
+
}
|
|
623
|
+
function buildFullScope(localScopes, fn, reactiveProps) {
|
|
624
|
+
const localScope = localScopes.get(fn);
|
|
625
|
+
if (!localScope)
|
|
626
|
+
return null;
|
|
627
|
+
const fullScope = { bindings: new Map(localScope.bindings) };
|
|
628
|
+
trackPropBindings(fullScope, fn.params[0], reactiveProps);
|
|
629
|
+
return fullScope;
|
|
630
|
+
}
|
|
631
|
+
function propagateReactiveProps(localScopes, callGraph, componentFunctions) {
|
|
632
|
+
const reactiveProps = new Map;
|
|
633
|
+
for (const [callerFn, sites] of callGraph) {
|
|
634
|
+
const localScope = localScopes.get(callerFn);
|
|
635
|
+
if (!localScope)
|
|
636
|
+
continue;
|
|
637
|
+
for (const site of sites) {
|
|
638
|
+
const calleeReactive = reactiveProps.get(site.callee) ?? new Set;
|
|
639
|
+
for (const prop of site.props) {
|
|
640
|
+
if (containsReactiveValue(prop.value, localScope)) {
|
|
641
|
+
calleeReactive.add(prop.name);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
reactiveProps.set(site.callee, calleeReactive);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
const worklist = new Set(componentFunctions);
|
|
648
|
+
while (worklist.size > 0) {
|
|
649
|
+
const callerFn = worklist.values().next().value;
|
|
650
|
+
worklist.delete(callerFn);
|
|
651
|
+
const callerReactive = reactiveProps.get(callerFn) ?? new Set;
|
|
652
|
+
const fullScope = buildFullScope(localScopes, callerFn, callerReactive);
|
|
653
|
+
if (!fullScope)
|
|
654
|
+
continue;
|
|
655
|
+
const sites = callGraph.get(callerFn) ?? [];
|
|
656
|
+
for (const site of sites) {
|
|
657
|
+
const calleeReactive = reactiveProps.get(site.callee) ?? new Set;
|
|
658
|
+
const initialSize = calleeReactive.size;
|
|
659
|
+
for (const prop of site.props) {
|
|
660
|
+
if (containsReactiveValue(prop.value, fullScope)) {
|
|
661
|
+
calleeReactive.add(prop.name);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
for (const spread of site.spreads) {
|
|
665
|
+
const resolved = t.isObjectExpression(spread) ? spread : t.isIdentifier(spread) ? resolveLocalObjectLiteral(callerFn, spread.name) : null;
|
|
666
|
+
if (resolved) {
|
|
667
|
+
analyzeObjectLiteralSpread(resolved, fullScope, calleeReactive);
|
|
668
|
+
} else {
|
|
669
|
+
const allCalleeProps = getAllPropNames(site.callee.params[0]);
|
|
670
|
+
for (const propName of allCalleeProps) {
|
|
671
|
+
calleeReactive.add(propName);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
if (calleeReactive.size > initialSize) {
|
|
676
|
+
reactiveProps.set(site.callee, calleeReactive);
|
|
677
|
+
worklist.add(site.callee);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
return reactiveProps;
|
|
682
|
+
}
|
|
683
|
+
function wrapReactiveExpressions(ast, options = {}) {
|
|
684
|
+
const names = trackReactiveImports(ast);
|
|
685
|
+
const hasAnyImport = Object.values(names).some((s) => s.size > 0);
|
|
686
|
+
if (!hasAnyImport)
|
|
687
|
+
return;
|
|
688
|
+
const {
|
|
689
|
+
functions: componentFunctions,
|
|
690
|
+
names: componentNames,
|
|
691
|
+
exported
|
|
692
|
+
} = collectComponentFunctions(ast, names.cc);
|
|
693
|
+
const localScopes = computeLocalScopes(ast, names);
|
|
694
|
+
const callGraph = collectComponentCallGraph(ast, componentNames);
|
|
695
|
+
const reactiveProps = propagateReactiveProps(localScopes, callGraph, componentFunctions);
|
|
696
|
+
const exportNameByFn = new Map;
|
|
697
|
+
for (const [localName, fn] of componentNames) {
|
|
698
|
+
if (exported.has(fn)) {
|
|
699
|
+
exportNameByFn.set(fn, localName);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
traverse(ast, {
|
|
703
|
+
ExportDefaultDeclaration(path2) {
|
|
704
|
+
const decl = path2.node.declaration;
|
|
705
|
+
if (t.isIdentifier(decl)) {
|
|
706
|
+
const fn = componentNames.get(decl.name);
|
|
707
|
+
if (fn)
|
|
708
|
+
exportNameByFn.set(fn, "default");
|
|
709
|
+
} else if (t.isCallExpression(decl)) {
|
|
710
|
+
const callee = decl.callee;
|
|
711
|
+
if (t.isIdentifier(callee) && names.cc.has(callee.name)) {
|
|
712
|
+
const firstArg = decl.arguments[0];
|
|
713
|
+
if (firstArg && t.isFunction(firstArg)) {
|
|
714
|
+
exportNameByFn.set(firstArg, "default");
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
let metadata = null;
|
|
721
|
+
if (options.analyzeMetadata) {
|
|
722
|
+
metadata = options.analyzeMetadata;
|
|
723
|
+
} else if (options.analyze && options.filename) {
|
|
724
|
+
try {
|
|
725
|
+
metadata = loadMetadata(options.analyze);
|
|
726
|
+
} catch {
|
|
727
|
+
metadata = null;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
const absoluteFilename = options.filename ? path.resolve(options.filename) : null;
|
|
731
|
+
traverse(ast, {
|
|
732
|
+
Function(path2) {
|
|
733
|
+
const scope = localScopes.get(path2.node);
|
|
734
|
+
if (!scope)
|
|
735
|
+
return;
|
|
736
|
+
const isComponent = componentFunctions.has(path2.node);
|
|
737
|
+
if (isComponent) {
|
|
738
|
+
const isExported = exported.has(path2.node);
|
|
739
|
+
if (isExported) {
|
|
740
|
+
let props = null;
|
|
741
|
+
if (metadata && absoluteFilename) {
|
|
742
|
+
const fileProps = metadata.get(absoluteFilename);
|
|
743
|
+
const exportName = exportNameByFn.get(path2.node);
|
|
744
|
+
if (fileProps && exportName) {
|
|
745
|
+
props = fileProps.get(exportName) ?? null;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
if (props) {
|
|
749
|
+
trackPropBindings(scope, path2.node.params[0], props);
|
|
750
|
+
} else {
|
|
751
|
+
const allProps = getAllPropNames(path2.node.params[0]);
|
|
752
|
+
trackPropBindings(scope, path2.node.params[0], allProps);
|
|
753
|
+
}
|
|
754
|
+
} else if (reactiveProps.has(path2.node)) {
|
|
755
|
+
const props = reactiveProps.get(path2.node);
|
|
756
|
+
trackPropBindings(scope, path2.node.params[0], props);
|
|
757
|
+
} else {
|
|
758
|
+
const allProps = getAllPropNames(path2.node.params[0]);
|
|
759
|
+
trackPropBindings(scope, path2.node.params[0], allProps);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
if (scope.bindings.size === 0)
|
|
763
|
+
return;
|
|
764
|
+
path2.traverse({
|
|
765
|
+
JSXExpressionContainer(exprPath) {
|
|
766
|
+
const expr = exprPath.node.expression;
|
|
767
|
+
const compInfo = getComponentExpressionInfo(exprPath);
|
|
768
|
+
if (compInfo.isComponent) {
|
|
769
|
+
if (!isReactiveComponentProp(compInfo, componentNames, reactiveProps)) {
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
if (shouldWrap(expr, scope)) {
|
|
774
|
+
exprPath.replaceWith(t.jsxExpressionContainer(createBindingDescriptor(expr, exprPath, options)));
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// src/analyze.ts
|
|
783
|
+
var traverse2 = typeof _traverse2 === "function" ? _traverse2 : _traverse2.default ?? _traverse2;
|
|
784
|
+
function functionRef(fn) {
|
|
785
|
+
return { start: fn.start ?? -1, end: fn.end ?? -1 };
|
|
786
|
+
}
|
|
787
|
+
function refKey(ref) {
|
|
788
|
+
return `${ref.start}:${ref.end}`;
|
|
789
|
+
}
|
|
790
|
+
function findFunctionByRef(ast, ref) {
|
|
791
|
+
let found = null;
|
|
792
|
+
traverse2(ast, {
|
|
793
|
+
Function(path3) {
|
|
794
|
+
const node = path3.node;
|
|
795
|
+
if (node.start === ref.start && node.end === ref.end) {
|
|
796
|
+
found = node;
|
|
797
|
+
path3.stop();
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
});
|
|
801
|
+
return found;
|
|
802
|
+
}
|
|
803
|
+
function serializeReactiveProps(reactiveProps) {
|
|
804
|
+
const result = {};
|
|
805
|
+
for (const [filePath, fileMap] of reactiveProps) {
|
|
806
|
+
const entry = {};
|
|
807
|
+
for (const [exportName, props] of fileMap) {
|
|
808
|
+
entry[exportName] = Array.from(props);
|
|
809
|
+
}
|
|
810
|
+
result[filePath] = entry;
|
|
811
|
+
}
|
|
812
|
+
return result;
|
|
813
|
+
}
|
|
814
|
+
function serializeModule(mod) {
|
|
815
|
+
const localScopes = {};
|
|
816
|
+
for (const [fn, scope] of mod.localScopes) {
|
|
817
|
+
localScopes[refKey(functionRef(fn))] = {
|
|
818
|
+
bindings: Object.fromEntries(scope.bindings)
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
const localCallGraph = {};
|
|
822
|
+
for (const [callerFn, sites] of mod.localCallGraph) {
|
|
823
|
+
localCallGraph[refKey(functionRef(callerFn))] = sites.map((s) => ({
|
|
824
|
+
callee: functionRef(s.callee),
|
|
825
|
+
props: s.props,
|
|
826
|
+
spreads: s.spreads
|
|
827
|
+
}));
|
|
828
|
+
}
|
|
829
|
+
return {
|
|
830
|
+
imports: Object.fromEntries(mod.imports),
|
|
831
|
+
exports: Object.fromEntries(Array.from(mod.exports.entries()).map(([name, fn]) => [
|
|
832
|
+
name,
|
|
833
|
+
functionRef(fn)
|
|
834
|
+
])),
|
|
835
|
+
defaultExport: mod.defaultExport ? functionRef(mod.defaultExport) : null,
|
|
836
|
+
componentFunctions: Array.from(mod.componentFunctions).map(functionRef),
|
|
837
|
+
localScopes,
|
|
838
|
+
localCallGraph,
|
|
839
|
+
importedCallSites: mod.importedCallSites.map((s) => ({
|
|
840
|
+
caller: functionRef(s.caller),
|
|
841
|
+
source: s.source,
|
|
842
|
+
name: s.name,
|
|
843
|
+
props: s.props,
|
|
844
|
+
spreads: s.spreads
|
|
845
|
+
}))
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
function restoreModule(filePath, serialized) {
|
|
849
|
+
let code;
|
|
850
|
+
try {
|
|
851
|
+
code = fs.readFileSync(filePath, "utf-8");
|
|
852
|
+
} catch {
|
|
853
|
+
return null;
|
|
854
|
+
}
|
|
855
|
+
const ast = parse(code, {
|
|
856
|
+
sourceType: "module",
|
|
857
|
+
plugins: ["jsx", "typescript"]
|
|
858
|
+
});
|
|
859
|
+
const imports = new Map(Object.entries(serialized.imports));
|
|
860
|
+
const exports = new Map;
|
|
861
|
+
for (const [name, ref] of Object.entries(serialized.exports)) {
|
|
862
|
+
const fn = findFunctionByRef(ast, ref);
|
|
863
|
+
if (!fn)
|
|
864
|
+
return null;
|
|
865
|
+
exports.set(name, fn);
|
|
866
|
+
}
|
|
867
|
+
const defaultExport = serialized.defaultExport ? findFunctionByRef(ast, serialized.defaultExport) : null;
|
|
868
|
+
if (serialized.defaultExport && !defaultExport)
|
|
869
|
+
return null;
|
|
870
|
+
const componentFunctions = new Set;
|
|
871
|
+
for (const ref of serialized.componentFunctions) {
|
|
872
|
+
const fn = findFunctionByRef(ast, ref);
|
|
873
|
+
if (!fn)
|
|
874
|
+
return null;
|
|
875
|
+
componentFunctions.add(fn);
|
|
876
|
+
}
|
|
877
|
+
const localScopes = new Map;
|
|
878
|
+
for (const [key, scope] of Object.entries(serialized.localScopes)) {
|
|
879
|
+
const ref = parseRefKey(key);
|
|
880
|
+
if (!ref)
|
|
881
|
+
return null;
|
|
882
|
+
const fn = findFunctionByRef(ast, ref);
|
|
883
|
+
if (!fn)
|
|
884
|
+
return null;
|
|
885
|
+
localScopes.set(fn, {
|
|
886
|
+
bindings: new Map(Object.entries(scope.bindings))
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
const localCallGraph = new Map;
|
|
890
|
+
for (const [key, sites] of Object.entries(serialized.localCallGraph)) {
|
|
891
|
+
const ref = parseRefKey(key);
|
|
892
|
+
if (!ref)
|
|
893
|
+
return null;
|
|
894
|
+
const callerFn = findFunctionByRef(ast, ref);
|
|
895
|
+
if (!callerFn)
|
|
896
|
+
return null;
|
|
897
|
+
localCallGraph.set(callerFn, sites.map((s) => {
|
|
898
|
+
const callee = findFunctionByRef(ast, s.callee);
|
|
899
|
+
if (!callee)
|
|
900
|
+
throw new Error("Failed to restore callee");
|
|
901
|
+
return {
|
|
902
|
+
callee,
|
|
903
|
+
props: s.props,
|
|
904
|
+
spreads: s.spreads
|
|
905
|
+
};
|
|
906
|
+
}));
|
|
907
|
+
}
|
|
908
|
+
const importedCallSites = [];
|
|
909
|
+
for (const s of serialized.importedCallSites) {
|
|
910
|
+
const caller = findFunctionByRef(ast, s.caller);
|
|
911
|
+
if (!caller)
|
|
912
|
+
return null;
|
|
913
|
+
importedCallSites.push({
|
|
914
|
+
caller,
|
|
915
|
+
source: s.source,
|
|
916
|
+
name: s.name,
|
|
917
|
+
props: s.props,
|
|
918
|
+
spreads: s.spreads
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
return {
|
|
922
|
+
filePath,
|
|
923
|
+
imports,
|
|
924
|
+
exports,
|
|
925
|
+
defaultExport,
|
|
926
|
+
componentFunctions,
|
|
927
|
+
localScopes,
|
|
928
|
+
localCallGraph,
|
|
929
|
+
importedCallSites
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
function parseRefKey(key) {
|
|
933
|
+
const parts = key.split(":");
|
|
934
|
+
if (parts.length !== 2)
|
|
935
|
+
return null;
|
|
936
|
+
const startStr = parts[0];
|
|
937
|
+
const endStr = parts[1];
|
|
938
|
+
if (!startStr || !endStr)
|
|
939
|
+
return null;
|
|
940
|
+
const start = Number(startStr);
|
|
941
|
+
const end = Number(endStr);
|
|
942
|
+
if (Number.isNaN(start) || Number.isNaN(end))
|
|
943
|
+
return null;
|
|
944
|
+
return { start, end };
|
|
945
|
+
}
|
|
946
|
+
function collectImports(ast) {
|
|
947
|
+
const imports = new Map;
|
|
948
|
+
traverse2(ast, {
|
|
949
|
+
ImportDeclaration(path3) {
|
|
950
|
+
const source = path3.node.source.value;
|
|
951
|
+
for (const spec of path3.node.specifiers) {
|
|
952
|
+
if (t2.isImportSpecifier(spec)) {
|
|
953
|
+
const localName = spec.local.name;
|
|
954
|
+
const importedName = t2.isIdentifier(spec.imported) ? spec.imported.name : spec.imported.value;
|
|
955
|
+
imports.set(localName, { source, importedName });
|
|
956
|
+
} else if (t2.isImportDefaultSpecifier(spec)) {
|
|
957
|
+
imports.set(spec.local.name, { source, importedName: "default" });
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
});
|
|
962
|
+
return imports;
|
|
963
|
+
}
|
|
964
|
+
function collectImportedCallSites(ast, componentNames, imports) {
|
|
965
|
+
const sites = [];
|
|
966
|
+
traverse2(ast, {
|
|
967
|
+
JSXElement(path3) {
|
|
968
|
+
const name = path3.node.openingElement.name;
|
|
969
|
+
if (!t2.isJSXIdentifier(name))
|
|
970
|
+
return;
|
|
971
|
+
const tagName = name.name;
|
|
972
|
+
if (!/^[A-Z]/.test(tagName))
|
|
973
|
+
return;
|
|
974
|
+
if (componentNames.has(tagName))
|
|
975
|
+
return;
|
|
976
|
+
const importInfo = imports.get(tagName);
|
|
977
|
+
if (!importInfo)
|
|
978
|
+
return;
|
|
979
|
+
const callerPath = path3.findParent((p) => p.isFunction());
|
|
980
|
+
if (!callerPath)
|
|
981
|
+
return;
|
|
982
|
+
const callerFn = callerPath.node;
|
|
983
|
+
const props = [];
|
|
984
|
+
const spreads = [];
|
|
985
|
+
for (const attr of path3.node.openingElement.attributes) {
|
|
986
|
+
if (t2.isJSXSpreadAttribute(attr)) {
|
|
987
|
+
if (attr.argument) {
|
|
988
|
+
spreads.push(attr.argument);
|
|
989
|
+
}
|
|
990
|
+
continue;
|
|
991
|
+
}
|
|
992
|
+
if (t2.isJSXAttribute(attr)) {
|
|
993
|
+
const attrName = attr.name.name;
|
|
994
|
+
if (!attrName)
|
|
995
|
+
continue;
|
|
996
|
+
if (t2.isJSXExpressionContainer(attr.value)) {
|
|
997
|
+
const expr = attr.value.expression;
|
|
998
|
+
if (!expr || t2.isJSXEmptyExpression(expr))
|
|
999
|
+
continue;
|
|
1000
|
+
props.push({ name: attrName, value: expr });
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
const childExprs = [];
|
|
1005
|
+
for (const child of path3.node.children) {
|
|
1006
|
+
if (t2.isJSXExpressionContainer(child)) {
|
|
1007
|
+
const expr = child.expression;
|
|
1008
|
+
if (!expr || t2.isJSXEmptyExpression(expr))
|
|
1009
|
+
continue;
|
|
1010
|
+
childExprs.push(expr);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
if (childExprs.length === 1) {
|
|
1014
|
+
props.push({ name: "children", value: childExprs[0] });
|
|
1015
|
+
} else if (childExprs.length > 1) {
|
|
1016
|
+
props.push({ name: "children", value: t2.arrayExpression(childExprs) });
|
|
1017
|
+
}
|
|
1018
|
+
sites.push({
|
|
1019
|
+
caller: callerFn,
|
|
1020
|
+
source: importInfo.source,
|
|
1021
|
+
name: importInfo.importedName,
|
|
1022
|
+
props,
|
|
1023
|
+
spreads
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
});
|
|
1027
|
+
return sites;
|
|
1028
|
+
}
|
|
1029
|
+
function analyzeModule(code, filePath) {
|
|
1030
|
+
const ast = parse(code, {
|
|
1031
|
+
sourceType: "module",
|
|
1032
|
+
plugins: ["jsx", "typescript"]
|
|
1033
|
+
});
|
|
1034
|
+
const names = trackReactiveImports(ast);
|
|
1035
|
+
const {
|
|
1036
|
+
functions,
|
|
1037
|
+
names: componentNames,
|
|
1038
|
+
exported
|
|
1039
|
+
} = collectComponentFunctions(ast, names.cc);
|
|
1040
|
+
const localScopes = computeLocalScopes(ast, names);
|
|
1041
|
+
const localCallGraph = collectComponentCallGraph(ast, componentNames);
|
|
1042
|
+
const imports = collectImports(ast);
|
|
1043
|
+
const importedCallSites = collectImportedCallSites(ast, componentNames, imports);
|
|
1044
|
+
const exports = new Map;
|
|
1045
|
+
let defaultExport = null;
|
|
1046
|
+
for (const [localName, fn] of componentNames) {
|
|
1047
|
+
if (exported.has(fn)) {
|
|
1048
|
+
exports.set(localName, fn);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
traverse2(ast, {
|
|
1052
|
+
ExportDefaultDeclaration(path3) {
|
|
1053
|
+
const decl = path3.node.declaration;
|
|
1054
|
+
if (t2.isIdentifier(decl)) {
|
|
1055
|
+
const fn = componentNames.get(decl.name);
|
|
1056
|
+
if (fn)
|
|
1057
|
+
defaultExport = fn;
|
|
1058
|
+
} else if (t2.isCallExpression(decl)) {
|
|
1059
|
+
const callee = decl.callee;
|
|
1060
|
+
if (t2.isIdentifier(callee) && names.cc.has(callee.name)) {
|
|
1061
|
+
const firstArg = decl.arguments[0];
|
|
1062
|
+
if (firstArg && t2.isFunction(firstArg)) {
|
|
1063
|
+
defaultExport = firstArg;
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
});
|
|
1069
|
+
return {
|
|
1070
|
+
filePath,
|
|
1071
|
+
imports,
|
|
1072
|
+
exports,
|
|
1073
|
+
defaultExport,
|
|
1074
|
+
componentFunctions: functions,
|
|
1075
|
+
localScopes,
|
|
1076
|
+
localCallGraph,
|
|
1077
|
+
importedCallSites
|
|
1078
|
+
};
|
|
1079
|
+
}
|
|
1080
|
+
function loadTsConfigPaths(tsConfigPath) {
|
|
1081
|
+
try {
|
|
1082
|
+
const raw = JSON.parse(fs.readFileSync(tsConfigPath, "utf-8"));
|
|
1083
|
+
const paths = raw.compilerOptions?.paths;
|
|
1084
|
+
if (!paths)
|
|
1085
|
+
return null;
|
|
1086
|
+
const baseUrl = raw.compilerOptions?.baseUrl;
|
|
1087
|
+
const root = path2.dirname(tsConfigPath);
|
|
1088
|
+
const resolvedBase = baseUrl ? path2.resolve(root, baseUrl) : root;
|
|
1089
|
+
const result = new Map;
|
|
1090
|
+
for (const [pattern, mappings] of Object.entries(paths)) {
|
|
1091
|
+
result.set(pattern, mappings.map((m) => path2.resolve(resolvedBase, m)));
|
|
1092
|
+
}
|
|
1093
|
+
return result;
|
|
1094
|
+
} catch {
|
|
1095
|
+
return null;
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
function matchTsConfigPath(source, paths) {
|
|
1099
|
+
for (const [pattern, mappings] of paths) {
|
|
1100
|
+
if (pattern.endsWith("/*")) {
|
|
1101
|
+
const prefix = pattern.slice(0, -1);
|
|
1102
|
+
if (source.startsWith(prefix)) {
|
|
1103
|
+
const rest = source.slice(prefix.length);
|
|
1104
|
+
for (const mapping of mappings) {
|
|
1105
|
+
if (mapping.endsWith("/*")) {
|
|
1106
|
+
return mapping.slice(0, -1) + rest;
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
} else if (pattern === source) {
|
|
1111
|
+
return mappings[0] ?? null;
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
return null;
|
|
1115
|
+
}
|
|
1116
|
+
function resolveRelativeImport(source, fromFile, extensions) {
|
|
1117
|
+
if (!source.startsWith("."))
|
|
1118
|
+
return null;
|
|
1119
|
+
const base = path2.resolve(path2.dirname(fromFile), source);
|
|
1120
|
+
const candidates = [
|
|
1121
|
+
...extensions.map((ext) => base + ext),
|
|
1122
|
+
...extensions.map((ext) => path2.join(base, "index" + ext))
|
|
1123
|
+
];
|
|
1124
|
+
for (const candidate of candidates) {
|
|
1125
|
+
if (fs.existsSync(candidate))
|
|
1126
|
+
return candidate;
|
|
1127
|
+
}
|
|
1128
|
+
return null;
|
|
1129
|
+
}
|
|
1130
|
+
function parseTomlString(value) {
|
|
1131
|
+
value = value.trim();
|
|
1132
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
1133
|
+
return value.slice(1, -1);
|
|
1134
|
+
}
|
|
1135
|
+
return value;
|
|
1136
|
+
}
|
|
1137
|
+
function parseAliasObject(content, aliases) {
|
|
1138
|
+
const pairs = [];
|
|
1139
|
+
let current = "";
|
|
1140
|
+
let inQuotes = null;
|
|
1141
|
+
for (const char of content) {
|
|
1142
|
+
if (char === '"' || char === "'") {
|
|
1143
|
+
if (inQuotes === char) {
|
|
1144
|
+
inQuotes = null;
|
|
1145
|
+
} else if (!inQuotes) {
|
|
1146
|
+
inQuotes = char;
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
if (char === "," && !inQuotes) {
|
|
1150
|
+
pairs.push(current);
|
|
1151
|
+
current = "";
|
|
1152
|
+
} else {
|
|
1153
|
+
current += char;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
if (current.trim())
|
|
1157
|
+
pairs.push(current);
|
|
1158
|
+
for (const pair of pairs) {
|
|
1159
|
+
const eq = pair.indexOf("=");
|
|
1160
|
+
if (eq < 0)
|
|
1161
|
+
continue;
|
|
1162
|
+
const key = parseTomlString(pair.slice(0, eq));
|
|
1163
|
+
const value = parseTomlString(pair.slice(eq + 1));
|
|
1164
|
+
if (key && value)
|
|
1165
|
+
aliases.set(key, value);
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
function loadBunfigAliases(bunfigPath) {
|
|
1169
|
+
try {
|
|
1170
|
+
const content = fs.readFileSync(bunfigPath, "utf-8");
|
|
1171
|
+
const aliases = new Map;
|
|
1172
|
+
const lines = content.split(`
|
|
1173
|
+
`);
|
|
1174
|
+
let section = null;
|
|
1175
|
+
for (const rawLine of lines) {
|
|
1176
|
+
const line = rawLine.trim();
|
|
1177
|
+
if (!line || line.startsWith("#"))
|
|
1178
|
+
continue;
|
|
1179
|
+
if (line.startsWith("[") && line.endsWith("]")) {
|
|
1180
|
+
section = line.slice(1, -1);
|
|
1181
|
+
continue;
|
|
1182
|
+
}
|
|
1183
|
+
if (section === "install" && line.startsWith("alias")) {
|
|
1184
|
+
const start = line.indexOf("{");
|
|
1185
|
+
const end = line.lastIndexOf("}");
|
|
1186
|
+
if (start >= 0 && end > start) {
|
|
1187
|
+
parseAliasObject(line.slice(start + 1, end), aliases);
|
|
1188
|
+
}
|
|
1189
|
+
continue;
|
|
1190
|
+
}
|
|
1191
|
+
if (section === "install.alias") {
|
|
1192
|
+
const eq = line.indexOf("=");
|
|
1193
|
+
if (eq >= 0) {
|
|
1194
|
+
const key = parseTomlString(line.slice(0, eq));
|
|
1195
|
+
const value = parseTomlString(line.slice(eq + 1));
|
|
1196
|
+
if (key && value)
|
|
1197
|
+
aliases.set(key, value);
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
return aliases.size > 0 ? aliases : null;
|
|
1202
|
+
} catch {
|
|
1203
|
+
return null;
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
function matchBunfigAlias(source, aliases, root) {
|
|
1207
|
+
for (const [alias, target] of aliases) {
|
|
1208
|
+
if (source === alias) {
|
|
1209
|
+
return path2.resolve(root, target);
|
|
1210
|
+
}
|
|
1211
|
+
if (source.startsWith(alias + "/")) {
|
|
1212
|
+
return path2.resolve(root, target, source.slice(alias.length + 1));
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
return null;
|
|
1216
|
+
}
|
|
1217
|
+
function resolveWithExtensions(mapped, extensions) {
|
|
1218
|
+
const candidates = [
|
|
1219
|
+
...extensions.map((ext) => mapped + ext),
|
|
1220
|
+
...extensions.map((ext) => path2.join(mapped, "index" + ext))
|
|
1221
|
+
];
|
|
1222
|
+
for (const candidate of candidates) {
|
|
1223
|
+
if (fs.existsSync(candidate))
|
|
1224
|
+
return candidate;
|
|
1225
|
+
}
|
|
1226
|
+
return null;
|
|
1227
|
+
}
|
|
1228
|
+
function readPackageName(pkgDir) {
|
|
1229
|
+
try {
|
|
1230
|
+
const pkgJson = JSON.parse(fs.readFileSync(path2.join(pkgDir, "package.json"), "utf-8"));
|
|
1231
|
+
return pkgJson.name ?? null;
|
|
1232
|
+
} catch {
|
|
1233
|
+
return null;
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
function resolveWorkspacePackage(source, packages, extensions) {
|
|
1237
|
+
if (packages.length === 0)
|
|
1238
|
+
return null;
|
|
1239
|
+
if (source.startsWith(".") || source.startsWith("/"))
|
|
1240
|
+
return null;
|
|
1241
|
+
let matchedPkg = null;
|
|
1242
|
+
let matchedName = null;
|
|
1243
|
+
for (const pkg of packages) {
|
|
1244
|
+
const pkgName = readPackageName(pkg);
|
|
1245
|
+
if (!pkgName)
|
|
1246
|
+
continue;
|
|
1247
|
+
if (source === pkgName || source.startsWith(pkgName + "/")) {
|
|
1248
|
+
if (!matchedName || pkgName.length > matchedName.length) {
|
|
1249
|
+
matchedName = pkgName;
|
|
1250
|
+
matchedPkg = pkg;
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
if (!matchedPkg || !matchedName)
|
|
1255
|
+
return null;
|
|
1256
|
+
const subpath = source.slice(matchedName.length + 1);
|
|
1257
|
+
let sourceDir;
|
|
1258
|
+
try {
|
|
1259
|
+
const pkgJson = JSON.parse(fs.readFileSync(path2.join(matchedPkg, "package.json"), "utf-8"));
|
|
1260
|
+
if (pkgJson.source) {
|
|
1261
|
+
sourceDir = path2.dirname(path2.resolve(matchedPkg, pkgJson.source));
|
|
1262
|
+
} else {
|
|
1263
|
+
sourceDir = path2.join(matchedPkg, "src");
|
|
1264
|
+
}
|
|
1265
|
+
} catch {
|
|
1266
|
+
sourceDir = path2.join(matchedPkg, "src");
|
|
1267
|
+
}
|
|
1268
|
+
const targetBase = subpath ? path2.join(sourceDir, subpath) : path2.join(sourceDir, "index");
|
|
1269
|
+
return resolveWithExtensions(targetBase, extensions);
|
|
1270
|
+
}
|
|
1271
|
+
function loadPackageJsonWorkspaces(filePath) {
|
|
1272
|
+
try {
|
|
1273
|
+
const pkg = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
1274
|
+
return pkg.workspaces ?? [];
|
|
1275
|
+
} catch {
|
|
1276
|
+
return [];
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
function loadPnpmWorkspaces(filePath) {
|
|
1280
|
+
try {
|
|
1281
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
1282
|
+
const lines = content.split(`
|
|
1283
|
+
`);
|
|
1284
|
+
const patterns = [];
|
|
1285
|
+
let inPackages = false;
|
|
1286
|
+
for (const rawLine of lines) {
|
|
1287
|
+
const line = rawLine.trim();
|
|
1288
|
+
if (!line || line.startsWith("#"))
|
|
1289
|
+
continue;
|
|
1290
|
+
if (line.startsWith("packages:")) {
|
|
1291
|
+
inPackages = true;
|
|
1292
|
+
continue;
|
|
1293
|
+
}
|
|
1294
|
+
if (inPackages) {
|
|
1295
|
+
if (!line.startsWith("-") && !line.startsWith(" ")) {
|
|
1296
|
+
inPackages = false;
|
|
1297
|
+
continue;
|
|
1298
|
+
}
|
|
1299
|
+
const match = line.match(/^-\s*["']?([^"']+)["']?$/);
|
|
1300
|
+
if (match)
|
|
1301
|
+
patterns.push(match[1]);
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
return patterns;
|
|
1305
|
+
} catch {
|
|
1306
|
+
return [];
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
function expandGlob(pattern, cwd) {
|
|
1310
|
+
const fullPattern = path2.resolve(cwd, pattern);
|
|
1311
|
+
const parts = fullPattern.split(path2.sep);
|
|
1312
|
+
const wildcardIndex = parts.findIndex((p) => p.includes("*"));
|
|
1313
|
+
if (wildcardIndex === -1) {
|
|
1314
|
+
return fs.existsSync(fullPattern) ? [fullPattern] : [];
|
|
1315
|
+
}
|
|
1316
|
+
const prefix = parts.slice(0, wildcardIndex).join(path2.sep);
|
|
1317
|
+
const suffixParts = parts.slice(wildcardIndex + 1);
|
|
1318
|
+
const suffix = suffixParts.join(path2.sep);
|
|
1319
|
+
let entries;
|
|
1320
|
+
try {
|
|
1321
|
+
entries = fs.readdirSync(prefix, { withFileTypes: true });
|
|
1322
|
+
} catch {
|
|
1323
|
+
return [];
|
|
1324
|
+
}
|
|
1325
|
+
const results = [];
|
|
1326
|
+
for (const entry of entries) {
|
|
1327
|
+
if (!entry.isDirectory())
|
|
1328
|
+
continue;
|
|
1329
|
+
const candidate = path2.join(prefix, entry.name, suffix);
|
|
1330
|
+
results.push(...expandGlob(candidate, cwd));
|
|
1331
|
+
}
|
|
1332
|
+
return results;
|
|
1333
|
+
}
|
|
1334
|
+
function loadWorkspacePackages(workspaces, root) {
|
|
1335
|
+
const patternEntries = [];
|
|
1336
|
+
if (typeof workspaces === "string") {
|
|
1337
|
+
const resolved = path2.resolve(root, workspaces);
|
|
1338
|
+
if (fs.existsSync(resolved)) {
|
|
1339
|
+
const stat = fs.statSync(resolved);
|
|
1340
|
+
if (stat.isDirectory()) {
|
|
1341
|
+
patternEntries.push({ pattern: workspaces, base: root });
|
|
1342
|
+
} else if (resolved.endsWith("package.json")) {
|
|
1343
|
+
const base = path2.dirname(resolved);
|
|
1344
|
+
for (const p of loadPackageJsonWorkspaces(resolved)) {
|
|
1345
|
+
patternEntries.push({ pattern: p, base });
|
|
1346
|
+
}
|
|
1347
|
+
} else if (resolved.endsWith("pnpm-workspace.yaml")) {
|
|
1348
|
+
const base = path2.dirname(resolved);
|
|
1349
|
+
for (const p of loadPnpmWorkspaces(resolved)) {
|
|
1350
|
+
patternEntries.push({ pattern: p, base });
|
|
1351
|
+
}
|
|
1352
|
+
} else {
|
|
1353
|
+
patternEntries.push({ pattern: workspaces, base: root });
|
|
1354
|
+
}
|
|
1355
|
+
} else {
|
|
1356
|
+
patternEntries.push({ pattern: workspaces, base: root });
|
|
1357
|
+
}
|
|
1358
|
+
} else if (Array.isArray(workspaces)) {
|
|
1359
|
+
for (const p of workspaces) {
|
|
1360
|
+
patternEntries.push({ pattern: p, base: root });
|
|
1361
|
+
}
|
|
1362
|
+
} else if (workspaces && typeof workspaces === "object") {
|
|
1363
|
+
if (workspaces.file) {
|
|
1364
|
+
const resolved = path2.resolve(root, workspaces.file);
|
|
1365
|
+
if (resolved.endsWith("package.json")) {
|
|
1366
|
+
const base = path2.dirname(resolved);
|
|
1367
|
+
for (const p of loadPackageJsonWorkspaces(resolved)) {
|
|
1368
|
+
patternEntries.push({ pattern: p, base });
|
|
1369
|
+
}
|
|
1370
|
+
} else if (resolved.endsWith("pnpm-workspace.yaml")) {
|
|
1371
|
+
const base = path2.dirname(resolved);
|
|
1372
|
+
for (const p of loadPnpmWorkspaces(resolved)) {
|
|
1373
|
+
patternEntries.push({ pattern: p, base });
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
if (workspaces.include) {
|
|
1378
|
+
for (const p of workspaces.include) {
|
|
1379
|
+
patternEntries.push({ pattern: p, base: root });
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
const packages = new Set;
|
|
1384
|
+
for (const { pattern, base } of patternEntries) {
|
|
1385
|
+
for (const dir of expandGlob(pattern, base)) {
|
|
1386
|
+
const pkgJson = path2.join(dir, "package.json");
|
|
1387
|
+
if (fs.existsSync(pkgJson)) {
|
|
1388
|
+
packages.add(dir);
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
return Array.from(packages);
|
|
1393
|
+
}
|
|
1394
|
+
function createResolver(root, extensions, tsConfigPath, bunfigPath, workspaces) {
|
|
1395
|
+
const paths = tsConfigPath ? loadTsConfigPaths(tsConfigPath) : null;
|
|
1396
|
+
const aliases = bunfigPath ? loadBunfigAliases(bunfigPath) : null;
|
|
1397
|
+
const packages = workspaces ? loadWorkspacePackages(workspaces, root) : [];
|
|
1398
|
+
return (source, fromFile) => {
|
|
1399
|
+
if (paths) {
|
|
1400
|
+
const mapped = matchTsConfigPath(source, paths);
|
|
1401
|
+
if (mapped) {
|
|
1402
|
+
const resolved = resolveWithExtensions(mapped, extensions);
|
|
1403
|
+
if (resolved)
|
|
1404
|
+
return resolved;
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
if (aliases) {
|
|
1408
|
+
const mapped = matchBunfigAlias(source, aliases, root);
|
|
1409
|
+
if (mapped) {
|
|
1410
|
+
const resolved = resolveWithExtensions(mapped, extensions);
|
|
1411
|
+
if (resolved)
|
|
1412
|
+
return resolved;
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
const pkgResolved = resolveWorkspacePackage(source, packages, extensions);
|
|
1416
|
+
if (pkgResolved)
|
|
1417
|
+
return pkgResolved;
|
|
1418
|
+
return resolveRelativeImport(source, fromFile, extensions);
|
|
1419
|
+
};
|
|
1420
|
+
}
|
|
1421
|
+
function findFiles(root, extensions) {
|
|
1422
|
+
const files = [];
|
|
1423
|
+
function walk(dir) {
|
|
1424
|
+
let entries;
|
|
1425
|
+
try {
|
|
1426
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
1427
|
+
} catch {
|
|
1428
|
+
return;
|
|
1429
|
+
}
|
|
1430
|
+
for (const entry of entries) {
|
|
1431
|
+
const fullPath = path2.join(dir, entry.name);
|
|
1432
|
+
if (entry.isDirectory()) {
|
|
1433
|
+
if (entry.name === "node_modules" || entry.name === ".git")
|
|
1434
|
+
continue;
|
|
1435
|
+
walk(fullPath);
|
|
1436
|
+
} else if (entry.isFile() && extensions.some((ext) => entry.name.endsWith(ext))) {
|
|
1437
|
+
files.push(fullPath);
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
walk(root);
|
|
1442
|
+
return files;
|
|
1443
|
+
}
|
|
1444
|
+
function analyzeProject(options) {
|
|
1445
|
+
const extensions = options.extensions ?? [".tsx", ".ts", ".jsx", ".js"];
|
|
1446
|
+
const resolve3 = options.resolve ?? createResolver(options.root, extensions, options.tsConfigPath, options.bunfigPath, options.workspaces);
|
|
1447
|
+
const modules = new Map;
|
|
1448
|
+
const providedFiles = options.files;
|
|
1449
|
+
if (providedFiles) {
|
|
1450
|
+
for (const [filePath, code] of Object.entries(providedFiles)) {
|
|
1451
|
+
modules.set(filePath, analyzeModule(code, filePath));
|
|
1452
|
+
}
|
|
1453
|
+
} else {
|
|
1454
|
+
const files = findFiles(options.root, extensions);
|
|
1455
|
+
for (const file of files) {
|
|
1456
|
+
const code = fs.readFileSync(file, "utf-8");
|
|
1457
|
+
modules.set(file, analyzeModule(code, file));
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
function ensureAnalyzed(filePath) {
|
|
1461
|
+
if (modules.has(filePath))
|
|
1462
|
+
return modules.get(filePath);
|
|
1463
|
+
try {
|
|
1464
|
+
const code = fs.readFileSync(filePath, "utf-8");
|
|
1465
|
+
const mod = analyzeModule(code, filePath);
|
|
1466
|
+
modules.set(filePath, mod);
|
|
1467
|
+
return mod;
|
|
1468
|
+
} catch {
|
|
1469
|
+
return;
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
const globalLocalScopes = new Map;
|
|
1473
|
+
const globalCallGraph = new Map;
|
|
1474
|
+
const globalComponentFunctions = new Set;
|
|
1475
|
+
for (const mod of modules.values()) {
|
|
1476
|
+
for (const [fn, scope] of mod.localScopes) {
|
|
1477
|
+
globalLocalScopes.set(fn, scope);
|
|
1478
|
+
}
|
|
1479
|
+
for (const fn of mod.componentFunctions) {
|
|
1480
|
+
globalComponentFunctions.add(fn);
|
|
1481
|
+
}
|
|
1482
|
+
for (const [callerFn, sites] of mod.localCallGraph) {
|
|
1483
|
+
const list = globalCallGraph.get(callerFn) ?? [];
|
|
1484
|
+
list.push(...sites);
|
|
1485
|
+
globalCallGraph.set(callerFn, list);
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
const reactiveProps = buildProjectReactiveProps(modules, resolve3, globalLocalScopes, globalCallGraph, globalComponentFunctions, ensureAnalyzed);
|
|
1489
|
+
return { modules, reactiveProps };
|
|
1490
|
+
}
|
|
1491
|
+
function buildProjectReactiveProps(modules, resolve3, globalLocalScopes, globalCallGraph, globalComponentFunctions, ensureAnalyzed) {
|
|
1492
|
+
for (const mod of modules.values()) {
|
|
1493
|
+
for (const site of mod.importedCallSites) {
|
|
1494
|
+
const resolvedFile = resolve3(site.source, mod.filePath);
|
|
1495
|
+
if (!resolvedFile)
|
|
1496
|
+
continue;
|
|
1497
|
+
let targetMod = modules.get(resolvedFile);
|
|
1498
|
+
if (!targetMod && ensureAnalyzed) {
|
|
1499
|
+
targetMod = ensureAnalyzed(resolvedFile);
|
|
1500
|
+
if (targetMod) {
|
|
1501
|
+
for (const [fn, scope] of targetMod.localScopes) {
|
|
1502
|
+
globalLocalScopes.set(fn, scope);
|
|
1503
|
+
}
|
|
1504
|
+
for (const fn of targetMod.componentFunctions) {
|
|
1505
|
+
globalComponentFunctions.add(fn);
|
|
1506
|
+
}
|
|
1507
|
+
for (const [callerFn, sites] of targetMod.localCallGraph) {
|
|
1508
|
+
const list2 = globalCallGraph.get(callerFn) ?? [];
|
|
1509
|
+
list2.push(...sites);
|
|
1510
|
+
globalCallGraph.set(callerFn, list2);
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
if (!targetMod)
|
|
1515
|
+
continue;
|
|
1516
|
+
const targetFn = site.name === "default" ? targetMod.defaultExport : targetMod.exports.get(site.name);
|
|
1517
|
+
if (!targetFn)
|
|
1518
|
+
continue;
|
|
1519
|
+
const list = globalCallGraph.get(site.caller) ?? [];
|
|
1520
|
+
list.push({
|
|
1521
|
+
callee: targetFn,
|
|
1522
|
+
props: site.props,
|
|
1523
|
+
spreads: site.spreads
|
|
1524
|
+
});
|
|
1525
|
+
globalCallGraph.set(site.caller, list);
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
const globalReactiveProps = propagateReactiveProps(globalLocalScopes, globalCallGraph, globalComponentFunctions);
|
|
1529
|
+
const reactiveProps = new Map;
|
|
1530
|
+
for (const mod of modules.values()) {
|
|
1531
|
+
const fileMap = new Map;
|
|
1532
|
+
for (const [exportName, fn] of mod.exports) {
|
|
1533
|
+
const props = globalReactiveProps.get(fn);
|
|
1534
|
+
fileMap.set(exportName, props ? new Set(props) : new Set);
|
|
1535
|
+
}
|
|
1536
|
+
if (mod.defaultExport) {
|
|
1537
|
+
const props = globalReactiveProps.get(mod.defaultExport);
|
|
1538
|
+
fileMap.set("default", props ? new Set(props) : new Set);
|
|
1539
|
+
}
|
|
1540
|
+
reactiveProps.set(mod.filePath, fileMap);
|
|
1541
|
+
}
|
|
1542
|
+
return reactiveProps;
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
class AnalyzerCache {
|
|
1546
|
+
root;
|
|
1547
|
+
extensions;
|
|
1548
|
+
tsConfigPath;
|
|
1549
|
+
bunfigPath;
|
|
1550
|
+
workspaces;
|
|
1551
|
+
cachePath;
|
|
1552
|
+
resolve;
|
|
1553
|
+
modules = new Map;
|
|
1554
|
+
reactiveProps = new Map;
|
|
1555
|
+
importers = new Map;
|
|
1556
|
+
constructor(options) {
|
|
1557
|
+
this.root = options.root;
|
|
1558
|
+
this.extensions = options.extensions ?? [".tsx", ".ts", ".jsx", ".js"];
|
|
1559
|
+
this.tsConfigPath = options.tsConfigPath;
|
|
1560
|
+
this.bunfigPath = options.bunfigPath;
|
|
1561
|
+
this.workspaces = options.workspaces;
|
|
1562
|
+
this.cachePath = options.cachePath;
|
|
1563
|
+
const bunfigPath = options.bunfigPath;
|
|
1564
|
+
const diskResolver = options.resolve ?? createResolver(this.root, this.extensions, this.tsConfigPath, bunfigPath, this.workspaces);
|
|
1565
|
+
this.resolve = (source, fromFile) => {
|
|
1566
|
+
if (source.startsWith(".")) {
|
|
1567
|
+
const base = path2.resolve(path2.dirname(fromFile), source);
|
|
1568
|
+
for (const ext of this.extensions) {
|
|
1569
|
+
if (this.modules.has(base + ext))
|
|
1570
|
+
return base + ext;
|
|
1571
|
+
const indexCandidate = path2.join(base, "index" + ext);
|
|
1572
|
+
if (this.modules.has(indexCandidate))
|
|
1573
|
+
return indexCandidate;
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
return diskResolver(source, fromFile);
|
|
1577
|
+
};
|
|
1578
|
+
if (this.cachePath) {
|
|
1579
|
+
this.restore();
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
update(filePath, code) {
|
|
1583
|
+
const oldMod = this.modules.get(filePath);
|
|
1584
|
+
const newMod = analyzeModule(code, filePath);
|
|
1585
|
+
this.modules.set(filePath, newMod);
|
|
1586
|
+
if (oldMod)
|
|
1587
|
+
this.removeImporters(oldMod);
|
|
1588
|
+
this.addImporters(newMod);
|
|
1589
|
+
this.recomputeFor([filePath]);
|
|
1590
|
+
this.save();
|
|
1591
|
+
}
|
|
1592
|
+
remove(filePath) {
|
|
1593
|
+
const oldMod = this.modules.get(filePath);
|
|
1594
|
+
const affectedImporters = new Set(this.importers.get(filePath) ?? []);
|
|
1595
|
+
this.modules.delete(filePath);
|
|
1596
|
+
this.importers.delete(filePath);
|
|
1597
|
+
if (oldMod)
|
|
1598
|
+
this.removeImporters(oldMod);
|
|
1599
|
+
this.recomputeFor(affectedImporters);
|
|
1600
|
+
this.save();
|
|
1601
|
+
}
|
|
1602
|
+
save() {
|
|
1603
|
+
if (!this.cachePath)
|
|
1604
|
+
return;
|
|
1605
|
+
const data = {
|
|
1606
|
+
version: 1,
|
|
1607
|
+
modules: Object.fromEntries(Array.from(this.modules.entries()).map(([filePath, mod]) => [
|
|
1608
|
+
filePath,
|
|
1609
|
+
serializeModule(mod)
|
|
1610
|
+
])),
|
|
1611
|
+
reactiveProps: serializeReactiveProps(this.reactiveProps)
|
|
1612
|
+
};
|
|
1613
|
+
const dir = path2.dirname(this.cachePath);
|
|
1614
|
+
if (!fs.existsSync(dir)) {
|
|
1615
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1616
|
+
}
|
|
1617
|
+
fs.writeFileSync(this.cachePath, JSON.stringify(data, null, 2));
|
|
1618
|
+
}
|
|
1619
|
+
restore() {
|
|
1620
|
+
if (!this.cachePath || !fs.existsSync(this.cachePath))
|
|
1621
|
+
return;
|
|
1622
|
+
let data;
|
|
1623
|
+
try {
|
|
1624
|
+
data = JSON.parse(fs.readFileSync(this.cachePath, "utf-8"));
|
|
1625
|
+
} catch {
|
|
1626
|
+
return;
|
|
1627
|
+
}
|
|
1628
|
+
if (data.version !== 1)
|
|
1629
|
+
return;
|
|
1630
|
+
for (const [filePath, serialized] of Object.entries(data.modules)) {
|
|
1631
|
+
const mod = restoreModule(filePath, serialized);
|
|
1632
|
+
if (mod) {
|
|
1633
|
+
this.modules.set(filePath, mod);
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
this.importers = new Map;
|
|
1637
|
+
for (const mod of this.modules.values()) {
|
|
1638
|
+
this.addImporters(mod);
|
|
1639
|
+
}
|
|
1640
|
+
this.reactiveProps = new Map;
|
|
1641
|
+
for (const [filePath, fileMap] of Object.entries(data.reactiveProps)) {
|
|
1642
|
+
const map = new Map;
|
|
1643
|
+
for (const [exportName, props] of Object.entries(fileMap)) {
|
|
1644
|
+
map.set(exportName, new Set(props));
|
|
1645
|
+
}
|
|
1646
|
+
this.reactiveProps.set(filePath, map);
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
addImporters(mod) {
|
|
1650
|
+
for (const site of mod.importedCallSites) {
|
|
1651
|
+
const resolvedFile = this.resolve(site.source, mod.filePath);
|
|
1652
|
+
if (!resolvedFile)
|
|
1653
|
+
continue;
|
|
1654
|
+
let set = this.importers.get(resolvedFile);
|
|
1655
|
+
if (!set) {
|
|
1656
|
+
set = new Set;
|
|
1657
|
+
this.importers.set(resolvedFile, set);
|
|
1658
|
+
}
|
|
1659
|
+
set.add(mod.filePath);
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
removeImporters(mod) {
|
|
1663
|
+
for (const site of mod.importedCallSites) {
|
|
1664
|
+
const resolvedFile = this.resolve(site.source, mod.filePath);
|
|
1665
|
+
if (!resolvedFile)
|
|
1666
|
+
continue;
|
|
1667
|
+
this.importers.get(resolvedFile)?.delete(mod.filePath);
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
ensureAnalyzed(filePath) {
|
|
1671
|
+
if (this.modules.has(filePath))
|
|
1672
|
+
return this.modules.get(filePath);
|
|
1673
|
+
try {
|
|
1674
|
+
const code = fs.readFileSync(filePath, "utf-8");
|
|
1675
|
+
const mod = analyzeModule(code, filePath);
|
|
1676
|
+
this.modules.set(filePath, mod);
|
|
1677
|
+
return mod;
|
|
1678
|
+
} catch {
|
|
1679
|
+
return;
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
collectAffected(filePaths) {
|
|
1683
|
+
const affected = new Set;
|
|
1684
|
+
const queue = Array.from(filePaths);
|
|
1685
|
+
while (queue.length > 0) {
|
|
1686
|
+
const current = queue.pop();
|
|
1687
|
+
if (affected.has(current))
|
|
1688
|
+
continue;
|
|
1689
|
+
let mod = this.modules.get(current);
|
|
1690
|
+
if (!mod) {
|
|
1691
|
+
mod = this.ensureAnalyzed(current);
|
|
1692
|
+
if (mod)
|
|
1693
|
+
this.addImporters(mod);
|
|
1694
|
+
}
|
|
1695
|
+
if (!mod)
|
|
1696
|
+
continue;
|
|
1697
|
+
affected.add(current);
|
|
1698
|
+
for (const importer of this.importers.get(current) ?? []) {
|
|
1699
|
+
if (!affected.has(importer))
|
|
1700
|
+
queue.push(importer);
|
|
1701
|
+
}
|
|
1702
|
+
for (const site of mod.importedCallSites) {
|
|
1703
|
+
const resolved = this.resolve(site.source, mod.filePath);
|
|
1704
|
+
if (resolved && !affected.has(resolved)) {
|
|
1705
|
+
queue.push(resolved);
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
return affected;
|
|
1710
|
+
}
|
|
1711
|
+
recomputeFor(filePaths) {
|
|
1712
|
+
const affected = this.collectAffected(filePaths);
|
|
1713
|
+
const subgraphModules = new Map;
|
|
1714
|
+
for (const filePath of affected) {
|
|
1715
|
+
const mod = this.modules.get(filePath);
|
|
1716
|
+
if (mod)
|
|
1717
|
+
subgraphModules.set(filePath, mod);
|
|
1718
|
+
}
|
|
1719
|
+
const globalLocalScopes = new Map;
|
|
1720
|
+
const globalCallGraph = new Map;
|
|
1721
|
+
const globalComponentFunctions = new Set;
|
|
1722
|
+
for (const mod of subgraphModules.values()) {
|
|
1723
|
+
for (const [fn, scope] of mod.localScopes) {
|
|
1724
|
+
globalLocalScopes.set(fn, scope);
|
|
1725
|
+
}
|
|
1726
|
+
for (const fn of mod.componentFunctions) {
|
|
1727
|
+
globalComponentFunctions.add(fn);
|
|
1728
|
+
}
|
|
1729
|
+
for (const [callerFn, sites] of mod.localCallGraph) {
|
|
1730
|
+
const list = globalCallGraph.get(callerFn) ?? [];
|
|
1731
|
+
list.push(...sites);
|
|
1732
|
+
globalCallGraph.set(callerFn, list);
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
const subgraphReactiveProps = buildProjectReactiveProps(subgraphModules, this.resolve, globalLocalScopes, globalCallGraph, globalComponentFunctions);
|
|
1736
|
+
for (const filePath of affected) {
|
|
1737
|
+
const map = subgraphReactiveProps.get(filePath);
|
|
1738
|
+
if (map) {
|
|
1739
|
+
this.reactiveProps.set(filePath, map);
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
recompute() {
|
|
1744
|
+
const affected = this.collectAffected(Array.from(this.modules.keys()));
|
|
1745
|
+
const subgraphModules = new Map;
|
|
1746
|
+
for (const filePath of affected) {
|
|
1747
|
+
const mod = this.modules.get(filePath);
|
|
1748
|
+
if (mod)
|
|
1749
|
+
subgraphModules.set(filePath, mod);
|
|
1750
|
+
}
|
|
1751
|
+
const globalLocalScopes = new Map;
|
|
1752
|
+
const globalCallGraph = new Map;
|
|
1753
|
+
const globalComponentFunctions = new Set;
|
|
1754
|
+
for (const mod of subgraphModules.values()) {
|
|
1755
|
+
for (const [fn, scope] of mod.localScopes) {
|
|
1756
|
+
globalLocalScopes.set(fn, scope);
|
|
1757
|
+
}
|
|
1758
|
+
for (const fn of mod.componentFunctions) {
|
|
1759
|
+
globalComponentFunctions.add(fn);
|
|
1760
|
+
}
|
|
1761
|
+
for (const [callerFn, sites] of mod.localCallGraph) {
|
|
1762
|
+
const list = globalCallGraph.get(callerFn) ?? [];
|
|
1763
|
+
list.push(...sites);
|
|
1764
|
+
globalCallGraph.set(callerFn, list);
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
this.reactiveProps = buildProjectReactiveProps(subgraphModules, this.resolve, globalLocalScopes, globalCallGraph, globalComponentFunctions);
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
function analyze(options) {
|
|
1771
|
+
const tsConfigPath = options.tsConfigPath ?? path2.join(options.root, "tsconfig.json");
|
|
1772
|
+
const bunfigPath = options.bunfigPath ?? path2.join(options.root, "bunfig.toml");
|
|
1773
|
+
const project = analyzeProject({
|
|
1774
|
+
root: options.root,
|
|
1775
|
+
extensions: options.extensions,
|
|
1776
|
+
resolve: options.resolve,
|
|
1777
|
+
tsConfigPath: fs.existsSync(tsConfigPath) ? tsConfigPath : undefined,
|
|
1778
|
+
bunfigPath: fs.existsSync(bunfigPath) ? bunfigPath : undefined,
|
|
1779
|
+
workspaces: options.workspaces
|
|
1780
|
+
});
|
|
1781
|
+
const serialized = {};
|
|
1782
|
+
for (const [filePath, fileMap] of project.reactiveProps) {
|
|
1783
|
+
const entry = {};
|
|
1784
|
+
for (const [exportName, props] of fileMap) {
|
|
1785
|
+
entry[exportName] = Array.from(props);
|
|
1786
|
+
}
|
|
1787
|
+
serialized[filePath] = entry;
|
|
1788
|
+
}
|
|
1789
|
+
const dir = path2.dirname(options.outFile);
|
|
1790
|
+
if (!fs.existsSync(dir)) {
|
|
1791
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1792
|
+
}
|
|
1793
|
+
fs.writeFileSync(options.outFile, JSON.stringify(serialized, null, 2));
|
|
1794
|
+
}
|
|
1795
|
+
function loadMetadata(filePath) {
|
|
1796
|
+
if (!fs.existsSync(filePath)) {
|
|
1797
|
+
return new Map;
|
|
1798
|
+
}
|
|
1799
|
+
const raw = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
1800
|
+
const result = new Map;
|
|
1801
|
+
for (const [filePath2, fileMap] of Object.entries(raw)) {
|
|
1802
|
+
const map = new Map;
|
|
1803
|
+
for (const [exportName, props] of Object.entries(fileMap)) {
|
|
1804
|
+
map.set(exportName, new Set(props));
|
|
1805
|
+
}
|
|
1806
|
+
result.set(filePath2, map);
|
|
1807
|
+
}
|
|
1808
|
+
return result;
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
// src/cli.ts
|
|
1812
|
+
function runAnalyzeCli(args = process.argv.slice(2)) {
|
|
1813
|
+
let command;
|
|
1814
|
+
let root;
|
|
1815
|
+
let outFile;
|
|
1816
|
+
let tsConfigPath;
|
|
1817
|
+
let bunfigPath;
|
|
1818
|
+
let workspaces;
|
|
1819
|
+
for (let i = 0;i < args.length; i++) {
|
|
1820
|
+
const arg = args[i];
|
|
1821
|
+
if (arg === "--tsconfig" || arg === "-t") {
|
|
1822
|
+
tsConfigPath = args[++i];
|
|
1823
|
+
} else if (arg === "--bunfig" || arg === "-b") {
|
|
1824
|
+
bunfigPath = args[++i];
|
|
1825
|
+
} else if (arg === "--workspaces" || arg === "-w") {
|
|
1826
|
+
workspaces = args[++i];
|
|
1827
|
+
} else if (command === undefined) {
|
|
1828
|
+
command = arg;
|
|
1829
|
+
} else if (root === undefined) {
|
|
1830
|
+
root = arg;
|
|
1831
|
+
} else if (outFile === undefined) {
|
|
1832
|
+
outFile = arg;
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
if (command !== "analyze") {
|
|
1836
|
+
console.error("Unknown command. Use: sinwan analyze [root] [outFile] [--tsconfig path] [--bunfig path] [--workspaces path]");
|
|
1837
|
+
process.exit(1);
|
|
1838
|
+
}
|
|
1839
|
+
root = root ?? process.cwd();
|
|
1840
|
+
outFile = outFile ?? path3.join(root, ".sinwan/reactive-props.json");
|
|
1841
|
+
analyze({ root, outFile, tsConfigPath, bunfigPath, workspaces });
|
|
1842
|
+
console.log(`[sinwan] analyzed ${root} -> ${outFile}`);
|
|
1843
|
+
}
|
|
1844
|
+
if (import.meta.main) {
|
|
1845
|
+
runAnalyzeCli();
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
export { analyzeModule, analyzeProject, AnalyzerCache, analyze, loadMetadata, trackReactiveImports, getAllPropNames, isReactiveValue, containsReactiveValue, collectComponentFunctions, computeLocalScopes, collectComponentCallGraph, buildFullScope, propagateReactiveProps, wrapReactiveExpressions, runAnalyzeCli };
|