> ## Documentation Index
> Fetch the complete documentation index at: https://docs.codemod.com/llms.txt
> Use this file to discover all available pages before exploring further.

# JavaScript and TypeScript

> Locate, add, remove, and rename JavaScript and TypeScript imports, match file style, and resolve bindings with @jssg/utils.

These helpers locate, add, remove, and rename imports in JavaScript and TypeScript programs, copy a file's quote and indent, and resolve a use back to the binding that defines it.

```typescript theme={null}
import {
  getImport,
  getAllImports,
  addImport,
  removeImport,
  updateImport,
} from "@jssg/utils/javascript/imports";
```

<Info>
  These utilities work with JavaScript, TypeScript, JSX, and TSX ASTs. They handle both ESM (`import`) and CJS (`require()`) formats automatically.
</Info>

## `getImport`

Locate an import of a given module and return its alias/identifier node.

**Parameters**

<ParamField body="program" type="SgNode<T, 'program'>" required>
  The root program node to search within.
</ParamField>

<ParamField body="options" type="GetImportOptions" required>
  What to look for:

  * `{ type: "default", from: string }` — Find the default import from a module.
  * `{ type: "named", name: string, from: string }` — Find a specific named import from a module.
</ParamField>

**Returns**

`GetImportResult<T> | null` — An object containing:

| Field | Type | Description |
| - | - | - |
| `alias` | `string` | The local name used at call sites (e.g., `baz` in `import { foo as baz }`) |
| `isNamespace` | `boolean` | `true` if the import is `import * as xyz from '...'` |
| `moduleType` | `"esm" \| "cjs"` | Whether the import is ESM (`import`) or CJS (`require()`) |
| `node` | `SgNode<T, "identifier">` | The identifier AST node for the local binding |

Returns `null` if the import is not found.

**Supported Import Shapes**

| Format | Example |
| - | - |
| ESM default | `import foo from "module"` |
| ESM named | `import { bar } from "module"` |
| ESM aliased | `import { bar as baz } from "module"` |
| ESM namespace | `import * as foo from "module"` |
| ESM bare | `import "module"` — no binding, so `getImport` returns `null`. Remove it with `removeImport` and `removeSideEffectForms: true`. |
| CJS default | `const foo = require("module")` |
| CJS destructured | `const { bar } = require("module")` |
| CJS aliased | `const { bar: baz } = require("module")` |
| Dynamic import | `const foo = await import("module")` |

**Example**

```typescript theme={null}
import type { Codemod } from "codemod:ast-grep";
import type TSX from "codemod:ast-grep/langs/tsx";
import { getImport } from "@jssg/utils/javascript/imports";

const codemod: Codemod<TSX> = async (root) => {
  const rootNode = root.root();

  // Find the default import of "express"
  const expressImport = getImport(rootNode, { type: "default", from: "express" });
  if (!expressImport) return null;

  // expressImport.alias is the local name (e.g., "app" from `import app from 'express'`)
  // expressImport.moduleType tells you if it's "esm" or "cjs"
  console.log(`Express imported as: ${expressImport.alias} (${expressImport.moduleType})`);

  // Find a named import
  const useStateImport = getImport(rootNode, { type: "named", name: "useState", from: "react" });
  if (useStateImport) {
    console.log(`useState aliased as: ${useStateImport.alias}`);
  }

  return null;
};

export default codemod;
```

***

## `getAllImports`

Locate imports of a given module and return their alias/identifier nodes.

**Parameters**

<ParamField body="program" type="SgNode<T, 'program'>" required>
  The root program node to search within.
</ParamField>

<ParamField body="options" type="GetImportOptions" required>
  What to look for:

  * `{ type: "default", from: string }` — Find all default imports from a module.
  * `{ type: "named", name: string, from: string }` — Find all instances of a specific named import from a module.
</ParamField>

**Returns**

`ResolvedImport<T>[]` — An array containing:

| Field | Type | Description |
| - | - | - |
| `alias` | `string` | The local name used at call sites (e.g., `baz` in `import { foo as baz }`) |
| `isNamespace` | `boolean` | `true` if the import is `import * as xyz from '...'` |
| `moduleType` | `"esm" \| "cjs"` | Whether the import is ESM (`import`) or CJS (`require()`) |
| `node` | `SgNode<T, "identifier">` | The identifier AST node for the local binding |

Returns an empty array if no imports are found.

**Example**

```typescript theme={null}
import type { Transform } from "codemod:ast-grep";
import type TSX from "codemod:ast-grep/langs/tsx";
import { getAllImports } from "@jssg/utils/javascript/imports";

const transform: Transform<TSX> = async (root) => {
  const rootNode = root.root();

  // Find all default imports of "express"
  const expressImports = getAllImports(rootNode, { type: "default", from: "express" });
  for (const imp of expressImports) {
    console.log(`Express imported as: ${imp.alias} (${imp.moduleType})`);
  }

  // Find all named imports
  const useStateImports = getAllImports(rootNode, { type: "named", name: "useState", from: "react" });
  for (const imp of useStateImports) {
    console.log(`useState aliased as: ${imp.alias}`);
  }

  return null;
};

export default transform;
```

***

## `listImports`

List the import statements and `require()` / `import()` declarations in a file. `getAllImports` asks for one exported name. `listImports` answers "what does this file import from these packages?", which is the question a barrel split asks (`lodash`, `date-fns`, `rxjs`, `@mui/material`).

**Parameters**

<ParamField body="program" type="SgNode<T, 'program'>" required>
  The root program node.
</ParamField>

<ParamField body="options" type="ListImportsOptions">
  `{ from?: string | readonly string[] }`. When set, only statements whose module specifier is exactly one of those strings are returned. `"mod"` does not match `"mod/extra"`. Quotes on the specifier do not matter.
</ParamField>

**Returns**

An array of statements in source order. Each record has:

| Field | Description |
| - | - |
| `node` | The statement node |
| `source` | Unquoted module specifier |
| `quote` | The quote character used for that specifier |
| `hasSemicolon` | Whether the statement text ends with `;` |
| `moduleType` | `"esm"` or `"cjs"` |
| `isTypeOnly` | True only for a statement-level `import type` |
| `defaultName` | Local name of a default import, or of `const name = require(...)` / `await import(...)`. Otherwise `null` |
| `namespaceName` | Local name of `import * as name` or of the `import().then(name => ...)` callback. Otherwise `null`. A `.catch` or `.finally` callback is not a binding. |
| `specifiers` | Named bindings: `importedName`, `localName`, `isTypeOnly`, `node` |

`specifiers[].isTypeOnly` is true for both `import type { Foo }` and `import { type Foo }`. The statement flag `isTypeOnly` is only the first form.

The same statements `removeImport` can edit are listed. A multi-declarator `const a = require("mod"), b = 1` is omitted, so a later deletion cannot drop `b`. `const x = import("mod")` without `await` is omitted. `import("mod").then(...)` is included.

***

## `addImport`

Add an import to the program. Smart behavior:

* **Skips** if the import already exists
* **Merges** named specifiers into an existing import statement from the same source
* **Creates** a new import statement otherwise
* Inserts after the last existing import (or at file start if no imports exist)
* A **new** statement copies the quote and semicolon of the first module specifier already in the file. When the file has none, it copies the first string literal and the first semicolon-style statement, and keeps `'` and `;` only when those are absent. A merge does not rewrite the existing clause.
* A named specifier with `isTypeOnly: true` is written as inline `type` (`import { type Foo }`), not `import type`. A value specifier is not merged into an existing `import type` statement. When a value import from the same module already exists, the specifier merges into that statement even if an `import type` line comes first. Otherwise it becomes a second statement. Type-only specifiers merged into `import type` do not repeat the `type` keyword.
* A type-only binding does not count as the value. `import { type Foo }` drops the `type` modifier, and `import type { Foo, Bar }` becomes `import { Foo, type Bar }`, when the local name matches. A different local name (`import type { Foo as F }` plus a value `Foo`) stays a separate statement.

**Parameters**

<ParamField body="program" type="SgNode<T, 'program'>" required>
  The root program node.
</ParamField>

<ParamField body="options" type="AddImportOptions" required>
  One of:

  * `{ type: "default", name: string, from: string, moduleType?: "esm" | "cjs" }` — Add a default import.
  * `{ type: "namespace", name: string, from: string }` — Add a namespace import (`import * as name`).
  * `{ type: "named", specifiers: Array<{ name: string, alias?: string, isTypeOnly?: boolean }>, from: string, moduleType?: "esm" | "cjs" }` — Add named imports. `isTypeOnly` emits an inline `type` modifier.
</ParamField>

**Returns**

`Edit | null` — An edit to apply via `rootNode.commitEdits()`, or `null` if the import already exists.

**Example**

```typescript theme={null}
import type { Codemod, Edit } from "codemod:ast-grep";
import type TSX from "codemod:ast-grep/langs/tsx";
import { addImport } from "@jssg/utils/javascript/imports";

const codemod: Codemod<TSX> = async (root) => {
  const rootNode = root.root();
  const edits: Edit[] = [];

  // Add a default import (skipped if already present)
  const defaultEdit = addImport(rootNode, {
    type: "default",
    name: "React",
    from: "react",
  });
  if (defaultEdit) edits.push(defaultEdit);

  // Add named imports (merges into existing `import { ... } from 'react'` if present)
  const namedEdit = addImport(rootNode, {
    type: "named",
    specifiers: [{ name: "useState" }, { name: "useEffect" }],
    from: "react",
  });
  if (namedEdit) edits.push(namedEdit);

  // Add a CJS require
  const cjsEdit = addImport(rootNode, {
    type: "default",
    name: "express",
    from: "express",
    moduleType: "cjs",
  });
  if (cjsEdit) edits.push(cjsEdit);

  return edits.length > 0 ? rootNode.commitEdits(edits) : null;
};

export default codemod;
```

***

## `removeImport`

Remove an import from the program. Smart behavior:

* **Default/namespace**: Removes the entire import statement.
* **Named (multiple specifiers)**: Removes only the specified specifiers, keeping the rest.
* **Named (last specifier)**: Removes the entire import statement.
* Handles both ESM and CJS formats.

**Parameters**

<ParamField body="program" type="SgNode<T, 'program'>" required>
  The root program node.
</ParamField>

<ParamField body="options" type="RemoveImportOptions" required>
  One of:

  * `{ type: "default", from: string, removeSideEffectForms?: boolean }` — Remove the default import from a module. Set `removeSideEffectForms` to also remove a side-effect-only `import "module"` or `require("module")` when no binding import exists.
  * `{ type: "namespace", from: string }` — Remove the namespace import from a module.
  * `{ type: "named", specifiers: string[], from: string }` — Remove specific named imports.
</ParamField>

**Returns**

`Edit | null` — An edit to apply via `rootNode.commitEdits()`, or `null` if the import was not found.

**Example**

```typescript theme={null}
import type { Codemod, Edit } from "codemod:ast-grep";
import type TSX from "codemod:ast-grep/langs/tsx";
import { removeImport } from "@jssg/utils/javascript/imports";

const codemod: Codemod<TSX> = async (root) => {
  const rootNode = root.root();
  const edits: Edit[] = [];

  // Remove a named import (only removes "PropTypes", keeps other specifiers)
  const removeEdit = removeImport(rootNode, {
    type: "named",
    specifiers: ["PropTypes"],
    from: "react",
  });
  if (removeEdit) edits.push(removeEdit);

  return edits.length > 0 ? rootNode.commitEdits(edits) : null;
};

export default codemod;
```

***

## `updateImport`

Replace named specifiers of an existing import or `require` in place. The whole clause is one edit, so the rewrite does not overlap other specifier edits.

* `import { foo } from "mod"` with `{ name: "foo", to: "bar" }` becomes `import { bar } from "mod"`.
* An existing local alias is kept: `import { foo as f }` becomes `import { bar as f }`. Pass `alias` to override that local name.
* Works for ESM named imports, destructured `require()`, and destructured dynamic `import()`.
* If `to` is already imported in the same clause, the old specifier is dropped instead of duplicated.

**Parameters**

<ParamField body="program" type="SgNode<T, 'program'>" required>
  The root program node.
</ParamField>

<ParamField body="options" type="UpdateImportOptions" required>
  Named imports only:

  * `{ type: "named", from: string, specifiers: Array<{ name: string, to: string, alias?: string }> }` — Rename `name` to `to` in the import from `from`.
</ParamField>

**Returns**

`Edit | null` — An edit to apply via `rootNode.commitEdits()`, or `null` if none of the named specifiers is found or `specifiers` is empty.

**Example**

```typescript theme={null}
import type { Codemod, Edit } from "codemod:ast-grep";
import type TSX from "codemod:ast-grep/langs/tsx";
import { updateImport } from "@jssg/utils/javascript/imports";

const codemod: Codemod<TSX> = async (root) => {
  const rootNode = root.root();

  const edit = updateImport(rootNode, {
    type: "named",
    from: "mod",
    specifiers: [{ name: "foo", to: "bar" }],
  });

  return edit ? rootNode.commitEdits([edit]) : null;
};

export default codemod;
```

***

## `stringToExactRegexString`

Escape a string and return a regex that matches only that exact text. The result is wrapped in `^...$`, with regex metacharacters escaped.

```typescript theme={null}
import { stringToExactRegexString } from "@jssg/utils/javascript/imports";

// Returns "^my\\.module$"
const regex = stringToExactRegexString("my.module");
```

***

## `getFileStyle`

Quote, semicolon, and indent of a JavaScript or TypeScript file.

```typescript theme={null}
import { getFileStyle } from "@jssg/utils/javascript/style";
```

`addImport` uses the quote and semicolon when it creates a statement. Call `getFileStyle` yourself when you insert other code.

| Field | Rule |
| - | - |
| `quote` | Quote of the first `import`, `export … from`, `require()`, or `import()` specifier. When the file has none, quote of the first string literal. `'` when the file has neither. |
| `semicolon` | `";"` when that specifier's statement ends with one, otherwise `""`. When the file has no specifier, the same rule applies to the first semicolon-style statement (`const`, `return`, `export { }`, a type alias, and similar). A function or class on its own does not count. `";"` when the file has no such statement. |
| `indentUnit` | One indent level, from the first non-comment statement in a block. A column-zero comment is skipped. Tabs stay tabs. Two spaces when no block shows a deeper line. |

The first module specifier wins. Later imports, and strings elsewhere in the file, are ignored while a specifier exists. This does not read Prettier or EditorConfig.

## `getLineIndent`

Leading spaces or tabs on the line a node starts on. Returns `""` when any other character sits before the node on that line, so `foo(bar)` and an indented `foo(bar)` do not treat the preceding code as indent. The node's position is a UTF-8 byte offset, so non-ASCII text earlier in the file does not shift the line. A carriage return is not part of the indent.

```typescript theme={null}
import { getLineIndent } from "@jssg/utils/javascript/style";

const indent = getLineIndent(rootNode.text(), statement);
```

## `indentText`

Prefix every non-empty line. Blank lines stay blank, so an inserted block does not grow trailing whitespace.

```typescript theme={null}
import { indentText } from "@jssg/utils/javascript/style";

const body = indentText("server.listen()\n", indent);
```

***

## `importBindingOf`

The import that defines this use, or `null` when the name is local. This is the opposite of `getImport`: the call site is known and the package is not.

```typescript theme={null}
import { importBindingOf } from "@jssg/utils/javascript/bindings";
```

| Field | Meaning |
| - | - |
| `importedName` | Exported name. `"default"` for a default import. `"*"` for `import * as`. |
| `localName` | Name at the use site. `import { setup as create }` reports local name `create`. |
| `source` | Module specifier of the import in the file that contains the use. |

`setup()` is rewritten only when `importedName` is `"setup"` and `source` is the package you mean. A parameter named `setup`, or the same name imported from another package, returns a different binding or `null`.

The lookup stops at the import in the file that contains the use. `import { server } from "./browser"` reports source `"./browser"`, not the package `./browser` imported from. `ns.graphql` after `import * as ns` is not resolved; call this on `ns` and read the property text yourself.

Returns `null` when semantic analysis is unavailable. `definition()` can throw in that case; this function does not.

## `resolveDefinition`

The node that defines this use, following one import into another project file.

* A local declaration returns that node.
* An import the workspace can resolve returns that file's definition. In the semantic API this kind is `"external"`, which means another project file, not a package on npm.
* An unresolved package import returns the import specifier.
* `const a = setup(); const b = a` resolves `b` to `b`, not to `setup()`. A second hop is out of scope.

Returns `null` when semantic analysis is unavailable.

## `resolvesToFactory`

True when the binding is a variable initialized by a direct call, or `new`, of `importedName` from one of `sources`.

```typescript theme={null}
import { resolvesToFactory } from "@jssg/utils/javascript/bindings";

if (resolvesToFactory(receiver, "setupServer", ["msw/node", "msw/native"])) {
  // receiver.stop(), not some other object that also has stop()
}
```

`new QueryClient()`, `new PrismaClient()`, and `express()` are the same shape. One hop includes a binding imported from another project file (`export const server = setupServer()` in `browser.ts`, `server.listen()` in the file that imports it).

These stay false:

* `const { listen } = setupServer()`
* `lib.setupServer()`
* `const a = setupServer(); const b = a`
* a shadowed local with the same spelling, such as `const server = app.listen()` inside a function

## `renameReferences`

Edits that rename references of one binding in `currentFile`. Pass `root.filename()`.

Shadowed names stay. `references()` only returns this binding, so a parameter spelled the same as the import is not renamed. Comments, string literals, and property names such as `record.cleanUrl` are not references and stay. A shorthand property is an identifier, so `{ cleanUrl }` becomes `{ getCleanUrlString }` and the key changes. The import clause is not renamed; use `updateImport` for that.

Workspace `references()` also returns other files. Those edits are omitted. Writing them onto the file currently being committed targets the wrong tree. Cross-file renames stay with the caller, via `root.write` on the other file.

Returns `[]` when semantic analysis is unavailable.

***

## Common Patterns

### Verify Import Before Transforming

Always verify that a symbol is imported from the expected package before transforming its usage:

```typescript theme={null}
const reactImport = getImport(rootNode, { type: "named", name: "useState", from: "react" });
if (!reactImport) return null; // Not from React, skip

// Now safe to transform useState usage
// Handles aliased imports like `import { useState as myState }`
const alias = reactImport.alias;
```

### Replace One Import With Another

Rename a named specifier in the same module with `updateImport`:

```typescript theme={null}
const edit = updateImport(rootNode, {
  type: "named",
  from: "mod",
  specifiers: [{ name: "oldUtil", to: "newUtil" }],
});
```

Switch packages with `removeImport` and `addImport`:

```typescript theme={null}
const edits: Edit[] = [];

const removeEdit = removeImport(rootNode, {
  type: "named",
  specifiers: ["oldUtil"],
  from: "old-pkg",
});
if (removeEdit) edits.push(removeEdit);

const addEdit = addImport(rootNode, {
  type: "named",
  specifiers: [{ name: "newUtil" }],
  from: "new-pkg",
});
if (addEdit) edits.push(addEdit);
```

### Match Existing Module Style

`getImport` automatically detects the module type. Use `moduleType` when adding imports to match the existing style:

```typescript theme={null}
const existing = getImport(rootNode, { type: "default", from: "express" });
const moduleType = existing?.moduleType ?? "esm"; // Match existing style

const edit = addImport(rootNode, {
  type: "named",
  specifiers: [{ name: "Router" }],
  from: "express",
  moduleType,
});
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.