Skip to main content
Utilities for resolving how global API paths should be accessed based on import patterns, and for safely removing or updating bindings.

Overview

These utilities handle the complexity of:
  • Determining how imported functions are accessed locally (namespace, destructured, aliased)
  • Safely removing unused imports while preserving other bindings
  • Updating or replacing import bindings
  • Handling both ES modules and CommonJS

Binding Path Resolution

resolveBindingPath

Resolves how a global API path should be accessed based on the import pattern.
import { resolveBindingPath } from '@nodejs/codemod-utils/ast-grep/resolve-binding-path';

// Example 1: Destructured import
// Given: const { types } = require('node:util');
const path = resolveBindingPath(node, '$.types.isNativeError');
// Returns: 'types.isNativeError'

// Example 2: Namespace import
// Given: const util = require('node:util');
const path = resolveBindingPath(node, '$.types.isNativeError');
// Returns: 'util.types.isNativeError'

// Example 3: Aliased import
// Given: import { types as utilTypes } from 'node:util';
const path = resolveBindingPath(node, '$.types.isNativeError');
// Returns: 'utilTypes.isNativeError'
node
SgNode<Js>
required
The AST node representing the import or require statement. Must be one of:
  • lexical_declaration
  • variable_declarator
  • import_statement
  • import_clause
path
string
required
The expected dotted path to resolve (e.g., ”.types.isNativeError").The.types.isNativeError"). The `` represents the module namespace.
Returns: string | undefined - The local access path that should be used in code, or undefined if the binding is not found
The $ in the path parameter represents the root module namespace and gets replaced with the actual local binding name.

Advanced Examples

// Given: const util = require('node:util');
const path = resolveBindingPath(node, '$.types.isNativeError');
// Returns: 'util.types.isNativeError'

const path2 = resolveBindingPath(node, '$.isDeepStrictEqual');
// Returns: 'util.isDeepStrictEqual'

Binding Removal

removeBinding

Removes a specific binding from imports/requires, or removes the entire statement if it’s the only binding.
import { removeBinding } from '@nodejs/codemod-utils/ast-grep/remove-binding';

// Example 1: Remove one binding from multiple
// Given: const { types, isNativeError } = require('node:util');
const result = removeBinding(node, 'isNativeError');
// Result: { edit: Edit } transforms to: const { types } = require('node:util');

// Example 2: Remove the only binding
// Given: const { isNativeError } = require('node:util');
const result = removeBinding(node, 'isNativeError');
// Result: { lineToRemove: Range } to remove entire statement

// Example 3: Remove namespace binding
// Given: const util = require('node:util');
const result = removeBinding(node, 'util');
// Result: { lineToRemove: Range } to remove entire statement
node
SgNode<Js>
required
The AST node representing the import or require statement
binding
string
required
The name of the binding to remove (e.g., “isNativeError”)
options
object
Optional configuration object
options.usageCheck
object
If provided, the binding will only be removed if it is unused
options.usageCheck.ignoredRanges
Range[]
Array of ranges to ignore when checking for usage
options.root
SgNode<Js>
Custom root node for usage checking. Defaults to the file root.
Returns: { edit?: Edit, lineToRemove?: Range } | undefined
  • If multiple bindings exist: returns { edit } to modify the destructuring pattern
  • If single binding: returns { lineToRemove } with the line range to remove
  • If binding not found: returns undefined

Usage Check Example

const result = removeBinding(node, 'isNativeError', {
  usageCheck: {
    ignoredRanges: [node.range()] // Ignore references in the import itself
  }
});

if (result?.edit) {
  context.applyEdit(result.edit);
} else if (result?.lineToRemove) {
  // Remove the entire import line
  const cleaned = removeLines(sourceCode, [result.lineToRemove]);
  context.writeFile(filePath, cleaned);
}

Binding Updates

updateBinding

Updates a specific binding from imports/requires. Can replace, add, or remove bindings.
import { updateBinding } from '@nodejs/codemod-utils/ast-grep/update-binding';

// Example 1: Replace a binding
// Given: const { isNativeError } = require('node:util');
const result = updateBinding(node, { old: 'isNativeError', new: 'types' });
// Result: const { types } = require('node:util');

// Example 2: Add a new binding
// Given: const { isNativeError } = require('node:util');
const result = updateBinding(node, { old: undefined, new: 'types' });
// Result: const { isNativeError, types } = require('node:util');

// Example 3: Remove a binding (same as removeBinding)
// Given: const { isNativeError, types } = require('node:util');
const result = updateBinding(node, { old: 'isNativeError', new: undefined });
// Result: const { types } = require('node:util');

// Example 4: Many-to-many replacement
// Given: const { fips } = require('node:crypto');
const result = updateBinding(node, { 
  old: 'fips', 
  new: ['getFips', 'setFips'] 
});
// Result: const { getFips, setFips } = require('node:crypto');
node
SgNode<Js>
required
The AST node representing the import or require statement
options
object
Configuration object for the update operation
options.old
string
The name of the binding to update or remove. Omit to add a new binding.
options.new
string | string[]
The new binding name(s) to replace the old one. Can be:
  • A single string for one-to-one replacement
  • An array of strings for one-to-many replacement
  • undefined to remove the binding
options.usageCheck
object
Optional usage checking configuration
options.usageCheck.ignoredRanges
Range[]
Ranges to ignore when checking if a binding is used
options.root
SgNode<Js>
Custom root node for usage checking
Returns: { edit?: Edit, lineToRemove?: Range } | undefined

Advanced Update Patterns

// Replacing deprecated single export with multiple new exports
// Given: const { fips } = require('node:crypto');
updateBinding(node, { 
  old: 'fips', 
  new: ['getFips', 'setFips'] 
});
// Result: const { getFips, setFips } = require('node:crypto');

Complete Migration Example

import { getModuleDependencies } from '@nodejs/codemod-utils/ast-grep/module-dependencies';
import { resolveBindingPath } from '@nodejs/codemod-utils/ast-grep/resolve-binding-path';
import { updateBinding } from '@nodejs/codemod-utils/ast-grep/update-binding';

const ast = context.getAST();
const utilImports = getModuleDependencies(ast, 'util');

for (const importNode of utilImports) {
  // Check if using deprecated isNativeError
  const oldPath = resolveBindingPath(importNode, '$.isNativeError');
  
  if (oldPath) {
    // Need to replace isNativeError with types.isNativeError
    const result = updateBinding(importNode, {
      old: 'isNativeError',
      new: 'types'
    });
    
    if (result?.edit) {
      context.applyEdit(result.edit);
      
      // Now update all usage sites
      const usages = ast.findAll({
        rule: { kind: 'identifier', pattern: 'isNativeError' }
      });
      
      for (const usage of usages) {
        context.applyEdit(usage.replace('types.isNativeError'));
      }
    }
  }
}

Build docs developers (and LLMs) love