@reckona/mreact-compiler 0.0.65 → 0.0.67

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.
@@ -0,0 +1,1905 @@
1
+ import type { ModuleIr } from "./ir.js";
2
+ import {
3
+ analyzeCompilerModuleContextWithOxc,
4
+ analyzeWithOxc,
5
+ } from "./oxc.js";
6
+ import {
7
+ createCompilerModuleContextWithOxc,
8
+ type CompilerModuleContext,
9
+ } from "./compiler-module-context.js";
10
+ export { transformCompilerModuleContext } from "./transform.js";
11
+ export { stripTypeScriptWithOxc } from "./oxc-transform.js";
12
+ export type { CompilerModuleContext } from "./compiler-module-context.js";
13
+ import type {
14
+ AnalyzeModuleOptions,
15
+ CompileTarget,
16
+ Diagnostic,
17
+ } from "./types.js";
18
+
19
+ export interface AnalyzeToIrInput {
20
+ code: string;
21
+ filename: string;
22
+ target: CompileTarget;
23
+ options?: AnalyzeModuleOptions;
24
+ }
25
+
26
+ export interface AnalyzeToIrOutput {
27
+ ir: ModuleIr;
28
+ diagnostics: Diagnostic[];
29
+ usedTypescriptFallback?: boolean;
30
+ }
31
+
32
+ export interface StaticImportReference {
33
+ localNames: string[];
34
+ sideEffect: boolean;
35
+ source: string;
36
+ }
37
+
38
+ export interface StaticImportSpecifierReference {
39
+ importedName: string;
40
+ kind: "default" | "named" | "namespace";
41
+ localName: string;
42
+ }
43
+
44
+ export interface ClientRouteStaticImportReference extends StaticImportReference {
45
+ specifiers: StaticImportSpecifierReference[];
46
+ }
47
+
48
+ export interface StaticExportReference {
49
+ exportedNames: string[];
50
+ exportAll: boolean;
51
+ source: string;
52
+ }
53
+
54
+ export interface TopLevelExportRenderInfo {
55
+ calledComponentRoots: string[];
56
+ clientRuntime: boolean;
57
+ name: string;
58
+ renderedComponentRoots: string[];
59
+ }
60
+
61
+ export interface ClientRouteModuleAnalysis {
62
+ clientRuntime: boolean;
63
+ hasUseClientDirective: boolean;
64
+ hasUseServerDirective: boolean;
65
+ componentCallRoots: string[];
66
+ identifierReferences: string[];
67
+ jsxComponentRoots: string[];
68
+ staticExports: StaticExportReference[];
69
+ staticImports: ClientRouteStaticImportReference[];
70
+ topLevelExportRenderInfo: TopLevelExportRenderInfo[];
71
+ }
72
+
73
+ export interface FormActionReference {
74
+ end: number;
75
+ name: string;
76
+ start: number;
77
+ }
78
+
79
+ interface ComponentAliasState {
80
+ aliases: Map<string, string>;
81
+ stringConstants: Map<string, string>;
82
+ }
83
+
84
+ export function analyzeToIr(input: AnalyzeToIrInput): AnalyzeToIrOutput {
85
+ return analyzeWithOxc(input);
86
+ }
87
+
88
+ export function analyzeCompilerModuleContextToIr(
89
+ context: CompilerModuleContext,
90
+ input: Omit<AnalyzeToIrInput, "code" | "filename">,
91
+ ): AnalyzeToIrOutput {
92
+ return analyzeCompilerModuleContextWithOxc(context, input);
93
+ }
94
+
95
+ export function createCompilerModuleContext(input: {
96
+ code: string;
97
+ filename?: string | undefined;
98
+ }): CompilerModuleContext {
99
+ return createCompilerModuleContextWithOxc(input);
100
+ }
101
+
102
+ export function hasTopLevelExportDeclaration(input: {
103
+ code: string;
104
+ filename?: string | undefined;
105
+ names: readonly string[];
106
+ }): boolean {
107
+ const names = new Set(input.names);
108
+ const parsed = parseModule(input.code, input.filename);
109
+
110
+ return programBody(parsed.program).some((statement) =>
111
+ exportedNames(statement).some((name) => names.has(name)),
112
+ );
113
+ }
114
+
115
+ export function stripTopLevelExportDeclarations(input: {
116
+ code: string;
117
+ filename?: string | undefined;
118
+ names: readonly string[];
119
+ }): string {
120
+ const names = new Set(input.names);
121
+ const parsed = parseModule(input.code, input.filename);
122
+ const replacements = programBody(parsed.program)
123
+ .map((statement) => exportDeclarationReplacement(input.code, statement, names))
124
+ .filter((replacement): replacement is Replacement => replacement !== undefined)
125
+ .sort((left, right) => right.start - left.start);
126
+ let code = input.code;
127
+
128
+ for (const replacement of replacements) {
129
+ code = `${code.slice(0, replacement.start)}${replacement.text}${code.slice(replacement.end)}`;
130
+ }
131
+
132
+ return code;
133
+ }
134
+
135
+ export function demoteTopLevelExportDeclarations(input: {
136
+ code: string;
137
+ filename?: string | undefined;
138
+ names: readonly string[];
139
+ }): string {
140
+ const names = new Set(input.names);
141
+ const parsed = parseModule(input.code, input.filename);
142
+ const replacements = programBody(parsed.program)
143
+ .map((statement) => exportDeclarationDemotion(input.code, statement, names))
144
+ .filter((replacement): replacement is Replacement => replacement !== undefined)
145
+ .sort((left, right) => right.start - left.start);
146
+ let code = input.code;
147
+
148
+ for (const replacement of replacements) {
149
+ code = `${code.slice(0, replacement.start)}${replacement.text}${code.slice(replacement.end)}`;
150
+ }
151
+
152
+ return code;
153
+ }
154
+
155
+ export function collectStaticModuleSpecifiers(input: {
156
+ code: string;
157
+ filename?: string | undefined;
158
+ }): string[] {
159
+ const parsed = parseModule(input.code, input.filename);
160
+
161
+ return programBody(parsed.program).flatMap(staticModuleSpecifier);
162
+ }
163
+
164
+ export function collectStaticImportReferences(input: {
165
+ code: string;
166
+ filename?: string | undefined;
167
+ }): StaticImportReference[] {
168
+ const parsed = parseModule(input.code, input.filename);
169
+
170
+ return programBody(parsed.program).flatMap(staticImportReference);
171
+ }
172
+
173
+ export function collectStaticExportReferences(input: {
174
+ code: string;
175
+ filename?: string | undefined;
176
+ }): StaticExportReference[] {
177
+ const parsed = parseModule(input.code, input.filename);
178
+
179
+ return programBody(parsed.program).flatMap(staticExportReference);
180
+ }
181
+
182
+ export function collectJsxComponentRootNames(input: {
183
+ code: string;
184
+ filename?: string | undefined;
185
+ }): string[] {
186
+ const parsed = parseModule(input.code, input.filename);
187
+ const names = new Set<string>();
188
+ const aliasState = createComponentAliasState();
189
+
190
+ collectJsxComponentRootNamesFromNode(parsed.program, names);
191
+ collectSimpleComponentAliasesFromNode(parsed.program, aliasState);
192
+ expandJsxComponentAliasRoots(names, aliasState.aliases);
193
+ return Array.from(names).sort();
194
+ }
195
+
196
+ export function collectIdentifierReferenceNames(input: {
197
+ code: string;
198
+ filename?: string | undefined;
199
+ }): string[] {
200
+ const parsed = parseModule(input.code, input.filename);
201
+ const names = new Set<string>();
202
+
203
+ collectIdentifierReferenceNamesFromNode(parsed.program, names);
204
+ return Array.from(names).sort();
205
+ }
206
+
207
+ export function collectFormActionReferenceNames(input: {
208
+ code: string;
209
+ filename?: string | undefined;
210
+ }): string[] {
211
+ return Array.from(
212
+ new Set(collectFormActionReferences(input).map((reference) => reference.name)),
213
+ ).sort();
214
+ }
215
+
216
+ export function collectFormActionReferences(input: {
217
+ code: string;
218
+ filename?: string | undefined;
219
+ }): FormActionReference[] {
220
+ const parsed = parseModule(input.code, input.filename);
221
+ const references: FormActionReference[] = [];
222
+
223
+ collectFormActionReferencesFromNode(parsed.program, references);
224
+ return references.sort((left, right) =>
225
+ left.start === right.start ? left.name.localeCompare(right.name) : left.start - right.start,
226
+ );
227
+ }
228
+
229
+ export function collectTopLevelValueExportNames(input: {
230
+ code: string;
231
+ filename?: string | undefined;
232
+ }): string[] {
233
+ const parsed = parseModule(input.code, input.filename);
234
+ const names = new Set<string>();
235
+
236
+ for (const statement of programBody(parsed.program)) {
237
+ for (const name of exportedNames(statement)) {
238
+ names.add(name);
239
+ }
240
+ }
241
+
242
+ return Array.from(names).sort();
243
+ }
244
+
245
+ export function collectTopLevelExportRenderInfo(input: {
246
+ code: string;
247
+ filename?: string | undefined;
248
+ }): TopLevelExportRenderInfo[] {
249
+ const parsed = parseModule(input.code, input.filename);
250
+
251
+ return collectTopLevelExportRenderInfoFromProgram(parsed.program);
252
+ }
253
+
254
+ export function collectClientRouteModuleAnalysis(input: {
255
+ code: string;
256
+ filename?: string | undefined;
257
+ }): ClientRouteModuleAnalysis {
258
+ const parsed = parseModule(input.code, input.filename);
259
+
260
+ return collectClientRouteModuleAnalysisFromContext(parsed);
261
+ }
262
+
263
+ export function collectClientRouteModuleAnalysisFromContext(
264
+ context: CompilerModuleContext,
265
+ ): ClientRouteModuleAnalysis {
266
+ const parsed = parseModuleContext(context);
267
+ const body = programBody(parsed.program);
268
+ const identifierReferences = new Set<string>();
269
+
270
+ collectIdentifierReferenceNamesFromNode(parsed.program, identifierReferences);
271
+
272
+ return {
273
+ clientRuntime: hasClientRuntimeSyntaxNode(parsed.program),
274
+ componentCallRoots: collectComponentCallRootNamesFromSubtree(parsed.program),
275
+ hasUseClientDirective: hasModuleDirectiveInProgram(parsed.program, "use client"),
276
+ hasUseServerDirective: hasModuleDirectiveInProgram(parsed.program, "use server"),
277
+ identifierReferences: Array.from(identifierReferences).sort(),
278
+ jsxComponentRoots: collectJsxComponentRootNamesFromSubtree(parsed.program),
279
+ staticExports: body.flatMap(staticExportReference),
280
+ staticImports: body.flatMap(staticImportReference),
281
+ topLevelExportRenderInfo: collectTopLevelExportRenderInfoFromProgram(parsed.program),
282
+ };
283
+ }
284
+
285
+ function collectTopLevelExportRenderInfoFromProgram(program: unknown): TopLevelExportRenderInfo[] {
286
+ const declarations = new Map<string, unknown>();
287
+ const exported = new Map<string, string>();
288
+ const directExports = new Map<string, unknown>();
289
+ const aliasState = createComponentAliasState();
290
+
291
+ collectSimpleComponentAliasesFromNode(program, aliasState);
292
+
293
+ for (const statement of programBody(program)) {
294
+ collectTopLevelDeclarationReferences(statement, declarations);
295
+
296
+ if (statement.type === "ExportDefaultDeclaration") {
297
+ const declaration = readOptionalObject(statement.declaration);
298
+ directExports.set("default", declaration);
299
+ exported.set("default", "default");
300
+ continue;
301
+ }
302
+
303
+ if (statement.type !== "ExportNamedDeclaration") {
304
+ continue;
305
+ }
306
+
307
+ const declaration = readOptionalObject(statement.declaration);
308
+ if (declaration !== undefined) {
309
+ for (const name of exportedNames(statement)) {
310
+ exported.set(name, name);
311
+ directExports.set(name, declarationForExportedName(declaration, name) ?? declaration);
312
+ }
313
+ continue;
314
+ }
315
+
316
+ const specifiers = Array.isArray(statement.specifiers) ? statement.specifiers.map(readObject) : [];
317
+ for (const specifier of specifiers) {
318
+ const exportedName = exportedNameForSpecifier(specifier);
319
+ const localName = localNameForExportSpecifier(specifier);
320
+
321
+ if (exportedName !== undefined && localName !== undefined) {
322
+ exported.set(exportedName, localName);
323
+ }
324
+ }
325
+ }
326
+
327
+ return [...exported.entries()]
328
+ .map(([name, localName]) => {
329
+ const node = directExports.get(name) ?? declarations.get(localName);
330
+ const calledComponentRoots = node === undefined
331
+ ? []
332
+ : collectComponentCallRootNamesFromSubtree(
333
+ node,
334
+ aliasState.aliases,
335
+ );
336
+ const renderedComponentRoots = node === undefined
337
+ ? []
338
+ : collectJsxComponentRootNamesFromSubtree(
339
+ node,
340
+ aliasState.aliases,
341
+ );
342
+ const clientRuntime = node === undefined
343
+ ? false
344
+ : hasReachableExportClientRuntime({
345
+ aliases: aliasState.aliases,
346
+ declarations,
347
+ localName,
348
+ node,
349
+ });
350
+
351
+ return node === undefined
352
+ ? undefined
353
+ : {
354
+ calledComponentRoots,
355
+ clientRuntime,
356
+ name,
357
+ renderedComponentRoots,
358
+ };
359
+ })
360
+ .filter((item): item is TopLevelExportRenderInfo => item !== undefined)
361
+ .sort((left, right) => left.name.localeCompare(right.name));
362
+ }
363
+
364
+ function hasReachableLocalClientRuntime(options: {
365
+ aliases: ReadonlyMap<string, string>;
366
+ declarations: ReadonlyMap<string, unknown>;
367
+ roots: readonly string[];
368
+ seen: Set<string>;
369
+ }): boolean {
370
+ for (const root of options.roots) {
371
+ const resolved = options.aliases.get(root) ?? root;
372
+ if (options.seen.has(resolved)) {
373
+ continue;
374
+ }
375
+
376
+ const declaration = options.declarations.get(resolved);
377
+ if (declaration === undefined) {
378
+ continue;
379
+ }
380
+
381
+ if (hasClientRuntimeSyntaxNode(declaration)) {
382
+ return true;
383
+ }
384
+
385
+ options.seen.add(resolved);
386
+ const nestedCalledRoots = collectComponentCallRootNamesFromSubtree(
387
+ declaration,
388
+ options.aliases,
389
+ );
390
+ const nestedRenderedRoots = collectJsxComponentRootNamesFromSubtree(
391
+ declaration,
392
+ options.aliases,
393
+ );
394
+
395
+ if (
396
+ hasReachableLocalClientRuntime({
397
+ aliases: options.aliases,
398
+ declarations: options.declarations,
399
+ roots: [...nestedCalledRoots, ...nestedRenderedRoots],
400
+ seen: options.seen,
401
+ })
402
+ ) {
403
+ return true;
404
+ }
405
+ }
406
+
407
+ return false;
408
+ }
409
+
410
+ function hasReachableExportClientRuntime(options: {
411
+ aliases: ReadonlyMap<string, string>;
412
+ declarations: ReadonlyMap<string, unknown>;
413
+ localName: string;
414
+ node: unknown;
415
+ }): boolean {
416
+ return hasReachableExportClientRuntimeNode({
417
+ aliases: options.aliases,
418
+ declarations: options.declarations,
419
+ node: options.node,
420
+ seen: new Set([options.localName]),
421
+ });
422
+ }
423
+
424
+ function hasReachableExportClientRuntimeNode(options: {
425
+ aliases: ReadonlyMap<string, string>;
426
+ declarations: ReadonlyMap<string, unknown>;
427
+ node: unknown;
428
+ seen: Set<string>;
429
+ }): boolean {
430
+ if (hasClientRuntimeSyntaxNode(options.node)) {
431
+ return true;
432
+ }
433
+
434
+ const references = collectRuntimeReferenceRootNamesFromSubtree(
435
+ options.node,
436
+ options.aliases,
437
+ );
438
+
439
+ for (const reference of references) {
440
+ const declaration = options.declarations.get(reference);
441
+ if (
442
+ declaration !== undefined &&
443
+ !isFunctionLikeClientRuntimeDeclaration(declaration) &&
444
+ hasClientRuntimeSyntaxNode(declaration)
445
+ ) {
446
+ return true;
447
+ }
448
+ }
449
+
450
+ const calledRoots = collectRuntimeCallRootNamesFromSubtree(
451
+ options.node,
452
+ options.aliases,
453
+ );
454
+ const renderedRoots = collectJsxComponentRootNamesFromSubtree(
455
+ options.node,
456
+ options.aliases,
457
+ );
458
+
459
+ if (
460
+ hasReachableLocalClientRuntime({
461
+ aliases: options.aliases,
462
+ declarations: options.declarations,
463
+ roots: [...calledRoots, ...renderedRoots],
464
+ seen: options.seen,
465
+ })
466
+ ) {
467
+ return true;
468
+ }
469
+
470
+ return false;
471
+ }
472
+
473
+ function collectRuntimeReferenceRootNamesFromSubtree(
474
+ node: unknown,
475
+ aliases: ReadonlyMap<string, string>,
476
+ ): string[] {
477
+ const names = new Set<string>();
478
+ collectRuntimeReferenceRootNamesFromNode(
479
+ node,
480
+ names,
481
+ createComponentAliasState(aliases),
482
+ new Set(),
483
+ undefined,
484
+ undefined,
485
+ );
486
+ return Array.from(names).sort();
487
+ }
488
+
489
+ function collectRuntimeCallRootNamesFromSubtree(
490
+ node: unknown,
491
+ aliases: ReadonlyMap<string, string>,
492
+ ): string[] {
493
+ const names = new Set<string>();
494
+ collectRuntimeCallRootNamesFromNode(
495
+ node,
496
+ names,
497
+ createComponentAliasState(aliases),
498
+ new Set(),
499
+ );
500
+ return Array.from(names).sort();
501
+ }
502
+
503
+ function collectRuntimeReferenceRootNamesFromNode(
504
+ node: unknown,
505
+ names: Set<string>,
506
+ state: ComponentAliasState,
507
+ shadowed: ReadonlySet<string>,
508
+ parent: Record<string, unknown> | undefined,
509
+ parentKey: string | undefined,
510
+ ): void {
511
+ if (Array.isArray(node)) {
512
+ for (const child of node) {
513
+ collectRuntimeReferenceRootNamesFromNode(
514
+ child,
515
+ names,
516
+ state,
517
+ shadowed,
518
+ parent,
519
+ parentKey,
520
+ );
521
+ }
522
+ return;
523
+ }
524
+
525
+ const object = readOptionalObject(node);
526
+ if (object === undefined || shouldSkipRuntimeReferenceNode(object)) {
527
+ return;
528
+ }
529
+
530
+ if (object.type === "Identifier" && typeof object.name === "string") {
531
+ if (
532
+ !shadowed.has(object.name) &&
533
+ isRuntimeReferenceIdentifier(parent, parentKey)
534
+ ) {
535
+ names.add(state.aliases.get(object.name) ?? object.name);
536
+ }
537
+ return;
538
+ }
539
+
540
+ if (isFunctionLikeClientRuntimeDeclaration(object)) {
541
+ const nextShadowed = new Set(shadowed);
542
+ collectBindingNamesInto(object.id, nextShadowed);
543
+ for (const parameter of readArray(object.params)) {
544
+ collectBindingNamesInto(parameter, nextShadowed);
545
+ }
546
+ collectRuntimeReferenceRootNamesFromNode(
547
+ object.body,
548
+ names,
549
+ state,
550
+ addBlockBindingNames(object.body, nextShadowed),
551
+ object,
552
+ "body",
553
+ );
554
+ return;
555
+ }
556
+
557
+ const childShadowed =
558
+ object.type === "Program" || object.type === "BlockStatement"
559
+ ? addBlockBindingNames(object, new Set(shadowed))
560
+ : shadowed;
561
+
562
+ for (const [key, value] of Object.entries(object)) {
563
+ if (shouldSkipRuntimeReferenceProperty(object, key)) {
564
+ continue;
565
+ }
566
+
567
+ collectRuntimeReferenceRootNamesFromNode(
568
+ value,
569
+ names,
570
+ state,
571
+ childShadowed,
572
+ object,
573
+ key,
574
+ );
575
+ }
576
+ }
577
+
578
+ function collectRuntimeCallRootNamesFromNode(
579
+ node: unknown,
580
+ names: Set<string>,
581
+ state: ComponentAliasState,
582
+ shadowed: ReadonlySet<string>,
583
+ ): void {
584
+ if (Array.isArray(node)) {
585
+ for (const child of node) {
586
+ collectRuntimeCallRootNamesFromNode(child, names, state, shadowed);
587
+ }
588
+ return;
589
+ }
590
+
591
+ const object = readOptionalObject(node);
592
+ if (object === undefined || shouldSkipRuntimeReferenceNode(object)) {
593
+ return;
594
+ }
595
+
596
+ if (isFunctionLikeClientRuntimeDeclaration(object)) {
597
+ const nextShadowed = new Set(shadowed);
598
+ collectBindingNamesInto(object.id, nextShadowed);
599
+ for (const parameter of readArray(object.params)) {
600
+ collectBindingNamesInto(parameter, nextShadowed);
601
+ }
602
+ collectRuntimeCallRootNamesFromNode(
603
+ object.body,
604
+ names,
605
+ state,
606
+ addBlockBindingNames(object.body, nextShadowed),
607
+ );
608
+ return;
609
+ }
610
+
611
+ if (object.type === "CallExpression") {
612
+ const root = expressionRootName(readOptionalObject(object.callee), state);
613
+ if (root !== undefined && !shadowed.has(root)) {
614
+ names.add(root);
615
+ }
616
+ }
617
+
618
+ const childShadowed =
619
+ object.type === "Program" || object.type === "BlockStatement"
620
+ ? addBlockBindingNames(object, new Set(shadowed))
621
+ : shadowed;
622
+
623
+ for (const [key, value] of Object.entries(object)) {
624
+ if (shouldSkipRuntimeReferenceProperty(object, key)) {
625
+ continue;
626
+ }
627
+
628
+ collectRuntimeCallRootNamesFromNode(value, names, state, childShadowed);
629
+ }
630
+ }
631
+
632
+ function shouldSkipRuntimeReferenceNode(node: Record<string, unknown>): boolean {
633
+ return (
634
+ (typeof node.type === "string" && node.type.startsWith("TS")) ||
635
+ node.type === "ImportDeclaration"
636
+ );
637
+ }
638
+
639
+ function shouldSkipRuntimeReferenceProperty(
640
+ node: Record<string, unknown>,
641
+ key: string,
642
+ ): boolean {
643
+ if (key === "type" || key === "start" || key === "end" || key === "loc") {
644
+ return true;
645
+ }
646
+
647
+ if (key === "id" && isDeclarationWithId(node)) {
648
+ return true;
649
+ }
650
+
651
+ if (key === "params" && isFunctionLikeClientRuntimeDeclaration(node)) {
652
+ return true;
653
+ }
654
+
655
+ if (key === "key" && isNonComputedPropertyKey(node)) {
656
+ return true;
657
+ }
658
+
659
+ return false;
660
+ }
661
+
662
+ function isRuntimeReferenceIdentifier(
663
+ parent: Record<string, unknown> | undefined,
664
+ parentKey: string | undefined,
665
+ ): boolean {
666
+ if (parent === undefined) {
667
+ return true;
668
+ }
669
+
670
+ if (parent.type === "MemberExpression" && parentKey === "property" && parent.computed !== true) {
671
+ return false;
672
+ }
673
+
674
+ if (parentKey === "id" && isDeclarationWithId(parent)) {
675
+ return false;
676
+ }
677
+
678
+ if (parentKey === "params" && isFunctionLikeClientRuntimeDeclaration(parent)) {
679
+ return false;
680
+ }
681
+
682
+ if (parentKey === "key" && isNonComputedPropertyKey(parent)) {
683
+ return false;
684
+ }
685
+
686
+ return !(
687
+ (parent.type === "BreakStatement" ||
688
+ parent.type === "ContinueStatement" ||
689
+ parent.type === "LabeledStatement") &&
690
+ parentKey === "label"
691
+ );
692
+ }
693
+
694
+ function addBlockBindingNames(
695
+ node: unknown,
696
+ names: Set<string>,
697
+ ): ReadonlySet<string> {
698
+ const body = readArray(readObject(node).body);
699
+
700
+ for (const statement of body) {
701
+ const object = readObject(statement);
702
+ if (object.type === "VariableDeclaration") {
703
+ for (const declaration of readArray(object.declarations)) {
704
+ collectBindingNamesInto(readObject(declaration).id, names);
705
+ }
706
+ continue;
707
+ }
708
+
709
+ if (object.type === "FunctionDeclaration" || object.type === "ClassDeclaration") {
710
+ collectBindingNamesInto(object.id, names);
711
+ }
712
+ }
713
+
714
+ return names;
715
+ }
716
+
717
+ function collectBindingNamesInto(node: unknown, names: Set<string>): void {
718
+ for (const name of bindingNames(node)) {
719
+ names.add(name);
720
+ }
721
+ }
722
+
723
+ function isFunctionLikeClientRuntimeDeclaration(node: unknown): boolean {
724
+ const object = readObject(node);
725
+ return (
726
+ object.type === "FunctionDeclaration" ||
727
+ object.type === "FunctionExpression" ||
728
+ object.type === "ArrowFunctionExpression"
729
+ );
730
+ }
731
+
732
+ function isDeclarationWithId(node: Record<string, unknown>): boolean {
733
+ return (
734
+ node.type === "VariableDeclarator" ||
735
+ node.type === "FunctionDeclaration" ||
736
+ node.type === "FunctionExpression" ||
737
+ node.type === "ClassDeclaration" ||
738
+ node.type === "ClassExpression"
739
+ );
740
+ }
741
+
742
+ function isNonComputedPropertyKey(node: Record<string, unknown>): boolean {
743
+ return (
744
+ (node.type === "Property" ||
745
+ node.type === "ObjectProperty" ||
746
+ node.type === "PropertyDefinition" ||
747
+ node.type === "MethodDefinition") &&
748
+ node.computed !== true
749
+ );
750
+ }
751
+
752
+ export function hasModuleDirective(input: {
753
+ code: string;
754
+ directive: string;
755
+ filename?: string | undefined;
756
+ }): boolean {
757
+ const parsed = parseModule(input.code, input.filename);
758
+
759
+ return hasModuleDirectiveInProgram(parsed.program, input.directive);
760
+ }
761
+
762
+ function hasModuleDirectiveInProgram(program: unknown, expectedDirective: string): boolean {
763
+ for (const statement of programBody(program)) {
764
+ if (statement.type !== "ExpressionStatement") {
765
+ return false;
766
+ }
767
+
768
+ const directive = statement.directive;
769
+ if (typeof directive !== "string") {
770
+ return false;
771
+ }
772
+
773
+ if (directive === expectedDirective) {
774
+ return true;
775
+ }
776
+ }
777
+
778
+ return false;
779
+ }
780
+
781
+ export function hasClientRuntimeSyntax(input: {
782
+ code: string;
783
+ filename?: string | undefined;
784
+ }): boolean {
785
+ const parsed = parseModule(input.code, input.filename);
786
+
787
+ return hasClientRuntimeSyntaxNode(parsed.program);
788
+ }
789
+
790
+ interface Replacement {
791
+ end: number;
792
+ start: number;
793
+ text: string;
794
+ }
795
+
796
+ function parseModule(code: string, filename: string | undefined) {
797
+ return parseModuleContext(createCompilerModuleContext({ code, filename }));
798
+ }
799
+
800
+ function parseModuleContext(context: CompilerModuleContext): CompilerModuleContext {
801
+ if (context.parseErrors.length > 0) {
802
+ throw new Error(
803
+ `${context.filename}: ${context.parseErrors
804
+ .map((error) => readObject(error).message)
805
+ .filter((message): message is string => typeof message === "string")
806
+ .join("\n")}`,
807
+ );
808
+ }
809
+
810
+ return context;
811
+ }
812
+
813
+ function programBody(program: unknown): Record<string, unknown>[] {
814
+ const body = readObject(program).body;
815
+ return Array.isArray(body) ? body.map(readObject) : [];
816
+ }
817
+
818
+ function exportedNames(statement: Record<string, unknown>): string[] {
819
+ if (statement.type === "ExportDefaultDeclaration") {
820
+ return ["default"];
821
+ }
822
+
823
+ if (statement.type !== "ExportNamedDeclaration") {
824
+ return [];
825
+ }
826
+
827
+ const declaration = readOptionalObject(statement.declaration);
828
+
829
+ if (declaration !== undefined) {
830
+ if (declaration.type === "FunctionDeclaration") {
831
+ const name = readOptionalObject(declaration.id)?.name;
832
+ return typeof name === "string" ? [name] : [];
833
+ }
834
+
835
+ if (declaration.type === "VariableDeclaration") {
836
+ const declarations = Array.isArray(declaration.declarations) ? declaration.declarations : [];
837
+
838
+ return declarations.flatMap((item) => bindingNames(readObject(item).id));
839
+ }
840
+
841
+ return [];
842
+ }
843
+
844
+ const specifiers = Array.isArray(statement.specifiers) ? statement.specifiers : [];
845
+ return specifiers.flatMap((specifier) => {
846
+ const name = exportedNameForSpecifier(readObject(specifier));
847
+
848
+ return typeof name === "string" ? [name] : [];
849
+ });
850
+ }
851
+
852
+ function collectTopLevelDeclarationReferences(
853
+ statement: Record<string, unknown>,
854
+ declarations: Map<string, unknown>,
855
+ ): void {
856
+ const declaration =
857
+ statement.type === "ExportNamedDeclaration"
858
+ ? readOptionalObject(statement.declaration)
859
+ : statement;
860
+
861
+ if (declaration?.type === "FunctionDeclaration") {
862
+ const name = readOptionalObject(declaration.id)?.name;
863
+
864
+ if (typeof name === "string") {
865
+ declarations.set(name, declaration);
866
+ }
867
+ return;
868
+ }
869
+
870
+ if (declaration?.type !== "VariableDeclaration") {
871
+ return;
872
+ }
873
+
874
+ const declarators = Array.isArray(declaration.declarations)
875
+ ? declaration.declarations.map(readObject)
876
+ : [];
877
+
878
+ for (const declarator of declarators) {
879
+ const id = readOptionalObject(declarator.id);
880
+ const name = typeof id?.name === "string" ? id.name : undefined;
881
+
882
+ if (name !== undefined) {
883
+ declarations.set(name, readOptionalObject(declarator.init) ?? declarator);
884
+ }
885
+ }
886
+ }
887
+
888
+ function declarationForExportedName(
889
+ declaration: Record<string, unknown>,
890
+ name: string,
891
+ ): unknown | undefined {
892
+ if (declaration.type === "VariableDeclaration") {
893
+ const declarators = Array.isArray(declaration.declarations)
894
+ ? declaration.declarations.map(readObject)
895
+ : [];
896
+
897
+ for (const declarator of declarators) {
898
+ if (bindingNames(declarator.id).includes(name)) {
899
+ return readOptionalObject(declarator.init) ?? declarator;
900
+ }
901
+ }
902
+ }
903
+
904
+ return declaration;
905
+ }
906
+
907
+ function localNameForExportSpecifier(specifier: Record<string, unknown>): string | undefined {
908
+ const local = readOptionalObject(specifier.local);
909
+ const name = local?.name ?? local?.value;
910
+
911
+ return typeof name === "string" ? name : undefined;
912
+ }
913
+
914
+ function exportDeclarationDemotion(
915
+ code: string,
916
+ statement: Record<string, unknown>,
917
+ names: ReadonlySet<string>,
918
+ ): Replacement | undefined {
919
+ if (statement.type !== "ExportNamedDeclaration") {
920
+ return undefined;
921
+ }
922
+
923
+ const declaration = readOptionalObject(statement.declaration);
924
+ const exported = exportedNames(statement);
925
+
926
+ if (exported.length === 0 || !exported.every((name) => names.has(name))) {
927
+ return partialSpecifierExportReplacement(code, statement, names);
928
+ }
929
+
930
+ const range = statementRange(code, statement);
931
+
932
+ if (range === undefined) {
933
+ return undefined;
934
+ }
935
+
936
+ if (declaration?.type === "FunctionDeclaration" || declaration?.type === "VariableDeclaration") {
937
+ return { ...range, text: `${nodeText(code, declaration)}\n` };
938
+ }
939
+
940
+ return { ...range, text: "" };
941
+ }
942
+
943
+ function exportDeclarationReplacement(
944
+ code: string,
945
+ statement: Record<string, unknown>,
946
+ names: ReadonlySet<string>,
947
+ ): Replacement | undefined {
948
+ if (statement.type === "ExportDefaultDeclaration") {
949
+ const range = statementRange(code, statement);
950
+ return names.has("default") && range !== undefined ? { ...range, text: "" } : undefined;
951
+ }
952
+
953
+ if (statement.type !== "ExportNamedDeclaration") {
954
+ return undefined;
955
+ }
956
+
957
+ const partial = partialExportDeclarationReplacement(code, statement, names);
958
+
959
+ if (partial !== undefined) {
960
+ return partial;
961
+ }
962
+
963
+ const exported = exportedNames(statement);
964
+ if (exported.length === 0 || !exported.every((name) => names.has(name))) {
965
+ return undefined;
966
+ }
967
+
968
+ const range = statementRange(code, statement);
969
+ return range === undefined ? undefined : { ...range, text: "" };
970
+ }
971
+
972
+ function partialExportDeclarationReplacement(
973
+ code: string,
974
+ statement: Record<string, unknown>,
975
+ names: ReadonlySet<string>,
976
+ ): Replacement | undefined {
977
+ const declaration = readOptionalObject(statement.declaration);
978
+
979
+ if (declaration?.type === "VariableDeclaration") {
980
+ return partialVariableExportReplacement(code, statement, declaration, names);
981
+ }
982
+
983
+ if (declaration !== undefined) {
984
+ return undefined;
985
+ }
986
+
987
+ return partialSpecifierExportReplacement(code, statement, names);
988
+ }
989
+
990
+ function partialVariableExportReplacement(
991
+ code: string,
992
+ statement: Record<string, unknown>,
993
+ declaration: Record<string, unknown>,
994
+ names: ReadonlySet<string>,
995
+ ): Replacement | undefined {
996
+ const declarations = Array.isArray(declaration.declarations)
997
+ ? declaration.declarations.map(readObject)
998
+ : [];
999
+
1000
+ if (declarations.length <= 1) {
1001
+ return undefined;
1002
+ }
1003
+
1004
+ const kept = declarations.filter((declarator) => {
1005
+ const declaredNames = bindingNames(declarator.id);
1006
+ return declaredNames.every((name) => !names.has(name));
1007
+ });
1008
+
1009
+ if (kept.length === declarations.length) {
1010
+ return undefined;
1011
+ }
1012
+
1013
+ if (kept.length === 0) {
1014
+ const range = statementRange(code, statement);
1015
+ return range === undefined ? undefined : { ...range, text: "" };
1016
+ }
1017
+
1018
+ const range = statementRange(code, statement);
1019
+ const kind = typeof declaration.kind === "string" ? declaration.kind : "const";
1020
+
1021
+ return range === undefined
1022
+ ? undefined
1023
+ : {
1024
+ ...range,
1025
+ text: `export ${kind} ${kept.map((item) => nodeText(code, item)).join(", ")};\n`,
1026
+ };
1027
+ }
1028
+
1029
+ function partialSpecifierExportReplacement(
1030
+ code: string,
1031
+ statement: Record<string, unknown>,
1032
+ names: ReadonlySet<string>,
1033
+ ): Replacement | undefined {
1034
+ const specifiers = Array.isArray(statement.specifiers)
1035
+ ? statement.specifiers.map(readObject)
1036
+ : [];
1037
+
1038
+ if (specifiers.length <= 1) {
1039
+ return undefined;
1040
+ }
1041
+
1042
+ const kept = specifiers.filter((specifier) => {
1043
+ const name = exportedNameForSpecifier(specifier);
1044
+ return typeof name !== "string" || !names.has(name);
1045
+ });
1046
+
1047
+ if (kept.length === specifiers.length) {
1048
+ return undefined;
1049
+ }
1050
+
1051
+ const range = statementRange(code, statement);
1052
+ if (range === undefined) {
1053
+ return undefined;
1054
+ }
1055
+
1056
+ if (kept.length === 0) {
1057
+ return { ...range, text: "" };
1058
+ }
1059
+
1060
+ const source = readOptionalObject(statement.source);
1061
+ const sourceText = source === undefined ? "" : ` from ${nodeText(code, source)}`;
1062
+ const exportKind = statement.exportKind === "type" ? "export type" : "export";
1063
+
1064
+ return {
1065
+ ...range,
1066
+ text: `${exportKind} { ${kept.map((item) => nodeText(code, item)).join(", ")} }${sourceText};\n`,
1067
+ };
1068
+ }
1069
+
1070
+ function staticModuleSpecifier(statement: Record<string, unknown>): string[] {
1071
+ if (statement.type === "ImportDeclaration") {
1072
+ if (statement.importKind === "type") {
1073
+ return [];
1074
+ }
1075
+
1076
+ return sourceValue(statement);
1077
+ }
1078
+
1079
+ if (statement.type === "ExportAllDeclaration" || statement.type === "ExportNamedDeclaration") {
1080
+ if (statement.exportKind === "type") {
1081
+ return [];
1082
+ }
1083
+
1084
+ return sourceValue(statement);
1085
+ }
1086
+
1087
+ return [];
1088
+ }
1089
+
1090
+ function staticImportReference(
1091
+ statement: Record<string, unknown>,
1092
+ ): ClientRouteStaticImportReference[] {
1093
+ if (statement.type !== "ImportDeclaration" || statement.importKind === "type") {
1094
+ return [];
1095
+ }
1096
+
1097
+ const source = sourceValue(statement)[0];
1098
+ if (source === undefined) {
1099
+ return [];
1100
+ }
1101
+
1102
+ const specifiers = Array.isArray(statement.specifiers)
1103
+ ? statement.specifiers.map(readObject)
1104
+ : [];
1105
+ const importSpecifiers = specifiers.flatMap(staticImportSpecifierReference);
1106
+ const localNames = specifiers
1107
+ .filter((specifier) => specifier.importKind !== "type")
1108
+ .flatMap((specifier) => {
1109
+ const local = readOptionalObject(specifier.local);
1110
+ return typeof local?.name === "string" ? [local.name] : [];
1111
+ });
1112
+
1113
+ return [
1114
+ {
1115
+ localNames,
1116
+ sideEffect: localNames.length === 0,
1117
+ source,
1118
+ specifiers: importSpecifiers,
1119
+ },
1120
+ ];
1121
+ }
1122
+
1123
+ function staticImportSpecifierReference(
1124
+ specifier: Record<string, unknown>,
1125
+ ): StaticImportSpecifierReference[] {
1126
+ if (specifier.importKind === "type") {
1127
+ return [];
1128
+ }
1129
+
1130
+ const localName = readOptionalObject(specifier.local)?.name;
1131
+ if (typeof localName !== "string") {
1132
+ return [];
1133
+ }
1134
+
1135
+ if (specifier.type === "ImportDefaultSpecifier") {
1136
+ return [{ importedName: "default", kind: "default", localName }];
1137
+ }
1138
+
1139
+ if (specifier.type === "ImportNamespaceSpecifier") {
1140
+ return [{ importedName: "*", kind: "namespace", localName }];
1141
+ }
1142
+
1143
+ const imported = readOptionalObject(specifier.imported);
1144
+ const importedName = imported?.name ?? imported?.value;
1145
+ return typeof importedName === "string"
1146
+ ? [{ importedName, kind: "named", localName }]
1147
+ : [];
1148
+ }
1149
+
1150
+ function staticExportReference(statement: Record<string, unknown>): StaticExportReference[] {
1151
+ if (statement.type === "ExportAllDeclaration") {
1152
+ if (statement.exportKind === "type") {
1153
+ return [];
1154
+ }
1155
+
1156
+ const source = sourceValue(statement)[0];
1157
+ return source === undefined
1158
+ ? []
1159
+ : [{ exportedNames: [], exportAll: true, source }];
1160
+ }
1161
+
1162
+ if (statement.type !== "ExportNamedDeclaration" || statement.exportKind === "type") {
1163
+ return [];
1164
+ }
1165
+
1166
+ const source = sourceValue(statement)[0];
1167
+ if (source === undefined) {
1168
+ return [];
1169
+ }
1170
+
1171
+ return [
1172
+ {
1173
+ exportedNames: exportedNames(statement),
1174
+ exportAll: false,
1175
+ source,
1176
+ },
1177
+ ];
1178
+ }
1179
+
1180
+ function sourceValue(statement: Record<string, unknown>): string[] {
1181
+ const source = readOptionalObject(statement.source);
1182
+ const value = source?.value;
1183
+
1184
+ return typeof value === "string" ? [value] : [];
1185
+ }
1186
+
1187
+ function collectJsxComponentRootNamesFromNode(
1188
+ node: unknown,
1189
+ names: Set<string>,
1190
+ ): void {
1191
+ if (Array.isArray(node)) {
1192
+ for (const child of node) {
1193
+ collectJsxComponentRootNamesFromNode(child, names);
1194
+ }
1195
+ return;
1196
+ }
1197
+
1198
+ const object = readOptionalObject(node);
1199
+ if (object === undefined) {
1200
+ return;
1201
+ }
1202
+
1203
+ if (object.type === "JSXElement") {
1204
+ const opening = readOptionalObject(object.openingElement);
1205
+ const nameNode = readOptionalObject(opening?.name);
1206
+ const name = jsxNameRoot(nameNode);
1207
+ if (name !== undefined && (nameNode?.type === "JSXMemberExpression" || /^[A-Z]/.test(name))) {
1208
+ names.add(name);
1209
+ }
1210
+ }
1211
+
1212
+ for (const [key, value] of Object.entries(object)) {
1213
+ if (key === "type" || key === "start" || key === "end" || key === "loc") {
1214
+ continue;
1215
+ }
1216
+
1217
+ collectJsxComponentRootNamesFromNode(value, names);
1218
+ }
1219
+ }
1220
+
1221
+ function collectJsxComponentRootNamesFromSubtree(
1222
+ node: unknown,
1223
+ outerAliases?: ReadonlyMap<string, string> | undefined,
1224
+ ): string[] {
1225
+ const names = new Set<string>();
1226
+ const aliasState = createComponentAliasState(outerAliases);
1227
+
1228
+ collectJsxComponentRootNamesFromNode(node, names);
1229
+ collectSimpleComponentAliasesFromNode(node, aliasState);
1230
+ expandJsxComponentAliasRoots(names, aliasState.aliases);
1231
+ return Array.from(names).sort();
1232
+ }
1233
+
1234
+ function collectComponentCallRootNamesFromNode(
1235
+ node: unknown,
1236
+ names: Set<string>,
1237
+ state: ComponentAliasState,
1238
+ ): void {
1239
+ if (Array.isArray(node)) {
1240
+ for (const child of node) {
1241
+ collectComponentCallRootNamesFromNode(child, names, state);
1242
+ }
1243
+ return;
1244
+ }
1245
+
1246
+ const object = readOptionalObject(node);
1247
+ if (object === undefined) {
1248
+ return;
1249
+ }
1250
+
1251
+ if (typeof object.type === "string" && object.type.startsWith("TS")) {
1252
+ return;
1253
+ }
1254
+
1255
+ if (object.type === "CallExpression") {
1256
+ const root = expressionRootName(readOptionalObject(object.callee), state);
1257
+ if (root !== undefined && /^[A-Z]/.test(root)) {
1258
+ names.add(root);
1259
+ }
1260
+ }
1261
+
1262
+ for (const [key, value] of Object.entries(object)) {
1263
+ if (key === "type" || key === "start" || key === "end" || key === "loc") {
1264
+ continue;
1265
+ }
1266
+
1267
+ collectComponentCallRootNamesFromNode(value, names, state);
1268
+ }
1269
+ }
1270
+
1271
+ function collectComponentCallRootNamesFromSubtree(
1272
+ node: unknown,
1273
+ outerAliases?: ReadonlyMap<string, string> | undefined,
1274
+ ): string[] {
1275
+ const names = new Set<string>();
1276
+ const aliasState = createComponentAliasState(outerAliases);
1277
+
1278
+ collectSimpleComponentAliasesFromNode(node, aliasState);
1279
+ collectComponentCallRootNamesFromNode(node, names, aliasState);
1280
+ expandJsxComponentAliasRoots(names, aliasState.aliases);
1281
+ return Array.from(names).sort();
1282
+ }
1283
+
1284
+ function jsxNameRoot(node: Record<string, unknown> | undefined): string | undefined {
1285
+ if (node === undefined) {
1286
+ return undefined;
1287
+ }
1288
+
1289
+ if (typeof node.name === "string") {
1290
+ return node.name;
1291
+ }
1292
+
1293
+ if (node.type === "JSXMemberExpression") {
1294
+ return jsxNameRoot(readOptionalObject(node.object));
1295
+ }
1296
+
1297
+ return undefined;
1298
+ }
1299
+
1300
+ function collectSimpleComponentAliasesFromNode(
1301
+ node: unknown,
1302
+ state: ComponentAliasState,
1303
+ ): void {
1304
+ if (Array.isArray(node)) {
1305
+ for (const child of node) {
1306
+ collectSimpleComponentAliasesFromNode(child, state);
1307
+ }
1308
+ return;
1309
+ }
1310
+
1311
+ const object = readOptionalObject(node);
1312
+ if (object === undefined) {
1313
+ return;
1314
+ }
1315
+
1316
+ if (typeof object.type === "string" && object.type.startsWith("TS")) {
1317
+ return;
1318
+ }
1319
+
1320
+ if (object.type === "VariableDeclaration") {
1321
+ const constant = object.kind === "const";
1322
+ const declarations = Array.isArray(object.declarations) ? object.declarations : [];
1323
+ for (const declaration of declarations) {
1324
+ collectVariableDeclaratorComponentAliases(readOptionalObject(declaration), state, constant);
1325
+ }
1326
+ return;
1327
+ }
1328
+
1329
+ if (object.type === "VariableDeclarator") {
1330
+ collectVariableDeclaratorComponentAliases(object, state, false);
1331
+ }
1332
+
1333
+ if (object.type === "AssignmentExpression") {
1334
+ collectAssignmentComponentAlias(object, state);
1335
+ }
1336
+
1337
+ if (object.type === "CallExpression") {
1338
+ collectObjectAssignComponentAliases(object, state);
1339
+ }
1340
+
1341
+ for (const [key, value] of Object.entries(object)) {
1342
+ if (key === "type" || key === "start" || key === "end" || key === "loc") {
1343
+ continue;
1344
+ }
1345
+
1346
+ collectSimpleComponentAliasesFromNode(value, state);
1347
+ }
1348
+ }
1349
+
1350
+ function createComponentAliasState(
1351
+ aliases?: ReadonlyMap<string, string> | undefined,
1352
+ ): ComponentAliasState {
1353
+ return {
1354
+ aliases: new Map(aliases),
1355
+ stringConstants: new Map(),
1356
+ };
1357
+ }
1358
+
1359
+ function collectVariableDeclaratorComponentAliases(
1360
+ object: Record<string, unknown> | undefined,
1361
+ state: ComponentAliasState,
1362
+ constant: boolean,
1363
+ ): void {
1364
+ if (object?.type !== "VariableDeclarator") {
1365
+ return;
1366
+ }
1367
+
1368
+ const id = readOptionalObject(object.id);
1369
+ const init = readOptionalObject(object.init);
1370
+ const aliasName = typeof id?.name === "string" ? id.name : undefined;
1371
+
1372
+ if (aliasName === undefined) {
1373
+ return;
1374
+ }
1375
+
1376
+ if (constant) {
1377
+ const stringValue = stringExpressionValue(init, state);
1378
+ if (stringValue !== undefined) {
1379
+ state.stringConstants.set(aliasName, stringValue);
1380
+ }
1381
+ }
1382
+
1383
+ collectObjectLiteralComponentAliases(aliasName, init, state);
1384
+
1385
+ const rootName = expressionRootName(init, state);
1386
+ if (rootName !== undefined) {
1387
+ state.aliases.set(aliasName, rootName);
1388
+ }
1389
+ }
1390
+
1391
+ function collectObjectLiteralComponentAliases(
1392
+ objectName: string | undefined,
1393
+ init: Record<string, unknown> | undefined,
1394
+ state: ComponentAliasState,
1395
+ ): void {
1396
+ if (objectName === undefined || init?.type !== "ObjectExpression") {
1397
+ return;
1398
+ }
1399
+
1400
+ const properties = Array.isArray(init.properties) ? init.properties : [];
1401
+ for (const propertyValue of properties) {
1402
+ const property = readOptionalObject(propertyValue);
1403
+ if (property?.type !== "Property") {
1404
+ continue;
1405
+ }
1406
+
1407
+ const keyName = propertyName(readOptionalObject(property.key), property.computed === true, state);
1408
+ const valueName = expressionRootName(readOptionalObject(property.value), state);
1409
+ if (keyName !== undefined && valueName !== undefined) {
1410
+ state.aliases.set(`${objectName}.${keyName}`, valueName);
1411
+ }
1412
+ }
1413
+ }
1414
+
1415
+ function collectAssignmentComponentAlias(
1416
+ node: Record<string, unknown>,
1417
+ state: ComponentAliasState,
1418
+ ): void {
1419
+ if (node.operator !== "=") {
1420
+ return;
1421
+ }
1422
+
1423
+ const left = readOptionalObject(node.left);
1424
+ const right = readOptionalObject(node.right);
1425
+ if (left?.type !== "MemberExpression") {
1426
+ return;
1427
+ }
1428
+
1429
+ const objectRoot = expressionRootName(readOptionalObject(left.object), state);
1430
+ const memberName = propertyName(readOptionalObject(left.property), left.computed === true, state);
1431
+ const valueName = expressionRootName(right, state);
1432
+ if (objectRoot !== undefined && memberName !== undefined && valueName !== undefined) {
1433
+ state.aliases.set(`${objectRoot}.${memberName}`, valueName);
1434
+ }
1435
+ }
1436
+
1437
+ function collectObjectAssignComponentAliases(
1438
+ node: Record<string, unknown>,
1439
+ state: ComponentAliasState,
1440
+ ): void {
1441
+ if (!isObjectAssignCall(node)) {
1442
+ return;
1443
+ }
1444
+
1445
+ const args = Array.isArray(node.arguments) ? node.arguments.map(readOptionalObject) : [];
1446
+ const target = expressionRootName(args[0], state);
1447
+ if (target === undefined) {
1448
+ return;
1449
+ }
1450
+
1451
+ for (const source of args.slice(1)) {
1452
+ collectObjectLiteralComponentAliases(target, source, state);
1453
+ }
1454
+ }
1455
+
1456
+ function isObjectAssignCall(node: Record<string, unknown>): boolean {
1457
+ const callee = readOptionalObject(node.callee);
1458
+ if (callee?.type !== "MemberExpression" || callee.computed === true) {
1459
+ return false;
1460
+ }
1461
+
1462
+ const object = readOptionalObject(callee.object);
1463
+ const property = readOptionalObject(callee.property);
1464
+ return object?.type === "Identifier" && object.name === "Object" &&
1465
+ property?.type === "Identifier" && property.name === "assign";
1466
+ }
1467
+
1468
+ function expandJsxComponentAliasRoots(
1469
+ names: Set<string>,
1470
+ aliases: ReadonlyMap<string, string>,
1471
+ ): void {
1472
+ let changed = true;
1473
+
1474
+ while (changed) {
1475
+ changed = false;
1476
+
1477
+ for (const [alias, root] of aliases) {
1478
+ if (names.has(alias) && !names.has(root)) {
1479
+ names.add(root);
1480
+ changed = true;
1481
+ }
1482
+ }
1483
+ }
1484
+ }
1485
+
1486
+ function expressionRootName(
1487
+ node: Record<string, unknown> | undefined,
1488
+ state?: ComponentAliasState | undefined,
1489
+ ): string | undefined {
1490
+ if (node === undefined) {
1491
+ return undefined;
1492
+ }
1493
+
1494
+ if (node.type === "Identifier" && typeof node.name === "string") {
1495
+ return node.name;
1496
+ }
1497
+
1498
+ if (node.type === "MemberExpression") {
1499
+ const objectRoot = expressionRootName(readOptionalObject(node.object), state);
1500
+ const aliasedObjectRoot =
1501
+ objectRoot === undefined ? undefined : state?.aliases.get(objectRoot) ?? objectRoot;
1502
+ const memberName = propertyName(readOptionalObject(node.property), node.computed === true, state);
1503
+ const memberAlias =
1504
+ objectRoot !== undefined && memberName !== undefined
1505
+ ? state?.aliases.get(`${objectRoot}.${memberName}`) ??
1506
+ (aliasedObjectRoot === undefined
1507
+ ? undefined
1508
+ : state?.aliases.get(`${aliasedObjectRoot}.${memberName}`))
1509
+ : undefined;
1510
+
1511
+ return memberAlias ??
1512
+ (node.computed === true && memberName === undefined
1513
+ ? uniqueObjectMemberAlias(aliasedObjectRoot, state)
1514
+ : aliasedObjectRoot);
1515
+ }
1516
+
1517
+ if (node.type === "ConditionalExpression") {
1518
+ return uniqueDefinedString([
1519
+ expressionRootName(readOptionalObject(node.consequent), state),
1520
+ expressionRootName(readOptionalObject(node.alternate), state),
1521
+ ]);
1522
+ }
1523
+
1524
+ if (
1525
+ node.type === "ChainExpression" ||
1526
+ node.type === "TSAsExpression" ||
1527
+ node.type === "TSSatisfiesExpression" ||
1528
+ node.type === "TSNonNullExpression" ||
1529
+ node.type === "ParenthesizedExpression"
1530
+ ) {
1531
+ return expressionRootName(readOptionalObject(node.expression), state);
1532
+ }
1533
+
1534
+ return undefined;
1535
+ }
1536
+
1537
+ function propertyName(
1538
+ node: Record<string, unknown> | undefined,
1539
+ computed: boolean,
1540
+ state?: ComponentAliasState | undefined,
1541
+ ): string | undefined {
1542
+ if (!computed && node?.type === "Identifier" && typeof node.name === "string") {
1543
+ return node.name;
1544
+ }
1545
+
1546
+ if (computed && node?.type === "Identifier" && typeof node.name === "string") {
1547
+ return state?.stringConstants.get(node.name);
1548
+ }
1549
+
1550
+ return stringExpressionValue(node, state);
1551
+ }
1552
+
1553
+ function stringExpressionValue(
1554
+ node: Record<string, unknown> | undefined,
1555
+ state?: ComponentAliasState | undefined,
1556
+ ): string | undefined {
1557
+ if (
1558
+ (node?.type === "StringLiteral" || node?.type === "Literal") &&
1559
+ typeof node.value === "string"
1560
+ ) {
1561
+ return node.value;
1562
+ }
1563
+
1564
+ if (node?.type === "ConditionalExpression") {
1565
+ return uniqueDefinedString([
1566
+ stringExpressionValue(readOptionalObject(node.consequent), state),
1567
+ stringExpressionValue(readOptionalObject(node.alternate), state),
1568
+ ]);
1569
+ }
1570
+
1571
+ if (node?.type === "Identifier" && typeof node.name === "string") {
1572
+ return state?.stringConstants.get(node.name);
1573
+ }
1574
+
1575
+ if (
1576
+ node?.type === "ChainExpression" ||
1577
+ node?.type === "TSAsExpression" ||
1578
+ node?.type === "TSSatisfiesExpression" ||
1579
+ node?.type === "TSNonNullExpression" ||
1580
+ node?.type === "ParenthesizedExpression"
1581
+ ) {
1582
+ return stringExpressionValue(readOptionalObject(node.expression), state);
1583
+ }
1584
+
1585
+ return undefined;
1586
+ }
1587
+
1588
+ function uniqueObjectMemberAlias(
1589
+ objectName: string | undefined,
1590
+ state?: ComponentAliasState | undefined,
1591
+ ): string | undefined {
1592
+ if (objectName === undefined || state === undefined) {
1593
+ return undefined;
1594
+ }
1595
+
1596
+ const prefix = `${objectName}.`;
1597
+ return uniqueDefinedString(
1598
+ Array.from(state.aliases.entries())
1599
+ .filter(([key]) => key.startsWith(prefix))
1600
+ .map(([, value]) => value),
1601
+ );
1602
+ }
1603
+
1604
+ function uniqueDefinedString(values: readonly (string | undefined)[]): string | undefined {
1605
+ const unique = new Set(values.filter((value): value is string => value !== undefined));
1606
+ return unique.size === 1 ? Array.from(unique)[0] : undefined;
1607
+ }
1608
+
1609
+ function collectIdentifierReferenceNamesFromNode(
1610
+ node: unknown,
1611
+ names: Set<string>,
1612
+ ): void {
1613
+ if (Array.isArray(node)) {
1614
+ for (const child of node) {
1615
+ collectIdentifierReferenceNamesFromNode(child, names);
1616
+ }
1617
+ return;
1618
+ }
1619
+
1620
+ const object = readOptionalObject(node);
1621
+ if (object === undefined) {
1622
+ return;
1623
+ }
1624
+
1625
+ if (typeof object.type === "string" && object.type.startsWith("TS")) {
1626
+ return;
1627
+ }
1628
+
1629
+ if (object.type === "ImportDeclaration") {
1630
+ return;
1631
+ }
1632
+
1633
+ if (
1634
+ (object.type === "Identifier" || object.type === "JSXIdentifier") &&
1635
+ typeof object.name === "string"
1636
+ ) {
1637
+ names.add(object.name);
1638
+ }
1639
+
1640
+ for (const [key, value] of Object.entries(object)) {
1641
+ if (key === "type" || key === "start" || key === "end" || key === "loc") {
1642
+ continue;
1643
+ }
1644
+
1645
+ collectIdentifierReferenceNamesFromNode(value, names);
1646
+ }
1647
+ }
1648
+
1649
+ function collectFormActionReferencesFromNode(
1650
+ node: unknown,
1651
+ references: FormActionReference[],
1652
+ ): void {
1653
+ if (Array.isArray(node)) {
1654
+ for (const child of node) {
1655
+ collectFormActionReferencesFromNode(child, references);
1656
+ }
1657
+ return;
1658
+ }
1659
+
1660
+ const object = readOptionalObject(node);
1661
+ if (object === undefined) {
1662
+ return;
1663
+ }
1664
+
1665
+ if (typeof object.type === "string" && object.type.startsWith("TS")) {
1666
+ return;
1667
+ }
1668
+
1669
+ if (object.type === "ImportDeclaration") {
1670
+ return;
1671
+ }
1672
+
1673
+ if (
1674
+ object.type === "JSXOpeningElement" &&
1675
+ jsxTagName(readOptionalObject(object.name)) === "form"
1676
+ ) {
1677
+ const attributes = Array.isArray(object.attributes) ? object.attributes : [];
1678
+
1679
+ for (const attribute of attributes) {
1680
+ const attr = readObject(attribute);
1681
+
1682
+ if (attr.type !== "JSXAttribute" || readObject(attr.name).name !== "action") {
1683
+ continue;
1684
+ }
1685
+
1686
+ const value = readObject(attr.value);
1687
+ const expression = readObject(value.expression);
1688
+ const start = typeof object.start === "number" ? object.start : undefined;
1689
+ const end = typeof object.end === "number" ? object.end : undefined;
1690
+
1691
+ if (
1692
+ value.type === "JSXExpressionContainer" &&
1693
+ typeof expression.name === "string" &&
1694
+ start !== undefined &&
1695
+ end !== undefined
1696
+ ) {
1697
+ references.push({ end, name: expression.name, start });
1698
+ }
1699
+ }
1700
+ }
1701
+
1702
+ for (const [key, value] of Object.entries(object)) {
1703
+ if (key === "type" || key === "start" || key === "end" || key === "loc") {
1704
+ continue;
1705
+ }
1706
+
1707
+ collectFormActionReferencesFromNode(value, references);
1708
+ }
1709
+ }
1710
+
1711
+ function jsxTagName(node: Record<string, unknown> | undefined): string {
1712
+ if (node === undefined) {
1713
+ return "";
1714
+ }
1715
+
1716
+ if (typeof node.name === "string") {
1717
+ return node.name;
1718
+ }
1719
+
1720
+ if (node.type === "JSXIdentifier" && typeof node.name === "string") {
1721
+ return node.name;
1722
+ }
1723
+
1724
+ if (node.type === "JSXMemberExpression") {
1725
+ const objectName = jsxTagName(readOptionalObject(node.object));
1726
+ const propertyName = jsxTagName(readOptionalObject(node.property));
1727
+ return `${objectName}.${propertyName}`;
1728
+ }
1729
+
1730
+ return "";
1731
+ }
1732
+
1733
+ function hasClientRuntimeSyntaxNode(node: unknown): boolean {
1734
+ if (Array.isArray(node)) {
1735
+ return node.some(hasClientRuntimeSyntaxNode);
1736
+ }
1737
+
1738
+ const object = readOptionalObject(node);
1739
+ if (object === undefined) {
1740
+ return false;
1741
+ }
1742
+
1743
+ if (typeof object.type === "string" && object.type.startsWith("TS")) {
1744
+ return false;
1745
+ }
1746
+
1747
+ if (object.type === "ImportDeclaration") {
1748
+ return false;
1749
+ }
1750
+
1751
+ if (object.type === "ExportAllDeclaration") {
1752
+ return false;
1753
+ }
1754
+
1755
+ if (object.type === "ExportNamedDeclaration" || object.type === "ExportDefaultDeclaration") {
1756
+ return hasClientRuntimeSyntaxNode(object.declaration);
1757
+ }
1758
+
1759
+ if (object.type === "JSXAttribute") {
1760
+ const name = readOptionalObject(object.name)?.name;
1761
+ return typeof name === "string" && /^on[A-Z]/.test(name);
1762
+ }
1763
+
1764
+ if (object.type === "CallExpression") {
1765
+ const callee = readOptionalObject(object.callee);
1766
+ if (callee?.type === "Identifier" && callee.name === "cell") {
1767
+ return true;
1768
+ }
1769
+ }
1770
+
1771
+ if (object.type === "Identifier" && isClientRuntimeGlobal(object.name)) {
1772
+ return true;
1773
+ }
1774
+
1775
+ if (object.type === "MemberExpression") {
1776
+ return (
1777
+ hasClientRuntimeSyntaxNode(object.object) ||
1778
+ (object.computed === true && hasClientRuntimeSyntaxNode(object.property))
1779
+ );
1780
+ }
1781
+
1782
+ for (const [key, value] of Object.entries(object)) {
1783
+ if (key === "type" || key === "start" || key === "end" || key === "loc") {
1784
+ continue;
1785
+ }
1786
+
1787
+ if (hasClientRuntimeSyntaxNode(value)) {
1788
+ return true;
1789
+ }
1790
+ }
1791
+
1792
+ return false;
1793
+ }
1794
+
1795
+ function isClientRuntimeGlobal(name: unknown): boolean {
1796
+ return name === "window" || name === "document" || name === "localStorage";
1797
+ }
1798
+
1799
+ function exportedNameForSpecifier(specifier: Record<string, unknown>): string | undefined {
1800
+ const exported = readOptionalObject(specifier.exported);
1801
+ const local = readOptionalObject(specifier.local);
1802
+ const name = exported?.name ?? exported?.value ?? local?.name ?? local?.value;
1803
+
1804
+ return typeof name === "string" ? name : undefined;
1805
+ }
1806
+
1807
+ function bindingNames(node: unknown): string[] {
1808
+ const object = readObject(node);
1809
+
1810
+ if (typeof object.name === "string") {
1811
+ return [object.name];
1812
+ }
1813
+
1814
+ if (Array.isArray(object.properties)) {
1815
+ return object.properties.flatMap((property) => bindingNames(readObject(property).value));
1816
+ }
1817
+
1818
+ if (Array.isArray(object.elements)) {
1819
+ return object.elements.flatMap((element) => (element === null ? [] : bindingNames(element)));
1820
+ }
1821
+
1822
+ if (object.type === "AssignmentPattern") {
1823
+ return bindingNames(object.left);
1824
+ }
1825
+
1826
+ if (object.type === "RestElement") {
1827
+ return bindingNames(object.argument);
1828
+ }
1829
+
1830
+ return [];
1831
+ }
1832
+
1833
+ function statementRange(
1834
+ code: string,
1835
+ statement: Record<string, unknown>,
1836
+ ): { end: number; start: number } | undefined {
1837
+ const start = typeof statement.start === "number" ? statement.start : undefined;
1838
+ const end = typeof statement.end === "number" ? statement.end : undefined;
1839
+
1840
+ if (start === undefined || end === undefined) {
1841
+ return undefined;
1842
+ }
1843
+
1844
+ let removalStart = start;
1845
+ while (removalStart > 0 && code[removalStart - 1] !== "\n") {
1846
+ if (!/\s/.test(code[removalStart - 1] ?? "")) {
1847
+ break;
1848
+ }
1849
+ removalStart -= 1;
1850
+ }
1851
+
1852
+ let removalEnd = end;
1853
+ while (removalEnd < code.length && /[ \t]/.test(code[removalEnd] ?? "")) {
1854
+ removalEnd += 1;
1855
+ }
1856
+ if (code[removalEnd] === "\r") {
1857
+ removalEnd += 1;
1858
+ }
1859
+ if (code[removalEnd] === "\n") {
1860
+ removalEnd += 1;
1861
+ }
1862
+
1863
+ return { start: removalStart, end: removalEnd };
1864
+ }
1865
+
1866
+ function nodeText(code: string, node: Record<string, unknown>): string {
1867
+ const start = typeof node.start === "number" ? node.start : undefined;
1868
+ const end = typeof node.end === "number" ? node.end : undefined;
1869
+
1870
+ return start === undefined || end === undefined ? "" : code.slice(start, end);
1871
+ }
1872
+
1873
+ function readObject(value: unknown): Record<string, unknown> {
1874
+ return typeof value === "object" && value !== null ? (value as Record<string, unknown>) : {};
1875
+ }
1876
+
1877
+ function readArray(value: unknown): unknown[] {
1878
+ return Array.isArray(value) ? value : [];
1879
+ }
1880
+
1881
+ function readOptionalObject(value: unknown): Record<string, unknown> | undefined {
1882
+ return typeof value === "object" && value !== null
1883
+ ? (value as Record<string, unknown>)
1884
+ : undefined;
1885
+ }
1886
+
1887
+ export type {
1888
+ AsyncBoundaryIr,
1889
+ AttributeIr,
1890
+ ComponentIr,
1891
+ ComponentPropIr,
1892
+ ComponentRefIr,
1893
+ ConditionalIr,
1894
+ DynamicAttributeIr,
1895
+ EventAttributeIr,
1896
+ ExprIr,
1897
+ JsxElementIr,
1898
+ JsxFragmentIr,
1899
+ JsxNodeIr,
1900
+ ListIr,
1901
+ ModuleIr,
1902
+ SpreadAttributeIr,
1903
+ StaticAttributeIr,
1904
+ TextIr,
1905
+ } from "./ir.js";