ProvenPin Install

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

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.