@openrewrite/rewrite 8.92.4 → 8.92.6
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/javascript/add-import.d.ts.map +1 -1
- package/dist/javascript/add-import.js +10 -4
- package/dist/javascript/add-import.js.map +1 -1
- package/dist/javascript/type-mapping.d.ts +12 -0
- package/dist/javascript/type-mapping.d.ts.map +1 -1
- package/dist/javascript/type-mapping.js +56 -32
- package/dist/javascript/type-mapping.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.map +1 -1
- package/dist/rpc/queue.js +11 -5
- package/dist/rpc/queue.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/javascript/add-import.ts +23 -6
- package/src/javascript/type-mapping.ts +58 -35
- package/src/rpc/index.ts +7 -4
- package/src/rpc/queue.ts +12 -5
- package/src/util.ts +52 -2
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {JavaScriptVisitor} from "./visitor";
|
|
2
|
-
import {ElementRemovalFormatter, emptySpace, isIdentifier, J, NameTree, rightPadded, singleSpace, space, Statement, Type} from "../java";
|
|
2
|
+
import {ElementRemovalFormatter, emptySpace, isIdentifier, J, NameTree, rightPadded, singleSpace, space, Statement, TrailingComma, Type} from "../java";
|
|
3
3
|
import {JS, JSX} from "./tree";
|
|
4
4
|
import {randomId, UUID} from "../uuid";
|
|
5
|
-
import {emptyMarkers, markers} from "../markers";
|
|
5
|
+
import {emptyMarkers, findMarker, markers} from "../markers";
|
|
6
6
|
import {getStyle, SpacesStyle, StyleKind} from "./style";
|
|
7
7
|
import {bindingNames, compilationUnitOf, cursorOf, declarationsOf, deconflict, isValueReference, namesDeclaredIn, scopeOf, walk} from "./scope";
|
|
8
8
|
import {create as produce, Draft} from "mutative";
|
|
@@ -940,7 +940,10 @@ export class AddImport<P> extends JavaScriptVisitor<P> {
|
|
|
940
940
|
// - trailingSpace: space before } (from last element's after)
|
|
941
941
|
const firstElementPrefix = existingElements[0]?.element?.prefix ?? emptySpace;
|
|
942
942
|
const lastIndex = existingElements.length - 1;
|
|
943
|
-
|
|
943
|
+
// A trailing comma is a marker on the last element rather than padding, so with
|
|
944
|
+
// one present the space before `}` lives in the marker's suffix, not in `after`.
|
|
945
|
+
const trailingComma = findMarker<TrailingComma>(existingElements[lastIndex], J.Markers.TrailingComma);
|
|
946
|
+
const trailingSpace = trailingComma ? trailingComma.suffix : existingElements[lastIndex].after;
|
|
944
947
|
|
|
945
948
|
// Build the new elements array with proper spacing
|
|
946
949
|
const updatedNamedImports: JS.NamedImports = await this.produceJavaScript(
|
|
@@ -961,9 +964,18 @@ export class AddImport<P> extends JavaScriptVisitor<P> {
|
|
|
961
964
|
if (j === 0 && insertIndex === 0 && elem.element) {
|
|
962
965
|
adjusted = {...elem, element: {...elem.element, prefix: singleSpace}};
|
|
963
966
|
}
|
|
964
|
-
// Last element before a new trailing element loses its trailing space
|
|
967
|
+
// Last element before a new trailing element loses its trailing space,
|
|
968
|
+
// and hands off its trailing comma to the element that becomes last.
|
|
965
969
|
if (j === lastIndex && insertIndex > lastIndex) {
|
|
966
|
-
adjusted =
|
|
970
|
+
adjusted = trailingComma ?
|
|
971
|
+
{
|
|
972
|
+
...adjusted,
|
|
973
|
+
markers: {
|
|
974
|
+
...adjusted.markers,
|
|
975
|
+
markers: adjusted.markers.markers.filter(m => m !== trailingComma)
|
|
976
|
+
}
|
|
977
|
+
} :
|
|
978
|
+
{...adjusted, after: emptySpace};
|
|
967
979
|
}
|
|
968
980
|
results.push(adjusted);
|
|
969
981
|
return results;
|
|
@@ -971,7 +983,12 @@ export class AddImport<P> extends JavaScriptVisitor<P> {
|
|
|
971
983
|
|
|
972
984
|
// Append at end if inserting after all existing elements
|
|
973
985
|
if (insertIndex > lastIndex) {
|
|
974
|
-
|
|
986
|
+
const appended = rightPadded(
|
|
987
|
+
{...newSpecifier, prefix: singleSpace},
|
|
988
|
+
trailingComma ? emptySpace : trailingSpace);
|
|
989
|
+
newElements.push(trailingComma ?
|
|
990
|
+
{...appended, markers: markers(trailingComma)} :
|
|
991
|
+
appended);
|
|
975
992
|
}
|
|
976
993
|
|
|
977
994
|
namedDraft.elements = {...namedImports.elements, elements: newElements};
|
|
@@ -693,6 +693,38 @@ export class JavaScriptTypeMapping {
|
|
|
693
693
|
return variable;
|
|
694
694
|
}
|
|
695
695
|
|
|
696
|
+
/**
|
|
697
|
+
* The module specifier of a `require('module')` call, or undefined for anything else.
|
|
698
|
+
*
|
|
699
|
+
* `require` is declared to return `any`, so the checker carries no module type through it and
|
|
700
|
+
* the specifier in the call is the only thing identifying the module.
|
|
701
|
+
*/
|
|
702
|
+
private requiredModuleSpecifier(node: ts.Expression): string | undefined {
|
|
703
|
+
if (!ts.isCallExpression(node) || !ts.isIdentifier(node.expression) ||
|
|
704
|
+
node.expression.text !== 'require' || node.arguments.length === 0) {
|
|
705
|
+
return undefined;
|
|
706
|
+
}
|
|
707
|
+
const moduleArg = node.arguments[0];
|
|
708
|
+
return ts.isStringLiteral(moduleArg) ? moduleArg.text : undefined;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
/**
|
|
712
|
+
* The module an expression evaluates to where that is a `require()` result — either the call
|
|
713
|
+
* itself (`require('x').fn()`) or an identifier bound to one (`const x = require('x')`).
|
|
714
|
+
*/
|
|
715
|
+
private requiredModuleOfExpression(node: ts.Expression): string | undefined {
|
|
716
|
+
const direct = this.requiredModuleSpecifier(node);
|
|
717
|
+
if (direct) {
|
|
718
|
+
return direct;
|
|
719
|
+
}
|
|
720
|
+
if (!ts.isIdentifier(node)) {
|
|
721
|
+
return undefined;
|
|
722
|
+
}
|
|
723
|
+
const valueDecl = this.checker.getSymbolAtLocation(node)?.valueDeclaration;
|
|
724
|
+
return valueDecl && ts.isVariableDeclaration(valueDecl) && valueDecl.initializer ?
|
|
725
|
+
this.requiredModuleSpecifier(valueDecl.initializer) : undefined;
|
|
726
|
+
}
|
|
727
|
+
|
|
696
728
|
/**
|
|
697
729
|
* Extract the npm module name from a file path.
|
|
698
730
|
* Handles various package manager layouts:
|
|
@@ -891,41 +923,14 @@ export class JavaScriptTypeMapping {
|
|
|
891
923
|
// where the module is typed as 'any' but methods still have signatures
|
|
892
924
|
|
|
893
925
|
// Try to trace back through the AST to find the require() call
|
|
894
|
-
|
|
895
|
-
const objExpr = node.expression.expression;
|
|
896
|
-
|
|
897
|
-
if (ts.isIdentifier(objExpr)) {
|
|
898
|
-
// Look for the variable declaration that assigns the require() result
|
|
899
|
-
const objSymbol = this.checker.getSymbolAtLocation(objExpr);
|
|
900
|
-
|
|
901
|
-
if (objSymbol && objSymbol.valueDeclaration) {
|
|
902
|
-
const valueDecl = objSymbol.valueDeclaration;
|
|
903
|
-
if (ts.isVariableDeclaration(valueDecl) && valueDecl.initializer) {
|
|
904
|
-
// Check if it's a require() call
|
|
905
|
-
if (ts.isCallExpression(valueDecl.initializer)) {
|
|
906
|
-
const callExpr = valueDecl.initializer;
|
|
907
|
-
if (ts.isIdentifier(callExpr.expression) &&
|
|
908
|
-
callExpr.expression.getText() === 'require' &&
|
|
909
|
-
callExpr.arguments.length > 0) {
|
|
910
|
-
// Extract the module name from require('module-name')
|
|
911
|
-
const moduleArg = callExpr.arguments[0];
|
|
912
|
-
if (ts.isStringLiteral(moduleArg)) {
|
|
913
|
-
const moduleName = moduleArg.text;
|
|
914
|
-
|
|
915
|
-
inferredDeclaringType = {
|
|
916
|
-
kind: Type.Kind.Class,
|
|
917
|
-
flags: 0, // TODO - determine flags
|
|
918
|
-
fullyQualifiedName: moduleName
|
|
919
|
-
} as Type.FullyQualified;
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
+
const moduleName = this.requiredModuleOfExpression(node.expression.expression);
|
|
926
927
|
|
|
927
928
|
// Use the inferred type or fall back to unknown
|
|
928
|
-
declaringType =
|
|
929
|
+
declaringType = moduleName ? {
|
|
930
|
+
kind: Type.Kind.Class,
|
|
931
|
+
flags: 0, // TODO - determine flags
|
|
932
|
+
fullyQualifiedName: moduleName
|
|
933
|
+
} as Type.FullyQualified : Type.unknownType as Type.FullyQualified;
|
|
929
934
|
|
|
930
935
|
// Create the method type using the helper
|
|
931
936
|
return this.createMethodType(signature, node, declaringType, methodName);
|
|
@@ -985,6 +990,19 @@ export class JavaScriptTypeMapping {
|
|
|
985
990
|
declaringType = Type.unknownType as Type.FullyQualified;
|
|
986
991
|
}
|
|
987
992
|
|
|
993
|
+
// A `require()` result is typed `any`, so nothing above identifies the module; the
|
|
994
|
+
// specifier in the call does.
|
|
995
|
+
if (declaringType === Type.unknownType) {
|
|
996
|
+
const moduleName = this.requiredModuleOfExpression(node.expression.expression);
|
|
997
|
+
if (moduleName) {
|
|
998
|
+
declaringType = {
|
|
999
|
+
kind: Type.Kind.Class,
|
|
1000
|
+
flags: 0, // TODO - determine flags
|
|
1001
|
+
fullyQualifiedName: moduleName
|
|
1002
|
+
} as Type.FullyQualified;
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
|
|
988
1006
|
// For string methods like 'hello'.split(), ensure we have a proper declaring type for primitives
|
|
989
1007
|
if (!isImport && declaringType === Type.unknownType) {
|
|
990
1008
|
// If the expression type is a primitive string, use String as declaring type
|
|
@@ -1294,9 +1312,14 @@ export class JavaScriptTypeMapping {
|
|
|
1294
1312
|
// file's node_modules path) match the names used for directly imported types.
|
|
1295
1313
|
packageName = this.normalizePackageName(packageName);
|
|
1296
1314
|
|
|
1297
|
-
//
|
|
1315
|
+
// The symbol name is appended to the declaration file's path, so it is whatever
|
|
1316
|
+
// follows the last dot of the final path segment. Directory names carry dots of
|
|
1317
|
+
// their own — `.pnpm`, `.yarn`, a hidden parent such as `.claude`, a versioned
|
|
1318
|
+
// pnpm directory — so a search over the whole path would split there instead and
|
|
1319
|
+
// fold most of the path into the name.
|
|
1320
|
+
const lastSegmentIndex = cleanedName.lastIndexOf('/') + 1;
|
|
1298
1321
|
const lastDotIndex = cleanedName.lastIndexOf('.');
|
|
1299
|
-
if (lastDotIndex >
|
|
1322
|
+
if (lastDotIndex > lastSegmentIndex) {
|
|
1300
1323
|
const symbolName = cleanedName.substring(lastDotIndex + 1);
|
|
1301
1324
|
cleanedName = `${packageName}.${symbolName}`;
|
|
1302
1325
|
} else {
|
package/src/rpc/index.ts
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
import {Checksum, FileAttributes, TreeKind} from "../tree";
|
|
17
17
|
import {RpcCodecs, RpcReceiveQueue, RpcSendQueue} from "./queue";
|
|
18
18
|
import {
|
|
19
|
+
emptyMarkers,
|
|
19
20
|
Markers,
|
|
20
21
|
MarkersKind,
|
|
21
22
|
MarkupDebug,
|
|
@@ -76,10 +77,12 @@ RpcCodecs.registerCodec(TreeKind.FileAttributes, {
|
|
|
76
77
|
|
|
77
78
|
RpcCodecs.registerCodec(MarkersKind.Markers, {
|
|
78
79
|
async rpcReceive(before: Markers, q: RpcReceiveQueue): Promise<Markers> {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
const id = await q.receive(before.id);
|
|
81
|
+
const markers = (await q.receiveList(before.markers))!;
|
|
82
|
+
if (markers.length === 0) {
|
|
83
|
+
return emptyMarkers;
|
|
84
|
+
}
|
|
85
|
+
return updateIfChanged(before, {id, markers});
|
|
83
86
|
},
|
|
84
87
|
|
|
85
88
|
async rpcSend(after: Markers, q: RpcSendQueue): Promise<void> {
|
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.
|
|
@@ -424,10 +426,12 @@ export class RpcReceiveQueue {
|
|
|
424
426
|
}
|
|
425
427
|
return this.receive(markers, async m => {
|
|
426
428
|
return saveTrace(this.trace, async () => {
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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});
|
|
431
435
|
})
|
|
432
436
|
})
|
|
433
437
|
}
|
|
@@ -516,7 +520,7 @@ export class RpcReceiveQueue {
|
|
|
516
520
|
before: T[] | undefined,
|
|
517
521
|
onChange?: (before: T) => T | Promise<T | undefined> | undefined
|
|
518
522
|
): Promise<T[]> {
|
|
519
|
-
return (await this.receiveList(before, onChange)) ?? [];
|
|
523
|
+
return (await this.receiveList(before, onChange)) ?? (EMPTY_LIST as unknown as T[]);
|
|
520
524
|
}
|
|
521
525
|
|
|
522
526
|
receiveList<T>(
|
|
@@ -554,6 +558,9 @@ export class RpcReceiveQueue {
|
|
|
554
558
|
if (!positions) {
|
|
555
559
|
throw new Error(`Expected positions array but got: ${JSON.stringify(d)}`);
|
|
556
560
|
}
|
|
561
|
+
if (positions.length === 0) {
|
|
562
|
+
return EMPTY_LIST as unknown as T[];
|
|
563
|
+
}
|
|
557
564
|
const after: T[] = new Array(positions.length);
|
|
558
565
|
for (let i = 0; i < positions.length; i++) {
|
|
559
566
|
const beforeIdx = positions[i];
|
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
|
}
|