@openrewrite/rewrite 8.96.5 → 8.97.0
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/java/tree.d.ts +2 -1
- package/dist/java/tree.d.ts.map +1 -1
- package/dist/java/tree.js.map +1 -1
- package/dist/java/visitor.d.ts +5 -0
- package/dist/java/visitor.d.ts.map +1 -1
- package/dist/java/visitor.js +264 -231
- package/dist/java/visitor.js.map +1 -1
- package/dist/javascript/dependency-workspace.d.ts +8 -2
- package/dist/javascript/dependency-workspace.d.ts.map +1 -1
- package/dist/javascript/dependency-workspace.js +36 -4
- package/dist/javascript/dependency-workspace.js.map +1 -1
- package/dist/javascript/templating/engine.d.ts +1 -0
- package/dist/javascript/templating/engine.d.ts.map +1 -1
- package/dist/javascript/templating/engine.js +28 -4
- package/dist/javascript/templating/engine.js.map +1 -1
- package/dist/javascript/templating/placeholder-replacement.d.ts +2 -0
- package/dist/javascript/templating/placeholder-replacement.d.ts.map +1 -1
- package/dist/javascript/templating/placeholder-replacement.js +24 -3
- package/dist/javascript/templating/placeholder-replacement.js.map +1 -1
- package/dist/javascript/visitor.d.ts.map +1 -1
- package/dist/javascript/visitor.js +135 -121
- package/dist/javascript/visitor.js.map +1 -1
- package/dist/rewrite-javascript-version.txt +1 -1
- package/package.json +1 -1
- package/src/java/tree.ts +2 -1
- package/src/java/visitor.ts +263 -230
- package/src/javascript/dependency-workspace.ts +35 -3
- package/src/javascript/templating/engine.ts +30 -3
- package/src/javascript/templating/placeholder-replacement.ts +24 -3
- package/src/javascript/visitor.ts +139 -121
|
@@ -229,6 +229,7 @@ export class DependencyWorkspace {
|
|
|
229
229
|
// Write package files
|
|
230
230
|
writePackageFiles(targetDir);
|
|
231
231
|
|
|
232
|
+
this.markUsed(cachedWorkspaceDir);
|
|
232
233
|
return targetDir;
|
|
233
234
|
} catch (symlinkError) {
|
|
234
235
|
// Symlink failed (e.g., cross-device, permissions) - fall through to npm install
|
|
@@ -257,6 +258,7 @@ export class DependencyWorkspace {
|
|
|
257
258
|
// Check cache
|
|
258
259
|
const cached = this.cache.get(hash);
|
|
259
260
|
if (cached && fs.existsSync(cached) && this.isWorkspaceValid(cached, depsForValidation)) {
|
|
261
|
+
this.markUsed(cached);
|
|
260
262
|
return cached;
|
|
261
263
|
}
|
|
262
264
|
|
|
@@ -266,6 +268,7 @@ export class DependencyWorkspace {
|
|
|
266
268
|
// Check if valid workspace already exists on disk (cross-VM reuse)
|
|
267
269
|
if (fs.existsSync(workspaceDir) && this.isWorkspaceValid(workspaceDir, depsForValidation)) {
|
|
268
270
|
this.cache.set(hash, workspaceDir);
|
|
271
|
+
this.markUsed(workspaceDir);
|
|
269
272
|
return workspaceDir;
|
|
270
273
|
}
|
|
271
274
|
|
|
@@ -390,6 +393,19 @@ export class DependencyWorkspace {
|
|
|
390
393
|
return crypto.createHash('sha256').update(content).digest('hex').substring(0, 16);
|
|
391
394
|
}
|
|
392
395
|
|
|
396
|
+
/**
|
|
397
|
+
* Marks a workspace used now, so that a cleanup ages it from its last use rather than from its install, and never
|
|
398
|
+
* deletes one a test run or a recipe has just used.
|
|
399
|
+
*/
|
|
400
|
+
private static markUsed(workspaceDir: string): void {
|
|
401
|
+
try {
|
|
402
|
+
const now = new Date();
|
|
403
|
+
fs.utimesSync(workspaceDir, now, now);
|
|
404
|
+
} catch {
|
|
405
|
+
// At worst, a cleanup deletes it once it is a day old, and it is installed again.
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
393
409
|
/**
|
|
394
410
|
* Checks if a workspace is valid (has node_modules and matching package.json).
|
|
395
411
|
* Handles both real node_modules directories and symlinks to cached workspaces.
|
|
@@ -444,6 +460,15 @@ export class DependencyWorkspace {
|
|
|
444
460
|
return false;
|
|
445
461
|
}
|
|
446
462
|
}
|
|
463
|
+
|
|
464
|
+
// And installed: a workspace whose node_modules has lost a package, as a deletion cut short leaves
|
|
465
|
+
// one, would otherwise be reused without it, and nothing it declares would be type attributed. Its
|
|
466
|
+
// devDependencies are not asked for, which npm leaves out under NODE_ENV=production.
|
|
467
|
+
for (const name of Object.keys(packageJsonContent.dependencies ?? {})) {
|
|
468
|
+
if (!fs.existsSync(path.join(nodeModules, name, 'package.json'))) {
|
|
469
|
+
return false;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
447
472
|
} catch (error) {
|
|
448
473
|
return false;
|
|
449
474
|
}
|
|
@@ -454,11 +479,12 @@ export class DependencyWorkspace {
|
|
|
454
479
|
|
|
455
480
|
/**
|
|
456
481
|
* Cleans up old workspace directories.
|
|
457
|
-
* Removes workspaces
|
|
482
|
+
* Removes workspaces unused for longer than the specified age, each moved out of its key's way before it is
|
|
483
|
+
* deleted, so that a deletion cut short leaves a temporary directory rather than part of a workspace.
|
|
458
484
|
* Also removes all temporary directories (*.tmp-*) regardless of age,
|
|
459
485
|
* as these indicate incomplete/crashed operations.
|
|
460
486
|
*
|
|
461
|
-
* @param maxAgeMs Maximum age in milliseconds (default: 24 hours)
|
|
487
|
+
* @param maxAgeMs Maximum age in milliseconds since a workspace was last used (default: 24 hours)
|
|
462
488
|
*/
|
|
463
489
|
static cleanupOldWorkspaces(maxAgeMs: number = 24 * 60 * 60 * 1000): void {
|
|
464
490
|
if (!fs.existsSync(this.WORKSPACE_BASE)) {
|
|
@@ -508,7 +534,12 @@ export class DependencyWorkspace {
|
|
|
508
534
|
const age = now - stats.mtimeMs;
|
|
509
535
|
|
|
510
536
|
if (age > maxAgeMs) {
|
|
511
|
-
|
|
537
|
+
// Out of its key's way in one rename, then deleted: a process looking the workspace up finds it
|
|
538
|
+
// whole or not at all. A deletion cut short, as when the RPC server is killed while it shuts
|
|
539
|
+
// down, then leaves a temporary directory of a process that has gone, which the next cleanup
|
|
540
|
+
// removes, rather than a workspace that has lost part of its node_modules and still looks valid.
|
|
541
|
+
const deleting = `${workspaceDir}.tmp-${process.pid}-${Date.now()}-deleting`;
|
|
542
|
+
fs.renameSync(workspaceDir, deleting);
|
|
512
543
|
// Remove from cache
|
|
513
544
|
for (const [hash, dir] of this.cache.entries()) {
|
|
514
545
|
if (dir === workspaceDir) {
|
|
@@ -516,6 +547,7 @@ export class DependencyWorkspace {
|
|
|
516
547
|
break;
|
|
517
548
|
}
|
|
518
549
|
}
|
|
550
|
+
fs.rmSync(deleting, {recursive: true, force: true});
|
|
519
551
|
}
|
|
520
552
|
} catch (error) {
|
|
521
553
|
// Ignore errors, workspace might be in use
|
|
@@ -481,9 +481,15 @@ export class TemplateEngine {
|
|
|
481
481
|
* Converts a Type instance to a TypeScript type string.
|
|
482
482
|
*
|
|
483
483
|
* @param type The Type instance
|
|
484
|
+
* @param enclosing The types that `type` is a type argument or element type of
|
|
484
485
|
* @returns A TypeScript type string
|
|
485
486
|
*/
|
|
486
|
-
private static typeToString(type: Type): string {
|
|
487
|
+
private static typeToString(type: Type, enclosing: Type[] = []): string {
|
|
488
|
+
// A recursive type, such as `type A = Array<A>`, is any where it holds itself
|
|
489
|
+
if (enclosing.includes(type)) {
|
|
490
|
+
return 'any';
|
|
491
|
+
}
|
|
492
|
+
|
|
487
493
|
// Handle Type.Class and Type.ShallowClass - return their fully qualified names
|
|
488
494
|
if (type.kind === Type.Kind.Class || type.kind === Type.Kind.ShallowClass) {
|
|
489
495
|
const classType = type as Type.Class;
|
|
@@ -518,10 +524,18 @@ export class TemplateEngine {
|
|
|
518
524
|
// Handle Type.Array - render component type plus []
|
|
519
525
|
if (type.kind === Type.Kind.Array) {
|
|
520
526
|
const arrayType = type as Type.Array;
|
|
521
|
-
const componentTypeString = this.typeToString(arrayType.elemType);
|
|
527
|
+
const componentTypeString = this.typeToString(arrayType.elemType, [...enclosing, type]);
|
|
522
528
|
return `${componentTypeString}[]`;
|
|
523
529
|
}
|
|
524
530
|
|
|
531
|
+
// An array, a map or any other generic type, with its type arguments
|
|
532
|
+
if (type.kind === Type.Kind.Parameterized) {
|
|
533
|
+
const parameterized = type as Type.Parameterized;
|
|
534
|
+
const base = this.typeToString(parameterized.type);
|
|
535
|
+
return base === 'any' || parameterized.typeParameters.length === 0 ? base :
|
|
536
|
+
`${base}<${parameterized.typeParameters.map(t => this.typeToString(t, [...enclosing, type])).join(', ')}>`;
|
|
537
|
+
}
|
|
538
|
+
|
|
525
539
|
// For other types, return 'any' as a fallback
|
|
526
540
|
// TODO: Implement proper Type to string conversion for other Type.Kind values
|
|
527
541
|
return 'any';
|
|
@@ -770,7 +784,8 @@ function expectsStatement(parentTree: J, childId: string): boolean {
|
|
|
770
784
|
parentTree.kind === J.Kind.Label ||
|
|
771
785
|
parentTree.kind === J.Kind.WhileLoop ||
|
|
772
786
|
parentTree.kind === JS.Kind.CompilationUnit ||
|
|
773
|
-
parentTree.kind === JS.Kind.ForInLoop
|
|
787
|
+
parentTree.kind === JS.Kind.ForInLoop ||
|
|
788
|
+
parentTree.kind === JS.Kind.WithStatement;
|
|
774
789
|
}
|
|
775
790
|
|
|
776
791
|
/** Whether the code opens with `{`, which is a block where a statement goes and an object literal elsewhere. */
|
|
@@ -917,6 +932,18 @@ export class TemplateApplier {
|
|
|
917
932
|
private wrapTree(originalTree: J, resultToUse: J) {
|
|
918
933
|
const parentTree = this.cursor?.parentTree()?.value;
|
|
919
934
|
|
|
935
|
+
// A `for` head holds each expression of its init and update in an expression statement, as the parser does
|
|
936
|
+
if (parentTree?.kind === J.Kind.ForLoopControl &&
|
|
937
|
+
(parentTree as J.ForLoop.Control).condition?.element.id !== originalTree.id) {
|
|
938
|
+
return isExpression(resultToUse) && resultToUse.kind !== JS.Kind.ExpressionStatement ? {
|
|
939
|
+
kind: JS.Kind.ExpressionStatement,
|
|
940
|
+
id: randomId(),
|
|
941
|
+
prefix: resultToUse.prefix,
|
|
942
|
+
markers: wrapperMarkers(resultToUse),
|
|
943
|
+
expression: {...resultToUse, prefix: emptySpace}
|
|
944
|
+
} as JS.ExpressionStatement : resultToUse;
|
|
945
|
+
}
|
|
946
|
+
|
|
920
947
|
// Only apply wrapping logic if we have parent context
|
|
921
948
|
if (parentTree) {
|
|
922
949
|
const parentExpectsStatement = expectsStatement(parentTree, originalTree.id);
|
|
@@ -53,6 +53,13 @@ function isObjectMember(value: unknown): value is J {
|
|
|
53
53
|
return false;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
/** `padded` less the trailing comma it brought from the end of the list it was taken from. */
|
|
57
|
+
function withoutTrailingComma(padded: J.RightPadded<J>): J.RightPadded<J> {
|
|
58
|
+
const markers = padded.markers?.markers;
|
|
59
|
+
return markers?.some(m => m.kind === J.Markers.TrailingComma) ?
|
|
60
|
+
{...padded, markers: {...padded.markers, markers: markers.filter(m => m.kind !== J.Markers.TrailingComma)}} : padded;
|
|
61
|
+
}
|
|
62
|
+
|
|
56
63
|
/**
|
|
57
64
|
* Visitor that replaces placeholder nodes with actual parameter values.
|
|
58
65
|
*/
|
|
@@ -331,6 +338,7 @@ export class PlaceholderReplacementVisitor extends JavaScriptVisitor<any> {
|
|
|
331
338
|
const param = this.substitutions.get(placeholderText);
|
|
332
339
|
if (param) {
|
|
333
340
|
let arrayToExpand: J[] | J.RightPadded<J>[] | undefined = undefined;
|
|
341
|
+
const member = element.kind === JS.Kind.PropertyAssignment ? this.objectMember(param.value) : undefined;
|
|
334
342
|
|
|
335
343
|
// Check if it's a J.Container
|
|
336
344
|
const isContainer = param.value && typeof param.value === 'object' &&
|
|
@@ -344,8 +352,8 @@ export class PlaceholderReplacementVisitor extends JavaScriptVisitor<any> {
|
|
|
344
352
|
arrayToExpand = param.value as J[];
|
|
345
353
|
}
|
|
346
354
|
// One member of an object literal fills the slot of a member, not the name of a property
|
|
347
|
-
else if (
|
|
348
|
-
arrayToExpand = [
|
|
355
|
+
else if (member) {
|
|
356
|
+
arrayToExpand = [member] as J[] | J.RightPadded<J>[];
|
|
349
357
|
}
|
|
350
358
|
// Check if it's a CaptureValue
|
|
351
359
|
else if (param.value instanceof CaptureValue) {
|
|
@@ -451,7 +459,20 @@ export class PlaceholderReplacementVisitor extends JavaScriptVisitor<any> {
|
|
|
451
459
|
}
|
|
452
460
|
}
|
|
453
461
|
|
|
454
|
-
|
|
462
|
+
// Only the last element prints the trailing comma that a given or captured padding brings along
|
|
463
|
+
return newElements.map((padded, i) => i === newElements.length - 1 ? padded : withoutTrailingComma(padded));
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/** The member of an object literal, padded or not, that a parameter is or is bound to, if it is one. */
|
|
467
|
+
private objectMember(value: any): J | J.RightPadded<J> | undefined {
|
|
468
|
+
if (value instanceof CaptureValue) {
|
|
469
|
+
value = value.resolve(this.values);
|
|
470
|
+
} else if (value instanceof TemplateParamImpl) {
|
|
471
|
+
value = this.values.get(value.name);
|
|
472
|
+
} else if (value instanceof CaptureImpl || (value && typeof value === 'object' && value[CAPTURE_NAME_SYMBOL])) {
|
|
473
|
+
value = this.values.get(value[CAPTURE_NAME_SYMBOL] || value.name);
|
|
474
|
+
}
|
|
475
|
+
return isObjectMember(value?.kind === J.Kind.RightPadded ? value.element : value) ? value : undefined;
|
|
455
476
|
}
|
|
456
477
|
|
|
457
478
|
/** As `visit` on a placeholder, with `merge` choosing the replacement's prefix. */
|