sinwan-compiler 0.3.1 → 1.0.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/dist/analyze.d.ts +1 -1
- package/dist/ast/generate.d.ts +16 -0
- package/dist/ast/index.d.ts +8 -0
- package/dist/ast/node.d.ts +147 -0
- package/dist/ast/parse.d.ts +19 -0
- package/dist/ast/path.d.ts +116 -0
- package/dist/ast/traverse.d.ts +4 -0
- package/dist/ast/types.d.ts +267 -0
- package/dist/auto-cc.d.ts +1 -1
- package/dist/chunk-rshqq0t3.js +4190 -0
- package/dist/cli.js +1 -1
- package/dist/exports.d.ts +7 -6
- package/dist/index.js +345 -132
- package/dist/live-cc-destructure.d.ts +1 -1
- package/dist/reactive-wrap.d.ts +4 -2
- package/dist/transform.d.ts +3 -0
- package/docs/api.md +3 -3
- package/docs/architecture.md +18 -0
- package/docs/plugins.md +9 -8
- package/docs/transform.md +322 -21
- package/package.json +5 -9
- package/dist/chunk-fadpxq9v.js +0 -2428
|
@@ -0,0 +1,4190 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/ast/types.ts
|
|
3
|
+
function lit(value, raw) {
|
|
4
|
+
return {
|
|
5
|
+
type: "Literal",
|
|
6
|
+
value,
|
|
7
|
+
raw: raw ?? (typeof value === "string" ? JSON.stringify(value) : value === null ? "null" : String(value))
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
function identifier(name) {
|
|
11
|
+
return { type: "Identifier", name };
|
|
12
|
+
}
|
|
13
|
+
function stringLiteral(value) {
|
|
14
|
+
return lit(value);
|
|
15
|
+
}
|
|
16
|
+
function numericLiteral(value) {
|
|
17
|
+
return lit(value);
|
|
18
|
+
}
|
|
19
|
+
function callExpression(callee, args) {
|
|
20
|
+
return {
|
|
21
|
+
type: "CallExpression",
|
|
22
|
+
callee,
|
|
23
|
+
arguments: args,
|
|
24
|
+
optional: false
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function memberExpression(object, property, computed = false) {
|
|
28
|
+
return {
|
|
29
|
+
type: "MemberExpression",
|
|
30
|
+
object,
|
|
31
|
+
property,
|
|
32
|
+
computed,
|
|
33
|
+
optional: false
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function arrowFunctionExpression(params, body, expression = !isBlockStatement(body)) {
|
|
37
|
+
return {
|
|
38
|
+
type: "ArrowFunctionExpression",
|
|
39
|
+
params,
|
|
40
|
+
body,
|
|
41
|
+
expression,
|
|
42
|
+
async: false,
|
|
43
|
+
generator: false,
|
|
44
|
+
id: null
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function functionExpression(id, params, body, generator = false, async = false) {
|
|
48
|
+
return {
|
|
49
|
+
type: "FunctionExpression",
|
|
50
|
+
id,
|
|
51
|
+
params,
|
|
52
|
+
body,
|
|
53
|
+
generator,
|
|
54
|
+
async
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function functionDeclaration(id, params, body, generator = false, async = false) {
|
|
58
|
+
return {
|
|
59
|
+
type: "FunctionDeclaration",
|
|
60
|
+
id,
|
|
61
|
+
params,
|
|
62
|
+
body,
|
|
63
|
+
generator,
|
|
64
|
+
async
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function variableDeclarator(id, init = null) {
|
|
68
|
+
return { type: "VariableDeclarator", id, init };
|
|
69
|
+
}
|
|
70
|
+
function variableDeclaration(kind, declarations) {
|
|
71
|
+
return { type: "VariableDeclaration", kind, declarations };
|
|
72
|
+
}
|
|
73
|
+
function arrayExpression(elements) {
|
|
74
|
+
return { type: "ArrayExpression", elements };
|
|
75
|
+
}
|
|
76
|
+
function objectExpression(properties) {
|
|
77
|
+
return { type: "ObjectExpression", properties };
|
|
78
|
+
}
|
|
79
|
+
function objectProperty(key, value, computed = false, shorthand = false) {
|
|
80
|
+
return {
|
|
81
|
+
type: "Property",
|
|
82
|
+
key,
|
|
83
|
+
value,
|
|
84
|
+
kind: "init",
|
|
85
|
+
method: false,
|
|
86
|
+
shorthand,
|
|
87
|
+
computed
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function importSpecifier(local, imported) {
|
|
91
|
+
return { type: "ImportSpecifier", local, imported, importKind: "value" };
|
|
92
|
+
}
|
|
93
|
+
function importDeclaration(specifiers, source) {
|
|
94
|
+
return {
|
|
95
|
+
type: "ImportDeclaration",
|
|
96
|
+
specifiers,
|
|
97
|
+
source,
|
|
98
|
+
importKind: "value",
|
|
99
|
+
attributes: []
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function exportNamedDeclaration(declaration, specifiers = []) {
|
|
103
|
+
return {
|
|
104
|
+
type: "ExportNamedDeclaration",
|
|
105
|
+
declaration,
|
|
106
|
+
specifiers,
|
|
107
|
+
source: null,
|
|
108
|
+
exportKind: "value",
|
|
109
|
+
attributes: []
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function exportDefaultDeclaration(declaration) {
|
|
113
|
+
return { type: "ExportDefaultDeclaration", declaration };
|
|
114
|
+
}
|
|
115
|
+
function jsxExpressionContainer(expression) {
|
|
116
|
+
return { type: "JSXExpressionContainer", expression };
|
|
117
|
+
}
|
|
118
|
+
function blockStatement(body) {
|
|
119
|
+
return { type: "BlockStatement", body };
|
|
120
|
+
}
|
|
121
|
+
function returnStatement(argument = null) {
|
|
122
|
+
return { type: "ReturnStatement", argument };
|
|
123
|
+
}
|
|
124
|
+
function binaryExpression(operator, left, right) {
|
|
125
|
+
return { type: "BinaryExpression", operator, left, right };
|
|
126
|
+
}
|
|
127
|
+
function conditionalExpression(test, consequent, alternate) {
|
|
128
|
+
return { type: "ConditionalExpression", test, consequent, alternate };
|
|
129
|
+
}
|
|
130
|
+
function unaryExpression(operator, argument, prefix = true) {
|
|
131
|
+
return { type: "UnaryExpression", operator, argument, prefix };
|
|
132
|
+
}
|
|
133
|
+
function isNode(n) {
|
|
134
|
+
return !!n && typeof n === "object" && typeof n.type === "string";
|
|
135
|
+
}
|
|
136
|
+
function isIdentifier(n, opts) {
|
|
137
|
+
if (!isNode(n) || n.type !== "Identifier" || typeof n.name !== "string")
|
|
138
|
+
return false;
|
|
139
|
+
if (opts?.name !== undefined)
|
|
140
|
+
return n.name === opts.name;
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
function isLiteral(n) {
|
|
144
|
+
return isNode(n) && n.type === "Literal";
|
|
145
|
+
}
|
|
146
|
+
function isStringLiteral(n, opts) {
|
|
147
|
+
if (!isLiteral(n) || typeof n.value !== "string")
|
|
148
|
+
return false;
|
|
149
|
+
if (opts?.value !== undefined)
|
|
150
|
+
return n.value === opts.value;
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
function isNumericLiteral(n) {
|
|
154
|
+
return isLiteral(n) && typeof n.value === "number";
|
|
155
|
+
}
|
|
156
|
+
function isBooleanLiteral(n) {
|
|
157
|
+
return isLiteral(n) && typeof n.value === "boolean";
|
|
158
|
+
}
|
|
159
|
+
function isNullLiteral(n) {
|
|
160
|
+
return isLiteral(n) && n.value === null && !n.regex;
|
|
161
|
+
}
|
|
162
|
+
function isTemplateLiteral(n) {
|
|
163
|
+
return isNode(n) && n.type === "TemplateLiteral";
|
|
164
|
+
}
|
|
165
|
+
function templateElementRaw(n) {
|
|
166
|
+
const raw = n?.value;
|
|
167
|
+
if (typeof raw === "string")
|
|
168
|
+
return raw;
|
|
169
|
+
if (raw && typeof raw === "object" && "raw" in raw) {
|
|
170
|
+
return String(raw.raw ?? "");
|
|
171
|
+
}
|
|
172
|
+
return "";
|
|
173
|
+
}
|
|
174
|
+
function isCallExpression(n) {
|
|
175
|
+
return isNode(n) && n.type === "CallExpression";
|
|
176
|
+
}
|
|
177
|
+
function isOptionalCallExpression(n) {
|
|
178
|
+
return isNode(n) && (n.type === "CallExpression" && n.optional === true || n.type === "ChainExpression" && isNode(n.expression) && n.expression.type === "CallExpression" && n.expression.optional === true);
|
|
179
|
+
}
|
|
180
|
+
function isMemberExpression(n) {
|
|
181
|
+
return isNode(n) && n.type === "MemberExpression";
|
|
182
|
+
}
|
|
183
|
+
function isOptionalMemberExpression(n) {
|
|
184
|
+
return isNode(n) && (n.type === "MemberExpression" && n.optional === true || n.type === "ChainExpression" && isNode(n.expression) && n.expression.type === "MemberExpression");
|
|
185
|
+
}
|
|
186
|
+
function isChainExpression(n) {
|
|
187
|
+
return isNode(n) && n.type === "ChainExpression";
|
|
188
|
+
}
|
|
189
|
+
function unwrapChain(n) {
|
|
190
|
+
return isChainExpression(n) && isNode(n.expression) ? n.expression : n;
|
|
191
|
+
}
|
|
192
|
+
function isParenthesizedExpression(n) {
|
|
193
|
+
return isNode(n) && n.type === "ParenthesizedExpression";
|
|
194
|
+
}
|
|
195
|
+
function unwrapParens(n) {
|
|
196
|
+
let current = n;
|
|
197
|
+
while (current && isParenthesizedExpression(current) && isNode(current.expression)) {
|
|
198
|
+
current = current.expression;
|
|
199
|
+
}
|
|
200
|
+
return current ?? n;
|
|
201
|
+
}
|
|
202
|
+
function unwrapCall(n) {
|
|
203
|
+
return unwrapParens(unwrapChain(unwrapParens(n)));
|
|
204
|
+
}
|
|
205
|
+
function isObjectExpression(n) {
|
|
206
|
+
return isNode(n) && n.type === "ObjectExpression";
|
|
207
|
+
}
|
|
208
|
+
function isObjectProperty(n) {
|
|
209
|
+
return isNode(n) && (n.type === "Property" || n.type === "ObjectProperty");
|
|
210
|
+
}
|
|
211
|
+
function isObjectPattern(n) {
|
|
212
|
+
return isNode(n) && n.type === "ObjectPattern";
|
|
213
|
+
}
|
|
214
|
+
function isArrayPattern(n) {
|
|
215
|
+
return isNode(n) && n.type === "ArrayPattern";
|
|
216
|
+
}
|
|
217
|
+
function isRestElement(n) {
|
|
218
|
+
return isNode(n) && n.type === "RestElement";
|
|
219
|
+
}
|
|
220
|
+
function isSpreadElement(n) {
|
|
221
|
+
return isNode(n) && n.type === "SpreadElement";
|
|
222
|
+
}
|
|
223
|
+
function isAssignmentPattern(n) {
|
|
224
|
+
return isNode(n) && n.type === "AssignmentPattern";
|
|
225
|
+
}
|
|
226
|
+
function isBlockStatement(n) {
|
|
227
|
+
return isNode(n) && n.type === "BlockStatement";
|
|
228
|
+
}
|
|
229
|
+
function isVariableDeclaration(n, opts) {
|
|
230
|
+
if (!isNode(n) || n.type !== "VariableDeclaration")
|
|
231
|
+
return false;
|
|
232
|
+
if (opts?.kind !== undefined)
|
|
233
|
+
return n.kind === opts.kind;
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
function isVariableDeclarator(n) {
|
|
237
|
+
return isNode(n) && n.type === "VariableDeclarator";
|
|
238
|
+
}
|
|
239
|
+
function isFunctionDeclaration(n) {
|
|
240
|
+
return isNode(n) && n.type === "FunctionDeclaration";
|
|
241
|
+
}
|
|
242
|
+
function isFunctionExpression(n) {
|
|
243
|
+
return isNode(n) && n.type === "FunctionExpression";
|
|
244
|
+
}
|
|
245
|
+
function isArrowFunctionExpression(n) {
|
|
246
|
+
return isNode(n) && n.type === "ArrowFunctionExpression";
|
|
247
|
+
}
|
|
248
|
+
function isFunction(n) {
|
|
249
|
+
return isFunctionDeclaration(n) || isFunctionExpression(n) || isArrowFunctionExpression(n);
|
|
250
|
+
}
|
|
251
|
+
function isImportDeclaration(n) {
|
|
252
|
+
return isNode(n) && n.type === "ImportDeclaration";
|
|
253
|
+
}
|
|
254
|
+
function isImportSpecifier(n) {
|
|
255
|
+
return isNode(n) && n.type === "ImportSpecifier";
|
|
256
|
+
}
|
|
257
|
+
function isImportDefaultSpecifier(n) {
|
|
258
|
+
return isNode(n) && n.type === "ImportDefaultSpecifier";
|
|
259
|
+
}
|
|
260
|
+
function isImportNamespaceSpecifier(n) {
|
|
261
|
+
return isNode(n) && n.type === "ImportNamespaceSpecifier";
|
|
262
|
+
}
|
|
263
|
+
function isExportSpecifier(n) {
|
|
264
|
+
return isNode(n) && n.type === "ExportSpecifier";
|
|
265
|
+
}
|
|
266
|
+
function isClassDeclaration(n) {
|
|
267
|
+
return isNode(n) && n.type === "ClassDeclaration";
|
|
268
|
+
}
|
|
269
|
+
function isJSXElement(n) {
|
|
270
|
+
return isNode(n) && n.type === "JSXElement";
|
|
271
|
+
}
|
|
272
|
+
function isJSXFragment(n) {
|
|
273
|
+
return isNode(n) && n.type === "JSXFragment";
|
|
274
|
+
}
|
|
275
|
+
function isJSXIdentifier(n) {
|
|
276
|
+
return isNode(n) && n.type === "JSXIdentifier" && typeof n.name === "string";
|
|
277
|
+
}
|
|
278
|
+
function isJSXOpeningElement(n) {
|
|
279
|
+
return isNode(n) && n.type === "JSXOpeningElement";
|
|
280
|
+
}
|
|
281
|
+
function isJSXMemberExpression(n) {
|
|
282
|
+
return isNode(n) && n.type === "JSXMemberExpression";
|
|
283
|
+
}
|
|
284
|
+
function isJSXAttribute(n) {
|
|
285
|
+
return isNode(n) && n.type === "JSXAttribute";
|
|
286
|
+
}
|
|
287
|
+
function isJSXSpreadAttribute(n) {
|
|
288
|
+
return isNode(n) && n.type === "JSXSpreadAttribute";
|
|
289
|
+
}
|
|
290
|
+
function isJSXExpressionContainer(n) {
|
|
291
|
+
return isNode(n) && n.type === "JSXExpressionContainer";
|
|
292
|
+
}
|
|
293
|
+
function isJSXEmptyExpression(n) {
|
|
294
|
+
return isNode(n) && n.type === "JSXEmptyExpression";
|
|
295
|
+
}
|
|
296
|
+
function isTSNonNullExpression(n) {
|
|
297
|
+
return isNode(n) && n.type === "TSNonNullExpression";
|
|
298
|
+
}
|
|
299
|
+
function isTSAsExpression(n) {
|
|
300
|
+
return isNode(n) && n.type === "TSAsExpression";
|
|
301
|
+
}
|
|
302
|
+
function isTSTypeAssertion(n) {
|
|
303
|
+
return isNode(n) && n.type === "TSTypeAssertion";
|
|
304
|
+
}
|
|
305
|
+
function isTSSatisfiesExpression(n) {
|
|
306
|
+
return isNode(n) && n.type === "TSSatisfiesExpression";
|
|
307
|
+
}
|
|
308
|
+
function isTSTypeAnnotation(n) {
|
|
309
|
+
return isNode(n) && n.type === "TSTypeAnnotation";
|
|
310
|
+
}
|
|
311
|
+
function isExpression(n) {
|
|
312
|
+
if (!isNode(n))
|
|
313
|
+
return false;
|
|
314
|
+
switch (n.type) {
|
|
315
|
+
case "Identifier":
|
|
316
|
+
case "Literal":
|
|
317
|
+
case "ThisExpression":
|
|
318
|
+
case "ArrayExpression":
|
|
319
|
+
case "ObjectExpression":
|
|
320
|
+
case "FunctionExpression":
|
|
321
|
+
case "ArrowFunctionExpression":
|
|
322
|
+
case "ClassExpression":
|
|
323
|
+
case "UnaryExpression":
|
|
324
|
+
case "UpdateExpression":
|
|
325
|
+
case "BinaryExpression":
|
|
326
|
+
case "LogicalExpression":
|
|
327
|
+
case "AssignmentExpression":
|
|
328
|
+
case "MemberExpression":
|
|
329
|
+
case "ConditionalExpression":
|
|
330
|
+
case "CallExpression":
|
|
331
|
+
case "NewExpression":
|
|
332
|
+
case "SequenceExpression":
|
|
333
|
+
case "TemplateLiteral":
|
|
334
|
+
case "TaggedTemplateExpression":
|
|
335
|
+
case "YieldExpression":
|
|
336
|
+
case "AwaitExpression":
|
|
337
|
+
case "ImportExpression":
|
|
338
|
+
case "MetaProperty":
|
|
339
|
+
case "ChainExpression":
|
|
340
|
+
case "ParenthesizedExpression":
|
|
341
|
+
case "JSXElement":
|
|
342
|
+
case "JSXFragment":
|
|
343
|
+
case "TSAsExpression":
|
|
344
|
+
case "TSTypeAssertion":
|
|
345
|
+
case "TSNonNullExpression":
|
|
346
|
+
case "TSSatisfiesExpression":
|
|
347
|
+
case "TSInstantiationExpression":
|
|
348
|
+
return true;
|
|
349
|
+
default:
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
function importedBindingName(n) {
|
|
354
|
+
if (!n)
|
|
355
|
+
return null;
|
|
356
|
+
if (isIdentifier(n))
|
|
357
|
+
return n.name;
|
|
358
|
+
if (typeof n.value === "string")
|
|
359
|
+
return n.value;
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
function moduleSourceValue(n) {
|
|
363
|
+
if (!n)
|
|
364
|
+
return null;
|
|
365
|
+
return typeof n.value === "string" ? n.value : null;
|
|
366
|
+
}
|
|
367
|
+
function assertExpression(n) {
|
|
368
|
+
if (!isExpression(n)) {
|
|
369
|
+
throw new TypeError(`Expected Expression, got ${isNode(n) ? n.type : typeof n}`);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// src/ast/path.ts
|
|
374
|
+
import { visitorKeys } from "oxc-parser";
|
|
375
|
+
class Binding {
|
|
376
|
+
identifier;
|
|
377
|
+
path;
|
|
378
|
+
kind;
|
|
379
|
+
constant;
|
|
380
|
+
referenced = false;
|
|
381
|
+
referencePaths = [];
|
|
382
|
+
constantViolations = [];
|
|
383
|
+
constructor(identifier, path, kind) {
|
|
384
|
+
this.identifier = identifier;
|
|
385
|
+
this.path = path;
|
|
386
|
+
this.kind = kind;
|
|
387
|
+
this.constant = kind === "const" || kind === "module" || kind === "hoisted" || kind === "param";
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
class Scope {
|
|
392
|
+
path;
|
|
393
|
+
parent;
|
|
394
|
+
bindings = new Map;
|
|
395
|
+
constructor(path, parent = null) {
|
|
396
|
+
this.path = path;
|
|
397
|
+
this.parent = parent;
|
|
398
|
+
}
|
|
399
|
+
getBinding(name) {
|
|
400
|
+
let scope = this;
|
|
401
|
+
while (scope) {
|
|
402
|
+
const b = scope.bindings.get(name);
|
|
403
|
+
if (b)
|
|
404
|
+
return b;
|
|
405
|
+
scope = scope.parent;
|
|
406
|
+
}
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
register(name, binding) {
|
|
410
|
+
this.bindings.set(name, binding);
|
|
411
|
+
}
|
|
412
|
+
crawl() {
|
|
413
|
+
this.bindings.clear();
|
|
414
|
+
seedScopeBindings(this);
|
|
415
|
+
markReferences(this.path);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
function singlePath(path) {
|
|
419
|
+
if (Array.isArray(path)) {
|
|
420
|
+
throw new Error("Expected a single NodePath");
|
|
421
|
+
}
|
|
422
|
+
return path;
|
|
423
|
+
}
|
|
424
|
+
function keysFor(node) {
|
|
425
|
+
return visitorKeys[node.type] ?? [];
|
|
426
|
+
}
|
|
427
|
+
function isScopeNode(node) {
|
|
428
|
+
return isFunction(node) || node.type === "Program" || node.type === "BlockStatement" || node.type === "CatchClause" || node.type === "SwitchStatement" || node.type === "ForStatement" || node.type === "ForInStatement" || node.type === "ForOfStatement";
|
|
429
|
+
}
|
|
430
|
+
function lexicalScopeFor(kind, scope) {
|
|
431
|
+
if (kind !== "var")
|
|
432
|
+
return scope;
|
|
433
|
+
let current = scope;
|
|
434
|
+
while (current.parent && !isFunction(current.path.node) && current.path.node.type !== "Program") {
|
|
435
|
+
current = current.parent;
|
|
436
|
+
}
|
|
437
|
+
return current;
|
|
438
|
+
}
|
|
439
|
+
function registerVariableDeclaration(stmt, stmtPath, scope) {
|
|
440
|
+
if (!isVariableDeclaration(stmt))
|
|
441
|
+
return;
|
|
442
|
+
const kind = String(stmt.kind);
|
|
443
|
+
const target = lexicalScopeFor(kind, scope);
|
|
444
|
+
const decls = stmt.declarations;
|
|
445
|
+
for (let j = 0;j < decls.length; j++) {
|
|
446
|
+
registerPattern(decls[j].id, singlePath(stmtPath.get(`declarations.${j}`)), target, kind);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
function isTypeOnlyImport(spec, stmt) {
|
|
450
|
+
const declarationKind = stmt.importKind;
|
|
451
|
+
const specifierKind = spec.importKind;
|
|
452
|
+
return declarationKind === "type" || specifierKind === "type";
|
|
453
|
+
}
|
|
454
|
+
function registerPattern(pattern, bindingPath, scope, kind) {
|
|
455
|
+
if (isIdentifier(pattern)) {
|
|
456
|
+
scope.register(pattern.name, new Binding(pattern, bindingPath, kind));
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
if (isObjectPattern(pattern)) {
|
|
460
|
+
for (const prop of pattern.properties ?? []) {
|
|
461
|
+
if (isRestElement(prop)) {
|
|
462
|
+
registerPattern(prop.argument, bindingPath, scope, kind);
|
|
463
|
+
} else if (isObjectProperty(prop)) {
|
|
464
|
+
let value = prop.value;
|
|
465
|
+
if (isAssignmentPattern(value))
|
|
466
|
+
value = value.left;
|
|
467
|
+
registerPattern(value, bindingPath, scope, kind);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
if (isArrayPattern(pattern)) {
|
|
473
|
+
for (const el of pattern.elements ?? []) {
|
|
474
|
+
if (!el)
|
|
475
|
+
continue;
|
|
476
|
+
if (isRestElement(el)) {
|
|
477
|
+
registerPattern(el.argument, bindingPath, scope, kind);
|
|
478
|
+
} else if (isAssignmentPattern(el)) {
|
|
479
|
+
registerPattern(el.left, bindingPath, scope, kind);
|
|
480
|
+
} else {
|
|
481
|
+
registerPattern(el, bindingPath, scope, kind);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
function seedScopeBindings(scope) {
|
|
487
|
+
const path = scope.path;
|
|
488
|
+
const node = path.node;
|
|
489
|
+
if (isFunction(node)) {
|
|
490
|
+
if ((node.type === "FunctionDeclaration" || node.type === "FunctionExpression") && isIdentifier(node.id)) {
|
|
491
|
+
scope.register(node.id.name, new Binding(node.id, singlePath(path.get("id")), "hoisted"));
|
|
492
|
+
}
|
|
493
|
+
for (let i = 0;i < node.params.length; i++) {
|
|
494
|
+
registerPattern(node.params[i], singlePath(path.get(`params.${i}`)), scope, "param");
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
if (node.type === "ForStatement" && isVariableDeclaration(node.init)) {
|
|
498
|
+
registerVariableDeclaration(node.init, singlePath(path.get("init")), scope);
|
|
499
|
+
}
|
|
500
|
+
if ((node.type === "ForInStatement" || node.type === "ForOfStatement") && isVariableDeclaration(node.left)) {
|
|
501
|
+
registerVariableDeclaration(node.left, singlePath(path.get("left")), scope);
|
|
502
|
+
}
|
|
503
|
+
if (node.type === "CatchClause" && node.param && typeof node.param === "object") {
|
|
504
|
+
registerPattern(node.param, singlePath(path.get("param")), scope, "const");
|
|
505
|
+
}
|
|
506
|
+
if (node.type === "SwitchStatement") {
|
|
507
|
+
const cases = node.cases ?? [];
|
|
508
|
+
for (let i = 0;i < cases.length; i++) {
|
|
509
|
+
const consequent = cases[i].consequent ?? [];
|
|
510
|
+
const casePath = singlePath(path.get(`cases.${i}`));
|
|
511
|
+
for (let j = 0;j < consequent.length; j++) {
|
|
512
|
+
const stmt = consequent[j];
|
|
513
|
+
if (!isVariableDeclaration(stmt))
|
|
514
|
+
continue;
|
|
515
|
+
registerVariableDeclaration(stmt, singlePath(casePath.get(`consequent.${j}`)), scope);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
if (node.type === "Program" || node.type === "BlockStatement") {
|
|
520
|
+
const body = node.body ?? [];
|
|
521
|
+
for (let i = 0;i < body.length; i++) {
|
|
522
|
+
const stmt = body[i];
|
|
523
|
+
const stmtPath = singlePath(path.get(`body.${i}`));
|
|
524
|
+
if (isVariableDeclaration(stmt)) {
|
|
525
|
+
registerVariableDeclaration(stmt, stmtPath, scope);
|
|
526
|
+
} else if (isFunctionDeclaration(stmt) && isIdentifier(stmt.id)) {
|
|
527
|
+
scope.register(stmt.id.name, new Binding(stmt.id, stmtPath, "hoisted"));
|
|
528
|
+
} else if (isClassDeclaration(stmt) && isIdentifier(stmt.id)) {
|
|
529
|
+
scope.register(stmt.id.name, new Binding(stmt.id, stmtPath, "const"));
|
|
530
|
+
} else if (isImportDeclaration(stmt)) {
|
|
531
|
+
const specs = stmt.specifiers;
|
|
532
|
+
for (let j = 0;j < specs.length; j++) {
|
|
533
|
+
const spec = specs[j];
|
|
534
|
+
if (isTypeOnlyImport(spec, stmt))
|
|
535
|
+
continue;
|
|
536
|
+
if (isImportSpecifier(spec) || isImportDefaultSpecifier(spec) || isImportNamespaceSpecifier(spec)) {
|
|
537
|
+
const local = spec.local;
|
|
538
|
+
scope.register(local.name, new Binding(local, singlePath(stmtPath.get(`specifiers.${j}`)), "module"));
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
} else if (stmt.type === "ExportNamedDeclaration" && stmt.declaration && isFunctionDeclaration(stmt.declaration) && isIdentifier(stmt.declaration.id)) {
|
|
542
|
+
const fnId = stmt.declaration.id;
|
|
543
|
+
scope.register(fnId.name, new Binding(fnId, singlePath(stmtPath.get("declaration")), "hoisted"));
|
|
544
|
+
} else if (stmt.type === "ExportNamedDeclaration" && stmt.declaration && isClassDeclaration(stmt.declaration) && isIdentifier(stmt.declaration.id)) {
|
|
545
|
+
const classId = stmt.declaration.id;
|
|
546
|
+
scope.register(classId.name, new Binding(classId, singlePath(stmtPath.get("declaration")), "const"));
|
|
547
|
+
} else if (stmt.type === "ExportNamedDeclaration" && stmt.declaration && isVariableDeclaration(stmt.declaration)) {
|
|
548
|
+
const decl = stmt.declaration;
|
|
549
|
+
const kind = String(decl.kind);
|
|
550
|
+
const decls = decl.declarations;
|
|
551
|
+
const declPath = singlePath(stmtPath.get("declaration"));
|
|
552
|
+
for (let j = 0;j < decls.length; j++) {
|
|
553
|
+
registerPattern(decls[j].id, singlePath(declPath.get(`declarations.${j}`)), scope, kind);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
function markReferences(root) {
|
|
560
|
+
const stack = [];
|
|
561
|
+
const visit = (path) => {
|
|
562
|
+
stack.push(path);
|
|
563
|
+
if (isIdentifier(path.node) && path.isReferencedIdentifier()) {
|
|
564
|
+
const binding = path.scope.getBinding(path.node.name);
|
|
565
|
+
if (binding) {
|
|
566
|
+
binding.referenced = true;
|
|
567
|
+
binding.referencePaths.push(path);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
if (path.node.type === "AssignmentExpression" && isIdentifier(path.node.left)) {
|
|
571
|
+
const binding = path.scope.getBinding(path.node.left.name);
|
|
572
|
+
if (binding) {
|
|
573
|
+
binding.constant = false;
|
|
574
|
+
binding.constantViolations.push(path);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
if (path.node.type === "UpdateExpression" && isIdentifier(path.node.argument)) {
|
|
578
|
+
const binding = path.scope.getBinding(path.node.argument.name);
|
|
579
|
+
if (binding) {
|
|
580
|
+
binding.constant = false;
|
|
581
|
+
binding.constantViolations.push(path);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
for (const key of keysFor(path.node)) {
|
|
585
|
+
const child = path.node[key];
|
|
586
|
+
if (Array.isArray(child)) {
|
|
587
|
+
for (let i = 0;i < child.length; i++) {
|
|
588
|
+
const c = child[i];
|
|
589
|
+
if (!c || typeof c !== "object" || !("type" in c))
|
|
590
|
+
continue;
|
|
591
|
+
visit(childPath(path, key, i, c));
|
|
592
|
+
}
|
|
593
|
+
} else if (child && typeof child === "object" && "type" in child) {
|
|
594
|
+
visit(childPath(path, key, null, child));
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
stack.pop();
|
|
598
|
+
};
|
|
599
|
+
visit(root);
|
|
600
|
+
}
|
|
601
|
+
function childPath(parent, key, index, node) {
|
|
602
|
+
let scope = parent.scope;
|
|
603
|
+
if (isScopeNode(node)) {
|
|
604
|
+
scope = new Scope(parent, parent.scope);
|
|
605
|
+
}
|
|
606
|
+
let path;
|
|
607
|
+
if (index !== null) {
|
|
608
|
+
path = new Path(node, parent.node, parent, index, parent.node[key], key, scope);
|
|
609
|
+
} else {
|
|
610
|
+
path = new Path(node, parent.node, parent, key, parent.node, null, scope);
|
|
611
|
+
}
|
|
612
|
+
if (isScopeNode(node)) {
|
|
613
|
+
scope.path = path;
|
|
614
|
+
seedScopeBindings(scope);
|
|
615
|
+
}
|
|
616
|
+
return path;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
class Path {
|
|
620
|
+
node;
|
|
621
|
+
parent;
|
|
622
|
+
parentPath;
|
|
623
|
+
key;
|
|
624
|
+
container;
|
|
625
|
+
listKey;
|
|
626
|
+
scope;
|
|
627
|
+
_skip = false;
|
|
628
|
+
_stop = false;
|
|
629
|
+
_removed = false;
|
|
630
|
+
_hubEmpty = false;
|
|
631
|
+
constructor(node, parent, parentPath, key, container, listKey, scope) {
|
|
632
|
+
this.node = node;
|
|
633
|
+
this.parent = parent;
|
|
634
|
+
this.parentPath = parentPath;
|
|
635
|
+
this.key = key;
|
|
636
|
+
this.container = container;
|
|
637
|
+
this.listKey = listKey;
|
|
638
|
+
this.scope = scope ?? new Scope(this, parentPath?.scope ?? null);
|
|
639
|
+
}
|
|
640
|
+
skip() {
|
|
641
|
+
this._skip = true;
|
|
642
|
+
}
|
|
643
|
+
stop() {
|
|
644
|
+
this._stop = true;
|
|
645
|
+
}
|
|
646
|
+
get shouldSkip() {
|
|
647
|
+
return this._skip;
|
|
648
|
+
}
|
|
649
|
+
get shouldStop() {
|
|
650
|
+
return this._stop;
|
|
651
|
+
}
|
|
652
|
+
get(dotPath) {
|
|
653
|
+
const parts = dotPath.split(".");
|
|
654
|
+
let current = this;
|
|
655
|
+
for (const part of parts) {
|
|
656
|
+
if (Array.isArray(current)) {
|
|
657
|
+
current = current[Number(part)];
|
|
658
|
+
continue;
|
|
659
|
+
}
|
|
660
|
+
const node = current.node;
|
|
661
|
+
if (/^\d+$/.test(part)) {
|
|
662
|
+
throw new Error(`Unexpected index without array at ${dotPath}`);
|
|
663
|
+
}
|
|
664
|
+
const value = node[part];
|
|
665
|
+
if (Array.isArray(value)) {
|
|
666
|
+
const paths = [];
|
|
667
|
+
for (let i = 0;i < value.length; i++) {
|
|
668
|
+
const c = value[i];
|
|
669
|
+
if (!c || typeof c !== "object" || !("type" in c))
|
|
670
|
+
continue;
|
|
671
|
+
let childScope = current.scope;
|
|
672
|
+
let childPath;
|
|
673
|
+
if (isScopeNode(c)) {
|
|
674
|
+
childScope = new Scope(current, current.scope);
|
|
675
|
+
childPath = new Path(c, node, current, i, value, part, childScope);
|
|
676
|
+
childScope.path = childPath;
|
|
677
|
+
childScope.crawl();
|
|
678
|
+
} else {
|
|
679
|
+
childPath = new Path(c, node, current, i, value, part, childScope);
|
|
680
|
+
}
|
|
681
|
+
paths.push(childPath);
|
|
682
|
+
}
|
|
683
|
+
current = paths;
|
|
684
|
+
continue;
|
|
685
|
+
}
|
|
686
|
+
if (value && typeof value === "object" && "type" in value) {
|
|
687
|
+
const childNode = value;
|
|
688
|
+
let childScope = current.scope;
|
|
689
|
+
if (isScopeNode(childNode)) {
|
|
690
|
+
childScope = new Scope(current, current.scope);
|
|
691
|
+
const tmp = new Path(childNode, node, current, part, node, null, childScope);
|
|
692
|
+
childScope.path = tmp;
|
|
693
|
+
childScope.crawl();
|
|
694
|
+
current = tmp;
|
|
695
|
+
} else {
|
|
696
|
+
current = new Path(childNode, node, current, part, node, null, childScope);
|
|
697
|
+
}
|
|
698
|
+
} else {
|
|
699
|
+
const empty = new Path({ type: "EmptyStatement" }, node, current, part, node, null, current.scope);
|
|
700
|
+
empty._hubEmpty = true;
|
|
701
|
+
current = empty;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
return current;
|
|
705
|
+
}
|
|
706
|
+
getFunctionParent() {
|
|
707
|
+
let cur = this.parentPath;
|
|
708
|
+
while (cur) {
|
|
709
|
+
if (isFunction(cur.node))
|
|
710
|
+
return cur;
|
|
711
|
+
cur = cur.parentPath;
|
|
712
|
+
}
|
|
713
|
+
return null;
|
|
714
|
+
}
|
|
715
|
+
findParent(pred) {
|
|
716
|
+
let cur = this.parentPath;
|
|
717
|
+
while (cur) {
|
|
718
|
+
if (pred(cur))
|
|
719
|
+
return cur;
|
|
720
|
+
cur = cur.parentPath;
|
|
721
|
+
}
|
|
722
|
+
return null;
|
|
723
|
+
}
|
|
724
|
+
replaceWith(replacement) {
|
|
725
|
+
if (this.parent == null || this.key == null) {
|
|
726
|
+
throw new Error("Cannot replace root");
|
|
727
|
+
}
|
|
728
|
+
if (Array.isArray(this.container) && typeof this.key === "number") {
|
|
729
|
+
this.container[this.key] = replacement;
|
|
730
|
+
} else if (typeof this.key === "string") {
|
|
731
|
+
this.parent[this.key] = replacement;
|
|
732
|
+
}
|
|
733
|
+
this.node = replacement;
|
|
734
|
+
}
|
|
735
|
+
replaceWithMultiple(nodes) {
|
|
736
|
+
if (!Array.isArray(this.container) || typeof this.key !== "number") {
|
|
737
|
+
throw new Error("replaceWithMultiple requires a list path");
|
|
738
|
+
}
|
|
739
|
+
this.container.splice(this.key, 1, ...nodes);
|
|
740
|
+
this._removed = true;
|
|
741
|
+
}
|
|
742
|
+
traverse(visitors) {
|
|
743
|
+
for (const key of keysFor(this.node)) {
|
|
744
|
+
const child = this.node[key];
|
|
745
|
+
if (Array.isArray(child)) {
|
|
746
|
+
for (let i = 0;i < child.length; ) {
|
|
747
|
+
const c = child[i];
|
|
748
|
+
if (!c || typeof c !== "object" || !("type" in c)) {
|
|
749
|
+
i += 1;
|
|
750
|
+
continue;
|
|
751
|
+
}
|
|
752
|
+
const childP = new Path(c, this.node, this, i, child, key, this.scope);
|
|
753
|
+
const stopped = traverseInner(childP, visitors, false);
|
|
754
|
+
if (stopped || this.shouldStop)
|
|
755
|
+
return;
|
|
756
|
+
if (child[i] === c)
|
|
757
|
+
i += 1;
|
|
758
|
+
}
|
|
759
|
+
} else if (child && typeof child === "object" && "type" in child) {
|
|
760
|
+
const childP = new Path(child, this.node, this, key, this.node, null, this.scope);
|
|
761
|
+
const stopped = traverseInner(childP, visitors, false);
|
|
762
|
+
if (stopped || this.shouldStop)
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
isReferencedIdentifier() {
|
|
768
|
+
if (!isIdentifier(this.node))
|
|
769
|
+
return false;
|
|
770
|
+
const parent = this.parent;
|
|
771
|
+
if (!parent)
|
|
772
|
+
return true;
|
|
773
|
+
if (this.listKey === "params")
|
|
774
|
+
return false;
|
|
775
|
+
if (isVariableDeclarator(parent) && this.key === "id")
|
|
776
|
+
return false;
|
|
777
|
+
if (isFunction(parent) && this.key === "id")
|
|
778
|
+
return false;
|
|
779
|
+
if ((parent.type === "ClassDeclaration" || parent.type === "ClassExpression") && this.key === "id")
|
|
780
|
+
return false;
|
|
781
|
+
if ((parent.type === "MethodDefinition" || parent.type === "PropertyDefinition") && this.key === "key" && parent.computed !== true)
|
|
782
|
+
return false;
|
|
783
|
+
if (isFunction(parent) && this.key === "params")
|
|
784
|
+
return false;
|
|
785
|
+
if ((isImportSpecifier(parent) || isImportDefaultSpecifier(parent) || isImportNamespaceSpecifier(parent)) && this.key === "local")
|
|
786
|
+
return false;
|
|
787
|
+
if (isObjectProperty(parent) && this.key === "key" && !parent.computed && !parent.shorthand)
|
|
788
|
+
return false;
|
|
789
|
+
if (isMemberExpression(parent) && this.key === "property" && !parent.computed)
|
|
790
|
+
return false;
|
|
791
|
+
if (parent.type === "Property" && this.key === "key" && !parent.computed)
|
|
792
|
+
return false;
|
|
793
|
+
if (isAssignmentPattern(parent) && this.key === "left")
|
|
794
|
+
return false;
|
|
795
|
+
if (isRestElement(parent)) {
|
|
796
|
+
const gp = this.parentPath?.parent;
|
|
797
|
+
if (gp && (isObjectPattern(gp) || isArrayPattern(gp)))
|
|
798
|
+
return false;
|
|
799
|
+
}
|
|
800
|
+
if (isObjectPattern(parent) || isArrayPattern(parent))
|
|
801
|
+
return false;
|
|
802
|
+
if (isObjectProperty(parent) && this.key === "value" && this.parentPath?.parent && (isObjectPattern(this.parentPath.parent) || isArrayPattern(this.parentPath.parent)))
|
|
803
|
+
return false;
|
|
804
|
+
if (isObjectProperty(parent) && parent.shorthand && (this.key === "key" || this.key === "value")) {
|
|
805
|
+
const gp = this.parentPath?.parent;
|
|
806
|
+
if (gp && isObjectPattern(gp))
|
|
807
|
+
return false;
|
|
808
|
+
return true;
|
|
809
|
+
}
|
|
810
|
+
if (parent.type === "CatchClause" && this.key === "param")
|
|
811
|
+
return false;
|
|
812
|
+
if (parent.type === "JSXAttribute" && this.key === "name")
|
|
813
|
+
return false;
|
|
814
|
+
if ((parent.type === "JSXOpeningElement" || parent.type === "JSXClosingElement") && this.key === "name")
|
|
815
|
+
return false;
|
|
816
|
+
if (parent.type === "JSXMemberExpression" && this.key === "property")
|
|
817
|
+
return false;
|
|
818
|
+
return true;
|
|
819
|
+
}
|
|
820
|
+
isFunction() {
|
|
821
|
+
return isFunction(this.node);
|
|
822
|
+
}
|
|
823
|
+
isIdentifier(opts) {
|
|
824
|
+
return isIdentifier(this.node, opts);
|
|
825
|
+
}
|
|
826
|
+
isLiteral() {
|
|
827
|
+
return isLiteral(this.node);
|
|
828
|
+
}
|
|
829
|
+
isCallExpression() {
|
|
830
|
+
return isCallExpression(this.node);
|
|
831
|
+
}
|
|
832
|
+
isOptionalCallExpression() {
|
|
833
|
+
return isOptionalCallExpression(this.node);
|
|
834
|
+
}
|
|
835
|
+
isMemberExpression() {
|
|
836
|
+
return isMemberExpression(this.node);
|
|
837
|
+
}
|
|
838
|
+
isOptionalMemberExpression() {
|
|
839
|
+
return isOptionalMemberExpression(this.node);
|
|
840
|
+
}
|
|
841
|
+
isObjectProperty(opts) {
|
|
842
|
+
if (!isObjectProperty(this.node))
|
|
843
|
+
return false;
|
|
844
|
+
if (opts?.shorthand !== undefined)
|
|
845
|
+
return this.node.shorthand === opts.shorthand;
|
|
846
|
+
return true;
|
|
847
|
+
}
|
|
848
|
+
isObjectExpression() {
|
|
849
|
+
return isObjectExpression(this.node);
|
|
850
|
+
}
|
|
851
|
+
isParenthesizedExpression() {
|
|
852
|
+
return isParenthesizedExpression(this.node);
|
|
853
|
+
}
|
|
854
|
+
isIfStatement() {
|
|
855
|
+
return this.node.type === "IfStatement";
|
|
856
|
+
}
|
|
857
|
+
isObjectPattern() {
|
|
858
|
+
return isObjectPattern(this.node);
|
|
859
|
+
}
|
|
860
|
+
isArrayExpression() {
|
|
861
|
+
return this.node.type === "ArrayExpression";
|
|
862
|
+
}
|
|
863
|
+
isArrayPattern() {
|
|
864
|
+
return isArrayPattern(this.node);
|
|
865
|
+
}
|
|
866
|
+
isVariableDeclarator() {
|
|
867
|
+
return isVariableDeclarator(this.node);
|
|
868
|
+
}
|
|
869
|
+
isVariableDeclaration(opts) {
|
|
870
|
+
return isVariableDeclaration(this.node, opts);
|
|
871
|
+
}
|
|
872
|
+
isImportSpecifier() {
|
|
873
|
+
return isImportSpecifier(this.node);
|
|
874
|
+
}
|
|
875
|
+
isImportDefaultSpecifier() {
|
|
876
|
+
return isImportDefaultSpecifier(this.node);
|
|
877
|
+
}
|
|
878
|
+
isImportDeclaration() {
|
|
879
|
+
return isImportDeclaration(this.node);
|
|
880
|
+
}
|
|
881
|
+
isJSXElement() {
|
|
882
|
+
return isJSXElement(this.node);
|
|
883
|
+
}
|
|
884
|
+
isJSXFragment() {
|
|
885
|
+
return isJSXFragment(this.node);
|
|
886
|
+
}
|
|
887
|
+
isJSXExpressionContainer() {
|
|
888
|
+
return isJSXExpressionContainer(this.node);
|
|
889
|
+
}
|
|
890
|
+
isJSXAttribute() {
|
|
891
|
+
return isJSXAttribute(this.node);
|
|
892
|
+
}
|
|
893
|
+
isBlockStatement() {
|
|
894
|
+
return isBlockStatement(this.node);
|
|
895
|
+
}
|
|
896
|
+
isArrowFunctionExpression() {
|
|
897
|
+
return isArrowFunctionExpression(this.node);
|
|
898
|
+
}
|
|
899
|
+
isFunctionExpression() {
|
|
900
|
+
return isFunctionExpression(this.node);
|
|
901
|
+
}
|
|
902
|
+
isFunctionDeclaration() {
|
|
903
|
+
return isFunctionDeclaration(this.node);
|
|
904
|
+
}
|
|
905
|
+
isSpreadElement() {
|
|
906
|
+
return isSpreadElement(this.node);
|
|
907
|
+
}
|
|
908
|
+
isTemplateLiteral() {
|
|
909
|
+
return isTemplateLiteral(this.node);
|
|
910
|
+
}
|
|
911
|
+
isTemplateElement() {
|
|
912
|
+
return this.node.type === "TemplateElement";
|
|
913
|
+
}
|
|
914
|
+
isBinaryExpression() {
|
|
915
|
+
return this.node.type === "BinaryExpression";
|
|
916
|
+
}
|
|
917
|
+
isLogicalExpression() {
|
|
918
|
+
return this.node.type === "LogicalExpression";
|
|
919
|
+
}
|
|
920
|
+
isConditionalExpression() {
|
|
921
|
+
return this.node.type === "ConditionalExpression";
|
|
922
|
+
}
|
|
923
|
+
isUnaryExpression(opts) {
|
|
924
|
+
if (this.node.type !== "UnaryExpression")
|
|
925
|
+
return false;
|
|
926
|
+
if (opts?.operator !== undefined)
|
|
927
|
+
return this.node.operator === opts.operator;
|
|
928
|
+
return true;
|
|
929
|
+
}
|
|
930
|
+
isUpdateExpression() {
|
|
931
|
+
return this.node.type === "UpdateExpression";
|
|
932
|
+
}
|
|
933
|
+
isAssignmentExpression() {
|
|
934
|
+
return this.node.type === "AssignmentExpression";
|
|
935
|
+
}
|
|
936
|
+
isForInStatement() {
|
|
937
|
+
return this.node.type === "ForInStatement";
|
|
938
|
+
}
|
|
939
|
+
isForOfStatement() {
|
|
940
|
+
return this.node.type === "ForOfStatement";
|
|
941
|
+
}
|
|
942
|
+
isTSType() {
|
|
943
|
+
return this.node.type.startsWith("TS") && !isTSAsExpression(this.node) && !isTSTypeAssertion(this.node) && !isTSNonNullExpression(this.node) && !isTSSatisfiesExpression(this.node);
|
|
944
|
+
}
|
|
945
|
+
isTSAsExpression() {
|
|
946
|
+
return isTSAsExpression(this.node);
|
|
947
|
+
}
|
|
948
|
+
isTSTypeAssertion() {
|
|
949
|
+
return isTSTypeAssertion(this.node);
|
|
950
|
+
}
|
|
951
|
+
isTSNonNullExpression() {
|
|
952
|
+
return isTSNonNullExpression(this.node);
|
|
953
|
+
}
|
|
954
|
+
isTSSatisfiesExpression() {
|
|
955
|
+
return isTSSatisfiesExpression(this.node);
|
|
956
|
+
}
|
|
957
|
+
isProgram() {
|
|
958
|
+
return this.node.type === "Program";
|
|
959
|
+
}
|
|
960
|
+
assertExpression() {
|
|
961
|
+
if (this._hubEmpty)
|
|
962
|
+
throw new TypeError("Expected Expression");
|
|
963
|
+
assertExpression(this.node);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
function aliasesFor(type) {
|
|
967
|
+
switch (type) {
|
|
968
|
+
case "FunctionDeclaration":
|
|
969
|
+
case "FunctionExpression":
|
|
970
|
+
case "ArrowFunctionExpression":
|
|
971
|
+
return ["Function"];
|
|
972
|
+
case "Property":
|
|
973
|
+
return ["ObjectProperty"];
|
|
974
|
+
case "Literal":
|
|
975
|
+
return [];
|
|
976
|
+
default:
|
|
977
|
+
return [];
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
function runVisitor(visitors, type, path, phase) {
|
|
981
|
+
const names = [type, ...aliasesFor(type)];
|
|
982
|
+
for (const key of Object.keys(visitors)) {
|
|
983
|
+
if (key.includes("|")) {
|
|
984
|
+
for (const part of key.split("|")) {
|
|
985
|
+
if (part === type || aliasesFor(type).includes(part)) {
|
|
986
|
+
names.push(key);
|
|
987
|
+
break;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
const seen = new Set;
|
|
993
|
+
for (const name of names) {
|
|
994
|
+
if (seen.has(name))
|
|
995
|
+
continue;
|
|
996
|
+
seen.add(name);
|
|
997
|
+
const v = visitors[name];
|
|
998
|
+
if (!v)
|
|
999
|
+
continue;
|
|
1000
|
+
if (typeof v === "function") {
|
|
1001
|
+
if (phase === "enter")
|
|
1002
|
+
v(path);
|
|
1003
|
+
} else if (phase === "enter" && v.enter) {
|
|
1004
|
+
v.enter(path);
|
|
1005
|
+
} else if (phase === "exit" && v.exit) {
|
|
1006
|
+
v.exit(path);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
if (phase === "enter" && typeof visitors.enter === "function") {
|
|
1010
|
+
visitors.enter(path);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
function traverseInner(path, visitors, isRoot) {
|
|
1014
|
+
if (isScopeNode(path.node) && path.parentPath) {
|
|
1015
|
+
const scoped = new Scope(path, path.parentPath.scope ?? null);
|
|
1016
|
+
path.scope = scoped;
|
|
1017
|
+
seedScopeBindings(scoped);
|
|
1018
|
+
}
|
|
1019
|
+
path._skip = false;
|
|
1020
|
+
runVisitor(visitors, path.node.type, path, "enter");
|
|
1021
|
+
if (path.shouldStop)
|
|
1022
|
+
return true;
|
|
1023
|
+
if (!path.shouldSkip && !path._removed) {
|
|
1024
|
+
for (const key of keysFor(path.node)) {
|
|
1025
|
+
const child = path.node[key];
|
|
1026
|
+
if (Array.isArray(child)) {
|
|
1027
|
+
const seen = new WeakSet;
|
|
1028
|
+
for (let i = 0;i < child.length; ) {
|
|
1029
|
+
const c = child[i];
|
|
1030
|
+
if (!c || typeof c !== "object" || !("type" in c)) {
|
|
1031
|
+
i += 1;
|
|
1032
|
+
continue;
|
|
1033
|
+
}
|
|
1034
|
+
if (seen.has(c)) {
|
|
1035
|
+
i += 1;
|
|
1036
|
+
continue;
|
|
1037
|
+
}
|
|
1038
|
+
seen.add(c);
|
|
1039
|
+
const childP = new Path(c, path.node, path, i, child, key, path.scope);
|
|
1040
|
+
const stopped = traverseInner(childP, visitors, false);
|
|
1041
|
+
if (stopped || path.shouldStop)
|
|
1042
|
+
return true;
|
|
1043
|
+
if (child[i] === c)
|
|
1044
|
+
i += 1;
|
|
1045
|
+
}
|
|
1046
|
+
} else if (child && typeof child === "object" && "type" in child) {
|
|
1047
|
+
const childP = new Path(child, path.node, path, key, path.node, null, path.scope);
|
|
1048
|
+
const stopped = traverseInner(childP, visitors, false);
|
|
1049
|
+
if (stopped || path.shouldStop)
|
|
1050
|
+
return true;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
runVisitor(visitors, path.node.type, path, "exit");
|
|
1055
|
+
return path.shouldStop;
|
|
1056
|
+
}
|
|
1057
|
+
function traverse(ast, visitors) {
|
|
1058
|
+
const root = ast.type === "File" && ast.program ? ast.program : ast;
|
|
1059
|
+
const rootPath = new Path(root, null, null, null, null, null, undefined);
|
|
1060
|
+
rootPath.scope = new Scope(rootPath, null);
|
|
1061
|
+
rootPath.scope.crawl();
|
|
1062
|
+
traverseInner(rootPath, visitors, true);
|
|
1063
|
+
}
|
|
1064
|
+
// src/auto-cc.ts
|
|
1065
|
+
var CC_SOURCE_MODULES = new Set(["sinwan", "sinwan/component"]);
|
|
1066
|
+
var EXCLUDE_KEYS = new Set([
|
|
1067
|
+
"loc",
|
|
1068
|
+
"start",
|
|
1069
|
+
"end",
|
|
1070
|
+
"type",
|
|
1071
|
+
"leadingComments",
|
|
1072
|
+
"trailingComments",
|
|
1073
|
+
"innerComments",
|
|
1074
|
+
"extra"
|
|
1075
|
+
]);
|
|
1076
|
+
function isComponentName(name) {
|
|
1077
|
+
return !!name && /^[A-Z]/.test(name);
|
|
1078
|
+
}
|
|
1079
|
+
function containsJsx(node, enterFunctions) {
|
|
1080
|
+
if (!node || typeof node !== "object")
|
|
1081
|
+
return false;
|
|
1082
|
+
if (isJSXElement(node) || isJSXFragment(node))
|
|
1083
|
+
return true;
|
|
1084
|
+
if (!enterFunctions && (isFunctionExpression(node) || isArrowFunctionExpression(node) || isFunctionDeclaration(node))) {
|
|
1085
|
+
return false;
|
|
1086
|
+
}
|
|
1087
|
+
for (const key of Object.keys(node)) {
|
|
1088
|
+
if (EXCLUDE_KEYS.has(key))
|
|
1089
|
+
continue;
|
|
1090
|
+
const val = node[key];
|
|
1091
|
+
if (Array.isArray(val)) {
|
|
1092
|
+
const foundInArray = val.some((item) => Boolean(item) && typeof item === "object" && containsJsx(item, enterFunctions));
|
|
1093
|
+
if (foundInArray)
|
|
1094
|
+
return true;
|
|
1095
|
+
} else if (val && typeof val === "object" && containsJsx(val, enterFunctions)) {
|
|
1096
|
+
return true;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
return false;
|
|
1100
|
+
}
|
|
1101
|
+
function functionReturnsJsx(fnPath) {
|
|
1102
|
+
const fn = fnPath.node;
|
|
1103
|
+
if (isArrowFunctionExpression(fn) && !isBlockStatement(fn.body)) {
|
|
1104
|
+
return containsJsx(fn.body, false);
|
|
1105
|
+
}
|
|
1106
|
+
let found = false;
|
|
1107
|
+
fnPath.traverse({
|
|
1108
|
+
ReturnStatement(p) {
|
|
1109
|
+
if (p.node.argument && containsJsx(p.node.argument, false)) {
|
|
1110
|
+
found = true;
|
|
1111
|
+
p.stop();
|
|
1112
|
+
}
|
|
1113
|
+
},
|
|
1114
|
+
Function(p) {
|
|
1115
|
+
p.skip();
|
|
1116
|
+
}
|
|
1117
|
+
});
|
|
1118
|
+
return found;
|
|
1119
|
+
}
|
|
1120
|
+
function isComponentArity(fn) {
|
|
1121
|
+
if (isFunctionDeclaration(fn) && fn.generator)
|
|
1122
|
+
return false;
|
|
1123
|
+
if ((isFunctionExpression(fn) || isArrowFunctionExpression(fn)) && fn.generator) {
|
|
1124
|
+
return false;
|
|
1125
|
+
}
|
|
1126
|
+
return fn.params.length <= 1;
|
|
1127
|
+
}
|
|
1128
|
+
function collectCcAliases(ast) {
|
|
1129
|
+
const aliases = new Set;
|
|
1130
|
+
traverse(ast, {
|
|
1131
|
+
ImportDeclaration(path) {
|
|
1132
|
+
const source = moduleSourceValue(path.node.source);
|
|
1133
|
+
if (!source)
|
|
1134
|
+
return;
|
|
1135
|
+
if (!CC_SOURCE_MODULES.has(source))
|
|
1136
|
+
return;
|
|
1137
|
+
for (const spec of path.node.specifiers ?? []) {
|
|
1138
|
+
if (!isImportSpecifier(spec))
|
|
1139
|
+
continue;
|
|
1140
|
+
const imported = importedBindingName(spec.imported);
|
|
1141
|
+
if (imported === "cc") {
|
|
1142
|
+
aliases.add(spec.local.name);
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
});
|
|
1147
|
+
return { aliases, alreadyImported: aliases.size > 0 };
|
|
1148
|
+
}
|
|
1149
|
+
function unwrapFunctionPath(fnPath) {
|
|
1150
|
+
let current = fnPath;
|
|
1151
|
+
while (current && isParenthesizedExpression(current.node)) {
|
|
1152
|
+
current = singlePath(current.get("expression"));
|
|
1153
|
+
}
|
|
1154
|
+
return current;
|
|
1155
|
+
}
|
|
1156
|
+
function ccCall(arg, ccName) {
|
|
1157
|
+
return callExpression(identifier(ccName), [arg]);
|
|
1158
|
+
}
|
|
1159
|
+
function autoWrapComponents2(ast, filename) {
|
|
1160
|
+
const { aliases, alreadyImported } = collectCcAliases(ast);
|
|
1161
|
+
const ccName = aliases.values().next().value;
|
|
1162
|
+
let changed = false;
|
|
1163
|
+
const wrapIfCandidate = (fnPath, name) => {
|
|
1164
|
+
if (name !== null && !isComponentName(name))
|
|
1165
|
+
return false;
|
|
1166
|
+
const fn = fnPath.node;
|
|
1167
|
+
if (!isComponentArity(fn))
|
|
1168
|
+
return false;
|
|
1169
|
+
if (!functionReturnsJsx(fnPath))
|
|
1170
|
+
return false;
|
|
1171
|
+
return true;
|
|
1172
|
+
};
|
|
1173
|
+
traverse(ast, {
|
|
1174
|
+
ExportNamedDeclaration(path) {
|
|
1175
|
+
const decl = path.node.declaration;
|
|
1176
|
+
if (!decl)
|
|
1177
|
+
return;
|
|
1178
|
+
if (isFunctionDeclaration(decl) && isIdentifier(decl.id)) {
|
|
1179
|
+
const name = decl.id.name;
|
|
1180
|
+
const fnPath = path.get("declaration");
|
|
1181
|
+
if (!wrapIfCandidate(fnPath, name))
|
|
1182
|
+
return;
|
|
1183
|
+
const fnExpr = functionExpression(decl.id, decl.params, decl.body, decl.generator, decl.async);
|
|
1184
|
+
const varDecl = variableDeclaration("const", [
|
|
1185
|
+
variableDeclarator(identifier(name), ccCall(fnExpr, ccName ?? "cc"))
|
|
1186
|
+
]);
|
|
1187
|
+
path.replaceWith(exportNamedDeclaration(varDecl, []));
|
|
1188
|
+
changed = true;
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1191
|
+
if (isVariableDeclaration(decl)) {
|
|
1192
|
+
for (const dPath of path.get("declaration.declarations")) {
|
|
1193
|
+
const id = dPath.node.id;
|
|
1194
|
+
if (!isIdentifier(id) || !isComponentName(id.name))
|
|
1195
|
+
continue;
|
|
1196
|
+
const init = dPath.node.init ? unwrapParens(dPath.node.init) : dPath.node.init;
|
|
1197
|
+
if (!init)
|
|
1198
|
+
continue;
|
|
1199
|
+
let initPath = unwrapFunctionPath(dPath.get("init"));
|
|
1200
|
+
if (isCallExpression(initPath.node) && isIdentifier(initPath.node.callee) && aliases.has(initPath.node.callee.name)) {
|
|
1201
|
+
continue;
|
|
1202
|
+
}
|
|
1203
|
+
if (!isFunction(initPath.node))
|
|
1204
|
+
continue;
|
|
1205
|
+
if (!wrapIfCandidate(initPath, id.name))
|
|
1206
|
+
continue;
|
|
1207
|
+
dPath.node.init = ccCall(initPath.node, ccName ?? "cc");
|
|
1208
|
+
changed = true;
|
|
1209
|
+
}
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
},
|
|
1213
|
+
ExportDefaultDeclaration(path) {
|
|
1214
|
+
const decl = isNode(path.node.declaration) ? unwrapParens(path.node.declaration) : path.node.declaration;
|
|
1215
|
+
if (isFunctionDeclaration(decl) && isIdentifier(decl.id)) {
|
|
1216
|
+
const name = decl.id.name;
|
|
1217
|
+
const fnPath = path.get("declaration");
|
|
1218
|
+
if (!wrapIfCandidate(fnPath, name))
|
|
1219
|
+
return;
|
|
1220
|
+
const fnDecl = functionDeclaration(decl.id, decl.params, decl.body, decl.generator, decl.async);
|
|
1221
|
+
const exportDefault = exportDefaultDeclaration(ccCall(identifier(name), ccName ?? "cc"));
|
|
1222
|
+
path.replaceWithMultiple([fnDecl, exportDefault]);
|
|
1223
|
+
changed = true;
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
1226
|
+
if (isFunctionDeclaration(decl) && !decl.id) {
|
|
1227
|
+
const fnPath = path.get("declaration");
|
|
1228
|
+
if (!wrapIfCandidate(fnPath, null))
|
|
1229
|
+
return;
|
|
1230
|
+
const fnExpr = functionExpression(null, decl.params, decl.body, decl.generator, decl.async);
|
|
1231
|
+
path.node.declaration = ccCall(fnExpr, ccName ?? "cc");
|
|
1232
|
+
changed = true;
|
|
1233
|
+
return;
|
|
1234
|
+
}
|
|
1235
|
+
if (isArrowFunctionExpression(decl) || isFunctionExpression(decl)) {
|
|
1236
|
+
const declPath = unwrapFunctionPath(path.get("declaration"));
|
|
1237
|
+
if (!isFunction(declPath.node))
|
|
1238
|
+
return;
|
|
1239
|
+
if (!wrapIfCandidate(declPath, null))
|
|
1240
|
+
return;
|
|
1241
|
+
path.node.declaration = ccCall(declPath.node, ccName ?? "cc");
|
|
1242
|
+
changed = true;
|
|
1243
|
+
return;
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
});
|
|
1247
|
+
if (changed && !alreadyImported) {
|
|
1248
|
+
ast.program.body.unshift(importDeclaration([importSpecifier(identifier("cc"), identifier("cc"))], stringLiteral("sinwan/component")));
|
|
1249
|
+
}
|
|
1250
|
+
return changed;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
// src/reactive-wrap.ts
|
|
1254
|
+
import * as path2 from "path";
|
|
1255
|
+
|
|
1256
|
+
// src/ast/parse.ts
|
|
1257
|
+
import { parseSync } from "oxc-parser";
|
|
1258
|
+
|
|
1259
|
+
// src/ast/node.ts
|
|
1260
|
+
function isAstNode(value) {
|
|
1261
|
+
return !!value && typeof value === "object" && typeof value.type === "string";
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
// src/ast/parse.ts
|
|
1265
|
+
function containsSpan(node, start, end) {
|
|
1266
|
+
const nodeStart = node.start ?? -1;
|
|
1267
|
+
const nodeEnd = node.end ?? -1;
|
|
1268
|
+
return nodeStart <= start && nodeEnd >= end;
|
|
1269
|
+
}
|
|
1270
|
+
function attachComments(program, comments) {
|
|
1271
|
+
if (comments.length === 0)
|
|
1272
|
+
return;
|
|
1273
|
+
const nodes = [];
|
|
1274
|
+
const visit = (node) => {
|
|
1275
|
+
if (typeof node.start === "number" && typeof node.end === "number") {
|
|
1276
|
+
nodes.push(node);
|
|
1277
|
+
}
|
|
1278
|
+
for (const key of Object.keys(node)) {
|
|
1279
|
+
if (key === "leadingComments" || key === "trailingComments" || key === "innerComments")
|
|
1280
|
+
continue;
|
|
1281
|
+
const val = node[key];
|
|
1282
|
+
if (Array.isArray(val)) {
|
|
1283
|
+
for (const item of val) {
|
|
1284
|
+
if (item && typeof item === "object" && "type" in item) {
|
|
1285
|
+
visit(item);
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
} else if (val && typeof val === "object" && "type" in val) {
|
|
1289
|
+
visit(val);
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
};
|
|
1293
|
+
visit(program);
|
|
1294
|
+
nodes.sort((a, b) => {
|
|
1295
|
+
const ds = (a.start ?? 0) - (b.start ?? 0);
|
|
1296
|
+
if (ds !== 0)
|
|
1297
|
+
return ds;
|
|
1298
|
+
return (b.end ?? 0) - (a.end ?? 0);
|
|
1299
|
+
});
|
|
1300
|
+
for (const comment of comments) {
|
|
1301
|
+
const mapped = {
|
|
1302
|
+
type: comment.type === "Line" ? "CommentLine" : "CommentBlock",
|
|
1303
|
+
value: comment.value
|
|
1304
|
+
};
|
|
1305
|
+
let container = null;
|
|
1306
|
+
let containerSpan = Number.POSITIVE_INFINITY;
|
|
1307
|
+
for (const node of nodes) {
|
|
1308
|
+
if (node.type === "Program")
|
|
1309
|
+
continue;
|
|
1310
|
+
if (!containsSpan(node, comment.start, comment.end))
|
|
1311
|
+
continue;
|
|
1312
|
+
const span = (node.end ?? 0) - (node.start ?? 0);
|
|
1313
|
+
if (span < containerSpan) {
|
|
1314
|
+
container = node;
|
|
1315
|
+
containerSpan = span;
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
const commentIsNode = container != null && (container.type === "JSXEmptyExpression" || (container.start ?? -1) === comment.start && (container.end ?? -1) === comment.end);
|
|
1319
|
+
if (commentIsNode && container) {
|
|
1320
|
+
const list = container.leadingComments ??= [];
|
|
1321
|
+
list.push(mapped);
|
|
1322
|
+
continue;
|
|
1323
|
+
}
|
|
1324
|
+
let leading = null;
|
|
1325
|
+
for (const node of nodes) {
|
|
1326
|
+
if (node.type === "Program" || node === container)
|
|
1327
|
+
continue;
|
|
1328
|
+
const start = node.start ?? 0;
|
|
1329
|
+
if (start < comment.end)
|
|
1330
|
+
continue;
|
|
1331
|
+
if (container && !containsSpan(container, node.start ?? 0, node.end ?? 0)) {
|
|
1332
|
+
continue;
|
|
1333
|
+
}
|
|
1334
|
+
leading = node;
|
|
1335
|
+
break;
|
|
1336
|
+
}
|
|
1337
|
+
if (leading) {
|
|
1338
|
+
const list = leading.leadingComments ??= [];
|
|
1339
|
+
list.push(mapped);
|
|
1340
|
+
continue;
|
|
1341
|
+
}
|
|
1342
|
+
if (container) {
|
|
1343
|
+
const list = container.innerComments ??= [];
|
|
1344
|
+
list.push(mapped);
|
|
1345
|
+
continue;
|
|
1346
|
+
}
|
|
1347
|
+
const list = program.trailingComments ??= [];
|
|
1348
|
+
list.push(mapped);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
function asProgram(parsed) {
|
|
1352
|
+
if (!isAstNode(parsed) || parsed.type !== "Program" || !Array.isArray(parsed.body)) {
|
|
1353
|
+
throw new Error("oxc-parser did not return a Program");
|
|
1354
|
+
}
|
|
1355
|
+
return {
|
|
1356
|
+
...parsed,
|
|
1357
|
+
type: "Program",
|
|
1358
|
+
body: parsed.body,
|
|
1359
|
+
sourceType: parsed.sourceType === "script" ? "script" : "module"
|
|
1360
|
+
};
|
|
1361
|
+
}
|
|
1362
|
+
function parse(code, options = {}) {
|
|
1363
|
+
const filename = options.sourceFilename ?? "unknown.tsx";
|
|
1364
|
+
const sourceType = options.sourceType === "script" ? "script" : options.sourceType === "unambiguous" ? "unambiguous" : "module";
|
|
1365
|
+
const result = parseSync(filename, code, {
|
|
1366
|
+
lang: "tsx",
|
|
1367
|
+
sourceType
|
|
1368
|
+
});
|
|
1369
|
+
if (result.errors.length > 0 && !options.errorRecovery) {
|
|
1370
|
+
throw new Error(result.errors[0].message);
|
|
1371
|
+
}
|
|
1372
|
+
const program = asProgram(result.program);
|
|
1373
|
+
const comments = result.comments ?? [];
|
|
1374
|
+
attachComments(program, comments);
|
|
1375
|
+
return {
|
|
1376
|
+
type: "File",
|
|
1377
|
+
program,
|
|
1378
|
+
comments: comments.map((c) => ({
|
|
1379
|
+
type: c.type === "Line" ? "CommentLine" : "CommentBlock",
|
|
1380
|
+
value: c.value
|
|
1381
|
+
}))
|
|
1382
|
+
};
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
// src/reactive-wrap.ts
|
|
1386
|
+
import * as fs2 from "fs";
|
|
1387
|
+
|
|
1388
|
+
// src/analyze.ts
|
|
1389
|
+
import * as fs from "fs";
|
|
1390
|
+
import * as path from "path";
|
|
1391
|
+
function functionRef(fn) {
|
|
1392
|
+
return { start: fn.start ?? -1, end: fn.end ?? -1 };
|
|
1393
|
+
}
|
|
1394
|
+
function refKey(ref) {
|
|
1395
|
+
return `${ref.start}:${ref.end}`;
|
|
1396
|
+
}
|
|
1397
|
+
function findFunctionByRef(ast, ref) {
|
|
1398
|
+
let found = null;
|
|
1399
|
+
traverse(ast, {
|
|
1400
|
+
Function(path) {
|
|
1401
|
+
const node = path.node;
|
|
1402
|
+
if (node.start === ref.start && node.end === ref.end) {
|
|
1403
|
+
found = node;
|
|
1404
|
+
path.stop();
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
});
|
|
1408
|
+
return found;
|
|
1409
|
+
}
|
|
1410
|
+
function serializeReactiveProps(reactiveProps) {
|
|
1411
|
+
const result = {};
|
|
1412
|
+
for (const [filePath, fileMap] of reactiveProps) {
|
|
1413
|
+
const entry = {};
|
|
1414
|
+
for (const [exportName, props] of fileMap) {
|
|
1415
|
+
entry[exportName] = Array.from(props);
|
|
1416
|
+
}
|
|
1417
|
+
result[filePath] = entry;
|
|
1418
|
+
}
|
|
1419
|
+
return result;
|
|
1420
|
+
}
|
|
1421
|
+
function serializeModule(mod) {
|
|
1422
|
+
const localScopes = {};
|
|
1423
|
+
for (const [fn, scope] of mod.localScopes) {
|
|
1424
|
+
localScopes[refKey(functionRef(fn))] = {
|
|
1425
|
+
bindings: Object.fromEntries(scope.bindings)
|
|
1426
|
+
};
|
|
1427
|
+
}
|
|
1428
|
+
const localCallGraph = {};
|
|
1429
|
+
for (const [callerFn, sites] of mod.localCallGraph) {
|
|
1430
|
+
localCallGraph[refKey(functionRef(callerFn))] = sites.map((s) => ({
|
|
1431
|
+
callee: functionRef(s.callee),
|
|
1432
|
+
props: s.props,
|
|
1433
|
+
spreads: s.spreads
|
|
1434
|
+
}));
|
|
1435
|
+
}
|
|
1436
|
+
return {
|
|
1437
|
+
imports: Object.fromEntries(mod.imports),
|
|
1438
|
+
exports: Object.fromEntries(Array.from(mod.exports.entries()).map(([name, fn]) => [
|
|
1439
|
+
name,
|
|
1440
|
+
functionRef(fn)
|
|
1441
|
+
])),
|
|
1442
|
+
defaultExport: mod.defaultExport ? functionRef(mod.defaultExport) : null,
|
|
1443
|
+
componentFunctions: Array.from(mod.componentFunctions).map(functionRef),
|
|
1444
|
+
localScopes,
|
|
1445
|
+
localCallGraph,
|
|
1446
|
+
importedCallSites: mod.importedCallSites.map((s) => ({
|
|
1447
|
+
caller: functionRef(s.caller),
|
|
1448
|
+
source: s.source,
|
|
1449
|
+
name: s.name,
|
|
1450
|
+
props: s.props,
|
|
1451
|
+
spreads: s.spreads
|
|
1452
|
+
}))
|
|
1453
|
+
};
|
|
1454
|
+
}
|
|
1455
|
+
function restoreModule(filePath, serialized) {
|
|
1456
|
+
let code;
|
|
1457
|
+
try {
|
|
1458
|
+
code = fs.readFileSync(filePath, "utf-8");
|
|
1459
|
+
} catch {
|
|
1460
|
+
return null;
|
|
1461
|
+
}
|
|
1462
|
+
const ast = parse(code, {
|
|
1463
|
+
sourceType: "module",
|
|
1464
|
+
plugins: ["jsx", "typescript"]
|
|
1465
|
+
});
|
|
1466
|
+
const imports = new Map(Object.entries(serialized.imports));
|
|
1467
|
+
const exports = new Map;
|
|
1468
|
+
for (const [name, ref] of Object.entries(serialized.exports)) {
|
|
1469
|
+
const fn = findFunctionByRef(ast, ref);
|
|
1470
|
+
if (!fn)
|
|
1471
|
+
return null;
|
|
1472
|
+
exports.set(name, fn);
|
|
1473
|
+
}
|
|
1474
|
+
const defaultExport = serialized.defaultExport ? findFunctionByRef(ast, serialized.defaultExport) : null;
|
|
1475
|
+
if (serialized.defaultExport && !defaultExport)
|
|
1476
|
+
return null;
|
|
1477
|
+
const componentFunctions = new Set;
|
|
1478
|
+
for (const ref of serialized.componentFunctions) {
|
|
1479
|
+
const fn = findFunctionByRef(ast, ref);
|
|
1480
|
+
if (!fn)
|
|
1481
|
+
return null;
|
|
1482
|
+
componentFunctions.add(fn);
|
|
1483
|
+
}
|
|
1484
|
+
const localScopes = new Map;
|
|
1485
|
+
for (const [key, scope] of Object.entries(serialized.localScopes)) {
|
|
1486
|
+
const ref = parseRefKey(key);
|
|
1487
|
+
if (!ref)
|
|
1488
|
+
return null;
|
|
1489
|
+
const fn = findFunctionByRef(ast, ref);
|
|
1490
|
+
if (!fn)
|
|
1491
|
+
return null;
|
|
1492
|
+
localScopes.set(fn, {
|
|
1493
|
+
bindings: new Map(Object.entries(scope.bindings))
|
|
1494
|
+
});
|
|
1495
|
+
}
|
|
1496
|
+
const localCallGraph = new Map;
|
|
1497
|
+
for (const [key, sites] of Object.entries(serialized.localCallGraph)) {
|
|
1498
|
+
const ref = parseRefKey(key);
|
|
1499
|
+
if (!ref)
|
|
1500
|
+
return null;
|
|
1501
|
+
const callerFn = findFunctionByRef(ast, ref);
|
|
1502
|
+
if (!callerFn)
|
|
1503
|
+
return null;
|
|
1504
|
+
const restored = [];
|
|
1505
|
+
for (const s of sites) {
|
|
1506
|
+
const callee = findFunctionByRef(ast, s.callee);
|
|
1507
|
+
if (!callee)
|
|
1508
|
+
return null;
|
|
1509
|
+
restored.push({
|
|
1510
|
+
callee,
|
|
1511
|
+
props: s.props,
|
|
1512
|
+
spreads: s.spreads
|
|
1513
|
+
});
|
|
1514
|
+
}
|
|
1515
|
+
localCallGraph.set(callerFn, restored);
|
|
1516
|
+
}
|
|
1517
|
+
const importedCallSites = [];
|
|
1518
|
+
for (const s of serialized.importedCallSites) {
|
|
1519
|
+
const caller = findFunctionByRef(ast, s.caller);
|
|
1520
|
+
if (!caller)
|
|
1521
|
+
return null;
|
|
1522
|
+
importedCallSites.push({
|
|
1523
|
+
caller,
|
|
1524
|
+
source: s.source,
|
|
1525
|
+
name: s.name,
|
|
1526
|
+
props: s.props,
|
|
1527
|
+
spreads: s.spreads
|
|
1528
|
+
});
|
|
1529
|
+
}
|
|
1530
|
+
return {
|
|
1531
|
+
filePath,
|
|
1532
|
+
imports,
|
|
1533
|
+
exports,
|
|
1534
|
+
defaultExport,
|
|
1535
|
+
componentFunctions,
|
|
1536
|
+
localScopes,
|
|
1537
|
+
localCallGraph,
|
|
1538
|
+
importedCallSites
|
|
1539
|
+
};
|
|
1540
|
+
}
|
|
1541
|
+
function parseRefKey(key) {
|
|
1542
|
+
const parts = key.split(":");
|
|
1543
|
+
if (parts.length !== 2)
|
|
1544
|
+
return null;
|
|
1545
|
+
const startStr = parts[0];
|
|
1546
|
+
const endStr = parts[1];
|
|
1547
|
+
if (!startStr || !endStr)
|
|
1548
|
+
return null;
|
|
1549
|
+
const start = Number(startStr);
|
|
1550
|
+
const end = Number(endStr);
|
|
1551
|
+
if (Number.isNaN(start) || Number.isNaN(end))
|
|
1552
|
+
return null;
|
|
1553
|
+
return { start, end };
|
|
1554
|
+
}
|
|
1555
|
+
function collectImports(ast) {
|
|
1556
|
+
const imports = new Map;
|
|
1557
|
+
traverse(ast, {
|
|
1558
|
+
ImportDeclaration(path) {
|
|
1559
|
+
const source = moduleSourceValue(path.node.source);
|
|
1560
|
+
if (!source)
|
|
1561
|
+
return;
|
|
1562
|
+
for (const spec of path.node.specifiers) {
|
|
1563
|
+
if (isImportSpecifier(spec)) {
|
|
1564
|
+
const localName = spec.local.name;
|
|
1565
|
+
const importedName = importedBindingName(spec.imported);
|
|
1566
|
+
if (!importedName)
|
|
1567
|
+
continue;
|
|
1568
|
+
imports.set(localName, { source, importedName });
|
|
1569
|
+
} else if (isImportDefaultSpecifier(spec)) {
|
|
1570
|
+
imports.set(spec.local.name, { source, importedName: "default" });
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
});
|
|
1575
|
+
return imports;
|
|
1576
|
+
}
|
|
1577
|
+
function collectImportedCallSites(ast, componentNames, imports) {
|
|
1578
|
+
const sites = [];
|
|
1579
|
+
traverse(ast, {
|
|
1580
|
+
JSXElement(path) {
|
|
1581
|
+
const name = path.node.openingElement.name;
|
|
1582
|
+
if (!isJSXIdentifier(name))
|
|
1583
|
+
return;
|
|
1584
|
+
const tagName = name.name;
|
|
1585
|
+
if (!/^[A-Z]/.test(tagName))
|
|
1586
|
+
return;
|
|
1587
|
+
if (componentNames.has(tagName))
|
|
1588
|
+
return;
|
|
1589
|
+
const importInfo = imports.get(tagName);
|
|
1590
|
+
if (!importInfo)
|
|
1591
|
+
return;
|
|
1592
|
+
const callerPath = path.findParent((p) => p.isFunction());
|
|
1593
|
+
if (!callerPath)
|
|
1594
|
+
return;
|
|
1595
|
+
const callerFn = callerPath.node;
|
|
1596
|
+
const props = [];
|
|
1597
|
+
const spreads = [];
|
|
1598
|
+
for (const attr of path.node.openingElement.attributes) {
|
|
1599
|
+
if (isJSXSpreadAttribute(attr)) {
|
|
1600
|
+
if (attr.argument) {
|
|
1601
|
+
spreads.push(attr.argument);
|
|
1602
|
+
}
|
|
1603
|
+
continue;
|
|
1604
|
+
}
|
|
1605
|
+
if (isJSXAttribute(attr)) {
|
|
1606
|
+
const attrName = attr.name.name;
|
|
1607
|
+
if (!attrName)
|
|
1608
|
+
continue;
|
|
1609
|
+
if (isJSXExpressionContainer(attr.value)) {
|
|
1610
|
+
const expr = attr.value.expression;
|
|
1611
|
+
if (!expr || isJSXEmptyExpression(expr))
|
|
1612
|
+
continue;
|
|
1613
|
+
props.push({ name: attrName, value: expr });
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
const childExprs = [];
|
|
1618
|
+
for (const child of path.node.children) {
|
|
1619
|
+
if (isJSXExpressionContainer(child)) {
|
|
1620
|
+
const expr = child.expression;
|
|
1621
|
+
if (!expr || isJSXEmptyExpression(expr))
|
|
1622
|
+
continue;
|
|
1623
|
+
childExprs.push(expr);
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
if (childExprs.length === 1) {
|
|
1627
|
+
props.push({ name: "children", value: childExprs[0] });
|
|
1628
|
+
} else if (childExprs.length > 1) {
|
|
1629
|
+
props.push({ name: "children", value: arrayExpression(childExprs) });
|
|
1630
|
+
}
|
|
1631
|
+
sites.push({
|
|
1632
|
+
caller: callerFn,
|
|
1633
|
+
source: importInfo.source,
|
|
1634
|
+
name: importInfo.importedName,
|
|
1635
|
+
props,
|
|
1636
|
+
spreads
|
|
1637
|
+
});
|
|
1638
|
+
}
|
|
1639
|
+
});
|
|
1640
|
+
return sites;
|
|
1641
|
+
}
|
|
1642
|
+
function analyzeModule2(code, filePath) {
|
|
1643
|
+
const ast = parse(code, {
|
|
1644
|
+
sourceType: "module",
|
|
1645
|
+
plugins: ["jsx", "typescript"]
|
|
1646
|
+
});
|
|
1647
|
+
autoWrapComponents2(ast, filePath);
|
|
1648
|
+
const names = trackReactiveImports2(ast);
|
|
1649
|
+
const {
|
|
1650
|
+
functions,
|
|
1651
|
+
names: componentNames,
|
|
1652
|
+
exported
|
|
1653
|
+
} = collectComponentFunctions2(ast, names.cc);
|
|
1654
|
+
const localScopes = computeLocalScopes2(ast, names);
|
|
1655
|
+
const localCallGraph = collectComponentCallGraph2(ast, componentNames);
|
|
1656
|
+
const imports = collectImports(ast);
|
|
1657
|
+
const importedCallSites = collectImportedCallSites(ast, componentNames, imports);
|
|
1658
|
+
const exports = new Map;
|
|
1659
|
+
let defaultExport = null;
|
|
1660
|
+
for (const [localName, fn] of componentNames) {
|
|
1661
|
+
if (exported.has(fn)) {
|
|
1662
|
+
exports.set(localName, fn);
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
traverse(ast, {
|
|
1666
|
+
ExportDefaultDeclaration(path) {
|
|
1667
|
+
const decl = isNode(path.node.declaration) ? unwrapParens(path.node.declaration) : path.node.declaration;
|
|
1668
|
+
if (isIdentifier(decl)) {
|
|
1669
|
+
const fn = componentNames.get(decl.name);
|
|
1670
|
+
if (fn)
|
|
1671
|
+
defaultExport = fn;
|
|
1672
|
+
} else if (isCallExpression(decl)) {
|
|
1673
|
+
const callee = decl.callee;
|
|
1674
|
+
if (isIdentifier(callee) && names.cc.has(callee.name)) {
|
|
1675
|
+
const firstArg = decl.arguments[0];
|
|
1676
|
+
const fnArg = firstArg && isNode(firstArg) ? unwrapParens(firstArg) : firstArg;
|
|
1677
|
+
if (fnArg && isFunction(fnArg)) {
|
|
1678
|
+
defaultExport = fnArg;
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
});
|
|
1684
|
+
return {
|
|
1685
|
+
filePath,
|
|
1686
|
+
imports,
|
|
1687
|
+
exports,
|
|
1688
|
+
defaultExport,
|
|
1689
|
+
componentFunctions: functions,
|
|
1690
|
+
localScopes,
|
|
1691
|
+
localCallGraph,
|
|
1692
|
+
importedCallSites
|
|
1693
|
+
};
|
|
1694
|
+
}
|
|
1695
|
+
function loadTsConfigPaths(tsConfigPath) {
|
|
1696
|
+
try {
|
|
1697
|
+
const raw = JSON.parse(fs.readFileSync(tsConfigPath, "utf-8"));
|
|
1698
|
+
const paths = raw.compilerOptions?.paths;
|
|
1699
|
+
if (!paths)
|
|
1700
|
+
return null;
|
|
1701
|
+
const baseUrl = raw.compilerOptions?.baseUrl;
|
|
1702
|
+
const root = path.dirname(tsConfigPath);
|
|
1703
|
+
const resolvedBase = baseUrl ? path.resolve(root, baseUrl) : root;
|
|
1704
|
+
const result = new Map;
|
|
1705
|
+
for (const [pattern, mappings] of Object.entries(paths)) {
|
|
1706
|
+
result.set(pattern, mappings.map((m) => path.resolve(resolvedBase, m)));
|
|
1707
|
+
}
|
|
1708
|
+
return result;
|
|
1709
|
+
} catch {
|
|
1710
|
+
return null;
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
function matchTsConfigPath(source, paths) {
|
|
1714
|
+
for (const [pattern, mappings] of paths) {
|
|
1715
|
+
if (pattern.endsWith("/*")) {
|
|
1716
|
+
const prefix = pattern.slice(0, -1);
|
|
1717
|
+
if (source.startsWith(prefix)) {
|
|
1718
|
+
const rest = source.slice(prefix.length);
|
|
1719
|
+
for (const mapping of mappings) {
|
|
1720
|
+
if (mapping.endsWith("/*")) {
|
|
1721
|
+
return mapping.slice(0, -1) + rest;
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
} else if (pattern === source) {
|
|
1726
|
+
return mappings[0] ?? null;
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
return null;
|
|
1730
|
+
}
|
|
1731
|
+
function resolveRelativeImport(source, fromFile, extensions) {
|
|
1732
|
+
if (!source.startsWith("."))
|
|
1733
|
+
return null;
|
|
1734
|
+
const base = path.resolve(path.dirname(fromFile), source);
|
|
1735
|
+
const candidates = [
|
|
1736
|
+
...extensions.map((ext) => base + ext),
|
|
1737
|
+
...extensions.map((ext) => path.join(base, "index" + ext))
|
|
1738
|
+
];
|
|
1739
|
+
for (const candidate of candidates) {
|
|
1740
|
+
if (fs.existsSync(candidate))
|
|
1741
|
+
return candidate;
|
|
1742
|
+
}
|
|
1743
|
+
return null;
|
|
1744
|
+
}
|
|
1745
|
+
function parseTomlString(value) {
|
|
1746
|
+
value = value.trim();
|
|
1747
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
1748
|
+
return value.slice(1, -1);
|
|
1749
|
+
}
|
|
1750
|
+
return value;
|
|
1751
|
+
}
|
|
1752
|
+
function parseAliasObject(content, aliases) {
|
|
1753
|
+
const pairs = [];
|
|
1754
|
+
let current = "";
|
|
1755
|
+
let inQuotes = null;
|
|
1756
|
+
for (const char of content) {
|
|
1757
|
+
if (char === '"' || char === "'") {
|
|
1758
|
+
if (inQuotes === char) {
|
|
1759
|
+
inQuotes = null;
|
|
1760
|
+
} else if (!inQuotes) {
|
|
1761
|
+
inQuotes = char;
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
if (char === "," && !inQuotes) {
|
|
1765
|
+
pairs.push(current);
|
|
1766
|
+
current = "";
|
|
1767
|
+
} else {
|
|
1768
|
+
current += char;
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
if (current.trim())
|
|
1772
|
+
pairs.push(current);
|
|
1773
|
+
for (const pair of pairs) {
|
|
1774
|
+
const eq = pair.indexOf("=");
|
|
1775
|
+
if (eq < 0)
|
|
1776
|
+
continue;
|
|
1777
|
+
const key = parseTomlString(pair.slice(0, eq));
|
|
1778
|
+
const value = parseTomlString(pair.slice(eq + 1));
|
|
1779
|
+
if (key && value)
|
|
1780
|
+
aliases.set(key, value);
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
function loadBunfigAliases(bunfigPath) {
|
|
1784
|
+
try {
|
|
1785
|
+
const content = fs.readFileSync(bunfigPath, "utf-8");
|
|
1786
|
+
const aliases = new Map;
|
|
1787
|
+
const lines = content.split(`
|
|
1788
|
+
`);
|
|
1789
|
+
let section = null;
|
|
1790
|
+
for (const rawLine of lines) {
|
|
1791
|
+
const line = rawLine.trim();
|
|
1792
|
+
if (!line || line.startsWith("#"))
|
|
1793
|
+
continue;
|
|
1794
|
+
if (line.startsWith("[") && line.endsWith("]")) {
|
|
1795
|
+
section = line.slice(1, -1);
|
|
1796
|
+
continue;
|
|
1797
|
+
}
|
|
1798
|
+
if (section === "install" && line.startsWith("alias")) {
|
|
1799
|
+
const start = line.indexOf("{");
|
|
1800
|
+
const end = line.lastIndexOf("}");
|
|
1801
|
+
if (start >= 0 && end > start) {
|
|
1802
|
+
parseAliasObject(line.slice(start + 1, end), aliases);
|
|
1803
|
+
}
|
|
1804
|
+
continue;
|
|
1805
|
+
}
|
|
1806
|
+
if (section === "install.alias") {
|
|
1807
|
+
const eq = line.indexOf("=");
|
|
1808
|
+
if (eq >= 0) {
|
|
1809
|
+
const key = parseTomlString(line.slice(0, eq));
|
|
1810
|
+
const value = parseTomlString(line.slice(eq + 1));
|
|
1811
|
+
if (key && value)
|
|
1812
|
+
aliases.set(key, value);
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
return aliases.size > 0 ? aliases : null;
|
|
1817
|
+
} catch {
|
|
1818
|
+
return null;
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
function matchBunfigAlias(source, aliases, root) {
|
|
1822
|
+
for (const [alias, target] of aliases) {
|
|
1823
|
+
if (source === alias) {
|
|
1824
|
+
return path.resolve(root, target);
|
|
1825
|
+
}
|
|
1826
|
+
if (source.startsWith(alias + "/")) {
|
|
1827
|
+
return path.resolve(root, target, source.slice(alias.length + 1));
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
return null;
|
|
1831
|
+
}
|
|
1832
|
+
function resolveWithExtensions(mapped, extensions) {
|
|
1833
|
+
const candidates = [
|
|
1834
|
+
...extensions.map((ext) => mapped + ext),
|
|
1835
|
+
...extensions.map((ext) => path.join(mapped, "index" + ext))
|
|
1836
|
+
];
|
|
1837
|
+
for (const candidate of candidates) {
|
|
1838
|
+
if (fs.existsSync(candidate))
|
|
1839
|
+
return candidate;
|
|
1840
|
+
}
|
|
1841
|
+
return null;
|
|
1842
|
+
}
|
|
1843
|
+
function readPackageName(pkgDir) {
|
|
1844
|
+
try {
|
|
1845
|
+
const pkgJson = JSON.parse(fs.readFileSync(path.join(pkgDir, "package.json"), "utf-8"));
|
|
1846
|
+
return pkgJson.name ?? null;
|
|
1847
|
+
} catch {
|
|
1848
|
+
return null;
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
function resolveWorkspacePackage(source, packages, extensions) {
|
|
1852
|
+
if (packages.length === 0)
|
|
1853
|
+
return null;
|
|
1854
|
+
if (source.startsWith(".") || source.startsWith("/"))
|
|
1855
|
+
return null;
|
|
1856
|
+
let matchedPkg = null;
|
|
1857
|
+
let matchedName = null;
|
|
1858
|
+
for (const pkg of packages) {
|
|
1859
|
+
const pkgName = readPackageName(pkg);
|
|
1860
|
+
if (!pkgName)
|
|
1861
|
+
continue;
|
|
1862
|
+
if (source === pkgName || source.startsWith(pkgName + "/")) {
|
|
1863
|
+
if (!matchedName || pkgName.length > matchedName.length) {
|
|
1864
|
+
matchedName = pkgName;
|
|
1865
|
+
matchedPkg = pkg;
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
if (!matchedPkg || !matchedName)
|
|
1870
|
+
return null;
|
|
1871
|
+
const subpath = source.slice(matchedName.length + 1);
|
|
1872
|
+
let sourceDir;
|
|
1873
|
+
try {
|
|
1874
|
+
const pkgJson = JSON.parse(fs.readFileSync(path.join(matchedPkg, "package.json"), "utf-8"));
|
|
1875
|
+
if (pkgJson.source) {
|
|
1876
|
+
sourceDir = path.dirname(path.resolve(matchedPkg, pkgJson.source));
|
|
1877
|
+
} else {
|
|
1878
|
+
sourceDir = path.join(matchedPkg, "src");
|
|
1879
|
+
}
|
|
1880
|
+
} catch {
|
|
1881
|
+
sourceDir = path.join(matchedPkg, "src");
|
|
1882
|
+
}
|
|
1883
|
+
const targetBase = subpath ? path.join(sourceDir, subpath) : path.join(sourceDir, "index");
|
|
1884
|
+
return resolveWithExtensions(targetBase, extensions);
|
|
1885
|
+
}
|
|
1886
|
+
function loadPackageJsonWorkspaces(filePath) {
|
|
1887
|
+
try {
|
|
1888
|
+
const pkg = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
1889
|
+
return pkg.workspaces ?? [];
|
|
1890
|
+
} catch {
|
|
1891
|
+
return [];
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
function loadPnpmWorkspaces(filePath) {
|
|
1895
|
+
try {
|
|
1896
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
1897
|
+
const lines = content.split(`
|
|
1898
|
+
`);
|
|
1899
|
+
const patterns = [];
|
|
1900
|
+
let inPackages = false;
|
|
1901
|
+
for (const rawLine of lines) {
|
|
1902
|
+
const line = rawLine.trim();
|
|
1903
|
+
if (!line || line.startsWith("#"))
|
|
1904
|
+
continue;
|
|
1905
|
+
if (line.startsWith("packages:")) {
|
|
1906
|
+
inPackages = true;
|
|
1907
|
+
continue;
|
|
1908
|
+
}
|
|
1909
|
+
if (inPackages) {
|
|
1910
|
+
if (!line.startsWith("-") && !line.startsWith(" ")) {
|
|
1911
|
+
inPackages = false;
|
|
1912
|
+
continue;
|
|
1913
|
+
}
|
|
1914
|
+
const match = line.match(/^-\s*["']?([^"']+)["']?$/);
|
|
1915
|
+
if (match)
|
|
1916
|
+
patterns.push(match[1]);
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
return patterns;
|
|
1920
|
+
} catch {
|
|
1921
|
+
return [];
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
function expandGlob(pattern, cwd) {
|
|
1925
|
+
const fullPattern = path.resolve(cwd, pattern);
|
|
1926
|
+
const parts = fullPattern.split(path.sep);
|
|
1927
|
+
const wildcardIndex = parts.findIndex((p) => p.includes("*"));
|
|
1928
|
+
if (wildcardIndex === -1) {
|
|
1929
|
+
return fs.existsSync(fullPattern) ? [fullPattern] : [];
|
|
1930
|
+
}
|
|
1931
|
+
const prefix = parts.slice(0, wildcardIndex).join(path.sep);
|
|
1932
|
+
const suffixParts = parts.slice(wildcardIndex + 1);
|
|
1933
|
+
const suffix = suffixParts.join(path.sep);
|
|
1934
|
+
let entries;
|
|
1935
|
+
try {
|
|
1936
|
+
entries = fs.readdirSync(prefix, { withFileTypes: true });
|
|
1937
|
+
} catch {
|
|
1938
|
+
return [];
|
|
1939
|
+
}
|
|
1940
|
+
const results = [];
|
|
1941
|
+
for (const entry of entries) {
|
|
1942
|
+
if (!entry.isDirectory())
|
|
1943
|
+
continue;
|
|
1944
|
+
const candidate = path.join(prefix, entry.name, suffix);
|
|
1945
|
+
results.push(...expandGlob(candidate, cwd));
|
|
1946
|
+
}
|
|
1947
|
+
return results;
|
|
1948
|
+
}
|
|
1949
|
+
function loadWorkspacePackages(workspaces, root) {
|
|
1950
|
+
const patternEntries = [];
|
|
1951
|
+
if (typeof workspaces === "string") {
|
|
1952
|
+
const resolved = path.resolve(root, workspaces);
|
|
1953
|
+
if (fs.existsSync(resolved)) {
|
|
1954
|
+
const stat = fs.statSync(resolved);
|
|
1955
|
+
if (stat.isDirectory()) {
|
|
1956
|
+
patternEntries.push({ pattern: workspaces, base: root });
|
|
1957
|
+
} else if (resolved.endsWith("package.json")) {
|
|
1958
|
+
const base = path.dirname(resolved);
|
|
1959
|
+
for (const p of loadPackageJsonWorkspaces(resolved)) {
|
|
1960
|
+
patternEntries.push({ pattern: p, base });
|
|
1961
|
+
}
|
|
1962
|
+
} else if (resolved.endsWith("pnpm-workspace.yaml")) {
|
|
1963
|
+
const base = path.dirname(resolved);
|
|
1964
|
+
for (const p of loadPnpmWorkspaces(resolved)) {
|
|
1965
|
+
patternEntries.push({ pattern: p, base });
|
|
1966
|
+
}
|
|
1967
|
+
} else {
|
|
1968
|
+
patternEntries.push({ pattern: workspaces, base: root });
|
|
1969
|
+
}
|
|
1970
|
+
} else {
|
|
1971
|
+
patternEntries.push({ pattern: workspaces, base: root });
|
|
1972
|
+
}
|
|
1973
|
+
} else if (Array.isArray(workspaces)) {
|
|
1974
|
+
for (const p of workspaces) {
|
|
1975
|
+
patternEntries.push({ pattern: p, base: root });
|
|
1976
|
+
}
|
|
1977
|
+
} else if (workspaces && typeof workspaces === "object") {
|
|
1978
|
+
if (workspaces.file) {
|
|
1979
|
+
const resolved = path.resolve(root, workspaces.file);
|
|
1980
|
+
if (resolved.endsWith("package.json")) {
|
|
1981
|
+
const base = path.dirname(resolved);
|
|
1982
|
+
for (const p of loadPackageJsonWorkspaces(resolved)) {
|
|
1983
|
+
patternEntries.push({ pattern: p, base });
|
|
1984
|
+
}
|
|
1985
|
+
} else if (resolved.endsWith("pnpm-workspace.yaml")) {
|
|
1986
|
+
const base = path.dirname(resolved);
|
|
1987
|
+
for (const p of loadPnpmWorkspaces(resolved)) {
|
|
1988
|
+
patternEntries.push({ pattern: p, base });
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
if (workspaces.include) {
|
|
1993
|
+
for (const p of workspaces.include) {
|
|
1994
|
+
patternEntries.push({ pattern: p, base: root });
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
const packages = new Set;
|
|
1999
|
+
for (const { pattern, base } of patternEntries) {
|
|
2000
|
+
for (const dir of expandGlob(pattern, base)) {
|
|
2001
|
+
const pkgJson = path.join(dir, "package.json");
|
|
2002
|
+
if (fs.existsSync(pkgJson)) {
|
|
2003
|
+
packages.add(dir);
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
return Array.from(packages);
|
|
2008
|
+
}
|
|
2009
|
+
function createResolver(root, extensions, tsConfigPath, bunfigPath, workspaces) {
|
|
2010
|
+
const paths = tsConfigPath ? loadTsConfigPaths(tsConfigPath) : null;
|
|
2011
|
+
const aliases = bunfigPath ? loadBunfigAliases(bunfigPath) : null;
|
|
2012
|
+
const packages = workspaces ? loadWorkspacePackages(workspaces, root) : [];
|
|
2013
|
+
return (source, fromFile) => {
|
|
2014
|
+
if (paths) {
|
|
2015
|
+
const mapped = matchTsConfigPath(source, paths);
|
|
2016
|
+
if (mapped) {
|
|
2017
|
+
const resolved = resolveWithExtensions(mapped, extensions);
|
|
2018
|
+
if (resolved)
|
|
2019
|
+
return resolved;
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
if (aliases) {
|
|
2023
|
+
const mapped = matchBunfigAlias(source, aliases, root);
|
|
2024
|
+
if (mapped) {
|
|
2025
|
+
const resolved = resolveWithExtensions(mapped, extensions);
|
|
2026
|
+
if (resolved)
|
|
2027
|
+
return resolved;
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
const pkgResolved = resolveWorkspacePackage(source, packages, extensions);
|
|
2031
|
+
if (pkgResolved)
|
|
2032
|
+
return pkgResolved;
|
|
2033
|
+
return resolveRelativeImport(source, fromFile, extensions);
|
|
2034
|
+
};
|
|
2035
|
+
}
|
|
2036
|
+
function findFiles(root, extensions) {
|
|
2037
|
+
const files = [];
|
|
2038
|
+
function walk(dir) {
|
|
2039
|
+
let entries;
|
|
2040
|
+
try {
|
|
2041
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
2042
|
+
} catch {
|
|
2043
|
+
return;
|
|
2044
|
+
}
|
|
2045
|
+
for (const entry of entries) {
|
|
2046
|
+
const fullPath = path.join(dir, entry.name);
|
|
2047
|
+
if (entry.isDirectory()) {
|
|
2048
|
+
if (entry.name === "node_modules" || entry.name === ".git")
|
|
2049
|
+
continue;
|
|
2050
|
+
walk(fullPath);
|
|
2051
|
+
} else if (entry.isFile() && extensions.some((ext) => entry.name.endsWith(ext))) {
|
|
2052
|
+
files.push(fullPath);
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
walk(root);
|
|
2057
|
+
return files;
|
|
2058
|
+
}
|
|
2059
|
+
function analyzeProject2(options) {
|
|
2060
|
+
const extensions = options.extensions ?? [".tsx", ".ts", ".jsx", ".js"];
|
|
2061
|
+
const resolve = options.resolve ?? createResolver(options.root, extensions, options.tsConfigPath, options.bunfigPath, options.workspaces);
|
|
2062
|
+
const modules = new Map;
|
|
2063
|
+
const providedFiles = options.files;
|
|
2064
|
+
if (providedFiles) {
|
|
2065
|
+
for (const [filePath, code] of Object.entries(providedFiles)) {
|
|
2066
|
+
modules.set(filePath, analyzeModule2(code, filePath));
|
|
2067
|
+
}
|
|
2068
|
+
} else {
|
|
2069
|
+
const files = findFiles(options.root, extensions);
|
|
2070
|
+
for (const file of files) {
|
|
2071
|
+
const code = fs.readFileSync(file, "utf-8");
|
|
2072
|
+
modules.set(file, analyzeModule2(code, file));
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
function ensureAnalyzed(filePath) {
|
|
2076
|
+
if (modules.has(filePath))
|
|
2077
|
+
return modules.get(filePath);
|
|
2078
|
+
try {
|
|
2079
|
+
const code = fs.readFileSync(filePath, "utf-8");
|
|
2080
|
+
const mod = analyzeModule2(code, filePath);
|
|
2081
|
+
modules.set(filePath, mod);
|
|
2082
|
+
return mod;
|
|
2083
|
+
} catch {
|
|
2084
|
+
return;
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
const globalLocalScopes = new Map;
|
|
2088
|
+
const globalCallGraph = new Map;
|
|
2089
|
+
const globalComponentFunctions = new Set;
|
|
2090
|
+
for (const mod of modules.values()) {
|
|
2091
|
+
for (const [fn, scope] of mod.localScopes) {
|
|
2092
|
+
globalLocalScopes.set(fn, scope);
|
|
2093
|
+
}
|
|
2094
|
+
for (const fn of mod.componentFunctions) {
|
|
2095
|
+
globalComponentFunctions.add(fn);
|
|
2096
|
+
}
|
|
2097
|
+
for (const [callerFn, sites] of mod.localCallGraph) {
|
|
2098
|
+
const list = globalCallGraph.get(callerFn) ?? [];
|
|
2099
|
+
list.push(...sites);
|
|
2100
|
+
globalCallGraph.set(callerFn, list);
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2103
|
+
const reactiveProps = buildProjectReactiveProps(modules, resolve, globalLocalScopes, globalCallGraph, globalComponentFunctions, ensureAnalyzed);
|
|
2104
|
+
return { modules, reactiveProps };
|
|
2105
|
+
}
|
|
2106
|
+
function buildProjectReactiveProps(modules, resolve, globalLocalScopes, globalCallGraph, globalComponentFunctions, ensureAnalyzed) {
|
|
2107
|
+
for (const mod of modules.values()) {
|
|
2108
|
+
for (const site of mod.importedCallSites) {
|
|
2109
|
+
const resolvedFile = resolve(site.source, mod.filePath);
|
|
2110
|
+
if (!resolvedFile)
|
|
2111
|
+
continue;
|
|
2112
|
+
let targetMod = modules.get(resolvedFile);
|
|
2113
|
+
if (!targetMod && ensureAnalyzed) {
|
|
2114
|
+
targetMod = ensureAnalyzed(resolvedFile);
|
|
2115
|
+
if (targetMod) {
|
|
2116
|
+
for (const [fn, scope] of targetMod.localScopes) {
|
|
2117
|
+
globalLocalScopes.set(fn, scope);
|
|
2118
|
+
}
|
|
2119
|
+
for (const fn of targetMod.componentFunctions) {
|
|
2120
|
+
globalComponentFunctions.add(fn);
|
|
2121
|
+
}
|
|
2122
|
+
for (const [callerFn, sites] of targetMod.localCallGraph) {
|
|
2123
|
+
const list = globalCallGraph.get(callerFn) ?? [];
|
|
2124
|
+
list.push(...sites);
|
|
2125
|
+
globalCallGraph.set(callerFn, list);
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
if (!targetMod)
|
|
2130
|
+
continue;
|
|
2131
|
+
const targetFn = site.name === "default" ? targetMod.defaultExport : targetMod.exports.get(site.name);
|
|
2132
|
+
if (!targetFn)
|
|
2133
|
+
continue;
|
|
2134
|
+
const list = globalCallGraph.get(site.caller) ?? [];
|
|
2135
|
+
list.push({
|
|
2136
|
+
callee: targetFn,
|
|
2137
|
+
props: site.props,
|
|
2138
|
+
spreads: site.spreads
|
|
2139
|
+
});
|
|
2140
|
+
globalCallGraph.set(site.caller, list);
|
|
2141
|
+
}
|
|
2142
|
+
}
|
|
2143
|
+
const globalReactiveProps = propagateReactiveProps2(globalLocalScopes, globalCallGraph, globalComponentFunctions);
|
|
2144
|
+
const reactiveProps = new Map;
|
|
2145
|
+
for (const mod of modules.values()) {
|
|
2146
|
+
const fileMap = new Map;
|
|
2147
|
+
for (const [exportName, fn] of mod.exports) {
|
|
2148
|
+
const props = globalReactiveProps.get(fn);
|
|
2149
|
+
fileMap.set(exportName, props ? new Set(props) : new Set);
|
|
2150
|
+
}
|
|
2151
|
+
if (mod.defaultExport) {
|
|
2152
|
+
const props = globalReactiveProps.get(mod.defaultExport);
|
|
2153
|
+
fileMap.set("default", props ? new Set(props) : new Set);
|
|
2154
|
+
}
|
|
2155
|
+
reactiveProps.set(mod.filePath, fileMap);
|
|
2156
|
+
}
|
|
2157
|
+
return reactiveProps;
|
|
2158
|
+
}
|
|
2159
|
+
|
|
2160
|
+
class AnalyzerCache2 {
|
|
2161
|
+
root;
|
|
2162
|
+
extensions;
|
|
2163
|
+
tsConfigPath;
|
|
2164
|
+
bunfigPath;
|
|
2165
|
+
workspaces;
|
|
2166
|
+
cachePath;
|
|
2167
|
+
resolve;
|
|
2168
|
+
modules = new Map;
|
|
2169
|
+
reactiveProps = new Map;
|
|
2170
|
+
importers = new Map;
|
|
2171
|
+
constructor(options) {
|
|
2172
|
+
this.root = options.root;
|
|
2173
|
+
this.extensions = options.extensions ?? [".tsx", ".ts", ".jsx", ".js"];
|
|
2174
|
+
this.tsConfigPath = options.tsConfigPath;
|
|
2175
|
+
this.bunfigPath = options.bunfigPath;
|
|
2176
|
+
this.workspaces = options.workspaces;
|
|
2177
|
+
this.cachePath = options.cachePath;
|
|
2178
|
+
const bunfigPath = options.bunfigPath;
|
|
2179
|
+
const diskResolver = options.resolve ?? createResolver(this.root, this.extensions, this.tsConfigPath, bunfigPath, this.workspaces);
|
|
2180
|
+
this.resolve = (source, fromFile) => {
|
|
2181
|
+
if (source.startsWith(".")) {
|
|
2182
|
+
const base = path.resolve(path.dirname(fromFile), source);
|
|
2183
|
+
for (const ext of this.extensions) {
|
|
2184
|
+
if (this.modules.has(base + ext))
|
|
2185
|
+
return base + ext;
|
|
2186
|
+
const indexCandidate = path.join(base, "index" + ext);
|
|
2187
|
+
if (this.modules.has(indexCandidate))
|
|
2188
|
+
return indexCandidate;
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
return diskResolver(source, fromFile);
|
|
2192
|
+
};
|
|
2193
|
+
if (this.cachePath) {
|
|
2194
|
+
this.restore();
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
update(filePath, code) {
|
|
2198
|
+
const oldMod = this.modules.get(filePath);
|
|
2199
|
+
const newMod = analyzeModule2(code, filePath);
|
|
2200
|
+
this.modules.set(filePath, newMod);
|
|
2201
|
+
if (oldMod)
|
|
2202
|
+
this.removeImporters(oldMod);
|
|
2203
|
+
this.addImporters(newMod);
|
|
2204
|
+
this.recomputeFor([filePath]);
|
|
2205
|
+
this.save();
|
|
2206
|
+
}
|
|
2207
|
+
remove(filePath) {
|
|
2208
|
+
const oldMod = this.modules.get(filePath);
|
|
2209
|
+
const affected = new Set(this.importers.get(filePath) ?? []);
|
|
2210
|
+
if (oldMod) {
|
|
2211
|
+
for (const site of oldMod.importedCallSites) {
|
|
2212
|
+
const resolved = this.resolve(site.source, oldMod.filePath);
|
|
2213
|
+
if (resolved)
|
|
2214
|
+
affected.add(resolved);
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
this.modules.delete(filePath);
|
|
2218
|
+
this.importers.delete(filePath);
|
|
2219
|
+
if (oldMod)
|
|
2220
|
+
this.removeImporters(oldMod);
|
|
2221
|
+
this.recomputeFor(affected);
|
|
2222
|
+
this.save();
|
|
2223
|
+
}
|
|
2224
|
+
save() {
|
|
2225
|
+
if (!this.cachePath)
|
|
2226
|
+
return;
|
|
2227
|
+
const data = {
|
|
2228
|
+
version: 1,
|
|
2229
|
+
modules: Object.fromEntries(Array.from(this.modules.entries()).map(([filePath, mod]) => [
|
|
2230
|
+
filePath,
|
|
2231
|
+
serializeModule(mod)
|
|
2232
|
+
])),
|
|
2233
|
+
reactiveProps: serializeReactiveProps(this.reactiveProps)
|
|
2234
|
+
};
|
|
2235
|
+
const dir = path.dirname(this.cachePath);
|
|
2236
|
+
if (!fs.existsSync(dir)) {
|
|
2237
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
2238
|
+
}
|
|
2239
|
+
fs.writeFileSync(this.cachePath, JSON.stringify(data, null, 2));
|
|
2240
|
+
}
|
|
2241
|
+
restore() {
|
|
2242
|
+
if (!this.cachePath || !fs.existsSync(this.cachePath))
|
|
2243
|
+
return;
|
|
2244
|
+
let data;
|
|
2245
|
+
try {
|
|
2246
|
+
data = JSON.parse(fs.readFileSync(this.cachePath, "utf-8"));
|
|
2247
|
+
} catch {
|
|
2248
|
+
return;
|
|
2249
|
+
}
|
|
2250
|
+
if (data.version !== 1)
|
|
2251
|
+
return;
|
|
2252
|
+
for (const [filePath, serialized] of Object.entries(data.modules)) {
|
|
2253
|
+
const mod = restoreModule(filePath, serialized);
|
|
2254
|
+
if (mod) {
|
|
2255
|
+
this.modules.set(filePath, mod);
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
this.importers = new Map;
|
|
2259
|
+
for (const mod of this.modules.values()) {
|
|
2260
|
+
this.addImporters(mod);
|
|
2261
|
+
}
|
|
2262
|
+
this.reactiveProps = new Map;
|
|
2263
|
+
for (const [filePath, fileMap] of Object.entries(data.reactiveProps)) {
|
|
2264
|
+
const map = new Map;
|
|
2265
|
+
for (const [exportName, props] of Object.entries(fileMap)) {
|
|
2266
|
+
map.set(exportName, new Set(props));
|
|
2267
|
+
}
|
|
2268
|
+
this.reactiveProps.set(filePath, map);
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
addImporters(mod) {
|
|
2272
|
+
for (const site of mod.importedCallSites) {
|
|
2273
|
+
const resolvedFile = this.resolve(site.source, mod.filePath);
|
|
2274
|
+
if (!resolvedFile)
|
|
2275
|
+
continue;
|
|
2276
|
+
let set = this.importers.get(resolvedFile);
|
|
2277
|
+
if (!set) {
|
|
2278
|
+
set = new Set;
|
|
2279
|
+
this.importers.set(resolvedFile, set);
|
|
2280
|
+
}
|
|
2281
|
+
set.add(mod.filePath);
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
removeImporters(mod) {
|
|
2285
|
+
for (const site of mod.importedCallSites) {
|
|
2286
|
+
const resolvedFile = this.resolve(site.source, mod.filePath);
|
|
2287
|
+
if (!resolvedFile)
|
|
2288
|
+
continue;
|
|
2289
|
+
this.importers.get(resolvedFile)?.delete(mod.filePath);
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
ensureAnalyzed(filePath) {
|
|
2293
|
+
if (this.modules.has(filePath))
|
|
2294
|
+
return this.modules.get(filePath);
|
|
2295
|
+
try {
|
|
2296
|
+
const code = fs.readFileSync(filePath, "utf-8");
|
|
2297
|
+
const mod = analyzeModule2(code, filePath);
|
|
2298
|
+
this.modules.set(filePath, mod);
|
|
2299
|
+
return mod;
|
|
2300
|
+
} catch {
|
|
2301
|
+
return;
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
collectAffected(filePaths) {
|
|
2305
|
+
const affected = new Set;
|
|
2306
|
+
const queue = Array.from(filePaths);
|
|
2307
|
+
while (queue.length > 0) {
|
|
2308
|
+
const current = queue.pop();
|
|
2309
|
+
if (affected.has(current))
|
|
2310
|
+
continue;
|
|
2311
|
+
let mod = this.modules.get(current);
|
|
2312
|
+
if (!mod) {
|
|
2313
|
+
mod = this.ensureAnalyzed(current);
|
|
2314
|
+
if (mod)
|
|
2315
|
+
this.addImporters(mod);
|
|
2316
|
+
}
|
|
2317
|
+
if (!mod)
|
|
2318
|
+
continue;
|
|
2319
|
+
affected.add(current);
|
|
2320
|
+
for (const importer of this.importers.get(current) ?? []) {
|
|
2321
|
+
if (!affected.has(importer))
|
|
2322
|
+
queue.push(importer);
|
|
2323
|
+
}
|
|
2324
|
+
for (const site of mod.importedCallSites) {
|
|
2325
|
+
const resolved = this.resolve(site.source, mod.filePath);
|
|
2326
|
+
if (resolved && !affected.has(resolved)) {
|
|
2327
|
+
queue.push(resolved);
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
return affected;
|
|
2332
|
+
}
|
|
2333
|
+
recomputeFor(filePaths) {
|
|
2334
|
+
const affected = this.collectAffected(filePaths);
|
|
2335
|
+
const subgraphModules = new Map;
|
|
2336
|
+
for (const filePath of affected) {
|
|
2337
|
+
const mod = this.modules.get(filePath);
|
|
2338
|
+
if (mod)
|
|
2339
|
+
subgraphModules.set(filePath, mod);
|
|
2340
|
+
}
|
|
2341
|
+
const globalLocalScopes = new Map;
|
|
2342
|
+
const globalCallGraph = new Map;
|
|
2343
|
+
const globalComponentFunctions = new Set;
|
|
2344
|
+
for (const mod of subgraphModules.values()) {
|
|
2345
|
+
for (const [fn, scope] of mod.localScopes) {
|
|
2346
|
+
globalLocalScopes.set(fn, scope);
|
|
2347
|
+
}
|
|
2348
|
+
for (const fn of mod.componentFunctions) {
|
|
2349
|
+
globalComponentFunctions.add(fn);
|
|
2350
|
+
}
|
|
2351
|
+
for (const [callerFn, sites] of mod.localCallGraph) {
|
|
2352
|
+
const list = globalCallGraph.get(callerFn) ?? [];
|
|
2353
|
+
list.push(...sites);
|
|
2354
|
+
globalCallGraph.set(callerFn, list);
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
const subgraphReactiveProps = buildProjectReactiveProps(subgraphModules, this.resolve, globalLocalScopes, globalCallGraph, globalComponentFunctions);
|
|
2358
|
+
for (const filePath of affected) {
|
|
2359
|
+
const map = subgraphReactiveProps.get(filePath);
|
|
2360
|
+
if (map) {
|
|
2361
|
+
this.reactiveProps.set(filePath, map);
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
function analyze2(options) {
|
|
2367
|
+
const tsConfigPath = options.tsConfigPath ?? path.join(options.root, "tsconfig.json");
|
|
2368
|
+
const bunfigPath = options.bunfigPath ?? path.join(options.root, "bunfig.toml");
|
|
2369
|
+
const project = analyzeProject2({
|
|
2370
|
+
root: options.root,
|
|
2371
|
+
extensions: options.extensions,
|
|
2372
|
+
resolve: options.resolve,
|
|
2373
|
+
tsConfigPath: fs.existsSync(tsConfigPath) ? tsConfigPath : undefined,
|
|
2374
|
+
bunfigPath: fs.existsSync(bunfigPath) ? bunfigPath : undefined,
|
|
2375
|
+
workspaces: options.workspaces
|
|
2376
|
+
});
|
|
2377
|
+
const serialized = {};
|
|
2378
|
+
for (const [filePath, fileMap] of project.reactiveProps) {
|
|
2379
|
+
const entry = {};
|
|
2380
|
+
for (const [exportName, props] of fileMap) {
|
|
2381
|
+
entry[exportName] = Array.from(props);
|
|
2382
|
+
}
|
|
2383
|
+
serialized[filePath] = entry;
|
|
2384
|
+
}
|
|
2385
|
+
const dir = path.dirname(options.outFile);
|
|
2386
|
+
if (!fs.existsSync(dir)) {
|
|
2387
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
2388
|
+
}
|
|
2389
|
+
fs.writeFileSync(options.outFile, JSON.stringify(serialized, null, 2));
|
|
2390
|
+
}
|
|
2391
|
+
function loadMetadata2(filePath) {
|
|
2392
|
+
if (!fs.existsSync(filePath)) {
|
|
2393
|
+
return new Map;
|
|
2394
|
+
}
|
|
2395
|
+
const raw = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
2396
|
+
const result = new Map;
|
|
2397
|
+
for (const [filePath, fileMap] of Object.entries(raw)) {
|
|
2398
|
+
const map = new Map;
|
|
2399
|
+
for (const [exportName, props] of Object.entries(fileMap)) {
|
|
2400
|
+
map.set(exportName, new Set(props));
|
|
2401
|
+
}
|
|
2402
|
+
result.set(filePath, map);
|
|
2403
|
+
}
|
|
2404
|
+
return result;
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2407
|
+
// src/reactive-wrap.ts
|
|
2408
|
+
var REACTIVE_SOURCE_MODULES = {
|
|
2409
|
+
sinway: new Set(["createMutable", "createStore"]),
|
|
2410
|
+
"sinwan/reactivity": new Set(["signal", "computed"]),
|
|
2411
|
+
"sinwan/react": new Set(["useState"]),
|
|
2412
|
+
"sinwan/hook": new Set(["useFetch"])
|
|
2413
|
+
};
|
|
2414
|
+
function addImportLocal(names, imported, local) {
|
|
2415
|
+
switch (imported) {
|
|
2416
|
+
case "createMutable":
|
|
2417
|
+
case "createStore":
|
|
2418
|
+
case "signal":
|
|
2419
|
+
case "computed":
|
|
2420
|
+
case "useState":
|
|
2421
|
+
case "useFetch":
|
|
2422
|
+
case "cc":
|
|
2423
|
+
names[imported].add(local);
|
|
2424
|
+
break;
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
var COMPONENT_FACTORY_MODULES = {
|
|
2428
|
+
sinwan: new Set(["cc"]),
|
|
2429
|
+
"sinwan/component": new Set(["cc"])
|
|
2430
|
+
};
|
|
2431
|
+
var BUILTIN_REACTIVE_PROPS = new Map([
|
|
2432
|
+
["For", { reactiveProps: new Set(["each"]) }],
|
|
2433
|
+
["Show", { reactiveProps: new Set(["when"]) }],
|
|
2434
|
+
["Switch", { reactiveProps: new Set(["when"]) }],
|
|
2435
|
+
["Match", { reactiveProps: new Set(["when"]) }],
|
|
2436
|
+
["Index", { reactiveProps: new Set(["each"]) }],
|
|
2437
|
+
["Key", { reactiveProps: new Set(["when"]) }],
|
|
2438
|
+
["Dynamic", { reactiveProps: new Set(["component"]) }],
|
|
2439
|
+
["Visible", { reactiveProps: new Set(["when", "style"]) }],
|
|
2440
|
+
["Portal", { reactiveProps: new Set(["mount"]) }],
|
|
2441
|
+
["Virtual", { reactiveProps: new Set(["each"]) }],
|
|
2442
|
+
["Activity", { reactiveProps: new Set(["mode"]) }]
|
|
2443
|
+
]);
|
|
2444
|
+
function isBuiltinReactiveProp(componentName, attrName) {
|
|
2445
|
+
const entry = BUILTIN_REACTIVE_PROPS.get(componentName);
|
|
2446
|
+
if (!entry)
|
|
2447
|
+
return false;
|
|
2448
|
+
return entry.reactiveProps.has(attrName);
|
|
2449
|
+
}
|
|
2450
|
+
function isReactiveContainerPassThrough(expr, scope) {
|
|
2451
|
+
let node = unwrapParens(expr);
|
|
2452
|
+
while (isTSNonNullExpression(node)) {
|
|
2453
|
+
node = node.expression;
|
|
2454
|
+
}
|
|
2455
|
+
if (!isIdentifier(node))
|
|
2456
|
+
return false;
|
|
2457
|
+
const binding = scope.bindings.get(node.name);
|
|
2458
|
+
if (!binding)
|
|
2459
|
+
return false;
|
|
2460
|
+
return binding.kind === "mutable" || binding.kind === "prop";
|
|
2461
|
+
}
|
|
2462
|
+
function trackReactiveImports2(ast) {
|
|
2463
|
+
const names = {
|
|
2464
|
+
createMutable: new Set,
|
|
2465
|
+
createStore: new Set,
|
|
2466
|
+
signal: new Set,
|
|
2467
|
+
computed: new Set,
|
|
2468
|
+
useState: new Set,
|
|
2469
|
+
useFetch: new Set,
|
|
2470
|
+
cc: new Set
|
|
2471
|
+
};
|
|
2472
|
+
traverse(ast, {
|
|
2473
|
+
ImportDeclaration(path) {
|
|
2474
|
+
const source = moduleSourceValue(path.node.source);
|
|
2475
|
+
if (!source)
|
|
2476
|
+
return;
|
|
2477
|
+
const reactiveAllowed = REACTIVE_SOURCE_MODULES[source];
|
|
2478
|
+
const componentAllowed = COMPONENT_FACTORY_MODULES[source];
|
|
2479
|
+
if (!reactiveAllowed && !componentAllowed)
|
|
2480
|
+
return;
|
|
2481
|
+
for (const spec of path.node.specifiers ?? []) {
|
|
2482
|
+
if (!isImportSpecifier(spec))
|
|
2483
|
+
continue;
|
|
2484
|
+
const imported = importedBindingName(spec.imported);
|
|
2485
|
+
if (imported && reactiveAllowed?.has(imported)) {
|
|
2486
|
+
addImportLocal(names, imported, spec.local.name);
|
|
2487
|
+
} else if (imported && componentAllowed?.has(imported)) {
|
|
2488
|
+
names.cc.add(spec.local.name);
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
});
|
|
2493
|
+
return names;
|
|
2494
|
+
}
|
|
2495
|
+
var pureImportCache = new Map;
|
|
2496
|
+
var PURE_EXTENSIONS = [
|
|
2497
|
+
".ts",
|
|
2498
|
+
".tsx",
|
|
2499
|
+
".js",
|
|
2500
|
+
".jsx",
|
|
2501
|
+
".mjs",
|
|
2502
|
+
".cjs",
|
|
2503
|
+
".mts",
|
|
2504
|
+
".cts"
|
|
2505
|
+
];
|
|
2506
|
+
function tryResolveFile(base) {
|
|
2507
|
+
if (PURE_EXTENSIONS.some((ext) => base.endsWith(ext))) {
|
|
2508
|
+
try {
|
|
2509
|
+
if (fs2.existsSync(base))
|
|
2510
|
+
return base;
|
|
2511
|
+
} catch {}
|
|
2512
|
+
return null;
|
|
2513
|
+
}
|
|
2514
|
+
for (const ext of PURE_EXTENSIONS) {
|
|
2515
|
+
try {
|
|
2516
|
+
if (fs2.existsSync(base + ext))
|
|
2517
|
+
return base + ext;
|
|
2518
|
+
} catch {}
|
|
2519
|
+
}
|
|
2520
|
+
return null;
|
|
2521
|
+
}
|
|
2522
|
+
function tryResolveIndex(dir) {
|
|
2523
|
+
for (const ext of PURE_EXTENSIONS) {
|
|
2524
|
+
try {
|
|
2525
|
+
const candidate = path2.join(dir, "index") + ext;
|
|
2526
|
+
if (fs2.existsSync(candidate))
|
|
2527
|
+
return candidate;
|
|
2528
|
+
} catch {}
|
|
2529
|
+
}
|
|
2530
|
+
return null;
|
|
2531
|
+
}
|
|
2532
|
+
function tryResolveRelative(source, fromFile) {
|
|
2533
|
+
const base = path2.resolve(path2.dirname(fromFile), source);
|
|
2534
|
+
return tryResolveFile(base) ?? tryResolveIndex(base);
|
|
2535
|
+
}
|
|
2536
|
+
function tryResolveBare(source, fromFile) {
|
|
2537
|
+
const parts = source.split("/");
|
|
2538
|
+
const pkgName = source.startsWith("@") ? `${parts[0]}/${parts[1]}` : parts[0];
|
|
2539
|
+
const subpath = parts.slice(source.startsWith("@") ? 2 : 1).join("/");
|
|
2540
|
+
let dir = path2.dirname(fromFile);
|
|
2541
|
+
for (let i = 0;i < 30; i++) {
|
|
2542
|
+
const nodeModules = path2.join(dir, "node_modules", pkgName);
|
|
2543
|
+
if (fs2.existsSync(nodeModules)) {
|
|
2544
|
+
return resolvePackageEntry(nodeModules, subpath);
|
|
2545
|
+
}
|
|
2546
|
+
const parent = path2.dirname(dir);
|
|
2547
|
+
if (parent === dir)
|
|
2548
|
+
break;
|
|
2549
|
+
dir = parent;
|
|
2550
|
+
}
|
|
2551
|
+
return null;
|
|
2552
|
+
}
|
|
2553
|
+
function resolvePackageEntry(pkgDir, subpath) {
|
|
2554
|
+
let pkgJson;
|
|
2555
|
+
try {
|
|
2556
|
+
pkgJson = JSON.parse(fs2.readFileSync(path2.join(pkgDir, "package.json"), "utf-8"));
|
|
2557
|
+
} catch {
|
|
2558
|
+
return null;
|
|
2559
|
+
}
|
|
2560
|
+
if (typeof pkgJson.exports === "object" && pkgJson.exports) {
|
|
2561
|
+
const exports = pkgJson.exports;
|
|
2562
|
+
const exportKey = subpath ? `./${subpath}` : ".";
|
|
2563
|
+
const entry = exports[exportKey];
|
|
2564
|
+
if (typeof entry === "string") {
|
|
2565
|
+
const resolved = tryResolveFile(path2.resolve(pkgDir, entry));
|
|
2566
|
+
if (resolved)
|
|
2567
|
+
return resolved;
|
|
2568
|
+
} else if (entry && typeof entry === "object") {
|
|
2569
|
+
for (const condValue of Object.values(entry)) {
|
|
2570
|
+
if (typeof condValue === "string") {
|
|
2571
|
+
const resolved = tryResolveFile(path2.resolve(pkgDir, condValue));
|
|
2572
|
+
if (resolved)
|
|
2573
|
+
return resolved;
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
if (!subpath) {
|
|
2579
|
+
for (const field of ["source", "module", "main", "types"]) {
|
|
2580
|
+
const entry = pkgJson[field];
|
|
2581
|
+
if (typeof entry === "string") {
|
|
2582
|
+
const resolved = tryResolveFile(path2.resolve(pkgDir, entry));
|
|
2583
|
+
if (resolved)
|
|
2584
|
+
return resolved;
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
const indexResolved = tryResolveIndex(pkgDir);
|
|
2588
|
+
if (indexResolved)
|
|
2589
|
+
return indexResolved;
|
|
2590
|
+
}
|
|
2591
|
+
if (subpath) {
|
|
2592
|
+
for (const srcDir of ["src", "dist", "."]) {
|
|
2593
|
+
const base = path2.join(pkgDir, srcDir, subpath);
|
|
2594
|
+
const resolved = tryResolveFile(base) ?? tryResolveIndex(base);
|
|
2595
|
+
if (resolved)
|
|
2596
|
+
return resolved;
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2599
|
+
return null;
|
|
2600
|
+
}
|
|
2601
|
+
function resolveAnyImport(source, fromFile, resolveImport) {
|
|
2602
|
+
if (resolveImport) {
|
|
2603
|
+
try {
|
|
2604
|
+
const resolved = resolveImport(source, fromFile);
|
|
2605
|
+
if (resolved)
|
|
2606
|
+
return resolved;
|
|
2607
|
+
} catch {}
|
|
2608
|
+
}
|
|
2609
|
+
if (source.startsWith(".")) {
|
|
2610
|
+
return tryResolveRelative(source, fromFile);
|
|
2611
|
+
}
|
|
2612
|
+
return tryResolveBare(source, fromFile);
|
|
2613
|
+
}
|
|
2614
|
+
function scanPureExports(filePath, resolveImport, depth = 0) {
|
|
2615
|
+
const pure = new Map;
|
|
2616
|
+
if (depth > 10)
|
|
2617
|
+
return pure;
|
|
2618
|
+
let code;
|
|
2619
|
+
try {
|
|
2620
|
+
code = fs2.readFileSync(filePath, "utf-8");
|
|
2621
|
+
} catch {
|
|
2622
|
+
return pure;
|
|
2623
|
+
}
|
|
2624
|
+
let ast;
|
|
2625
|
+
try {
|
|
2626
|
+
ast = parse(code, {
|
|
2627
|
+
sourceType: "module",
|
|
2628
|
+
plugins: ["jsx", "typescript"],
|
|
2629
|
+
sourceFilename: filePath
|
|
2630
|
+
});
|
|
2631
|
+
} catch {
|
|
2632
|
+
return pure;
|
|
2633
|
+
}
|
|
2634
|
+
const hasPureJSDoc = (node) => node?.leadingComments?.some((c) => c.type === "CommentBlock" && c.value.includes("@sinwan-pure")) ?? false;
|
|
2635
|
+
traverse(ast, {
|
|
2636
|
+
ExportNamedDeclaration(p) {
|
|
2637
|
+
if (hasPureJSDoc(p.node)) {
|
|
2638
|
+
const decl = p.node.declaration;
|
|
2639
|
+
if (isFunctionDeclaration(decl) && isIdentifier(decl.id)) {
|
|
2640
|
+
pure.set(decl.id.name, "function");
|
|
2641
|
+
} else if (isClassDeclaration(decl) && isIdentifier(decl.id)) {
|
|
2642
|
+
pure.set(decl.id.name, "class");
|
|
2643
|
+
} else if (isVariableDeclaration(decl)) {
|
|
2644
|
+
for (const d of decl.declarations) {
|
|
2645
|
+
if (!isIdentifier(d.id))
|
|
2646
|
+
continue;
|
|
2647
|
+
const init = d.init;
|
|
2648
|
+
if (isObjectExpression(init)) {
|
|
2649
|
+
pure.set(d.id.name, "object");
|
|
2650
|
+
} else {
|
|
2651
|
+
pure.set(d.id.name, "function");
|
|
2652
|
+
}
|
|
2653
|
+
}
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2656
|
+
const source = moduleSourceValue(p.node.source);
|
|
2657
|
+
if (source && p.node.specifiers) {
|
|
2658
|
+
const resolved = resolveAnyImport(source, filePath, resolveImport);
|
|
2659
|
+
if (resolved) {
|
|
2660
|
+
let reExportPure = pureImportCache.get(resolved);
|
|
2661
|
+
if (!reExportPure) {
|
|
2662
|
+
reExportPure = scanPureExports(resolved, resolveImport, depth + 1);
|
|
2663
|
+
pureImportCache.set(resolved, reExportPure);
|
|
2664
|
+
}
|
|
2665
|
+
for (const spec of p.node.specifiers ?? []) {
|
|
2666
|
+
if (!isExportSpecifier(spec))
|
|
2667
|
+
continue;
|
|
2668
|
+
const exported = isIdentifier(spec.exported) ? spec.exported.name : spec.exported.value;
|
|
2669
|
+
const original = spec.local.name;
|
|
2670
|
+
const kind = reExportPure.get(original);
|
|
2671
|
+
if (kind) {
|
|
2672
|
+
pure.set(exported, kind);
|
|
2673
|
+
}
|
|
2674
|
+
}
|
|
2675
|
+
}
|
|
2676
|
+
}
|
|
2677
|
+
},
|
|
2678
|
+
ExportDefaultDeclaration(p) {
|
|
2679
|
+
if (!hasPureJSDoc(p.node))
|
|
2680
|
+
return;
|
|
2681
|
+
const decl = p.node.declaration;
|
|
2682
|
+
if (isFunctionDeclaration(decl) || isArrowFunctionExpression(decl) || isFunctionExpression(decl)) {
|
|
2683
|
+
pure.set("default", "function");
|
|
2684
|
+
} else if (isClassDeclaration(decl)) {
|
|
2685
|
+
pure.set("default", "class");
|
|
2686
|
+
}
|
|
2687
|
+
},
|
|
2688
|
+
ExportAllDeclaration(p) {
|
|
2689
|
+
const source = moduleSourceValue(p.node.source);
|
|
2690
|
+
if (!source)
|
|
2691
|
+
return;
|
|
2692
|
+
const resolved = resolveAnyImport(source, filePath, resolveImport);
|
|
2693
|
+
if (!resolved)
|
|
2694
|
+
return;
|
|
2695
|
+
let reExportPure = pureImportCache.get(resolved);
|
|
2696
|
+
if (!reExportPure) {
|
|
2697
|
+
reExportPure = scanPureExports(resolved, resolveImport, depth + 1);
|
|
2698
|
+
pureImportCache.set(resolved, reExportPure);
|
|
2699
|
+
}
|
|
2700
|
+
for (const [name, kind] of reExportPure) {
|
|
2701
|
+
if (name !== "default") {
|
|
2702
|
+
pure.set(name, kind);
|
|
2703
|
+
}
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
});
|
|
2707
|
+
return pure;
|
|
2708
|
+
}
|
|
2709
|
+
function collectPureImports(ast, filename, resolveImport) {
|
|
2710
|
+
const result = {
|
|
2711
|
+
direct: new Set,
|
|
2712
|
+
namespace: new Map,
|
|
2713
|
+
member: new Set
|
|
2714
|
+
};
|
|
2715
|
+
if (!filename)
|
|
2716
|
+
return result;
|
|
2717
|
+
const absoluteFilename = path2.resolve(filename);
|
|
2718
|
+
traverse(ast, {
|
|
2719
|
+
ImportDeclaration(p) {
|
|
2720
|
+
const source = moduleSourceValue(p.node.source);
|
|
2721
|
+
if (!source)
|
|
2722
|
+
return;
|
|
2723
|
+
if (p.node.importKind === "type")
|
|
2724
|
+
return;
|
|
2725
|
+
const resolved = resolveAnyImport(source, absoluteFilename, resolveImport);
|
|
2726
|
+
if (!resolved)
|
|
2727
|
+
return;
|
|
2728
|
+
let filePure = pureImportCache.get(resolved);
|
|
2729
|
+
if (!filePure) {
|
|
2730
|
+
filePure = scanPureExports(resolved, resolveImport);
|
|
2731
|
+
pureImportCache.set(resolved, filePure);
|
|
2732
|
+
}
|
|
2733
|
+
for (const spec of p.node.specifiers ?? []) {
|
|
2734
|
+
if (isImportSpecifier(spec)) {
|
|
2735
|
+
const imported = importedBindingName(spec.imported);
|
|
2736
|
+
if (!imported)
|
|
2737
|
+
continue;
|
|
2738
|
+
const kind = filePure.get(imported);
|
|
2739
|
+
if (!kind)
|
|
2740
|
+
continue;
|
|
2741
|
+
if (kind === "function") {
|
|
2742
|
+
result.direct.add(spec.local.name);
|
|
2743
|
+
} else {
|
|
2744
|
+
result.member.add(spec.local.name);
|
|
2745
|
+
}
|
|
2746
|
+
} else if (isImportDefaultSpecifier(spec)) {
|
|
2747
|
+
const kind = filePure.get("default");
|
|
2748
|
+
if (!kind)
|
|
2749
|
+
continue;
|
|
2750
|
+
if (kind === "function") {
|
|
2751
|
+
result.direct.add(spec.local.name);
|
|
2752
|
+
} else {
|
|
2753
|
+
result.member.add(spec.local.name);
|
|
2754
|
+
}
|
|
2755
|
+
} else if (isImportNamespaceSpecifier(spec)) {
|
|
2756
|
+
const pureMembers = new Set;
|
|
2757
|
+
for (const [name, kind] of filePure) {
|
|
2758
|
+
if (name === "default")
|
|
2759
|
+
continue;
|
|
2760
|
+
if (kind === "function") {
|
|
2761
|
+
pureMembers.add(name);
|
|
2762
|
+
} else {
|
|
2763
|
+
pureMembers.add(name);
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
if (pureMembers.size > 0) {
|
|
2767
|
+
result.namespace.set(spec.local.name, pureMembers);
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2771
|
+
}
|
|
2772
|
+
});
|
|
2773
|
+
return result;
|
|
2774
|
+
}
|
|
2775
|
+
function isReactiveSourceCall(expr, names) {
|
|
2776
|
+
if (!isCallExpression(expr))
|
|
2777
|
+
return null;
|
|
2778
|
+
const callee = expr.callee;
|
|
2779
|
+
if (!isIdentifier(callee))
|
|
2780
|
+
return null;
|
|
2781
|
+
if (names.createMutable.has(callee.name)) {
|
|
2782
|
+
return { kind: "mutable", isArray: false };
|
|
2783
|
+
}
|
|
2784
|
+
if (names.createStore.has(callee.name)) {
|
|
2785
|
+
return { kind: "mutable", isArray: true };
|
|
2786
|
+
}
|
|
2787
|
+
if (names.signal.has(callee.name)) {
|
|
2788
|
+
return { kind: "signal", isArray: false };
|
|
2789
|
+
}
|
|
2790
|
+
if (names.computed.has(callee.name)) {
|
|
2791
|
+
return { kind: "computed", isArray: false };
|
|
2792
|
+
}
|
|
2793
|
+
if (names.useState.has(callee.name)) {
|
|
2794
|
+
return { kind: "getter", isArray: true };
|
|
2795
|
+
}
|
|
2796
|
+
return null;
|
|
2797
|
+
}
|
|
2798
|
+
var USEFETCH_CHAIN_METHODS = new Set([
|
|
2799
|
+
"json",
|
|
2800
|
+
"text",
|
|
2801
|
+
"blob",
|
|
2802
|
+
"arrayBuffer",
|
|
2803
|
+
"formData",
|
|
2804
|
+
"get",
|
|
2805
|
+
"put",
|
|
2806
|
+
"post",
|
|
2807
|
+
"delete",
|
|
2808
|
+
"patch",
|
|
2809
|
+
"head",
|
|
2810
|
+
"options"
|
|
2811
|
+
]);
|
|
2812
|
+
function isUseFetchCall(expr, names) {
|
|
2813
|
+
if (!isCallExpression(expr))
|
|
2814
|
+
return false;
|
|
2815
|
+
const callee = expr.callee;
|
|
2816
|
+
if (isIdentifier(callee)) {
|
|
2817
|
+
return names.useFetch.has(callee.name);
|
|
2818
|
+
}
|
|
2819
|
+
if (isMemberExpression(callee) && !callee.computed && isIdentifier(callee.property) && USEFETCH_CHAIN_METHODS.has(callee.property.name)) {
|
|
2820
|
+
return isUseFetchCall(callee.object, names);
|
|
2821
|
+
}
|
|
2822
|
+
return false;
|
|
2823
|
+
}
|
|
2824
|
+
function trackLocalScopeBindings(path, names, scope, shallow = false) {
|
|
2825
|
+
const collect = (visitors) => {
|
|
2826
|
+
path.traverse(shallow ? {
|
|
2827
|
+
...visitors,
|
|
2828
|
+
Function(p) {
|
|
2829
|
+
p.skip();
|
|
2830
|
+
}
|
|
2831
|
+
} : visitors);
|
|
2832
|
+
};
|
|
2833
|
+
collect({
|
|
2834
|
+
VariableDeclarator(p) {
|
|
2835
|
+
const id = p.node.id;
|
|
2836
|
+
const init = p.node.init;
|
|
2837
|
+
if (!init)
|
|
2838
|
+
return;
|
|
2839
|
+
if (isIdentifier(id) && isCallExpression(init) && isIdentifier(init.callee) && (init.callee.name === "_$createLiveRest" || init.callee.name === "createLiveRest")) {
|
|
2840
|
+
scope.bindings.set(id.name, { kind: "prop" });
|
|
2841
|
+
return;
|
|
2842
|
+
}
|
|
2843
|
+
const source = isReactiveSourceCall(init, names);
|
|
2844
|
+
if (source) {
|
|
2845
|
+
if (source.isArray) {
|
|
2846
|
+
if (isArrayPattern(id) && id.elements[0]) {
|
|
2847
|
+
const first = id.elements[0];
|
|
2848
|
+
if (isIdentifier(first)) {
|
|
2849
|
+
scope.bindings.set(first.name, { kind: source.kind });
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
return;
|
|
2853
|
+
}
|
|
2854
|
+
if (isIdentifier(id)) {
|
|
2855
|
+
if (source.kind === "mutable") {
|
|
2856
|
+
scope.bindings.set(id.name, {
|
|
2857
|
+
kind: "mutable",
|
|
2858
|
+
root: id.name,
|
|
2859
|
+
path: []
|
|
2860
|
+
});
|
|
2861
|
+
} else {
|
|
2862
|
+
scope.bindings.set(id.name, { kind: source.kind });
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
return;
|
|
2866
|
+
}
|
|
2867
|
+
if (isUseFetchCall(init, names)) {
|
|
2868
|
+
if (isObjectPattern(id)) {
|
|
2869
|
+
for (const prop of id.properties) {
|
|
2870
|
+
if (isObjectProperty(prop) && !prop.computed && isIdentifier(prop.value)) {
|
|
2871
|
+
scope.bindings.set(prop.value.name, {
|
|
2872
|
+
kind: "signal"
|
|
2873
|
+
});
|
|
2874
|
+
}
|
|
2875
|
+
}
|
|
2876
|
+
} else if (isIdentifier(id)) {
|
|
2877
|
+
scope.bindings.set(id.name, { kind: "signalObject" });
|
|
2878
|
+
}
|
|
2879
|
+
return;
|
|
2880
|
+
}
|
|
2881
|
+
}
|
|
2882
|
+
});
|
|
2883
|
+
collect({
|
|
2884
|
+
VariableDeclarator(p) {
|
|
2885
|
+
const id = p.node.id;
|
|
2886
|
+
const init = p.node.init;
|
|
2887
|
+
if (!init || !isIdentifier(init))
|
|
2888
|
+
return;
|
|
2889
|
+
const initBinding = scope.bindings.get(init.name);
|
|
2890
|
+
if (!initBinding || initBinding.kind !== "mutable")
|
|
2891
|
+
return;
|
|
2892
|
+
if (isObjectPattern(id)) {
|
|
2893
|
+
for (const prop of id.properties) {
|
|
2894
|
+
if (!isObjectProperty(prop))
|
|
2895
|
+
continue;
|
|
2896
|
+
const key = prop.key;
|
|
2897
|
+
const value = prop.value;
|
|
2898
|
+
if (!isIdentifier(key) || !isIdentifier(value))
|
|
2899
|
+
continue;
|
|
2900
|
+
scope.bindings.set(value.name, {
|
|
2901
|
+
kind: "mutable",
|
|
2902
|
+
root: initBinding.root,
|
|
2903
|
+
path: [...initBinding.path, key.name]
|
|
2904
|
+
});
|
|
2905
|
+
}
|
|
2906
|
+
}
|
|
2907
|
+
}
|
|
2908
|
+
});
|
|
2909
|
+
collect({
|
|
2910
|
+
"FunctionDeclaration|VariableDeclarator"(p) {
|
|
2911
|
+
const node = p.node;
|
|
2912
|
+
let fn = null;
|
|
2913
|
+
let name = null;
|
|
2914
|
+
if (isFunctionDeclaration(node)) {
|
|
2915
|
+
fn = node;
|
|
2916
|
+
name = isIdentifier(node.id) ? node.id.name : null;
|
|
2917
|
+
} else if (isVariableDeclarator(node)) {
|
|
2918
|
+
const init = node.init;
|
|
2919
|
+
if (init && (isArrowFunctionExpression(init) || isFunctionExpression(init))) {
|
|
2920
|
+
fn = init;
|
|
2921
|
+
}
|
|
2922
|
+
if (isIdentifier(node.id)) {
|
|
2923
|
+
name = node.id.name;
|
|
2924
|
+
}
|
|
2925
|
+
}
|
|
2926
|
+
if (!fn || !name || scope.bindings.has(name))
|
|
2927
|
+
return;
|
|
2928
|
+
const body = fn.body;
|
|
2929
|
+
if (body && containsReactiveRead(body, scope)) {
|
|
2930
|
+
scope.bindings.set(name, { kind: "getter" });
|
|
2931
|
+
} else {
|
|
2932
|
+
scope.bindings.set(name, { kind: "function" });
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2935
|
+
});
|
|
2936
|
+
}
|
|
2937
|
+
function trackPropBindings(scope, params, reactiveProps) {
|
|
2938
|
+
if (!params || reactiveProps.size === 0)
|
|
2939
|
+
return;
|
|
2940
|
+
if (isIdentifier(params)) {
|
|
2941
|
+
scope.bindings.set(params.name, { kind: "prop" });
|
|
2942
|
+
} else if (isObjectPattern(params)) {
|
|
2943
|
+
for (const prop of params.properties) {
|
|
2944
|
+
if (isObjectProperty(prop) && isIdentifier(prop.value)) {
|
|
2945
|
+
const key = prop.key;
|
|
2946
|
+
if (isIdentifier(key) && reactiveProps.has(key.name)) {
|
|
2947
|
+
scope.bindings.set(prop.value.name, { kind: "prop" });
|
|
2948
|
+
}
|
|
2949
|
+
}
|
|
2950
|
+
}
|
|
2951
|
+
}
|
|
2952
|
+
}
|
|
2953
|
+
function getAllPropNames2(params) {
|
|
2954
|
+
if (!params)
|
|
2955
|
+
return new Set;
|
|
2956
|
+
if (isIdentifier(params))
|
|
2957
|
+
return new Set(["*"]);
|
|
2958
|
+
if (isObjectPattern(params)) {
|
|
2959
|
+
const names = new Set;
|
|
2960
|
+
for (const prop of params.properties) {
|
|
2961
|
+
if (isObjectProperty(prop) && isIdentifier(prop.key)) {
|
|
2962
|
+
names.add(prop.key.name);
|
|
2963
|
+
}
|
|
2964
|
+
}
|
|
2965
|
+
names.add("children");
|
|
2966
|
+
return names;
|
|
2967
|
+
}
|
|
2968
|
+
return new Set;
|
|
2969
|
+
}
|
|
2970
|
+
function resolveLocalObjectLiteral(fn, name) {
|
|
2971
|
+
const body = fn.body;
|
|
2972
|
+
if (!isBlockStatement(body))
|
|
2973
|
+
return null;
|
|
2974
|
+
for (const stmt of body.body) {
|
|
2975
|
+
if (!isVariableDeclaration(stmt))
|
|
2976
|
+
continue;
|
|
2977
|
+
for (const decl of stmt.declarations) {
|
|
2978
|
+
if (!isIdentifier(decl.id) || decl.id.name !== name)
|
|
2979
|
+
continue;
|
|
2980
|
+
const init = decl.init;
|
|
2981
|
+
if (!isObjectExpression(init))
|
|
2982
|
+
continue;
|
|
2983
|
+
return init;
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
2986
|
+
return null;
|
|
2987
|
+
}
|
|
2988
|
+
function analyzeObjectLiteralSpread(spread, fullScope, calleeReactive) {
|
|
2989
|
+
for (const prop of spread.properties) {
|
|
2990
|
+
if (isObjectProperty(prop) && !prop.computed) {
|
|
2991
|
+
const key = prop.key;
|
|
2992
|
+
const name = isIdentifier(key) ? key.name : isStringLiteral(key) ? key.value : null;
|
|
2993
|
+
if (name && containsReactiveValue2(prop.value, fullScope)) {
|
|
2994
|
+
calleeReactive.add(name);
|
|
2995
|
+
}
|
|
2996
|
+
}
|
|
2997
|
+
}
|
|
2998
|
+
}
|
|
2999
|
+
function isMemberLike(node) {
|
|
3000
|
+
if (isChainExpression(node))
|
|
3001
|
+
return isMemberLike(unwrapChain(node));
|
|
3002
|
+
return isMemberExpression(node);
|
|
3003
|
+
}
|
|
3004
|
+
function isFunctionLike(node) {
|
|
3005
|
+
return isArrowFunctionExpression(node) || isFunctionExpression(node);
|
|
3006
|
+
}
|
|
3007
|
+
function isCallLike(node) {
|
|
3008
|
+
const inner = unwrapCall(node);
|
|
3009
|
+
return isCallExpression(inner);
|
|
3010
|
+
}
|
|
3011
|
+
var COMPILER_BINDING_CALLEES = new Set([
|
|
3012
|
+
"_$bindText",
|
|
3013
|
+
"_$bindAttr",
|
|
3014
|
+
"_$bindStyle",
|
|
3015
|
+
"_$bindClass",
|
|
3016
|
+
"_$unwrap"
|
|
3017
|
+
]);
|
|
3018
|
+
function isCompilerBindingCall(node) {
|
|
3019
|
+
const call = unwrapCall(node);
|
|
3020
|
+
if (!isCallExpression(call))
|
|
3021
|
+
return false;
|
|
3022
|
+
const callee = unwrapParens(call.callee);
|
|
3023
|
+
return isIdentifier(callee) && COMPILER_BINDING_CALLEES.has(callee.name);
|
|
3024
|
+
}
|
|
3025
|
+
function visitCallCalleeAndArgs(node, visitExpr) {
|
|
3026
|
+
const call = unwrapCall(node);
|
|
3027
|
+
if (!isCallExpression(call))
|
|
3028
|
+
return;
|
|
3029
|
+
if (isCompilerBindingCall(call))
|
|
3030
|
+
return;
|
|
3031
|
+
const callee = unwrapParens(call.callee);
|
|
3032
|
+
visitExpr(callee, true);
|
|
3033
|
+
for (const arg of call.arguments) {
|
|
3034
|
+
if (isSpreadElement(arg)) {
|
|
3035
|
+
visitExpr(arg.argument, true);
|
|
3036
|
+
continue;
|
|
3037
|
+
}
|
|
3038
|
+
visitExpr(arg, true);
|
|
3039
|
+
}
|
|
3040
|
+
}
|
|
3041
|
+
function isSignalValuePath(path) {
|
|
3042
|
+
return path[0] === "value" || path.length >= 2 && path[1] === "value";
|
|
3043
|
+
}
|
|
3044
|
+
function getMemberExpressionRootAndPath(node) {
|
|
3045
|
+
const path = [];
|
|
3046
|
+
let current = node;
|
|
3047
|
+
while (true) {
|
|
3048
|
+
current = unwrapParens(current);
|
|
3049
|
+
if (isTSNonNullExpression(current)) {
|
|
3050
|
+
current = current.expression;
|
|
3051
|
+
continue;
|
|
3052
|
+
}
|
|
3053
|
+
if (isChainExpression(current)) {
|
|
3054
|
+
current = unwrapChain(current);
|
|
3055
|
+
continue;
|
|
3056
|
+
}
|
|
3057
|
+
if (isMemberLike(current)) {
|
|
3058
|
+
const member = isChainExpression(current) ? unwrapChain(current) : current;
|
|
3059
|
+
if (member.computed)
|
|
3060
|
+
return null;
|
|
3061
|
+
const property = member.property;
|
|
3062
|
+
if (!isIdentifier(property))
|
|
3063
|
+
return null;
|
|
3064
|
+
path.push(property.name);
|
|
3065
|
+
current = member.object;
|
|
3066
|
+
continue;
|
|
3067
|
+
}
|
|
3068
|
+
break;
|
|
3069
|
+
}
|
|
3070
|
+
if (isIdentifier(current)) {
|
|
3071
|
+
return { root: current.name, path: path.reverse() };
|
|
3072
|
+
}
|
|
3073
|
+
return null;
|
|
3074
|
+
}
|
|
3075
|
+
function isReactiveRead(node, scope) {
|
|
3076
|
+
if (isTSNonNullExpression(node)) {
|
|
3077
|
+
return isReactiveRead(node.expression, scope);
|
|
3078
|
+
}
|
|
3079
|
+
if (isChainExpression(node)) {
|
|
3080
|
+
return isReactiveRead(unwrapChain(node), scope);
|
|
3081
|
+
}
|
|
3082
|
+
if (isIdentifier(node)) {
|
|
3083
|
+
const binding = scope.bindings.get(node.name);
|
|
3084
|
+
if (!binding)
|
|
3085
|
+
return false;
|
|
3086
|
+
if (binding.kind === "prop") {
|
|
3087
|
+
return true;
|
|
3088
|
+
}
|
|
3089
|
+
if (binding.kind === "mutable" && binding.path.length > 0) {
|
|
3090
|
+
return true;
|
|
3091
|
+
}
|
|
3092
|
+
return false;
|
|
3093
|
+
}
|
|
3094
|
+
if (!isMemberLike(node))
|
|
3095
|
+
return false;
|
|
3096
|
+
const rootPath = getMemberExpressionRootAndPath(node);
|
|
3097
|
+
if (!rootPath)
|
|
3098
|
+
return false;
|
|
3099
|
+
const binding = scope.bindings.get(rootPath.root);
|
|
3100
|
+
if (!binding) {
|
|
3101
|
+
return isSignalValuePath(rootPath.path);
|
|
3102
|
+
}
|
|
3103
|
+
if (binding.kind === "prop") {
|
|
3104
|
+
return true;
|
|
3105
|
+
}
|
|
3106
|
+
if (binding.kind === "mutable") {
|
|
3107
|
+
return true;
|
|
3108
|
+
}
|
|
3109
|
+
if (binding.kind === "signal" || binding.kind === "computed") {
|
|
3110
|
+
return rootPath.path[0] === "value";
|
|
3111
|
+
}
|
|
3112
|
+
if (binding.kind === "signalObject") {
|
|
3113
|
+
return rootPath.path.length >= 2 && rootPath.path[1] === "value";
|
|
3114
|
+
}
|
|
3115
|
+
return false;
|
|
3116
|
+
}
|
|
3117
|
+
function isGetterCall(node, scope) {
|
|
3118
|
+
const call = unwrapCall(node);
|
|
3119
|
+
if (!isCallExpression(call))
|
|
3120
|
+
return false;
|
|
3121
|
+
const callee = unwrapParens(call.callee);
|
|
3122
|
+
if (isIdentifier(callee)) {
|
|
3123
|
+
const binding = scope.bindings.get(callee.name);
|
|
3124
|
+
if (binding?.kind === "getter")
|
|
3125
|
+
return true;
|
|
3126
|
+
if (binding)
|
|
3127
|
+
return false;
|
|
3128
|
+
return call.arguments.length === 0;
|
|
3129
|
+
}
|
|
3130
|
+
if (isMemberLike(callee) && !callee.computed && isIdentifier(callee.property)) {
|
|
3131
|
+
if (call.arguments.length !== 0)
|
|
3132
|
+
return false;
|
|
3133
|
+
const rootPath = getMemberExpressionRootAndPath(callee);
|
|
3134
|
+
if (!rootPath)
|
|
3135
|
+
return false;
|
|
3136
|
+
const binding = scope.bindings.get(rootPath.root);
|
|
3137
|
+
if (binding?.kind === "mutable" || binding?.kind === "function" || binding?.kind === "signal" || binding?.kind === "computed" || binding?.kind === "prop" || binding?.kind === "getter" || binding?.kind === "signalObject") {
|
|
3138
|
+
return false;
|
|
3139
|
+
}
|
|
3140
|
+
return true;
|
|
3141
|
+
}
|
|
3142
|
+
return false;
|
|
3143
|
+
}
|
|
3144
|
+
function isReactiveValue2(node, scope) {
|
|
3145
|
+
if (isParenthesizedExpression(node)) {
|
|
3146
|
+
return isReactiveValue2(unwrapParens(node), scope);
|
|
3147
|
+
}
|
|
3148
|
+
if (isTSNonNullExpression(node)) {
|
|
3149
|
+
return isReactiveValue2(node.expression, scope);
|
|
3150
|
+
}
|
|
3151
|
+
if (isChainExpression(node)) {
|
|
3152
|
+
return isReactiveValue2(unwrapChain(node), scope);
|
|
3153
|
+
}
|
|
3154
|
+
if (isIdentifier(node)) {
|
|
3155
|
+
const binding = scope.bindings.get(node.name);
|
|
3156
|
+
if (!binding)
|
|
3157
|
+
return false;
|
|
3158
|
+
return binding.kind === "prop" || binding.kind === "signal" || binding.kind === "computed" || binding.kind === "mutable";
|
|
3159
|
+
}
|
|
3160
|
+
if (!isMemberLike(node))
|
|
3161
|
+
return false;
|
|
3162
|
+
const rootPath = getMemberExpressionRootAndPath(node);
|
|
3163
|
+
if (!rootPath)
|
|
3164
|
+
return false;
|
|
3165
|
+
const binding = scope.bindings.get(rootPath.root);
|
|
3166
|
+
if (!binding) {
|
|
3167
|
+
return isSignalValuePath(rootPath.path);
|
|
3168
|
+
}
|
|
3169
|
+
if (binding.kind === "prop" || binding.kind === "mutable") {
|
|
3170
|
+
return true;
|
|
3171
|
+
}
|
|
3172
|
+
if (binding.kind === "signal" || binding.kind === "computed") {
|
|
3173
|
+
return rootPath.path[0] === "value";
|
|
3174
|
+
}
|
|
3175
|
+
if (binding.kind === "signalObject") {
|
|
3176
|
+
return rootPath.path.length >= 2 && rootPath.path[1] === "value";
|
|
3177
|
+
}
|
|
3178
|
+
return false;
|
|
3179
|
+
}
|
|
3180
|
+
function containsReactiveValue2(expr, scope) {
|
|
3181
|
+
let found = false;
|
|
3182
|
+
function visit(node) {
|
|
3183
|
+
if (found)
|
|
3184
|
+
return;
|
|
3185
|
+
if (!node || typeof node !== "object")
|
|
3186
|
+
return;
|
|
3187
|
+
if (node.type === "ParenthesizedExpression") {
|
|
3188
|
+
visit(node.expression);
|
|
3189
|
+
return;
|
|
3190
|
+
}
|
|
3191
|
+
if (isChainExpression(node)) {
|
|
3192
|
+
visit(unwrapChain(node));
|
|
3193
|
+
return;
|
|
3194
|
+
}
|
|
3195
|
+
if (isMemberLike(node) || isTSNonNullExpression(node) || isIdentifier(node)) {
|
|
3196
|
+
if (isReactiveValue2(node, scope)) {
|
|
3197
|
+
found = true;
|
|
3198
|
+
return;
|
|
3199
|
+
}
|
|
3200
|
+
}
|
|
3201
|
+
if (isCallLike(node) && isGetterCall(node, scope)) {
|
|
3202
|
+
found = true;
|
|
3203
|
+
return;
|
|
3204
|
+
}
|
|
3205
|
+
if (isArrowFunctionExpression(node) || isFunctionExpression(node)) {
|
|
3206
|
+
return;
|
|
3207
|
+
}
|
|
3208
|
+
for (const key of Object.keys(node)) {
|
|
3209
|
+
if (key === "loc" || key === "start" || key === "end" || key === "leadingComments" || key === "trailingComments")
|
|
3210
|
+
continue;
|
|
3211
|
+
const value = node[key];
|
|
3212
|
+
if (Array.isArray(value)) {
|
|
3213
|
+
for (const item of value) {
|
|
3214
|
+
if (item && typeof item === "object")
|
|
3215
|
+
visit(item);
|
|
3216
|
+
}
|
|
3217
|
+
} else if (value && typeof value === "object") {
|
|
3218
|
+
visit(value);
|
|
3219
|
+
}
|
|
3220
|
+
}
|
|
3221
|
+
}
|
|
3222
|
+
visit(expr);
|
|
3223
|
+
return found;
|
|
3224
|
+
}
|
|
3225
|
+
function containsReactiveRead(expr, scope) {
|
|
3226
|
+
let found = false;
|
|
3227
|
+
function visit(node, enterFunction) {
|
|
3228
|
+
if (found)
|
|
3229
|
+
return;
|
|
3230
|
+
if (!node || typeof node !== "object")
|
|
3231
|
+
return;
|
|
3232
|
+
if (node.type === "ParenthesizedExpression") {
|
|
3233
|
+
visit(node.expression, enterFunction);
|
|
3234
|
+
return;
|
|
3235
|
+
}
|
|
3236
|
+
if (isChainExpression(node)) {
|
|
3237
|
+
visit(unwrapChain(node), enterFunction);
|
|
3238
|
+
return;
|
|
3239
|
+
}
|
|
3240
|
+
if (isFunctionLike(node)) {
|
|
3241
|
+
if (!enterFunction)
|
|
3242
|
+
return;
|
|
3243
|
+
for (const param of node.params) {
|
|
3244
|
+
visit(param, false);
|
|
3245
|
+
}
|
|
3246
|
+
visit(node.body, false);
|
|
3247
|
+
return;
|
|
3248
|
+
}
|
|
3249
|
+
if (isJSXElement(node) || isJSXFragment(node)) {
|
|
3250
|
+
return;
|
|
3251
|
+
}
|
|
3252
|
+
if (isMemberLike(node) || isTSNonNullExpression(node) || isIdentifier(node)) {
|
|
3253
|
+
if (isReactiveRead(node, scope)) {
|
|
3254
|
+
found = true;
|
|
3255
|
+
return;
|
|
3256
|
+
}
|
|
3257
|
+
}
|
|
3258
|
+
if (isCallLike(node) && isGetterCall(node, scope)) {
|
|
3259
|
+
found = true;
|
|
3260
|
+
return;
|
|
3261
|
+
}
|
|
3262
|
+
if (isCallLike(node)) {
|
|
3263
|
+
visitCallCalleeAndArgs(node, visit);
|
|
3264
|
+
return;
|
|
3265
|
+
}
|
|
3266
|
+
for (const key of Object.keys(node)) {
|
|
3267
|
+
if (key === "loc" || key === "start" || key === "end" || key === "leadingComments" || key === "trailingComments")
|
|
3268
|
+
continue;
|
|
3269
|
+
const value = node[key];
|
|
3270
|
+
if (Array.isArray(value)) {
|
|
3271
|
+
for (const item of value) {
|
|
3272
|
+
if (item && typeof item === "object")
|
|
3273
|
+
visit(item, false);
|
|
3274
|
+
}
|
|
3275
|
+
} else if (value && typeof value === "object") {
|
|
3276
|
+
visit(value, false);
|
|
3277
|
+
}
|
|
3278
|
+
}
|
|
3279
|
+
}
|
|
3280
|
+
visit(expr, false);
|
|
3281
|
+
return found;
|
|
3282
|
+
}
|
|
3283
|
+
function getComponentExpressionInfo(exprPath) {
|
|
3284
|
+
const parent = exprPath.parentPath;
|
|
3285
|
+
if (!parent) {
|
|
3286
|
+
return { isComponent: false, componentName: null, attributeName: null };
|
|
3287
|
+
}
|
|
3288
|
+
let elementPath = null;
|
|
3289
|
+
let attributeName = null;
|
|
3290
|
+
if (parent.isJSXAttribute && parent.isJSXAttribute()) {
|
|
3291
|
+
const attrName = parent.node.name;
|
|
3292
|
+
if (isJSXIdentifier(attrName)) {
|
|
3293
|
+
attributeName = attrName.name;
|
|
3294
|
+
}
|
|
3295
|
+
const openingElementPath = parent.parentPath;
|
|
3296
|
+
elementPath = openingElementPath?.parentPath;
|
|
3297
|
+
} else if (parent.isJSXElement && parent.isJSXElement()) {
|
|
3298
|
+
elementPath = parent;
|
|
3299
|
+
}
|
|
3300
|
+
if (!elementPath || !elementPath.isJSXElement || !elementPath.isJSXElement()) {
|
|
3301
|
+
return { isComponent: false, componentName: null, attributeName: null };
|
|
3302
|
+
}
|
|
3303
|
+
const name = elementPath.node.openingElement.name;
|
|
3304
|
+
let firstChar = null;
|
|
3305
|
+
let componentName = null;
|
|
3306
|
+
if (isJSXIdentifier(name)) {
|
|
3307
|
+
componentName = name.name;
|
|
3308
|
+
firstChar = name.name?.[0] ?? null;
|
|
3309
|
+
} else if (isJSXMemberExpression(name)) {
|
|
3310
|
+
let current = name;
|
|
3311
|
+
while (isJSXMemberExpression(current)) {
|
|
3312
|
+
current = current.object;
|
|
3313
|
+
}
|
|
3314
|
+
if (isJSXIdentifier(current)) {
|
|
3315
|
+
componentName = current.name;
|
|
3316
|
+
firstChar = current.name?.[0] ?? null;
|
|
3317
|
+
}
|
|
3318
|
+
}
|
|
3319
|
+
if (!firstChar || firstChar !== firstChar.toUpperCase() || !/[A-Z]/.test(firstChar)) {
|
|
3320
|
+
return { isComponent: false, componentName: null, attributeName };
|
|
3321
|
+
}
|
|
3322
|
+
return { isComponent: true, componentName, attributeName };
|
|
3323
|
+
}
|
|
3324
|
+
function isReactiveComponentProp(componentName, attributeName, componentNames, reactiveProps, importedReactiveProps) {
|
|
3325
|
+
if (isBuiltinReactiveProp(componentName, attributeName)) {
|
|
3326
|
+
return true;
|
|
3327
|
+
}
|
|
3328
|
+
const componentFn = componentNames.get(componentName);
|
|
3329
|
+
if (componentFn) {
|
|
3330
|
+
const props = reactiveProps.get(componentFn);
|
|
3331
|
+
if (!props)
|
|
3332
|
+
return false;
|
|
3333
|
+
return props.has(attributeName);
|
|
3334
|
+
}
|
|
3335
|
+
const importedProps = importedReactiveProps.get(componentName);
|
|
3336
|
+
if (!importedProps)
|
|
3337
|
+
return false;
|
|
3338
|
+
return importedProps.has(attributeName);
|
|
3339
|
+
}
|
|
3340
|
+
function shouldWrap(expr, scope) {
|
|
3341
|
+
const inner = unwrapParens(expr);
|
|
3342
|
+
if (isArrowFunctionExpression(inner) || isFunctionExpression(inner))
|
|
3343
|
+
return false;
|
|
3344
|
+
if (isJSXEmptyExpression(inner))
|
|
3345
|
+
return false;
|
|
3346
|
+
return containsReactiveRead(inner, scope);
|
|
3347
|
+
}
|
|
3348
|
+
var RESOLVE_HELPER = "_$unwrap";
|
|
3349
|
+
function transformPropMemberAccess(expr, scope) {
|
|
3350
|
+
let changed = false;
|
|
3351
|
+
function resolveRoot(rootName) {
|
|
3352
|
+
return callExpression(identifier(RESOLVE_HELPER), [
|
|
3353
|
+
identifier(rootName)
|
|
3354
|
+
]);
|
|
3355
|
+
}
|
|
3356
|
+
function replaceMemberRoot(node, rootName) {
|
|
3357
|
+
let current = node;
|
|
3358
|
+
let parent = null;
|
|
3359
|
+
while (true) {
|
|
3360
|
+
if (isParenthesizedExpression(current)) {
|
|
3361
|
+
current = unwrapParens(current);
|
|
3362
|
+
continue;
|
|
3363
|
+
}
|
|
3364
|
+
if (isChainExpression(current)) {
|
|
3365
|
+
current = unwrapChain(current);
|
|
3366
|
+
continue;
|
|
3367
|
+
}
|
|
3368
|
+
if (isTSNonNullExpression(current)) {
|
|
3369
|
+
current = current.expression;
|
|
3370
|
+
continue;
|
|
3371
|
+
}
|
|
3372
|
+
if (!isMemberExpression(current))
|
|
3373
|
+
break;
|
|
3374
|
+
parent = current;
|
|
3375
|
+
current = current.object;
|
|
3376
|
+
}
|
|
3377
|
+
if (parent && isIdentifier(current) && current.name === rootName) {
|
|
3378
|
+
parent.object = resolveRoot(rootName);
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
3381
|
+
function visit(node, enterFunction) {
|
|
3382
|
+
if (!node || typeof node !== "object")
|
|
3383
|
+
return;
|
|
3384
|
+
if (node.type === "ParenthesizedExpression") {
|
|
3385
|
+
visit(node.expression, enterFunction);
|
|
3386
|
+
return;
|
|
3387
|
+
}
|
|
3388
|
+
if (isFunctionLike(node)) {
|
|
3389
|
+
if (!enterFunction)
|
|
3390
|
+
return;
|
|
3391
|
+
for (const param of node.params) {
|
|
3392
|
+
visit(param, false);
|
|
3393
|
+
}
|
|
3394
|
+
visit(node.body, false);
|
|
3395
|
+
return;
|
|
3396
|
+
}
|
|
3397
|
+
if (isJSXElement(node) || isJSXFragment(node)) {
|
|
3398
|
+
return;
|
|
3399
|
+
}
|
|
3400
|
+
if (isMemberLike(node)) {
|
|
3401
|
+
const rootPath = getMemberExpressionRootAndPath(node);
|
|
3402
|
+
if (rootPath) {
|
|
3403
|
+
const binding = scope.bindings.get(rootPath.root);
|
|
3404
|
+
if (binding && binding.kind === "prop") {
|
|
3405
|
+
replaceMemberRoot(node, rootPath.root);
|
|
3406
|
+
changed = true;
|
|
3407
|
+
return;
|
|
3408
|
+
}
|
|
3409
|
+
}
|
|
3410
|
+
return;
|
|
3411
|
+
}
|
|
3412
|
+
if (isCallLike(node)) {
|
|
3413
|
+
visitCallCalleeAndArgs(node, visit);
|
|
3414
|
+
return;
|
|
3415
|
+
}
|
|
3416
|
+
for (const key of Object.keys(node)) {
|
|
3417
|
+
if (key === "loc" || key === "start" || key === "end" || key === "leadingComments" || key === "trailingComments")
|
|
3418
|
+
continue;
|
|
3419
|
+
const value = node[key];
|
|
3420
|
+
if (Array.isArray(value)) {
|
|
3421
|
+
for (const item of value) {
|
|
3422
|
+
if (item && typeof item === "object")
|
|
3423
|
+
visit(item, false);
|
|
3424
|
+
}
|
|
3425
|
+
} else if (value && typeof value === "object") {
|
|
3426
|
+
visit(value, false);
|
|
3427
|
+
}
|
|
3428
|
+
}
|
|
3429
|
+
}
|
|
3430
|
+
visit(expr, false);
|
|
3431
|
+
return changed;
|
|
3432
|
+
}
|
|
3433
|
+
function wrapExpression(expr) {
|
|
3434
|
+
return arrowFunctionExpression([], expr);
|
|
3435
|
+
}
|
|
3436
|
+
function isPropRoot(expr, scope) {
|
|
3437
|
+
if (isIdentifier(expr)) {
|
|
3438
|
+
return scope.bindings.get(expr.name)?.kind === "prop";
|
|
3439
|
+
}
|
|
3440
|
+
if (isCallExpression(expr) && isIdentifier(expr.callee, { name: "_$unwrap" }) && expr.arguments.length === 1 && isIdentifier(expr.arguments[0])) {
|
|
3441
|
+
return scope.bindings.get(expr.arguments[0].name)?.kind === "prop";
|
|
3442
|
+
}
|
|
3443
|
+
return false;
|
|
3444
|
+
}
|
|
3445
|
+
function isChildrenPropRead(expr, scope) {
|
|
3446
|
+
let node = unwrapParens(expr);
|
|
3447
|
+
if (isChainExpression(node))
|
|
3448
|
+
node = unwrapParens(unwrapChain(node));
|
|
3449
|
+
if (isIdentifier(node, { name: "children" }))
|
|
3450
|
+
return true;
|
|
3451
|
+
if (!isMemberExpression(node) || node.computed)
|
|
3452
|
+
return false;
|
|
3453
|
+
if (!isIdentifier(node.property, { name: "children" }))
|
|
3454
|
+
return false;
|
|
3455
|
+
return isPropRoot(node.object, scope);
|
|
3456
|
+
}
|
|
3457
|
+
function createBindingDescriptor(expr, exprPath, options, scope, childrenGetter) {
|
|
3458
|
+
if (!options.explicitBindings) {
|
|
3459
|
+
return wrapExpression(expr);
|
|
3460
|
+
}
|
|
3461
|
+
const parent = exprPath.parentPath;
|
|
3462
|
+
if (parent && parent.isJSXAttribute && parent.isJSXAttribute()) {
|
|
3463
|
+
const compInfo = getComponentExpressionInfo(exprPath);
|
|
3464
|
+
if (compInfo.isComponent) {
|
|
3465
|
+
return wrapExpression(expr);
|
|
3466
|
+
}
|
|
3467
|
+
const attrName = parent.node.name.name;
|
|
3468
|
+
if (attrName === "style") {
|
|
3469
|
+
return callExpression(identifier("_$bindStyle"), [
|
|
3470
|
+
wrapExpression(expr)
|
|
3471
|
+
]);
|
|
3472
|
+
}
|
|
3473
|
+
if (attrName === "class") {
|
|
3474
|
+
return callExpression(identifier("_$bindClass"), [
|
|
3475
|
+
wrapExpression(expr)
|
|
3476
|
+
]);
|
|
3477
|
+
}
|
|
3478
|
+
return callExpression(identifier("_$bindAttr"), [
|
|
3479
|
+
stringLiteral(attrName),
|
|
3480
|
+
wrapExpression(expr)
|
|
3481
|
+
]);
|
|
3482
|
+
}
|
|
3483
|
+
const compInfo = getComponentExpressionInfo(exprPath);
|
|
3484
|
+
if (isChildrenPropRead(expr, scope) || childrenGetter || compInfo.isComponent && compInfo.attributeName === null) {
|
|
3485
|
+
return wrapExpression(expr);
|
|
3486
|
+
}
|
|
3487
|
+
return callExpression(identifier("_$bindText"), [wrapExpression(expr)]);
|
|
3488
|
+
}
|
|
3489
|
+
function collectComponentFunctions2(ast, ccNames) {
|
|
3490
|
+
const functions = new Set;
|
|
3491
|
+
const names = new Map;
|
|
3492
|
+
const exported = new Set;
|
|
3493
|
+
if (ccNames.size === 0)
|
|
3494
|
+
return { functions, names, exported };
|
|
3495
|
+
traverse(ast, {
|
|
3496
|
+
CallExpression(path) {
|
|
3497
|
+
const callee = path.node.callee;
|
|
3498
|
+
if (!isIdentifier(callee) || !ccNames.has(callee.name))
|
|
3499
|
+
return;
|
|
3500
|
+
const firstArg = path.node.arguments[0];
|
|
3501
|
+
if (!firstArg || !isFunction(firstArg))
|
|
3502
|
+
return;
|
|
3503
|
+
const componentFn = firstArg;
|
|
3504
|
+
functions.add(componentFn);
|
|
3505
|
+
const parent = path.parentPath;
|
|
3506
|
+
if (parent && parent.isVariableDeclarator && parent.isVariableDeclarator()) {
|
|
3507
|
+
const id = parent.node.id;
|
|
3508
|
+
if (isIdentifier(id)) {
|
|
3509
|
+
names.set(id.name, componentFn);
|
|
3510
|
+
}
|
|
3511
|
+
}
|
|
3512
|
+
}
|
|
3513
|
+
});
|
|
3514
|
+
traverse(ast, {
|
|
3515
|
+
ExportNamedDeclaration(path) {
|
|
3516
|
+
const declaration = path.node.declaration;
|
|
3517
|
+
if (!isVariableDeclaration(declaration))
|
|
3518
|
+
return;
|
|
3519
|
+
for (const decl of declaration.declarations) {
|
|
3520
|
+
const init = decl.init;
|
|
3521
|
+
if (!init || !isCallExpression(init))
|
|
3522
|
+
continue;
|
|
3523
|
+
const callee = init.callee;
|
|
3524
|
+
if (!isIdentifier(callee) || !ccNames.has(callee.name))
|
|
3525
|
+
continue;
|
|
3526
|
+
const firstArg = init.arguments[0];
|
|
3527
|
+
if (!firstArg || !isFunction(firstArg))
|
|
3528
|
+
continue;
|
|
3529
|
+
exported.add(firstArg);
|
|
3530
|
+
}
|
|
3531
|
+
},
|
|
3532
|
+
ExportDefaultDeclaration(path) {
|
|
3533
|
+
const decl = path.node.declaration;
|
|
3534
|
+
if (isIdentifier(decl)) {
|
|
3535
|
+
const componentFn = names.get(decl.name);
|
|
3536
|
+
if (componentFn) {
|
|
3537
|
+
exported.add(componentFn);
|
|
3538
|
+
}
|
|
3539
|
+
} else if (isCallExpression(decl)) {
|
|
3540
|
+
const callee = decl.callee;
|
|
3541
|
+
if (isIdentifier(callee) && ccNames.has(callee.name)) {
|
|
3542
|
+
const firstArg = decl.arguments[0];
|
|
3543
|
+
if (firstArg && isFunction(firstArg)) {
|
|
3544
|
+
exported.add(firstArg);
|
|
3545
|
+
}
|
|
3546
|
+
}
|
|
3547
|
+
}
|
|
3548
|
+
}
|
|
3549
|
+
});
|
|
3550
|
+
return { functions, names, exported };
|
|
3551
|
+
}
|
|
3552
|
+
function computeLocalScopes2(ast, names) {
|
|
3553
|
+
const scopes = new Map;
|
|
3554
|
+
traverse(ast, {
|
|
3555
|
+
Function(path) {
|
|
3556
|
+
const scope = { bindings: new Map };
|
|
3557
|
+
trackLocalScopeBindings(path, names, scope);
|
|
3558
|
+
scopes.set(path.node, scope);
|
|
3559
|
+
}
|
|
3560
|
+
});
|
|
3561
|
+
return scopes;
|
|
3562
|
+
}
|
|
3563
|
+
function collectComponentCallGraph2(ast, componentNames) {
|
|
3564
|
+
const callGraph = new Map;
|
|
3565
|
+
if (componentNames.size === 0)
|
|
3566
|
+
return callGraph;
|
|
3567
|
+
traverse(ast, {
|
|
3568
|
+
JSXElement(path) {
|
|
3569
|
+
const name = path.node.openingElement.name;
|
|
3570
|
+
if (!isJSXIdentifier(name))
|
|
3571
|
+
return;
|
|
3572
|
+
const callee = componentNames.get(name.name);
|
|
3573
|
+
if (!callee)
|
|
3574
|
+
return;
|
|
3575
|
+
const callerPath = path.findParent((p) => p.isFunction());
|
|
3576
|
+
const callerFn = callerPath ? callerPath.node : null;
|
|
3577
|
+
if (!callerFn)
|
|
3578
|
+
return;
|
|
3579
|
+
const props = [];
|
|
3580
|
+
const spreads = [];
|
|
3581
|
+
for (const attr of path.node.openingElement.attributes) {
|
|
3582
|
+
if (isJSXSpreadAttribute(attr)) {
|
|
3583
|
+
if (attr.argument) {
|
|
3584
|
+
spreads.push(attr.argument);
|
|
3585
|
+
}
|
|
3586
|
+
continue;
|
|
3587
|
+
}
|
|
3588
|
+
if (isJSXAttribute(attr)) {
|
|
3589
|
+
const attrName = attr.name.name;
|
|
3590
|
+
if (!attrName)
|
|
3591
|
+
continue;
|
|
3592
|
+
if (isJSXExpressionContainer(attr.value)) {
|
|
3593
|
+
const expr = attr.value.expression;
|
|
3594
|
+
if (!expr || isJSXEmptyExpression(expr))
|
|
3595
|
+
continue;
|
|
3596
|
+
props.push({ name: attrName, value: expr });
|
|
3597
|
+
}
|
|
3598
|
+
}
|
|
3599
|
+
}
|
|
3600
|
+
const childExprs = [];
|
|
3601
|
+
for (const child of path.node.children) {
|
|
3602
|
+
if (isJSXExpressionContainer(child)) {
|
|
3603
|
+
const expr = child.expression;
|
|
3604
|
+
if (!expr || isJSXEmptyExpression(expr))
|
|
3605
|
+
continue;
|
|
3606
|
+
childExprs.push(expr);
|
|
3607
|
+
}
|
|
3608
|
+
}
|
|
3609
|
+
if (childExprs.length === 1) {
|
|
3610
|
+
props.push({ name: "children", value: childExprs[0] });
|
|
3611
|
+
} else if (childExprs.length > 1) {
|
|
3612
|
+
props.push({ name: "children", value: arrayExpression(childExprs) });
|
|
3613
|
+
}
|
|
3614
|
+
const sites = callGraph.get(callerFn) ?? [];
|
|
3615
|
+
sites.push({ callee, props, spreads });
|
|
3616
|
+
callGraph.set(callerFn, sites);
|
|
3617
|
+
}
|
|
3618
|
+
});
|
|
3619
|
+
return callGraph;
|
|
3620
|
+
}
|
|
3621
|
+
function buildFullScope2(localScopes, fn, reactiveProps) {
|
|
3622
|
+
const localScope = localScopes.get(fn);
|
|
3623
|
+
if (!localScope)
|
|
3624
|
+
return null;
|
|
3625
|
+
const fullScope = { bindings: new Map(localScope.bindings) };
|
|
3626
|
+
trackPropBindings(fullScope, fn.params[0], reactiveProps);
|
|
3627
|
+
return fullScope;
|
|
3628
|
+
}
|
|
3629
|
+
function propagateReactiveProps2(localScopes, callGraph, componentFunctions) {
|
|
3630
|
+
const reactiveProps = new Map;
|
|
3631
|
+
for (const [callerFn, sites] of callGraph) {
|
|
3632
|
+
const localScope = localScopes.get(callerFn);
|
|
3633
|
+
if (!localScope)
|
|
3634
|
+
continue;
|
|
3635
|
+
for (const site of sites) {
|
|
3636
|
+
const calleeReactive = reactiveProps.get(site.callee) ?? new Set;
|
|
3637
|
+
for (const prop of site.props) {
|
|
3638
|
+
if (containsReactiveValue2(prop.value, localScope)) {
|
|
3639
|
+
calleeReactive.add(prop.name);
|
|
3640
|
+
}
|
|
3641
|
+
}
|
|
3642
|
+
reactiveProps.set(site.callee, calleeReactive);
|
|
3643
|
+
}
|
|
3644
|
+
}
|
|
3645
|
+
const worklist = new Set(componentFunctions);
|
|
3646
|
+
while (worklist.size > 0) {
|
|
3647
|
+
const callerFn = worklist.values().next().value;
|
|
3648
|
+
worklist.delete(callerFn);
|
|
3649
|
+
const callerReactive = reactiveProps.get(callerFn) ?? new Set;
|
|
3650
|
+
const fullScope = buildFullScope2(localScopes, callerFn, callerReactive);
|
|
3651
|
+
if (!fullScope)
|
|
3652
|
+
continue;
|
|
3653
|
+
const sites = callGraph.get(callerFn) ?? [];
|
|
3654
|
+
for (const site of sites) {
|
|
3655
|
+
const calleeReactive = reactiveProps.get(site.callee) ?? new Set;
|
|
3656
|
+
const initialSize = calleeReactive.size;
|
|
3657
|
+
for (const prop of site.props) {
|
|
3658
|
+
if (containsReactiveValue2(prop.value, fullScope)) {
|
|
3659
|
+
calleeReactive.add(prop.name);
|
|
3660
|
+
}
|
|
3661
|
+
}
|
|
3662
|
+
for (const spread of site.spreads) {
|
|
3663
|
+
const resolved = isObjectExpression(spread) ? spread : isIdentifier(spread) ? resolveLocalObjectLiteral(callerFn, spread.name) : null;
|
|
3664
|
+
if (resolved) {
|
|
3665
|
+
analyzeObjectLiteralSpread(resolved, fullScope, calleeReactive);
|
|
3666
|
+
} else {
|
|
3667
|
+
const allCalleeProps = getAllPropNames2(site.callee.params[0]);
|
|
3668
|
+
for (const propName of allCalleeProps) {
|
|
3669
|
+
calleeReactive.add(propName);
|
|
3670
|
+
}
|
|
3671
|
+
}
|
|
3672
|
+
}
|
|
3673
|
+
if (calleeReactive.size > initialSize) {
|
|
3674
|
+
reactiveProps.set(site.callee, calleeReactive);
|
|
3675
|
+
worklist.add(site.callee);
|
|
3676
|
+
}
|
|
3677
|
+
}
|
|
3678
|
+
}
|
|
3679
|
+
return reactiveProps;
|
|
3680
|
+
}
|
|
3681
|
+
function hasSnapshotPragma(node) {
|
|
3682
|
+
return node.leadingComments?.some((comment) => /\bsinwan-snapshot\b/.test(comment.value)) ?? false;
|
|
3683
|
+
}
|
|
3684
|
+
function isTypePosition(ref) {
|
|
3685
|
+
return ref.findParent((parent) => parent.isTSType()) != null;
|
|
3686
|
+
}
|
|
3687
|
+
function canRewriteDerivedReference(ref) {
|
|
3688
|
+
if (!ref.isIdentifier() || !ref.isReferencedIdentifier())
|
|
3689
|
+
return false;
|
|
3690
|
+
if (isTypePosition(ref))
|
|
3691
|
+
return true;
|
|
3692
|
+
let target = ref;
|
|
3693
|
+
while ((target.parentPath?.isMemberExpression() || target.parentPath?.isOptionalMemberExpression()) && target.key === "object" || (target.parentPath?.isTSNonNullExpression() || target.parentPath?.isTSAsExpression() || target.parentPath?.isTSTypeAssertion() || target.parentPath?.isTSSatisfiesExpression() || target.parentPath?.isParenthesizedExpression()) && target.key === "expression") {
|
|
3694
|
+
target = target.parentPath;
|
|
3695
|
+
}
|
|
3696
|
+
const parent = target.parentPath;
|
|
3697
|
+
return !(parent?.isAssignmentExpression() && target.key === "left" || parent?.isUpdateExpression() || parent?.isUnaryExpression({ operator: "delete" }) || (parent?.isForInStatement() || parent?.isForOfStatement()) && target.key === "left" || target !== ref && (parent?.isCallExpression() || parent?.isOptionalCallExpression()) && target.key === "callee");
|
|
3698
|
+
}
|
|
3699
|
+
function isPureDerivedCall(path, names, pureImports) {
|
|
3700
|
+
if (!path.isCallExpression() && !path.isOptionalCallExpression())
|
|
3701
|
+
return false;
|
|
3702
|
+
const call = unwrapCall(path.node);
|
|
3703
|
+
const callee = unwrapParens(call.callee);
|
|
3704
|
+
const args = call.arguments ?? [];
|
|
3705
|
+
if (isMemberExpression(callee) || isOptionalMemberExpression(callee)) {
|
|
3706
|
+
const props = [];
|
|
3707
|
+
let node = unwrapParens(callee);
|
|
3708
|
+
while (true) {
|
|
3709
|
+
node = unwrapParens(node);
|
|
3710
|
+
if (isChainExpression(node)) {
|
|
3711
|
+
node = unwrapChain(node);
|
|
3712
|
+
continue;
|
|
3713
|
+
}
|
|
3714
|
+
if (!isMemberExpression(node))
|
|
3715
|
+
break;
|
|
3716
|
+
const prop = node.property;
|
|
3717
|
+
if (isIdentifier(prop)) {
|
|
3718
|
+
props.unshift(prop.name);
|
|
3719
|
+
} else if (isStringLiteral(prop)) {
|
|
3720
|
+
props.unshift(prop.value);
|
|
3721
|
+
} else {
|
|
3722
|
+
return false;
|
|
3723
|
+
}
|
|
3724
|
+
node = node.object;
|
|
3725
|
+
}
|
|
3726
|
+
if (!isIdentifier(node))
|
|
3727
|
+
return false;
|
|
3728
|
+
const root = node.name;
|
|
3729
|
+
if (pureImports.member.has(root)) {
|
|
3730
|
+
return true;
|
|
3731
|
+
}
|
|
3732
|
+
const nsMembers = pureImports.namespace.get(root);
|
|
3733
|
+
if (nsMembers && props.some((p) => nsMembers.has(p))) {
|
|
3734
|
+
return true;
|
|
3735
|
+
}
|
|
3736
|
+
return false;
|
|
3737
|
+
}
|
|
3738
|
+
if (!isIdentifier(callee))
|
|
3739
|
+
return false;
|
|
3740
|
+
const binding = path.scope.getBinding(callee.name);
|
|
3741
|
+
if (!binding) {
|
|
3742
|
+
return ["Boolean", "Number", "String"].includes(callee.name) && args.length === 1;
|
|
3743
|
+
}
|
|
3744
|
+
if ((binding.path.isImportSpecifier() || binding.path.isImportDefaultSpecifier()) && pureImports.direct.has(callee.name)) {
|
|
3745
|
+
return true;
|
|
3746
|
+
}
|
|
3747
|
+
if (args.length !== 0 || !binding.path.isVariableDeclarator())
|
|
3748
|
+
return false;
|
|
3749
|
+
const { id, init } = binding.path.node;
|
|
3750
|
+
return isArrayPattern(id) && id.elements[0] === binding.identifier && isCallExpression(init) && isIdentifier(init.callee) && names.useState.has(init.callee.name) && binding.path.scope.getBinding(init.callee.name)?.path.isImportSpecifier() === true;
|
|
3751
|
+
}
|
|
3752
|
+
function isDerivedInitializer(declaration, scope, names, derived, pureImports) {
|
|
3753
|
+
const init = singlePath(declaration.get("init"));
|
|
3754
|
+
init.assertExpression();
|
|
3755
|
+
let safe = true;
|
|
3756
|
+
let reactive = false;
|
|
3757
|
+
const inspect = (path) => {
|
|
3758
|
+
if (!safe || path.isTSType()) {
|
|
3759
|
+
path.skip();
|
|
3760
|
+
return;
|
|
3761
|
+
}
|
|
3762
|
+
const node = path.node;
|
|
3763
|
+
if (path.isReferencedIdentifier()) {
|
|
3764
|
+
const binding = path.scope.getBinding(path.node.name);
|
|
3765
|
+
if (binding && (!binding.constant || binding.kind === "let" || binding.kind === "var" || binding.path.isVariableDeclarator() && (binding.path.node.start ?? -1) >= (declaration.node.start ?? Infinity))) {
|
|
3766
|
+
safe = false;
|
|
3767
|
+
return;
|
|
3768
|
+
}
|
|
3769
|
+
if (binding && derived.has(binding))
|
|
3770
|
+
reactive = true;
|
|
3771
|
+
if (isReactiveRead(node, scope))
|
|
3772
|
+
reactive = true;
|
|
3773
|
+
} else if (path.isMemberExpression() || path.isOptionalMemberExpression()) {
|
|
3774
|
+
if (isReactiveRead(node, scope))
|
|
3775
|
+
reactive = true;
|
|
3776
|
+
} else if (path.isCallExpression() || path.isOptionalCallExpression()) {
|
|
3777
|
+
if (!isPureDerivedCall(path, names, pureImports))
|
|
3778
|
+
safe = false;
|
|
3779
|
+
if (isGetterCall(path.node, scope))
|
|
3780
|
+
reactive = true;
|
|
3781
|
+
} else if (path.isSpreadElement()) {} else if (!(path.isIdentifier() || path.isLiteral() || path.isTemplateLiteral() || path.isTemplateElement() || path.isBinaryExpression() || path.isLogicalExpression() || path.isConditionalExpression() || path.isObjectExpression() || path.isObjectProperty() || path.isArrayExpression() || path.isParenthesizedExpression() || path.node.type === "ChainExpression" || path.isUnaryExpression() && path.node.operator !== "delete" || path.isTSAsExpression() || path.isTSSatisfiesExpression() || path.isTSNonNullExpression() || path.isTSTypeAssertion())) {
|
|
3782
|
+
safe = false;
|
|
3783
|
+
}
|
|
3784
|
+
};
|
|
3785
|
+
inspect(init);
|
|
3786
|
+
if (safe)
|
|
3787
|
+
init.traverse({ enter: inspect });
|
|
3788
|
+
return safe && reactive;
|
|
3789
|
+
}
|
|
3790
|
+
function promoteReactiveConstInitializers(ast, options) {
|
|
3791
|
+
if (!options.derivedLocals && !options.dev)
|
|
3792
|
+
return;
|
|
3793
|
+
const names = trackReactiveImports2(ast);
|
|
3794
|
+
const pureImports = collectPureImports(ast, options.filename, options.resolveImport);
|
|
3795
|
+
const { functions } = collectComponentFunctions2(ast, names.cc);
|
|
3796
|
+
traverse(ast, {
|
|
3797
|
+
Program(p) {
|
|
3798
|
+
p.scope.crawl();
|
|
3799
|
+
}
|
|
3800
|
+
});
|
|
3801
|
+
traverse(ast, {
|
|
3802
|
+
Function(fnPath) {
|
|
3803
|
+
if (!isFunction(fnPath.node) || !functions.has(fnPath.node))
|
|
3804
|
+
return;
|
|
3805
|
+
const factory = fnPath.parentPath;
|
|
3806
|
+
if (!factory || !factory.isCallExpression() || !isIdentifier(factory.node.callee) || factory.scope.getBinding(factory.node.callee.name)?.kind !== "module")
|
|
3807
|
+
return;
|
|
3808
|
+
const body = singlePath(fnPath.get("body"));
|
|
3809
|
+
if (!body.isBlockStatement())
|
|
3810
|
+
return;
|
|
3811
|
+
let hasDirectEval = false;
|
|
3812
|
+
fnPath.traverse({
|
|
3813
|
+
CallExpression(p) {
|
|
3814
|
+
if (isIdentifier(p.node.callee, { name: "eval" }) && !p.scope.getBinding("eval"))
|
|
3815
|
+
hasDirectEval = true;
|
|
3816
|
+
}
|
|
3817
|
+
});
|
|
3818
|
+
if (hasDirectEval)
|
|
3819
|
+
return;
|
|
3820
|
+
const scope = { bindings: new Map };
|
|
3821
|
+
trackPropBindings(scope, fnPath.node.params[0], getAllPropNames2(fnPath.node.params[0]));
|
|
3822
|
+
trackLocalScopeBindings(fnPath, names, scope, true);
|
|
3823
|
+
const derived = new Map;
|
|
3824
|
+
const statements = body.get("body");
|
|
3825
|
+
for (const statement of Array.isArray(statements) ? statements : [statements]) {
|
|
3826
|
+
if (!statement.isVariableDeclaration({ kind: "const" }) || hasSnapshotPragma(statement.node))
|
|
3827
|
+
continue;
|
|
3828
|
+
for (const declaration of (() => {
|
|
3829
|
+
const decls = statement.get("declarations");
|
|
3830
|
+
return Array.isArray(decls) ? decls : [decls];
|
|
3831
|
+
})()) {
|
|
3832
|
+
const { id, init } = declaration.node;
|
|
3833
|
+
if (!isIdentifier(id) || !init || hasSnapshotPragma(declaration.node) || hasSnapshotPragma(init))
|
|
3834
|
+
continue;
|
|
3835
|
+
const binding = declaration.scope.getBinding(id.name);
|
|
3836
|
+
if (!binding.constant || !binding.referenced || !binding.referencePaths.every(canRewriteDerivedReference))
|
|
3837
|
+
continue;
|
|
3838
|
+
if (isDerivedInitializer(declaration, scope, names, derived, pureImports)) {
|
|
3839
|
+
derived.set(binding, { declaration, binding });
|
|
3840
|
+
}
|
|
3841
|
+
}
|
|
3842
|
+
}
|
|
3843
|
+
for (const { declaration, binding } of derived.values()) {
|
|
3844
|
+
const { id, init } = declaration.node;
|
|
3845
|
+
if (!isIdentifier(id) || !isExpression(init))
|
|
3846
|
+
continue;
|
|
3847
|
+
const location = `${options.filename ?? "<module>"}:${id.loc?.start.line ?? 0}`;
|
|
3848
|
+
if (!options.derivedLocals) {
|
|
3849
|
+
if (binding.referencePaths.some((ref) => ref.findParent((p) => p.isJSXExpressionContainer()))) {
|
|
3850
|
+
console.warn(`[Sinwan] ${location}: derived local "${id.name}" snapshots a reactive read used in JSX. Enable derivedLocals or use a getter/computed; use /* sinwan-snapshot */ for an intentional snapshot.`);
|
|
3851
|
+
}
|
|
3852
|
+
continue;
|
|
3853
|
+
}
|
|
3854
|
+
const getter = arrowFunctionExpression([], init);
|
|
3855
|
+
if (isTSTypeAnnotation(id.typeAnnotation)) {
|
|
3856
|
+
getter.returnType = id.typeAnnotation;
|
|
3857
|
+
id.typeAnnotation = null;
|
|
3858
|
+
}
|
|
3859
|
+
declaration.node.init = getter;
|
|
3860
|
+
for (const ref of binding.referencePaths) {
|
|
3861
|
+
if (isTypePosition(ref))
|
|
3862
|
+
continue;
|
|
3863
|
+
if (options.dev && ref.parentPath?.isIfStatement() && ref.key === "test" && ref.getFunctionParent()?.node === fnPath.node) {
|
|
3864
|
+
console.warn(`[Sinwan] ${location}: derived local "${id.name}" in a setup if is read once, not a reactive branch. The compiler calls its getter to preserve boolean semantics; use Show for reactive control flow.`);
|
|
3865
|
+
}
|
|
3866
|
+
if (ref.parentPath?.isObjectProperty({ shorthand: true })) {
|
|
3867
|
+
ref.parentPath.node.shorthand = false;
|
|
3868
|
+
}
|
|
3869
|
+
ref.replaceWith(callExpression(identifier(id.name), []));
|
|
3870
|
+
}
|
|
3871
|
+
}
|
|
3872
|
+
fnPath.scope.crawl();
|
|
3873
|
+
}
|
|
3874
|
+
});
|
|
3875
|
+
}
|
|
3876
|
+
function wrapReactiveExpressions2(ast, options = {}) {
|
|
3877
|
+
promoteReactiveConstInitializers(ast, options);
|
|
3878
|
+
let metadata = null;
|
|
3879
|
+
if (options.analyzeMetadata) {
|
|
3880
|
+
metadata = options.analyzeMetadata;
|
|
3881
|
+
} else if (options.analyze && options.filename) {
|
|
3882
|
+
try {
|
|
3883
|
+
metadata = loadMetadata2(options.analyze);
|
|
3884
|
+
} catch {
|
|
3885
|
+
metadata = null;
|
|
3886
|
+
}
|
|
3887
|
+
}
|
|
3888
|
+
const absoluteFilename = options.filename ? path2.resolve(options.filename) : null;
|
|
3889
|
+
const resolveImport = options.resolveImport;
|
|
3890
|
+
const names = {
|
|
3891
|
+
createMutable: new Set,
|
|
3892
|
+
createStore: new Set,
|
|
3893
|
+
signal: new Set,
|
|
3894
|
+
computed: new Set,
|
|
3895
|
+
useState: new Set,
|
|
3896
|
+
useFetch: new Set,
|
|
3897
|
+
cc: new Set
|
|
3898
|
+
};
|
|
3899
|
+
const componentFunctions = new Set;
|
|
3900
|
+
const componentNames = new Map;
|
|
3901
|
+
const exported = new Set;
|
|
3902
|
+
const localScopes = new Map;
|
|
3903
|
+
const callGraph = new Map;
|
|
3904
|
+
const rawCallGraph = new Map;
|
|
3905
|
+
const importedReactiveProps = new Map;
|
|
3906
|
+
const exportNameByFn = new Map;
|
|
3907
|
+
let hasUnwrapImport = false;
|
|
3908
|
+
traverse(ast, {
|
|
3909
|
+
ImportDeclaration(p) {
|
|
3910
|
+
const source = moduleSourceValue(p.node.source);
|
|
3911
|
+
if (!source)
|
|
3912
|
+
return;
|
|
3913
|
+
const reactiveAllowed = REACTIVE_SOURCE_MODULES[source];
|
|
3914
|
+
const componentAllowed = COMPONENT_FACTORY_MODULES[source];
|
|
3915
|
+
if (reactiveAllowed || componentAllowed) {
|
|
3916
|
+
for (const spec of p.node.specifiers ?? []) {
|
|
3917
|
+
if (!isImportSpecifier(spec))
|
|
3918
|
+
continue;
|
|
3919
|
+
const imported = importedBindingName(spec.imported);
|
|
3920
|
+
if (imported && reactiveAllowed?.has(imported)) {
|
|
3921
|
+
addImportLocal(names, imported, spec.local.name);
|
|
3922
|
+
} else if (imported && componentAllowed?.has(imported)) {
|
|
3923
|
+
names.cc.add(spec.local.name);
|
|
3924
|
+
}
|
|
3925
|
+
}
|
|
3926
|
+
}
|
|
3927
|
+
if (source === "sinwan/reactivity") {
|
|
3928
|
+
for (const spec of p.node.specifiers ?? []) {
|
|
3929
|
+
if (isImportSpecifier(spec) && isIdentifier(spec.imported) && spec.imported.name === "unwrap" && spec.local.name === RESOLVE_HELPER) {
|
|
3930
|
+
hasUnwrapImport = true;
|
|
3931
|
+
}
|
|
3932
|
+
}
|
|
3933
|
+
}
|
|
3934
|
+
if (metadata && absoluteFilename && resolveImport && source.startsWith(".")) {
|
|
3935
|
+
const resolved = resolveImport(source, absoluteFilename);
|
|
3936
|
+
if (resolved) {
|
|
3937
|
+
const fileProps = metadata.get(resolved);
|
|
3938
|
+
if (fileProps) {
|
|
3939
|
+
for (const spec of p.node.specifiers ?? []) {
|
|
3940
|
+
if (!isImportSpecifier(spec))
|
|
3941
|
+
continue;
|
|
3942
|
+
const imported = importedBindingName(spec.imported);
|
|
3943
|
+
if (!imported || !/^[A-Z]/.test(imported))
|
|
3944
|
+
continue;
|
|
3945
|
+
const props = fileProps.get(imported);
|
|
3946
|
+
if (props && props.size > 0) {
|
|
3947
|
+
importedReactiveProps.set(spec.local.name, new Set(props));
|
|
3948
|
+
}
|
|
3949
|
+
}
|
|
3950
|
+
}
|
|
3951
|
+
}
|
|
3952
|
+
}
|
|
3953
|
+
},
|
|
3954
|
+
CallExpression(p) {
|
|
3955
|
+
const callee = p.node.callee;
|
|
3956
|
+
if (!isIdentifier(callee) || !names.cc.has(callee.name))
|
|
3957
|
+
return;
|
|
3958
|
+
const firstArg = p.node.arguments[0];
|
|
3959
|
+
if (!firstArg || !isFunction(firstArg))
|
|
3960
|
+
return;
|
|
3961
|
+
const componentFn = firstArg;
|
|
3962
|
+
componentFunctions.add(componentFn);
|
|
3963
|
+
const parent = p.parentPath;
|
|
3964
|
+
if (parent && parent.isVariableDeclarator && parent.isVariableDeclarator()) {
|
|
3965
|
+
const id = parent.node.id;
|
|
3966
|
+
if (isIdentifier(id)) {
|
|
3967
|
+
componentNames.set(id.name, componentFn);
|
|
3968
|
+
}
|
|
3969
|
+
}
|
|
3970
|
+
},
|
|
3971
|
+
ExportNamedDeclaration(p) {
|
|
3972
|
+
const declaration = p.node.declaration;
|
|
3973
|
+
if (!isVariableDeclaration(declaration))
|
|
3974
|
+
return;
|
|
3975
|
+
for (const decl of declaration.declarations) {
|
|
3976
|
+
const init = decl.init;
|
|
3977
|
+
if (!init || !isCallExpression(init))
|
|
3978
|
+
continue;
|
|
3979
|
+
const callee = init.callee;
|
|
3980
|
+
if (!isIdentifier(callee) || !names.cc.has(callee.name))
|
|
3981
|
+
continue;
|
|
3982
|
+
const firstArg = init.arguments[0];
|
|
3983
|
+
if (!firstArg || !isFunction(firstArg))
|
|
3984
|
+
continue;
|
|
3985
|
+
exported.add(firstArg);
|
|
3986
|
+
}
|
|
3987
|
+
},
|
|
3988
|
+
ExportDefaultDeclaration(p) {
|
|
3989
|
+
const decl = p.node.declaration;
|
|
3990
|
+
if (isIdentifier(decl)) {
|
|
3991
|
+
const fn = componentNames.get(decl.name);
|
|
3992
|
+
if (fn) {
|
|
3993
|
+
exported.add(fn);
|
|
3994
|
+
exportNameByFn.set(fn, "default");
|
|
3995
|
+
}
|
|
3996
|
+
} else if (isCallExpression(decl)) {
|
|
3997
|
+
const callee = decl.callee;
|
|
3998
|
+
if (isIdentifier(callee) && names.cc.has(callee.name)) {
|
|
3999
|
+
const firstArg = decl.arguments[0];
|
|
4000
|
+
if (firstArg && isFunction(firstArg)) {
|
|
4001
|
+
exported.add(firstArg);
|
|
4002
|
+
exportNameByFn.set(firstArg, "default");
|
|
4003
|
+
}
|
|
4004
|
+
}
|
|
4005
|
+
}
|
|
4006
|
+
},
|
|
4007
|
+
Function(p) {
|
|
4008
|
+
const scope = { bindings: new Map };
|
|
4009
|
+
if (options.derivedLocals && componentFunctions.has(p.node)) {
|
|
4010
|
+
trackPropBindings(scope, p.node.params[0], getAllPropNames2(p.node.params[0]));
|
|
4011
|
+
}
|
|
4012
|
+
trackLocalScopeBindings(p, names, scope, options.derivedLocals === true && componentFunctions.has(p.node));
|
|
4013
|
+
localScopes.set(p.node, scope);
|
|
4014
|
+
},
|
|
4015
|
+
JSXElement(p) {
|
|
4016
|
+
const name = p.node.openingElement.name;
|
|
4017
|
+
if (!isJSXIdentifier(name))
|
|
4018
|
+
return;
|
|
4019
|
+
const callerPath = p.findParent((pp) => pp.isFunction());
|
|
4020
|
+
const callerFn = callerPath ? callerPath.node : null;
|
|
4021
|
+
if (!callerFn)
|
|
4022
|
+
return;
|
|
4023
|
+
const props = [];
|
|
4024
|
+
const spreads = [];
|
|
4025
|
+
for (const attr of p.node.openingElement.attributes) {
|
|
4026
|
+
if (isJSXSpreadAttribute(attr)) {
|
|
4027
|
+
if (attr.argument) {
|
|
4028
|
+
spreads.push(attr.argument);
|
|
4029
|
+
}
|
|
4030
|
+
continue;
|
|
4031
|
+
}
|
|
4032
|
+
if (isJSXAttribute(attr)) {
|
|
4033
|
+
const attrName = attr.name.name;
|
|
4034
|
+
if (!attrName)
|
|
4035
|
+
continue;
|
|
4036
|
+
if (isJSXExpressionContainer(attr.value)) {
|
|
4037
|
+
const expr = attr.value.expression;
|
|
4038
|
+
if (!expr || isJSXEmptyExpression(expr))
|
|
4039
|
+
continue;
|
|
4040
|
+
props.push({ name: attrName, value: expr });
|
|
4041
|
+
}
|
|
4042
|
+
}
|
|
4043
|
+
}
|
|
4044
|
+
const childExprs = [];
|
|
4045
|
+
for (const child of p.node.children) {
|
|
4046
|
+
if (isJSXExpressionContainer(child)) {
|
|
4047
|
+
const expr = child.expression;
|
|
4048
|
+
if (!expr || isJSXEmptyExpression(expr))
|
|
4049
|
+
continue;
|
|
4050
|
+
childExprs.push(expr);
|
|
4051
|
+
}
|
|
4052
|
+
}
|
|
4053
|
+
if (childExprs.length === 1) {
|
|
4054
|
+
props.push({ name: "children", value: childExprs[0] });
|
|
4055
|
+
} else if (childExprs.length > 1) {
|
|
4056
|
+
props.push({ name: "children", value: arrayExpression(childExprs) });
|
|
4057
|
+
}
|
|
4058
|
+
const sites = rawCallGraph.get(callerFn) ?? [];
|
|
4059
|
+
sites.push({ calleeName: name.name, props, spreads });
|
|
4060
|
+
rawCallGraph.set(callerFn, sites);
|
|
4061
|
+
}
|
|
4062
|
+
});
|
|
4063
|
+
for (const [callerFn, sites] of rawCallGraph) {
|
|
4064
|
+
const resolved = [];
|
|
4065
|
+
for (const site of sites) {
|
|
4066
|
+
const callee = componentNames.get(site.calleeName);
|
|
4067
|
+
if (callee) {
|
|
4068
|
+
resolved.push({ callee, props: site.props, spreads: site.spreads });
|
|
4069
|
+
}
|
|
4070
|
+
}
|
|
4071
|
+
if (resolved.length > 0) {
|
|
4072
|
+
callGraph.set(callerFn, resolved);
|
|
4073
|
+
}
|
|
4074
|
+
}
|
|
4075
|
+
for (const [localName, fn] of componentNames) {
|
|
4076
|
+
if (exported.has(fn)) {
|
|
4077
|
+
if (!exportNameByFn.has(fn)) {
|
|
4078
|
+
exportNameByFn.set(fn, localName);
|
|
4079
|
+
}
|
|
4080
|
+
}
|
|
4081
|
+
}
|
|
4082
|
+
const hasAnyImport = Object.values(names).some((s) => s.size > 0);
|
|
4083
|
+
if (!hasAnyImport)
|
|
4084
|
+
return;
|
|
4085
|
+
const reactiveProps = propagateReactiveProps2(localScopes, callGraph, componentFunctions);
|
|
4086
|
+
let needsResolve = false;
|
|
4087
|
+
traverse(ast, {
|
|
4088
|
+
Function(path) {
|
|
4089
|
+
const scope = localScopes.get(path.node);
|
|
4090
|
+
if (!scope)
|
|
4091
|
+
return;
|
|
4092
|
+
const isComponent = componentFunctions.has(path.node);
|
|
4093
|
+
if (isComponent) {
|
|
4094
|
+
const isExported = exported.has(path.node);
|
|
4095
|
+
if (isExported) {
|
|
4096
|
+
let props = null;
|
|
4097
|
+
if (metadata && absoluteFilename) {
|
|
4098
|
+
const fileProps = metadata.get(absoluteFilename);
|
|
4099
|
+
const exportName = exportNameByFn.get(path.node);
|
|
4100
|
+
if (fileProps && exportName) {
|
|
4101
|
+
props = fileProps.get(exportName) ?? null;
|
|
4102
|
+
}
|
|
4103
|
+
}
|
|
4104
|
+
if (props) {
|
|
4105
|
+
trackPropBindings(scope, path.node.params[0], props);
|
|
4106
|
+
} else {
|
|
4107
|
+
const allProps = getAllPropNames2(path.node.params[0]);
|
|
4108
|
+
trackPropBindings(scope, path.node.params[0], allProps);
|
|
4109
|
+
}
|
|
4110
|
+
} else if (reactiveProps.has(path.node)) {
|
|
4111
|
+
const props = reactiveProps.get(path.node);
|
|
4112
|
+
trackPropBindings(scope, path.node.params[0], props);
|
|
4113
|
+
} else {
|
|
4114
|
+
const allProps = getAllPropNames2(path.node.params[0]);
|
|
4115
|
+
trackPropBindings(scope, path.node.params[0], allProps);
|
|
4116
|
+
}
|
|
4117
|
+
}
|
|
4118
|
+
path.traverse({
|
|
4119
|
+
JSXExpressionContainer(exprPath) {
|
|
4120
|
+
const expr = exprPath.node.expression;
|
|
4121
|
+
const compInfo = getComponentExpressionInfo(exprPath);
|
|
4122
|
+
if (compInfo.attributeName?.startsWith("on")) {
|
|
4123
|
+
return;
|
|
4124
|
+
}
|
|
4125
|
+
if (compInfo.isComponent && compInfo.attributeName !== null) {
|
|
4126
|
+
const knownProp = !!compInfo.componentName && isReactiveComponentProp(compInfo.componentName, compInfo.attributeName, componentNames, reactiveProps, importedReactiveProps);
|
|
4127
|
+
if (!knownProp && (!shouldWrap(expr, scope) || isReactiveContainerPassThrough(expr, scope))) {
|
|
4128
|
+
return;
|
|
4129
|
+
}
|
|
4130
|
+
}
|
|
4131
|
+
if (shouldWrap(expr, scope)) {
|
|
4132
|
+
const childrenGetter = isChildrenPropRead(expr, scope);
|
|
4133
|
+
if (transformPropMemberAccess(expr, scope)) {
|
|
4134
|
+
needsResolve = true;
|
|
4135
|
+
}
|
|
4136
|
+
exprPath.replaceWith(jsxExpressionContainer(createBindingDescriptor(expr, exprPath, options, scope, childrenGetter)));
|
|
4137
|
+
}
|
|
4138
|
+
}
|
|
4139
|
+
});
|
|
4140
|
+
}
|
|
4141
|
+
});
|
|
4142
|
+
if (needsResolve && !hasUnwrapImport) {
|
|
4143
|
+
ast.program.body.unshift(importDeclaration([
|
|
4144
|
+
importSpecifier(identifier(RESOLVE_HELPER), identifier("unwrap"))
|
|
4145
|
+
], stringLiteral("sinwan/reactivity")));
|
|
4146
|
+
}
|
|
4147
|
+
}
|
|
4148
|
+
|
|
4149
|
+
// src/cli.ts
|
|
4150
|
+
import * as path3 from "path";
|
|
4151
|
+
function runAnalyzeCli2(args = process.argv.slice(2)) {
|
|
4152
|
+
let command;
|
|
4153
|
+
let root;
|
|
4154
|
+
let outFile;
|
|
4155
|
+
let tsConfigPath;
|
|
4156
|
+
let bunfigPath;
|
|
4157
|
+
let workspaces;
|
|
4158
|
+
for (let i = 0;i < args.length; i++) {
|
|
4159
|
+
const arg = args[i];
|
|
4160
|
+
if (arg === "--tsconfig" || arg === "-t") {
|
|
4161
|
+
tsConfigPath = args[++i];
|
|
4162
|
+
} else if (arg === "--bunfig" || arg === "-b") {
|
|
4163
|
+
bunfigPath = args[++i];
|
|
4164
|
+
} else if (arg === "--workspaces" || arg === "-w") {
|
|
4165
|
+
workspaces = args[++i];
|
|
4166
|
+
} else if (command === undefined) {
|
|
4167
|
+
command = arg;
|
|
4168
|
+
} else if (root === undefined) {
|
|
4169
|
+
root = arg;
|
|
4170
|
+
} else if (outFile === undefined) {
|
|
4171
|
+
outFile = arg;
|
|
4172
|
+
}
|
|
4173
|
+
}
|
|
4174
|
+
if (command !== "analyze") {
|
|
4175
|
+
console.error("Unknown command. Use: sinwan analyze [root] [outFile] [--tsconfig path] [--bunfig path] [--workspaces path]");
|
|
4176
|
+
process.exit(1);
|
|
4177
|
+
}
|
|
4178
|
+
root = root ?? process.cwd();
|
|
4179
|
+
outFile = outFile ?? path3.join(root, ".sinwan/reactive-props.json");
|
|
4180
|
+
analyze2({ root, outFile, tsConfigPath, bunfigPath, workspaces });
|
|
4181
|
+
console.log(`[sinwan] analyzed ${root} -> ${outFile}`);
|
|
4182
|
+
}
|
|
4183
|
+
function runCliIfMain2(isMain = import.meta.main, args) {
|
|
4184
|
+
if (!isMain)
|
|
4185
|
+
return;
|
|
4186
|
+
runAnalyzeCli2(args);
|
|
4187
|
+
}
|
|
4188
|
+
runCliIfMain2();
|
|
4189
|
+
|
|
4190
|
+
export { identifier, stringLiteral, numericLiteral, callExpression, memberExpression, variableDeclarator, variableDeclaration, arrayExpression, objectExpression, objectProperty, importSpecifier, importDeclaration, jsxExpressionContainer, blockStatement, returnStatement, binaryExpression, conditionalExpression, unaryExpression, isNode, isIdentifier, isStringLiteral, isNumericLiteral, isBooleanLiteral, isNullLiteral, isTemplateLiteral, templateElementRaw, isParenthesizedExpression, unwrapParens, isObjectExpression, isObjectProperty, isObjectPattern, isRestElement, isAssignmentPattern, isBlockStatement, isArrowFunctionExpression, isFunction, isImportDeclaration, isImportSpecifier, isJSXIdentifier, isJSXOpeningElement, isJSXMemberExpression, isExpression, importedBindingName, moduleSourceValue, singlePath, traverse, autoWrapComponents2, parse, analyzeModule2, analyzeProject2, AnalyzerCache2, analyze2, loadMetadata2, trackReactiveImports2, getAllPropNames2, isReactiveValue2, containsReactiveValue2, collectComponentFunctions2, computeLocalScopes2, collectComponentCallGraph2, buildFullScope2, propagateReactiveProps2, wrapReactiveExpressions2, runAnalyzeCli2, runCliIfMain2 };
|