jscodeshift rename and require-to-import transform: working versions (tested 2026-10-17)
Run jscodeshift programmatically with the tsx parser to rename an imported identifier and its usages and to convert require calls into ES imports, asserting exact output source offline.
passed4 passed · 0 failed2026-10-17 19:19 UTC- Status
- passed
- Last passed
- 2026-10-17 19:19 UTC
- Tests
- 4 / 4
- Language
- TypeScript
- Runtime
- node v22.23.3
pinned versions3
jscodeshift@17.4.0@types/node@26.6.5tsx@4.23.15
run it
npm install --no-audit --no-fund --ignore-scripts=false npm test
test environment
node v22.23.3 linux 6.17.0-1022-azure x64 runner: github-actions 39061946720
The code
Exactly the files that passed. Your agent can pull them with get_recipe("jscodeshift-transform-test") (install), or see them on GitHub.
package.json
{
"name": "jscodeshift-transform-test",
"private": true,
"type": "module",
"scripts": {
"test": "node --import tsx --test test/*.test.ts"
},
"dependencies": {
"jscodeshift": "17.4.0"
},
"devDependencies": {
"@types/node": "26.6.5",
"tsx": "4.23.15"
}
}
src/transform.ts
import jscodeshift from "jscodeshift";
import type { API, FileInfo } from "jscodeshift";
const PARSER = "tsx";
export type RenameOptions = {
from: string;
to: string;
};
export default function transform(
file: FileInfo,
_api: API,
options: RenameOptions,
): string {
const j = jscodeshift.withParser(PARSER);
const root = j(file.source);
const { from, to } = options;
root.find(j.Identifier).forEach((path) => {
if (path.node.name !== from) return;
const parent = path.parentPath ? path.parentPath.node : null;
if (parent) {
if (
parent.type === "MemberExpression" &&
parent.property === path.node &&
!parent.computed
) {
return;
}
if (
(parent.type === "ObjectProperty" || parent.type === "Property") &&
parent.key === path.node &&
!parent.computed
) {
return;
}
}
path.node.name = to;
});
root.find(j.VariableDeclaration).forEach((path) => {
const declaration = path.node;
if (declaration.declarations.length !== 1) return;
const declarator = declaration.declarations[0];
const init = declarator.init;
if (init == null || init.type !== "CallExpression") return;
if (init.callee.type !== "Identifier" || init.callee.name !== "require") return;
if (init.arguments.length !== 1) return;
const source = init.arguments[0];
if (source.type !== "StringLiteral" && source.type !== "Literal") return;
if (typeof source.value !== "string") return;
const specifiers = [];
const id = declarator.id;
if (id.type === "Identifier") {
specifiers.push(j.importDefaultSpecifier(j.identifier(id.name)));
} else if (id.type === "ObjectPattern") {
for (const property of id.properties) {
if (property.type !== "ObjectProperty" && property.type !== "Property") {
return;
}
if (property.computed) return;
if (property.key.type !== "Identifier") return;
const importedName = property.key.name;
const localName =
property.value.type === "Identifier"
? property.value.name
: importedName;
specifiers.push(
j.importSpecifier(j.identifier(importedName), j.identifier(localName)),
);
}
} else {
return;
}
j(path).replaceWith(j.importDeclaration(specifiers, source));
});
return root.toSource();
}
export function runTransform(source: string, options: RenameOptions): string {
const api = {} as API;
return transform({ path: "input.tsx", source }, api, options);
}
test/transform.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import { runTransform } from "../src/transform.ts";
test("renames an imported identifier and its usages", () => {
const input = [
"import { oldName } from './mod';",
"const value = oldName(1, 2);",
"",
].join("\n");
const output = runTransform(input, { from: "oldName", to: "newName" });
assert.equal(
output,
[
"import { newName } from './mod';",
"const value = newName(1, 2);",
"",
].join("\n"),
);
});
test("converts a require call to a default import and renames usages", () => {
const input = [
"const oldName = require('./mod');",
"oldName('x');",
"",
].join("\n");
const output = runTransform(input, { from: "oldName", to: "newName" });
assert.equal(
output,
[
"import newName from './mod';",
"newName('x');",
"",
].join("\n"),
);
});
test("converts a destructured require to a named import", () => {
const input = [
"const { oldName } = require('./mod');",
"oldName();",
"",
].join("\n");
const output = runTransform(input, { from: "oldName", to: "newName" });
assert.equal(
output,
[
"import { oldName as newName } from './mod';",
"newName();",
"",
].join("\n"),
);
});
test("leaves non-computed member property names untouched", () => {
const input = [
"import { oldName } from './mod';",
"const value = api.oldName;",
"oldName();",
"",
].join("\n");
const output = runTransform(input, { from: "oldName", to: "newName" });
assert.equal(
output,
[
"import { newName } from './mod';",
"const value = api.oldName;",
"newName();",
"",
].join("\n"),
);
});
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
~/.cursor/mcp.json
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }.vscode/mcp.json
{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }~/.codeium/windsurf/mcp_config.json
{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }MCP settings
{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.