@openrewrite/rewrite 8.69.0-20251207-184829 → 8.69.0-20251207-214914

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.
Files changed (44) hide show
  1. package/dist/cli/cli-utils.d.ts.map +1 -1
  2. package/dist/cli/cli-utils.js +3 -2
  3. package/dist/cli/cli-utils.js.map +1 -1
  4. package/dist/cli/rewrite.js +2 -1
  5. package/dist/cli/rewrite.js.map +1 -1
  6. package/dist/index.js +2 -2
  7. package/dist/index.js.map +1 -1
  8. package/dist/javascript/parser.d.ts.map +1 -1
  9. package/dist/javascript/parser.js +48 -8
  10. package/dist/javascript/parser.js.map +1 -1
  11. package/dist/javascript/recipes/change-import.d.ts +51 -0
  12. package/dist/javascript/recipes/change-import.d.ts.map +1 -0
  13. package/dist/javascript/recipes/change-import.js +658 -0
  14. package/dist/javascript/recipes/change-import.js.map +1 -0
  15. package/dist/javascript/recipes/index.d.ts +2 -0
  16. package/dist/javascript/recipes/index.d.ts.map +1 -1
  17. package/dist/javascript/recipes/index.js +2 -0
  18. package/dist/javascript/recipes/index.js.map +1 -1
  19. package/dist/javascript/recipes/order-imports.d.ts +10 -0
  20. package/dist/javascript/recipes/order-imports.d.ts.map +1 -0
  21. package/dist/javascript/recipes/order-imports.js +240 -0
  22. package/dist/javascript/recipes/order-imports.js.map +1 -0
  23. package/dist/json/parser.js +78 -30
  24. package/dist/json/parser.js.map +1 -1
  25. package/dist/version.txt +1 -1
  26. package/package.json +1 -1
  27. package/src/cli/cli-utils.ts +3 -2
  28. package/src/cli/rewrite.ts +2 -1
  29. package/src/index.ts +2 -2
  30. package/src/javascript/parser.ts +49 -8
  31. package/src/javascript/recipes/change-import.ts +700 -0
  32. package/src/javascript/recipes/index.ts +2 -0
  33. package/src/javascript/recipes/order-imports.ts +242 -0
  34. package/src/json/parser.ts +69 -24
  35. package/dist/recipe/index.d.ts +0 -2
  36. package/dist/recipe/index.d.ts.map +0 -1
  37. package/dist/recipe/index.js +0 -21
  38. package/dist/recipe/index.js.map +0 -1
  39. package/dist/recipe/order-imports.d.ts +0 -10
  40. package/dist/recipe/order-imports.d.ts.map +0 -1
  41. package/dist/recipe/order-imports.js +0 -213
  42. package/dist/recipe/order-imports.js.map +0 -1
  43. package/src/recipe/index.ts +0 -17
  44. package/src/recipe/order-imports.ts +0 -195
@@ -0,0 +1,700 @@
1
+ /*
2
+ * Copyright 2025 the original author or authors.
3
+ * <p>
4
+ * Licensed under the Moderne Source Available License (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ * <p>
8
+ * https://docs.moderne.io/licensing/moderne-source-available-license
9
+ * <p>
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import {Option, Recipe} from "../../recipe";
18
+ import {TreeVisitor} from "../../visitor";
19
+ import {ExecutionContext} from "../../execution";
20
+ import {JavaScriptVisitor, JS} from "../index";
21
+ import {maybeAddImport} from "../add-import";
22
+ import {J, isIdentifier, Type} from "../../java";
23
+ import {produce, WritableDraft} from "immer";
24
+
25
+ /**
26
+ * Changes an import from one module to another, updating all type attributions.
27
+ *
28
+ * This recipe is useful for:
29
+ * - Library migrations (e.g., moving `act` from `react-dom/test-utils` to `react`)
30
+ * - Module restructuring (e.g., split packages)
31
+ * - Renaming exported members
32
+ *
33
+ * @example
34
+ * // Migrate act import from react-dom/test-utils to react
35
+ * const recipe = new ChangeImport({
36
+ * oldModule: "react-dom/test-utils",
37
+ * oldMember: "act",
38
+ * newModule: "react"
39
+ * });
40
+ * // Before: import { act } from 'react-dom/test-utils';
41
+ * // After: import { act } from 'react';
42
+ *
43
+ * @example
44
+ * // Change a named import to a different name
45
+ * const recipe = new ChangeImport({
46
+ * oldModule: "lodash",
47
+ * oldMember: "extend",
48
+ * newModule: "lodash",
49
+ * newMember: "assign"
50
+ * });
51
+ * // Before: import { extend } from 'lodash';
52
+ * // After: import { assign } from 'lodash';
53
+ */
54
+ export class ChangeImport extends Recipe {
55
+ readonly name = "org.openrewrite.javascript.change-import";
56
+ readonly displayName = "Change import";
57
+ readonly description = "Changes an import from one module/member to another, updating all type attributions.";
58
+
59
+ @Option({
60
+ displayName: "Old module",
61
+ description: "The module to change imports from",
62
+ example: "react-dom/test-utils"
63
+ })
64
+ oldModule!: string;
65
+
66
+ @Option({
67
+ displayName: "Old member",
68
+ description: "The member to change (or 'default' for default imports, '*' for namespace imports)",
69
+ example: "act"
70
+ })
71
+ oldMember!: string;
72
+
73
+ @Option({
74
+ displayName: "New module",
75
+ description: "The module to change imports to",
76
+ example: "react"
77
+ })
78
+ newModule!: string;
79
+
80
+ @Option({
81
+ displayName: "New member",
82
+ description: "The new member name. If not specified, keeps the same member name.",
83
+ example: "act",
84
+ required: false
85
+ })
86
+ newMember?: string;
87
+
88
+ @Option({
89
+ displayName: "New alias",
90
+ description: "Optional alias for the new import. Required when newMember is 'default' or '*'.",
91
+ required: false
92
+ })
93
+ newAlias?: string;
94
+
95
+ constructor(options?: {
96
+ oldModule?: string;
97
+ oldMember?: string;
98
+ newModule?: string;
99
+ newMember?: string;
100
+ newAlias?: string;
101
+ }) {
102
+ super(options);
103
+ }
104
+
105
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
106
+ const oldModule = this.oldModule;
107
+ const oldMember = this.oldMember;
108
+ const newModule = this.newModule;
109
+ const newMember = this.newMember ?? oldMember;
110
+ const newAlias = this.newAlias;
111
+
112
+ // Build the old and new FQNs for type attribution updates
113
+ const oldFqn = oldMember === 'default' || oldMember === '*'
114
+ ? oldModule
115
+ : `${oldModule}.${oldMember}`;
116
+ const newFqn = newMember === 'default' || newMember === '*'
117
+ ? newModule
118
+ : `${newModule}.${newMember}`;
119
+
120
+ return new class extends JavaScriptVisitor<ExecutionContext> {
121
+ private hasOldImport = false;
122
+ private oldAlias?: string;
123
+ private transformedImport = false;
124
+
125
+ override async visitJsCompilationUnit(cu: JS.CompilationUnit, ctx: ExecutionContext): Promise<J | undefined> {
126
+ // Reset tracking for each file
127
+ this.hasOldImport = false;
128
+ this.oldAlias = undefined;
129
+ this.transformedImport = false;
130
+
131
+ // First pass: check if the old import exists and capture any alias
132
+ for (const statement of cu.statements) {
133
+ const stmt = statement.element ?? statement;
134
+ if (stmt.kind === JS.Kind.Import) {
135
+ const jsImport = stmt as JS.Import;
136
+ const aliasInfo = this.checkForOldImport(jsImport);
137
+ if (aliasInfo.found) {
138
+ this.hasOldImport = true;
139
+ this.oldAlias = aliasInfo.alias;
140
+ break;
141
+ }
142
+ }
143
+ }
144
+
145
+ // Visit the compilation unit (this will transform imports via visitJsImport)
146
+ let result = await super.visitJsCompilationUnit(cu, ctx) as JS.CompilationUnit;
147
+
148
+ // If we transformed an import but need to add to existing import from new module,
149
+ // or if we only removed a member from a multi-import, use maybeAddImport
150
+ if (this.hasOldImport && !this.transformedImport) {
151
+ const aliasToUse = newAlias ?? this.oldAlias;
152
+
153
+ if (newMember === 'default') {
154
+ maybeAddImport(this, {
155
+ module: newModule,
156
+ member: 'default',
157
+ alias: aliasToUse,
158
+ onlyIfReferenced: false
159
+ });
160
+ } else if (newMember === '*') {
161
+ maybeAddImport(this, {
162
+ module: newModule,
163
+ member: '*',
164
+ alias: aliasToUse,
165
+ onlyIfReferenced: false
166
+ });
167
+ } else if (aliasToUse && aliasToUse !== newMember) {
168
+ maybeAddImport(this, {
169
+ module: newModule,
170
+ member: newMember,
171
+ alias: aliasToUse,
172
+ onlyIfReferenced: false
173
+ });
174
+ } else {
175
+ maybeAddImport(this, {
176
+ module: newModule,
177
+ member: newMember,
178
+ onlyIfReferenced: false
179
+ });
180
+ }
181
+ }
182
+
183
+ return result;
184
+ }
185
+
186
+ override async visitImportDeclaration(jsImport: JS.Import, ctx: ExecutionContext): Promise<J | undefined> {
187
+ let imp = await super.visitImportDeclaration(jsImport, ctx) as JS.Import;
188
+
189
+ if (!this.hasOldImport) {
190
+ return imp;
191
+ }
192
+
193
+ const aliasInfo = this.checkForOldImport(imp);
194
+ if (!aliasInfo.found) {
195
+ return imp;
196
+ }
197
+
198
+ // Check if this is the only import from the old module
199
+ const namedImports = this.getNamedImports(imp);
200
+ const isOnlyImport = namedImports.length === 1 ||
201
+ (oldMember === 'default' && !imp.importClause?.namedBindings) ||
202
+ (oldMember === '*');
203
+
204
+ if (isOnlyImport) {
205
+ // Transform the module specifier in place
206
+ this.transformedImport = true;
207
+ return produce(imp, draft => {
208
+ if (draft.moduleSpecifier) {
209
+ const literal = draft.moduleSpecifier.element as WritableDraft<J.Literal>;
210
+ literal.value = newModule;
211
+ // Update valueSource to preserve quote style
212
+ const originalSource = literal.valueSource || `"${oldModule}"`;
213
+ const quoteChar = originalSource.startsWith("'") ? "'" : '"';
214
+ literal.valueSource = `${quoteChar}${newModule}${quoteChar}`;
215
+ }
216
+ // If we're also renaming the member, update the import specifier
217
+ if (newMember !== oldMember && oldMember !== 'default' && oldMember !== '*') {
218
+ const importClause = draft.importClause;
219
+ if (importClause?.namedBindings?.kind === JS.Kind.NamedImports) {
220
+ const namedImports = importClause.namedBindings as WritableDraft<JS.NamedImports>;
221
+ for (const elem of namedImports.elements.elements) {
222
+ const specifier = elem.element;
223
+ if (specifier.specifier.kind === J.Kind.Identifier &&
224
+ specifier.specifier.simpleName === oldMember) {
225
+ specifier.specifier.simpleName = newMember;
226
+ }
227
+ }
228
+ }
229
+ }
230
+ });
231
+ } else {
232
+ // Remove just the specific member from the import
233
+ // maybeAddImport will add the new import
234
+ return this.removeNamedImportMember(imp, oldMember, ctx);
235
+ }
236
+ }
237
+
238
+ private async removeNamedImportMember(imp: JS.Import, memberToRemove: string, _ctx: ExecutionContext): Promise<JS.Import> {
239
+ return produce(imp, draft => {
240
+ const importClause = draft.importClause;
241
+ if (!importClause?.namedBindings) return;
242
+ if (importClause.namedBindings.kind !== JS.Kind.NamedImports) return;
243
+
244
+ const namedImports = importClause.namedBindings as WritableDraft<JS.NamedImports>;
245
+ const elements = namedImports.elements.elements;
246
+ const filteredElements = elements.filter(elem => {
247
+ const specifier = elem.element;
248
+ const specifierNode = specifier.specifier;
249
+
250
+ if (specifierNode.kind === J.Kind.Identifier) {
251
+ return specifierNode.simpleName !== memberToRemove;
252
+ }
253
+
254
+ if (specifierNode.kind === JS.Kind.Alias) {
255
+ const alias = specifierNode as JS.Alias;
256
+ const propertyName = alias.propertyName.element;
257
+ if (propertyName.kind === J.Kind.Identifier) {
258
+ return propertyName.simpleName !== memberToRemove;
259
+ }
260
+ }
261
+
262
+ return true;
263
+ });
264
+
265
+ namedImports.elements.elements = filteredElements;
266
+ });
267
+ }
268
+
269
+ private getNamedImports(imp: JS.Import): string[] {
270
+ const imports: string[] = [];
271
+ const importClause = imp.importClause;
272
+ if (!importClause) return imports;
273
+
274
+ const namedBindings = importClause.namedBindings;
275
+ if (!namedBindings || namedBindings.kind !== JS.Kind.NamedImports) return imports;
276
+
277
+ const namedImports = namedBindings as JS.NamedImports;
278
+ for (const elem of namedImports.elements.elements) {
279
+ const specifier = elem.element;
280
+ const specifierNode = specifier.specifier;
281
+
282
+ if (isIdentifier(specifierNode)) {
283
+ imports.push(specifierNode.simpleName);
284
+ } else if (specifierNode.kind === JS.Kind.Alias) {
285
+ const alias = specifierNode as JS.Alias;
286
+ const propertyName = alias.propertyName.element;
287
+ if (isIdentifier(propertyName)) {
288
+ imports.push(propertyName.simpleName);
289
+ }
290
+ }
291
+ }
292
+
293
+ return imports;
294
+ }
295
+
296
+ override async visitIdentifier(identifier: J.Identifier, ctx: ExecutionContext): Promise<J | undefined> {
297
+ let ident = await super.visitIdentifier(identifier, ctx) as J.Identifier;
298
+
299
+ if (!this.hasOldImport) {
300
+ return ident;
301
+ }
302
+
303
+ // Check and update type attribution
304
+ let changed = false;
305
+
306
+ // Update type if it references the old module
307
+ const updatedType = this.updateType(ident.type);
308
+ if (updatedType !== ident.type) {
309
+ changed = true;
310
+ }
311
+
312
+ // Update fieldType if it references the old module
313
+ // fieldType is specifically Type.Variable, so we need to handle it specially
314
+ let updatedFieldType: Type.Variable | undefined = ident.fieldType;
315
+ if (ident.fieldType) {
316
+ const updated = this.updateVariableType(ident.fieldType);
317
+ if (updated !== ident.fieldType) {
318
+ updatedFieldType = updated;
319
+ changed = true;
320
+ }
321
+ }
322
+
323
+ if (changed) {
324
+ return produce(ident, draft => {
325
+ if (updatedType !== ident.type) {
326
+ draft.type = updatedType;
327
+ }
328
+ if (updatedFieldType !== ident.fieldType) {
329
+ draft.fieldType = updatedFieldType;
330
+ }
331
+ });
332
+ }
333
+
334
+ return ident;
335
+ }
336
+
337
+ override async visitMethodInvocation(method: J.MethodInvocation, ctx: ExecutionContext): Promise<J | undefined> {
338
+ let m = await super.visitMethodInvocation(method, ctx) as J.MethodInvocation;
339
+
340
+ if (!this.hasOldImport) {
341
+ return m;
342
+ }
343
+
344
+ // Update methodType if it references the old module
345
+ const updatedMethodType = this.updateMethodType(m.methodType);
346
+ if (updatedMethodType !== m.methodType) {
347
+ return produce(m, draft => {
348
+ draft.methodType = updatedMethodType;
349
+ });
350
+ }
351
+
352
+ return m;
353
+ }
354
+
355
+ override async visitFieldAccess(fieldAccess: J.FieldAccess, ctx: ExecutionContext): Promise<J | undefined> {
356
+ let fa = await super.visitFieldAccess(fieldAccess, ctx) as J.FieldAccess;
357
+
358
+ if (!this.hasOldImport) {
359
+ return fa;
360
+ }
361
+
362
+ // Update type if it references the old module
363
+ const updatedType = this.updateType(fa.type);
364
+ if (updatedType !== fa.type) {
365
+ return produce(fa, draft => {
366
+ draft.type = updatedType;
367
+ });
368
+ }
369
+
370
+ return fa;
371
+ }
372
+
373
+ override async visitFunctionCall(functionCall: JS.FunctionCall, ctx: ExecutionContext): Promise<J | undefined> {
374
+ let fc = await super.visitFunctionCall(functionCall, ctx) as JS.FunctionCall;
375
+
376
+ if (!this.hasOldImport) {
377
+ return fc;
378
+ }
379
+
380
+ // Update methodType if it references the old module
381
+ const updatedMethodType = this.updateMethodType(fc.methodType);
382
+ if (updatedMethodType !== fc.methodType) {
383
+ return produce(fc, draft => {
384
+ draft.methodType = updatedMethodType;
385
+ });
386
+ }
387
+
388
+ return fc;
389
+ }
390
+
391
+ override async visitNewClass(newClass: J.NewClass, ctx: ExecutionContext): Promise<J | undefined> {
392
+ let nc = await super.visitNewClass(newClass, ctx) as J.NewClass;
393
+
394
+ if (!this.hasOldImport) {
395
+ return nc;
396
+ }
397
+
398
+ let changed = false;
399
+
400
+ // Update methodType if it references the old module
401
+ const updatedMethodType = this.updateMethodType(nc.methodType);
402
+ if (updatedMethodType !== nc.methodType) {
403
+ changed = true;
404
+ }
405
+
406
+ // Update constructorType if it references the old module
407
+ const updatedConstructorType = this.updateMethodType(nc.constructorType);
408
+ if (updatedConstructorType !== nc.constructorType) {
409
+ changed = true;
410
+ }
411
+
412
+ // Update type if it references the old module
413
+ const updatedType = this.updateType(nc.type);
414
+ if (updatedType !== nc.type) {
415
+ changed = true;
416
+ }
417
+
418
+ if (changed) {
419
+ return produce(nc, draft => {
420
+ if (updatedMethodType !== nc.methodType) {
421
+ draft.methodType = updatedMethodType;
422
+ }
423
+ if (updatedConstructorType !== nc.constructorType) {
424
+ draft.constructorType = updatedConstructorType;
425
+ }
426
+ if (updatedType !== nc.type) {
427
+ draft.type = updatedType;
428
+ }
429
+ });
430
+ }
431
+
432
+ return nc;
433
+ }
434
+
435
+ /**
436
+ * Update a type if it references the old module
437
+ */
438
+ private updateType(type: Type | undefined): Type | undefined {
439
+ if (!type) return type;
440
+
441
+ switch (type.kind) {
442
+ case Type.Kind.Class:
443
+ case Type.Kind.ShallowClass:
444
+ return this.updateClassType(type as Type.Class);
445
+
446
+ case Type.Kind.Method:
447
+ return this.updateMethodType(type as Type.Method);
448
+
449
+ case Type.Kind.Variable:
450
+ return this.updateVariableType(type as Type.Variable);
451
+
452
+ case Type.Kind.Parameterized:
453
+ return this.updateParameterizedType(type as Type.Parameterized);
454
+
455
+ case Type.Kind.Array:
456
+ return this.updateArrayType(type as Type.Array);
457
+
458
+ default:
459
+ return type;
460
+ }
461
+ }
462
+
463
+ /**
464
+ * Update a Class type if its FQN references the old module
465
+ */
466
+ private updateClassType(classType: Type.Class): Type.Class {
467
+ let changed = false;
468
+ let newFullyQualifiedName = classType.fullyQualifiedName;
469
+ let newOwningClass = classType.owningClass;
470
+
471
+ // Check if the FQN matches or starts with the old module
472
+ if (classType.fullyQualifiedName === oldFqn) {
473
+ newFullyQualifiedName = newFqn;
474
+ changed = true;
475
+ } else if (classType.fullyQualifiedName === oldModule) {
476
+ newFullyQualifiedName = newModule;
477
+ changed = true;
478
+ } else if (classType.fullyQualifiedName.startsWith(oldModule + '.')) {
479
+ newFullyQualifiedName = newModule + classType.fullyQualifiedName.substring(oldModule.length);
480
+ changed = true;
481
+ }
482
+
483
+ // Recursively update owningClass
484
+ if (classType.owningClass) {
485
+ const updatedOwningClass = this.updateClassType(classType.owningClass);
486
+ if (updatedOwningClass !== classType.owningClass) {
487
+ newOwningClass = updatedOwningClass;
488
+ changed = true;
489
+ }
490
+ }
491
+
492
+ if (changed) {
493
+ // Type objects are marked as non-draftable, so we manually create new objects
494
+ return {
495
+ ...classType,
496
+ fullyQualifiedName: newFullyQualifiedName,
497
+ owningClass: newOwningClass
498
+ } as Type.Class;
499
+ }
500
+
501
+ return classType;
502
+ }
503
+
504
+ /**
505
+ * Update a Method type if its declaringType references the old module
506
+ */
507
+ private updateMethodType(methodType: Type.Method | undefined): Type.Method | undefined {
508
+ if (!methodType) return methodType;
509
+
510
+ // Update the declaring type
511
+ if (Type.isFullyQualified(methodType.declaringType)) {
512
+ const declaringTypeFqn = Type.FullyQualified.getFullyQualifiedName(methodType.declaringType);
513
+
514
+ if (declaringTypeFqn === oldModule ||
515
+ declaringTypeFqn === oldFqn ||
516
+ declaringTypeFqn.startsWith(oldModule + '.')) {
517
+
518
+ // Need to update the declaring type
519
+ const updatedDeclaringType = this.updateType(methodType.declaringType) as Type.FullyQualified;
520
+
521
+ // Also update the method name if we're renaming the member
522
+ const updatedName = (oldMember !== 'default' && oldMember !== '*' &&
523
+ methodType.name === oldMember && newMember !== oldMember)
524
+ ? newMember
525
+ : methodType.name;
526
+
527
+ // Type objects are marked as non-draftable, so we manually create new objects
528
+ return {
529
+ ...methodType,
530
+ declaringType: updatedDeclaringType,
531
+ name: updatedName
532
+ } as Type.Method;
533
+ }
534
+ }
535
+
536
+ return methodType;
537
+ }
538
+
539
+ /**
540
+ * Update a Variable type if its owner references the old module
541
+ */
542
+ private updateVariableType(variableType: Type.Variable): Type.Variable {
543
+ let changed = false;
544
+ let newOwner = variableType.owner;
545
+ let newInnerType = variableType.type;
546
+
547
+ // Update owner if it references the old module
548
+ if (variableType.owner) {
549
+ const updatedOwner = this.updateType(variableType.owner);
550
+ if (updatedOwner !== variableType.owner) {
551
+ newOwner = updatedOwner;
552
+ changed = true;
553
+ }
554
+ }
555
+
556
+ // Update inner type if it references the old module
557
+ const updatedInnerType = this.updateType(variableType.type);
558
+ if (updatedInnerType !== variableType.type) {
559
+ newInnerType = updatedInnerType!;
560
+ changed = true;
561
+ }
562
+
563
+ if (changed) {
564
+ // Type objects are marked as non-draftable, so we manually create new objects
565
+ return {
566
+ ...variableType,
567
+ owner: newOwner,
568
+ type: newInnerType
569
+ } as Type.Variable;
570
+ }
571
+
572
+ return variableType;
573
+ }
574
+
575
+ /**
576
+ * Update a Parameterized type if its base type references the old module
577
+ */
578
+ private updateParameterizedType(paramType: Type.Parameterized): Type.Parameterized {
579
+ let changed = false;
580
+ let newBaseType = paramType.type;
581
+ let newTypeParams = paramType.typeParameters;
582
+
583
+ // Update base type
584
+ if (Type.isFullyQualified(paramType.type)) {
585
+ const updatedType = this.updateType(paramType.type) as Type.FullyQualified;
586
+ if (updatedType !== paramType.type) {
587
+ newBaseType = updatedType;
588
+ changed = true;
589
+ }
590
+ }
591
+
592
+ // Update type parameters
593
+ const updatedParams = paramType.typeParameters.map(tp => this.updateType(tp)!);
594
+ if (updatedParams.some((p, i) => p !== paramType.typeParameters[i])) {
595
+ newTypeParams = updatedParams;
596
+ changed = true;
597
+ }
598
+
599
+ if (changed) {
600
+ // Type objects are marked as non-draftable, so we manually create new objects
601
+ return {
602
+ ...paramType,
603
+ type: newBaseType,
604
+ typeParameters: newTypeParams
605
+ } as Type.Parameterized;
606
+ }
607
+
608
+ return paramType;
609
+ }
610
+
611
+ /**
612
+ * Update an Array type if its element type references the old module
613
+ */
614
+ private updateArrayType(arrayType: Type.Array): Type.Array {
615
+ const updatedElemType = this.updateType(arrayType.elemType);
616
+ if (updatedElemType !== arrayType.elemType) {
617
+ // Type objects are marked as non-draftable, so we manually create new objects
618
+ return {
619
+ ...arrayType,
620
+ elemType: updatedElemType!
621
+ } as Type.Array;
622
+ }
623
+ return arrayType;
624
+ }
625
+
626
+ private checkForOldImport(jsImport: JS.Import): { found: boolean; alias?: string } {
627
+ // Check if this import is from the old module
628
+ const moduleSpecifier = jsImport.moduleSpecifier;
629
+ if (!moduleSpecifier) return { found: false };
630
+
631
+ const literal = moduleSpecifier.element;
632
+ if (literal.kind !== J.Kind.Literal) return { found: false };
633
+
634
+ const value = (literal as J.Literal).value;
635
+ if (value !== oldModule) return { found: false };
636
+
637
+ const importClause = jsImport.importClause;
638
+ if (!importClause) {
639
+ // Side-effect import - not what we're looking for
640
+ return { found: false };
641
+ }
642
+
643
+ // Check for default import
644
+ if (oldMember === 'default') {
645
+ if (importClause.name) {
646
+ const nameElem = importClause.name.element;
647
+ if (isIdentifier(nameElem)) {
648
+ return { found: true, alias: nameElem.simpleName };
649
+ }
650
+ }
651
+ return { found: false };
652
+ }
653
+
654
+ // Check for namespace import
655
+ if (oldMember === '*') {
656
+ const namedBindings = importClause.namedBindings;
657
+ if (namedBindings?.kind === JS.Kind.Alias) {
658
+ const alias = namedBindings as JS.Alias;
659
+ if (isIdentifier(alias.alias)) {
660
+ return { found: true, alias: alias.alias.simpleName };
661
+ }
662
+ }
663
+ return { found: false };
664
+ }
665
+
666
+ // Check for named imports
667
+ const namedBindings = importClause.namedBindings;
668
+ if (!namedBindings) return { found: false };
669
+
670
+ if (namedBindings.kind !== JS.Kind.NamedImports) return { found: false };
671
+
672
+ const namedImports = namedBindings as JS.NamedImports;
673
+ const elements = namedImports.elements.elements;
674
+
675
+ for (const elem of elements) {
676
+ const specifier = elem.element;
677
+ const specifierNode = specifier.specifier;
678
+
679
+ // Handle direct import: import { act }
680
+ if (isIdentifier(specifierNode) && specifierNode.simpleName === oldMember) {
681
+ return { found: true };
682
+ }
683
+
684
+ // Handle aliased import: import { act as something }
685
+ if (specifierNode.kind === JS.Kind.Alias) {
686
+ const alias = specifierNode as JS.Alias;
687
+ const propertyName = alias.propertyName.element;
688
+ if (isIdentifier(propertyName) && propertyName.simpleName === oldMember) {
689
+ if (isIdentifier(alias.alias)) {
690
+ return { found: true, alias: alias.alias.simpleName };
691
+ }
692
+ }
693
+ }
694
+ }
695
+
696
+ return { found: false };
697
+ }
698
+ }();
699
+ }
700
+ }