Replace all instances of a specific import via jscodeshift

2.1k views Asked by At

okay so I have code that looks like this:

import { wait } from "@testing-library/react";

describe("MyTest", () => {
  it("should wait", async () => {
    await wait(() => {
      console.log("Done");
    });
  });
});

I want to change that import member wait to be waitFor. I'm able to change it in the AST like so:

    source
      .find(j.ImportDeclaration)
      .filter((path) => path.node.source.value === "@testing-library/react")
      .find(j.ImportSpecifier)
      .filter((path) => path.node.imported.name === "wait")
      .replaceWith(j.importSpecifier(j.identifier("waitFor")))
      .toSource()

However, the outputed code will look as follows:

import { waitFor } from "@testing-library/react";

describe("MyTest", () => {
  it("should wait", async () => {
    await wait(() => {
      console.log("Done");
    });
  });
});

I'm looking for a way to change all subsequent usages of that import to match the new name

Is this possible with jscodeshift?

1

There are 1 answers

0
Dave B. On

You can do it by visiting all the CallExpression nodes, filtering for those with the name you're targeting ("wait") and replacing them with new nodes.

To find the relevant nodes you can either loop through the collection returned by jscodeshift's find() method and add your logic there, or you can give find() a second argument. This should be a predicate used for filtering:

const isWaitExpression = (node) =>
    node.callee && node.callee.name === "wait";

// I've used "root" where you used "source"
root
    .find(j.CallExpression, isWaitExpression)

Then you can replace those nodes with replaceWith():

const replaceExpression = (path, j) =>
    j.callExpression(j.identifier("waitFor"), path.node.arguments);

root
   .find(j.CallExpression, isWaitExpression)
   .replaceWith((path) => replaceExpression(path, j));

It can look a bit confusing, but to create a CallExpression node, you call the callExpression() method from the api. Note the camel-casing there.

All together, a transformer that renames "wait" from RTL to "waitFor" — both the named import declaration and every instance where it's called in the file — can be done like this:

const isWaitExpression = (node) => node.callee && node.callee.name === "wait";

const replaceExpression = (path, j) =>
  j.callExpression(j.identifier("waitFor"), path.node.arguments);

export default function transformer(file, api) {
  const j = api.jscodeshift;
  const root = j(file.source);

  root
    .find(j.ImportDeclaration)
    .filter((path) => path.node.source.value === "@testing-library/react")
    .find(j.ImportSpecifier)
    .filter((path) => path.node.imported.name === "wait")
    .replaceWith(j.importSpecifier(j.identifier("waitFor")));

  root
    .find(j.CallExpression, isWaitExpression)
    .replaceWith((path) => replaceExpression(path, j));

  return root.toSource();
}

And here's a link to it on AST explorer