@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.
- package/README.md +48 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/java/formatting-utils.d.ts +2 -1
- package/dist/java/formatting-utils.d.ts.map +1 -1
- package/dist/java/formatting-utils.js +4 -3
- package/dist/java/formatting-utils.js.map +1 -1
- package/dist/java/rpc.d.ts.map +1 -1
- package/dist/java/rpc.js +2 -0
- package/dist/java/rpc.js.map +1 -1
- package/dist/java/tree.d.ts +8 -0
- package/dist/java/tree.d.ts.map +1 -1
- package/dist/java/tree.js +11 -1
- package/dist/java/tree.js.map +1 -1
- package/dist/java/type.d.ts +7 -1
- package/dist/java/type.d.ts.map +1 -1
- package/dist/java/type.js +16 -1
- package/dist/java/type.js.map +1 -1
- package/dist/javascript/add-import.d.ts +27 -21
- package/dist/javascript/add-import.d.ts.map +1 -1
- package/dist/javascript/add-import.js +303 -143
- package/dist/javascript/add-import.js.map +1 -1
- package/dist/javascript/amd.d.ts.map +1 -1
- package/dist/javascript/amd.js +25 -10
- package/dist/javascript/amd.js.map +1 -1
- package/dist/javascript/autodetect.js +36 -45
- package/dist/javascript/autodetect.js.map +1 -1
- package/dist/javascript/binding.d.ts +6 -4
- package/dist/javascript/binding.d.ts.map +1 -1
- package/dist/javascript/binding.js +54 -18
- package/dist/javascript/binding.js.map +1 -1
- package/dist/javascript/comparator.d.ts +22 -5
- package/dist/javascript/comparator.d.ts.map +1 -1
- package/dist/javascript/comparator.js +116 -38
- package/dist/javascript/comparator.js.map +1 -1
- package/dist/javascript/format/format.d.ts +2 -0
- package/dist/javascript/format/format.d.ts.map +1 -1
- package/dist/javascript/format/format.js +11 -1
- package/dist/javascript/format/format.js.map +1 -1
- package/dist/javascript/index.d.ts +4 -2
- package/dist/javascript/index.d.ts.map +1 -1
- package/dist/javascript/index.js +10 -1
- package/dist/javascript/index.js.map +1 -1
- package/dist/javascript/markers.d.ts.map +1 -1
- package/dist/javascript/markers.js +64 -0
- package/dist/javascript/markers.js.map +1 -1
- package/dist/javascript/method-matcher.d.ts +13 -2
- package/dist/javascript/method-matcher.d.ts.map +1 -1
- package/dist/javascript/method-matcher.js +32 -19
- package/dist/javascript/method-matcher.js.map +1 -1
- package/dist/javascript/package-name.d.ts +6 -0
- package/dist/javascript/package-name.d.ts.map +1 -0
- package/dist/javascript/package-name.js +47 -0
- package/dist/javascript/package-name.js.map +1 -0
- package/dist/javascript/preconditions.d.ts +4 -1
- package/dist/javascript/preconditions.d.ts.map +1 -1
- package/dist/javascript/preconditions.js +7 -4
- package/dist/javascript/preconditions.js.map +1 -1
- package/dist/javascript/recipes/change-method-target-to-import.d.ts +29 -0
- package/dist/javascript/recipes/change-method-target-to-import.d.ts.map +1 -0
- package/dist/javascript/recipes/change-method-target-to-import.js +164 -0
- package/dist/javascript/recipes/change-method-target-to-import.js.map +1 -0
- package/dist/javascript/recipes/index.d.ts +1 -0
- package/dist/javascript/recipes/index.d.ts.map +1 -1
- package/dist/javascript/recipes/index.js +1 -0
- package/dist/javascript/recipes/index.js.map +1 -1
- package/dist/javascript/scope.d.ts +60 -16
- package/dist/javascript/scope.d.ts.map +1 -1
- package/dist/javascript/scope.js +596 -131
- package/dist/javascript/scope.js.map +1 -1
- package/dist/javascript/search/uses-method.d.ts +1 -1
- package/dist/javascript/search/uses-method.d.ts.map +1 -1
- package/dist/javascript/search/uses-method.js +2 -2
- package/dist/javascript/search/uses-method.js.map +1 -1
- package/dist/javascript/search/uses-type.d.ts +14 -1
- package/dist/javascript/search/uses-type.d.ts.map +1 -1
- package/dist/javascript/search/uses-type.js +24 -9
- package/dist/javascript/search/uses-type.js.map +1 -1
- package/dist/javascript/templating/bindings.d.ts +1 -1
- package/dist/javascript/templating/bindings.d.ts.map +1 -1
- package/dist/javascript/templating/bindings.js +10 -50
- package/dist/javascript/templating/bindings.js.map +1 -1
- package/dist/javascript/templating/comparator.d.ts +8 -11
- package/dist/javascript/templating/comparator.d.ts.map +1 -1
- package/dist/javascript/templating/comparator.js +12 -110
- package/dist/javascript/templating/comparator.js.map +1 -1
- package/dist/javascript/templating/engine.d.ts +34 -24
- package/dist/javascript/templating/engine.d.ts.map +1 -1
- package/dist/javascript/templating/engine.js +200 -112
- package/dist/javascript/templating/engine.js.map +1 -1
- package/dist/javascript/templating/pattern.d.ts +11 -5
- package/dist/javascript/templating/pattern.d.ts.map +1 -1
- package/dist/javascript/templating/pattern.js +86 -20
- package/dist/javascript/templating/pattern.js.map +1 -1
- package/dist/javascript/templating/placeholder-replacement.d.ts +34 -3
- package/dist/javascript/templating/placeholder-replacement.d.ts.map +1 -1
- package/dist/javascript/templating/placeholder-replacement.js +126 -60
- package/dist/javascript/templating/placeholder-replacement.js.map +1 -1
- package/dist/javascript/templating/precedence.d.ts +3 -1
- package/dist/javascript/templating/precedence.d.ts.map +1 -1
- package/dist/javascript/templating/precedence.js +28 -8
- package/dist/javascript/templating/precedence.js.map +1 -1
- package/dist/javascript/templating/template.d.ts +7 -6
- package/dist/javascript/templating/template.d.ts.map +1 -1
- package/dist/javascript/templating/template.js +31 -28
- package/dist/javascript/templating/template.js.map +1 -1
- package/dist/javascript/templating/types.d.ts +6 -3
- package/dist/javascript/templating/types.d.ts.map +1 -1
- package/dist/javascript/templating/utils.d.ts +10 -1
- package/dist/javascript/templating/utils.d.ts.map +1 -1
- package/dist/javascript/templating/utils.js +22 -3
- package/dist/javascript/templating/utils.js.map +1 -1
- package/dist/javascript/tree-debug.d.ts +2 -0
- package/dist/javascript/tree-debug.d.ts.map +1 -1
- package/dist/javascript/tree-debug.js +6 -0
- package/dist/javascript/tree-debug.js.map +1 -1
- package/dist/javascript/tsconfig.d.ts.map +1 -1
- package/dist/javascript/tsconfig.js +7 -17
- package/dist/javascript/tsconfig.js.map +1 -1
- package/dist/javascript/type-mapping.d.ts +34 -16
- package/dist/javascript/type-mapping.d.ts.map +1 -1
- package/dist/javascript/type-mapping.js +253 -249
- package/dist/javascript/type-mapping.js.map +1 -1
- package/dist/javascript/type-report-cli.d.ts +7 -0
- package/dist/javascript/type-report-cli.d.ts.map +1 -0
- package/dist/javascript/type-report-cli.js +129 -0
- package/dist/javascript/type-report-cli.js.map +1 -0
- package/dist/javascript/type-report.d.ts +61 -0
- package/dist/javascript/type-report.d.ts.map +1 -0
- package/dist/javascript/type-report.js +488 -0
- package/dist/javascript/type-report.js.map +1 -0
- package/dist/markers.d.ts +4 -0
- package/dist/markers.d.ts.map +1 -1
- package/dist/markers.js +8 -0
- package/dist/markers.js.map +1 -1
- package/dist/recipe.d.ts +5 -0
- package/dist/recipe.d.ts.map +1 -1
- package/dist/recipe.js +5 -0
- package/dist/recipe.js.map +1 -1
- package/dist/rewrite-javascript-version.txt +1 -1
- package/dist/rpc/queue.d.ts +16 -0
- package/dist/rpc/queue.d.ts.map +1 -1
- package/dist/rpc/queue.js +27 -9
- package/dist/rpc/queue.js.map +1 -1
- package/dist/rpc/request/prepare-recipe.d.ts +3 -1
- package/dist/rpc/request/prepare-recipe.d.ts.map +1 -1
- package/dist/rpc/request/prepare-recipe.js +13 -6
- package/dist/rpc/request/prepare-recipe.js.map +1 -1
- package/dist/search/is-source-file.d.ts +1 -0
- package/dist/search/is-source-file.d.ts.map +1 -1
- package/dist/search/is-source-file.js +7 -2
- package/dist/search/is-source-file.js.map +1 -1
- package/dist/test/rewrite-test.d.ts +5 -0
- package/dist/test/rewrite-test.d.ts.map +1 -1
- package/dist/test/rewrite-test.js +14 -3
- package/dist/test/rewrite-test.js.map +1 -1
- package/package.json +4 -3
- package/src/index.ts +3 -1
- package/src/java/formatting-utils.ts +5 -4
- package/src/java/rpc.ts +2 -0
- package/src/java/tree.ts +15 -0
- package/src/java/type.ts +19 -2
- package/src/javascript/add-import.ts +338 -152
- package/src/javascript/amd.ts +28 -12
- package/src/javascript/autodetect.ts +32 -41
- package/src/javascript/binding.ts +64 -22
- package/src/javascript/comparator.ts +119 -43
- package/src/javascript/format/format.ts +9 -0
- package/src/javascript/index.ts +6 -3
- package/src/javascript/markers.ts +43 -1
- package/src/javascript/method-matcher.ts +34 -22
- package/src/javascript/package-name.ts +45 -0
- package/src/javascript/preconditions.ts +7 -4
- package/src/javascript/recipes/change-method-target-to-import.ts +178 -0
- package/src/javascript/recipes/index.ts +1 -0
- package/src/javascript/scope.ts +705 -169
- package/src/javascript/search/uses-method.ts +2 -2
- package/src/javascript/search/uses-type.ts +30 -9
- package/src/javascript/templating/bindings.ts +13 -38
- package/src/javascript/templating/comparator.ts +16 -127
- package/src/javascript/templating/engine.ts +229 -127
- package/src/javascript/templating/pattern.ts +107 -28
- package/src/javascript/templating/placeholder-replacement.ts +154 -62
- package/src/javascript/templating/precedence.ts +33 -11
- package/src/javascript/templating/template.ts +35 -30
- package/src/javascript/templating/types.ts +6 -3
- package/src/javascript/templating/utils.ts +24 -2
- package/src/javascript/tree-debug.ts +9 -0
- package/src/javascript/tsconfig.ts +7 -17
- package/src/javascript/type-mapping.ts +258 -252
- package/src/javascript/type-report-cli.ts +96 -0
- package/src/javascript/type-report.ts +575 -0
- package/src/markers.ts +8 -0
- package/src/recipe.ts +6 -0
- package/src/rpc/queue.ts +44 -5
- package/src/rpc/request/prepare-recipe.ts +16 -6
- package/src/search/is-source-file.ts +8 -3
- 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
|
-
|
|
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
|
-
|
|
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 =>
|
|
93
|
+
const argStrings = method.parameterTypes.map(type => MethodMatcher.typeName(type));
|
|
101
94
|
return this.matchesArguments(argStrings);
|
|
102
95
|
}
|
|
103
96
|
|
|
104
|
-
private
|
|
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
|
|
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
|
+
}
|