Skip to content

Commit 63dd976

Browse files
doc: clarify example (#221)
1 parent e2e1f0f commit 63dd976

File tree

2 files changed

+22
-111
lines changed

2 files changed

+22
-111
lines changed

CONTRIBUTING.md

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ Each codemod resides in its own directory under `recipes/` and should include:
3939

4040
**`src/workflow.ts` example:**
4141
```ts
42+
import { getNodeImportStatements } from "@nodejs/codemod-utils/ast-grep/import-statement";
43+
import { getNodeRequireCalls } from "@nodejs/codemod-utils/ast-grep/require-call";
44+
import { resolveBindingPath } from '@nodejs/codemod-utils/ast-grep/resolve-binding-path';
4245
import type { SgRoot, Edit } from "@codemod.com/jssg-types/main";
4346
import type JS from "@codemod.com/jssg-types/langs/javascript";
4447

@@ -55,8 +58,25 @@ export default function transform(root: SgRoot<JS>): string | null {
5558
const rootNode = root.root();
5659
const edits: Edit[] = [];
5760

58-
// do some transformation
59-
edits.push(...);
61+
const allStatementNodes = [
62+
...getNodeImportStatements(root, 'api'),
63+
...getNodeRequireCalls(root, 'api')
64+
];
65+
66+
for (const statementNode of allStatementNodes) {
67+
const bindingPath = resolveBindingPath(statementNode, 'api.fn');
68+
if (!bindingPath) continue;
69+
// Find all calls to the resolved bindingPath
70+
const callNodes = rootNode.findDescendants((node) => {
71+
return node.isCallExpression() &&
72+
node.getChild('callee')?.getText() === bindingPath;
73+
});
74+
for (const callNode of callNodes) {
75+
// Perform transformation on callNode
76+
// e.g., replace 'api.fn' with 'api.newFn'
77+
edits.push(...);
78+
}
79+
}
6080

6181
if (edits.length === 0) return null;
6282

utils/README.md

Lines changed: 0 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -123,112 +123,3 @@ import { getNodeJsUsage } from '@nodejs/codemod-utils';
123123
// Finds scripts like: "start": "node server.js", "build": "node.exe build.js"
124124
const nodeUsages = getNodeJsUsage(packageJsonAst);
125125
```
126-
127-
## Practical Examples
128-
129-
### Complete Codemod Workflow
130-
131-
Here's how these utilities work together in a typical Node.js deprecation codemod:
132-
133-
```typescript
134-
import astGrep from '@ast-grep/napi';
135-
import {
136-
getNodeImportStatements,
137-
getNodeRequireCalls,
138-
resolveBindingPath,
139-
removeBinding,
140-
removeLines
141-
} from '@nodejs/codemod-utils';
142-
143-
export default function workflow({ file, options }) {
144-
// 1. Parse the source code
145-
const ast = astGrep.parse(astGrep.Lang.JavaScript, file.source);
146-
147-
// 2. Find all util imports/requires
148-
const importStatements = getNodeImportStatements(ast, 'util');
149-
const requireCalls = getNodeRequireCalls(ast, 'util');
150-
const allUtilNodes = [...importStatements, ...requireCalls];
151-
152-
// 3. Find and transform deprecated API usage
153-
const edits = [];
154-
const linesToRemove = [];
155-
156-
for (const node of allUtilNodes) {
157-
// Resolve how the deprecated API is accessed locally
158-
const localPath = resolveBindingPath(node, '$.types.isNativeError');
159-
160-
if (localPath) {
161-
// Find all usages of the deprecated API
162-
const usages = ast.root().findAll({
163-
rule: {
164-
kind: 'call_expression',
165-
has: {
166-
field: 'function',
167-
kind: 'member_expression',
168-
regex: localPath.replace('.', '\\.')
169-
}
170-
}
171-
});
172-
173-
// Transform each usage
174-
for (const usage of usages) {
175-
edits.push({
176-
startIndex: usage.range().start.index,
177-
endIndex: usage.range().end.index,
178-
newText: usage.text().replace(localPath, 'util.types.isError')
179-
});
180-
}
181-
182-
// Remove the binding if it's no longer needed
183-
const bindingRemoval = removeBinding(node, 'types');
184-
if (bindingRemoval?.edit) {
185-
edits.push(bindingRemoval.edit);
186-
} else if (bindingRemoval?.lineToRemove) {
187-
linesToRemove.push(bindingRemoval.lineToRemove);
188-
}
189-
}
190-
}
191-
192-
// 4. Apply all transformations
193-
let transformedSource = file.source;
194-
195-
// Apply edits
196-
for (const edit of edits.reverse()) { // reverse to maintain indices
197-
transformedSource = transformedSource.slice(0, edit.startIndex) +
198-
edit.newText +
199-
transformedSource.slice(edit.endIndex);
200-
}
201-
202-
// Remove entire lines
203-
if (linesToRemove.length > 0) {
204-
transformedSource = removeLines(transformedSource, linesToRemove);
205-
}
206-
207-
return {
208-
...file,
209-
source: transformedSource
210-
};
211-
}
212-
```
213-
214-
### Handling Different Import Patterns
215-
216-
The utilities automatically handle various import/require patterns:
217-
218-
```typescript
219-
// ES Modules
220-
import util from 'util'; // → util.types.isNativeError
221-
import { types } from 'util'; // → types.isNativeError
222-
import { types as t } from 'util'; // → t.isNativeError
223-
224-
// CommonJS
225-
const util = require('util'); // → util.types.isNativeError
226-
const { types } = require('util'); // → types.isNativeError
227-
const { types: t } = require('util'); // → t.isNativeError
228-
229-
// Mixed with node: protocol
230-
import { types } from 'node:util'; // → types.isNativeError
231-
const util = require('node:util'); // → util.types.isNativeError
232-
```
233-
234-
This unified approach ensures your codemods work correctly regardless of how developers import Node.js modules in their projects.

0 commit comments

Comments
 (0)