Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 88 additions & 17 deletions utils/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,37 @@ Building Node.js codemods with [ast-grep](https://ast-grep.github.io/) requires

These utilities provide battle-tested solutions for common codemod operations, reducing boilerplate and ensuring consistent behavior across all migration recipes.

## Package Utilities

### `removeDependencies(dependenciesToRemove, options?)`

Removes dependencies from `dependencies` and `devDependencies` in `package.json`, then optionally runs the detected package manager install command.

```typescript
import removeDependencies from '@nodejs/codemod-utils/remove-dependencies';

await removeDependencies(['chalk', 'lodash'], {
runInstall: false,
});
```

#### Parameters

- `dependenciesToRemove`: `string | string[]` — package name(s) to remove.
- `options.packageJsonPath`: optional path to a target `package.json` (default: `package.json`).
- `options.runInstall`: set to `false` to skip install after changes (default: `true`).
- `options.persistFileWrite`: set to `false` to preview returned JSON without writing to disk (default: `true`).

#### Return value

- Returns updated `package.json` content as a string when changes are applied.
- Returns `null` when there is nothing to remove, no `package.json`, or on parsing/runtime failure.

#### Notes

- Package manager detection order: `packageJson.packageManager` → lockfiles (`pnpm-lock.yaml`, `yarn.lock`) → `npm`.
- When `runInstall` is enabled, runs `<package-manager> install` in the package directory.

## AST-grep Utilities

### Import and Require Detection
Expand All @@ -24,7 +55,7 @@ Under the hood, calls `getNodeRequireCalls`, `getNodeImportStatements`, `getNode
combines the return values.

```typescript
import { getNodeImportStatements } from '@nodejs/codemod-utils';
import { getModuleDependencies } from '@nodejs/codemod-utils/ast-grep/module-dependencies';

// Finds: import fs from 'fs'; import { readFile } from 'node:fs';
// Finds: const fs = require('fs')
Expand All @@ -37,7 +68,7 @@ const fsImports = getModuleDependencies(ast, 'fs');
Finds all ES module import statements for a specific Node.js module.

```typescript
import { getNodeImportStatements } from '@nodejs/codemod-utils';
import { getNodeImportStatements } from '@nodejs/codemod-utils/ast-grep/import-statement';

// Finds: import fs from 'fs'; import { readFile } from 'node:fs';
const fsImports = getNodeImportStatements(ast, 'fs');
Expand All @@ -48,7 +79,7 @@ const fsImports = getNodeImportStatements(ast, 'fs');
Finds dynamic import calls assigned to variables (excludes unassigned imports).

```typescript
import { getNodeImportCalls } from '@nodejs/codemod-utils';
import { getNodeImportCalls } from '@nodejs/codemod-utils/ast-grep/import-statement';

// Finds: const fs = await import('node:fs');
// Ignores: import('node:fs'); // unassigned
Expand All @@ -60,7 +91,7 @@ const fsImportCalls = getNodeImportCalls(ast, 'fs');
Finds CommonJS require calls assigned to variables.

```typescript
import { getNodeRequireCalls } from '@nodejs/codemod-utils';
import { getNodeRequireCalls } from '@nodejs/codemod-utils/ast-grep/require-call';

// Finds: const fs = require('fs'); const { readFile } = require('node:fs');
const fsRequires = getNodeRequireCalls(ast, 'fs');
Expand All @@ -73,7 +104,7 @@ const fsRequires = getNodeRequireCalls(ast, 'fs');
Resolves how a global API path should be accessed based on the import pattern.

```typescript
import { resolveBindingPath } from '@nodejs/codemod-utils';
import { resolveBindingPath } from '@nodejs/codemod-utils/ast-grep/resolve-binding-path';

// Given: const { types } = require('node:util');
// resolveBindingPath(node, '$.types.isNativeError') → 'types.isNativeError'
Expand All @@ -90,7 +121,7 @@ import { resolveBindingPath } from '@nodejs/codemod-utils';
Removes a specific binding from imports/requires, or removes the entire statement if it's the only binding.

```typescript
import { removeBinding } from '@nodejs/codemod-utils';
import { removeBinding } from '@nodejs/codemod-utils/ast-grep/remove-binding';

// Given: const { types, isNativeError } = require('node:util');
// removeBinding(node, 'isNativeError') → Edit to: const { types } = require('node:util');
Expand All @@ -107,7 +138,7 @@ import { removeBinding } from '@nodejs/codemod-utils';
Updates a specific binding from imports/requires. It can be used to replace, add, or remove bindings.

```typescript
import { updateBinding } from '@nodejs/codemod-utils';
import { updateBinding } from '@nodejs/codemod-utils/ast-grep/update-binding';

// Given: const { isNativeError } = require('node:util');
// updateBinding(node, {old: 'isNativeError', new: 'types'}) → Edit to: const { types } = require('node:util');
Expand All @@ -126,7 +157,7 @@ import { updateBinding } from '@nodejs/codemod-utils';
Safely removes multiple line ranges from source code, handling overlaps and duplicates.

```typescript
import { removeLines } from '@nodejs/codemod-utils';
import { removeLines } from '@nodejs/codemod-utils/ast-grep/remove-lines';

const ranges = [
{ start: { line: 5, column: 0 }, end: { line: 5, column: 50 } },
Expand All @@ -143,7 +174,7 @@ const cleanedCode = removeLines(sourceCode, ranges);
Finds the "scripts" section in a package.json AST.

```typescript
import { getScriptsNode } from '@nodejs/codemod-utils';
import { getScriptsNode } from '@nodejs/codemod-utils/ast-grep/package-json';

const scriptsNodes = getScriptsNode(packageJsonAst);
```
Expand All @@ -153,20 +184,50 @@ const scriptsNodes = getScriptsNode(packageJsonAst);
Finds all references to `node` or `node.exe` in package.json scripts.

```typescript
import { getNodeJsUsage } from '@nodejs/codemod-utils';
import { getNodeJsUsage } from '@nodejs/codemod-utils/ast-grep/package-json';

// Finds scripts like: "start": "node server.js", "build": "node.exe build.js"
const nodeUsages = getNodeJsUsage(packageJsonAst);
```

### Additional AST-grep Helpers (missing in earlier list)
### Additional AST-grep Helpers

#### `detectIndentUnit(source)`

Detects the indentation unit used in source code (`\t` or spaces).

```typescript
import { detectIndentUnit } from '@nodejs/codemod-utils/ast-grep/indent';

const indentUnit = detectIndentUnit(sourceCode);
```

#### `getLineIndent(source, index)`

Returns the indentation prefix for the line containing the given character index.

```typescript
import { getLineIndent } from '@nodejs/codemod-utils/ast-grep/indent';

const indent = getLineIndent(sourceCode, node.range().start.index);
```

#### `getShebang(rootNode)`

Returns the Node.js shebang line node when present (for example `#!/usr/bin/env node`).

```typescript
import { getShebang } from '@nodejs/codemod-utils/ast-grep/shebang';

const shebang = getShebang(ast);
```

#### `getScope(node, customParent?)`

Finds the enclosing scope for a node (e.g., `statement_block` or `program`).

```typescript
import { getScope } from '@nodejs/codemod-utils';
import { getScope } from '@nodejs/codemod-utils/ast-grep/get-scope';

const scope = getScope(node);
```
Expand All @@ -176,7 +237,7 @@ const scope = getScope(node);
Returns the identifier node for a default `import` (e.g., `import fs from 'fs'`).

```typescript
import { getDefaultImportIdentifier } from '@nodejs/codemod-utils';
import { getDefaultImportIdentifier } from '@nodejs/codemod-utils/ast-grep/import-statement';

const id = getDefaultImportIdentifier(importNode);
```
Expand All @@ -186,28 +247,38 @@ const id = getDefaultImportIdentifier(importNode);
Returns the identifier node for namespace-style `require` (e.g., `const util = require('util')`).

```typescript
import { getRequireNamespaceIdentifier } from '@nodejs/codemod-utils';
import { getRequireNamespaceIdentifier } from '@nodejs/codemod-utils/ast-grep/require-call';

const id = getRequireNamespaceIdentifier(varDeclaratorNode);
```

#### `replaceNodeJsArgs(rootNode, argsToValues)` (shebang)

Replaces Node.js arguments in shebang lines.

```typescript
import { replaceNodeJsArgs as replaceShebangNodeJsArgs } from '@nodejs/codemod-utils/ast-grep/shebang';

const edits = replaceShebangNodeJsArgs(ast, { '--inspect': '' });
```

#### `replaceNodeJsArgs(packageJsonRootNode, argsToValues)`

Replaces Node.js script arguments in `package.json` scripts. Useful for normalizing or
rewriting CLI arguments used with `node`.

```typescript
import { replaceNodeJsArgs } from '@nodejs/codemod-utils';
import { replaceNodeJsArgs as replacePackageJsonNodeJsArgs } from '@nodejs/codemod-utils/ast-grep/package-json';

const edits = replaceNodeJsArgs(packageJsonAst, { '--inspect': '' });
const edits = replacePackageJsonNodeJsArgs(packageJsonAst, { '--inspect': '' });
```

#### `removeNodeJsArgs(packageJsonRootNode, argsToRemove)`

Removes specified arguments from Node.js script usages in `package.json`.

```typescript
import { removeNodeJsArgs } from '@nodejs/codemod-utils';
import { removeNodeJsArgs } from '@nodejs/codemod-utils/ast-grep/package-json';

const edits = removeNodeJsArgs(packageJsonAst, ['--inspect']);
```
4 changes: 1 addition & 3 deletions utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
"version": "0.0.0",
"description": "Utilities for Node.js userland migrations.",
"exports": {
"./*": [
"./src/*.ts"
]
"./*":"./src/*.ts"
},
"scripts": {
"test": "node --no-warnings --import=./src/codemod-jssg-context.ts --experimental-test-snapshots --experimental-strip-types --import='./src/snapshots.ts' --test --experimental-test-coverage --test-coverage-include='src/**/*' --test-coverage-exclude='**/*.test.ts' './**/*.test.ts'"
Expand Down
Loading