Skip to main content
@jssg/utils provides reusable utility functions for JSSG codemods. These helpers handle common import manipulation tasks that are tedious and error-prone when done manually with AST queries.

Installation

Add @jssg/utils to your codemod project:
package.json
{
  "dependencies": {
    "@jssg/utils": "latest"
  }
}

Import

import { getImport, addImport, removeImport } from "@jssg/utils/javascript/imports";
These utilities work with JavaScript, TypeScript, JSX, and TSX ASTs. They handle both ESM (import) and CJS (require()) formats automatically.

getImport

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

Parameters

program
SgNode<T, 'program'>
required
The root program node to search within.
options
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.

Returns

GetImportResult<T> | null — An object containing:
FieldTypeDescription
aliasstringThe local name used at call sites (e.g., baz in import { foo as baz })
isNamespacebooleantrue if the import is import * as xyz from '...'
moduleType"esm" | "cjs"Whether the import is ESM (import) or CJS (require())
nodeSgNode<T, "identifier">The identifier AST node for the local binding
Returns null if the import is not found.

Supported Import Shapes

FormatExample
ESM defaultimport foo from "module"
ESM namedimport { bar } from "module"
ESM aliasedimport { bar as baz } from "module"
ESM namespaceimport * as foo from "module"
ESM bareimport "module"
CJS defaultconst foo = require("module")
CJS destructuredconst { bar } = require("module")
CJS aliasedconst { bar: baz } = require("module")
Dynamic importconst foo = await import("module")

Example

import type { Transform } from "codemod:ast-grep";
import type TSX from "codemod:ast-grep/langs/tsx";
import { getImport } from "@jssg/utils/javascript/imports";

const transform: Transform<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 transform;

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)

Parameters

program
SgNode<T, 'program'>
required
The root program node.
options
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 }>, from: string, moduleType?: "esm" | "cjs" } — Add named imports.

Returns

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

Example

import type { Transform, Edit } from "codemod:ast-grep";
import type TSX from "codemod:ast-grep/langs/tsx";
import { addImport } from "@jssg/utils/javascript/imports";

const transform: Transform<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 transform;

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

program
SgNode<T, 'program'>
required
The root program node.
options
RemoveImportOptions
required
One of:
  • { type: "default", from: string } — Remove the default import from a module.
  • { type: "namespace", from: string } — Remove the namespace import from a module.
  • { type: "named", specifiers: string[], from: string } — Remove specific named imports.

Returns

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

Example

import type { Transform, Edit } from "codemod:ast-grep";
import type TSX from "codemod:ast-grep/langs/tsx";
import { removeImport } from "@jssg/utils/javascript/imports";

const transform: Transform<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 transform;

stringToExactRegexString

Escape a string for use as an exact-match regex pattern. Wraps in ^...$ and escapes special characters.
import { stringToExactRegexString } from "@jssg/utils/javascript/imports";

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

Common Patterns

Verify Import Before Transforming

Always verify that a symbol is imported from the expected package before transforming its usage:
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

const edits: Edit[] = [];

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

// Add new import
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:
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,
});