Skip to content

Commit 5e4eabf

Browse files
feat(repl-builtin-modules): first draft
1 parent 6caf1a7 commit 5e4eabf

File tree

3 files changed

+199
-14
lines changed

3 files changed

+199
-14
lines changed
Lines changed: 195 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,207 @@
11
import { getNodeImportStatements } from "@nodejs/codemod-utils/ast-grep/import-statement";
22
import { getNodeRequireCalls } from "@nodejs/codemod-utils/ast-grep/require-call";
33
import type { SgRoot, Edit } from "@codemod.com/jssg-types/main";
4+
import type Js from "@codemod.com/jssg-types/langs/javascript";
45

56
/**
6-
* Transform function that converts deprecated fs.rmdir calls
7-
* with recursive: true option to the new fs.rm API.
7+
* Transform function that converts deprecated repl.builtinModules
8+
* to module.builtinModules API.
89
*
910
* Handles:
10-
* 1. ...
11+
* 1. const repl = require('node:repl'); repl.builtinModules -> const module = require('node:module'); module.builtinModules
12+
* 2. const { builtinModules } = require('node:repl'); -> const { builtinModules } = require('node:module');
13+
* 3. const { builtinModules, foo } = require('node:repl'); -> const { foo } = require('node:repl'); const { builtinModules } = require('node:module');
14+
* 4. import { builtinModules } from 'node:repl'; -> import { builtinModules } from 'node:module';
15+
* 5. import { builtinModules, foo } from 'node:repl'; -> import { foo } from 'node:repl'; import { builtinModules } from 'node:module';
1116
*/
12-
export default function transform(root: SgRoot): string | null {
13-
const rootNode = root.root();
14-
let hasChanges = false;
15-
const edits: Edit[] = [];
17+
export default function transform(root: SgRoot<Js>): string | null {
18+
const rootNode = root.root();
19+
let hasChanges = false;
20+
const edits: Edit[] = [];
1621

17-
// do things
22+
// Step 1: Handle require statements
23+
// @ts-ignore - ast-grep types are not fully compatible with JSSG types
24+
const replRequireStatements = getNodeRequireCalls(root, "repl");
1825

19-
if (!hasChanges) return null;
26+
for (const statement of replRequireStatements) {
27+
// Check if this is destructuring assignment with builtinModules
28+
const objectPattern = statement.find({
29+
rule: {
30+
kind: "object_pattern"
31+
}
32+
});
2033

21-
return rootNode.commitEdits(edits);
34+
if (objectPattern) {
35+
const originalText = objectPattern.text();
36+
37+
if (originalText.includes("builtinModules")) {
38+
// Check if builtinModules is the only destructured property
39+
const properties = objectPattern.findAll({
40+
rule: {
41+
kind: "shorthand_property_identifier_pattern"
42+
}
43+
});
44+
45+
const hasOnlyBuiltinModules = properties.length === 1 &&
46+
properties[0].text() === "builtinModules";
47+
48+
if (hasOnlyBuiltinModules) {
49+
// Case 2: Replace entire require statement
50+
const moduleSpecifier = statement.find({
51+
rule: {
52+
kind: "string"
53+
}
54+
});
55+
if (moduleSpecifier) {
56+
const currentModule = moduleSpecifier.text();
57+
const newModule = currentModule.includes("node:") ? '"node:module"' : '"module"';
58+
edits.push(moduleSpecifier.replace(newModule));
59+
hasChanges = true;
60+
}
61+
} else {
62+
// Case 3: Split into two statements
63+
const newText = originalText.replace(/,?\s*builtinModules\s*,?/g, "").replace(/,\s*$/, "").replace(/^\s*,/, "");
64+
65+
if (newText !== "{ }") {
66+
edits.push(objectPattern.replace(newText));
67+
}
68+
69+
// Add new module require statement
70+
const moduleSpecifier = statement.find({
71+
rule: {
72+
kind: "string"
73+
}
74+
});
75+
if (moduleSpecifier) {
76+
const currentModule = moduleSpecifier.text();
77+
const newModule = currentModule.includes("node:") ? "node:module" : "module";
78+
const newStatement = `const { builtinModules } = require(${newModule.includes("node:") ? '"node:module"' : '"module"'});`;
79+
80+
// Insert after current statement
81+
const statementEnd = statement.range().end;
82+
edits.push({
83+
startPos: statementEnd.index,
84+
endPos: statementEnd.index,
85+
insertedText: `\n${newStatement}`
86+
});
87+
hasChanges = true;
88+
}
89+
}
90+
}
91+
} else {
92+
// Case 1: Handle namespace import like const repl = require('node:repl')
93+
// Find usages of repl.builtinModules and replace with module.builtinModules
94+
const variableDeclarator = statement.find({
95+
rule: {
96+
kind: "variable_declarator"
97+
}
98+
});
99+
100+
if (variableDeclarator) {
101+
const identifier = variableDeclarator.find({
102+
rule: {
103+
kind: "identifier"
104+
}
105+
});
106+
107+
if (identifier) {
108+
const varName = identifier.text();
109+
110+
// Find all member expressions using this variable with builtinModules
111+
const memberExpressions = rootNode.findAll({
112+
rule: {
113+
pattern: `${varName}.builtinModules`
114+
}
115+
});
116+
117+
if (memberExpressions.length > 0) {
118+
// Replace require statement to use module instead
119+
const moduleSpecifier = statement.find({
120+
rule: {
121+
kind: "string"
122+
}
123+
});
124+
if (moduleSpecifier) {
125+
const currentModule = moduleSpecifier.text();
126+
const newModule = currentModule.includes("node:") ? '"node:module"' : '"module"';
127+
edits.push(moduleSpecifier.replace(newModule));
128+
hasChanges = true;
129+
}
130+
}
131+
}
132+
}
133+
}
134+
}
135+
136+
// Step 2: Handle import statements
137+
// @ts-ignore - ast-grep types are not fully compatible with JSSG types
138+
const replImportStatements = getNodeImportStatements(root, "repl");
139+
140+
for (const statement of replImportStatements) {
141+
const namedImports = statement.find({
142+
rule: {
143+
kind: "named_imports"
144+
}
145+
});
146+
147+
if (namedImports) {
148+
const originalText = namedImports.text();
149+
150+
if (originalText.includes("builtinModules")) {
151+
// Check if builtinModules is the only import
152+
const importSpecifiers = namedImports.findAll({
153+
rule: {
154+
kind: "import_specifier"
155+
}
156+
});
157+
158+
const hasOnlyBuiltinModules = importSpecifiers.length === 1 &&
159+
importSpecifiers[0].text().includes("builtinModules");
160+
161+
if (hasOnlyBuiltinModules) {
162+
// Case 4: Replace entire import statement
163+
const moduleSpecifier = statement.find({
164+
rule: {
165+
kind: "string"
166+
}
167+
});
168+
if (moduleSpecifier) {
169+
const currentModule = moduleSpecifier.text();
170+
const newModule = currentModule.includes("node:") ? '"node:module"' : '"module"';
171+
edits.push(moduleSpecifier.replace(newModule));
172+
hasChanges = true;
173+
}
174+
} else {
175+
// Case 5: Split into two statements
176+
const newText = originalText.replace(/,?\s*builtinModules\s*,?/g, "").replace(/,\s*$/, "").replace(/^\s*,/, "");
177+
edits.push(namedImports.replace(newText));
178+
179+
// Add new module import statement
180+
const moduleSpecifier = statement.find({
181+
rule: {
182+
kind: "string"
183+
}
184+
});
185+
if (moduleSpecifier) {
186+
const currentModule = moduleSpecifier.text();
187+
const newModule = currentModule.includes("node:") ? "node:module" : "module";
188+
const newStatement = `import { builtinModules } from ${newModule.includes("node:") ? '"node:module"' : '"module"'};`;
189+
190+
// Insert after current statement
191+
const statementEnd = statement.range().end;
192+
edits.push({
193+
startPos: statementEnd.index,
194+
endPos: statementEnd.index,
195+
insertedText: `\n${newStatement}`
196+
});
197+
hasChanges = true;
198+
}
199+
}
200+
}
201+
}
202+
}
203+
204+
if (!hasChanges) return null;
205+
206+
return rootNode.commitEdits(edits);
22207
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
const repl = require('node:repl');
1+
const module = require('node:module');
22

3-
console.log(repl.builtinModules);
3+
console.log(module.builtinModules);
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
const module = require('node:module');
1+
const repl = require('node:repl');
22

3-
console.log(module.builtinModules);
3+
console.log(repl.builtinModules);

0 commit comments

Comments
 (0)