@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
@@ -1,13 +1,20 @@
1
1
  import {Type} from "../java";
2
2
  import FullyQualified = Type.FullyQualified;
3
3
 
4
+ /**
5
+ * Matches a method type against a `<declaring type> <name>(<args>)` pattern.
6
+ * `REWRITE_JAVASCRIPT_DUMP_TYPES=1` in a test run prints the pattern each call would match.
7
+ *
8
+ * With `matchOverrides`, the pattern's type may also be a supertype or interface of the declaring
9
+ * type, as in Java. A pattern naming a subtype of the declaring type never matches.
10
+ */
4
11
  export class MethodMatcher {
5
12
  private readonly packagePattern: string;
6
13
  private readonly typePattern: string;
7
14
  private readonly methodPattern: string;
8
15
  private readonly argumentPatterns: string[];
9
16
 
10
- constructor(pattern: string) {
17
+ constructor(pattern: string, private readonly matchOverrides: boolean = false) {
11
18
  // Find the last space before the method spec (which contains parentheses)
12
19
  const firstParenIndex = pattern.indexOf('(');
13
20
  if (firstParenIndex === -1) {
@@ -31,7 +38,9 @@ export class MethodMatcher {
31
38
  } else {
32
39
  const lastDotIndex = typeSpec.lastIndexOf('.');
33
40
  if (lastDotIndex === -1) {
34
- this.packagePattern = '*';
41
+ // As in Java, a type without a package matches only the type in no package, such as
42
+ // the global `Array` or the module `fs-extra`. `*.Array` matches any package.
43
+ this.packagePattern = '';
35
44
  this.typePattern = typeSpec;
36
45
  } else {
37
46
  // Check if we're splitting a *.. pattern incorrectly
@@ -71,23 +80,7 @@ export class MethodMatcher {
71
80
  return false;
72
81
  }
73
82
 
74
- // Extract fully qualified name from declaringType
75
- const fullyQualifiedName = FullyQualified.getFullyQualifiedName(method.declaringType);
76
-
77
- // Split fully qualified name into package and type
78
- const lastDotIndex = fullyQualifiedName.lastIndexOf('.');
79
- const packageName = lastDotIndex === -1 ? '' : fullyQualifiedName.substring(0, lastDotIndex);
80
- const typeName = lastDotIndex === -1 ? fullyQualifiedName : fullyQualifiedName.substring(lastDotIndex + 1);
81
-
82
- // Match package
83
- if (!this.matchesPackage(packageName)) {
84
- return false;
85
- }
86
-
87
- // Match type (normalize primitives for matching)
88
- const normalizedTypePattern = this.normalizePrimitiveType(this.typePattern);
89
- const normalizedTypeName = this.normalizePrimitiveType(typeName);
90
- if (!this.matchesPattern(normalizedTypePattern, normalizedTypeName)) {
83
+ if (!this.matchesTargetType(method.declaringType)) {
91
84
  return false;
92
85
  }
93
86
 
@@ -97,11 +90,30 @@ export class MethodMatcher {
97
90
  }
98
91
 
99
92
  // Match arguments - convert Type[] to string representations
100
- const argStrings = method.parameterTypes.map(type => this.typeToString(type));
93
+ const argStrings = method.parameterTypes.map(type => MethodMatcher.typeName(type));
101
94
  return this.matchesArguments(argStrings);
102
95
  }
103
96
 
104
- private typeToString(type: Type): string {
97
+ private matchesTargetType(type: Type | undefined): boolean {
98
+ const matches = (name: string) => this.matchesTypeName(name);
99
+ // A call on an untyped receiver declares on the unknown type, which a wildcard pattern
100
+ // matches by name. Java's matcher has no such case because Java calls are always typed.
101
+ return type?.kind === Type.Kind.Unknown
102
+ ? matches(FullyQualified.getFullyQualifiedName(type))
103
+ : Type.isOfTypeWithName(type, this.matchOverrides, matches);
104
+ }
105
+
106
+ private matchesTypeName(fullyQualifiedName: string): boolean {
107
+ const lastDotIndex = fullyQualifiedName.lastIndexOf('.');
108
+ const packageName = lastDotIndex === -1 ? '' : fullyQualifiedName.substring(0, lastDotIndex);
109
+ const typeName = lastDotIndex === -1 ? fullyQualifiedName : fullyQualifiedName.substring(lastDotIndex + 1);
110
+
111
+ return this.matchesPackage(packageName) &&
112
+ this.matchesPattern(this.normalizePrimitiveType(this.typePattern), this.normalizePrimitiveType(typeName));
113
+ }
114
+
115
+ /** The name an argument pattern compares against a parameter of this type. */
116
+ static typeName(type: Type): string {
105
117
  switch (type.kind) {
106
118
  case Type.Kind.Primitive:
107
119
  return (type as Type.Primitive).keyword;
@@ -111,7 +123,7 @@ export class MethodMatcher {
111
123
  return FullyQualified.getFullyQualifiedName((type as Type.Parameterized).type);
112
124
  case Type.Kind.Array:
113
125
  const arrayType = type as Type.Array;
114
- return this.typeToString(arrayType.elemType) + '[]';
126
+ return MethodMatcher.typeName(arrayType.elemType) + '[]';
115
127
  case Type.Kind.GenericTypeVariable:
116
128
  return (type as Type.GenericTypeVariable).name;
117
129
  default:
@@ -0,0 +1,45 @@
1
+ /*
2
+ * Copyright 2026 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
+ /**
18
+ * The package a bare module specifier, or a path below `node_modules`, names. Attribution names a
19
+ * type declared in an installed package after it, so `@scope/pkg/sub` gives `@scope/pkg`.
20
+ */
21
+ export function packageNameOf(specifier: string): string {
22
+ const segments = specifier.split('/');
23
+ return normalizePackageName(segments[0].startsWith('@') && segments.length > 1
24
+ ? `${segments[0]}/${segments[1]}`
25
+ : segments[0]);
26
+ }
27
+
28
+ /**
29
+ * The specifier consumers import a node_modules package under. A DefinitelyTyped package
30
+ * `@types/<pkg>` names `<pkg>`, and `@types/testing-library__react` names `@testing-library/react`.
31
+ * Types reached directly and through a declaration file's path then share one name.
32
+ */
33
+ function normalizePackageName(packageName: string): string {
34
+ if (packageName.startsWith('@types/')) {
35
+ packageName = packageName.substring('@types/'.length);
36
+ // Decode __ encoding for scoped packages: testing-library__react -> @testing-library/react
37
+ if (packageName.includes('__')) {
38
+ const parts = packageName.split('__');
39
+ if (parts.length === 2) {
40
+ packageName = `@${parts[0]}/${parts[1]}`;
41
+ }
42
+ }
43
+ }
44
+ return packageName;
45
+ }
@@ -68,6 +68,8 @@ export function isVendoredOrBundled(): RecipeRef {
68
68
  * ``methodPattern`` follows the OpenRewrite method-pattern syntax:
69
69
  * ``<receiver-type> <method-name>(<args>)`` — e.g.
70
70
  * ``"*..* tostring(..)"`` or ``"java.util.Collections emptyList()"``.
71
+ * For a pattern that does not fire, ``REWRITE_JAVASCRIPT_DUMP_TYPES=1`` prints the
72
+ * declaring type each call got during the test run.
71
73
  *
72
74
  * Delegates to {@code org.openrewrite.java.search.HasMethod}.
73
75
  */
@@ -75,7 +77,7 @@ export function usesMethod(methodPattern: string, matchOverrides: boolean = fals
75
77
  return new RecipeRef(
76
78
  "org.openrewrite.java.search.HasMethod",
77
79
  {methodPattern, matchOverrides},
78
- new UsesMethod(methodPattern),
80
+ new UsesMethod(methodPattern, matchOverrides),
79
81
  );
80
82
  }
81
83
 
@@ -86,13 +88,14 @@ export function usesMethod(methodPattern: string, matchOverrides: boolean = fals
86
88
  * {@link UsesType} visitor for in-process evaluation; see
87
89
  * {@link hasSourcePath} for the introspection / lazy-evaluation pattern.
88
90
  *
89
- * Delegates to {@code org.openrewrite.java.search.HasType}.
91
+ * Delegates to {@code org.openrewrite.java.search.HasType}, which matches a type through any class
92
+ * or interface it extends and reads `checkAssignability` as `UsesType`'s `includeImplicit`.
90
93
  */
91
94
  export function usesType(fullyQualifiedTypeName: string, checkAssignability: boolean = false): RecipeRef {
92
95
  return new RecipeRef(
93
96
  "org.openrewrite.java.search.HasType",
94
97
  {fullyQualifiedTypeName, checkAssignability},
95
- new UsesType(fullyQualifiedTypeName),
98
+ new UsesType(fullyQualifiedTypeName, {assignable: true, includeImplicit: checkAssignability}),
96
99
  );
97
100
  }
98
101
 
@@ -109,7 +112,7 @@ export function findMethods(methodPattern: string, matchOverrides: boolean = fal
109
112
  return new RecipeRef(
110
113
  "org.openrewrite.java.search.FindMethods",
111
114
  {methodPattern, matchOverrides},
112
- new UsesMethod(methodPattern),
115
+ new UsesMethod(methodPattern, matchOverrides),
113
116
  );
114
117
  }
115
118
 
@@ -0,0 +1,178 @@
1
+ /*
2
+ * Copyright 2026 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
+ import {Option, Recipe} from "../../recipe";
17
+ import {TreeVisitor} from "../../visitor";
18
+ import {ExecutionContext} from "../../execution";
19
+ import {check} from "../../preconditions";
20
+ import {emptyMarkers} from "../../markers";
21
+ import {randomId} from "../../uuid";
22
+ import {emptySpace, isIdentifier, J, rightPadded, Type} from "../../java";
23
+ import {JavaScriptVisitor} from "../visitor";
24
+ import {MethodMatcher} from "../method-matcher";
25
+ import {usesMethod} from "../preconditions";
26
+ import {maybeBind, maybeUnbind} from "../binding";
27
+ import {moduleScopeBindings} from "../add-import";
28
+ import {compilationUnitOf, scopeOf} from "../scope";
29
+
30
+ /**
31
+ * Moves calls matching a method pattern onto a member imported from a module, such as `jest.fn()`
32
+ * to `vi.fn()` with `import {vi} from "vitest"`.
33
+ *
34
+ * A call through an import of the old target, whether a namespace, default or named import, moves
35
+ * too, and the import goes once nothing else uses it.
36
+ */
37
+ export class ChangeMethodTargetToImport extends Recipe {
38
+ readonly name = "org.openrewrite.javascript.change-method-target-to-import";
39
+ readonly displayName = "Change method target to an import";
40
+ readonly description = "Changes the target of calls matching a method pattern to a member imported from a module, " +
41
+ "adding the import. A CommonJS file changes only where it already requires the target, and an AMD module not at all.";
42
+
43
+ @Option({
44
+ displayName: "Method pattern",
45
+ description: "A method pattern matching the calls to move, as `<declaring type> <name>(<args>)`.",
46
+ example: "jest *(..)"
47
+ })
48
+ methodPattern!: string;
49
+
50
+ @Option({
51
+ displayName: "Target module",
52
+ description: "The module the new target is imported from.",
53
+ example: "vitest"
54
+ })
55
+ targetModule!: string;
56
+
57
+ @Option({
58
+ displayName: "Target member",
59
+ description: "The member of the target module the calls move onto.",
60
+ example: "vi"
61
+ })
62
+ targetMember!: string;
63
+
64
+ @Option({
65
+ displayName: "Target type",
66
+ description: "The fully-qualified type of the target member, which declares the methods the calls move to. " +
67
+ "Without it, the moved calls are attributed to an unknown declaring type.",
68
+ example: "vitest.VitestUtils",
69
+ required: false
70
+ })
71
+ targetType?: string;
72
+
73
+ @Option({
74
+ displayName: "Match on overrides",
75
+ description: "When enabled, find methods that are overrides of the method pattern.",
76
+ required: false
77
+ })
78
+ matchOverrides?: boolean;
79
+
80
+ constructor(options?: {
81
+ methodPattern?: string;
82
+ targetModule?: string;
83
+ targetMember?: string;
84
+ targetType?: string;
85
+ matchOverrides?: boolean;
86
+ }) {
87
+ super(options);
88
+ }
89
+
90
+ async editor(): Promise<TreeVisitor<any, ExecutionContext>> {
91
+ const matcher = new MethodMatcher(this.methodPattern, this.matchOverrides);
92
+ const module = this.targetModule;
93
+ const member = this.targetMember;
94
+ // Named after the specifier, as the parser names a module object.
95
+ const moduleObject = classNamed(module);
96
+ const memberType = this.targetType ? classNamed(this.targetType) : Type.unknownType as Type.FullyQualified;
97
+
98
+ return check(usesMethod(this.methodPattern, this.matchOverrides), new class extends JavaScriptVisitor<ExecutionContext> {
99
+ protected override async visitMethodInvocation(method: J.MethodInvocation, ctx: ExecutionContext): Promise<J | undefined> {
100
+ const m = await super.visitMethodInvocation(method, ctx) as J.MethodInvocation;
101
+ if (!matcher.matches(m.methodType)) {
102
+ return m;
103
+ }
104
+ const select = m.select?.element;
105
+ if (select !== undefined && !isIdentifier(select)) {
106
+ return m;
107
+ }
108
+ const oldName = select?.simpleName ?? m.name.simpleName;
109
+ const cu = compilationUnitOf(this)!;
110
+ const owner = scopeOf(this.cursor).declaringScope(oldName);
111
+ const imported = owner === cu ?
112
+ moduleScopeBindings(cu).find(b => b.name === oldName && b.module !== undefined) : undefined;
113
+ if (owner !== undefined && !imported) {
114
+ return m;
115
+ }
116
+ // A call without a receiver names the member it was imported as, which a default import lacks.
117
+ const methodName = select || !imported ? m.name.simpleName : imported.member;
118
+ if (methodName === undefined) {
119
+ return m;
120
+ }
121
+
122
+ const name = maybeBind(this, {module, member, onlyIfReferenced: false});
123
+ if (name === undefined) {
124
+ return m;
125
+ }
126
+ if (imported) {
127
+ maybeUnbind(this, {module: imported.module!, member: imported.member ?? 'default'});
128
+ }
129
+
130
+ const methodType = m.methodType && {...m.methodType, declaringType: memberType};
131
+ const target: J.Identifier = {
132
+ kind: J.Kind.Identifier,
133
+ id: randomId(),
134
+ prefix: select?.prefix ?? m.name.prefix,
135
+ markers: emptyMarkers,
136
+ annotations: [],
137
+ simpleName: name,
138
+ type: memberType,
139
+ fieldType: {
140
+ kind: Type.Kind.Variable,
141
+ name: member,
142
+ flags: 0,
143
+ owner: moduleObject,
144
+ type: memberType,
145
+ annotations: []
146
+ } as Type.Variable
147
+ };
148
+ const moved: J.MethodInvocation = {
149
+ ...m,
150
+ select: m.select ? {...m.select, element: target} : rightPadded(target, emptySpace),
151
+ name: {
152
+ ...m.name,
153
+ prefix: m.select ? m.name.prefix : emptySpace,
154
+ simpleName: methodName,
155
+ type: Type.isMethod(m.name.type) ? methodType : m.name.type,
156
+ fieldType: m.name.fieldType && {...m.name.fieldType, name: methodName, owner: memberType}
157
+ },
158
+ methodType
159
+ };
160
+ return moved;
161
+ }
162
+ }());
163
+ }
164
+ }
165
+
166
+ function classNamed(fullyQualifiedName: string): Type.Class {
167
+ return {
168
+ kind: Type.Kind.Class,
169
+ flags: 0,
170
+ classKind: Type.Class.Kind.Interface,
171
+ fullyQualifiedName,
172
+ typeParameters: [],
173
+ annotations: [],
174
+ interfaces: [],
175
+ members: [],
176
+ methods: []
177
+ } as Type.Class;
178
+ }
@@ -18,4 +18,5 @@ export * from "./async-callback-in-sync-array-method";
18
18
  export * from "./auto-format";
19
19
  export * from "./order-imports";
20
20
  export * from "./change-import";
21
+ export * from "./change-method-target-to-import";
21
22
  export * from "./dependencies";