Skip to content

Commit 72d0b97

Browse files
feat(repl-builtin-modules): working draft
1 parent 1927e99 commit 72d0b97

File tree

1 file changed

+129
-28
lines changed

1 file changed

+129
-28
lines changed

recipes/repl-builtin-modules/src/workflow.ts

Lines changed: 129 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,14 @@ export default function transform(root: SgRoot<Js>): string | null {
4242
}
4343
});
4444

45-
const hasOnlyBuiltinModules = properties.length === 1 &&
46-
properties[0].text() === "builtinModules";
45+
const pairProperties = objectPattern.findAll({
46+
rule: {
47+
kind: "pair_pattern"
48+
}
49+
});
50+
51+
const hasOnlyBuiltinModules = (properties.length === 1 && properties[0].text() === "builtinModules") ||
52+
(properties.length === 0 && pairProperties.length === 1 && pairProperties[0].text().includes("builtinModules"));
4753

4854
if (hasOnlyBuiltinModules) {
4955
// Case 2: Replace entire require statement
@@ -54,37 +60,50 @@ export default function transform(root: SgRoot<Js>): string | null {
5460
});
5561
if (moduleSpecifier) {
5662
const currentModule = moduleSpecifier.text();
57-
const newModule = currentModule.includes("node:") ? '"node:module"' : '"module"';
63+
const newModule = currentModule.includes("node:") ? "'node:module'" : "'module'";
5864
edits.push(moduleSpecifier.replace(newModule));
5965
hasChanges = true;
6066
}
6167
} else {
6268
// Case 3: Split into two statements
63-
const newText = originalText.replace(/,?\s*builtinModules\s*,?/g, "").replace(/,\s*$/, "").replace(/^\s*,/, "");
69+
const newText = originalText.replace(/,?\s*builtinModules\s*(:\s*\w+)?\s*,?/g, "").replace(/,\s*$/, "").replace(/^\s*,/, "");
6470

6571
if (newText !== "{ }") {
66-
edits.push(objectPattern.replace(newText));
67-
}
72+
// Get the parent variable declaration to replace the entire statement
73+
const variableDeclaration = statement.parent();
6874

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"'});`;
75+
if (variableDeclaration && (variableDeclaration.kind() === "variable_declaration" || variableDeclaration.kind() === "lexical_declaration")) {
76+
// Extract the alias if present
77+
const aliasMatch = originalText.match(/builtinModules\s*:\s*(\w+)/);
78+
const aliasText = aliasMatch ? `: ${aliasMatch[1]}` : "";
7979

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;
80+
const moduleSpecifier = statement.find({
81+
rule: {
82+
kind: "string"
83+
}
84+
});
85+
86+
if (moduleSpecifier) {
87+
const currentModule = moduleSpecifier.text();
88+
const newModule = currentModule.includes("node:") ? "node:module" : "module";
89+
90+
// Create the new statements
91+
const firstStatement = `const ${newText} = require(${currentModule});`;
92+
const secondStatement = `const { builtinModules${aliasText} } = require(${newModule.includes('node:') ? "'node:module'" : "'module'"});`;
93+
94+
// Replace the entire variable declaration with both statements
95+
const replacementText = `${firstStatement}\n${secondStatement}`;
96+
97+
edits.push(variableDeclaration.replace(replacementText));
98+
hasChanges = true;
99+
}
100+
} else {
101+
// Fallback to just replacing the object pattern
102+
edits.push(objectPattern.replace(newText));
103+
}
104+
} else {
105+
// If we're removing the entire destructuring, we need to remove the whole statement
106+
edits.push(statement.replace(""));
88107
}
89108
}
90109
}
@@ -115,6 +134,14 @@ export default function transform(root: SgRoot<Js>): string | null {
115134
});
116135

117136
if (memberExpressions.length > 0) {
137+
// Replace variable name from repl to module
138+
edits.push(identifier.replace("module"));
139+
140+
// Replace all member expressions
141+
for (const memberExpr of memberExpressions) {
142+
edits.push(memberExpr.replace("module.builtinModules"));
143+
}
144+
118145
// Replace require statement to use module instead
119146
const moduleSpecifier = statement.find({
120147
rule: {
@@ -123,7 +150,7 @@ export default function transform(root: SgRoot<Js>): string | null {
123150
});
124151
if (moduleSpecifier) {
125152
const currentModule = moduleSpecifier.text();
126-
const newModule = currentModule.includes("node:") ? '"node:module"' : '"module"';
153+
const newModule = currentModule.includes("node:") ? "'node:module'" : "'module'";
127154
edits.push(moduleSpecifier.replace(newModule));
128155
hasChanges = true;
129156
}
@@ -138,6 +165,7 @@ export default function transform(root: SgRoot<Js>): string | null {
138165
const replImportStatements = getNodeImportStatements(root, "repl");
139166

140167
for (const statement of replImportStatements) {
168+
// Handle named imports like: import { builtinModules } from 'node:repl'
141169
const namedImports = statement.find({
142170
rule: {
143171
kind: "named_imports"
@@ -167,13 +195,13 @@ export default function transform(root: SgRoot<Js>): string | null {
167195
});
168196
if (moduleSpecifier) {
169197
const currentModule = moduleSpecifier.text();
170-
const newModule = currentModule.includes("node:") ? '"node:module"' : '"module"';
198+
const newModule = currentModule.includes("node:") ? "'node:module'" : "'module'";
171199
edits.push(moduleSpecifier.replace(newModule));
172200
hasChanges = true;
173201
}
174202
} else {
175203
// Case 5: Split into two statements
176-
const newText = originalText.replace(/,?\s*builtinModules\s*,?/g, "").replace(/,\s*$/, "").replace(/^\s*,/, "");
204+
const newText = originalText.replace(/,?\s*builtinModules\s*(:\s*\w+)?\s*,?/g, "").replace(/,\s*$/, "").replace(/^\s*,/, "");
177205
edits.push(namedImports.replace(newText));
178206

179207
// Add new module import statement
@@ -185,7 +213,12 @@ export default function transform(root: SgRoot<Js>): string | null {
185213
if (moduleSpecifier) {
186214
const currentModule = moduleSpecifier.text();
187215
const newModule = currentModule.includes("node:") ? "node:module" : "module";
188-
const newStatement = `import { builtinModules } from ${newModule.includes("node:") ? '"node:module"' : '"module"'};`;
216+
217+
// Extract the alias if present
218+
const aliasMatch = originalText.match(/builtinModules\s*(as\s+\w+)/);
219+
const aliasText = aliasMatch ? ` ${aliasMatch[1]}` : "";
220+
221+
const newStatement = `import { builtinModules${aliasText} } from ${newModule.includes("node:") ? "'node:module'" : "'module'"};`;
189222

190223
// Insert after current statement
191224
const statementEnd = statement.range().end;
@@ -199,6 +232,74 @@ export default function transform(root: SgRoot<Js>): string | null {
199232
}
200233
}
201234
}
235+
236+
// Handle default imports like: import repl from 'node:repl'
237+
// Handle namespace imports like: import * as repl from 'node:repl'
238+
if (!namedImports) {
239+
// Look for default or namespace imports that use repl.builtinModules
240+
const importClause = statement.find({
241+
rule: {
242+
kind: "import_clause"
243+
}
244+
});
245+
246+
if (importClause) {
247+
let importIdentifier = null;
248+
249+
// Check for default import
250+
const defaultImport = importClause.find({
251+
rule: {
252+
kind: "identifier"
253+
}
254+
});
255+
256+
// Check for namespace import
257+
const namespaceImport = importClause.find({
258+
rule: {
259+
kind: "namespace_import"
260+
}
261+
});
262+
263+
if (defaultImport) {
264+
importIdentifier = defaultImport;
265+
} else if (namespaceImport) {
266+
const namespaceIdentifier = namespaceImport.find({
267+
rule: {
268+
kind: "identifier"
269+
}
270+
});
271+
if (namespaceIdentifier) {
272+
importIdentifier = namespaceIdentifier;
273+
}
274+
}
275+
276+
if (importIdentifier) {
277+
const varName = importIdentifier.text();
278+
279+
// Find all member expressions using this variable with builtinModules
280+
const memberExpressions = rootNode.findAll({
281+
rule: {
282+
pattern: `${varName}.builtinModules`
283+
}
284+
});
285+
286+
if (memberExpressions.length > 0) {
287+
// Replace the import to use module instead
288+
const moduleSpecifier = statement.find({
289+
rule: {
290+
kind: "string"
291+
}
292+
});
293+
if (moduleSpecifier) {
294+
const currentModule = moduleSpecifier.text();
295+
const newModule = currentModule.includes("node:") ? "'node:module'" : "'module'";
296+
edits.push(moduleSpecifier.replace(newModule));
297+
hasChanges = true;
298+
}
299+
}
300+
}
301+
}
302+
}
202303
}
203304

204305
if (!hasChanges) return null;

0 commit comments

Comments
 (0)