@openrewrite/rewrite 8.92.3 → 8.92.5
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/rpc.d.ts.map +1 -1
- package/dist/java/rpc.js +3 -1
- package/dist/java/rpc.js.map +1 -1
- package/dist/rewrite-javascript-version.txt +1 -1
- package/dist/rpc/index.d.ts.map +1 -1
- package/dist/rpc/index.js +6 -4
- package/dist/rpc/index.js.map +1 -1
- package/dist/rpc/queue.d.ts +26 -3
- package/dist/rpc/queue.d.ts.map +1 -1
- package/dist/rpc/queue.js +67 -19
- package/dist/rpc/queue.js.map +1 -1
- package/dist/rpc/rewrite-rpc.d.ts +1 -0
- package/dist/rpc/rewrite-rpc.d.ts.map +1 -1
- package/dist/rpc/rewrite-rpc.js +6 -1
- package/dist/rpc/rewrite-rpc.js.map +1 -1
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +41 -2
- package/dist/util.js.map +1 -1
- package/package.json +1 -1
- package/src/java/rpc.ts +4 -2
- package/src/rpc/index.ts +7 -4
- package/src/rpc/queue.ts +71 -20
- package/src/rpc/rewrite-rpc.ts +8 -2
- package/src/util.ts +52 -2
package/src/rpc/queue.ts
CHANGED
|
@@ -19,6 +19,8 @@ import {saveTrace, trace} from "./trace";
|
|
|
19
19
|
import {updateIfChanged} from "../util";
|
|
20
20
|
import {isRef, ReferenceMap} from "../reference";
|
|
21
21
|
|
|
22
|
+
const EMPTY_LIST: readonly never[] = Object.freeze([]);
|
|
23
|
+
|
|
22
24
|
/**
|
|
23
25
|
* Interface representing an RPC codec that defines methods
|
|
24
26
|
* for sending and receiving objects in an RPC communication.
|
|
@@ -316,18 +318,71 @@ export class RpcSendQueue {
|
|
|
316
318
|
}
|
|
317
319
|
}
|
|
318
320
|
|
|
321
|
+
/**
|
|
322
|
+
* Collapses repeated strings decoded from RPC messages to a single instance. Shared across every
|
|
323
|
+
* {@link RpcReceiveQueue} of a connection so the same discriminators, enum values and whitespace
|
|
324
|
+
* are deduplicated across the whole run rather than only within one received object.
|
|
325
|
+
*/
|
|
326
|
+
export class StringInternTable {
|
|
327
|
+
private readonly strings = new Map<string, string>();
|
|
328
|
+
|
|
329
|
+
constructor(private readonly maxEntries = 1 << 16,
|
|
330
|
+
private readonly maxValueLength = 200) {
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Interns a `kind`/`valueType` discriminator. This set is bounded by the number of LST node
|
|
335
|
+
* types, so it is always interned and never subject to the cap.
|
|
336
|
+
*/
|
|
337
|
+
internType(value: string): string {
|
|
338
|
+
const existing = this.strings.get(value);
|
|
339
|
+
if (existing !== undefined) {
|
|
340
|
+
return existing;
|
|
341
|
+
}
|
|
342
|
+
this.strings.set(value, value);
|
|
343
|
+
return value;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Interns a scalar string value, which is unbounded in principle. Only short strings are
|
|
348
|
+
* interned (whitespace and short tokens dominate the duplication), and the table stops growing
|
|
349
|
+
* at a cap so it can never turn into a leak; past either limit the original is returned as-is.
|
|
350
|
+
*/
|
|
351
|
+
internValue(value: string): string {
|
|
352
|
+
if (value.length > this.maxValueLength) {
|
|
353
|
+
return value;
|
|
354
|
+
}
|
|
355
|
+
const existing = this.strings.get(value);
|
|
356
|
+
if (existing !== undefined) {
|
|
357
|
+
return existing;
|
|
358
|
+
}
|
|
359
|
+
if (this.strings.size >= this.maxEntries) {
|
|
360
|
+
return value;
|
|
361
|
+
}
|
|
362
|
+
this.strings.set(value, value);
|
|
363
|
+
return value;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
clear(): void {
|
|
367
|
+
this.strings.clear();
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
get size(): number {
|
|
371
|
+
return this.strings.size;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
319
375
|
export class RpcReceiveQueue {
|
|
320
376
|
private batch: RpcObjectData[] = [];
|
|
321
377
|
private batchIndex = 0;
|
|
322
378
|
private sinceYield = 0;
|
|
323
379
|
|
|
324
|
-
private readonly internedStrings = new Map<string, string>();
|
|
325
|
-
|
|
326
380
|
constructor(private readonly refs: Map<number, any>,
|
|
327
381
|
private readonly sourceFileType: string | undefined,
|
|
328
382
|
private readonly pull: () => Promise<RpcObjectData[]>,
|
|
329
383
|
private readonly logger: rpc.Logger | undefined,
|
|
330
|
-
private readonly trace: boolean
|
|
384
|
+
private readonly trace: boolean,
|
|
385
|
+
private readonly internedStrings: StringInternTable = new StringInternTable()) {
|
|
331
386
|
}
|
|
332
387
|
|
|
333
388
|
/**
|
|
@@ -371,10 +426,12 @@ export class RpcReceiveQueue {
|
|
|
371
426
|
}
|
|
372
427
|
return this.receive(markers, async m => {
|
|
373
428
|
return saveTrace(this.trace, async () => {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
429
|
+
const id = await this.receive(m.id);
|
|
430
|
+
const markerList = (await this.receiveList(m.markers))!;
|
|
431
|
+
if (markerList.length === 0) {
|
|
432
|
+
return emptyMarkers;
|
|
433
|
+
}
|
|
434
|
+
return updateIfChanged(markers!, {id, markers: markerList});
|
|
378
435
|
})
|
|
379
436
|
})
|
|
380
437
|
}
|
|
@@ -435,8 +492,8 @@ export class RpcReceiveQueue {
|
|
|
435
492
|
after = await codec.rpcReceive(before, this);
|
|
436
493
|
} else if (message.value !== undefined) {
|
|
437
494
|
after = message.valueType ?
|
|
438
|
-
{kind: this.
|
|
439
|
-
typeof message.value === "string" ? this.
|
|
495
|
+
{kind: this.internedStrings.internType(message.valueType), ...message.value} :
|
|
496
|
+
typeof message.value === "string" ? this.internedStrings.internValue(message.value) : message.value;
|
|
440
497
|
} else if (message.state === RpcObjectState.ADD && message.valueType) {
|
|
441
498
|
throw new Error(
|
|
442
499
|
`No RPC codec registered on the TypeScript side for '${message.valueType}'. ` +
|
|
@@ -463,7 +520,7 @@ export class RpcReceiveQueue {
|
|
|
463
520
|
before: T[] | undefined,
|
|
464
521
|
onChange?: (before: T) => T | Promise<T | undefined> | undefined
|
|
465
522
|
): Promise<T[]> {
|
|
466
|
-
return (await this.receiveList(before, onChange)) ?? [];
|
|
523
|
+
return (await this.receiveList(before, onChange)) ?? (EMPTY_LIST as unknown as T[]);
|
|
467
524
|
}
|
|
468
525
|
|
|
469
526
|
receiveList<T>(
|
|
@@ -501,6 +558,9 @@ export class RpcReceiveQueue {
|
|
|
501
558
|
if (!positions) {
|
|
502
559
|
throw new Error(`Expected positions array but got: ${JSON.stringify(d)}`);
|
|
503
560
|
}
|
|
561
|
+
if (positions.length === 0) {
|
|
562
|
+
return EMPTY_LIST as unknown as T[];
|
|
563
|
+
}
|
|
504
564
|
const after: T[] = new Array(positions.length);
|
|
505
565
|
for (let i = 0; i < positions.length; i++) {
|
|
506
566
|
const beforeIdx = positions[i];
|
|
@@ -519,16 +579,7 @@ export class RpcReceiveQueue {
|
|
|
519
579
|
if (codec?.rpcNew) {
|
|
520
580
|
return codec.rpcNew();
|
|
521
581
|
}
|
|
522
|
-
return {kind: this.
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
private intern(value: string): string {
|
|
526
|
-
const existing = this.internedStrings.get(value);
|
|
527
|
-
if (existing !== undefined) {
|
|
528
|
-
return existing;
|
|
529
|
-
}
|
|
530
|
-
this.internedStrings.set(value, value);
|
|
531
|
-
return value;
|
|
582
|
+
return {kind: this.internedStrings.internType(type)} as T;
|
|
532
583
|
}
|
|
533
584
|
}
|
|
534
585
|
|
package/src/rpc/rewrite-rpc.ts
CHANGED
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
import {DataTableStore} from "../data-table";
|
|
41
41
|
import {RecipeMarketplace} from "../marketplace";
|
|
42
42
|
import {initializeMetricsCsv, setCacheSizeProvider} from "./request/metrics";
|
|
43
|
-
import {RpcObjectData, RpcObjectState, RpcReceiveQueue} from "./queue";
|
|
43
|
+
import {RpcObjectData, RpcObjectState, RpcReceiveQueue, StringInternTable} from "./queue";
|
|
44
44
|
import {RpcRecipe} from "./recipe";
|
|
45
45
|
import {ExecutionContext} from "../execution";
|
|
46
46
|
import {InstallRecipes, InstallRecipesResponse} from "./request/install-recipes";
|
|
@@ -68,6 +68,11 @@ export class RewriteRpc {
|
|
|
68
68
|
readonly remoteRefs: Map<number, any> = new Map();
|
|
69
69
|
readonly localRefs: ReferenceMap = new ReferenceMap();
|
|
70
70
|
|
|
71
|
+
// One table for the whole connection so repeated discriminators, enum values and whitespace
|
|
72
|
+
// decoded across the many getObject calls of a run collapse to a single string instance rather
|
|
73
|
+
// than one per received object.
|
|
74
|
+
private readonly internedStrings = new StringInternTable();
|
|
75
|
+
|
|
71
76
|
// Ref high-water per source file, captured before it is first visited so an Evict rolls
|
|
72
77
|
// back exactly the refs it introduced. `send` = localRefs snapshot, `recvMax` = max remoteRefs key.
|
|
73
78
|
readonly refCheckpoints: Map<string, { send: number, recvMax: number }> = new Map();
|
|
@@ -158,6 +163,7 @@ export class RewriteRpc {
|
|
|
158
163
|
this.remoteRefs.clear();
|
|
159
164
|
this.localRefs.clear();
|
|
160
165
|
this.refCheckpoints.clear();
|
|
166
|
+
this.internedStrings.clear();
|
|
161
167
|
preparedRecipes.clear();
|
|
162
168
|
this.remoteLanguages = undefined;
|
|
163
169
|
};
|
|
@@ -262,7 +268,7 @@ export class RewriteRpc {
|
|
|
262
268
|
nextPage = requestPage();
|
|
263
269
|
}
|
|
264
270
|
return page;
|
|
265
|
-
}, this.logger, this.traceGetObject.receive);
|
|
271
|
+
}, this.logger, this.traceGetObject.receive, this.internedStrings);
|
|
266
272
|
|
|
267
273
|
let remoteObject: P;
|
|
268
274
|
try {
|
package/src/util.ts
CHANGED
|
@@ -50,16 +50,66 @@ export function trimIndent(str: string | null | undefined): string {
|
|
|
50
50
|
.trim();
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/**
|
|
54
|
+
* A compiled `(original, updates) => merged` builder that names every resulting property in an
|
|
55
|
+
* object literal. V8 sizes a literal's in-object storage to the properties it names, so the merged
|
|
56
|
+
* node keeps its fields inline; the `{...original, ...updates}` spread of a shared, megamorphic
|
|
57
|
+
* function instead lands in an out-of-object `system / PropertyArray` (millions of them across an
|
|
58
|
+
* RPC-received LST forest). Builders are keyed and cached by the pair of property-name lists so a
|
|
59
|
+
* given shape compiles once.
|
|
60
|
+
*/
|
|
61
|
+
type MergeBuilder = (original: any, updates: any) => any;
|
|
62
|
+
|
|
63
|
+
const mergeBuilders = new Map<string, MergeBuilder>();
|
|
64
|
+
|
|
65
|
+
function mergeBuilderFor(originalKeys: string[], updateKeys: string[]): MergeBuilder | undefined {
|
|
66
|
+
const cacheKey = JSON.stringify(originalKeys) + JSON.stringify(updateKeys);
|
|
67
|
+
let builder = mergeBuilders.get(cacheKey);
|
|
68
|
+
if (builder === undefined) {
|
|
69
|
+
const fromUpdates = new Set(updateKeys);
|
|
70
|
+
const names = originalKeys.slice();
|
|
71
|
+
for (const key of updateKeys) {
|
|
72
|
+
if (!names.includes(key)) {
|
|
73
|
+
names.push(key);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const body = "return {" + names
|
|
77
|
+
.map(name => `${JSON.stringify(name)}:(${fromUpdates.has(name) ? "u" : "o"})[${JSON.stringify(name)}]`)
|
|
78
|
+
.join(",") + "};";
|
|
79
|
+
try {
|
|
80
|
+
builder = new Function("o", "u", body) as MergeBuilder;
|
|
81
|
+
} catch {
|
|
82
|
+
builder = (o, u) => ({...o, ...u});
|
|
83
|
+
}
|
|
84
|
+
mergeBuilders.set(cacheKey, builder);
|
|
85
|
+
}
|
|
86
|
+
return builder;
|
|
87
|
+
}
|
|
88
|
+
|
|
53
89
|
/**
|
|
54
90
|
* Helper function to create a new object only if any properties have changed.
|
|
55
91
|
* Compares each property in updates with the original object.
|
|
56
92
|
* Returns the original object if nothing changed, or a new object with updates applied.
|
|
57
93
|
*/
|
|
58
94
|
export function updateIfChanged<O extends object>(original: O, updates: Partial<O>): O {
|
|
95
|
+
let changed = false;
|
|
59
96
|
for (const key in updates) {
|
|
60
97
|
if (updates[key] !== original[key]) {
|
|
61
|
-
|
|
98
|
+
changed = true;
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (!changed) {
|
|
103
|
+
return original;
|
|
104
|
+
}
|
|
105
|
+
// A merged node built through a shared spread overflows into a PropertyArray; a compiled
|
|
106
|
+
// literal builder keeps its fields inline. Symbol-keyed originals fall back to the spread,
|
|
107
|
+
// since only string keys survive the JSON-serialized builder body.
|
|
108
|
+
if (typeof original === "object" && Object.getOwnPropertySymbols(original).length === 0) {
|
|
109
|
+
const builder = mergeBuilderFor(Object.keys(original), Object.keys(updates as object));
|
|
110
|
+
if (builder !== undefined) {
|
|
111
|
+
return builder(original, updates);
|
|
62
112
|
}
|
|
63
113
|
}
|
|
64
|
-
return original;
|
|
114
|
+
return {...original, ...updates};
|
|
65
115
|
}
|