@openrewrite/rewrite 8.88.0 → 8.88.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/node-resolution-result.js +18 -16
- package/dist/javascript/node-resolution-result.js.map +1 -1
- package/dist/javascript/package-exported-types.d.ts +13 -0
- package/dist/javascript/package-exported-types.d.ts.map +1 -0
- package/dist/javascript/package-exported-types.js +374 -0
- package/dist/javascript/package-exported-types.js.map +1 -0
- package/dist/javascript/type-mapping.d.ts +6 -0
- package/dist/javascript/type-mapping.d.ts.map +1 -1
- package/dist/javascript/type-mapping.js +16 -0
- package/dist/javascript/type-mapping.js.map +1 -1
- package/dist/rewrite-javascript-version.txt +1 -1
- package/dist/rpc/queue.d.ts +12 -0
- package/dist/rpc/queue.d.ts.map +1 -1
- package/dist/rpc/queue.js +35 -4
- package/dist/rpc/queue.js.map +1 -1
- package/dist/rpc/request/dependency-types.d.ts +15 -0
- package/dist/rpc/request/dependency-types.d.ts.map +1 -0
- package/dist/rpc/request/dependency-types.js +143 -0
- package/dist/rpc/request/dependency-types.js.map +1 -0
- package/dist/rpc/request/index.d.ts +1 -0
- package/dist/rpc/request/index.d.ts.map +1 -1
- package/dist/rpc/request/index.js +1 -0
- package/dist/rpc/request/index.js.map +1 -1
- package/dist/rpc/request/last-parsed-project.d.ts +3 -0
- package/dist/rpc/request/last-parsed-project.d.ts.map +1 -0
- package/dist/rpc/request/last-parsed-project.js +29 -0
- package/dist/rpc/request/last-parsed-project.js.map +1 -0
- package/dist/rpc/request/parse-project.d.ts.map +1 -1
- package/dist/rpc/request/parse-project.js +2 -0
- package/dist/rpc/request/parse-project.js.map +1 -1
- package/dist/rpc/rewrite-rpc.d.ts.map +1 -1
- package/dist/rpc/rewrite-rpc.js +1 -0
- package/dist/rpc/rewrite-rpc.js.map +1 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +39 -27
- package/dist/run.js.map +1 -1
- package/package.json +1 -1
- package/src/javascript/node-resolution-result.ts +18 -16
- package/src/javascript/package-exported-types.ts +351 -0
- package/src/javascript/type-mapping.ts +16 -0
- package/src/rpc/queue.ts +38 -4
- package/src/rpc/request/dependency-types.ts +128 -0
- package/src/rpc/request/index.ts +1 -0
- package/src/rpc/request/last-parsed-project.ts +27 -0
- package/src/rpc/request/parse-project.ts +2 -0
- package/src/rpc/rewrite-rpc.ts +2 -0
- package/src/run.ts +44 -23
|
@@ -0,0 +1,351 @@
|
|
|
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 ts from "typescript";
|
|
17
|
+
import * as path from "path";
|
|
18
|
+
import {Type} from "../java";
|
|
19
|
+
import {JavaScriptTypeMapping} from "./type-mapping";
|
|
20
|
+
|
|
21
|
+
// Free functions are not exported-type entries; only nominal types (and value modules, which are
|
|
22
|
+
// descended into for nested types) qualify.
|
|
23
|
+
const TYPE_SYMBOL_FLAGS =
|
|
24
|
+
ts.SymbolFlags.Class |
|
|
25
|
+
ts.SymbolFlags.Interface |
|
|
26
|
+
ts.SymbolFlags.Enum |
|
|
27
|
+
ts.SymbolFlags.TypeAlias |
|
|
28
|
+
ts.SymbolFlags.ValueModule;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Enumerate the OWN public exported types of one or more npm packages into the engine's
|
|
32
|
+
* {@link Type} model. The JS analog of the C# {@code AssemblyTypeEnumerator.Enumerate}:
|
|
33
|
+
* a {@code ts.Program} is rooted at each package's declaration entry, the {@code references}
|
|
34
|
+
* closure supplies node_modules resolution, and the types each own package exports are
|
|
35
|
+
* mapped with full members and methods. Types reached transitively come back shallow.
|
|
36
|
+
*
|
|
37
|
+
* @param ownArtifacts package directories whose public types should be defined (full bodies)
|
|
38
|
+
* @param references node_modules roots / sibling packages used only for symbol resolution
|
|
39
|
+
*/
|
|
40
|
+
export function exportedTypes(ownArtifacts: string[], references: string[]): Type.FullyQualified[] {
|
|
41
|
+
const entries: string[] = [];
|
|
42
|
+
for (const artifact of ownArtifacts) {
|
|
43
|
+
const entry = declarationFileEntry(artifact) ?? resolvePackageEntry(artifact);
|
|
44
|
+
if (entry) {
|
|
45
|
+
entries.push(path.normalize(entry));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (entries.length === 0) {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const compilerOptions: ts.CompilerOptions = {
|
|
53
|
+
target: ts.ScriptTarget.Latest,
|
|
54
|
+
module: ts.ModuleKind.CommonJS,
|
|
55
|
+
moduleResolution: ts.ModuleResolutionKind.Node10,
|
|
56
|
+
noEmit: true,
|
|
57
|
+
allowJs: true,
|
|
58
|
+
checkJs: false,
|
|
59
|
+
esModuleInterop: true,
|
|
60
|
+
allowSyntheticDefaultImports: true,
|
|
61
|
+
skipLibCheck: true,
|
|
62
|
+
forceConsistentCasingInFileNames: false,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const host = ts.createCompilerHost(compilerOptions);
|
|
66
|
+
// Default node resolution from each file's on-disk location, falling back to the
|
|
67
|
+
// explicit reference roots when a dependency isn't co-located with the own package.
|
|
68
|
+
host.resolveModuleNameLiterals = (moduleLiterals, containingFile) =>
|
|
69
|
+
moduleLiterals.map(literal => {
|
|
70
|
+
let resolved = ts.resolveModuleName(literal.text, containingFile, compilerOptions, host);
|
|
71
|
+
if (!resolved.resolvedModule) {
|
|
72
|
+
for (const ref of references) {
|
|
73
|
+
const probe = ts.resolveModuleName(literal.text, path.join(ref, "__probe__.ts"), compilerOptions, host);
|
|
74
|
+
if (probe.resolvedModule) {
|
|
75
|
+
resolved = probe;
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return resolved;
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const program = ts.createProgram(entries, compilerOptions, host);
|
|
84
|
+
const checker = program.getTypeChecker();
|
|
85
|
+
const typeMapping = new JavaScriptTypeMapping(checker);
|
|
86
|
+
|
|
87
|
+
// Lowest arity wins for a given FQN, mirroring the C# byFqn dedup.
|
|
88
|
+
const byFqn = new Map<string, Type.Class>();
|
|
89
|
+
const collect = (cls: Type.Class): void => {
|
|
90
|
+
const fqn = cls.fullyQualifiedName;
|
|
91
|
+
if (!fqn || fqn === "unknown" || fqn === "<unknown>" || fqn === Type.FUNCTION_TYPE_NAME) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const existing = byFqn.get(fqn);
|
|
95
|
+
if (!existing || arity(cls) < arity(existing)) {
|
|
96
|
+
byFqn.set(fqn, cls);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const mapSymbols = (symbols: ts.Symbol[]): void => {
|
|
101
|
+
for (const symbol of symbols) {
|
|
102
|
+
// Isolate per-symbol mapping: one throwing export must not abort the coordinate.
|
|
103
|
+
try {
|
|
104
|
+
const declNodes = classLikeDeclarations(symbol);
|
|
105
|
+
if (declNodes.length > 0) {
|
|
106
|
+
const cls = typeMapping.declarationType(declNodes[0]);
|
|
107
|
+
if (Type.isClass(cls)) {
|
|
108
|
+
// Walk every declaration so merged interfaces / class+namespace merges keep
|
|
109
|
+
// all their methods; populateMethods dedups by signature across the calls.
|
|
110
|
+
for (const declNode of declNodes) {
|
|
111
|
+
populateMethods(typeMapping, declNode, cls);
|
|
112
|
+
}
|
|
113
|
+
collect(cls);
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
// Type aliases / namespaces: keep only those that resolve to a nominal class.
|
|
117
|
+
const mapped = typeMapping.exportedType(symbol);
|
|
118
|
+
if (Type.isClass(mapped)) {
|
|
119
|
+
collect(mapped);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
} catch (e) {
|
|
123
|
+
console.warn(`exportedTypes: skipping export '${symbol.getName()}': ${e}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
for (const entry of entries) {
|
|
129
|
+
const sourceFile = program.getSourceFile(entry);
|
|
130
|
+
if (!sourceFile) {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (ts.isExternalModule(sourceFile)) {
|
|
134
|
+
const moduleSymbol = checker.getSymbolAtLocation(sourceFile) ?? (sourceFile as { symbol?: ts.Symbol }).symbol;
|
|
135
|
+
if (moduleSymbol) {
|
|
136
|
+
mapSymbols(enumerateTypeSymbols(checker, moduleSymbol));
|
|
137
|
+
}
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
// A global script (lib.*.d.ts / ambient .d.ts): globals live in global scope, not on a
|
|
141
|
+
// module symbol. Enumerate top-level ambient declarations + `declare module` ambients.
|
|
142
|
+
mapSymbols(globalTypeSymbols(checker, sourceFile));
|
|
143
|
+
for (const ambient of checker.getAmbientModules()) {
|
|
144
|
+
if (ambient.declarations?.some(d => d.getSourceFile() === sourceFile)) {
|
|
145
|
+
mapSymbols(enumerateTypeSymbols(checker, ambient));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return [...byFqn.values()];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function arity(cls: Type.Class): number {
|
|
154
|
+
return cls.typeParameters?.length ?? 0;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** A TypeScript declaration/source FILE (e.g. a `lib.*.d.ts` of ambient globals), used directly as a program entry. */
|
|
158
|
+
function declarationFileEntry(artifact: string): string | undefined {
|
|
159
|
+
return ts.sys.fileExists(artifact) && /\.[cm]?tsx?$/.test(artifact) ? artifact : undefined;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Top-level ambient type declarations of a script (non-module) file — a `lib.*.d.ts`'s globals; namespaces descended for nested types. */
|
|
163
|
+
function globalTypeSymbols(checker: ts.TypeChecker, sourceFile: ts.SourceFile): ts.Symbol[] {
|
|
164
|
+
const out: ts.Symbol[] = [];
|
|
165
|
+
const seen = new Set<ts.Symbol>();
|
|
166
|
+
const push = (name: ts.Node | undefined): void => {
|
|
167
|
+
const symbol = name ? checker.getSymbolAtLocation(name) : undefined;
|
|
168
|
+
if (symbol && !seen.has(symbol)) {
|
|
169
|
+
seen.add(symbol);
|
|
170
|
+
out.push(symbol);
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
const visit = (node: ts.Node): void => {
|
|
174
|
+
if (ts.isInterfaceDeclaration(node) || ts.isClassDeclaration(node) ||
|
|
175
|
+
ts.isEnumDeclaration(node) || ts.isTypeAliasDeclaration(node)) {
|
|
176
|
+
push(node.name);
|
|
177
|
+
} else if (ts.isModuleDeclaration(node)) {
|
|
178
|
+
push(node.name);
|
|
179
|
+
if (node.body && ts.isModuleBlock(node.body)) {
|
|
180
|
+
node.body.statements.forEach(visit);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
sourceFile.statements.forEach(visit);
|
|
185
|
+
return out;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Resolve a package directory to its declaration entry: the package.json `types`/`typings`
|
|
190
|
+
* field, else the `exports["."]` types condition (modern packages declare types only there),
|
|
191
|
+
* else a top-level `index.d.ts` (then `index.ts`).
|
|
192
|
+
*/
|
|
193
|
+
function resolvePackageEntry(dir: string): string | undefined {
|
|
194
|
+
const packageJson = path.join(dir, "package.json");
|
|
195
|
+
if (ts.sys.fileExists(packageJson)) {
|
|
196
|
+
try {
|
|
197
|
+
const pkg = JSON.parse(ts.sys.readFile(packageJson) || "{}");
|
|
198
|
+
const typesField: unknown = pkg.types ?? pkg.typings;
|
|
199
|
+
const candidate = typeof typesField === "string"
|
|
200
|
+
? typesField
|
|
201
|
+
: exportsTypesEntry(pkg.exports);
|
|
202
|
+
if (typeof candidate === "string") {
|
|
203
|
+
const resolved = path.resolve(dir, candidate);
|
|
204
|
+
if (ts.sys.fileExists(resolved)) {
|
|
205
|
+
return resolved;
|
|
206
|
+
}
|
|
207
|
+
if (ts.sys.fileExists(resolved + ".d.ts")) {
|
|
208
|
+
return resolved + ".d.ts";
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
} catch {
|
|
212
|
+
// Malformed package.json; fall through to conventional entry points.
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
for (const candidate of ["index.d.ts", "index.ts", "index.tsx"]) {
|
|
216
|
+
const resolved = path.join(dir, candidate);
|
|
217
|
+
if (ts.sys.fileExists(resolved)) {
|
|
218
|
+
return resolved;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return undefined;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** The declaration file for the package's `.` subpath from its `exports` map, if any. */
|
|
225
|
+
function exportsTypesEntry(exports: unknown): string | undefined {
|
|
226
|
+
if (typeof exports === "string") {
|
|
227
|
+
return typesFromCondition(exports);
|
|
228
|
+
}
|
|
229
|
+
if (!exports || typeof exports !== "object") {
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
const map = exports as Record<string, unknown>;
|
|
233
|
+
// `exports["."]`, or the whole object when it is condition-only sugar (no subpath keys).
|
|
234
|
+
const dot = "." in map
|
|
235
|
+
? map["."]
|
|
236
|
+
: (Object.keys(map).some(k => k.startsWith(".")) ? undefined : map);
|
|
237
|
+
return dot === undefined ? undefined : typesFromCondition(dot);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Pull a declaration file out of an `exports` condition value: an explicit `types` condition,
|
|
242
|
+
* else the `types` nested under `import`/`require`/`default`. A bare target string is a runtime
|
|
243
|
+
* (JS) entry, not types, unless it is itself a declaration file.
|
|
244
|
+
*/
|
|
245
|
+
function typesFromCondition(entry: unknown): string | undefined {
|
|
246
|
+
if (typeof entry === "string") {
|
|
247
|
+
return /\.d\.[cm]?ts$/.test(entry) ? entry : undefined;
|
|
248
|
+
}
|
|
249
|
+
if (!entry || typeof entry !== "object") {
|
|
250
|
+
return undefined;
|
|
251
|
+
}
|
|
252
|
+
const o = entry as Record<string, unknown>;
|
|
253
|
+
if (typeof o.types === "string") {
|
|
254
|
+
return o.types;
|
|
255
|
+
}
|
|
256
|
+
const nestedTypes = typesFromCondition(o.types);
|
|
257
|
+
if (nestedTypes) {
|
|
258
|
+
return nestedTypes;
|
|
259
|
+
}
|
|
260
|
+
for (const cond of ["import", "require", "default"]) {
|
|
261
|
+
const nested = typesFromCondition(o[cond]);
|
|
262
|
+
if (nested) {
|
|
263
|
+
return nested;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return undefined;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* The exported symbols of a module that name a type, resolving aliases and descending one
|
|
271
|
+
* level into re-exported namespaces (e.g. `export = SomeNamespace`) to reach nested types.
|
|
272
|
+
*/
|
|
273
|
+
function enumerateTypeSymbols(checker: ts.TypeChecker, moduleSymbol: ts.Symbol): ts.Symbol[] {
|
|
274
|
+
const out: ts.Symbol[] = [];
|
|
275
|
+
const seen = new Set<ts.Symbol>();
|
|
276
|
+
const queue: ts.Symbol[] = [...moduleExports(checker, moduleSymbol)];
|
|
277
|
+
while (queue.length > 0) {
|
|
278
|
+
let symbol = queue.shift()!;
|
|
279
|
+
if (symbol.flags & ts.SymbolFlags.Alias) {
|
|
280
|
+
symbol = checker.getAliasedSymbol(symbol);
|
|
281
|
+
}
|
|
282
|
+
if (seen.has(symbol)) {
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
seen.add(symbol);
|
|
286
|
+
if (symbol.flags & TYPE_SYMBOL_FLAGS) {
|
|
287
|
+
out.push(symbol);
|
|
288
|
+
}
|
|
289
|
+
if (symbol.flags & (ts.SymbolFlags.ValueModule | ts.SymbolFlags.NamespaceModule)) {
|
|
290
|
+
for (const nested of moduleExports(checker, symbol)) {
|
|
291
|
+
queue.push(nested);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return out;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function moduleExports(checker: ts.TypeChecker, symbol: ts.Symbol): ts.Symbol[] {
|
|
299
|
+
try {
|
|
300
|
+
return checker.getExportsOfModule(symbol);
|
|
301
|
+
} catch {
|
|
302
|
+
const members: ts.Symbol[] = [];
|
|
303
|
+
symbol.exports?.forEach(s => members.push(s));
|
|
304
|
+
return members;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// All class/interface/enum declarations of a symbol. Returns more than one for declaration
|
|
309
|
+
// merging (e.g. two `interface Req {...}` blocks, or a class merged with a namespace).
|
|
310
|
+
function classLikeDeclarations(
|
|
311
|
+
symbol: ts.Symbol
|
|
312
|
+
): Array<ts.ClassDeclaration | ts.InterfaceDeclaration | ts.EnumDeclaration> {
|
|
313
|
+
const out: Array<ts.ClassDeclaration | ts.InterfaceDeclaration | ts.EnumDeclaration> = [];
|
|
314
|
+
for (const declaration of symbol.declarations ?? []) {
|
|
315
|
+
if (ts.isClassDeclaration(declaration) || ts.isInterfaceDeclaration(declaration) || ts.isEnumDeclaration(declaration)) {
|
|
316
|
+
out.push(declaration);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return out;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Populate `classType.methods`, which the normal mapping path leaves empty. Scoped to the
|
|
324
|
+
* enumerator so parser output is unchanged: walk the declaration's method/constructor nodes,
|
|
325
|
+
* map each via the public {@link JavaScriptTypeMapping.methodType}, and dedup by signature.
|
|
326
|
+
*/
|
|
327
|
+
function populateMethods(
|
|
328
|
+
typeMapping: JavaScriptTypeMapping,
|
|
329
|
+
node: ts.ClassDeclaration | ts.InterfaceDeclaration | ts.EnumDeclaration,
|
|
330
|
+
classType: Type.Class
|
|
331
|
+
): void {
|
|
332
|
+
if (!ts.isClassDeclaration(node) && !ts.isInterfaceDeclaration(node)) {
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
const seen = new Set<string>(classType.methods.map(m => Type.signature(m)));
|
|
336
|
+
for (const member of node.members) {
|
|
337
|
+
if (ts.isMethodDeclaration(member) || ts.isMethodSignature(member) || ts.isConstructorDeclaration(member)) {
|
|
338
|
+
const method = typeMapping.methodType(member);
|
|
339
|
+
if (!method) {
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
method.declaringType = classType;
|
|
343
|
+
const sig = Type.signature(method);
|
|
344
|
+
if (seen.has(sig)) {
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
seen.add(sig);
|
|
348
|
+
classType.methods.push(method);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
@@ -197,6 +197,22 @@ export class JavaScriptTypeMapping {
|
|
|
197
197
|
return classType;
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
+
/**
|
|
201
|
+
* Map an exported symbol (type alias, namespace, function, ...) to its {@link Type} via the
|
|
202
|
+
* symbol's declared or value type. Enumerator-only entry point ({@code exportedTypes}) for
|
|
203
|
+
* exports that are not class/interface/enum declarations; those go through {@link declarationType}.
|
|
204
|
+
*/
|
|
205
|
+
exportedType(symbol: ts.Symbol): Type | undefined {
|
|
206
|
+
const target = symbol.flags & ts.SymbolFlags.Alias ? this.checker.getAliasedSymbol(symbol) : symbol;
|
|
207
|
+
let type: ts.Type | undefined;
|
|
208
|
+
if (target.flags & (ts.SymbolFlags.Class | ts.SymbolFlags.Interface | ts.SymbolFlags.Enum | ts.SymbolFlags.TypeAlias | ts.SymbolFlags.ValueModule)) {
|
|
209
|
+
type = this.checker.getDeclaredTypeOfSymbol(target);
|
|
210
|
+
} else if (target.valueDeclaration) {
|
|
211
|
+
type = this.checker.getTypeOfSymbolAtLocation(target, target.valueDeclaration);
|
|
212
|
+
}
|
|
213
|
+
return type ? this.getType(type) : undefined;
|
|
214
|
+
}
|
|
215
|
+
|
|
200
216
|
private getType(type: ts.Type): Type {
|
|
201
217
|
// Check for error types first - these indicate type-checking failures
|
|
202
218
|
// and should not be processed further
|
package/src/rpc/queue.ts
CHANGED
|
@@ -130,6 +130,30 @@ export class RpcSendQueue {
|
|
|
130
130
|
return result;
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
/**
|
|
134
|
+
* Send a bare list (no enclosing object) and return the complete batch terminated by
|
|
135
|
+
* END_OF_OBJECT. Used by self-contained responses like DependencyTypes, where the peer
|
|
136
|
+
* drains one list of ref-deduplicated elements rather than a tree.
|
|
137
|
+
*/
|
|
138
|
+
async generateList<T>(after: T[] | undefined,
|
|
139
|
+
id: (value: T) => any,
|
|
140
|
+
onChange: (value: T) => Promise<any>): Promise<RpcObjectData[]> {
|
|
141
|
+
await this.sendList(after, undefined, id, onChange);
|
|
142
|
+
return this.finish();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Terminate a hand-composed batch: append END_OF_OBJECT and return the accumulated data,
|
|
147
|
+
* resetting the queue. Lets a caller emit several {@link sendList}s into one stream that the
|
|
148
|
+
* peer drains as consecutive lists (e.g. DependencyTypes sends its FQN list then its type list).
|
|
149
|
+
*/
|
|
150
|
+
finish(): RpcObjectData[] {
|
|
151
|
+
const result = this.q;
|
|
152
|
+
result.push({state: RpcObjectState.END_OF_OBJECT});
|
|
153
|
+
this.q = [];
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
|
|
133
157
|
private put(d: RpcObjectData): void {
|
|
134
158
|
if (this.trace) {
|
|
135
159
|
d.trace = trace("Sender");
|
|
@@ -163,6 +187,12 @@ export class RpcSendQueue {
|
|
|
163
187
|
await this.add(after, onChange);
|
|
164
188
|
} else if (after === undefined) {
|
|
165
189
|
this.put({state: RpcObjectState.DELETE});
|
|
190
|
+
} else if (isRef(after)) {
|
|
191
|
+
// A ref-deduplicated slot is resolved by the receiver against a persistent cache whose
|
|
192
|
+
// instance may be aliased by any number of other slots and source files. A CHANGE would
|
|
193
|
+
// be applied to that shared instance in place, corrupting every alias, so the new value
|
|
194
|
+
// is re-added instead; the refs map collapses repeats of it into ref-only ADDs.
|
|
195
|
+
await this.add(after, onChange);
|
|
166
196
|
} else {
|
|
167
197
|
let afterCodec = onChange ? undefined : RpcCodecs.forInstance(after, this.sourceFileType);
|
|
168
198
|
this.put({state: RpcObjectState.CHANGE, value: onChange || afterCodec ? undefined : after});
|
|
@@ -191,12 +221,16 @@ export class RpcSendQueue {
|
|
|
191
221
|
const aBefore = before?.[beforePos];
|
|
192
222
|
if (aBefore === anAfter) {
|
|
193
223
|
this.put({state: RpcObjectState.NO_CHANGE});
|
|
194
|
-
} else if (anAfter !== undefined && this.typesAreDifferent(anAfter, aBefore)) {
|
|
195
|
-
// Type changed - treat as ADD
|
|
224
|
+
} else if (anAfter !== undefined && (isRef(anAfter) || this.typesAreDifferent(anAfter, aBefore))) {
|
|
225
|
+
// Type changed - treat as ADD. Ref-deduplicated items are also always
|
|
226
|
+
// re-added rather than CHANGEd (see send()).
|
|
196
227
|
await this.add(anAfter, onChangeRun);
|
|
197
228
|
} else {
|
|
198
|
-
|
|
199
|
-
|
|
229
|
+
const afterCodec = onChangeRun ? undefined : RpcCodecs.forInstance(anAfter, this.sourceFileType);
|
|
230
|
+
// Without an onChange callback or codec, no property messages follow, so the
|
|
231
|
+
// value must travel inline (as in send()) or the receiver keeps the stale element
|
|
232
|
+
this.put({state: RpcObjectState.CHANGE, value: onChangeRun || afterCodec ? undefined : anAfter});
|
|
233
|
+
await this.doChange(anAfter, aBefore, onChangeRun, afterCodec);
|
|
200
234
|
}
|
|
201
235
|
}
|
|
202
236
|
}
|
|
@@ -0,0 +1,128 @@
|
|
|
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 * as rpc from "vscode-jsonrpc/node";
|
|
17
|
+
import * as fs from "fs";
|
|
18
|
+
import * as path from "path";
|
|
19
|
+
import {RpcObjectData, RpcSendQueue} from "../queue";
|
|
20
|
+
import {ReferenceMap, asRef} from "../../reference";
|
|
21
|
+
import {JavaSender} from "../../java/rpc";
|
|
22
|
+
import {Type} from "../../java";
|
|
23
|
+
import {withMetrics} from "./metrics";
|
|
24
|
+
import {lastParsedProject} from "./last-parsed-project";
|
|
25
|
+
|
|
26
|
+
const JAVA_TYPE_CLASS = "org.openrewrite.java.tree.JavaType$Class";
|
|
27
|
+
|
|
28
|
+
type ExportedTypes = typeof import("../../javascript/package-exported-types.js")["exportedTypes"];
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Handle a DependencyTypes request: resolve one dependency, named by its npm coordinate, against
|
|
32
|
+
* the most recently parsed project's node_modules, enumerate its public types, and stream them
|
|
33
|
+
* back terminated by END_OF_OBJECT. Built once, returned in {@code batchSize} slices across the
|
|
34
|
+
* client's repeated pulls. A {@code null} version names a TypeScript runtime declaration file
|
|
35
|
+
* (e.g. {@code lib.dom}) instead of an installed package.
|
|
36
|
+
*/
|
|
37
|
+
export class DependencyTypes {
|
|
38
|
+
constructor(private readonly name: string,
|
|
39
|
+
private readonly version?: string | null) {
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
static handle(connection: rpc.MessageConnection, batchSize: number, metricsCsv?: string): void {
|
|
43
|
+
// Keyed by coordinate: holds the remaining slices of an in-flight
|
|
44
|
+
// response across the client's repeated pulls. Evicted once fully drained.
|
|
45
|
+
const pending = new Map<string, RpcObjectData[]>();
|
|
46
|
+
|
|
47
|
+
connection.onRequest(
|
|
48
|
+
new rpc.RequestType<DependencyTypes, RpcObjectData[], Error>("DependencyTypes"),
|
|
49
|
+
withMetrics<DependencyTypes, RpcObjectData[]>(
|
|
50
|
+
"DependencyTypes",
|
|
51
|
+
metricsCsv,
|
|
52
|
+
(context) => async (request) => {
|
|
53
|
+
const version = request.version ?? null;
|
|
54
|
+
context.target = version ? `${request.name}@${version}` : request.name;
|
|
55
|
+
|
|
56
|
+
const key = JSON.stringify([request.name, version]);
|
|
57
|
+
if (!pending.has(key)) {
|
|
58
|
+
// Dynamic import to break the circular dependency with the javascript module.
|
|
59
|
+
const {exportedTypes} = await import("../../javascript/package-exported-types.js");
|
|
60
|
+
const types = version !== null
|
|
61
|
+
? packageTypes(request.name, exportedTypes)
|
|
62
|
+
: exportedTypes([runtimeLibFile(request.name)], []);
|
|
63
|
+
|
|
64
|
+
const q = new RpcSendQueue(new ReferenceMap(), JAVA_TYPE_CLASS, false);
|
|
65
|
+
const sender = new JavaSender();
|
|
66
|
+
// FQNs this dependency defines, sent before the types so the caller can tell
|
|
67
|
+
// defined types from references up front, then the types themselves.
|
|
68
|
+
await q.sendList(types.map(t => Type.FullyQualified.getFullyQualifiedName(t)), undefined, s => s);
|
|
69
|
+
await q.sendList(
|
|
70
|
+
types.map(t => asRef(t)),
|
|
71
|
+
undefined,
|
|
72
|
+
t => Type.signature(t),
|
|
73
|
+
async t => {
|
|
74
|
+
await sender.visitType(t, q);
|
|
75
|
+
}
|
|
76
|
+
);
|
|
77
|
+
pending.set(key, q.finish());
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const data = pending.get(key)!;
|
|
81
|
+
const batch = data.splice(0, batchSize);
|
|
82
|
+
if (data.length === 0) {
|
|
83
|
+
pending.delete(key);
|
|
84
|
+
}
|
|
85
|
+
return batch;
|
|
86
|
+
}
|
|
87
|
+
)
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function projectNodeModules(): string {
|
|
93
|
+
const project = lastParsedProject();
|
|
94
|
+
if (!project) {
|
|
95
|
+
throw new Error("No project has been parsed yet; cannot resolve a dependency coordinate");
|
|
96
|
+
}
|
|
97
|
+
return path.join(project, "node_modules");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** The installed package's own types, falling back to its DefinitelyTyped package when it ships none. */
|
|
101
|
+
function packageTypes(name: string, exportedTypes: ExportedTypes): Type.FullyQualified[] {
|
|
102
|
+
const nodeModules = projectNodeModules();
|
|
103
|
+
const pkgDir = path.join(nodeModules, ...name.split("/"));
|
|
104
|
+
if (!fs.existsSync(pkgDir)) {
|
|
105
|
+
throw new Error(`Package '${name}' is not installed under ${nodeModules}`);
|
|
106
|
+
}
|
|
107
|
+
const types = exportedTypes([pkgDir], []);
|
|
108
|
+
if (types.length > 0) {
|
|
109
|
+
return types;
|
|
110
|
+
}
|
|
111
|
+
// @scope/pkg -> @types/scope__pkg
|
|
112
|
+
const typesDir = path.join(nodeModules, "@types",
|
|
113
|
+
name.startsWith("@") ? name.slice(1).replace("/", "__") : name);
|
|
114
|
+
return fs.existsSync(typesDir) ? exportedTypes([typesDir], []) : types;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** A runtime declaration file (e.g. lib.dom.d.ts): the project's installed TypeScript first, else the engine's own. */
|
|
118
|
+
function runtimeLibFile(name: string): string {
|
|
119
|
+
for (const lib of [
|
|
120
|
+
path.join(projectNodeModules(), "typescript", "lib", `${name}.d.ts`),
|
|
121
|
+
path.join(path.dirname(require.resolve("typescript")), `${name}.d.ts`),
|
|
122
|
+
]) {
|
|
123
|
+
if (fs.existsSync(lib)) {
|
|
124
|
+
return lib;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
throw new Error(`No TypeScript declaration file for '${name}'`);
|
|
128
|
+
}
|
package/src/rpc/request/index.ts
CHANGED
|
@@ -0,0 +1,27 @@
|
|
|
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
|
+
// ParseProject records the most recently parsed project here; DependencyTypes
|
|
18
|
+
// resolves npm coordinates against its node_modules tree.
|
|
19
|
+
let projectDir: string | undefined;
|
|
20
|
+
|
|
21
|
+
export function setLastParsedProject(dir: string): void {
|
|
22
|
+
projectDir = dir;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function lastParsedProject(): string | undefined {
|
|
26
|
+
return projectDir;
|
|
27
|
+
}
|
|
@@ -21,6 +21,7 @@ import {SourceFile} from "../../tree";
|
|
|
21
21
|
import {Parsers} from "../../parser";
|
|
22
22
|
import {withMetrics} from "./metrics";
|
|
23
23
|
import {replaceMarkerByKind} from "../../markers";
|
|
24
|
+
import {setLastParsedProject} from "./last-parsed-project";
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
27
|
* Response item with object ID and source file type for proper deserialization.
|
|
@@ -74,6 +75,7 @@ export class ParseProject {
|
|
|
74
75
|
const {DEFAULT_EXCLUSIONS, ProjectParser} = await import("../../javascript/index.js");
|
|
75
76
|
|
|
76
77
|
const projectPath = path.resolve(request.projectPath);
|
|
78
|
+
setLastParsedProject(projectPath);
|
|
77
79
|
const exclusions = request.exclusions ?? DEFAULT_EXCLUSIONS;
|
|
78
80
|
// Use relativeTo if specified, otherwise default to projectPath
|
|
79
81
|
const relativeTo = request.relativeTo ? path.resolve(request.relativeTo) : projectPath;
|
package/src/rpc/rewrite-rpc.ts
CHANGED
|
@@ -19,6 +19,7 @@ import {Cursor, isSourceFile, isTree, rootCursor, SourceFile, Tree} from "../tre
|
|
|
19
19
|
import {Recipe} from "../recipe";
|
|
20
20
|
import {SnowflakeId} from "@akashrajpurohit/snowflake-id";
|
|
21
21
|
import {
|
|
22
|
+
DependencyTypes,
|
|
22
23
|
Generate,
|
|
23
24
|
GenerateResponse,
|
|
24
25
|
GetObject,
|
|
@@ -142,6 +143,7 @@ export class RewriteRpc {
|
|
|
142
143
|
PrepareRecipe.handle(this.connection, marketplace, preparedRecipes, options.metricsCsv);
|
|
143
144
|
Parse.handle(this.connection, this.localObjects, options.metricsCsv);
|
|
144
145
|
ParseProject.handle(this.connection, this.localObjects, options.metricsCsv);
|
|
146
|
+
DependencyTypes.handle(this.connection, options?.batchSize || 1000, options.metricsCsv);
|
|
145
147
|
Print.handle(this.connection, getObject, options.logger, options.metricsCsv);
|
|
146
148
|
InstallRecipes.handle(this.connection, options.recipeInstallDir ?? ".rewrite", marketplace, recipeOrigin, options.logger, options.metricsCsv);
|
|
147
149
|
|