Skip to content

Commit d176620

Browse files
authored
Add chrome trace format profiling to JavaScript Rewrite RPC and improve type mapping for default function exports (#6046)
1 parent d3671d9 commit d176620

7 files changed

Lines changed: 679 additions & 52 deletions

File tree

rewrite-core/src/main/java/org/openrewrite/rpc/RewriteRpcProcess.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ public class RewriteRpcProcess extends Thread {
5151
@Setter
5252
private boolean trace;
5353

54+
@Setter
55+
private @Nullable Path workingDirectory;
56+
5457
@Nullable
5558
private Process process;
5659

@@ -80,6 +83,9 @@ public void run() {
8083
try {
8184
ProcessBuilder pb = new ProcessBuilder(command);
8285
pb.environment().putAll(environment);
86+
if (workingDirectory != null) {
87+
pb.directory(workingDirectory.toFile());
88+
}
8389
process = pb.start();
8490
} catch (IOException e) {
8591
throw new UncheckedIOException(e);

rewrite-javascript/rewrite/src/javascript/type-mapping.ts

Lines changed: 71 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -173,13 +173,38 @@ export class JavaScriptTypeMapping {
173173
declaringType = this.getType(exprType) as Type.FullyQualified;
174174
} else if (ts.isIdentifier(node.expression)) {
175175
methodName = node.expression.getText();
176-
// For standalone functions, use the symbol's parent or module
177-
const parent = (symbol as any).parent;
178-
if (parent) {
179-
const parentType = this.checker.getDeclaredTypeOfSymbol(parent);
180-
declaringType = this.getType(parentType) as Type.FullyQualified;
176+
// For standalone functions, we need to determine the appropriate declaring type
177+
const exprType = this.checker.getTypeAtLocation(node.expression);
178+
const funcType = this.getType(exprType);
179+
180+
if (funcType && funcType.kind === Type.Kind.Class) {
181+
const fqn = (funcType as Type.Class).fullyQualifiedName;
182+
const lastDot = fqn.lastIndexOf('.');
183+
184+
if (lastDot > 0) {
185+
// For functions from modules, use the module part as declaring type
186+
// Examples:
187+
// - "node.assert" -> declaring type: "node"
188+
// - "@types/lodash.map" -> declaring type: "@types/lodash"
189+
// - "@types/express.express" -> declaring type: "@types/express"
190+
declaringType = {
191+
kind: Type.Kind.Class,
192+
fullyQualifiedName: fqn.substring(0, lastDot)
193+
} as Type.FullyQualified;
194+
} else {
195+
// No dots in the name - the type IS the module itself
196+
// This handles single-name modules like "axios", "lodash" etc.
197+
declaringType = funcType as Type.FullyQualified;
198+
}
181199
} else {
182-
declaringType = Type.unknownType as Type.FullyQualified;
200+
// Try to use the symbol's parent or module
201+
const parent = (symbol as any).parent;
202+
if (parent) {
203+
const parentType = this.checker.getDeclaredTypeOfSymbol(parent);
204+
declaringType = this.getType(parentType) as Type.FullyQualified;
205+
} else {
206+
declaringType = Type.unknownType as Type.FullyQualified;
207+
}
183208
}
184209
} else {
185210
methodName = symbol.getName();
@@ -330,6 +355,30 @@ export class JavaScriptTypeMapping {
330355
if (sourceFile.isDeclarationFile || fileName.includes("node_modules")) {
331356
const packageName = this.extractPackageName(fileName);
332357
if (packageName) {
358+
// Special handling for @types/node - these are Node.js built-in modules
359+
// and should be mapped to "node.*" instead of "@types/node.*"
360+
if (packageName === "@types/node") {
361+
// Extract the module name from the file path
362+
// e.g., /node_modules/@types/node/assert.d.ts -> assert
363+
// e.g., /node_modules/@types/node/fs/promises.d.ts -> fs/promises
364+
const nodeMatch = fileName.match(/node_modules\/@types\/node\/([^.]+)\.d\.ts/);
365+
if (nodeMatch) {
366+
const modulePath = nodeMatch[1];
367+
// For default exports from Node modules, we want the module to be the "class"
368+
// But we still need to include the type name for proper identification
369+
if (typeName === "default" || typeName === modulePath) {
370+
// This is likely the default export, just use the module name
371+
return `node.${modulePath}`;
372+
}
373+
// For named exports, include both module and type name
374+
if (modulePath.includes('/')) {
375+
return `node.${modulePath.replace(/\//g, '.')}.${typeName}`;
376+
}
377+
return `node.${modulePath}.${typeName}`;
378+
}
379+
// Fallback for @types/node types that don't match the pattern
380+
return `node.${typeName}`;
381+
}
333382
return `${packageName}.${typeName}`;
334383
}
335384
}
@@ -392,8 +441,22 @@ export class JavaScriptTypeMapping {
392441
const typesMatch = fileName.match(/node_modules\/@types\/([^/]+)/);
393442
if (typesMatch) {
394443
const packageName = typesMatch[1];
395-
// Replace the module specifier part with @types/package
396-
fullyQualifiedName = fullyQualifiedName.replace(/^[^.]+\./, `@types/${packageName}.`);
444+
// Special handling for @types/node - use "node" prefix instead
445+
if (packageName === "node") {
446+
// Extract the module name from the file path if possible
447+
const nodeMatch = fileName.match(/node_modules\/@types\/node\/([^.]+)\.d\.ts/);
448+
if (nodeMatch) {
449+
const modulePath = nodeMatch[1];
450+
// Replace the module specifier with node.module
451+
fullyQualifiedName = fullyQualifiedName.replace(/^[^.]+\./, `node.${modulePath}.`);
452+
} else {
453+
// Fallback: just use "node" prefix
454+
fullyQualifiedName = fullyQualifiedName.replace(/^[^.]+\./, `node.`);
455+
}
456+
} else {
457+
// Replace the module specifier part with @types/package
458+
fullyQualifiedName = fullyQualifiedName.replace(/^[^.]+\./, `@types/${packageName}.`);
459+
}
397460
}
398461
}
399462
}

0 commit comments

Comments
 (0)