diff --git a/recipes/chalk-to-util-styletext/codemod.yaml b/recipes/chalk-to-util-styletext/codemod.yaml index 1aed15ba..84f6cd1f 100644 --- a/recipes/chalk-to-util-styletext/codemod.yaml +++ b/recipes/chalk-to-util-styletext/codemod.yaml @@ -1,6 +1,6 @@ schema_version: "1.0" name: "@nodejs/chalk-to-util-styletext" -version: 1.0.0 +version: 1.0.1 capabilities: - fs - child_process diff --git a/recipes/chalk-to-util-styletext/package.json b/recipes/chalk-to-util-styletext/package.json index fc9f1cbc..04d8a0d5 100644 --- a/recipes/chalk-to-util-styletext/package.json +++ b/recipes/chalk-to-util-styletext/package.json @@ -1,6 +1,6 @@ { "name": "@nodejs/chalk-to-util-styletext", - "version": "1.0.0", + "version": "1.0.1", "description": "Migrate from the chalk package to Node.js's built-in util.styleText API", "type": "module", "scripts": { diff --git a/recipes/chalk-to-util-styletext/src/workflow.ts b/recipes/chalk-to-util-styletext/src/workflow.ts index 7be3fbc7..080dde9a 100644 --- a/recipes/chalk-to-util-styletext/src/workflow.ts +++ b/recipes/chalk-to-util-styletext/src/workflow.ts @@ -1,11 +1,7 @@ -import { getNodeRequireCalls } from '@nodejs/codemod-utils/ast-grep/require-call'; -import { - getNodeImportCalls, - getNodeImportStatements, -} from '@nodejs/codemod-utils/ast-grep/import-statement'; import { resolveBindingPath } from '@nodejs/codemod-utils/ast-grep/resolve-binding-path'; import type { Edit, SgNode, SgRoot } from '@codemod.com/jssg-types/main'; import type Js from '@codemod.com/jssg-types/langs/javascript'; +import { getModuleDependencies } from '@nodejs/codemod-utils/ast-grep/module-dependencies'; const chalkBinding = 'chalk'; const chalkStdErrBinding = 'chalkStderr'; @@ -22,11 +18,7 @@ export default function transform(root: SgRoot): string | null { const edits: Edit[] = []; // This actually catches `node:chalk` import but we don't care as it shouldn't append - const statements = [ - ...getNodeImportStatements(root, chalkBinding), - ...getNodeRequireCalls(root, chalkBinding), - ...getNodeImportCalls(root, chalkBinding), - ]; + const statements = getModuleDependencies(root, chalkBinding); // If there aren't any imports then we don't process the file if (!statements.length) return null; diff --git a/recipes/crypto-fips-to-getFips/codemod.yaml b/recipes/crypto-fips-to-getFips/codemod.yaml index 03124d67..e0dff76a 100644 --- a/recipes/crypto-fips-to-getFips/codemod.yaml +++ b/recipes/crypto-fips-to-getFips/codemod.yaml @@ -1,6 +1,6 @@ schema_version: "1.0" name: "@nodejs/crypto-fips-to-getFips" -version: "1.0.1" +version: "1.0.2" description: Handle DEP0093 via transforming `crypto.fips` to `crypto.getFips()` and `crypto.setFips()` author: Usman S. license: MIT diff --git a/recipes/crypto-fips-to-getFips/package.json b/recipes/crypto-fips-to-getFips/package.json index df6a4108..fdce9fc4 100644 --- a/recipes/crypto-fips-to-getFips/package.json +++ b/recipes/crypto-fips-to-getFips/package.json @@ -1,6 +1,6 @@ { "name": "@nodejs/crypto-fips-to-getFips", - "version": "1.0.1", + "version": "1.0.2", "description": "Handle DEP0093 via transforming `crypto.fips` to `crypto.getFips()` and `crypto.setFips()`", "type": "module", "scripts": { diff --git a/recipes/crypto-fips-to-getFips/src/workflow.ts b/recipes/crypto-fips-to-getFips/src/workflow.ts index 6b0eb3a5..27feb4b5 100644 --- a/recipes/crypto-fips-to-getFips/src/workflow.ts +++ b/recipes/crypto-fips-to-getFips/src/workflow.ts @@ -1,13 +1,9 @@ -import { - getNodeImportCalls, - getNodeImportStatements, -} from '@nodejs/codemod-utils/ast-grep/import-statement'; -import { getNodeRequireCalls } from '@nodejs/codemod-utils/ast-grep/require-call'; import { resolveBindingPath } from '@nodejs/codemod-utils/ast-grep/resolve-binding-path'; import { updateBinding } from '@nodejs/codemod-utils/ast-grep/update-binding'; import { removeLines } from '@nodejs/codemod-utils/ast-grep/remove-lines'; import type { SgRoot, SgNode, Edit, Range } from '@codemod.com/jssg-types/main'; import type Js from '@codemod.com/jssg-types/langs/javascript'; +import { getModuleDependencies } from '@nodejs/codemod-utils/ast-grep/module-dependencies'; type Binding = { type: 'namespace' | 'destructured'; @@ -68,11 +64,7 @@ export default function transform(root: SgRoot): string | null { */ function collectCryptoFipsBindings(root: SgRoot): Binding[] { const bindings: Binding[] = []; - const allStatements = [ - ...getNodeImportStatements(root, 'crypto'), - ...getNodeImportCalls(root, 'crypto'), - ...getNodeRequireCalls(root, 'crypto'), - ]; + const allStatements = getModuleDependencies(root, 'crypto'); // If no statements found, skip transformation if (!allStatements.length) return bindings; diff --git a/recipes/crypto-rsa-pss-update/codemod.yaml b/recipes/crypto-rsa-pss-update/codemod.yaml index ef635d36..a50a6713 100644 --- a/recipes/crypto-rsa-pss-update/codemod.yaml +++ b/recipes/crypto-rsa-pss-update/codemod.yaml @@ -1,6 +1,6 @@ schema_version: "1.0" name: "@nodejs/crypto-rsa-pss-update" -version: 1.0.1 +version: 1.0.2 description: >- Handle DEP0154 via transforming deprecated RSA-PSS crypto options `hash` to `hashAlgorithm` and `mgf1Hash` to `mgf1HashAlgorithm` diff --git a/recipes/crypto-rsa-pss-update/package.json b/recipes/crypto-rsa-pss-update/package.json index 19bb176e..f68c7e2b 100644 --- a/recipes/crypto-rsa-pss-update/package.json +++ b/recipes/crypto-rsa-pss-update/package.json @@ -1,6 +1,6 @@ { "name": "@nodejs/crypto-rsa-pss-update", - "version": "1.0.1", + "version": "1.0.2", "description": "Handle DEP0154 via transforming deprecated RSA-PSS crypto options `hash` to `hashAlgorithm` and `mgf1Hash` to `mgf1HashAlgorithm`.", "type": "module", "scripts": { diff --git a/recipes/crypto-rsa-pss-update/src/workflow.ts b/recipes/crypto-rsa-pss-update/src/workflow.ts index 37fa809f..cea2c820 100644 --- a/recipes/crypto-rsa-pss-update/src/workflow.ts +++ b/recipes/crypto-rsa-pss-update/src/workflow.ts @@ -1,8 +1,7 @@ import type { SgRoot, SgNode, Edit } from '@codemod.com/jssg-types/main'; import type JS from '@codemod.com/jssg-types/langs/javascript'; -import { getNodeImportStatements } from '@nodejs/codemod-utils/ast-grep/import-statement'; -import { getNodeRequireCalls } from '@nodejs/codemod-utils/ast-grep/require-call'; import { resolveBindingPath } from '@nodejs/codemod-utils/ast-grep/resolve-binding-path'; +import { getModuleDependencies } from '@nodejs/codemod-utils/ast-grep/module-dependencies'; const RSA_PSS_REGEX = /^['"]rsa-pss['"]$/; const IDENTIFIER_REGEX = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/; @@ -61,7 +60,7 @@ function transformRsaPssCalls( } function getCryptoBindings(root: SgRoot): string[] { - const bindings = resolveBindings(getModuleStatements(root, 'crypto'), [ + const bindings = resolveBindings(getModuleDependencies(root, 'crypto'), [ '$.generateKeyPair', '$.generateKeyPairSync', ]); @@ -72,7 +71,7 @@ function getPromisifiedBindings( root: SgRoot, existingBindings: string[], ): string[] { - const utilStatements = getModuleStatements(root, 'util'); + const utilStatements = getModuleDependencies(root, 'util'); const promisifyBindings = resolveBindings(utilStatements, '$.promisify'); if (promisifyBindings.length === 0 && utilStatements.length > 0) { @@ -109,15 +108,6 @@ function getText(node: SgNode | undefined): string | null { return text || null; } -function getModuleStatements( - root: SgRoot, - moduleName: string, -): SgNode[] { - const importStatements = getNodeImportStatements(root, moduleName); - const requireCalls = getNodeRequireCalls(root, moduleName); - return [...importStatements, ...requireCalls]; -} - function resolveBindings( statements: SgNode[], paths: string | string[], @@ -374,4 +364,3 @@ function findAndTransformVariableDeclarations( .map((decl) => decl.replace(decl.text().replace(from, to))), ); } - diff --git a/recipes/dirent-path-to-parent-path/codemod.yaml b/recipes/dirent-path-to-parent-path/codemod.yaml index 554dfbcf..b0d1dc9d 100644 --- a/recipes/dirent-path-to-parent-path/codemod.yaml +++ b/recipes/dirent-path-to-parent-path/codemod.yaml @@ -1,6 +1,6 @@ schema_version: "1.0" name: "@nodejs/dirent-path-to-parent-path" -version: 1.0.0 +version: 1.0.1 description: Handle DEP0178 via transforming `dirent.path` to `dirent.parentPath`. author: Bruno Rodrigues license: MIT diff --git a/recipes/dirent-path-to-parent-path/package.json b/recipes/dirent-path-to-parent-path/package.json index 9cceeba0..d4947059 100644 --- a/recipes/dirent-path-to-parent-path/package.json +++ b/recipes/dirent-path-to-parent-path/package.json @@ -1,11 +1,10 @@ { "name": "@nodejs/dirent-path-to-parent-path", - "version": "1.0.0", + "version": "1.0.1", "description": "Handle DEP0178 via transforming `dirent.path` to `dirent.parentPath`", "type": "module", "scripts": { - "test": "npx codemod jssg test -l typescript ./src/workflow.ts ./", - "testu": "npx codemod jssg test -l typescript -u ./src/workflow.ts ./" + "test": "npx codemod jssg test -l typescript ./src/workflow.ts ./" }, "repository": { "type": "git", diff --git a/recipes/dirent-path-to-parent-path/src/workflow.ts b/recipes/dirent-path-to-parent-path/src/workflow.ts index 2369ab7b..0ec6cf07 100644 --- a/recipes/dirent-path-to-parent-path/src/workflow.ts +++ b/recipes/dirent-path-to-parent-path/src/workflow.ts @@ -1,13 +1,9 @@ -import { getNodeRequireCalls } from '@nodejs/codemod-utils/ast-grep/require-call'; -import { - getNodeImportCalls, - getNodeImportStatements, -} from '@nodejs/codemod-utils/ast-grep/import-statement'; import { resolveBindingPath } from '@nodejs/codemod-utils/ast-grep/resolve-binding-path'; import { removeLines } from '@nodejs/codemod-utils/ast-grep/remove-lines'; import { getScope } from '@nodejs/codemod-utils/ast-grep/get-scope'; import type { Edit, Range, SgNode, SgRoot } from '@codemod.com/jssg-types/main'; import type Js from '@codemod.com/jssg-types/langs/javascript'; +import { getModuleDependencies } from '@nodejs/codemod-utils/ast-grep/module-dependencies'; type BindingToReplace = { node: SgNode; @@ -56,9 +52,7 @@ export default function transform(root: SgRoot): string | null { const importRequireStatement: SgNode[] = []; for (const mod of handledModules) { - importRequireStatement.push(...getNodeRequireCalls(root, mod)); - importRequireStatement.push(...getNodeImportStatements(root, mod)); - importRequireStatement.push(...getNodeImportCalls(root, mod)); + importRequireStatement.push(...getModuleDependencies(root, mod)); } if (!importRequireStatement.length) return null; @@ -166,7 +160,8 @@ export default function transform(root: SgRoot): string | null { } for (const dirArray of dirArrays) { - const pattern = dirArray.node.kind() === 'variable_declarator' + const pattern = + dirArray.node.kind() === 'variable_declarator' ? (dirArray.node as SgNode) .field('name') .text() @@ -256,7 +251,8 @@ export default function transform(root: SgRoot): string | null { }); for (const arrowFn of arrowFns) { - const parameters = arrowFn.field('parameters') || arrowFn.field('parameter'); + const parameters = + arrowFn.field('parameters') || arrowFn.field('parameter'); const fnBody = arrowFn.field('body'); const param = parameters?.find<'identifier'>({ diff --git a/recipes/fs-truncate-fd-deprecation/codemod.yaml b/recipes/fs-truncate-fd-deprecation/codemod.yaml index 635b5757..750f8182 100644 --- a/recipes/fs-truncate-fd-deprecation/codemod.yaml +++ b/recipes/fs-truncate-fd-deprecation/codemod.yaml @@ -1,6 +1,6 @@ schema_version: "1.0" name: "@nodejs/fs-truncate-fd-deprecation" -version: "1.0.1" +version: "1.0.2" description: Handle DEP0081 via transforming `truncate` to `ftruncateSync` when using a file descriptor. author: Augustin Mauroy license: MIT diff --git a/recipes/fs-truncate-fd-deprecation/package.json b/recipes/fs-truncate-fd-deprecation/package.json index 62406aa8..d1389c54 100644 --- a/recipes/fs-truncate-fd-deprecation/package.json +++ b/recipes/fs-truncate-fd-deprecation/package.json @@ -1,6 +1,6 @@ { "name": "@nodejs/fs-truncate-fd-deprecation", - "version": "1.0.1", + "version": "1.0.2", "description": "Handle DEP0081 via transforming `truncate` to `ftruncateSync` when using a file descriptor.", "type": "module", "scripts": { diff --git a/recipes/fs-truncate-fd-deprecation/src/workflow.ts b/recipes/fs-truncate-fd-deprecation/src/workflow.ts index d7098ff4..77aad829 100644 --- a/recipes/fs-truncate-fd-deprecation/src/workflow.ts +++ b/recipes/fs-truncate-fd-deprecation/src/workflow.ts @@ -1,30 +1,31 @@ import { getNodeImportStatements, getNodeImportCalls, -} from "@nodejs/codemod-utils/ast-grep/import-statement"; -import { getNodeRequireCalls } from "@nodejs/codemod-utils/ast-grep/require-call"; -import { resolveBindingPath } from "@nodejs/codemod-utils/ast-grep/resolve-binding-path"; -import type { SgRoot, Edit, SgNode } from "@codemod.com/jssg-types/main"; -import type Js from "@codemod.com/jssg-types/langs/javascript"; +} from '@nodejs/codemod-utils/ast-grep/import-statement'; +import { getNodeRequireCalls } from '@nodejs/codemod-utils/ast-grep/require-call'; +import { resolveBindingPath } from '@nodejs/codemod-utils/ast-grep/resolve-binding-path'; +import type { SgRoot, Edit, SgNode } from '@codemod.com/jssg-types/main'; +import type Js from '@codemod.com/jssg-types/langs/javascript'; +import { getModuleDependencies } from '@nodejs/codemod-utils/ast-grep/module-dependencies'; // Bindings we care about and their replacements for truncate ➜ ftruncate const checks = [ { - path: "$.truncate", - prop: "truncate", - replaceFn: (name: string) => name.replace(/truncate$/, "ftruncate"), + path: '$.truncate', + prop: 'truncate', + replaceFn: (name: string) => name.replace(/truncate$/, 'ftruncate'), isSync: false, }, { - path: "$.truncateSync", - prop: "truncateSync", - replaceFn: (name: string) => name.replace(/truncateSync$/, "ftruncateSync"), + path: '$.truncateSync', + prop: 'truncateSync', + replaceFn: (name: string) => name.replace(/truncateSync$/, 'ftruncateSync'), isSync: true, }, { - path: "$.promises.truncate", - prop: "truncate", - replaceFn: (name: string) => name.replace(/truncate$/, "ftruncate"), + path: '$.promises.truncate', + prop: 'truncate', + replaceFn: (name: string) => name.replace(/truncate$/, 'ftruncate'), isSync: false, }, ]; @@ -46,7 +47,10 @@ export default function transform(root: SgRoot): string | null { const edits: Edit[] = []; // Gather fs import/require statements to resolve local binding names - const stmtNodes = [...getNodeRequireCalls(root, "fs"), ...getNodeImportStatements(root, "fs")]; + const stmtNodes = [ + ...getNodeRequireCalls(root, 'fs'), + ...getNodeImportStatements(root, 'fs'), + ]; let usedTruncate = false; let usedTruncateSync = false; @@ -73,7 +77,7 @@ export default function transform(root: SgRoot): string | null { let transformedAny = false; for (const call of calls) { - const fdMatch = call.getMatch("FD"); + const fdMatch = call.getMatch('FD'); if (!fdMatch) continue; const fdText = fdMatch.text(); @@ -87,8 +91,10 @@ export default function transform(root: SgRoot): string | null { let replacedAny = false; // Try to replace a simple identifier callee (destructured import: `truncate(...)`) - const localName = local.split(".").at(-1) || local; - const idNode = call.find({ rule: { kind: "identifier", regex: `^${localName}$` } }); + const localName = local.split('.').at(-1) || local; + const idNode = call.find({ + rule: { kind: 'identifier', regex: `^${localName}$` }, + }); if (idNode) { edits.push(idNode.replace(check.replaceFn(idNode.text()))); replacedAny = true; @@ -97,7 +103,7 @@ export default function transform(root: SgRoot): string | null { // Try to replace a member expression property (e.g. `fs.truncate(...)` or `myFS.truncate(...)`) if (!replacedAny) { const propNode = call.find({ - rule: { kind: "property_identifier", regex: `^${propName}$` }, + rule: { kind: 'property_identifier', regex: `^${propName}$` }, }); if (propNode) { edits.push(propNode.replace(check.replaceFn(propNode.text()))); @@ -114,12 +120,12 @@ export default function transform(root: SgRoot): string | null { // Update import/destructure to include/rename to ftruncate/ftruncateSync where necessary const namedNode = - stmt.find({ rule: { kind: "object_pattern" } }) || - stmt.find({ rule: { kind: "named_imports" } }); + stmt.find({ rule: { kind: 'object_pattern' } }) || + stmt.find({ rule: { kind: 'named_imports' } }); if (transformedAny && namedNode?.text().includes(propName)) { const original = namedNode.text(); const newText = original.replace( - new RegExp(`\\b${propName}\\b`, "g"), + new RegExp(`\\b${propName}\\b`, 'g'), check.replaceFn(propName), ); if (newText !== original) { @@ -136,7 +142,7 @@ export default function transform(root: SgRoot): string | null { // trigger a no-op replacement to force a reprint. This normalizes // indentation (tabs → spaces) to match expected fixtures. if (!edits.length) { - const dynImportCalls = getNodeImportCalls(root, "fs"); + const dynImportCalls = getNodeImportCalls(root, 'fs'); for (const dynImport of dynImportCalls) { edits.push(dynImport.replace(dynImport.text())); @@ -157,21 +163,28 @@ function updateImportsAndRequires( usedTruncateSync: boolean, edits: Edit[], ): void { - const importStatements = getNodeImportStatements(root, "fs"); - const requireStatements = getNodeRequireCalls(root, "fs"); + const moduleDeps = getModuleDependencies(root, 'fs'); // Update import and require statements - for (const statement of [...importStatements, ...requireStatements]) { + for (const statement of moduleDeps) { let text = statement.text(); let updated = false; - if (usedTruncate && text.includes("truncate") && !text.includes("ftruncate")) { - text = text.replace(/\btruncate\b/g, "ftruncate"); + if ( + usedTruncate && + text.includes('truncate') && + !text.includes('ftruncate') + ) { + text = text.replace(/\btruncate\b/g, 'ftruncate'); updated = true; } - if (usedTruncateSync && text.includes("truncateSync") && !text.includes("ftruncateSync")) { - text = text.replace(/\btruncateSync\b/g, "ftruncateSync"); + if ( + usedTruncateSync && + text.includes('truncateSync') && + !text.includes('ftruncateSync') + ) { + text = text.replace(/\btruncateSync\b/g, 'ftruncateSync'); updated = true; } @@ -214,7 +227,7 @@ function isLikelyFileDescriptor(param: string, rootNode: SgNode): boolean { function isInCallbackContext(param: string, rootNode: SgNode): boolean { const parameterUsages = rootNode.findAll({ rule: { - kind: "identifier", + kind: 'identifier', regex: `^${param}$`, }, }); @@ -223,20 +236,26 @@ function isInCallbackContext(param: string, rootNode: SgNode): boolean { // Check if this usage is inside a callback parameter for fs.open or open const isInOpenCallback = usage.inside({ rule: { - kind: "call_expression", + kind: 'call_expression', has: { - field: "function", + field: 'function', any: [ { - kind: "member_expression", + kind: 'member_expression', all: [ - { has: { field: "object", kind: "identifier", regex: "^fs$" } }, - { has: { field: "property", kind: "property_identifier", regex: "^open$" } }, + { has: { field: 'object', kind: 'identifier', regex: '^fs$' } }, + { + has: { + field: 'property', + kind: 'property_identifier', + regex: '^open$', + }, + }, ], }, { - kind: "identifier", - regex: "^open$", + kind: 'identifier', + regex: '^open$', }, ], }, @@ -260,32 +279,38 @@ function isAssignedFromOpenSync(param: string, rootNode: SgNode): boolean { rule: { any: [ { - kind: "variable_declarator", + kind: 'variable_declarator', all: [ - { has: { field: "name", kind: "identifier", regex: `^${param}$` } }, + { has: { field: 'name', kind: 'identifier', regex: `^${param}$` } }, { has: { - field: "value", - kind: "call_expression", + field: 'value', + kind: 'call_expression', has: { - field: "function", + field: 'function', any: [ { - kind: "member_expression", + kind: 'member_expression', all: [ - { has: { field: "object", kind: "identifier", regex: "^fs$" } }, { has: { - field: "property", - kind: "property_identifier", - regex: "^openSync$", + field: 'object', + kind: 'identifier', + regex: '^fs$', + }, + }, + { + has: { + field: 'property', + kind: 'property_identifier', + regex: '^openSync$', }, }, ], }, { - kind: "identifier", - regex: "^openSync$", + kind: 'identifier', + regex: '^openSync$', }, ], }, @@ -294,32 +319,38 @@ function isAssignedFromOpenSync(param: string, rootNode: SgNode): boolean { ], }, { - kind: "assignment_expression", + kind: 'assignment_expression', all: [ - { has: { field: "left", kind: "identifier", regex: `^${param}$` } }, + { has: { field: 'left', kind: 'identifier', regex: `^${param}$` } }, { has: { - field: "right", - kind: "call_expression", + field: 'right', + kind: 'call_expression', has: { - field: "function", + field: 'function', any: [ { - kind: "member_expression", + kind: 'member_expression', all: [ - { has: { field: "object", kind: "identifier", regex: "^fs$" } }, { has: { - field: "property", - kind: "property_identifier", - regex: "^openSync$", + field: 'object', + kind: 'identifier', + regex: '^fs$', + }, + }, + { + has: { + field: 'property', + kind: 'property_identifier', + regex: '^openSync$', }, }, ], }, { - kind: "identifier", - regex: "^openSync$", + kind: 'identifier', + regex: '^openSync$', }, ], }, diff --git a/recipes/process-assert-to-node-assert/codemod.yaml b/recipes/process-assert-to-node-assert/codemod.yaml index f100aeb9..0f27b06a 100644 --- a/recipes/process-assert-to-node-assert/codemod.yaml +++ b/recipes/process-assert-to-node-assert/codemod.yaml @@ -1,6 +1,6 @@ schema_version: "1.0" name: "@nodejs/process-assert-to-node-assert" -version: "1.0.1" +version: "1.0.2" description: Handle DEP0100 via transforming `process.assert` to `node:assert`. author: matheusmorett2 license: MIT diff --git a/recipes/process-assert-to-node-assert/package.json b/recipes/process-assert-to-node-assert/package.json index a0dbae2b..f86512ab 100644 --- a/recipes/process-assert-to-node-assert/package.json +++ b/recipes/process-assert-to-node-assert/package.json @@ -1,6 +1,6 @@ { "name": "@nodejs/process-assert-to-node-assert", - "version": "1.0.1", + "version": "1.0.2", "description": "Handle DEP0100 via transforming `process.assert` to `node:assert`.", "type": "module", "scripts": { diff --git a/recipes/process-assert-to-node-assert/src/workflow.ts b/recipes/process-assert-to-node-assert/src/workflow.ts index 568358ce..5f2346d0 100644 --- a/recipes/process-assert-to-node-assert/src/workflow.ts +++ b/recipes/process-assert-to-node-assert/src/workflow.ts @@ -1,11 +1,6 @@ import { EOL } from 'node:os'; import { readFileSync } from 'node:fs'; import { join } from 'node:path'; -import { getNodeRequireCalls } from '@nodejs/codemod-utils/ast-grep/require-call'; -import { - getNodeImportCalls, - getNodeImportStatements, -} from '@nodejs/codemod-utils/ast-grep/import-statement'; import { resolveBindingPath } from '@nodejs/codemod-utils/ast-grep/resolve-binding-path'; import { removeBinding } from '@nodejs/codemod-utils/ast-grep/remove-binding'; import { removeLines } from '@nodejs/codemod-utils/ast-grep/remove-lines'; @@ -17,6 +12,7 @@ import type { SgRoot, } from '@codemod.com/jssg-types/main'; import type JS from '@codemod.com/jssg-types/langs/javascript'; +import { getModuleDependencies } from '@nodejs/codemod-utils/ast-grep/module-dependencies'; type ReplaceRule = { importNode?: SgNode; @@ -60,10 +56,7 @@ export default function transform(root: SgRoot): string | null { const processImportsToRemove = new Set>(); - const requireCalls = getNodeRequireCalls(root, 'process'); - const importStatements = getNodeImportStatements(root, 'process'); - const importCalls = getNodeImportCalls(root, 'process'); - const allImports = [...requireCalls, ...importStatements, ...importCalls]; + const allImports = getModuleDependencies(root, 'process'); const processUsages = rootNode.findAll({ rule: { kind: 'member_expression', @@ -183,11 +176,7 @@ export default function transform(root: SgRoot): string | null { if (edits.length === 0 && linesToRemove) return sourceCode; - const alreadyRequiringAssert = getNodeRequireCalls(root, 'assert'); - const alreadyImportingAssert = getNodeImportStatements(root, 'assert'); - - if (alreadyRequiringAssert.length || alreadyImportingAssert.length) - return sourceCode; + if (getModuleDependencies(root, 'assert').length) return sourceCode; /** * Re add the appropriate import or require statement for `node:assert` diff --git a/recipes/repl-classes-with-new/codemod.yaml b/recipes/repl-classes-with-new/codemod.yaml index da7f9645..0424c665 100644 --- a/recipes/repl-classes-with-new/codemod.yaml +++ b/recipes/repl-classes-with-new/codemod.yaml @@ -1,6 +1,6 @@ schema_version: "1.0" name: "@nodejs/repl-classes-with-new" -version: "1.0.1" +version: "1.0.2" description: "Handle DEP0185: Instantiating node:repl classes without new" author: GitHub Copilot license: MIT diff --git a/recipes/repl-classes-with-new/package.json b/recipes/repl-classes-with-new/package.json index e17b09bc..3700f09f 100644 --- a/recipes/repl-classes-with-new/package.json +++ b/recipes/repl-classes-with-new/package.json @@ -1,6 +1,6 @@ { "name": "@nodejs/repl-classes-with-new", - "version": "1.0.1", + "version": "1.0.2", "description": "Handle DEP0185: Instantiating node:repl classes without new.", "type": "module", "scripts": { diff --git a/recipes/repl-classes-with-new/src/workflow.ts b/recipes/repl-classes-with-new/src/workflow.ts index a1d4b9cb..324ca241 100644 --- a/recipes/repl-classes-with-new/src/workflow.ts +++ b/recipes/repl-classes-with-new/src/workflow.ts @@ -1,16 +1,12 @@ -import { getNodeImportStatements, getNodeImportCalls } from '@nodejs/codemod-utils/ast-grep/import-statement'; -import { getNodeRequireCalls } from '@nodejs/codemod-utils/ast-grep/require-call'; import { resolveBindingPath } from '@nodejs/codemod-utils/ast-grep/resolve-binding-path'; import type { SgRoot, Edit, SgNode } from '@codemod.com/jssg-types/main'; -import type JS from "@codemod.com/jssg-types/langs/javascript"; +import type JS from '@codemod.com/jssg-types/langs/javascript'; +import { getModuleDependencies } from '@nodejs/codemod-utils/ast-grep/module-dependencies'; /** * Classes of the repl module */ -const CLASS_NAMES = [ - 'REPLServer', - 'Recoverable', -]; +const CLASS_NAMES = ['REPLServer', 'Recoverable']; /** * Transform function that converts deprecated node:repl classes to use the `new` keyword @@ -25,11 +21,7 @@ export default function transform(root: SgRoot): string | null { const rootNode = root.root(); const edits: Edit[] = []; - const allStatementNodes = [ - ...getNodeImportStatements(root, 'repl'), - ...getNodeRequireCalls(root, 'repl'), - ...getNodeImportCalls(root, 'repl'), - ]; + const allStatementNodes = getModuleDependencies(root, 'repl'); // if no imports are present it means that we don't need to process the file if (!allStatementNodes.length) return null; diff --git a/recipes/slow-buffer-to-buffer-alloc-unsafe-slow/codemod.yaml b/recipes/slow-buffer-to-buffer-alloc-unsafe-slow/codemod.yaml index f4616bc9..4c30285b 100644 --- a/recipes/slow-buffer-to-buffer-alloc-unsafe-slow/codemod.yaml +++ b/recipes/slow-buffer-to-buffer-alloc-unsafe-slow/codemod.yaml @@ -1,6 +1,6 @@ schema_version: "1.0" name: "@nodejs/slow-buffer-to-buffer-alloc-unsafe-slow" -version: "1.0.1" +version: "1.0.2" description: Handle DEP0030 via transforming SlowBuffer usage to Buffer.allocUnsafeSlow(). author: lluisemper(Lluis Semper Lloret) license: MIT diff --git a/recipes/slow-buffer-to-buffer-alloc-unsafe-slow/package.json b/recipes/slow-buffer-to-buffer-alloc-unsafe-slow/package.json index bf9dca9e..7c2d600c 100644 --- a/recipes/slow-buffer-to-buffer-alloc-unsafe-slow/package.json +++ b/recipes/slow-buffer-to-buffer-alloc-unsafe-slow/package.json @@ -1,6 +1,6 @@ { "name": "@nodejs/slow-buffer-to-buffer-alloc-unsafe-slow", - "version": "1.0.1", + "version": "1.0.2", "description": "Handle DEP0030 via transforming SlowBuffer usage to Buffer.allocUnsafeSlow().", "type": "module", "scripts": { diff --git a/recipes/slow-buffer-to-buffer-alloc-unsafe-slow/src/workflow.ts b/recipes/slow-buffer-to-buffer-alloc-unsafe-slow/src/workflow.ts index 1fb29b64..3729af0e 100644 --- a/recipes/slow-buffer-to-buffer-alloc-unsafe-slow/src/workflow.ts +++ b/recipes/slow-buffer-to-buffer-alloc-unsafe-slow/src/workflow.ts @@ -7,6 +7,7 @@ import { import { getNodeRequireCalls } from "@nodejs/codemod-utils/ast-grep/require-call"; import { resolveBindingPath } from "@nodejs/codemod-utils/ast-grep/resolve-binding-path"; import { updateBinding } from "@nodejs/codemod-utils/ast-grep/update-binding"; +import { getModuleDependencies } from "@nodejs/codemod-utils/ast-grep/module-dependencies"; type StatementType = "import-dynamic" | "import-static" | "require"; @@ -244,11 +245,7 @@ function transformSlowBufferCall(match: SgNode, binding: string, edits: Edit */ function processSlowBufferUsage(rootNode: SgNode, edits: Edit[]): void { const root = rootNode.getRoot(); - const allStatements = [ - ...getNodeImportStatements(root, "buffer"), - ...getNodeRequireCalls(root, "buffer"), - ...getNodeImportCalls(root, "buffer"), - ]; + const allStatements = getModuleDependencies(root, "buffer") // Process bound SlowBuffer calls (from imports/requires) for (const importNode of allStatements) { diff --git a/recipes/util-extend-to-object-assign/codemod.yaml b/recipes/util-extend-to-object-assign/codemod.yaml index 249d8af2..8f0fba14 100644 --- a/recipes/util-extend-to-object-assign/codemod.yaml +++ b/recipes/util-extend-to-object-assign/codemod.yaml @@ -1,6 +1,6 @@ schema_version: "1.0" name: "@nodejs/util-extend-to-object-assign" -version: 1.0.0 +version: 1.0.1 description: "Handle DEP0060 by replacing `util._extend()` with `Object.assign()`." author: GitHub Copilot license: MIT diff --git a/recipes/util-extend-to-object-assign/package.json b/recipes/util-extend-to-object-assign/package.json index 8a473a66..ef8c1fa3 100644 --- a/recipes/util-extend-to-object-assign/package.json +++ b/recipes/util-extend-to-object-assign/package.json @@ -1,6 +1,6 @@ { "name": "@nodejs/util-extend-to-object-assign", - "version": "1.0.0", + "version": "1.0.1", "description": "Handle DEP0060 by replacing `util._extend()` with `Object.assign()`.", "type": "module", "scripts": { diff --git a/recipes/util-extend-to-object-assign/src/workflow.ts b/recipes/util-extend-to-object-assign/src/workflow.ts index 43d9d90a..8c61fb58 100644 --- a/recipes/util-extend-to-object-assign/src/workflow.ts +++ b/recipes/util-extend-to-object-assign/src/workflow.ts @@ -1,10 +1,7 @@ import { - getNodeImportStatements, - getNodeImportCalls, getDefaultImportIdentifier, } from '@nodejs/codemod-utils/ast-grep/import-statement'; import { - getNodeRequireCalls, getRequireNamespaceIdentifier, } from '@nodejs/codemod-utils/ast-grep/require-call'; import { removeBinding } from '@nodejs/codemod-utils/ast-grep/remove-binding'; @@ -12,6 +9,7 @@ import { resolveBindingPath } from '@nodejs/codemod-utils/ast-grep/resolve-bindi import { removeLines } from '@nodejs/codemod-utils/ast-grep/remove-lines'; import type { SgRoot, Edit, Range } from '@codemod.com/jssg-types/main'; import type JS from '@codemod.com/jssg-types/langs/javascript'; +import { getModuleDependencies } from '@nodejs/codemod-utils/ast-grep/module-dependencies'; const method = '_extend'; @@ -33,11 +31,7 @@ export default function transform(root: SgRoot): string | null { const linesToRemove: Range[] = []; const editRanges: Range[] = []; - const importOrRequireNodes = [ - ...getNodeRequireCalls(root, 'util'), - ...getNodeImportStatements(root, 'util'), - ...getNodeImportCalls(root, 'util'), - ]; + const importOrRequireNodes = getModuleDependencies(root, 'util') // If no util imports/requires, nothing to do if (!importOrRequireNodes.length) return null; diff --git a/recipes/util-is/codemod.yaml b/recipes/util-is/codemod.yaml index 65762c78..bed936a1 100644 --- a/recipes/util-is/codemod.yaml +++ b/recipes/util-is/codemod.yaml @@ -1,6 +1,6 @@ schema_version: "1.0" name: "@nodejs/util-is" -version: 1.0.1 +version: 1.0.2 description: "Replaces deprecated `util.is*()` methods with their modern equivalents." author: Augustin Mauroy license: MIT diff --git a/recipes/util-is/package.json b/recipes/util-is/package.json index 8e9d5a7e..1e42915e 100644 --- a/recipes/util-is/package.json +++ b/recipes/util-is/package.json @@ -1,6 +1,6 @@ { "name": "@nodejs/util-is", - "version": "1.0.1", + "version": "1.0.2", "description": "Replaces deprecated `util.is*()` methods with their modern equivalents.", "type": "module", "scripts": { diff --git a/recipes/util-is/src/workflow.ts b/recipes/util-is/src/workflow.ts index 57cbaa68..bc0359a9 100644 --- a/recipes/util-is/src/workflow.ts +++ b/recipes/util-is/src/workflow.ts @@ -1,7 +1,6 @@ import { getNodeImportStatements, getDefaultImportIdentifier, - getNodeImportCalls, } from '@nodejs/codemod-utils/ast-grep/import-statement'; import { getNodeRequireCalls, @@ -12,6 +11,7 @@ import { resolveBindingPath } from '@nodejs/codemod-utils/ast-grep/resolve-bindi import { removeLines } from '@nodejs/codemod-utils/ast-grep/remove-lines'; import type { SgRoot, SgNode, Edit, Range } from '@codemod.com/jssg-types/main'; import type JS from '@codemod.com/jssg-types/langs/javascript'; +import { getModuleDependencies } from '@nodejs/codemod-utils/ast-grep/module-dependencies'; // Clean up unused imports using removeBinding const allIsMethods = [ @@ -91,11 +91,7 @@ export default function transform(root: SgRoot): string | null { const nonIsMethodsUsed = new Set(); // Collect util import/require nodes once - const importOrRequireNodes = [ - ...getNodeRequireCalls(root, 'util'), - ...getNodeImportStatements(root, 'util'), - ...getNodeImportCalls(root, 'util'), - ]; + const importOrRequireNodes = getModuleDependencies(root, 'util'); // If no import is found that means we can skip transformation on this file if (!importOrRequireNodes.length) return null; diff --git a/recipes/util-print-to-console-log/codemod.yaml b/recipes/util-print-to-console-log/codemod.yaml index 81d5c3b3..51a47c42 100644 --- a/recipes/util-print-to-console-log/codemod.yaml +++ b/recipes/util-print-to-console-log/codemod.yaml @@ -1,6 +1,6 @@ schema_version: "1.0" name: "@nodejs/util-print-to-console-log" -version: "1.0.1" +version: "1.0.2" description: Handle DEP0026, DEP0027, DEP0028, DEP0029 via transforming `util.print|puts|debug|error()` to `console.log|error()` author: Bruno Rodrigues license: MIT diff --git a/recipes/util-print-to-console-log/package.json b/recipes/util-print-to-console-log/package.json index d5457d3b..39567c23 100644 --- a/recipes/util-print-to-console-log/package.json +++ b/recipes/util-print-to-console-log/package.json @@ -1,6 +1,6 @@ { "name": "@nodejs/util-print-to-console-log", - "version": "1.0.1", + "version": "1.0.2", "description": "Handle DEP0026, DEP0027, DEP0028, DEP0029 via transforming `util.print|puts|debug|error()` to `console.log|error()`", "type": "module", "scripts": { diff --git a/recipes/util-print-to-console-log/src/workflow.ts b/recipes/util-print-to-console-log/src/workflow.ts index dc88e4ac..78c2eb51 100644 --- a/recipes/util-print-to-console-log/src/workflow.ts +++ b/recipes/util-print-to-console-log/src/workflow.ts @@ -1,5 +1,3 @@ -import { getNodeRequireCalls } from '@nodejs/codemod-utils/ast-grep/require-call'; -import { getNodeImportStatements } from '@nodejs/codemod-utils/ast-grep/import-statement'; import { resolveBindingPath } from '@nodejs/codemod-utils/ast-grep/resolve-binding-path'; import { removeBinding } from '@nodejs/codemod-utils/ast-grep/remove-binding'; import { removeLines } from '@nodejs/codemod-utils/ast-grep/remove-lines'; @@ -11,6 +9,7 @@ import type { SgRoot, } from '@codemod.com/jssg-types/main'; import type Js from '@codemod.com/jssg-types/langs/javascript'; +import { getModuleDependencies } from '@nodejs/codemod-utils/ast-grep/module-dependencies'; type BindingToReplace = { rule: Rule; @@ -59,10 +58,7 @@ export default function transform(root: SgRoot): string | null { const linesToRemove: Range[] = []; const bindsToReplace: BindingToReplace[] = []; - const importRequireStatement = [ - ...getNodeRequireCalls(root, 'util'), - ...getNodeImportStatements(root, 'util'), - ]; + const importRequireStatement = getModuleDependencies(root, 'util'); // if no imports are present it means that we don't need to process the file if (!importRequireStatement.length) return null; diff --git a/recipes/zlib-bytesread-to-byteswritten/codemod.yaml b/recipes/zlib-bytesread-to-byteswritten/codemod.yaml index a3b381d1..644a320b 100644 --- a/recipes/zlib-bytesread-to-byteswritten/codemod.yaml +++ b/recipes/zlib-bytesread-to-byteswritten/codemod.yaml @@ -1,6 +1,6 @@ schema_version: "1.0" name: "@nodejs/zlib-bytesread-to-byteswritten" -version: 1.0.0 +version: 1.0.1 description: Handle DEP0108 by replacing deprecated `zlib.bytesRead` with `zlib.bytesWritten` in Node.js transform streams author: Elie Khoury license: MIT diff --git a/recipes/zlib-bytesread-to-byteswritten/package.json b/recipes/zlib-bytesread-to-byteswritten/package.json index 18b7619b..51db2377 100644 --- a/recipes/zlib-bytesread-to-byteswritten/package.json +++ b/recipes/zlib-bytesread-to-byteswritten/package.json @@ -1,6 +1,6 @@ { "name": "@nodejs/zlib-bytesread-to-byteswritten", - "version": "1.0.0", + "version": "1.0.1", "description": "Replace deprecated `zlib.bytesRead` with `zlib.bytesWritten` in Node.js transform streams", "type": "module", "scripts": { diff --git a/recipes/zlib-bytesread-to-byteswritten/src/workflow.ts b/recipes/zlib-bytesread-to-byteswritten/src/workflow.ts index a23d0d7b..6e953707 100644 --- a/recipes/zlib-bytesread-to-byteswritten/src/workflow.ts +++ b/recipes/zlib-bytesread-to-byteswritten/src/workflow.ts @@ -1,12 +1,8 @@ -import { getNodeRequireCalls } from '@nodejs/codemod-utils/ast-grep/require-call'; -import { - getNodeImportCalls, - getNodeImportStatements, -} from '@nodejs/codemod-utils/ast-grep/import-statement'; import { resolveBindingPath } from '@nodejs/codemod-utils/ast-grep/resolve-binding-path'; import { removeLines } from '@nodejs/codemod-utils/ast-grep/remove-lines'; import type { Edit, Range, SgRoot } from '@codemod.com/jssg-types/main'; import type Js from '@codemod.com/jssg-types/langs/javascript'; +import { getModuleDependencies } from '@nodejs/codemod-utils/ast-grep/module-dependencies'; const ZLIB_FACTORIES = [ 'createGzip', @@ -30,11 +26,7 @@ export default function transform(root: SgRoot): string | null { const linesToRemove: Range[] = []; // 1 Find all static and dynamic zlib imports/requires - const importNodes = [ - ...getNodeRequireCalls(root, 'node:zlib'), - ...getNodeImportStatements(root, 'node:zlib'), - ...getNodeImportCalls(root, 'node:zlib'), - ]; + const importNodes = getModuleDependencies(root, 'node:zlib'); // If no import is found that means we can skip transformation on this file if (!importNodes.length) return null; diff --git a/utils/README.md b/utils/README.md index 45e43bd7..e041cf58 100644 --- a/utils/README.md +++ b/utils/README.md @@ -17,6 +17,21 @@ These utilities provide battle-tested solutions for common codemod operations, r ### Import and Require Detection +#### `getModuleDependencies(rootNode, nodeModuleName)` + +Finds all module import/require statements for a specific Node.js module. +Under the hood, calls `getNodeRequireCalls`, `getNodeImportStatements`, `getNodeImportCalls`, and +combines the return values. + +```typescript +import { getNodeImportStatements } from '@nodejs/codemod-utils'; + +// Finds: import fs from 'fs'; import { readFile } from 'node:fs'; +// Finds: const fs = require('fs') +// Finds: const fs = await import('fs') +const fsImports = getModuleDependencies(ast, 'fs'); +``` + #### `getNodeImportStatements(rootNode, nodeModuleName)` Finds all ES module import statements for a specific Node.js module. diff --git a/utils/src/ast-grep/module-dependencies.test.ts b/utils/src/ast-grep/module-dependencies.test.ts new file mode 100644 index 00000000..34e95e9b --- /dev/null +++ b/utils/src/ast-grep/module-dependencies.test.ts @@ -0,0 +1,33 @@ +import assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; +import astGrep from '@ast-grep/napi'; +import dedent from 'dedent'; +import { getModuleDependencies } from './module-dependencies.ts'; +import type Js from '@codemod.com/jssg-types/langs/javascript'; +import type { SgRoot } from '@codemod.com/jssg-types/main'; + +describe('import-statement', () => { + const code = dedent` + import fs from 'fs'; + const fs = require('fs'); + const variable = 'node:tls' + import(variable).then(console.log); + import * as test from 'test' + `; + const ast = astGrep.parse(astGrep.Lang.JavaScript, code) as SgRoot; + + it('should return two fs dependencies', () => { + const fsImports = getModuleDependencies(ast, 'fs'); + assert.equal(2, fsImports.length); + }); + + it('should return import that use variable', () => { + const tlsImport = getModuleDependencies(ast, 'tls'); + assert.equal(1, tlsImport.length); + }); + + it('should return default import ', () => { + const testImports = getModuleDependencies(ast, 'test'); + assert.equal(1, testImports.length); + }); +}); diff --git a/utils/src/ast-grep/module-dependencies.ts b/utils/src/ast-grep/module-dependencies.ts new file mode 100644 index 00000000..cb49b5fd --- /dev/null +++ b/utils/src/ast-grep/module-dependencies.ts @@ -0,0 +1,16 @@ +import type { SgRoot } from '@codemod.com/jssg-types/main'; +import type Js from '@codemod.com/jssg-types/langs/javascript'; + +import { + getNodeImportStatements, + getNodeImportCalls, +} from './import-statement.ts'; +import { getNodeRequireCalls } from './require-call.ts'; + +export function getModuleDependencies(node: SgRoot, name: string) { + return [ + ...getNodeRequireCalls(node, name), + ...getNodeImportStatements(node, name), + ...getNodeImportCalls(node, name), + ]; +} diff --git a/utils/src/ast-grep/resolve-binding-path.ts b/utils/src/ast-grep/resolve-binding-path.ts index 42a06391..25d6b944 100644 --- a/utils/src/ast-grep/resolve-binding-path.ts +++ b/utils/src/ast-grep/resolve-binding-path.ts @@ -42,7 +42,7 @@ export function resolveBindingPath(node: SgNode, path: string) { if (!supportedKinds.includes(rootKind.toString())) { throw Error( - `Invalid node kind. To resolve binding path, one of these types must be provided: ${supportedKinds.join(', ')}`, + `The node kind "${rootKind}" is invalid. To resolve binding path, one of these types must be provided: ${supportedKinds.join(', ')}`, ); }