@openrewrite/rewrite 8.92.17 → 8.92.18

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 (199) hide show
  1. package/README.md +48 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +2 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/java/formatting-utils.d.ts +2 -1
  6. package/dist/java/formatting-utils.d.ts.map +1 -1
  7. package/dist/java/formatting-utils.js +4 -3
  8. package/dist/java/formatting-utils.js.map +1 -1
  9. package/dist/java/rpc.d.ts.map +1 -1
  10. package/dist/java/rpc.js +2 -0
  11. package/dist/java/rpc.js.map +1 -1
  12. package/dist/java/tree.d.ts +8 -0
  13. package/dist/java/tree.d.ts.map +1 -1
  14. package/dist/java/tree.js +11 -1
  15. package/dist/java/tree.js.map +1 -1
  16. package/dist/java/type.d.ts +7 -1
  17. package/dist/java/type.d.ts.map +1 -1
  18. package/dist/java/type.js +16 -1
  19. package/dist/java/type.js.map +1 -1
  20. package/dist/javascript/add-import.d.ts +27 -21
  21. package/dist/javascript/add-import.d.ts.map +1 -1
  22. package/dist/javascript/add-import.js +303 -143
  23. package/dist/javascript/add-import.js.map +1 -1
  24. package/dist/javascript/amd.d.ts.map +1 -1
  25. package/dist/javascript/amd.js +25 -10
  26. package/dist/javascript/amd.js.map +1 -1
  27. package/dist/javascript/autodetect.js +36 -45
  28. package/dist/javascript/autodetect.js.map +1 -1
  29. package/dist/javascript/binding.d.ts +6 -4
  30. package/dist/javascript/binding.d.ts.map +1 -1
  31. package/dist/javascript/binding.js +54 -18
  32. package/dist/javascript/binding.js.map +1 -1
  33. package/dist/javascript/comparator.d.ts +22 -5
  34. package/dist/javascript/comparator.d.ts.map +1 -1
  35. package/dist/javascript/comparator.js +116 -38
  36. package/dist/javascript/comparator.js.map +1 -1
  37. package/dist/javascript/format/format.d.ts +2 -0
  38. package/dist/javascript/format/format.d.ts.map +1 -1
  39. package/dist/javascript/format/format.js +11 -1
  40. package/dist/javascript/format/format.js.map +1 -1
  41. package/dist/javascript/index.d.ts +4 -2
  42. package/dist/javascript/index.d.ts.map +1 -1
  43. package/dist/javascript/index.js +10 -1
  44. package/dist/javascript/index.js.map +1 -1
  45. package/dist/javascript/markers.d.ts.map +1 -1
  46. package/dist/javascript/markers.js +64 -0
  47. package/dist/javascript/markers.js.map +1 -1
  48. package/dist/javascript/method-matcher.d.ts +13 -2
  49. package/dist/javascript/method-matcher.d.ts.map +1 -1
  50. package/dist/javascript/method-matcher.js +32 -19
  51. package/dist/javascript/method-matcher.js.map +1 -1
  52. package/dist/javascript/package-name.d.ts +6 -0
  53. package/dist/javascript/package-name.d.ts.map +1 -0
  54. package/dist/javascript/package-name.js +47 -0
  55. package/dist/javascript/package-name.js.map +1 -0
  56. package/dist/javascript/preconditions.d.ts +4 -1
  57. package/dist/javascript/preconditions.d.ts.map +1 -1
  58. package/dist/javascript/preconditions.js +7 -4
  59. package/dist/javascript/preconditions.js.map +1 -1
  60. package/dist/javascript/recipes/change-method-target-to-import.d.ts +29 -0
  61. package/dist/javascript/recipes/change-method-target-to-import.d.ts.map +1 -0
  62. package/dist/javascript/recipes/change-method-target-to-import.js +164 -0
  63. package/dist/javascript/recipes/change-method-target-to-import.js.map +1 -0
  64. package/dist/javascript/recipes/index.d.ts +1 -0
  65. package/dist/javascript/recipes/index.d.ts.map +1 -1
  66. package/dist/javascript/recipes/index.js +1 -0
  67. package/dist/javascript/recipes/index.js.map +1 -1
  68. package/dist/javascript/scope.d.ts +60 -16
  69. package/dist/javascript/scope.d.ts.map +1 -1
  70. package/dist/javascript/scope.js +596 -131
  71. package/dist/javascript/scope.js.map +1 -1
  72. package/dist/javascript/search/uses-method.d.ts +1 -1
  73. package/dist/javascript/search/uses-method.d.ts.map +1 -1
  74. package/dist/javascript/search/uses-method.js +2 -2
  75. package/dist/javascript/search/uses-method.js.map +1 -1
  76. package/dist/javascript/search/uses-type.d.ts +14 -1
  77. package/dist/javascript/search/uses-type.d.ts.map +1 -1
  78. package/dist/javascript/search/uses-type.js +24 -9
  79. package/dist/javascript/search/uses-type.js.map +1 -1
  80. package/dist/javascript/templating/bindings.d.ts +1 -1
  81. package/dist/javascript/templating/bindings.d.ts.map +1 -1
  82. package/dist/javascript/templating/bindings.js +10 -50
  83. package/dist/javascript/templating/bindings.js.map +1 -1
  84. package/dist/javascript/templating/comparator.d.ts +8 -11
  85. package/dist/javascript/templating/comparator.d.ts.map +1 -1
  86. package/dist/javascript/templating/comparator.js +12 -110
  87. package/dist/javascript/templating/comparator.js.map +1 -1
  88. package/dist/javascript/templating/engine.d.ts +34 -24
  89. package/dist/javascript/templating/engine.d.ts.map +1 -1
  90. package/dist/javascript/templating/engine.js +200 -112
  91. package/dist/javascript/templating/engine.js.map +1 -1
  92. package/dist/javascript/templating/pattern.d.ts +11 -5
  93. package/dist/javascript/templating/pattern.d.ts.map +1 -1
  94. package/dist/javascript/templating/pattern.js +86 -20
  95. package/dist/javascript/templating/pattern.js.map +1 -1
  96. package/dist/javascript/templating/placeholder-replacement.d.ts +34 -3
  97. package/dist/javascript/templating/placeholder-replacement.d.ts.map +1 -1
  98. package/dist/javascript/templating/placeholder-replacement.js +126 -60
  99. package/dist/javascript/templating/placeholder-replacement.js.map +1 -1
  100. package/dist/javascript/templating/precedence.d.ts +3 -1
  101. package/dist/javascript/templating/precedence.d.ts.map +1 -1
  102. package/dist/javascript/templating/precedence.js +28 -8
  103. package/dist/javascript/templating/precedence.js.map +1 -1
  104. package/dist/javascript/templating/template.d.ts +7 -6
  105. package/dist/javascript/templating/template.d.ts.map +1 -1
  106. package/dist/javascript/templating/template.js +31 -28
  107. package/dist/javascript/templating/template.js.map +1 -1
  108. package/dist/javascript/templating/types.d.ts +6 -3
  109. package/dist/javascript/templating/types.d.ts.map +1 -1
  110. package/dist/javascript/templating/utils.d.ts +10 -1
  111. package/dist/javascript/templating/utils.d.ts.map +1 -1
  112. package/dist/javascript/templating/utils.js +22 -3
  113. package/dist/javascript/templating/utils.js.map +1 -1
  114. package/dist/javascript/tree-debug.d.ts +2 -0
  115. package/dist/javascript/tree-debug.d.ts.map +1 -1
  116. package/dist/javascript/tree-debug.js +6 -0
  117. package/dist/javascript/tree-debug.js.map +1 -1
  118. package/dist/javascript/tsconfig.d.ts.map +1 -1
  119. package/dist/javascript/tsconfig.js +7 -17
  120. package/dist/javascript/tsconfig.js.map +1 -1
  121. package/dist/javascript/type-mapping.d.ts +34 -16
  122. package/dist/javascript/type-mapping.d.ts.map +1 -1
  123. package/dist/javascript/type-mapping.js +253 -249
  124. package/dist/javascript/type-mapping.js.map +1 -1
  125. package/dist/javascript/type-report-cli.d.ts +7 -0
  126. package/dist/javascript/type-report-cli.d.ts.map +1 -0
  127. package/dist/javascript/type-report-cli.js +129 -0
  128. package/dist/javascript/type-report-cli.js.map +1 -0
  129. package/dist/javascript/type-report.d.ts +61 -0
  130. package/dist/javascript/type-report.d.ts.map +1 -0
  131. package/dist/javascript/type-report.js +488 -0
  132. package/dist/javascript/type-report.js.map +1 -0
  133. package/dist/markers.d.ts +4 -0
  134. package/dist/markers.d.ts.map +1 -1
  135. package/dist/markers.js +8 -0
  136. package/dist/markers.js.map +1 -1
  137. package/dist/recipe.d.ts +5 -0
  138. package/dist/recipe.d.ts.map +1 -1
  139. package/dist/recipe.js +5 -0
  140. package/dist/recipe.js.map +1 -1
  141. package/dist/rewrite-javascript-version.txt +1 -1
  142. package/dist/rpc/queue.d.ts +16 -0
  143. package/dist/rpc/queue.d.ts.map +1 -1
  144. package/dist/rpc/queue.js +27 -9
  145. package/dist/rpc/queue.js.map +1 -1
  146. package/dist/rpc/request/prepare-recipe.d.ts +3 -1
  147. package/dist/rpc/request/prepare-recipe.d.ts.map +1 -1
  148. package/dist/rpc/request/prepare-recipe.js +13 -6
  149. package/dist/rpc/request/prepare-recipe.js.map +1 -1
  150. package/dist/search/is-source-file.d.ts +1 -0
  151. package/dist/search/is-source-file.d.ts.map +1 -1
  152. package/dist/search/is-source-file.js +7 -2
  153. package/dist/search/is-source-file.js.map +1 -1
  154. package/dist/test/rewrite-test.d.ts +5 -0
  155. package/dist/test/rewrite-test.d.ts.map +1 -1
  156. package/dist/test/rewrite-test.js +14 -3
  157. package/dist/test/rewrite-test.js.map +1 -1
  158. package/package.json +4 -3
  159. package/src/index.ts +3 -1
  160. package/src/java/formatting-utils.ts +5 -4
  161. package/src/java/rpc.ts +2 -0
  162. package/src/java/tree.ts +15 -0
  163. package/src/java/type.ts +19 -2
  164. package/src/javascript/add-import.ts +338 -152
  165. package/src/javascript/amd.ts +28 -12
  166. package/src/javascript/autodetect.ts +32 -41
  167. package/src/javascript/binding.ts +64 -22
  168. package/src/javascript/comparator.ts +119 -43
  169. package/src/javascript/format/format.ts +9 -0
  170. package/src/javascript/index.ts +6 -3
  171. package/src/javascript/markers.ts +43 -1
  172. package/src/javascript/method-matcher.ts +34 -22
  173. package/src/javascript/package-name.ts +45 -0
  174. package/src/javascript/preconditions.ts +7 -4
  175. package/src/javascript/recipes/change-method-target-to-import.ts +178 -0
  176. package/src/javascript/recipes/index.ts +1 -0
  177. package/src/javascript/scope.ts +705 -169
  178. package/src/javascript/search/uses-method.ts +2 -2
  179. package/src/javascript/search/uses-type.ts +30 -9
  180. package/src/javascript/templating/bindings.ts +13 -38
  181. package/src/javascript/templating/comparator.ts +16 -127
  182. package/src/javascript/templating/engine.ts +229 -127
  183. package/src/javascript/templating/pattern.ts +107 -28
  184. package/src/javascript/templating/placeholder-replacement.ts +154 -62
  185. package/src/javascript/templating/precedence.ts +33 -11
  186. package/src/javascript/templating/template.ts +35 -30
  187. package/src/javascript/templating/types.ts +6 -3
  188. package/src/javascript/templating/utils.ts +24 -2
  189. package/src/javascript/tree-debug.ts +9 -0
  190. package/src/javascript/tsconfig.ts +7 -17
  191. package/src/javascript/type-mapping.ts +258 -252
  192. package/src/javascript/type-report-cli.ts +96 -0
  193. package/src/javascript/type-report.ts +575 -0
  194. package/src/markers.ts +8 -0
  195. package/src/recipe.ts +6 -0
  196. package/src/rpc/queue.ts +44 -5
  197. package/src/rpc/request/prepare-recipe.ts +16 -6
  198. package/src/search/is-source-file.ts +8 -3
  199. package/src/test/rewrite-test.ts +14 -3
@@ -2,12 +2,14 @@ import {JavaScriptVisitor} from "./visitor";
2
2
  import {ElementRemovalFormatter, emptySpace, isIdentifier, J, NameTree, rightPadded, singleSpace, space, Statement, TrailingComma, Type} from "../java";
3
3
  import {JS, JSX} from "./tree";
4
4
  import {randomId, UUID} from "../uuid";
5
+ import {TypeVisitor} from "../java/type-visitor";
6
+ import {mapAsync, updateIfChanged} from "../util";
7
+ import {packageNameOf} from "./package-name";
5
8
  import {emptyMarkers, findMarker, markers, MarkersKind} from "../markers";
6
9
  import {NamedStyles} from "../style";
7
10
  import {getStyle, SpacesStyle, StyleKind} from "./style";
8
- import {bindingNames, compilationUnitOf, cursorOf, declarationsOf, deconflict, isValueReference, namesDeclaredIn, scopeOf, walk} from "./scope";
11
+ import {bindingNames, compilationUnitOf, cursorOf, declarationsOf, deconflict, namesDeclaredIn, resolve, scopeOf, walk} from "./scope";
9
12
  import {create as produce, Draft} from "mutative";
10
- import {TypeVisitor} from "../java/type-visitor";
11
13
  import {autoFormat} from "./format";
12
14
  import {getPrettierStyle} from "./format/prettier-format";
13
15
 
@@ -90,8 +92,13 @@ export function bindImport(
90
92
  // A queued import binds the module as much as one already in the file, so it answers a later
91
93
  // request the same way; a name a merged request never emits would be referenced but not bound.
92
94
  for (const v of visitor.afterVisit || []) {
95
+ if (v instanceof RebindImport && !sideEffectOnly && v.to.module === module &&
96
+ memberName(v.to.member) === memberName(options.member) &&
97
+ (options.alias === undefined || options.alias === v.boundName)) {
98
+ return v.boundName;
99
+ }
93
100
  if (!(v instanceof AddImport) || v.module !== module ||
94
- v.sideEffectOnly !== sideEffectOnly || v.typeOnly !== typeOnly) {
101
+ v.sideEffectOnly !== sideEffectOnly || !answersTypeOnly(v.typeOnly, typeOnly)) {
95
102
  continue;
96
103
  }
97
104
  // How a specifier prints, or what name would be nice, does not say which binding is wanted.
@@ -138,7 +145,11 @@ export function bindImport(
138
145
  const scope = scopeOf(cursor);
139
146
  for (const binding of moduleScopeBindings(cu)) {
140
147
  if (binding.module === module && binding.member === memberName(options.member) &&
141
- binding.typeOnly === typeOnly && scope.declaringScope(binding.name) === cu) {
148
+ scope.declaringScope(binding.name) === cu) {
149
+ if (!answersTypeOnly(binding.typeOnly ?? false, typeOnly)) {
150
+ // The queued request finds this binding and drops its `type`, so the name stays
151
+ visitor.afterVisit.push(new AddImport(options, binding.name));
152
+ }
142
153
  return binding.name;
143
154
  }
144
155
  }
@@ -340,6 +351,56 @@ function requireBindings(pattern: J | undefined, module: string): ModuleScopeBin
340
351
  : {name: bound.name, module, member: bound.member, typeOnly: false});
341
352
  }
342
353
 
354
+ /** Whether a binding answers a request, given which of the two is type-only. A value import binds the type too. */
355
+ function answersTypeOnly(bindingTypeOnly: boolean, requestTypeOnly: boolean): boolean {
356
+ return requestTypeOnly || !bindingTypeOnly;
357
+ }
358
+
359
+ /**
360
+ * `jsImport` binding `member` as a value. A `type` on the member's specifier goes, and one on the
361
+ * clause moves onto the clause's other specifiers.
362
+ */
363
+ function asValueImport(jsImport: JS.Import, member: string | undefined): JS.Import {
364
+ const clause = jsImport.importClause!;
365
+ const named = clause.namedBindings?.kind === JS.Kind.NamedImports ? clause.namedBindings as JS.NamedImports : undefined;
366
+ const imports = (specifier: J): boolean =>
367
+ specifier.kind === JS.Kind.ImportSpecifier && specifierBinding(specifier as JS.ImportSpecifier)?.member === member;
368
+
369
+ if (!clause.typeOnly) {
370
+ return !named ? jsImport : produce(jsImport, draft => {
371
+ for (const elem of (draft.importClause!.namedBindings as Draft<JS.NamedImports>).elements.elements) {
372
+ const specifier = elem.element as Draft<JS.ImportSpecifier>;
373
+ if (member !== undefined && imports(elem.element as J) && specifier.importType.element) {
374
+ specifier.specifier.prefix = specifier.importType.before;
375
+ specifier.importType = {...specifier.importType, before: emptySpace, element: false};
376
+ }
377
+ }
378
+ });
379
+ }
380
+ return produce(jsImport, draft => {
381
+ const draftClause = draft.importClause!;
382
+ draftClause.typeOnly = false;
383
+ // The space after `type` separates nothing once it is gone
384
+ if (draftClause.name) {
385
+ draftClause.name.element.prefix = emptySpace;
386
+ } else if (draftClause.namedBindings) {
387
+ draftClause.namedBindings.prefix = emptySpace;
388
+ if (named) {
389
+ (draftClause.namedBindings as Draft<JS.NamedImports>).elements.before = emptySpace;
390
+ }
391
+ }
392
+ if (named) {
393
+ for (const elem of (draftClause.namedBindings as Draft<JS.NamedImports>).elements.elements) {
394
+ const specifier = elem.element as Draft<JS.ImportSpecifier>;
395
+ if (!imports(elem.element as J) && specifier.kind === JS.Kind.ImportSpecifier) {
396
+ specifier.importType = {...specifier.importType, before: emptySpace, element: true};
397
+ specifier.specifier.prefix = singleSpace;
398
+ }
399
+ }
400
+ }
401
+ });
402
+ }
403
+
343
404
  /** The member a specifier imports and the name it binds it under, which are the same absent an alias. */
344
405
  function specifierBinding(specifier: JS.ImportSpecifier): { name: string; member: string } | undefined {
345
406
  if (specifier.specifier?.kind === J.Kind.Identifier) {
@@ -386,7 +447,8 @@ function importBindings(jsImport: JS.Import): ModuleScopeBinding[] {
386
447
  ? specifierBinding(elem.element as JS.ImportSpecifier)
387
448
  : undefined;
388
449
  if (bound) {
389
- bindings.push({...bound, module, typeOnly});
450
+ const specifier = elem.element as JS.ImportSpecifier;
451
+ bindings.push({...bound, module, typeOnly: typeOnly || specifier.importType.element});
390
452
  }
391
453
  }
392
454
  }
@@ -395,16 +457,17 @@ function importBindings(jsImport: JS.Import): ModuleScopeBinding[] {
395
457
  }
396
458
 
397
459
  /**
398
- * `names`, plus those pending `AddImport`s on the `afterVisit` queue have claimed. A queued
399
- * `RemoveImport` does not free one: it removes only what the file leaves unused, and binding a name
400
- * it keeps is an error, where an unnecessary suffix merely reads oddly.
460
+ * `names`, plus those pending `AddImport`s and `RebindImport`s on the `afterVisit` queue have
461
+ * claimed. A queued `RemoveImport` does not free one: it removes only what the file leaves unused,
462
+ * and binding a name it keeps is an error, where an unnecessary suffix merely reads oddly.
401
463
  */
402
464
  export function takenNames(names: ReadonlySet<string>, visitor: JavaScriptVisitor<any>): Set<string> {
403
465
  const taken = new Set<string>(names);
404
466
 
405
467
  for (const v of visitor.afterVisit || []) {
406
- if (v instanceof AddImport && v.bindingName) {
407
- taken.add(v.bindingName);
468
+ const claimed = claimedName(v);
469
+ if (claimed) {
470
+ taken.add(claimed);
408
471
  }
409
472
  }
410
473
 
@@ -413,8 +476,11 @@ export function takenNames(names: ReadonlySet<string>, visitor: JavaScriptVisito
413
476
 
414
477
  /** As {@link takenNames}, for a caller asking about one name rather than probing repeatedly. */
415
478
  export function nameTaken(name: string, names: ReadonlySet<string>, visitor: JavaScriptVisitor<any>): boolean {
416
- return names.has(name) ||
417
- (visitor.afterVisit || []).some(v => v instanceof AddImport && v.bindingName === name);
479
+ return names.has(name) || (visitor.afterVisit || []).some(v => claimedName(v) === name);
480
+ }
481
+
482
+ function claimedName(v: unknown): string | undefined {
483
+ return v instanceof AddImport ? v.bindingName : v instanceof RebindImport ? v.boundName : undefined;
418
484
  }
419
485
 
420
486
  /**
@@ -866,6 +932,21 @@ export class AddImport<P> extends JavaScriptVisitor<P> {
866
932
  return compilationUnit;
867
933
  }
868
934
 
935
+ // A type-only binding of the member would leave a value use unbound, and a second binding
936
+ // of the same name would not compile, so the binding the file has loses its `type`
937
+ if (!this.typeOnly && !this.sideEffectOnly) {
938
+ const statements = compilationUnit.statements.map(stmt => {
939
+ const element = stmt.element;
940
+ return element?.kind === JS.Kind.Import &&
941
+ importBindings(element as JS.Import).some(b => b.typeOnly && this.bindsSameName(b))
942
+ ? {...stmt, element: asValueImport(element as JS.Import, memberName(this.member))}
943
+ : stmt;
944
+ });
945
+ if (statements.some((stmt, i) => stmt !== compilationUnit.statements[i])) {
946
+ return {...compilationUnit, statements} as JS.CompilationUnit;
947
+ }
948
+ }
949
+
869
950
  // If onlyIfReferenced is true, check if the identifier is actually used
870
951
  // Skip this check for side-effect imports
871
952
  if (!this.sideEffectOnly && this.onlyIfReferenced) {
@@ -972,36 +1053,13 @@ export class AddImport<P> extends JavaScriptVisitor<P> {
972
1053
 
973
1054
  if (statement?.kind === JS.Kind.Import) {
974
1055
  const jsImport = statement as JS.Import;
975
- const moduleSpecifier = jsImport.moduleSpecifier?.element;
976
-
977
- if (!moduleSpecifier) {
978
- continue;
979
- }
980
-
981
- const moduleName = this.getModuleName(moduleSpecifier);
982
-
983
- // Check if this is an import from our target module
984
- if (moduleName !== this.module) {
985
- continue;
986
- }
987
-
988
- const importClause = jsImport.importClause;
989
- if (!importClause) {
990
- continue;
991
- }
992
-
993
- // Only merge into imports with matching typeOnly - don't mix type and value imports
994
- if (importClause.typeOnly !== this.typeOnly) {
1056
+ if (!acceptsNamedMember(jsImport, this.module, this.typeOnly)) {
995
1057
  continue;
996
1058
  }
1059
+ const importClause = jsImport.importClause!;
997
1060
 
998
1061
  // Case 1: Existing import has named bindings - merge into them
999
1062
  if (importClause.namedBindings) {
1000
- // Only merge into NamedImports, not namespace imports
1001
- if (importClause.namedBindings.kind !== JS.Kind.NamedImports) {
1002
- continue;
1003
- }
1004
-
1005
1063
  // We found a matching import with named bindings - merge into it
1006
1064
  return formatMergedImport(await this.produceJavaScript(compilationUnit, p, async draft => {
1007
1065
  const namedImports = importClause.namedBindings as JS.NamedImports;
@@ -1102,7 +1160,7 @@ export class AddImport<P> extends JavaScriptVisitor<P> {
1102
1160
 
1103
1161
  // Case 2: Default import without named bindings - add named bindings
1104
1162
  // Transform: import React from 'react' -> import React, { useState } from 'react'
1105
- if (importClause.name && !importClause.namedBindings) {
1163
+ if (importClause.name) {
1106
1164
  return formatMergedImport(await this.produceJavaScript(compilationUnit, p, async draft => {
1107
1165
  const newSpecifier = this.createImportSpecifier();
1108
1166
 
@@ -1207,9 +1265,13 @@ export class AddImport<P> extends JavaScriptVisitor<P> {
1207
1265
 
1208
1266
  /** Whether a binding the file already has serves this request. */
1209
1267
  private answeredBy(binding: ModuleScopeBinding): boolean {
1268
+ return this.bindsSameName(binding) && answersTypeOnly(binding.typeOnly ?? false, this.typeOnly);
1269
+ }
1270
+
1271
+ /** Whether `binding` binds this request's member under its name, whether or not it is type-only. */
1272
+ private bindsSameName(binding: ModuleScopeBinding): boolean {
1210
1273
  return binding.module === this.module &&
1211
1274
  binding.member === memberName(this.member) &&
1212
- binding.typeOnly === this.typeOnly &&
1213
1275
  (this.anyNameAnswers || binding.name === this.bindingName);
1214
1276
  }
1215
1277
 
@@ -1797,6 +1859,13 @@ function namedSpecifierImports(specifier: JS.ImportSpecifier["specifier"], key:
1797
1859
  return false;
1798
1860
  }
1799
1861
 
1862
+ /** Whether `imp` binds `member` type-only, by its clause's `type` or the specifier's own. */
1863
+ function bindsTypeOnly(imp: JS.Import, member: string | undefined): boolean {
1864
+ const key = memberName(member);
1865
+ return (imp.importClause?.typeOnly ?? false) ||
1866
+ (key !== undefined && key !== '*' && namedSpecifierIsTypeOnly(imp, key));
1867
+ }
1868
+
1800
1869
  /** Whether the named specifier binding `key` carries its own inline `type`, as in `{type a, b}`. */
1801
1870
  function namedSpecifierIsTypeOnly(imp: JS.Import, key: string): boolean {
1802
1871
  const namedBindings = imp.importClause?.namedBindings;
@@ -1832,12 +1901,39 @@ function isOnlyMember(jsImport: JS.Import): boolean {
1832
1901
  return (hasDefault ? 1 : 0) + (hasNamespace ? 1 : 0) + namedImportCount(jsImport) === 1;
1833
1902
  }
1834
1903
 
1904
+ function importedModule(jsImport: JS.Import): string | undefined {
1905
+ const specifier = jsImport.moduleSpecifier?.element;
1906
+ return jsImport.importClause !== undefined && specifier?.kind === J.Kind.Literal
1907
+ ? moduleNameOf(specifier as J.Literal)
1908
+ : undefined;
1909
+ }
1910
+
1911
+ /** Whether `AddImport` merges a named member of `module` into `jsImport` rather than adding a statement. */
1912
+ function acceptsNamedMember(jsImport: JS.Import, module: string, typeOnly: boolean): boolean {
1913
+ const clause = jsImport.importClause;
1914
+ const specifier = jsImport.moduleSpecifier?.element;
1915
+ if (!clause || clause.typeOnly !== typeOnly || specifier?.kind !== J.Kind.Literal ||
1916
+ moduleNameOf(specifier as J.Literal) !== module) {
1917
+ return false;
1918
+ }
1919
+ return clause.namedBindings ? clause.namedBindings.kind === JS.Kind.NamedImports : clause.name !== undefined;
1920
+ }
1921
+
1922
+ /** Which of an import clause's three slots `member` binds — `import`, `import *`, or `import {}`. */
1923
+ export function bindingShape(member: string | undefined): "default" | "namespace" | "named" {
1924
+ const key = memberName(member);
1925
+ return key === undefined ? "default" : key === "*" ? "namespace" : "named";
1926
+ }
1927
+
1835
1928
  export interface ExistingImportBinding {
1836
1929
  localName: string;
1837
1930
  onlyMemberOfStatement: boolean;
1838
1931
 
1839
1932
  /** Whether the source states this local name — `import {a as b}` — or takes it from the member. */
1840
1933
  aliased: boolean;
1934
+
1935
+ /** Whether the clause or the specifier itself is marked `type`. */
1936
+ typeOnly: boolean;
1841
1937
  }
1842
1938
 
1843
1939
  /**
@@ -1859,7 +1955,8 @@ export function existingImportBinding(
1859
1955
  return {
1860
1956
  localName,
1861
1957
  onlyMemberOfStatement: isOnlyMember(element as JS.Import),
1862
- aliased: localName !== memberName(member)
1958
+ aliased: localName !== memberName(member),
1959
+ typeOnly: bindsTypeOnly(element as JS.Import, member)
1863
1960
  };
1864
1961
  }
1865
1962
  }
@@ -1961,11 +2058,44 @@ function removeBinding(jsImport: JS.Import, member: string | undefined): JS.Impo
1961
2058
  return {...jsImport, importClause: {...importClause, namedBindings: updatedNamedImports}};
1962
2059
  }
1963
2060
 
2061
+ /** `cu` without the statement `id`, its prefix carried onto the next one so a file header survives. */
2062
+ function withoutStatement(cu: JS.CompilationUnit, id: UUID): JS.CompilationUnit {
2063
+ const formatter = new ElementRemovalFormatter<J>(true);
2064
+ const statements: J.RightPadded<Statement>[] = [];
2065
+ for (const stmt of cu.statements) {
2066
+ if (stmt.element?.id === id) {
2067
+ formatter.markRemoved(stmt.element);
2068
+ } else {
2069
+ const kept = stmt.element && formatter.processKept(stmt.element) as Statement;
2070
+ statements.push(kept === stmt.element ? stmt : {...stmt, element: kept!});
2071
+ }
2072
+ }
2073
+ return {...cu, statements};
2074
+ }
2075
+
1964
2076
  /**
1965
- * Moves the binding `from` names to `to`, binding it under `boundName`. In place when the
1966
- * statement that carries it binds nothing else — module and member specifier rewritten there
1967
- * directly; otherwise the old specifier drops and {@link bindImport} queues the replacement.
1968
- * A `boundName` of its own renames the binding, and the file's references to it follow.
2077
+ * Whether moving the only binding of `jsImport` to `to` replaces the statement rather than rewriting
2078
+ * it in place. A different shape needs a different clause, and another import of `to` takes it in.
2079
+ */
2080
+ function replacesStatement(
2081
+ cu: JS.CompilationUnit,
2082
+ jsImport: JS.Import,
2083
+ from: {member?: string},
2084
+ to: {module: string; member?: string},
2085
+ typeOnly: boolean
2086
+ ): boolean {
2087
+ const shape = bindingShape(to.member);
2088
+ return shape !== bindingShape(from.member) || (shape === "named" && cu.statements.some(s =>
2089
+ s.element !== jsImport && s.element?.kind === JS.Kind.Import &&
2090
+ acceptsNamedMember(s.element as JS.Import, to.module, typeOnly)));
2091
+ }
2092
+
2093
+ const esmStyle = {default: ImportStyle.ES6Default, namespace: ImportStyle.ES6Namespace, named: ImportStyle.ES6Named};
2094
+
2095
+ /**
2096
+ * Moves the binding `from` names to `to`, binding it under `boundName`, and the file's references
2097
+ * follow a `boundName` of its own. Whether the statement is rewritten in place or replaced is decided
2098
+ * when this visitor runs, because a sibling rebind may empty the statement first.
1969
2099
  *
1970
2100
  * Not built on `RemoveImport`/`maybeUnbind`: those only drop a binding once nothing references
1971
2101
  * it, but a rebind moves one that is still in use — removal here has to be unconditional.
@@ -1982,23 +2112,46 @@ export class RebindImport<P> extends JavaScriptVisitor<P> {
1982
2112
 
1983
2113
  private transformedInPlace = false;
1984
2114
  private typeOnly = false;
1985
- private readonly movedTypes = new MovedTypes(this.from, this.to);
1986
- /** Identifiers settled as references to the binding, so a parent need not settle it again. */
1987
- private readonly references = new Set<string>();
2115
+ private cu?: JS.CompilationUnit;
2116
+ private dropped?: UUID;
2117
+ private droppedQuote?: QuoteChar;
2118
+ private movedTypes!: MovedTypes;
1988
2119
 
1989
2120
  private get renaming(): boolean {
1990
2121
  return this.boundName !== this.localName;
1991
2122
  }
1992
2123
 
1993
2124
  override async visitJsCompilationUnit(cu: JS.CompilationUnit, p: P): Promise<J | undefined> {
1994
- const visited = await super.visitJsCompilationUnit(cu, p) as JS.CompilationUnit;
2125
+ this.cu = cu;
2126
+ const imports = cu.statements
2127
+ .filter(s => s.element?.kind === JS.Kind.Import)
2128
+ .map(s => s.element as JS.Import);
2129
+ const ofModule = imports.filter(imp => importedModule(imp) === this.from.module);
2130
+ const fromPackage = packageOf(this.from.module);
2131
+ const member = declaredMember(this.from);
2132
+ this.movedTypes = new MovedTypes(this.from, this.to, {
2133
+ module: ofModule.length > 1 || ofModule.some(imp => !isOnlyMember(imp)),
2134
+ // Same-named classes from two subpaths of one package share a name.
2135
+ className: member !== undefined && imports.filter(imp => {
2136
+ const module = importedModule(imp);
2137
+ return module !== undefined && packageOf(module) === fromPackage &&
2138
+ importBinds(imp, module, member) !== undefined;
2139
+ }).length > 1
2140
+ });
2141
+ let visited = await super.visitJsCompilationUnit(cu, p) as JS.CompilationUnit;
2142
+ if (this.dropped !== undefined) {
2143
+ visited = withoutStatement(visited, this.dropped);
2144
+ }
1995
2145
  if (!this.transformedInPlace) {
1996
2146
  bindImport(this, {
1997
2147
  module: this.to.module,
1998
2148
  member: this.to.member,
1999
2149
  alias: this.boundName,
2000
2150
  typeOnly: this.typeOnly,
2001
- onlyIfReferenced: false
2151
+ onlyIfReferenced: false,
2152
+ quoteStyle: this.droppedQuote,
2153
+ // The binding was an ES import, though the file may no longer read as a module without it.
2154
+ style: esmStyle[bindingShape(this.to.member)]
2002
2155
  });
2003
2156
  }
2004
2157
  return visited;
@@ -2008,19 +2161,24 @@ export class RebindImport<P> extends JavaScriptVisitor<P> {
2008
2161
  const imp = await super.visitImportDeclaration(jsImport, p) as JS.Import;
2009
2162
 
2010
2163
  const key = memberName(this.from.member);
2011
- // One call moves one binding, and no two imports bind the same local name, so the name
2012
- // read from the matched statement is what picks it back out of the file.
2013
- if (importBinds(imp, this.from.module, this.from.member) !== this.localName) {
2164
+ // One call moves one module-scope binding, and no two of those share a local name, so the
2165
+ // name read from the matched statement is what picks it back out of the file.
2166
+ const cu = this.cu;
2167
+ if (!cu?.statements.some(s => s.element === jsImport) ||
2168
+ importBinds(imp, this.from.module, this.from.member) !== this.localName) {
2014
2169
  return imp;
2015
2170
  }
2016
- // A moved named specifier's own inline `type` marks it type-only even where the clause
2017
- // it's leaving is not — the replacement needs the same answer to stay type-safe.
2018
- this.typeOnly = (imp.importClause?.typeOnly ?? false) ||
2019
- (key !== undefined && key !== '*' && namedSpecifierIsTypeOnly(imp, key));
2171
+ // The replacement takes the moved binding's `type` marking to stay type-safe.
2172
+ this.typeOnly = bindsTypeOnly(imp, this.from.member);
2020
2173
 
2021
2174
  if (!isOnlyMember(imp)) {
2022
2175
  return removeBinding(imp, this.from.member);
2023
2176
  }
2177
+ if (replacesStatement(cu, jsImport, this.from, this.to, this.typeOnly)) {
2178
+ this.dropped = imp.id;
2179
+ this.droppedQuote = quoteOf(imp.moduleSpecifier?.element);
2180
+ return imp;
2181
+ }
2024
2182
 
2025
2183
  this.transformedInPlace = true;
2026
2184
  return produce(imp, draft => {
@@ -2044,56 +2202,14 @@ export class RebindImport<P> extends JavaScriptVisitor<P> {
2044
2202
  }
2045
2203
 
2046
2204
  override async visitIdentifier(identifier: J.Identifier, p: P): Promise<J | undefined> {
2047
- if (!this.referencesBinding(identifier)) {
2048
- return super.visitIdentifier(identifier, p);
2049
- }
2050
- this.references.add(identifier.id);
2051
- // The name follows the binding, and so does what the name is attributed to; an aliased
2052
- // binding keeps its name and still resolves somewhere new.
2053
- const type = await this.movedTypes.visit(identifier.type, undefined);
2054
- const fieldType = await this.movedTypes.visit(identifier.fieldType, undefined) as Type.Variable | undefined;
2055
- return !this.renaming && type === identifier.type && fieldType === identifier.fieldType
2056
- ? identifier
2057
- : {...identifier, simpleName: this.boundName, type, fieldType} as J.Identifier;
2058
- }
2059
-
2060
- override async visitMethodInvocation(method: J.MethodInvocation, p: P): Promise<J | undefined> {
2061
- const m = await super.visitMethodInvocation(method, p) as J.MethodInvocation;
2062
- if (!this.referencesMovedBinding(m.name)) {
2063
- return m;
2064
- }
2065
- const methodType = await this.movedTypes.visit(m.methodType, undefined) as Type.Method | undefined;
2066
- return methodType === m.methodType ? m : {...m, methodType} as J.MethodInvocation;
2067
- }
2068
-
2069
- override async visitNewClass(newClass: J.NewClass, p: P): Promise<J | undefined> {
2070
- const nc = await super.visitNewClass(newClass, p) as J.NewClass;
2071
- if (!this.referencesMovedBinding(nc.class)) {
2072
- return nc;
2073
- }
2074
- const type = await this.movedTypes.visit(nc.type, undefined);
2075
- const methodType = await this.movedTypes.visit(nc.methodType, undefined) as Type.Method | undefined;
2076
- const constructorType = await this.movedTypes.visit(nc.constructorType, undefined) as Type.Method | undefined;
2077
- return type === nc.type && methodType === nc.methodType && constructorType === nc.constructorType
2078
- ? nc
2079
- : {...nc, type, methodType, constructorType} as J.NewClass;
2080
- }
2081
-
2082
- override async visitFunctionCall(functionCall: JS.FunctionCall, p: P): Promise<J | undefined> {
2083
- const fc = await super.visitFunctionCall(functionCall, p) as JS.FunctionCall;
2084
- if (!this.referencesMovedBinding(fc.function?.element)) {
2085
- return fc;
2086
- }
2087
- const methodType = await this.movedTypes.visit(fc.methodType, undefined) as Type.Method | undefined;
2088
- return methodType === fc.methodType ? fc : {...fc, methodType} as JS.FunctionCall;
2205
+ const renamed = this.renaming && this.referencesBinding(identifier);
2206
+ const visited = await super.visitIdentifier(identifier, p) as J.Identifier;
2207
+ return renamed ? {...visited, simpleName: this.boundName} as J.Identifier : visited;
2089
2208
  }
2090
2209
 
2091
- /**
2092
- * Whether `callee` is the identifier `visitIdentifier` settled as a reference to the binding.
2093
- * A rewrite keeps the identifier's id, so the one it settled is the one still standing here.
2094
- */
2095
- private referencesMovedBinding(callee: J | undefined): boolean {
2096
- return callee?.kind === J.Kind.Identifier && this.references.has(callee.id);
2210
+ /** Any type in the file can name what moved, whether or not it references the binding. */
2211
+ protected override async visitType(javaType: Type | undefined, p: P): Promise<Type | undefined> {
2212
+ return this.movedTypes.visit(javaType, undefined);
2097
2213
  }
2098
2214
 
2099
2215
  /**
@@ -2120,7 +2236,14 @@ export class RebindImport<P> extends JavaScriptVisitor<P> {
2120
2236
  }
2121
2237
  const specifier = exportSpecifier.specifier;
2122
2238
  if (specifier.kind === J.Kind.Identifier && (specifier as J.Identifier).simpleName === this.localName) {
2123
- return {...exportSpecifier, specifier: aliasing(specifier as J.Identifier, this.boundName)} as JS.ExportSpecifier;
2239
+ const retyped = await this.retyped(specifier as J.Identifier);
2240
+ const alias = aliasing(retyped, this.boundName);
2241
+ // The property name is the reference here, so it carries the binding's attribution.
2242
+ const reference = {...alias.propertyName.element, type: retyped.type, fieldType: retyped.fieldType};
2243
+ return {
2244
+ ...exportSpecifier,
2245
+ specifier: {...alias, propertyName: {...alias.propertyName, element: reference}}
2246
+ } as JS.ExportSpecifier;
2124
2247
  }
2125
2248
  if (specifier.kind === JS.Kind.Alias) {
2126
2249
  const alias = specifier as JS.Alias;
@@ -2130,7 +2253,10 @@ export class RebindImport<P> extends JavaScriptVisitor<P> {
2130
2253
  ...exportSpecifier,
2131
2254
  specifier: {
2132
2255
  ...alias,
2133
- propertyName: {...alias.propertyName, element: {...propertyName, simpleName: this.boundName}}
2256
+ propertyName: {
2257
+ ...alias.propertyName,
2258
+ element: {...await this.retyped(propertyName as J.Identifier), simpleName: this.boundName}
2259
+ }
2134
2260
  }
2135
2261
  } as JS.ExportSpecifier
2136
2262
  : exportSpecifier;
@@ -2138,64 +2264,105 @@ export class RebindImport<P> extends JavaScriptVisitor<P> {
2138
2264
  return super.visitExportSpecifier(exportSpecifier, p);
2139
2265
  }
2140
2266
 
2267
+ /** `identifier` attributed to what moved, for a rewrite that does not visit it again. */
2268
+ private async retyped(identifier: J.Identifier): Promise<J.Identifier> {
2269
+ return {
2270
+ ...identifier,
2271
+ type: await this.movedTypes.visit(identifier.type, undefined),
2272
+ fieldType: await this.movedTypes.visit(identifier.fieldType, undefined)
2273
+ };
2274
+ }
2275
+
2141
2276
  /**
2142
2277
  * Whether the identifier at the cursor stands for the moved binding: the right name, in a
2143
- * position that references rather than declares, reaching the module scope that binds it.
2278
+ * position a rename may rewrite, resolving to the module scope that binds it.
2144
2279
  */
2145
2280
  private referencesBinding(identifier: J.Identifier): boolean {
2146
- return identifier.simpleName === this.localName &&
2147
- // The specifier binding the name is the one edit that is not a reference to it.
2148
- !this.cursor.firstEnclosing((v): v is JS.Import => (v as J | undefined)?.kind === JS.Kind.Import) &&
2149
- isValueReference(this.cursor, identifier) &&
2150
- scopeOf(this.cursor).declaringScope(this.localName)?.kind === JS.Kind.CompilationUnit;
2281
+ return identifier.simpleName === this.localName && this.readsModuleBinding(identifier);
2282
+ }
2283
+
2284
+ private readsModuleBinding(identifier: J.Identifier): boolean {
2285
+ return resolve(this.cursor, identifier)?.kind === JS.Kind.CompilationUnit;
2151
2286
  }
2152
2287
 
2153
- /** A shorthand property's name slot is also the reference to the binding. */
2288
+ /**
2289
+ * A shorthand property's name slot is also the reference to the binding: `{x}` becomes
2290
+ * `{x: y}`, and `{x = 1}` becomes `{x: y = 1}`.
2291
+ */
2154
2292
  override async visitPropertyAssignment(propertyAssignment: JS.PropertyAssignment, p: P): Promise<J | undefined> {
2155
2293
  const name = propertyAssignment.name.element;
2156
- if (this.renaming && propertyAssignment.initializer === undefined &&
2294
+ if (this.renaming && propertyAssignment.assigmentToken === JS.PropertyAssignment.Token.Equals &&
2157
2295
  name.kind === J.Kind.Identifier && (name as J.Identifier).simpleName === this.localName &&
2158
- scopeOf(this.cursor).declaringScope(this.localName)?.kind === JS.Kind.CompilationUnit) {
2159
- // The key names a property rather than the binding, so it carries no attribution,
2160
- // the same way `aliasing` builds a property name that stands for nothing.
2296
+ this.readsModuleBinding(name as J.Identifier)) {
2297
+ const reference: J.Identifier = {...await this.retyped(name as J.Identifier), simpleName: this.boundName};
2298
+ const fallback = propertyAssignment.initializer;
2299
+ // The key names a property rather than the binding, so it is a new node carrying no
2300
+ // attribution, the same way `aliasing` builds a property name that stands for nothing.
2161
2301
  return {
2162
2302
  ...propertyAssignment,
2163
- name: {...propertyAssignment.name, element: {...name, type: undefined, fieldType: undefined}},
2303
+ name: {
2304
+ ...propertyAssignment.name,
2305
+ element: {...name, id: randomId(), type: undefined, fieldType: undefined},
2306
+ after: emptySpace
2307
+ },
2164
2308
  assigmentToken: JS.PropertyAssignment.Token.Colon,
2165
- initializer: {...(name as J.Identifier), prefix: singleSpace, simpleName: this.boundName}
2309
+ initializer: fallback === undefined ? {...reference, prefix: singleSpace} : {
2310
+ id: randomId(),
2311
+ kind: J.Kind.Assignment,
2312
+ prefix: singleSpace,
2313
+ markers: emptyMarkers,
2314
+ variable: {...reference, prefix: emptySpace},
2315
+ assignment: {
2316
+ kind: J.Kind.LeftPadded,
2317
+ before: propertyAssignment.name.after,
2318
+ element: fallback,
2319
+ markers: emptyMarkers
2320
+ },
2321
+ type: reference.type
2322
+ } as J.Assignment
2166
2323
  } as JS.PropertyAssignment;
2167
2324
  }
2168
2325
  return super.visitPropertyAssignment(propertyAssignment, p);
2169
2326
  }
2170
2327
  }
2171
2328
 
2172
- /**
2173
- * Rewrites the attribution a move invalidates, onto the module and member it moved to. Applied at
2174
- * a reference to the moved binding. See CLAUDE.md: What a rebind's attribution follows.
2175
- */
2329
+ /** Rewrites a file's attribution onto the module and member a binding moved to. */
2176
2330
  class MovedTypes extends TypeVisitor<undefined> {
2177
2331
  private readonly onPath = new Set<Type>();
2178
2332
  private readonly answered = new Map<Type, Type | undefined>();
2179
- private readonly renamed: ReadonlyMap<string, string>;
2333
+ private readonly modules = new Map<Type, Type.FullyQualified>();
2334
+ private readonly classes: ReadonlyMap<string, string>;
2180
2335
  private readonly fromMember?: string;
2181
2336
  private readonly toMember?: string;
2182
- private readonly toModule: string;
2183
2337
 
2184
- constructor(from: {module: string; member?: string}, to: {module: string; member?: string}) {
2338
+ /**
2339
+ * `shared` says what another binding in the file shares with the moved one. A shared module
2340
+ * object or package-qualified class name stands for that binding too, so it keeps its name.
2341
+ */
2342
+ constructor(
2343
+ private readonly from: {module: string; member?: string},
2344
+ private readonly to: {module: string; member?: string},
2345
+ shared: {module: boolean; className: boolean}
2346
+ ) {
2185
2347
  super();
2186
- // Where no member is named the two keys coincide, and both name the module.
2187
- this.renamed = new Map([[from.module, to.module], [qualifiedName(from), qualifiedName(to)]]);
2188
2348
  this.fromMember = declaredMember(from);
2189
2349
  this.toMember = declaredMember(to);
2190
- this.toModule = to.module;
2350
+ if (this.fromMember === undefined) {
2351
+ this.classes = new Map(shared.module ? [] : [[from.module, qualifiedName(to)]]);
2352
+ } else {
2353
+ // A default export declares no member name, so the class keeps its own.
2354
+ const toClass = `${packageOf(to.module)}.${this.toMember ?? this.fromMember}`;
2355
+ const packaged = `${packageOf(from.module)}.${this.fromMember}`;
2356
+ // The type mapper names an imported alias symbol after the specifier.
2357
+ this.classes = new Map([packaged, qualifiedName(from)]
2358
+ .filter(name => !shared.className || name !== packaged)
2359
+ .map(name => [name, toClass]));
2360
+ }
2191
2361
  }
2192
2362
 
2193
2363
  /**
2194
- * A type reached while it is still being visited is a cycle — a class holds a method whose
2195
- * declaring type is that class — and answers with itself, which is what ends the walk. Every
2196
- * type visited is remembered by its answer, so a graph whose references fan out or rejoin is
2197
- * walked once rather than once per path that reaches into it. An answer settled inside a cycle
2198
- * stands for the path it was on, so a walk reusing it can leave a rename unapplied.
2364
+ * A type reached while it is still being visited answers with itself, which ends a cycle
2365
+ * through a type variable's bounds. An answer settled inside such a cycle can miss a rename.
2199
2366
  */
2200
2367
  override async visit<T extends Type>(type: T | undefined, p: undefined): Promise<T | undefined> {
2201
2368
  if (type === undefined || this.onPath.has(type)) {
@@ -2214,38 +2381,57 @@ class MovedTypes extends TypeVisitor<undefined> {
2214
2381
  }
2215
2382
  }
2216
2383
 
2384
+ /** A class's members are left out, which keeps the walk to a type's signature, as in Java's `ChangeType`. */
2217
2385
  protected override async visitClass(aClass: Type.Class, p: undefined): Promise<Type | undefined> {
2218
- const visited = await super.visitClass(aClass, p) as Type.Class;
2219
- const moved = this.renamed.get(visited.fullyQualifiedName);
2220
- return moved === undefined || moved === visited.fullyQualifiedName
2221
- ? visited
2222
- : {...visited, fullyQualifiedName: moved} as Type.Class;
2386
+ const visited = updateIfChanged(aClass, {
2387
+ supertype: await this.visit(aClass.supertype, p),
2388
+ owningClass: await this.visit(aClass.owningClass, p),
2389
+ interfaces: aClass.interfaces && await mapAsync(aClass.interfaces, i => this.visit(i, p)),
2390
+ typeParameters: aClass.typeParameters && await mapAsync(aClass.typeParameters, t => this.visit(t, p))
2391
+ });
2392
+ const moved = this.classes.get(visited.fullyQualifiedName);
2393
+ return moved === undefined ? visited : {...visited, fullyQualifiedName: moved} as Type.Class;
2223
2394
  }
2224
2395
 
2225
2396
  protected override async visitMethod(method: Type.Method, p: undefined): Promise<Type | undefined> {
2226
2397
  const visited = await super.visitMethod(method, p) as Type.Method;
2227
- return this.declaresMovedMember(visited.name, visited.declaringType)
2228
- ? {...visited, name: this.toMember} as Type.Method
2229
- : visited;
2398
+ if (!this.isMovedMember(method.name, method.declaringType)) {
2399
+ return visited;
2400
+ }
2401
+ const declaringType = this.movedModule(method.declaringType);
2402
+ return {...visited, declaringType, name: this.toMember ?? visited.name} as Type.Method;
2230
2403
  }
2231
2404
 
2232
2405
  protected override async visitVariable(variable: Type.Variable, p: undefined): Promise<Type | undefined> {
2233
2406
  const visited = await super.visitVariable(variable, p) as Type.Variable;
2234
- return this.declaresMovedMember(visited.name, visited.owner)
2235
- ? {...visited, name: this.toMember} as Type.Variable
2236
- : visited;
2407
+ if (!this.isMovedMember(variable.name, variable.owner)) {
2408
+ return visited;
2409
+ }
2410
+ return {...visited, owner: this.movedModule(variable.owner!), name: this.toMember ?? visited.name} as Type.Variable;
2237
2411
  }
2238
2412
 
2239
2413
  /**
2240
- * Whether `name`, declared on `owner`, is the member that moved. `owner` has already followed
2241
- * the move by the time this runs, so it is the module moved *to* that it has to name. A
2242
- * default or namespace binding declares no member, so there is no name for one to take.
2414
+ * Whether `name` is the moved member as the module object `owner` declares it. That covers a
2415
+ * function, a constant and a moved class's constructor. A sibling declared beside it is not one.
2243
2416
  */
2244
- private declaresMovedMember(name: string, owner: Type | undefined): boolean {
2245
- return this.toMember !== undefined && name === this.fromMember && this.toMember !== this.fromMember &&
2246
- owner !== undefined && Type.isFullyQualified(owner) &&
2247
- Type.FullyQualified.getFullyQualifiedName(owner) === this.toModule;
2417
+ private isMovedMember(name: string, owner: Type | undefined): boolean {
2418
+ return this.fromMember !== undefined && name === this.fromMember && owner !== undefined &&
2419
+ Type.isFullyQualified(owner) && Type.FullyQualified.getFullyQualifiedName(owner) === this.from.module;
2248
2420
  }
2421
+
2422
+ private movedModule(owner: Type): Type.FullyQualified {
2423
+ let moved = this.modules.get(owner);
2424
+ if (moved === undefined) {
2425
+ moved = {...owner, fullyQualifiedName: this.to.module} as Type.FullyQualified;
2426
+ this.modules.set(owner, moved);
2427
+ }
2428
+ return moved;
2429
+ }
2430
+ }
2431
+
2432
+ /** The package a class `module` exports is named after. A relative specifier names none, so it stands for itself. */
2433
+ function packageOf(module: string): string {
2434
+ return module.startsWith('.') || module.startsWith('/') ? module : packageNameOf(module);
2249
2435
  }
2250
2436
 
2251
2437
  /** A member's qualified name, or the module's own where no member is named. */