|
1 | 1 | import { getNodeImportStatements } from "@nodejs/codemod-utils/ast-grep/import-statement";
|
2 | 2 | import { getNodeRequireCalls } from "@nodejs/codemod-utils/ast-grep/require-call";
|
3 | 3 | import type { SgRoot, Edit } from "@codemod.com/jssg-types/main";
|
| 4 | +import type Js from "@codemod.com/jssg-types/langs/javascript"; |
4 | 5 |
|
5 | 6 | /**
|
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. |
8 | 9 | *
|
9 | 10 | * 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'; |
11 | 16 | */
|
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[] = []; |
16 | 21 |
|
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"); |
18 | 25 |
|
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 | + }); |
20 | 33 |
|
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); |
22 | 207 | }
|
0 commit comments