@openrewrite/rewrite 8.77.1 → 8.77.2
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/dist/javascript/parser.d.ts.map +1 -1
- package/dist/javascript/parser.js +57 -30
- package/dist/javascript/parser.js.map +1 -1
- package/dist/javascript/recipes/upgrade-dependency-version.d.ts +12 -26
- package/dist/javascript/recipes/upgrade-dependency-version.d.ts.map +1 -1
- package/dist/javascript/recipes/upgrade-dependency-version.js +74 -53
- package/dist/javascript/recipes/upgrade-dependency-version.js.map +1 -1
- package/dist/javascript/type-mapping.d.ts +2 -1
- package/dist/javascript/type-mapping.d.ts.map +1 -1
- package/dist/javascript/type-mapping.js +68 -5
- package/dist/javascript/type-mapping.js.map +1 -1
- package/dist/rewrite-javascript-version.txt +1 -1
- package/package.json +1 -1
- package/src/javascript/parser.ts +54 -31
- package/src/javascript/recipes/upgrade-dependency-version.ts +92 -85
- package/src/javascript/type-mapping.ts +34 -5
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
} from "../node-resolution-result";
|
|
31
31
|
import * as path from "path";
|
|
32
32
|
import * as semver from "semver";
|
|
33
|
+
import * as picomatch from "picomatch";
|
|
33
34
|
import {markupWarn, replaceMarkerByKind} from "../../markers";
|
|
34
35
|
import {TreePrinters} from "../../print";
|
|
35
36
|
import {
|
|
@@ -45,28 +46,19 @@ import {
|
|
|
45
46
|
updateNodeResolutionMarker
|
|
46
47
|
} from "../package-manager";
|
|
47
48
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
interface MatchedDependency {
|
|
50
|
+
packageName: string;
|
|
51
|
+
dependencyScope: DependencyScope;
|
|
52
|
+
currentVersion: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
51
55
|
interface ProjectUpdateInfo {
|
|
52
|
-
/** Relative path to package.json (from source root) */
|
|
53
56
|
packageJsonPath: string;
|
|
54
|
-
/** Original package.json content */
|
|
55
57
|
originalPackageJson: string;
|
|
56
|
-
|
|
57
|
-
dependencyScope: DependencyScope;
|
|
58
|
-
/** Current version constraint */
|
|
59
|
-
currentVersion: string;
|
|
60
|
-
/** New version constraint to apply */
|
|
58
|
+
matchedDependencies: MatchedDependency[];
|
|
61
59
|
newVersion: string;
|
|
62
|
-
/** The package manager used by this project */
|
|
63
60
|
packageManager: PackageManager;
|
|
64
|
-
/**
|
|
65
|
-
* If true, skip running the package manager because the resolved version
|
|
66
|
-
* already satisfies the new constraint. Only package.json needs updating.
|
|
67
|
-
*/
|
|
68
61
|
skipInstall: boolean;
|
|
69
|
-
/** Config file contents extracted from the project (e.g., .npmrc) */
|
|
70
62
|
configFiles?: Record<string, string>;
|
|
71
63
|
}
|
|
72
64
|
|
|
@@ -92,14 +84,23 @@ interface Accumulator extends DependencyRecipeAccumulator<ProjectUpdateInfo> {
|
|
|
92
84
|
export class UpgradeDependencyVersion extends ScanningRecipe<Accumulator> {
|
|
93
85
|
readonly name = "org.openrewrite.javascript.dependencies.upgrade-dependency-version";
|
|
94
86
|
readonly displayName = "Upgrade npm dependency version";
|
|
95
|
-
readonly description = "Upgrades the version of a direct dependency in `package.json` and updates the lock file by running the package manager.";
|
|
87
|
+
readonly description = "Upgrades the version of a direct dependency in `package.json` and updates the lock file by running the package manager. Either `packageName` or `packagePattern` must be specified.";
|
|
96
88
|
|
|
97
89
|
@Option({
|
|
98
90
|
displayName: "Package name",
|
|
99
|
-
description: "The name of the npm package to upgrade (e.g., `lodash`, `@types/node`)",
|
|
91
|
+
description: "The exact name of the npm package to upgrade (e.g., `lodash`, `@types/node`). Either this or `packagePattern` must be specified.",
|
|
92
|
+
required: false,
|
|
100
93
|
example: "lodash"
|
|
101
94
|
})
|
|
102
|
-
packageName
|
|
95
|
+
packageName?: string;
|
|
96
|
+
|
|
97
|
+
@Option({
|
|
98
|
+
displayName: "Package pattern",
|
|
99
|
+
description: "A glob expression to match package names (e.g., `@angular/*`, `@types/*`). Either this or `packageName` must be specified.",
|
|
100
|
+
required: false,
|
|
101
|
+
example: "@angular/*"
|
|
102
|
+
})
|
|
103
|
+
packagePattern?: string;
|
|
103
104
|
|
|
104
105
|
@Option({
|
|
105
106
|
displayName: "Version",
|
|
@@ -108,7 +109,31 @@ export class UpgradeDependencyVersion extends ScanningRecipe<Accumulator> {
|
|
|
108
109
|
})
|
|
109
110
|
newVersion!: string;
|
|
110
111
|
|
|
112
|
+
private _matcher?: picomatch.Matcher;
|
|
113
|
+
|
|
114
|
+
private get matcher(): picomatch.Matcher | undefined {
|
|
115
|
+
if (this.packagePattern && !this._matcher) {
|
|
116
|
+
this._matcher = picomatch.default
|
|
117
|
+
? picomatch.default(this.packagePattern)
|
|
118
|
+
: (picomatch as any)(this.packagePattern);
|
|
119
|
+
}
|
|
120
|
+
return this._matcher;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
matchesPackage(name: string): boolean {
|
|
124
|
+
if (this.packageName && name === this.packageName) {
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
if (this.matcher) {
|
|
128
|
+
return this.matcher(name);
|
|
129
|
+
}
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
|
|
111
133
|
initialValue(_ctx: ExecutionContext): Accumulator {
|
|
134
|
+
if (!this.packageName && !this.packagePattern) {
|
|
135
|
+
throw new Error("Either packageName or packagePattern must be specified");
|
|
136
|
+
}
|
|
112
137
|
return {
|
|
113
138
|
...createDependencyRecipeAccumulator<ProjectUpdateInfo>(),
|
|
114
139
|
originalLockFiles: new Map()
|
|
@@ -193,40 +218,30 @@ export class UpgradeDependencyVersion extends ScanningRecipe<Accumulator> {
|
|
|
193
218
|
|
|
194
219
|
const pm = marker.packageManager ?? PackageManager.Npm;
|
|
195
220
|
|
|
196
|
-
|
|
197
|
-
const
|
|
198
|
-
let foundScope: DependencyScope | undefined;
|
|
199
|
-
let currentVersion: string | undefined;
|
|
200
|
-
|
|
201
|
-
for (const scope of scopes) {
|
|
221
|
+
const matchedDeps: MatchedDependency[] = [];
|
|
222
|
+
for (const scope of allDependencyScopes) {
|
|
202
223
|
const deps = marker[scope];
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
224
|
+
if (!deps) continue;
|
|
225
|
+
for (const dep of deps) {
|
|
226
|
+
if (recipe.matchesPackage(dep.name) && recipe.shouldUpgrade(dep.versionConstraint, recipe.newVersion)) {
|
|
227
|
+
matchedDeps.push({
|
|
228
|
+
packageName: dep.name,
|
|
229
|
+
dependencyScope: scope,
|
|
230
|
+
currentVersion: dep.versionConstraint
|
|
231
|
+
});
|
|
232
|
+
}
|
|
209
233
|
}
|
|
210
234
|
}
|
|
211
235
|
|
|
212
|
-
if (
|
|
213
|
-
return doc;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Check if upgrade is needed
|
|
217
|
-
if (!recipe.shouldUpgrade(currentVersion, recipe.newVersion)) {
|
|
218
|
-
return doc; // Already at target version or newer
|
|
236
|
+
if (matchedDeps.length === 0) {
|
|
237
|
+
return doc;
|
|
219
238
|
}
|
|
220
239
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
);
|
|
226
|
-
const skipInstall = resolvedDep !== undefined &&
|
|
227
|
-
semver.satisfies(resolvedDep.version, recipe.newVersion);
|
|
240
|
+
const skipInstall = matchedDeps.every(md => {
|
|
241
|
+
const resolvedDep = marker.resolvedDependencies?.find(rd => rd.name === md.packageName);
|
|
242
|
+
return resolvedDep !== undefined && semver.satisfies(resolvedDep.version, recipe.newVersion);
|
|
243
|
+
});
|
|
228
244
|
|
|
229
|
-
// Serialize npmrc configs from marker using requested scopes
|
|
230
245
|
const configFiles: Record<string, string> = {};
|
|
231
246
|
const npmrcContent = serializeNpmrcConfigs(marker.npmrcConfigs);
|
|
232
247
|
if (npmrcContent) {
|
|
@@ -236,8 +251,7 @@ export class UpgradeDependencyVersion extends ScanningRecipe<Accumulator> {
|
|
|
236
251
|
acc.projectsToUpdate.set(doc.sourcePath, {
|
|
237
252
|
packageJsonPath: doc.sourcePath,
|
|
238
253
|
originalPackageJson: await TreePrinters.print(doc),
|
|
239
|
-
|
|
240
|
-
currentVersion,
|
|
254
|
+
matchedDependencies: matchedDeps,
|
|
241
255
|
newVersion: recipe.newVersion,
|
|
242
256
|
packageManager: pm,
|
|
243
257
|
skipInstall,
|
|
@@ -280,32 +294,31 @@ export class UpgradeDependencyVersion extends ScanningRecipe<Accumulator> {
|
|
|
280
294
|
return doc; // This package.json doesn't need updating
|
|
281
295
|
}
|
|
282
296
|
|
|
283
|
-
// Run package manager install if needed, check for failure
|
|
284
|
-
// Skip if the resolved version already satisfies the new constraint
|
|
285
297
|
const failureMessage = updateInfo.skipInstall
|
|
286
298
|
? undefined
|
|
287
299
|
: await runInstallIfNeeded(sourcePath, acc, () =>
|
|
288
300
|
recipe.runPackageManagerInstall(acc, updateInfo, ctx)
|
|
289
301
|
);
|
|
290
302
|
if (failureMessage) {
|
|
303
|
+
const names = updateInfo.matchedDependencies.map(d => d.packageName).join(', ');
|
|
291
304
|
return markupWarn(
|
|
292
305
|
doc,
|
|
293
|
-
`Failed to upgrade ${
|
|
306
|
+
`Failed to upgrade ${names} to ${recipe.newVersion}`,
|
|
294
307
|
failureMessage
|
|
295
308
|
);
|
|
296
309
|
}
|
|
297
310
|
|
|
298
|
-
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
311
|
+
let modifiedDoc = doc;
|
|
312
|
+
for (const md of updateInfo.matchedDependencies) {
|
|
313
|
+
const visitor = new UpdateVersionVisitor(
|
|
314
|
+
md.packageName,
|
|
315
|
+
updateInfo.newVersion,
|
|
316
|
+
md.dependencyScope
|
|
317
|
+
);
|
|
318
|
+
modifiedDoc = await visitor.visit(modifiedDoc, undefined) as Json.Document;
|
|
319
|
+
}
|
|
305
320
|
|
|
306
|
-
// Update the NodeResolutionResult marker
|
|
307
321
|
if (updateInfo.skipInstall) {
|
|
308
|
-
// Just update the versionConstraint in the marker - resolved version is unchanged
|
|
309
322
|
return recipe.updateMarkerVersionConstraint(modifiedDoc, updateInfo);
|
|
310
323
|
}
|
|
311
324
|
return updateNodeResolutionMarker(modifiedDoc, updateInfo, acc);
|
|
@@ -344,10 +357,9 @@ export class UpgradeDependencyVersion extends ScanningRecipe<Accumulator> {
|
|
|
344
357
|
updateInfo: ProjectUpdateInfo,
|
|
345
358
|
_ctx: ExecutionContext
|
|
346
359
|
): Promise<void> {
|
|
347
|
-
// Create modified package.json with the new version constraint
|
|
348
360
|
const modifiedPackageJson = this.createModifiedPackageJson(
|
|
349
361
|
updateInfo.originalPackageJson,
|
|
350
|
-
updateInfo.
|
|
362
|
+
updateInfo.matchedDependencies,
|
|
351
363
|
updateInfo.newVersion
|
|
352
364
|
);
|
|
353
365
|
|
|
@@ -373,28 +385,22 @@ export class UpgradeDependencyVersion extends ScanningRecipe<Accumulator> {
|
|
|
373
385
|
storeInstallResult(result, acc, updateInfo, modifiedPackageJson);
|
|
374
386
|
}
|
|
375
387
|
|
|
376
|
-
/**
|
|
377
|
-
* Creates a modified package.json with the updated dependency version.
|
|
378
|
-
* Used for the temp directory to validate the version exists.
|
|
379
|
-
*/
|
|
380
388
|
private createModifiedPackageJson(
|
|
381
389
|
originalContent: string,
|
|
382
|
-
|
|
390
|
+
matchedDependencies: MatchedDependency[],
|
|
383
391
|
newVersion: string
|
|
384
392
|
): string {
|
|
385
393
|
const packageJson = JSON.parse(originalContent);
|
|
386
394
|
|
|
387
|
-
|
|
388
|
-
packageJson[
|
|
395
|
+
for (const md of matchedDependencies) {
|
|
396
|
+
if (packageJson[md.dependencyScope]?.[md.packageName]) {
|
|
397
|
+
packageJson[md.dependencyScope][md.packageName] = newVersion;
|
|
398
|
+
}
|
|
389
399
|
}
|
|
390
400
|
|
|
391
401
|
return JSON.stringify(packageJson, null, 2);
|
|
392
402
|
}
|
|
393
403
|
|
|
394
|
-
/**
|
|
395
|
-
* Updates just the versionConstraint in the marker for the target dependency.
|
|
396
|
-
* Used when skipInstall is true - the resolved version is unchanged.
|
|
397
|
-
*/
|
|
398
404
|
private updateMarkerVersionConstraint(
|
|
399
405
|
doc: Json.Document,
|
|
400
406
|
updateInfo: ProjectUpdateInfo
|
|
@@ -404,18 +410,19 @@ export class UpgradeDependencyVersion extends ScanningRecipe<Accumulator> {
|
|
|
404
410
|
return doc;
|
|
405
411
|
}
|
|
406
412
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
const
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
413
|
+
const matchedNames = new Set(updateInfo.matchedDependencies.map(md => md.packageName));
|
|
414
|
+
let newMarker = {...existingMarker};
|
|
415
|
+
for (const md of updateInfo.matchedDependencies) {
|
|
416
|
+
const deps = newMarker[md.dependencyScope];
|
|
417
|
+
newMarker = {
|
|
418
|
+
...newMarker,
|
|
419
|
+
[md.dependencyScope]: deps?.map(dep =>
|
|
420
|
+
matchedNames.has(dep.name)
|
|
421
|
+
? {...dep, versionConstraint: updateInfo.newVersion}
|
|
422
|
+
: dep
|
|
423
|
+
)
|
|
424
|
+
};
|
|
425
|
+
}
|
|
419
426
|
|
|
420
427
|
return {
|
|
421
428
|
...doc,
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
import ts from "typescript";
|
|
17
|
+
import * as path from "path";
|
|
17
18
|
import {Type} from "../java";
|
|
18
19
|
import FUNCTION_TYPE_NAME = Type.FUNCTION_TYPE_NAME;
|
|
19
20
|
|
|
@@ -30,7 +31,8 @@ export class JavaScriptTypeMapping {
|
|
|
30
31
|
|
|
31
32
|
|
|
32
33
|
constructor(
|
|
33
|
-
private readonly checker: ts.TypeChecker
|
|
34
|
+
private readonly checker: ts.TypeChecker,
|
|
35
|
+
private readonly sourceRoot?: string
|
|
34
36
|
) {
|
|
35
37
|
this.regExpSymbol = checker.resolveName(
|
|
36
38
|
"RegExp",
|
|
@@ -69,11 +71,14 @@ export class JavaScriptTypeMapping {
|
|
|
69
71
|
// Fall through to regular type checking if not a variable
|
|
70
72
|
}
|
|
71
73
|
|
|
74
|
+
// TypeNode needs getTypeFromTypeNode (resolves the annotation itself).
|
|
75
|
+
// Everything else — expressions, declarations, specifiers, bindings, etc. —
|
|
76
|
+
// uses getTypeAtLocation which works for virtually all node kinds.
|
|
72
77
|
let type: ts.Type | undefined;
|
|
73
|
-
if (ts.
|
|
74
|
-
type = this.checker.getTypeAtLocation(node);
|
|
75
|
-
} else if (ts.isTypeNode(node)) {
|
|
78
|
+
if (ts.isTypeNode(node)) {
|
|
76
79
|
type = this.checker.getTypeFromTypeNode(node);
|
|
80
|
+
} else {
|
|
81
|
+
type = this.checker.getTypeAtLocation(node);
|
|
77
82
|
}
|
|
78
83
|
return type && this.getType(type);
|
|
79
84
|
}
|
|
@@ -565,7 +570,7 @@ export class JavaScriptTypeMapping {
|
|
|
565
570
|
*/
|
|
566
571
|
private createMethodType(
|
|
567
572
|
signature: ts.Signature,
|
|
568
|
-
node: ts.MethodSignature | ts.FunctionDeclaration | ts.MethodDeclaration | ts.FunctionExpression | ts.CallExpression | ts.NewExpression,
|
|
573
|
+
node: ts.MethodSignature | ts.FunctionDeclaration | ts.MethodDeclaration | ts.ConstructorDeclaration | ts.FunctionExpression | ts.CallExpression | ts.NewExpression,
|
|
569
574
|
declaringType: Type.FullyQualified,
|
|
570
575
|
name: string,
|
|
571
576
|
declaredFormalTypeNames: string[] = []
|
|
@@ -915,6 +920,22 @@ export class JavaScriptTypeMapping {
|
|
|
915
920
|
declaredFormalTypeNames.push(tp.name.getText());
|
|
916
921
|
}
|
|
917
922
|
}
|
|
923
|
+
} else if (ts.isConstructorDeclaration(node)) {
|
|
924
|
+
// For constructor declarations
|
|
925
|
+
signature = this.checker.getSignatureFromDeclaration(node);
|
|
926
|
+
if (!signature) {
|
|
927
|
+
return undefined;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
methodName = "<constructor>";
|
|
931
|
+
|
|
932
|
+
const parent = node.parent;
|
|
933
|
+
if (ts.isClassDeclaration(parent) || ts.isClassExpression(parent)) {
|
|
934
|
+
const parentType = this.checker.getTypeAtLocation(parent);
|
|
935
|
+
declaringType = this.getType(parentType) as Type.FullyQualified;
|
|
936
|
+
} else {
|
|
937
|
+
declaringType = Type.unknownType as Type.FullyQualified;
|
|
938
|
+
}
|
|
918
939
|
} else if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node)) {
|
|
919
940
|
// For function declarations/expressions
|
|
920
941
|
signature = this.checker.getSignatureFromDeclaration(node);
|
|
@@ -1032,6 +1053,14 @@ export class JavaScriptTypeMapping {
|
|
|
1032
1053
|
cleanedName = packageName;
|
|
1033
1054
|
}
|
|
1034
1055
|
}
|
|
1056
|
+
} else if (path.isAbsolute(cleanedName)) {
|
|
1057
|
+
// TypeScript returns absolute file paths as module names for project source files
|
|
1058
|
+
// that are ES modules (have imports/exports). Relativize using sourceRoot.
|
|
1059
|
+
// Example: /var/moderne/.../BookStack/resources/js/foo.MyClass
|
|
1060
|
+
// Should become: resources/js/foo.MyClass
|
|
1061
|
+
if (this.sourceRoot) {
|
|
1062
|
+
cleanedName = path.relative(this.sourceRoot, cleanedName);
|
|
1063
|
+
}
|
|
1035
1064
|
}
|
|
1036
1065
|
|
|
1037
1066
|
return cleanedName.endsWith('Constructor') ?
|