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.
@@ -1,2428 +0,0 @@
1
- // @bun
2
- // src/cli.ts
3
- import * as path3 from "path";
4
-
5
- // src/analyze.ts
6
- import { parse } from "@babel/parser";
7
- import * as t3 from "@babel/types";
8
- import _traverse3 from "@babel/traverse";
9
- import * as fs from "fs";
10
- import * as path2 from "path";
11
-
12
- // src/reactive-wrap.ts
13
- import * as t from "@babel/types";
14
- import _traverse from "@babel/traverse";
15
- import * as path from "path";
16
- var traverse = typeof _traverse === "function" ? _traverse : _traverse.default ?? _traverse;
17
- var REACTIVE_SOURCE_MODULES = {
18
- "sinwan/store": new Set(["createMutable", "createStore"]),
19
- "sinwan/reactivity": new Set(["signal", "computed"]),
20
- "sinwan/react": new Set(["useState"]),
21
- "sinwan/hook": new Set(["useFetch"])
22
- };
23
- var COMPONENT_FACTORY_MODULES = {
24
- sinwan: new Set(["cc"]),
25
- "sinwan/component": new Set(["cc"])
26
- };
27
- var BUILTIN_REACTIVE_PROPS = new Map([
28
- ["For", { reactiveProps: new Set(["each"]) }],
29
- ["Show", { reactiveProps: new Set(["when"]) }],
30
- ["Switch", { reactiveProps: new Set(["when"]) }],
31
- ["Match", { reactiveProps: new Set(["when"]) }],
32
- ["Index", { reactiveProps: new Set(["each"]) }],
33
- ["Key", { reactiveProps: new Set(["when"]) }],
34
- ["Dynamic", { reactiveProps: new Set(["component"]) }],
35
- ["Visible", { reactiveProps: new Set(["when", "style"]) }],
36
- ["Portal", { reactiveProps: new Set(["mount"]) }],
37
- ["Virtual", { reactiveProps: new Set(["each"]) }],
38
- ["Activity", { reactiveProps: new Set(["mode"]) }]
39
- ]);
40
- function isBuiltinReactiveProp(componentName, attrName) {
41
- const entry = BUILTIN_REACTIVE_PROPS.get(componentName);
42
- if (!entry)
43
- return false;
44
- return entry.reactiveProps.has(attrName);
45
- }
46
- function isReactiveContainerPassThrough(expr, scope) {
47
- let node = expr;
48
- while (t.isTSNonNullExpression(node)) {
49
- node = node.expression;
50
- }
51
- if (!t.isIdentifier(node))
52
- return false;
53
- const binding = scope.bindings.get(node.name);
54
- if (!binding)
55
- return false;
56
- return binding.kind === "mutable" || binding.kind === "prop";
57
- }
58
- function trackReactiveImports2(ast) {
59
- const names = {
60
- createMutable: new Set,
61
- createStore: new Set,
62
- signal: new Set,
63
- computed: new Set,
64
- useState: new Set,
65
- useFetch: new Set,
66
- cc: new Set
67
- };
68
- traverse(ast, {
69
- ImportDeclaration(path) {
70
- const source = path.node.source.value;
71
- const reactiveAllowed = REACTIVE_SOURCE_MODULES[source];
72
- const componentAllowed = COMPONENT_FACTORY_MODULES[source];
73
- if (!reactiveAllowed && !componentAllowed)
74
- return;
75
- for (const spec of path.node.specifiers) {
76
- if (!t.isImportSpecifier(spec))
77
- continue;
78
- const imported = t.isIdentifier(spec.imported) ? spec.imported.name : spec.imported.value;
79
- if (reactiveAllowed?.has(imported)) {
80
- const local = spec.local.name;
81
- names[imported].add(local);
82
- } else if (componentAllowed?.has(imported)) {
83
- names.cc.add(spec.local.name);
84
- }
85
- }
86
- }
87
- });
88
- return names;
89
- }
90
- function isReactiveSourceCall(expr, names) {
91
- if (!t.isCallExpression(expr))
92
- return null;
93
- const callee = expr.callee;
94
- if (!t.isIdentifier(callee))
95
- return null;
96
- if (names.createMutable.has(callee.name)) {
97
- return { kind: "mutable", isArray: false };
98
- }
99
- if (names.createStore.has(callee.name)) {
100
- return { kind: "mutable", isArray: true };
101
- }
102
- if (names.signal.has(callee.name)) {
103
- return { kind: "signal", isArray: false };
104
- }
105
- if (names.computed.has(callee.name)) {
106
- return { kind: "computed", isArray: false };
107
- }
108
- if (names.useState.has(callee.name)) {
109
- return { kind: "getter", isArray: true };
110
- }
111
- return null;
112
- }
113
- var USEFETCH_CHAIN_METHODS = new Set([
114
- "json",
115
- "text",
116
- "blob",
117
- "arrayBuffer",
118
- "formData",
119
- "get",
120
- "put",
121
- "post",
122
- "delete",
123
- "patch",
124
- "head",
125
- "options"
126
- ]);
127
- function isUseFetchCall(expr, names) {
128
- if (!t.isCallExpression(expr))
129
- return false;
130
- const callee = expr.callee;
131
- if (t.isIdentifier(callee)) {
132
- return names.useFetch.has(callee.name);
133
- }
134
- if (t.isMemberExpression(callee) && !callee.computed && t.isIdentifier(callee.property) && USEFETCH_CHAIN_METHODS.has(callee.property.name)) {
135
- return isUseFetchCall(callee.object, names);
136
- }
137
- return false;
138
- }
139
- function trackLocalScopeBindings(path, names, scope) {
140
- path.traverse({
141
- VariableDeclarator(p) {
142
- const id = p.node.id;
143
- const init = p.node.init;
144
- if (!init)
145
- return;
146
- if (t.isIdentifier(id) && t.isCallExpression(init) && t.isIdentifier(init.callee) && (init.callee.name === "_$createLiveRest" || init.callee.name === "createLiveRest")) {
147
- scope.bindings.set(id.name, { kind: "prop" });
148
- return;
149
- }
150
- const source = isReactiveSourceCall(init, names);
151
- if (source) {
152
- if (source.isArray) {
153
- if (t.isArrayPattern(id) && id.elements[0]) {
154
- const first = id.elements[0];
155
- if (t.isIdentifier(first)) {
156
- scope.bindings.set(first.name, { kind: source.kind });
157
- }
158
- }
159
- return;
160
- }
161
- if (t.isIdentifier(id)) {
162
- if (source.kind === "mutable") {
163
- scope.bindings.set(id.name, {
164
- kind: "mutable",
165
- root: id.name,
166
- path: []
167
- });
168
- } else {
169
- scope.bindings.set(id.name, { kind: source.kind });
170
- }
171
- }
172
- return;
173
- }
174
- if (isUseFetchCall(init, names)) {
175
- if (t.isObjectPattern(id)) {
176
- for (const prop of id.properties) {
177
- if (t.isObjectProperty(prop) && !prop.computed && t.isIdentifier(prop.value)) {
178
- scope.bindings.set(prop.value.name, {
179
- kind: "signal"
180
- });
181
- }
182
- }
183
- } else if (t.isIdentifier(id)) {
184
- scope.bindings.set(id.name, { kind: "signalObject" });
185
- }
186
- return;
187
- }
188
- }
189
- });
190
- path.traverse({
191
- VariableDeclarator(p) {
192
- const id = p.node.id;
193
- const init = p.node.init;
194
- if (!init || !t.isIdentifier(init))
195
- return;
196
- const initBinding = scope.bindings.get(init.name);
197
- if (!initBinding || initBinding.kind !== "mutable")
198
- return;
199
- if (t.isObjectPattern(id)) {
200
- for (const prop of id.properties) {
201
- if (!t.isObjectProperty(prop))
202
- continue;
203
- const key = prop.key;
204
- const value = prop.value;
205
- if (!t.isIdentifier(key) || !t.isIdentifier(value))
206
- continue;
207
- scope.bindings.set(value.name, {
208
- kind: "mutable",
209
- root: initBinding.root,
210
- path: [...initBinding.path, key.name]
211
- });
212
- }
213
- }
214
- }
215
- });
216
- path.traverse({
217
- "FunctionDeclaration|VariableDeclarator"(p) {
218
- const node = p.node;
219
- let fn = null;
220
- let name = null;
221
- if (t.isFunctionDeclaration(node)) {
222
- fn = node;
223
- name = node.id?.name ?? null;
224
- } else if (t.isVariableDeclarator(node)) {
225
- const init = node.init;
226
- if (init && (t.isArrowFunctionExpression(init) || t.isFunctionExpression(init))) {
227
- fn = init;
228
- }
229
- if (t.isIdentifier(node.id)) {
230
- name = node.id.name;
231
- }
232
- }
233
- if (!fn || !name || scope.bindings.has(name))
234
- return;
235
- const body = fn.body;
236
- if (body && containsReactiveRead(body, scope)) {
237
- scope.bindings.set(name, { kind: "getter" });
238
- } else {
239
- scope.bindings.set(name, { kind: "function" });
240
- }
241
- }
242
- });
243
- }
244
- function trackPropBindings(scope, params, reactiveProps) {
245
- if (!params || reactiveProps.size === 0)
246
- return;
247
- if (t.isIdentifier(params)) {
248
- scope.bindings.set(params.name, { kind: "prop" });
249
- } else if (t.isObjectPattern(params)) {
250
- for (const prop of params.properties) {
251
- if (t.isObjectProperty(prop) && t.isIdentifier(prop.value)) {
252
- const key = prop.key;
253
- if (t.isIdentifier(key) && reactiveProps.has(key.name)) {
254
- scope.bindings.set(prop.value.name, { kind: "prop" });
255
- }
256
- }
257
- }
258
- }
259
- }
260
- function getAllPropNames2(params) {
261
- if (!params)
262
- return new Set;
263
- if (t.isIdentifier(params))
264
- return new Set(["*"]);
265
- if (t.isObjectPattern(params)) {
266
- const names = new Set;
267
- for (const prop of params.properties) {
268
- if (t.isObjectProperty(prop) && t.isIdentifier(prop.key)) {
269
- names.add(prop.key.name);
270
- }
271
- }
272
- names.add("children");
273
- return names;
274
- }
275
- return new Set;
276
- }
277
- function resolveLocalObjectLiteral(fn, name) {
278
- const body = fn.body;
279
- if (!t.isBlockStatement(body))
280
- return null;
281
- for (const stmt of body.body) {
282
- if (!t.isVariableDeclaration(stmt))
283
- continue;
284
- for (const decl of stmt.declarations) {
285
- if (!t.isIdentifier(decl.id) || decl.id.name !== name)
286
- continue;
287
- const init = decl.init;
288
- if (!t.isObjectExpression(init))
289
- continue;
290
- return init;
291
- }
292
- }
293
- return null;
294
- }
295
- function analyzeObjectLiteralSpread(spread, fullScope, calleeReactive) {
296
- for (const prop of spread.properties) {
297
- if (t.isObjectProperty(prop) && !prop.computed) {
298
- const key = prop.key;
299
- const name = t.isIdentifier(key) ? key.name : t.isStringLiteral(key) ? key.value : null;
300
- if (name && containsReactiveValue2(prop.value, fullScope)) {
301
- calleeReactive.add(name);
302
- }
303
- }
304
- }
305
- }
306
- function isMemberLike(node) {
307
- return t.isMemberExpression(node) || t.isOptionalMemberExpression(node);
308
- }
309
- function isFunctionLike(node) {
310
- return t.isArrowFunctionExpression(node) || t.isFunctionExpression(node);
311
- }
312
- function isCallLike(node) {
313
- return t.isCallExpression(node) || t.isOptionalCallExpression(node);
314
- }
315
- var COMPILER_BINDING_CALLEES = new Set([
316
- "_$bindText",
317
- "_$bindAttr",
318
- "_$bindStyle",
319
- "_$bindClass",
320
- "_$unwrap"
321
- ]);
322
- function isCompilerBindingCall(node) {
323
- const callee = node.callee;
324
- return t.isIdentifier(callee) && COMPILER_BINDING_CALLEES.has(callee.name);
325
- }
326
- function visitCallCalleeAndArgs(node, visitExpr) {
327
- if (isCompilerBindingCall(node))
328
- return;
329
- visitExpr(node.callee, true);
330
- for (const arg of node.arguments) {
331
- if (t.isSpreadElement(arg)) {
332
- visitExpr(arg.argument, true);
333
- continue;
334
- }
335
- visitExpr(arg, true);
336
- }
337
- }
338
- function isSignalValuePath(path) {
339
- return path[0] === "value" || path.length >= 2 && path[1] === "value";
340
- }
341
- function getMemberExpressionRootAndPath(node) {
342
- const path = [];
343
- let current = node;
344
- while (true) {
345
- if (t.isTSNonNullExpression(current)) {
346
- current = current.expression;
347
- continue;
348
- }
349
- if (isMemberLike(current)) {
350
- if (current.computed)
351
- return null;
352
- if (!t.isIdentifier(current.property))
353
- return null;
354
- path.push(current.property.name);
355
- current = current.object;
356
- continue;
357
- }
358
- break;
359
- }
360
- if (t.isIdentifier(current)) {
361
- return { root: current.name, path: path.reverse() };
362
- }
363
- return null;
364
- }
365
- function isReactiveRead(node, scope) {
366
- if (t.isTSNonNullExpression(node)) {
367
- return isReactiveRead(node.expression, scope);
368
- }
369
- if (t.isIdentifier(node)) {
370
- const binding = scope.bindings.get(node.name);
371
- if (!binding)
372
- return false;
373
- if (binding.kind === "prop") {
374
- return true;
375
- }
376
- if (binding.kind === "mutable" && binding.path.length > 0) {
377
- return true;
378
- }
379
- return false;
380
- }
381
- if (!isMemberLike(node))
382
- return false;
383
- const rootPath = getMemberExpressionRootAndPath(node);
384
- if (!rootPath)
385
- return false;
386
- const binding = scope.bindings.get(rootPath.root);
387
- if (!binding) {
388
- return isSignalValuePath(rootPath.path);
389
- }
390
- if (binding.kind === "prop") {
391
- return true;
392
- }
393
- if (binding.kind === "mutable") {
394
- return true;
395
- }
396
- if (binding.kind === "signal" || binding.kind === "computed") {
397
- return rootPath.path[0] === "value";
398
- }
399
- if (binding.kind === "signalObject") {
400
- return rootPath.path.length >= 2 && rootPath.path[1] === "value";
401
- }
402
- return false;
403
- }
404
- function isGetterCall(node, scope) {
405
- const callee = node.callee;
406
- if (t.isIdentifier(callee)) {
407
- const binding = scope.bindings.get(callee.name);
408
- if (binding?.kind === "getter")
409
- return true;
410
- if (binding)
411
- return false;
412
- return node.arguments.length === 0;
413
- }
414
- if (isMemberLike(callee) && !callee.computed && t.isIdentifier(callee.property)) {
415
- if (node.arguments.length !== 0)
416
- return false;
417
- const rootPath = getMemberExpressionRootAndPath(callee);
418
- if (!rootPath)
419
- return false;
420
- const binding = scope.bindings.get(rootPath.root);
421
- if (binding?.kind === "mutable" || binding?.kind === "function" || binding?.kind === "signal" || binding?.kind === "computed" || binding?.kind === "prop" || binding?.kind === "getter" || binding?.kind === "signalObject") {
422
- return false;
423
- }
424
- return true;
425
- }
426
- return false;
427
- }
428
- function isReactiveValue2(node, scope) {
429
- if (t.isTSNonNullExpression(node)) {
430
- return isReactiveValue2(node.expression, scope);
431
- }
432
- if (t.isIdentifier(node)) {
433
- const binding = scope.bindings.get(node.name);
434
- if (!binding)
435
- return false;
436
- return binding.kind === "prop" || binding.kind === "signal" || binding.kind === "computed" || binding.kind === "mutable";
437
- }
438
- if (!isMemberLike(node))
439
- return false;
440
- const rootPath = getMemberExpressionRootAndPath(node);
441
- if (!rootPath)
442
- return false;
443
- const binding = scope.bindings.get(rootPath.root);
444
- if (!binding) {
445
- return isSignalValuePath(rootPath.path);
446
- }
447
- if (binding.kind === "prop" || binding.kind === "mutable") {
448
- return true;
449
- }
450
- if (binding.kind === "signal" || binding.kind === "computed") {
451
- return rootPath.path[0] === "value";
452
- }
453
- if (binding.kind === "signalObject") {
454
- return rootPath.path.length >= 2 && rootPath.path[1] === "value";
455
- }
456
- return false;
457
- }
458
- function containsReactiveValue2(expr, scope) {
459
- let found = false;
460
- function visit(node) {
461
- if (found)
462
- return;
463
- if (!node || typeof node !== "object")
464
- return;
465
- if (isMemberLike(node) || t.isTSNonNullExpression(node) || t.isIdentifier(node)) {
466
- if (isReactiveValue2(node, scope)) {
467
- found = true;
468
- return;
469
- }
470
- }
471
- if (isCallLike(node) && isGetterCall(node, scope)) {
472
- found = true;
473
- return;
474
- }
475
- if (t.isArrowFunctionExpression(node) || t.isFunctionExpression(node)) {
476
- return;
477
- }
478
- for (const key of Object.keys(node)) {
479
- if (key === "loc" || key === "start" || key === "end" || key === "leadingComments" || key === "trailingComments")
480
- continue;
481
- const value = node[key];
482
- if (Array.isArray(value)) {
483
- for (const item of value) {
484
- if (item && typeof item === "object")
485
- visit(item);
486
- }
487
- } else if (value && typeof value === "object") {
488
- visit(value);
489
- }
490
- }
491
- }
492
- visit(expr);
493
- return found;
494
- }
495
- function containsReactiveRead(expr, scope) {
496
- let found = false;
497
- function visit(node, enterFunction) {
498
- if (found)
499
- return;
500
- if (!node || typeof node !== "object")
501
- return;
502
- if (isFunctionLike(node)) {
503
- if (!enterFunction)
504
- return;
505
- for (const param of node.params) {
506
- visit(param, false);
507
- }
508
- visit(node.body, false);
509
- return;
510
- }
511
- if (t.isJSXElement(node) || t.isJSXFragment(node)) {
512
- return;
513
- }
514
- if (isMemberLike(node) || t.isTSNonNullExpression(node) || t.isIdentifier(node)) {
515
- if (isReactiveRead(node, scope)) {
516
- found = true;
517
- return;
518
- }
519
- }
520
- if (isCallLike(node) && isGetterCall(node, scope)) {
521
- found = true;
522
- return;
523
- }
524
- if (isCallLike(node)) {
525
- visitCallCalleeAndArgs(node, visit);
526
- return;
527
- }
528
- for (const key of Object.keys(node)) {
529
- if (key === "loc" || key === "start" || key === "end" || key === "leadingComments" || key === "trailingComments")
530
- continue;
531
- const value = node[key];
532
- if (Array.isArray(value)) {
533
- for (const item of value) {
534
- if (item && typeof item === "object")
535
- visit(item, false);
536
- }
537
- } else if (value && typeof value === "object") {
538
- visit(value, false);
539
- }
540
- }
541
- }
542
- visit(expr, false);
543
- return found;
544
- }
545
- function getComponentExpressionInfo(exprPath) {
546
- const parent = exprPath.parentPath;
547
- if (!parent) {
548
- return { isComponent: false, componentName: null, attributeName: null };
549
- }
550
- let elementPath = null;
551
- let attributeName = null;
552
- if (parent.isJSXAttribute && parent.isJSXAttribute()) {
553
- const attrName = parent.node.name;
554
- if (t.isJSXIdentifier(attrName)) {
555
- attributeName = attrName.name;
556
- }
557
- const openingElementPath = parent.parentPath;
558
- elementPath = openingElementPath?.parentPath;
559
- } else if (parent.isJSXElement && parent.isJSXElement()) {
560
- elementPath = parent;
561
- }
562
- if (!elementPath || !elementPath.isJSXElement || !elementPath.isJSXElement()) {
563
- return { isComponent: false, componentName: null, attributeName: null };
564
- }
565
- const name = elementPath.node.openingElement.name;
566
- let firstChar = null;
567
- let componentName = null;
568
- if (t.isJSXIdentifier(name)) {
569
- componentName = name.name;
570
- firstChar = name.name?.[0] ?? null;
571
- } else if (t.isJSXMemberExpression(name)) {
572
- let current = name;
573
- while (t.isJSXMemberExpression(current)) {
574
- current = current.object;
575
- }
576
- if (t.isJSXIdentifier(current)) {
577
- componentName = current.name;
578
- firstChar = current.name?.[0] ?? null;
579
- }
580
- }
581
- if (!firstChar || firstChar !== firstChar.toUpperCase() || !/[A-Z]/.test(firstChar)) {
582
- return { isComponent: false, componentName: null, attributeName };
583
- }
584
- return { isComponent: true, componentName, attributeName };
585
- }
586
- function isReactiveComponentProp(componentName, attributeName, componentNames, reactiveProps, importedReactiveProps) {
587
- if (isBuiltinReactiveProp(componentName, attributeName)) {
588
- return true;
589
- }
590
- const componentFn = componentNames.get(componentName);
591
- if (componentFn) {
592
- const props = reactiveProps.get(componentFn);
593
- if (!props)
594
- return false;
595
- return props.has(attributeName);
596
- }
597
- const importedProps = importedReactiveProps.get(componentName);
598
- if (!importedProps)
599
- return false;
600
- return importedProps.has(attributeName);
601
- }
602
- function shouldWrap(expr, scope) {
603
- if (t.isArrowFunctionExpression(expr) || t.isFunctionExpression(expr))
604
- return false;
605
- if (t.isJSXEmptyExpression(expr))
606
- return false;
607
- return containsReactiveRead(expr, scope);
608
- }
609
- var RESOLVE_HELPER = "_$unwrap";
610
- function transformPropMemberAccess(expr, scope) {
611
- let changed = false;
612
- function resolveRoot(rootName) {
613
- return t.callExpression(t.identifier(RESOLVE_HELPER), [
614
- t.identifier(rootName)
615
- ]);
616
- }
617
- function replaceMemberRoot(node, rootName) {
618
- let current = node;
619
- let parent = null;
620
- while (true) {
621
- if (t.isTSNonNullExpression(current)) {
622
- current = current.expression;
623
- continue;
624
- }
625
- if (!isMemberLike(current))
626
- break;
627
- parent = current;
628
- current = current.object;
629
- }
630
- if (parent && t.isIdentifier(current) && current.name === rootName) {
631
- parent.object = resolveRoot(rootName);
632
- }
633
- }
634
- function visit(node, enterFunction) {
635
- if (!node || typeof node !== "object")
636
- return;
637
- if (isFunctionLike(node)) {
638
- if (!enterFunction)
639
- return;
640
- for (const param of node.params) {
641
- visit(param, false);
642
- }
643
- visit(node.body, false);
644
- return;
645
- }
646
- if (t.isJSXElement(node) || t.isJSXFragment(node)) {
647
- return;
648
- }
649
- if (isMemberLike(node)) {
650
- const rootPath = getMemberExpressionRootAndPath(node);
651
- if (rootPath) {
652
- const binding = scope.bindings.get(rootPath.root);
653
- if (binding && binding.kind === "prop") {
654
- replaceMemberRoot(node, rootPath.root);
655
- changed = true;
656
- return;
657
- }
658
- }
659
- return;
660
- }
661
- if (isCallLike(node)) {
662
- visitCallCalleeAndArgs(node, visit);
663
- return;
664
- }
665
- for (const key of Object.keys(node)) {
666
- if (key === "loc" || key === "start" || key === "end" || key === "leadingComments" || key === "trailingComments")
667
- continue;
668
- const value = node[key];
669
- if (Array.isArray(value)) {
670
- for (const item of value) {
671
- if (item && typeof item === "object")
672
- visit(item, false);
673
- }
674
- } else if (value && typeof value === "object") {
675
- visit(value, false);
676
- }
677
- }
678
- }
679
- visit(expr, false);
680
- return changed;
681
- }
682
- function wrapExpression(expr) {
683
- return t.arrowFunctionExpression([], expr);
684
- }
685
- function isPropRoot(expr, scope) {
686
- if (t.isIdentifier(expr)) {
687
- return scope.bindings.get(expr.name)?.kind === "prop";
688
- }
689
- if (t.isCallExpression(expr) && t.isIdentifier(expr.callee, { name: "_$unwrap" }) && expr.arguments.length === 1 && t.isIdentifier(expr.arguments[0])) {
690
- return scope.bindings.get(expr.arguments[0].name)?.kind === "prop";
691
- }
692
- return false;
693
- }
694
- function isChildrenPropRead(expr, scope) {
695
- if (t.isIdentifier(expr, { name: "children" }))
696
- return true;
697
- if (!isMemberLike(expr) || expr.computed)
698
- return false;
699
- if (!t.isIdentifier(expr.property, { name: "children" }))
700
- return false;
701
- return isPropRoot(expr.object, scope);
702
- }
703
- function createBindingDescriptor(expr, exprPath, options, scope, childrenGetter) {
704
- if (!options.explicitBindings) {
705
- return wrapExpression(expr);
706
- }
707
- const parent = exprPath.parentPath;
708
- if (parent && parent.isJSXAttribute && parent.isJSXAttribute()) {
709
- const compInfo = getComponentExpressionInfo(exprPath);
710
- if (compInfo.isComponent) {
711
- return wrapExpression(expr);
712
- }
713
- const attrName = parent.node.name.name;
714
- if (attrName === "style") {
715
- return t.callExpression(t.identifier("_$bindStyle"), [
716
- wrapExpression(expr)
717
- ]);
718
- }
719
- if (attrName === "class") {
720
- return t.callExpression(t.identifier("_$bindClass"), [
721
- wrapExpression(expr)
722
- ]);
723
- }
724
- return t.callExpression(t.identifier("_$bindAttr"), [
725
- t.stringLiteral(attrName),
726
- wrapExpression(expr)
727
- ]);
728
- }
729
- const compInfo = getComponentExpressionInfo(exprPath);
730
- if (isChildrenPropRead(expr, scope) || childrenGetter || compInfo.isComponent && compInfo.attributeName === null) {
731
- return wrapExpression(expr);
732
- }
733
- return t.callExpression(t.identifier("_$bindText"), [wrapExpression(expr)]);
734
- }
735
- function collectComponentFunctions2(ast, ccNames) {
736
- const functions = new Set;
737
- const names = new Map;
738
- const exported = new Set;
739
- if (ccNames.size === 0)
740
- return { functions, names, exported };
741
- traverse(ast, {
742
- CallExpression(path) {
743
- const callee = path.node.callee;
744
- if (!t.isIdentifier(callee) || !ccNames.has(callee.name))
745
- return;
746
- const firstArg = path.node.arguments[0];
747
- if (!firstArg || !t.isFunction(firstArg))
748
- return;
749
- const componentFn = firstArg;
750
- functions.add(componentFn);
751
- const parent = path.parentPath;
752
- if (parent && parent.isVariableDeclarator && parent.isVariableDeclarator()) {
753
- const id = parent.node.id;
754
- if (t.isIdentifier(id)) {
755
- names.set(id.name, componentFn);
756
- }
757
- }
758
- }
759
- });
760
- traverse(ast, {
761
- ExportNamedDeclaration(path) {
762
- const declaration = path.node.declaration;
763
- if (!t.isVariableDeclaration(declaration))
764
- return;
765
- for (const decl of declaration.declarations) {
766
- const init = decl.init;
767
- if (!init || !t.isCallExpression(init))
768
- continue;
769
- const callee = init.callee;
770
- if (!t.isIdentifier(callee) || !ccNames.has(callee.name))
771
- continue;
772
- const firstArg = init.arguments[0];
773
- if (!firstArg || !t.isFunction(firstArg))
774
- continue;
775
- exported.add(firstArg);
776
- }
777
- },
778
- ExportDefaultDeclaration(path) {
779
- const decl = path.node.declaration;
780
- if (t.isIdentifier(decl)) {
781
- const componentFn = names.get(decl.name);
782
- if (componentFn) {
783
- exported.add(componentFn);
784
- }
785
- } else if (t.isCallExpression(decl)) {
786
- const callee = decl.callee;
787
- if (t.isIdentifier(callee) && ccNames.has(callee.name)) {
788
- const firstArg = decl.arguments[0];
789
- if (firstArg && t.isFunction(firstArg)) {
790
- exported.add(firstArg);
791
- }
792
- }
793
- }
794
- }
795
- });
796
- return { functions, names, exported };
797
- }
798
- function computeLocalScopes2(ast, names) {
799
- const scopes = new Map;
800
- traverse(ast, {
801
- Function(path) {
802
- const scope = { bindings: new Map };
803
- trackLocalScopeBindings(path, names, scope);
804
- scopes.set(path.node, scope);
805
- }
806
- });
807
- return scopes;
808
- }
809
- function collectComponentCallGraph2(ast, componentNames) {
810
- const callGraph = new Map;
811
- if (componentNames.size === 0)
812
- return callGraph;
813
- traverse(ast, {
814
- JSXElement(path) {
815
- const name = path.node.openingElement.name;
816
- if (!t.isJSXIdentifier(name))
817
- return;
818
- const callee = componentNames.get(name.name);
819
- if (!callee)
820
- return;
821
- const callerPath = path.findParent((p) => p.isFunction());
822
- const callerFn = callerPath ? callerPath.node : null;
823
- if (!callerFn)
824
- return;
825
- const props = [];
826
- const spreads = [];
827
- for (const attr of path.node.openingElement.attributes) {
828
- if (t.isJSXSpreadAttribute(attr)) {
829
- if (attr.argument) {
830
- spreads.push(attr.argument);
831
- }
832
- continue;
833
- }
834
- if (t.isJSXAttribute(attr)) {
835
- const attrName = attr.name.name;
836
- if (!attrName)
837
- continue;
838
- if (t.isJSXExpressionContainer(attr.value)) {
839
- const expr = attr.value.expression;
840
- if (!expr || t.isJSXEmptyExpression(expr))
841
- continue;
842
- props.push({ name: attrName, value: expr });
843
- }
844
- }
845
- }
846
- const childExprs = [];
847
- for (const child of path.node.children) {
848
- if (t.isJSXExpressionContainer(child)) {
849
- const expr = child.expression;
850
- if (!expr || t.isJSXEmptyExpression(expr))
851
- continue;
852
- childExprs.push(expr);
853
- }
854
- }
855
- if (childExprs.length === 1) {
856
- props.push({ name: "children", value: childExprs[0] });
857
- } else if (childExprs.length > 1) {
858
- props.push({ name: "children", value: t.arrayExpression(childExprs) });
859
- }
860
- const sites = callGraph.get(callerFn) ?? [];
861
- sites.push({ callee, props, spreads });
862
- callGraph.set(callerFn, sites);
863
- }
864
- });
865
- return callGraph;
866
- }
867
- function buildFullScope2(localScopes, fn, reactiveProps) {
868
- const localScope = localScopes.get(fn);
869
- if (!localScope)
870
- return null;
871
- const fullScope = { bindings: new Map(localScope.bindings) };
872
- trackPropBindings(fullScope, fn.params[0], reactiveProps);
873
- return fullScope;
874
- }
875
- function propagateReactiveProps2(localScopes, callGraph, componentFunctions) {
876
- const reactiveProps = new Map;
877
- for (const [callerFn, sites] of callGraph) {
878
- const localScope = localScopes.get(callerFn);
879
- if (!localScope)
880
- continue;
881
- for (const site of sites) {
882
- const calleeReactive = reactiveProps.get(site.callee) ?? new Set;
883
- for (const prop of site.props) {
884
- if (containsReactiveValue2(prop.value, localScope)) {
885
- calleeReactive.add(prop.name);
886
- }
887
- }
888
- reactiveProps.set(site.callee, calleeReactive);
889
- }
890
- }
891
- const worklist = new Set(componentFunctions);
892
- while (worklist.size > 0) {
893
- const callerFn = worklist.values().next().value;
894
- worklist.delete(callerFn);
895
- const callerReactive = reactiveProps.get(callerFn) ?? new Set;
896
- const fullScope = buildFullScope2(localScopes, callerFn, callerReactive);
897
- if (!fullScope)
898
- continue;
899
- const sites = callGraph.get(callerFn) ?? [];
900
- for (const site of sites) {
901
- const calleeReactive = reactiveProps.get(site.callee) ?? new Set;
902
- const initialSize = calleeReactive.size;
903
- for (const prop of site.props) {
904
- if (containsReactiveValue2(prop.value, fullScope)) {
905
- calleeReactive.add(prop.name);
906
- }
907
- }
908
- for (const spread of site.spreads) {
909
- const resolved = t.isObjectExpression(spread) ? spread : t.isIdentifier(spread) ? resolveLocalObjectLiteral(callerFn, spread.name) : null;
910
- if (resolved) {
911
- analyzeObjectLiteralSpread(resolved, fullScope, calleeReactive);
912
- } else {
913
- const allCalleeProps = getAllPropNames2(site.callee.params[0]);
914
- for (const propName of allCalleeProps) {
915
- calleeReactive.add(propName);
916
- }
917
- }
918
- }
919
- if (calleeReactive.size > initialSize) {
920
- reactiveProps.set(site.callee, calleeReactive);
921
- worklist.add(site.callee);
922
- }
923
- }
924
- }
925
- return reactiveProps;
926
- }
927
- function wrapReactiveExpressions2(ast, options = {}) {
928
- let metadata = null;
929
- if (options.analyzeMetadata) {
930
- metadata = options.analyzeMetadata;
931
- } else if (options.analyze && options.filename) {
932
- try {
933
- metadata = loadMetadata2(options.analyze);
934
- } catch {
935
- metadata = null;
936
- }
937
- }
938
- const absoluteFilename = options.filename ? path.resolve(options.filename) : null;
939
- const resolveImport = options.resolveImport;
940
- const names = {
941
- createMutable: new Set,
942
- createStore: new Set,
943
- signal: new Set,
944
- computed: new Set,
945
- useState: new Set,
946
- useFetch: new Set,
947
- cc: new Set
948
- };
949
- const componentFunctions = new Set;
950
- const componentNames = new Map;
951
- const exported = new Set;
952
- const localScopes = new Map;
953
- const callGraph = new Map;
954
- const rawCallGraph = new Map;
955
- const importedReactiveProps = new Map;
956
- const exportNameByFn = new Map;
957
- let hasUnwrapImport = false;
958
- traverse(ast, {
959
- ImportDeclaration(p) {
960
- const source = p.node.source.value;
961
- const reactiveAllowed = REACTIVE_SOURCE_MODULES[source];
962
- const componentAllowed = COMPONENT_FACTORY_MODULES[source];
963
- if (reactiveAllowed || componentAllowed) {
964
- for (const spec of p.node.specifiers) {
965
- if (!t.isImportSpecifier(spec))
966
- continue;
967
- const imported = t.isIdentifier(spec.imported) ? spec.imported.name : spec.imported.value;
968
- if (reactiveAllowed?.has(imported)) {
969
- names[imported].add(spec.local.name);
970
- } else if (componentAllowed?.has(imported)) {
971
- names.cc.add(spec.local.name);
972
- }
973
- }
974
- }
975
- if (source === "sinwan/reactivity") {
976
- for (const spec of p.node.specifiers) {
977
- if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported) && spec.imported.name === "unwrap" && spec.local.name === RESOLVE_HELPER) {
978
- hasUnwrapImport = true;
979
- }
980
- }
981
- }
982
- if (metadata && absoluteFilename && resolveImport && source.startsWith(".")) {
983
- const resolved = resolveImport(source, absoluteFilename);
984
- if (resolved) {
985
- const fileProps = metadata.get(resolved);
986
- if (fileProps) {
987
- for (const spec of p.node.specifiers) {
988
- if (!t.isImportSpecifier(spec))
989
- continue;
990
- const imported = t.isIdentifier(spec.imported) ? spec.imported.name : spec.imported.value;
991
- if (!imported || !/^[A-Z]/.test(imported))
992
- continue;
993
- const props = fileProps.get(imported);
994
- if (props && props.size > 0) {
995
- importedReactiveProps.set(spec.local.name, new Set(props));
996
- }
997
- }
998
- }
999
- }
1000
- }
1001
- },
1002
- CallExpression(p) {
1003
- const callee = p.node.callee;
1004
- if (!t.isIdentifier(callee) || !names.cc.has(callee.name))
1005
- return;
1006
- const firstArg = p.node.arguments[0];
1007
- if (!firstArg || !t.isFunction(firstArg))
1008
- return;
1009
- const componentFn = firstArg;
1010
- componentFunctions.add(componentFn);
1011
- const parent = p.parentPath;
1012
- if (parent && parent.isVariableDeclarator && parent.isVariableDeclarator()) {
1013
- const id = parent.node.id;
1014
- if (t.isIdentifier(id)) {
1015
- componentNames.set(id.name, componentFn);
1016
- }
1017
- }
1018
- },
1019
- ExportNamedDeclaration(p) {
1020
- const declaration = p.node.declaration;
1021
- if (!t.isVariableDeclaration(declaration))
1022
- return;
1023
- for (const decl of declaration.declarations) {
1024
- const init = decl.init;
1025
- if (!init || !t.isCallExpression(init))
1026
- continue;
1027
- const callee = init.callee;
1028
- if (!t.isIdentifier(callee) || !names.cc.has(callee.name))
1029
- continue;
1030
- const firstArg = init.arguments[0];
1031
- if (!firstArg || !t.isFunction(firstArg))
1032
- continue;
1033
- exported.add(firstArg);
1034
- }
1035
- },
1036
- ExportDefaultDeclaration(p) {
1037
- const decl = p.node.declaration;
1038
- if (t.isIdentifier(decl)) {
1039
- const fn = componentNames.get(decl.name);
1040
- if (fn) {
1041
- exported.add(fn);
1042
- exportNameByFn.set(fn, "default");
1043
- }
1044
- } else if (t.isCallExpression(decl)) {
1045
- const callee = decl.callee;
1046
- if (t.isIdentifier(callee) && names.cc.has(callee.name)) {
1047
- const firstArg = decl.arguments[0];
1048
- if (firstArg && t.isFunction(firstArg)) {
1049
- exported.add(firstArg);
1050
- exportNameByFn.set(firstArg, "default");
1051
- }
1052
- }
1053
- }
1054
- },
1055
- Function(p) {
1056
- const scope = { bindings: new Map };
1057
- trackLocalScopeBindings(p, names, scope);
1058
- localScopes.set(p.node, scope);
1059
- },
1060
- JSXElement(p) {
1061
- const name = p.node.openingElement.name;
1062
- if (!t.isJSXIdentifier(name))
1063
- return;
1064
- const callerPath = p.findParent((pp) => pp.isFunction());
1065
- const callerFn = callerPath ? callerPath.node : null;
1066
- if (!callerFn)
1067
- return;
1068
- const props = [];
1069
- const spreads = [];
1070
- for (const attr of p.node.openingElement.attributes) {
1071
- if (t.isJSXSpreadAttribute(attr)) {
1072
- if (attr.argument) {
1073
- spreads.push(attr.argument);
1074
- }
1075
- continue;
1076
- }
1077
- if (t.isJSXAttribute(attr)) {
1078
- const attrName = attr.name.name;
1079
- if (!attrName)
1080
- continue;
1081
- if (t.isJSXExpressionContainer(attr.value)) {
1082
- const expr = attr.value.expression;
1083
- if (!expr || t.isJSXEmptyExpression(expr))
1084
- continue;
1085
- props.push({ name: attrName, value: expr });
1086
- }
1087
- }
1088
- }
1089
- const childExprs = [];
1090
- for (const child of p.node.children) {
1091
- if (t.isJSXExpressionContainer(child)) {
1092
- const expr = child.expression;
1093
- if (!expr || t.isJSXEmptyExpression(expr))
1094
- continue;
1095
- childExprs.push(expr);
1096
- }
1097
- }
1098
- if (childExprs.length === 1) {
1099
- props.push({ name: "children", value: childExprs[0] });
1100
- } else if (childExprs.length > 1) {
1101
- props.push({ name: "children", value: t.arrayExpression(childExprs) });
1102
- }
1103
- const sites = rawCallGraph.get(callerFn) ?? [];
1104
- sites.push({ calleeName: name.name, props, spreads });
1105
- rawCallGraph.set(callerFn, sites);
1106
- }
1107
- });
1108
- for (const [callerFn, sites] of rawCallGraph) {
1109
- const resolved = [];
1110
- for (const site of sites) {
1111
- const callee = componentNames.get(site.calleeName);
1112
- if (callee) {
1113
- resolved.push({ callee, props: site.props, spreads: site.spreads });
1114
- }
1115
- }
1116
- if (resolved.length > 0) {
1117
- callGraph.set(callerFn, resolved);
1118
- }
1119
- }
1120
- for (const [localName, fn] of componentNames) {
1121
- if (exported.has(fn)) {
1122
- if (!exportNameByFn.has(fn)) {
1123
- exportNameByFn.set(fn, localName);
1124
- }
1125
- }
1126
- }
1127
- const hasAnyImport = Object.values(names).some((s) => s.size > 0);
1128
- if (!hasAnyImport)
1129
- return;
1130
- const reactiveProps = propagateReactiveProps2(localScopes, callGraph, componentFunctions);
1131
- let needsResolve = false;
1132
- traverse(ast, {
1133
- Function(path) {
1134
- const scope = localScopes.get(path.node);
1135
- if (!scope)
1136
- return;
1137
- const isComponent = componentFunctions.has(path.node);
1138
- if (isComponent) {
1139
- const isExported = exported.has(path.node);
1140
- if (isExported) {
1141
- let props = null;
1142
- if (metadata && absoluteFilename) {
1143
- const fileProps = metadata.get(absoluteFilename);
1144
- const exportName = exportNameByFn.get(path.node);
1145
- if (fileProps && exportName) {
1146
- props = fileProps.get(exportName) ?? null;
1147
- }
1148
- }
1149
- if (props) {
1150
- trackPropBindings(scope, path.node.params[0], props);
1151
- } else {
1152
- const allProps = getAllPropNames2(path.node.params[0]);
1153
- trackPropBindings(scope, path.node.params[0], allProps);
1154
- }
1155
- } else if (reactiveProps.has(path.node)) {
1156
- const props = reactiveProps.get(path.node);
1157
- trackPropBindings(scope, path.node.params[0], props);
1158
- } else {
1159
- const allProps = getAllPropNames2(path.node.params[0]);
1160
- trackPropBindings(scope, path.node.params[0], allProps);
1161
- }
1162
- }
1163
- path.traverse({
1164
- JSXExpressionContainer(exprPath) {
1165
- const expr = exprPath.node.expression;
1166
- const compInfo = getComponentExpressionInfo(exprPath);
1167
- if (compInfo.attributeName?.startsWith("on")) {
1168
- return;
1169
- }
1170
- if (compInfo.isComponent && compInfo.attributeName !== null) {
1171
- const knownProp = !!compInfo.componentName && isReactiveComponentProp(compInfo.componentName, compInfo.attributeName, componentNames, reactiveProps, importedReactiveProps);
1172
- if (!knownProp && (!shouldWrap(expr, scope) || isReactiveContainerPassThrough(expr, scope))) {
1173
- return;
1174
- }
1175
- }
1176
- if (shouldWrap(expr, scope)) {
1177
- const childrenGetter = isChildrenPropRead(expr, scope);
1178
- if (transformPropMemberAccess(expr, scope)) {
1179
- needsResolve = true;
1180
- }
1181
- exprPath.replaceWith(t.jsxExpressionContainer(createBindingDescriptor(expr, exprPath, options, scope, childrenGetter)));
1182
- }
1183
- }
1184
- });
1185
- }
1186
- });
1187
- if (needsResolve && !hasUnwrapImport) {
1188
- ast.program.body.unshift(t.importDeclaration([
1189
- t.importSpecifier(t.identifier(RESOLVE_HELPER), t.identifier("unwrap"))
1190
- ], t.stringLiteral("sinwan/reactivity")));
1191
- }
1192
- }
1193
-
1194
- // src/auto-cc.ts
1195
- import * as t2 from "@babel/types";
1196
- import _traverse2 from "@babel/traverse";
1197
- var traverse2 = typeof _traverse2 === "function" ? _traverse2 : _traverse2.default ?? _traverse2;
1198
- var CC_SOURCE_MODULES = new Set(["sinwan", "sinwan/component"]);
1199
- var EXCLUDE_KEYS = new Set([
1200
- "loc",
1201
- "start",
1202
- "end",
1203
- "type",
1204
- "leadingComments",
1205
- "trailingComments",
1206
- "innerComments",
1207
- "extra"
1208
- ]);
1209
- function isComponentName(name) {
1210
- return !!name && /^[A-Z]/.test(name);
1211
- }
1212
- function containsJsx(node, enterFunctions) {
1213
- if (!node || typeof node !== "object")
1214
- return false;
1215
- if (t2.isJSXElement(node) || t2.isJSXFragment(node))
1216
- return true;
1217
- if (!enterFunctions && (t2.isFunctionExpression(node) || t2.isArrowFunctionExpression(node) || t2.isFunctionDeclaration(node))) {
1218
- return false;
1219
- }
1220
- for (const key of Object.keys(node)) {
1221
- if (EXCLUDE_KEYS.has(key))
1222
- continue;
1223
- const val = node[key];
1224
- if (Array.isArray(val)) {
1225
- const foundInArray = val.some((item) => Boolean(item) && typeof item === "object" && containsJsx(item, enterFunctions));
1226
- if (foundInArray)
1227
- return true;
1228
- } else if (val && typeof val === "object" && containsJsx(val, enterFunctions)) {
1229
- return true;
1230
- }
1231
- }
1232
- return false;
1233
- }
1234
- function functionReturnsJsx(fnPath) {
1235
- const fn = fnPath.node;
1236
- if (t2.isArrowFunctionExpression(fn) && !t2.isBlockStatement(fn.body)) {
1237
- return containsJsx(fn.body, false);
1238
- }
1239
- let found = false;
1240
- fnPath.traverse({
1241
- ReturnStatement(p) {
1242
- if (p.node.argument && containsJsx(p.node.argument, false)) {
1243
- found = true;
1244
- p.stop();
1245
- }
1246
- },
1247
- Function(p) {
1248
- p.skip();
1249
- }
1250
- });
1251
- return found;
1252
- }
1253
- function isComponentArity(fn) {
1254
- if (t2.isFunctionDeclaration(fn) && fn.generator)
1255
- return false;
1256
- if ((t2.isFunctionExpression(fn) || t2.isArrowFunctionExpression(fn)) && fn.generator) {
1257
- return false;
1258
- }
1259
- return fn.params.length <= 1;
1260
- }
1261
- function collectCcAliases(ast) {
1262
- const aliases = new Set;
1263
- traverse2(ast, {
1264
- ImportDeclaration(path) {
1265
- const source = path.node.source.value;
1266
- if (!CC_SOURCE_MODULES.has(source))
1267
- return;
1268
- for (const spec of path.node.specifiers) {
1269
- if (!t2.isImportSpecifier(spec))
1270
- continue;
1271
- const imported = t2.isIdentifier(spec.imported) ? spec.imported.name : spec.imported.value;
1272
- if (imported === "cc") {
1273
- aliases.add(spec.local.name);
1274
- }
1275
- }
1276
- }
1277
- });
1278
- return { aliases, alreadyImported: aliases.size > 0 };
1279
- }
1280
- function ccCall(arg, ccName) {
1281
- return t2.callExpression(t2.identifier(ccName), [arg]);
1282
- }
1283
- function autoWrapComponents2(ast, filename) {
1284
- const { aliases, alreadyImported } = collectCcAliases(ast);
1285
- const ccName = aliases.values().next().value;
1286
- let changed = false;
1287
- const wrapIfCandidate = (fnPath, name) => {
1288
- if (name !== null && !isComponentName(name))
1289
- return false;
1290
- const fn = fnPath.node;
1291
- if (!isComponentArity(fn))
1292
- return false;
1293
- if (!functionReturnsJsx(fnPath))
1294
- return false;
1295
- return true;
1296
- };
1297
- traverse2(ast, {
1298
- ExportNamedDeclaration(path) {
1299
- const decl = path.node.declaration;
1300
- if (!decl)
1301
- return;
1302
- if (t2.isFunctionDeclaration(decl) && decl.id) {
1303
- const name = decl.id.name;
1304
- const fnPath = path.get("declaration");
1305
- if (!wrapIfCandidate(fnPath, name))
1306
- return;
1307
- const fnExpr = t2.functionExpression(decl.id, decl.params, decl.body, decl.generator, decl.async);
1308
- const varDecl = t2.variableDeclaration("const", [
1309
- t2.variableDeclarator(t2.identifier(name), ccCall(fnExpr, ccName ?? "cc"))
1310
- ]);
1311
- path.replaceWith(t2.exportNamedDeclaration(varDecl, []));
1312
- changed = true;
1313
- return;
1314
- }
1315
- if (t2.isVariableDeclaration(decl)) {
1316
- for (const dPath of path.get("declaration.declarations")) {
1317
- const id = dPath.node.id;
1318
- if (!t2.isIdentifier(id) || !isComponentName(id.name))
1319
- continue;
1320
- const init = dPath.node.init;
1321
- if (!init)
1322
- continue;
1323
- if (t2.isCallExpression(init) && t2.isIdentifier(init.callee) && aliases.has(init.callee.name)) {
1324
- continue;
1325
- }
1326
- if (!t2.isFunction(init))
1327
- continue;
1328
- const initPath = dPath.get("init");
1329
- if (!wrapIfCandidate(initPath, id.name))
1330
- continue;
1331
- dPath.node.init = ccCall(init, ccName ?? "cc");
1332
- changed = true;
1333
- }
1334
- return;
1335
- }
1336
- },
1337
- ExportDefaultDeclaration(path) {
1338
- const decl = path.node.declaration;
1339
- if (t2.isFunctionDeclaration(decl) && decl.id) {
1340
- const name = decl.id.name;
1341
- const fnPath = path.get("declaration");
1342
- if (!wrapIfCandidate(fnPath, name))
1343
- return;
1344
- const fnDecl = t2.functionDeclaration(decl.id, decl.params, decl.body, decl.generator, decl.async);
1345
- const exportDefault = t2.exportDefaultDeclaration(ccCall(t2.identifier(name), ccName ?? "cc"));
1346
- path.replaceWithMultiple([fnDecl, exportDefault]);
1347
- changed = true;
1348
- return;
1349
- }
1350
- if (t2.isFunctionDeclaration(decl) && !decl.id) {
1351
- const fnPath = path.get("declaration");
1352
- if (!wrapIfCandidate(fnPath, null))
1353
- return;
1354
- const fnExpr = t2.functionExpression(null, decl.params, decl.body, decl.generator, decl.async);
1355
- path.node.declaration = ccCall(fnExpr, ccName ?? "cc");
1356
- changed = true;
1357
- return;
1358
- }
1359
- if (t2.isArrowFunctionExpression(decl) || t2.isFunctionExpression(decl)) {
1360
- const declPath = path.get("declaration");
1361
- if (!wrapIfCandidate(declPath, null))
1362
- return;
1363
- path.node.declaration = ccCall(decl, ccName ?? "cc");
1364
- changed = true;
1365
- return;
1366
- }
1367
- }
1368
- });
1369
- if (changed && !alreadyImported) {
1370
- ast.program.body.unshift(t2.importDeclaration([t2.importSpecifier(t2.identifier("cc"), t2.identifier("cc"))], t2.stringLiteral("sinwan/component")));
1371
- }
1372
- return changed;
1373
- }
1374
-
1375
- // src/analyze.ts
1376
- var traverse3 = typeof _traverse3 === "function" ? _traverse3 : _traverse3.default ?? _traverse3;
1377
- function functionRef(fn) {
1378
- return { start: fn.start ?? -1, end: fn.end ?? -1 };
1379
- }
1380
- function refKey(ref) {
1381
- return `${ref.start}:${ref.end}`;
1382
- }
1383
- function findFunctionByRef(ast, ref) {
1384
- let found = null;
1385
- traverse3(ast, {
1386
- Function(path) {
1387
- const node = path.node;
1388
- if (node.start === ref.start && node.end === ref.end) {
1389
- found = node;
1390
- path.stop();
1391
- }
1392
- }
1393
- });
1394
- return found;
1395
- }
1396
- function serializeReactiveProps(reactiveProps) {
1397
- const result = {};
1398
- for (const [filePath, fileMap] of reactiveProps) {
1399
- const entry = {};
1400
- for (const [exportName, props] of fileMap) {
1401
- entry[exportName] = Array.from(props);
1402
- }
1403
- result[filePath] = entry;
1404
- }
1405
- return result;
1406
- }
1407
- function serializeModule(mod) {
1408
- const localScopes = {};
1409
- for (const [fn, scope] of mod.localScopes) {
1410
- localScopes[refKey(functionRef(fn))] = {
1411
- bindings: Object.fromEntries(scope.bindings)
1412
- };
1413
- }
1414
- const localCallGraph = {};
1415
- for (const [callerFn, sites] of mod.localCallGraph) {
1416
- localCallGraph[refKey(functionRef(callerFn))] = sites.map((s) => ({
1417
- callee: functionRef(s.callee),
1418
- props: s.props,
1419
- spreads: s.spreads
1420
- }));
1421
- }
1422
- return {
1423
- imports: Object.fromEntries(mod.imports),
1424
- exports: Object.fromEntries(Array.from(mod.exports.entries()).map(([name, fn]) => [
1425
- name,
1426
- functionRef(fn)
1427
- ])),
1428
- defaultExport: mod.defaultExport ? functionRef(mod.defaultExport) : null,
1429
- componentFunctions: Array.from(mod.componentFunctions).map(functionRef),
1430
- localScopes,
1431
- localCallGraph,
1432
- importedCallSites: mod.importedCallSites.map((s) => ({
1433
- caller: functionRef(s.caller),
1434
- source: s.source,
1435
- name: s.name,
1436
- props: s.props,
1437
- spreads: s.spreads
1438
- }))
1439
- };
1440
- }
1441
- function restoreModule(filePath, serialized) {
1442
- let code;
1443
- try {
1444
- code = fs.readFileSync(filePath, "utf-8");
1445
- } catch {
1446
- return null;
1447
- }
1448
- const ast = parse(code, {
1449
- sourceType: "module",
1450
- plugins: ["jsx", "typescript"]
1451
- });
1452
- const imports = new Map(Object.entries(serialized.imports));
1453
- const exports = new Map;
1454
- for (const [name, ref] of Object.entries(serialized.exports)) {
1455
- const fn = findFunctionByRef(ast, ref);
1456
- if (!fn)
1457
- return null;
1458
- exports.set(name, fn);
1459
- }
1460
- const defaultExport = serialized.defaultExport ? findFunctionByRef(ast, serialized.defaultExport) : null;
1461
- if (serialized.defaultExport && !defaultExport)
1462
- return null;
1463
- const componentFunctions = new Set;
1464
- for (const ref of serialized.componentFunctions) {
1465
- const fn = findFunctionByRef(ast, ref);
1466
- if (!fn)
1467
- return null;
1468
- componentFunctions.add(fn);
1469
- }
1470
- const localScopes = new Map;
1471
- for (const [key, scope] of Object.entries(serialized.localScopes)) {
1472
- const ref = parseRefKey(key);
1473
- if (!ref)
1474
- return null;
1475
- const fn = findFunctionByRef(ast, ref);
1476
- if (!fn)
1477
- return null;
1478
- localScopes.set(fn, {
1479
- bindings: new Map(Object.entries(scope.bindings))
1480
- });
1481
- }
1482
- const localCallGraph = new Map;
1483
- for (const [key, sites] of Object.entries(serialized.localCallGraph)) {
1484
- const ref = parseRefKey(key);
1485
- if (!ref)
1486
- return null;
1487
- const callerFn = findFunctionByRef(ast, ref);
1488
- if (!callerFn)
1489
- return null;
1490
- const restored = [];
1491
- for (const s of sites) {
1492
- const callee = findFunctionByRef(ast, s.callee);
1493
- if (!callee)
1494
- return null;
1495
- restored.push({
1496
- callee,
1497
- props: s.props,
1498
- spreads: s.spreads
1499
- });
1500
- }
1501
- localCallGraph.set(callerFn, restored);
1502
- }
1503
- const importedCallSites = [];
1504
- for (const s of serialized.importedCallSites) {
1505
- const caller = findFunctionByRef(ast, s.caller);
1506
- if (!caller)
1507
- return null;
1508
- importedCallSites.push({
1509
- caller,
1510
- source: s.source,
1511
- name: s.name,
1512
- props: s.props,
1513
- spreads: s.spreads
1514
- });
1515
- }
1516
- return {
1517
- filePath,
1518
- imports,
1519
- exports,
1520
- defaultExport,
1521
- componentFunctions,
1522
- localScopes,
1523
- localCallGraph,
1524
- importedCallSites
1525
- };
1526
- }
1527
- function parseRefKey(key) {
1528
- const parts = key.split(":");
1529
- if (parts.length !== 2)
1530
- return null;
1531
- const startStr = parts[0];
1532
- const endStr = parts[1];
1533
- if (!startStr || !endStr)
1534
- return null;
1535
- const start = Number(startStr);
1536
- const end = Number(endStr);
1537
- if (Number.isNaN(start) || Number.isNaN(end))
1538
- return null;
1539
- return { start, end };
1540
- }
1541
- function collectImports(ast) {
1542
- const imports = new Map;
1543
- traverse3(ast, {
1544
- ImportDeclaration(path) {
1545
- const source = path.node.source.value;
1546
- for (const spec of path.node.specifiers) {
1547
- if (t3.isImportSpecifier(spec)) {
1548
- const localName = spec.local.name;
1549
- const importedName = t3.isIdentifier(spec.imported) ? spec.imported.name : spec.imported.value;
1550
- imports.set(localName, { source, importedName });
1551
- } else if (t3.isImportDefaultSpecifier(spec)) {
1552
- imports.set(spec.local.name, { source, importedName: "default" });
1553
- }
1554
- }
1555
- }
1556
- });
1557
- return imports;
1558
- }
1559
- function collectImportedCallSites(ast, componentNames, imports) {
1560
- const sites = [];
1561
- traverse3(ast, {
1562
- JSXElement(path) {
1563
- const name = path.node.openingElement.name;
1564
- if (!t3.isJSXIdentifier(name))
1565
- return;
1566
- const tagName = name.name;
1567
- if (!/^[A-Z]/.test(tagName))
1568
- return;
1569
- if (componentNames.has(tagName))
1570
- return;
1571
- const importInfo = imports.get(tagName);
1572
- if (!importInfo)
1573
- return;
1574
- const callerPath = path.findParent((p) => p.isFunction());
1575
- if (!callerPath)
1576
- return;
1577
- const callerFn = callerPath.node;
1578
- const props = [];
1579
- const spreads = [];
1580
- for (const attr of path.node.openingElement.attributes) {
1581
- if (t3.isJSXSpreadAttribute(attr)) {
1582
- if (attr.argument) {
1583
- spreads.push(attr.argument);
1584
- }
1585
- continue;
1586
- }
1587
- if (t3.isJSXAttribute(attr)) {
1588
- const attrName = attr.name.name;
1589
- if (!attrName)
1590
- continue;
1591
- if (t3.isJSXExpressionContainer(attr.value)) {
1592
- const expr = attr.value.expression;
1593
- if (!expr || t3.isJSXEmptyExpression(expr))
1594
- continue;
1595
- props.push({ name: attrName, value: expr });
1596
- }
1597
- }
1598
- }
1599
- const childExprs = [];
1600
- for (const child of path.node.children) {
1601
- if (t3.isJSXExpressionContainer(child)) {
1602
- const expr = child.expression;
1603
- if (!expr || t3.isJSXEmptyExpression(expr))
1604
- continue;
1605
- childExprs.push(expr);
1606
- }
1607
- }
1608
- if (childExprs.length === 1) {
1609
- props.push({ name: "children", value: childExprs[0] });
1610
- } else if (childExprs.length > 1) {
1611
- props.push({ name: "children", value: t3.arrayExpression(childExprs) });
1612
- }
1613
- sites.push({
1614
- caller: callerFn,
1615
- source: importInfo.source,
1616
- name: importInfo.importedName,
1617
- props,
1618
- spreads
1619
- });
1620
- }
1621
- });
1622
- return sites;
1623
- }
1624
- function analyzeModule2(code, filePath) {
1625
- const ast = parse(code, {
1626
- sourceType: "module",
1627
- plugins: ["jsx", "typescript"]
1628
- });
1629
- autoWrapComponents2(ast, filePath);
1630
- const names = trackReactiveImports2(ast);
1631
- const {
1632
- functions,
1633
- names: componentNames,
1634
- exported
1635
- } = collectComponentFunctions2(ast, names.cc);
1636
- const localScopes = computeLocalScopes2(ast, names);
1637
- const localCallGraph = collectComponentCallGraph2(ast, componentNames);
1638
- const imports = collectImports(ast);
1639
- const importedCallSites = collectImportedCallSites(ast, componentNames, imports);
1640
- const exports = new Map;
1641
- let defaultExport = null;
1642
- for (const [localName, fn] of componentNames) {
1643
- if (exported.has(fn)) {
1644
- exports.set(localName, fn);
1645
- }
1646
- }
1647
- traverse3(ast, {
1648
- ExportDefaultDeclaration(path) {
1649
- const decl = path.node.declaration;
1650
- if (t3.isIdentifier(decl)) {
1651
- const fn = componentNames.get(decl.name);
1652
- if (fn)
1653
- defaultExport = fn;
1654
- } else if (t3.isCallExpression(decl)) {
1655
- const callee = decl.callee;
1656
- if (t3.isIdentifier(callee) && names.cc.has(callee.name)) {
1657
- const firstArg = decl.arguments[0];
1658
- if (firstArg && t3.isFunction(firstArg)) {
1659
- defaultExport = firstArg;
1660
- }
1661
- }
1662
- }
1663
- }
1664
- });
1665
- return {
1666
- filePath,
1667
- imports,
1668
- exports,
1669
- defaultExport,
1670
- componentFunctions: functions,
1671
- localScopes,
1672
- localCallGraph,
1673
- importedCallSites
1674
- };
1675
- }
1676
- function loadTsConfigPaths(tsConfigPath) {
1677
- try {
1678
- const raw = JSON.parse(fs.readFileSync(tsConfigPath, "utf-8"));
1679
- const paths = raw.compilerOptions?.paths;
1680
- if (!paths)
1681
- return null;
1682
- const baseUrl = raw.compilerOptions?.baseUrl;
1683
- const root = path2.dirname(tsConfigPath);
1684
- const resolvedBase = baseUrl ? path2.resolve(root, baseUrl) : root;
1685
- const result = new Map;
1686
- for (const [pattern, mappings] of Object.entries(paths)) {
1687
- result.set(pattern, mappings.map((m) => path2.resolve(resolvedBase, m)));
1688
- }
1689
- return result;
1690
- } catch {
1691
- return null;
1692
- }
1693
- }
1694
- function matchTsConfigPath(source, paths) {
1695
- for (const [pattern, mappings] of paths) {
1696
- if (pattern.endsWith("/*")) {
1697
- const prefix = pattern.slice(0, -1);
1698
- if (source.startsWith(prefix)) {
1699
- const rest = source.slice(prefix.length);
1700
- for (const mapping of mappings) {
1701
- if (mapping.endsWith("/*")) {
1702
- return mapping.slice(0, -1) + rest;
1703
- }
1704
- }
1705
- }
1706
- } else if (pattern === source) {
1707
- return mappings[0] ?? null;
1708
- }
1709
- }
1710
- return null;
1711
- }
1712
- function resolveRelativeImport(source, fromFile, extensions) {
1713
- if (!source.startsWith("."))
1714
- return null;
1715
- const base = path2.resolve(path2.dirname(fromFile), source);
1716
- const candidates = [
1717
- ...extensions.map((ext) => base + ext),
1718
- ...extensions.map((ext) => path2.join(base, "index" + ext))
1719
- ];
1720
- for (const candidate of candidates) {
1721
- if (fs.existsSync(candidate))
1722
- return candidate;
1723
- }
1724
- return null;
1725
- }
1726
- function parseTomlString(value) {
1727
- value = value.trim();
1728
- if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
1729
- return value.slice(1, -1);
1730
- }
1731
- return value;
1732
- }
1733
- function parseAliasObject(content, aliases) {
1734
- const pairs = [];
1735
- let current = "";
1736
- let inQuotes = null;
1737
- for (const char of content) {
1738
- if (char === '"' || char === "'") {
1739
- if (inQuotes === char) {
1740
- inQuotes = null;
1741
- } else if (!inQuotes) {
1742
- inQuotes = char;
1743
- }
1744
- }
1745
- if (char === "," && !inQuotes) {
1746
- pairs.push(current);
1747
- current = "";
1748
- } else {
1749
- current += char;
1750
- }
1751
- }
1752
- if (current.trim())
1753
- pairs.push(current);
1754
- for (const pair of pairs) {
1755
- const eq = pair.indexOf("=");
1756
- if (eq < 0)
1757
- continue;
1758
- const key = parseTomlString(pair.slice(0, eq));
1759
- const value = parseTomlString(pair.slice(eq + 1));
1760
- if (key && value)
1761
- aliases.set(key, value);
1762
- }
1763
- }
1764
- function loadBunfigAliases(bunfigPath) {
1765
- try {
1766
- const content = fs.readFileSync(bunfigPath, "utf-8");
1767
- const aliases = new Map;
1768
- const lines = content.split(`
1769
- `);
1770
- let section = null;
1771
- for (const rawLine of lines) {
1772
- const line = rawLine.trim();
1773
- if (!line || line.startsWith("#"))
1774
- continue;
1775
- if (line.startsWith("[") && line.endsWith("]")) {
1776
- section = line.slice(1, -1);
1777
- continue;
1778
- }
1779
- if (section === "install" && line.startsWith("alias")) {
1780
- const start = line.indexOf("{");
1781
- const end = line.lastIndexOf("}");
1782
- if (start >= 0 && end > start) {
1783
- parseAliasObject(line.slice(start + 1, end), aliases);
1784
- }
1785
- continue;
1786
- }
1787
- if (section === "install.alias") {
1788
- const eq = line.indexOf("=");
1789
- if (eq >= 0) {
1790
- const key = parseTomlString(line.slice(0, eq));
1791
- const value = parseTomlString(line.slice(eq + 1));
1792
- if (key && value)
1793
- aliases.set(key, value);
1794
- }
1795
- }
1796
- }
1797
- return aliases.size > 0 ? aliases : null;
1798
- } catch {
1799
- return null;
1800
- }
1801
- }
1802
- function matchBunfigAlias(source, aliases, root) {
1803
- for (const [alias, target] of aliases) {
1804
- if (source === alias) {
1805
- return path2.resolve(root, target);
1806
- }
1807
- if (source.startsWith(alias + "/")) {
1808
- return path2.resolve(root, target, source.slice(alias.length + 1));
1809
- }
1810
- }
1811
- return null;
1812
- }
1813
- function resolveWithExtensions(mapped, extensions) {
1814
- const candidates = [
1815
- ...extensions.map((ext) => mapped + ext),
1816
- ...extensions.map((ext) => path2.join(mapped, "index" + ext))
1817
- ];
1818
- for (const candidate of candidates) {
1819
- if (fs.existsSync(candidate))
1820
- return candidate;
1821
- }
1822
- return null;
1823
- }
1824
- function readPackageName(pkgDir) {
1825
- try {
1826
- const pkgJson = JSON.parse(fs.readFileSync(path2.join(pkgDir, "package.json"), "utf-8"));
1827
- return pkgJson.name ?? null;
1828
- } catch {
1829
- return null;
1830
- }
1831
- }
1832
- function resolveWorkspacePackage(source, packages, extensions) {
1833
- if (packages.length === 0)
1834
- return null;
1835
- if (source.startsWith(".") || source.startsWith("/"))
1836
- return null;
1837
- let matchedPkg = null;
1838
- let matchedName = null;
1839
- for (const pkg of packages) {
1840
- const pkgName = readPackageName(pkg);
1841
- if (!pkgName)
1842
- continue;
1843
- if (source === pkgName || source.startsWith(pkgName + "/")) {
1844
- if (!matchedName || pkgName.length > matchedName.length) {
1845
- matchedName = pkgName;
1846
- matchedPkg = pkg;
1847
- }
1848
- }
1849
- }
1850
- if (!matchedPkg || !matchedName)
1851
- return null;
1852
- const subpath = source.slice(matchedName.length + 1);
1853
- let sourceDir;
1854
- try {
1855
- const pkgJson = JSON.parse(fs.readFileSync(path2.join(matchedPkg, "package.json"), "utf-8"));
1856
- if (pkgJson.source) {
1857
- sourceDir = path2.dirname(path2.resolve(matchedPkg, pkgJson.source));
1858
- } else {
1859
- sourceDir = path2.join(matchedPkg, "src");
1860
- }
1861
- } catch {
1862
- sourceDir = path2.join(matchedPkg, "src");
1863
- }
1864
- const targetBase = subpath ? path2.join(sourceDir, subpath) : path2.join(sourceDir, "index");
1865
- return resolveWithExtensions(targetBase, extensions);
1866
- }
1867
- function loadPackageJsonWorkspaces(filePath) {
1868
- try {
1869
- const pkg = JSON.parse(fs.readFileSync(filePath, "utf-8"));
1870
- return pkg.workspaces ?? [];
1871
- } catch {
1872
- return [];
1873
- }
1874
- }
1875
- function loadPnpmWorkspaces(filePath) {
1876
- try {
1877
- const content = fs.readFileSync(filePath, "utf-8");
1878
- const lines = content.split(`
1879
- `);
1880
- const patterns = [];
1881
- let inPackages = false;
1882
- for (const rawLine of lines) {
1883
- const line = rawLine.trim();
1884
- if (!line || line.startsWith("#"))
1885
- continue;
1886
- if (line.startsWith("packages:")) {
1887
- inPackages = true;
1888
- continue;
1889
- }
1890
- if (inPackages) {
1891
- if (!line.startsWith("-") && !line.startsWith(" ")) {
1892
- inPackages = false;
1893
- continue;
1894
- }
1895
- const match = line.match(/^-\s*["']?([^"']+)["']?$/);
1896
- if (match)
1897
- patterns.push(match[1]);
1898
- }
1899
- }
1900
- return patterns;
1901
- } catch {
1902
- return [];
1903
- }
1904
- }
1905
- function expandGlob(pattern, cwd) {
1906
- const fullPattern = path2.resolve(cwd, pattern);
1907
- const parts = fullPattern.split(path2.sep);
1908
- const wildcardIndex = parts.findIndex((p) => p.includes("*"));
1909
- if (wildcardIndex === -1) {
1910
- return fs.existsSync(fullPattern) ? [fullPattern] : [];
1911
- }
1912
- const prefix = parts.slice(0, wildcardIndex).join(path2.sep);
1913
- const suffixParts = parts.slice(wildcardIndex + 1);
1914
- const suffix = suffixParts.join(path2.sep);
1915
- let entries;
1916
- try {
1917
- entries = fs.readdirSync(prefix, { withFileTypes: true });
1918
- } catch {
1919
- return [];
1920
- }
1921
- const results = [];
1922
- for (const entry of entries) {
1923
- if (!entry.isDirectory())
1924
- continue;
1925
- const candidate = path2.join(prefix, entry.name, suffix);
1926
- results.push(...expandGlob(candidate, cwd));
1927
- }
1928
- return results;
1929
- }
1930
- function loadWorkspacePackages(workspaces, root) {
1931
- const patternEntries = [];
1932
- if (typeof workspaces === "string") {
1933
- const resolved = path2.resolve(root, workspaces);
1934
- if (fs.existsSync(resolved)) {
1935
- const stat = fs.statSync(resolved);
1936
- if (stat.isDirectory()) {
1937
- patternEntries.push({ pattern: workspaces, base: root });
1938
- } else if (resolved.endsWith("package.json")) {
1939
- const base = path2.dirname(resolved);
1940
- for (const p of loadPackageJsonWorkspaces(resolved)) {
1941
- patternEntries.push({ pattern: p, base });
1942
- }
1943
- } else if (resolved.endsWith("pnpm-workspace.yaml")) {
1944
- const base = path2.dirname(resolved);
1945
- for (const p of loadPnpmWorkspaces(resolved)) {
1946
- patternEntries.push({ pattern: p, base });
1947
- }
1948
- } else {
1949
- patternEntries.push({ pattern: workspaces, base: root });
1950
- }
1951
- } else {
1952
- patternEntries.push({ pattern: workspaces, base: root });
1953
- }
1954
- } else if (Array.isArray(workspaces)) {
1955
- for (const p of workspaces) {
1956
- patternEntries.push({ pattern: p, base: root });
1957
- }
1958
- } else if (workspaces && typeof workspaces === "object") {
1959
- if (workspaces.file) {
1960
- const resolved = path2.resolve(root, workspaces.file);
1961
- if (resolved.endsWith("package.json")) {
1962
- const base = path2.dirname(resolved);
1963
- for (const p of loadPackageJsonWorkspaces(resolved)) {
1964
- patternEntries.push({ pattern: p, base });
1965
- }
1966
- } else if (resolved.endsWith("pnpm-workspace.yaml")) {
1967
- const base = path2.dirname(resolved);
1968
- for (const p of loadPnpmWorkspaces(resolved)) {
1969
- patternEntries.push({ pattern: p, base });
1970
- }
1971
- }
1972
- }
1973
- if (workspaces.include) {
1974
- for (const p of workspaces.include) {
1975
- patternEntries.push({ pattern: p, base: root });
1976
- }
1977
- }
1978
- }
1979
- const packages = new Set;
1980
- for (const { pattern, base } of patternEntries) {
1981
- for (const dir of expandGlob(pattern, base)) {
1982
- const pkgJson = path2.join(dir, "package.json");
1983
- if (fs.existsSync(pkgJson)) {
1984
- packages.add(dir);
1985
- }
1986
- }
1987
- }
1988
- return Array.from(packages);
1989
- }
1990
- function createResolver(root, extensions, tsConfigPath, bunfigPath, workspaces) {
1991
- const paths = tsConfigPath ? loadTsConfigPaths(tsConfigPath) : null;
1992
- const aliases = bunfigPath ? loadBunfigAliases(bunfigPath) : null;
1993
- const packages = workspaces ? loadWorkspacePackages(workspaces, root) : [];
1994
- return (source, fromFile) => {
1995
- if (paths) {
1996
- const mapped = matchTsConfigPath(source, paths);
1997
- if (mapped) {
1998
- const resolved = resolveWithExtensions(mapped, extensions);
1999
- if (resolved)
2000
- return resolved;
2001
- }
2002
- }
2003
- if (aliases) {
2004
- const mapped = matchBunfigAlias(source, aliases, root);
2005
- if (mapped) {
2006
- const resolved = resolveWithExtensions(mapped, extensions);
2007
- if (resolved)
2008
- return resolved;
2009
- }
2010
- }
2011
- const pkgResolved = resolveWorkspacePackage(source, packages, extensions);
2012
- if (pkgResolved)
2013
- return pkgResolved;
2014
- return resolveRelativeImport(source, fromFile, extensions);
2015
- };
2016
- }
2017
- function findFiles(root, extensions) {
2018
- const files = [];
2019
- function walk(dir) {
2020
- let entries;
2021
- try {
2022
- entries = fs.readdirSync(dir, { withFileTypes: true });
2023
- } catch {
2024
- return;
2025
- }
2026
- for (const entry of entries) {
2027
- const fullPath = path2.join(dir, entry.name);
2028
- if (entry.isDirectory()) {
2029
- if (entry.name === "node_modules" || entry.name === ".git")
2030
- continue;
2031
- walk(fullPath);
2032
- } else if (entry.isFile() && extensions.some((ext) => entry.name.endsWith(ext))) {
2033
- files.push(fullPath);
2034
- }
2035
- }
2036
- }
2037
- walk(root);
2038
- return files;
2039
- }
2040
- function analyzeProject2(options) {
2041
- const extensions = options.extensions ?? [".tsx", ".ts", ".jsx", ".js"];
2042
- const resolve = options.resolve ?? createResolver(options.root, extensions, options.tsConfigPath, options.bunfigPath, options.workspaces);
2043
- const modules = new Map;
2044
- const providedFiles = options.files;
2045
- if (providedFiles) {
2046
- for (const [filePath, code] of Object.entries(providedFiles)) {
2047
- modules.set(filePath, analyzeModule2(code, filePath));
2048
- }
2049
- } else {
2050
- const files = findFiles(options.root, extensions);
2051
- for (const file of files) {
2052
- const code = fs.readFileSync(file, "utf-8");
2053
- modules.set(file, analyzeModule2(code, file));
2054
- }
2055
- }
2056
- function ensureAnalyzed(filePath) {
2057
- if (modules.has(filePath))
2058
- return modules.get(filePath);
2059
- try {
2060
- const code = fs.readFileSync(filePath, "utf-8");
2061
- const mod = analyzeModule2(code, filePath);
2062
- modules.set(filePath, mod);
2063
- return mod;
2064
- } catch {
2065
- return;
2066
- }
2067
- }
2068
- const globalLocalScopes = new Map;
2069
- const globalCallGraph = new Map;
2070
- const globalComponentFunctions = new Set;
2071
- for (const mod of modules.values()) {
2072
- for (const [fn, scope] of mod.localScopes) {
2073
- globalLocalScopes.set(fn, scope);
2074
- }
2075
- for (const fn of mod.componentFunctions) {
2076
- globalComponentFunctions.add(fn);
2077
- }
2078
- for (const [callerFn, sites] of mod.localCallGraph) {
2079
- const list = globalCallGraph.get(callerFn) ?? [];
2080
- list.push(...sites);
2081
- globalCallGraph.set(callerFn, list);
2082
- }
2083
- }
2084
- const reactiveProps = buildProjectReactiveProps(modules, resolve, globalLocalScopes, globalCallGraph, globalComponentFunctions, ensureAnalyzed);
2085
- return { modules, reactiveProps };
2086
- }
2087
- function buildProjectReactiveProps(modules, resolve, globalLocalScopes, globalCallGraph, globalComponentFunctions, ensureAnalyzed) {
2088
- for (const mod of modules.values()) {
2089
- for (const site of mod.importedCallSites) {
2090
- const resolvedFile = resolve(site.source, mod.filePath);
2091
- if (!resolvedFile)
2092
- continue;
2093
- let targetMod = modules.get(resolvedFile);
2094
- if (!targetMod && ensureAnalyzed) {
2095
- targetMod = ensureAnalyzed(resolvedFile);
2096
- if (targetMod) {
2097
- for (const [fn, scope] of targetMod.localScopes) {
2098
- globalLocalScopes.set(fn, scope);
2099
- }
2100
- for (const fn of targetMod.componentFunctions) {
2101
- globalComponentFunctions.add(fn);
2102
- }
2103
- for (const [callerFn, sites] of targetMod.localCallGraph) {
2104
- const list = globalCallGraph.get(callerFn) ?? [];
2105
- list.push(...sites);
2106
- globalCallGraph.set(callerFn, list);
2107
- }
2108
- }
2109
- }
2110
- if (!targetMod)
2111
- continue;
2112
- const targetFn = site.name === "default" ? targetMod.defaultExport : targetMod.exports.get(site.name);
2113
- if (!targetFn)
2114
- continue;
2115
- const list = globalCallGraph.get(site.caller) ?? [];
2116
- list.push({
2117
- callee: targetFn,
2118
- props: site.props,
2119
- spreads: site.spreads
2120
- });
2121
- globalCallGraph.set(site.caller, list);
2122
- }
2123
- }
2124
- const globalReactiveProps = propagateReactiveProps2(globalLocalScopes, globalCallGraph, globalComponentFunctions);
2125
- const reactiveProps = new Map;
2126
- for (const mod of modules.values()) {
2127
- const fileMap = new Map;
2128
- for (const [exportName, fn] of mod.exports) {
2129
- const props = globalReactiveProps.get(fn);
2130
- fileMap.set(exportName, props ? new Set(props) : new Set);
2131
- }
2132
- if (mod.defaultExport) {
2133
- const props = globalReactiveProps.get(mod.defaultExport);
2134
- fileMap.set("default", props ? new Set(props) : new Set);
2135
- }
2136
- reactiveProps.set(mod.filePath, fileMap);
2137
- }
2138
- return reactiveProps;
2139
- }
2140
-
2141
- class AnalyzerCache2 {
2142
- root;
2143
- extensions;
2144
- tsConfigPath;
2145
- bunfigPath;
2146
- workspaces;
2147
- cachePath;
2148
- resolve;
2149
- modules = new Map;
2150
- reactiveProps = new Map;
2151
- importers = new Map;
2152
- constructor(options) {
2153
- this.root = options.root;
2154
- this.extensions = options.extensions ?? [".tsx", ".ts", ".jsx", ".js"];
2155
- this.tsConfigPath = options.tsConfigPath;
2156
- this.bunfigPath = options.bunfigPath;
2157
- this.workspaces = options.workspaces;
2158
- this.cachePath = options.cachePath;
2159
- const bunfigPath = options.bunfigPath;
2160
- const diskResolver = options.resolve ?? createResolver(this.root, this.extensions, this.tsConfigPath, bunfigPath, this.workspaces);
2161
- this.resolve = (source, fromFile) => {
2162
- if (source.startsWith(".")) {
2163
- const base = path2.resolve(path2.dirname(fromFile), source);
2164
- for (const ext of this.extensions) {
2165
- if (this.modules.has(base + ext))
2166
- return base + ext;
2167
- const indexCandidate = path2.join(base, "index" + ext);
2168
- if (this.modules.has(indexCandidate))
2169
- return indexCandidate;
2170
- }
2171
- }
2172
- return diskResolver(source, fromFile);
2173
- };
2174
- if (this.cachePath) {
2175
- this.restore();
2176
- }
2177
- }
2178
- update(filePath, code) {
2179
- const oldMod = this.modules.get(filePath);
2180
- const newMod = analyzeModule2(code, filePath);
2181
- this.modules.set(filePath, newMod);
2182
- if (oldMod)
2183
- this.removeImporters(oldMod);
2184
- this.addImporters(newMod);
2185
- this.recomputeFor([filePath]);
2186
- this.save();
2187
- }
2188
- remove(filePath) {
2189
- const oldMod = this.modules.get(filePath);
2190
- const affected = new Set(this.importers.get(filePath) ?? []);
2191
- if (oldMod) {
2192
- for (const site of oldMod.importedCallSites) {
2193
- const resolved = this.resolve(site.source, oldMod.filePath);
2194
- if (resolved)
2195
- affected.add(resolved);
2196
- }
2197
- }
2198
- this.modules.delete(filePath);
2199
- this.importers.delete(filePath);
2200
- if (oldMod)
2201
- this.removeImporters(oldMod);
2202
- this.recomputeFor(affected);
2203
- this.save();
2204
- }
2205
- save() {
2206
- if (!this.cachePath)
2207
- return;
2208
- const data = {
2209
- version: 1,
2210
- modules: Object.fromEntries(Array.from(this.modules.entries()).map(([filePath, mod]) => [
2211
- filePath,
2212
- serializeModule(mod)
2213
- ])),
2214
- reactiveProps: serializeReactiveProps(this.reactiveProps)
2215
- };
2216
- const dir = path2.dirname(this.cachePath);
2217
- if (!fs.existsSync(dir)) {
2218
- fs.mkdirSync(dir, { recursive: true });
2219
- }
2220
- fs.writeFileSync(this.cachePath, JSON.stringify(data, null, 2));
2221
- }
2222
- restore() {
2223
- if (!this.cachePath || !fs.existsSync(this.cachePath))
2224
- return;
2225
- let data;
2226
- try {
2227
- data = JSON.parse(fs.readFileSync(this.cachePath, "utf-8"));
2228
- } catch {
2229
- return;
2230
- }
2231
- if (data.version !== 1)
2232
- return;
2233
- for (const [filePath, serialized] of Object.entries(data.modules)) {
2234
- const mod = restoreModule(filePath, serialized);
2235
- if (mod) {
2236
- this.modules.set(filePath, mod);
2237
- }
2238
- }
2239
- this.importers = new Map;
2240
- for (const mod of this.modules.values()) {
2241
- this.addImporters(mod);
2242
- }
2243
- this.reactiveProps = new Map;
2244
- for (const [filePath, fileMap] of Object.entries(data.reactiveProps)) {
2245
- const map = new Map;
2246
- for (const [exportName, props] of Object.entries(fileMap)) {
2247
- map.set(exportName, new Set(props));
2248
- }
2249
- this.reactiveProps.set(filePath, map);
2250
- }
2251
- }
2252
- addImporters(mod) {
2253
- for (const site of mod.importedCallSites) {
2254
- const resolvedFile = this.resolve(site.source, mod.filePath);
2255
- if (!resolvedFile)
2256
- continue;
2257
- let set = this.importers.get(resolvedFile);
2258
- if (!set) {
2259
- set = new Set;
2260
- this.importers.set(resolvedFile, set);
2261
- }
2262
- set.add(mod.filePath);
2263
- }
2264
- }
2265
- removeImporters(mod) {
2266
- for (const site of mod.importedCallSites) {
2267
- const resolvedFile = this.resolve(site.source, mod.filePath);
2268
- if (!resolvedFile)
2269
- continue;
2270
- this.importers.get(resolvedFile)?.delete(mod.filePath);
2271
- }
2272
- }
2273
- ensureAnalyzed(filePath) {
2274
- if (this.modules.has(filePath))
2275
- return this.modules.get(filePath);
2276
- try {
2277
- const code = fs.readFileSync(filePath, "utf-8");
2278
- const mod = analyzeModule2(code, filePath);
2279
- this.modules.set(filePath, mod);
2280
- return mod;
2281
- } catch {
2282
- return;
2283
- }
2284
- }
2285
- collectAffected(filePaths) {
2286
- const affected = new Set;
2287
- const queue = Array.from(filePaths);
2288
- while (queue.length > 0) {
2289
- const current = queue.pop();
2290
- if (affected.has(current))
2291
- continue;
2292
- let mod = this.modules.get(current);
2293
- if (!mod) {
2294
- mod = this.ensureAnalyzed(current);
2295
- if (mod)
2296
- this.addImporters(mod);
2297
- }
2298
- if (!mod)
2299
- continue;
2300
- affected.add(current);
2301
- for (const importer of this.importers.get(current) ?? []) {
2302
- if (!affected.has(importer))
2303
- queue.push(importer);
2304
- }
2305
- for (const site of mod.importedCallSites) {
2306
- const resolved = this.resolve(site.source, mod.filePath);
2307
- if (resolved && !affected.has(resolved)) {
2308
- queue.push(resolved);
2309
- }
2310
- }
2311
- }
2312
- return affected;
2313
- }
2314
- recomputeFor(filePaths) {
2315
- const affected = this.collectAffected(filePaths);
2316
- const subgraphModules = new Map;
2317
- for (const filePath of affected) {
2318
- const mod = this.modules.get(filePath);
2319
- if (mod)
2320
- subgraphModules.set(filePath, mod);
2321
- }
2322
- const globalLocalScopes = new Map;
2323
- const globalCallGraph = new Map;
2324
- const globalComponentFunctions = new Set;
2325
- for (const mod of subgraphModules.values()) {
2326
- for (const [fn, scope] of mod.localScopes) {
2327
- globalLocalScopes.set(fn, scope);
2328
- }
2329
- for (const fn of mod.componentFunctions) {
2330
- globalComponentFunctions.add(fn);
2331
- }
2332
- for (const [callerFn, sites] of mod.localCallGraph) {
2333
- const list = globalCallGraph.get(callerFn) ?? [];
2334
- list.push(...sites);
2335
- globalCallGraph.set(callerFn, list);
2336
- }
2337
- }
2338
- const subgraphReactiveProps = buildProjectReactiveProps(subgraphModules, this.resolve, globalLocalScopes, globalCallGraph, globalComponentFunctions);
2339
- for (const filePath of affected) {
2340
- const map = subgraphReactiveProps.get(filePath);
2341
- if (map) {
2342
- this.reactiveProps.set(filePath, map);
2343
- }
2344
- }
2345
- }
2346
- }
2347
- function analyze2(options) {
2348
- const tsConfigPath = options.tsConfigPath ?? path2.join(options.root, "tsconfig.json");
2349
- const bunfigPath = options.bunfigPath ?? path2.join(options.root, "bunfig.toml");
2350
- const project = analyzeProject2({
2351
- root: options.root,
2352
- extensions: options.extensions,
2353
- resolve: options.resolve,
2354
- tsConfigPath: fs.existsSync(tsConfigPath) ? tsConfigPath : undefined,
2355
- bunfigPath: fs.existsSync(bunfigPath) ? bunfigPath : undefined,
2356
- workspaces: options.workspaces
2357
- });
2358
- const serialized = {};
2359
- for (const [filePath, fileMap] of project.reactiveProps) {
2360
- const entry = {};
2361
- for (const [exportName, props] of fileMap) {
2362
- entry[exportName] = Array.from(props);
2363
- }
2364
- serialized[filePath] = entry;
2365
- }
2366
- const dir = path2.dirname(options.outFile);
2367
- if (!fs.existsSync(dir)) {
2368
- fs.mkdirSync(dir, { recursive: true });
2369
- }
2370
- fs.writeFileSync(options.outFile, JSON.stringify(serialized, null, 2));
2371
- }
2372
- function loadMetadata2(filePath) {
2373
- if (!fs.existsSync(filePath)) {
2374
- return new Map;
2375
- }
2376
- const raw = JSON.parse(fs.readFileSync(filePath, "utf-8"));
2377
- const result = new Map;
2378
- for (const [filePath, fileMap] of Object.entries(raw)) {
2379
- const map = new Map;
2380
- for (const [exportName, props] of Object.entries(fileMap)) {
2381
- map.set(exportName, new Set(props));
2382
- }
2383
- result.set(filePath, map);
2384
- }
2385
- return result;
2386
- }
2387
-
2388
- // src/cli.ts
2389
- function runAnalyzeCli2(args = process.argv.slice(2)) {
2390
- let command;
2391
- let root;
2392
- let outFile;
2393
- let tsConfigPath;
2394
- let bunfigPath;
2395
- let workspaces;
2396
- for (let i = 0;i < args.length; i++) {
2397
- const arg = args[i];
2398
- if (arg === "--tsconfig" || arg === "-t") {
2399
- tsConfigPath = args[++i];
2400
- } else if (arg === "--bunfig" || arg === "-b") {
2401
- bunfigPath = args[++i];
2402
- } else if (arg === "--workspaces" || arg === "-w") {
2403
- workspaces = args[++i];
2404
- } else if (command === undefined) {
2405
- command = arg;
2406
- } else if (root === undefined) {
2407
- root = arg;
2408
- } else if (outFile === undefined) {
2409
- outFile = arg;
2410
- }
2411
- }
2412
- if (command !== "analyze") {
2413
- console.error("Unknown command. Use: sinwan analyze [root] [outFile] [--tsconfig path] [--bunfig path] [--workspaces path]");
2414
- process.exit(1);
2415
- }
2416
- root = root ?? process.cwd();
2417
- outFile = outFile ?? path3.join(root, ".sinwan/reactive-props.json");
2418
- analyze2({ root, outFile, tsConfigPath, bunfigPath, workspaces });
2419
- console.log(`[sinwan] analyzed ${root} -> ${outFile}`);
2420
- }
2421
- function runCliIfMain2(isMain = import.meta.main, args) {
2422
- if (!isMain)
2423
- return;
2424
- runAnalyzeCli2(args);
2425
- }
2426
- runCliIfMain2();
2427
-
2428
- export { autoWrapComponents2, analyzeModule2, analyzeProject2, AnalyzerCache2, analyze2, loadMetadata2, trackReactiveImports2, getAllPropNames2, isReactiveValue2, containsReactiveValue2, collectComponentFunctions2, computeLocalScopes2, collectComponentCallGraph2, buildFullScope2, propagateReactiveProps2, wrapReactiveExpressions2, runAnalyzeCli2, runCliIfMain2 };