From 21b6e0b71182b7556b8b4886b6a702befb4dee6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= Date: Sun, 3 Aug 2025 22:59:12 +0530 Subject: [PATCH 01/33] feat(tls.createSecureContext): introduce --- recipes/crypto-createCredentials/README.md | 51 +++ recipes/crypto-createCredentials/codemod.yaml | 21 ++ recipes/crypto-createCredentials/package.json | 25 ++ .../crypto-createCredentials/src/workflow.ts | 328 ++++++++++++++++++ .../tests/expected/file-1.js | 7 + .../tests/expected/file-2.js | 6 + .../tests/expected/file-3.js | 10 + .../tests/expected/file-4.mjs | 7 + .../tests/expected/file-5.mjs | 6 + .../tests/input/file-1.js | 7 + .../tests/input/file-2.js | 6 + .../tests/input/file-3.js | 9 + .../tests/input/file-4.mjs | 7 + .../tests/input/file-5.mjs | 6 + .../crypto-createCredentials/tsconfig.json | 23 ++ .../crypto-createCredentials/workflow.yaml | 25 ++ 16 files changed, 544 insertions(+) create mode 100644 recipes/crypto-createCredentials/README.md create mode 100644 recipes/crypto-createCredentials/codemod.yaml create mode 100644 recipes/crypto-createCredentials/package.json create mode 100644 recipes/crypto-createCredentials/src/workflow.ts create mode 100644 recipes/crypto-createCredentials/tests/expected/file-1.js create mode 100644 recipes/crypto-createCredentials/tests/expected/file-2.js create mode 100644 recipes/crypto-createCredentials/tests/expected/file-3.js create mode 100644 recipes/crypto-createCredentials/tests/expected/file-4.mjs create mode 100644 recipes/crypto-createCredentials/tests/expected/file-5.mjs create mode 100644 recipes/crypto-createCredentials/tests/input/file-1.js create mode 100644 recipes/crypto-createCredentials/tests/input/file-2.js create mode 100644 recipes/crypto-createCredentials/tests/input/file-3.js create mode 100644 recipes/crypto-createCredentials/tests/input/file-4.mjs create mode 100644 recipes/crypto-createCredentials/tests/input/file-5.mjs create mode 100644 recipes/crypto-createCredentials/tsconfig.json create mode 100644 recipes/crypto-createCredentials/workflow.yaml diff --git a/recipes/crypto-createCredentials/README.md b/recipes/crypto-createCredentials/README.md new file mode 100644 index 00000000..b3bace49 --- /dev/null +++ b/recipes/crypto-createCredentials/README.md @@ -0,0 +1,51 @@ +# `fs.rmdir` DEP0147 + +This recipe provides a guide for migrating from the deprecated `crypto.createCredentials` method in Node.js. + +See [DEP0010](https://nodejs.org/api/deprecations.html#DEP0010). + +## Examples + +**Before:** + +```js +// Using the deprecated createCredentials from node:crypto +const { createCredentials } = require('node:crypto'); + +const credentials = createCredentials({ + key: privateKey, + cert: certificate, + ca: [caCertificate] +}); + +// Using destructuring with ES module imports +import { createCredentials } from 'node:crypto'; + +const credentials = createCredentials({ + key: privateKey, + cert: certificate, + ca: [caCertificate] +}); +``` + +**After:** + +```js +// Updated to use createSecureContext from node:tls +const { createSecureContext } = require('node:tls'); + +const credentials = createSecureContext({ + key: privateKey, + cert: certificate, + ca: [caCertificate] +}); + +// Updated ES module import +import { createSecureContext } from 'node:tls'; + +const credentials = createSecureContext({ + key: privateKey, + cert: certificate, + ca: [caCertificate] +}); +``` diff --git a/recipes/crypto-createCredentials/codemod.yaml b/recipes/crypto-createCredentials/codemod.yaml new file mode 100644 index 00000000..eded0cba --- /dev/null +++ b/recipes/crypto-createCredentials/codemod.yaml @@ -0,0 +1,21 @@ +schema_version: "1.0" +name: "@nodejs/crypto-createCredentials" +version: 1.0.0 +description: Handle DEP0010 via transforming `crypto.createCredentials` to `tls.createSecureContext` +author: 0hmx +license: MIT +workflow: workflow.yaml +category: migration + +targets: + languages: + - javascript + - typescript + +keywords: + - transformation + - migration + +registry: + access: public + visibility: public diff --git a/recipes/crypto-createCredentials/package.json b/recipes/crypto-createCredentials/package.json new file mode 100644 index 00000000..8dbd7078 --- /dev/null +++ b/recipes/crypto-createCredentials/package.json @@ -0,0 +1,25 @@ +{ + "name": "@nodejs/crypto-createCredentials", + "version": "1.0.0", + "description": "Handle DEP0010 via transforming `crypto.createCredentials` to `tls.createSecureContext` with the appropriate options.", + "type": "module", + "scripts": { + "test": "npx codemod@next jssg test -l typescript ./src/workflow.ts ./" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/nodejs/userland-migrations.git", + "directory": "recipes/rmdirs", + "bugs": "https://github.com/nodejs/userland-migrations/issues" + }, + "author": "0hmx", + "license": "MIT", + "homepage": "https://github.com/nodejs/userland-migrations/blob/main/recipes/rmdirs/README.md", + "devDependencies": { + "@types/node": "^24.0.3", + "@codemod.com/jssg-types": "^1.0.3" + }, + "dependencies": { + "@nodejs/codemod-utils": "*" + } +} diff --git a/recipes/crypto-createCredentials/src/workflow.ts b/recipes/crypto-createCredentials/src/workflow.ts new file mode 100644 index 00000000..397b3044 --- /dev/null +++ b/recipes/crypto-createCredentials/src/workflow.ts @@ -0,0 +1,328 @@ +import type { SgRoot, Edit, SgNode } from "@codemod.com/jssg-types/main"; + +type ImportType = + | 'DESTRUCTURED_REQUIRE' + | 'NAMESPACE_REQUIRE' + | 'DESTRUCTURED_IMPORT' + | 'NAMESPACE_IMPORT'; + +function ensureTlsImport( + rootNode: SgNode, + edits: Edit[], + importType: ImportType, + tlsImportState: { added: boolean; identifier: string }, +): string { + if (tlsImportState.added) { + return tlsImportState.identifier; + } + + const isEsm = + importType === 'DESTRUCTURED_IMPORT' || importType === 'NAMESPACE_IMPORT'; + const moduleSpecifier = 'node:tls'; + + const findRule = { + rule: { + kind: isEsm ? 'import_statement' : 'variable_declarator', + has: { + field: isEsm ? 'source' : 'value', + has: { regex: `^['"](node:)?tls['"]$` }, + }, + }, + }; + const existingTlsImport = rootNode.find(findRule); + + if (existingTlsImport) { + const nameNode = existingTlsImport.field('name'); + if (nameNode?.is('identifier')) { + tlsImportState.identifier = nameNode.text(); + tlsImportState.added = true; + return tlsImportState.identifier; + } + } + + const firstNode = rootNode.children()[0]; + if (firstNode) { + const newImportText = isEsm + ? `import * as ${tlsImportState.identifier} from '${moduleSpecifier}';\n` + : `const ${tlsImportState.identifier} = require('${moduleSpecifier}');\n`; + + const edit = { + startPos: firstNode.range().start.index, + endPos: firstNode.range().start.index, + insertedText: newImportText, + }; + edits.push(edit); + } + + tlsImportState.added = true; + return tlsImportState.identifier; +} + +function handleDestructuredImport( + statement: SgNode, + rootNode: SgNode, + edits: Edit[], + tlsImportState: { added: boolean }, +) { + const isEsm = statement.kind() === 'import_statement'; + + let specifiersNode; + if (isEsm) { + specifiersNode = + statement.find({ rule: { kind: 'named_imports' } }) ?? + statement.field('imports'); + } else { + const declaratorFindRule = { + rule: { + kind: 'variable_declarator', + has: { + field: 'value', + kind: 'call_expression', + has: { + field: 'arguments', + has: { regex: "^['\"](node:)?crypto['\"]$" }, + }, + }, + }, + }; + const declarator = statement.find(declaratorFindRule); + specifiersNode = declarator?.field('name'); + } + + if (!specifiersNode) { + return false; + } + + const findPropsRule = { + rule: { + any: [ + { kind: 'shorthand_property_identifier_pattern' }, + { kind: 'import_specifier' }, + ], + }, + }; + const destructuredProps = specifiersNode.findAll(findPropsRule); + + const createCredentialsNode = destructuredProps.find( + (id) => id.text() === 'createCredentials', + ); + + if (!createCredentialsNode) { + return false; + } + + const usagesFindRule = { + rule: { + kind: 'call_expression', + has: { + field: 'function', + kind: 'identifier', + regex: '^createCredentials$', + }, + }, + }; + const usages = rootNode.findAll(usagesFindRule); + for (const usage of usages) { + const functionIdentifier = usage.field('function'); + if (functionIdentifier) { + edits.push(functionIdentifier.replace('createSecureContext')); + } + } + + const newImportModule = 'node:tls'; + const newImportFunction = 'createSecureContext'; + const newImportStatement = isEsm + ? `import { ${newImportFunction} } from '${newImportModule}';` + : `const { ${newImportFunction} } = require('${newImportModule}');`; + + if (destructuredProps.length === 1) { + edits.push(statement.replace(newImportStatement)); + tlsImportState.added = true; + } else { + const otherProps = destructuredProps + .filter((id) => id.text() !== 'createCredentials') + .map((id) => id.text()); + + const newDestructuredString = `{ ${otherProps.join(', ')} }`; + edits.push(specifiersNode.replace(newDestructuredString)); + + if (!tlsImportState.added) { + const newEdit = { + startPos: statement.range().end.index, + endPos: statement.range().end.index, + insertedText: `\n${newImportStatement}`, + }; + edits.push(newEdit); + tlsImportState.added = true; + } else { + } + } + + return true; +} + +function handleNamespaceImport( + importOrDeclarator: SgNode, + rootNode: SgNode, + edits: Edit[], + importType: ImportType, + tlsImportState: { added: boolean; identifier: string }, +) { + const isEsm = importType === 'NAMESPACE_IMPORT'; + const nameNode = isEsm + ? importOrDeclarator.find({ rule: { kind: 'namespace_import' } })?.field('name') + : importOrDeclarator.field('name'); + + if (!nameNode) { + return false; + } + const namespaceName = nameNode.text(); + + const memberAccessFindRule = { + rule: { + kind: 'member_expression', + all: [ + { has: { field: 'object', regex: `^${namespaceName}$` } }, + { has: { field: 'property', regex: '^createCredentials$' } }, + ], + }, + }; + const memberAccessUsages = rootNode.findAll(memberAccessFindRule); + + if (memberAccessUsages.length > 0) { + + const allUsagesFindRule = { + rule: { + kind: 'member_expression', + has: { field: 'object', regex: `^${namespaceName}$` }, + }, + }; + const allUsages = rootNode.findAll(allUsagesFindRule); + + if (allUsages.length === memberAccessUsages.length) { + const newTlsIdentifier = tlsImportState.identifier; + const newImportModule = 'node:tls'; + const newImportStatement = isEsm + ? `import * as ${newTlsIdentifier} from '${newImportModule}';` + : `const ${newTlsIdentifier} = require('${newImportModule}');`; + + const nodeToReplace = isEsm + ? importOrDeclarator + : importOrDeclarator.parent(); + edits.push(nodeToReplace.replace(newImportStatement)); + tlsImportState.added = true; + + for (const usage of memberAccessUsages) { + const replacementText = `${newTlsIdentifier}.createSecureContext`; + edits.push(usage.replace(replacementText)); + } + } else { + let tlsIdentifier = ensureTlsImport( + rootNode, + edits, + importType, + tlsImportState, + ); + + for (const usage of memberAccessUsages) { + const replacementText = `${tlsIdentifier}.createSecureContext`; + edits.push(usage.replace(replacementText)); + } + } + return true; + } else { + return false; + } +} + +export default function transform(root: SgRoot): string | null { + const edits: Edit[] = []; + const rootNode = root.root(); + let wasTransformed = false; + const tlsImportState = { added: false, identifier: 'tls' }; + + const cryptoImportsRule = { + rule: { + any: [ + { + kind: 'variable_declarator', + has: { + field: 'value', + kind: 'call_expression', + has: { + field: 'arguments', + has: { regex: "^['\"](node:)?crypto['\"]$" }, + }, + }, + }, + { + kind: 'import_statement', + has: { field: 'source', regex: "^['\"](node:)?crypto['\"]$" }, + }, + ], + }, + }; + const cryptoImports = rootNode.findAll(cryptoImportsRule); + + for (const importMatch of cryptoImports) { + const nameNode = importMatch.field('imports') ?? importMatch.field('name'); + let importType: ImportType | undefined; + + if (importMatch.kind() === 'import_statement') { + if (importMatch.find({ rule: { kind: 'namespace_import' } })) { + importType = 'NAMESPACE_IMPORT'; + } else { + importType = 'DESTRUCTURED_IMPORT'; + } + } else { + // variable_declarator for require + if (nameNode?.is('object_pattern')) { + importType = 'DESTRUCTURED_REQUIRE'; + } else if (nameNode?.is('identifier')) { + importType = 'NAMESPACE_REQUIRE'; + } + } + + if (importType === undefined) { + continue; + } + + let transformedByType = false; + switch (importType) { + case 'DESTRUCTURED_REQUIRE': + case 'DESTRUCTURED_IMPORT': + const statement = + importType === 'DESTRUCTURED_REQUIRE' + ? importMatch.parent() + : importMatch; + transformedByType = handleDestructuredImport( + statement, + rootNode, + edits, + tlsImportState, + ); + break; + + case 'NAMESPACE_REQUIRE': + case 'NAMESPACE_IMPORT': + transformedByType = handleNamespaceImport( + importMatch, + rootNode, + edits, + importType, + tlsImportState, + ); + break; + } + + if (transformedByType) { + wasTransformed = true; + } + } + + if (wasTransformed) { + return rootNode.commitEdits(edits); + } else { + return null; + } +} diff --git a/recipes/crypto-createCredentials/tests/expected/file-1.js b/recipes/crypto-createCredentials/tests/expected/file-1.js new file mode 100644 index 00000000..059c04bf --- /dev/null +++ b/recipes/crypto-createCredentials/tests/expected/file-1.js @@ -0,0 +1,7 @@ +const { createSecureContext } = require('node:tls'); + +const credentials = createSecureContext({ + key: privateKey, + cert: certificate, + ca: [caCertificate] +}); diff --git a/recipes/crypto-createCredentials/tests/expected/file-2.js b/recipes/crypto-createCredentials/tests/expected/file-2.js new file mode 100644 index 00000000..74454515 --- /dev/null +++ b/recipes/crypto-createCredentials/tests/expected/file-2.js @@ -0,0 +1,6 @@ +const tls = require('node:tls'); + +const credentials = tls.createSecureContext({ + key: fs.readFileSync('server-key.pem'), + cert: fs.readFileSync('server-cert.pem') +}); diff --git a/recipes/crypto-createCredentials/tests/expected/file-3.js b/recipes/crypto-createCredentials/tests/expected/file-3.js new file mode 100644 index 00000000..e12b91bb --- /dev/null +++ b/recipes/crypto-createCredentials/tests/expected/file-3.js @@ -0,0 +1,10 @@ +const { createHash } = require('node:crypto'); +const { createSecureContext } = require('node:tls'); + +const credentials = createSecureContext({ + key: privateKey, + cert: certificate +}); + +const hash = createHash('sha256'); +hash.update('some data'); diff --git a/recipes/crypto-createCredentials/tests/expected/file-4.mjs b/recipes/crypto-createCredentials/tests/expected/file-4.mjs new file mode 100644 index 00000000..45258e37 --- /dev/null +++ b/recipes/crypto-createCredentials/tests/expected/file-4.mjs @@ -0,0 +1,7 @@ +import { createSecureContext } from 'node:tls'; + +const credentials = createSecureContext({ + key: privateKey, + cert: certificate, + ca: [caCertificate] +}); diff --git a/recipes/crypto-createCredentials/tests/expected/file-5.mjs b/recipes/crypto-createCredentials/tests/expected/file-5.mjs new file mode 100644 index 00000000..2f064efc --- /dev/null +++ b/recipes/crypto-createCredentials/tests/expected/file-5.mjs @@ -0,0 +1,6 @@ +import * as crypto from 'node:crypto'; + +const credentials = crypto.createCredentials({ + key: privateKey, + cert: certificate +}); diff --git a/recipes/crypto-createCredentials/tests/input/file-1.js b/recipes/crypto-createCredentials/tests/input/file-1.js new file mode 100644 index 00000000..217368dc --- /dev/null +++ b/recipes/crypto-createCredentials/tests/input/file-1.js @@ -0,0 +1,7 @@ +const { createCredentials } = require('node:crypto'); + +const credentials = createCredentials({ + key: privateKey, + cert: certificate, + ca: [caCertificate] +}); diff --git a/recipes/crypto-createCredentials/tests/input/file-2.js b/recipes/crypto-createCredentials/tests/input/file-2.js new file mode 100644 index 00000000..dcaff4bf --- /dev/null +++ b/recipes/crypto-createCredentials/tests/input/file-2.js @@ -0,0 +1,6 @@ +const crypto = require('node:crypto'); + +const credentials = crypto.createCredentials({ + key: fs.readFileSync('server-key.pem'), + cert: fs.readFileSync('server-cert.pem') +}); diff --git a/recipes/crypto-createCredentials/tests/input/file-3.js b/recipes/crypto-createCredentials/tests/input/file-3.js new file mode 100644 index 00000000..4077b761 --- /dev/null +++ b/recipes/crypto-createCredentials/tests/input/file-3.js @@ -0,0 +1,9 @@ +const { createCredentials, createHash } = require('node:crypto'); + +const credentials = createCredentials({ + key: privateKey, + cert: certificate +}); + +const hash = createHash('sha256'); +hash.update('some data'); diff --git a/recipes/crypto-createCredentials/tests/input/file-4.mjs b/recipes/crypto-createCredentials/tests/input/file-4.mjs new file mode 100644 index 00000000..e49ca2fc --- /dev/null +++ b/recipes/crypto-createCredentials/tests/input/file-4.mjs @@ -0,0 +1,7 @@ +import { createCredentials } from 'node:crypto'; + +const credentials = createCredentials({ + key: privateKey, + cert: certificate, + ca: [caCertificate] +}); diff --git a/recipes/crypto-createCredentials/tests/input/file-5.mjs b/recipes/crypto-createCredentials/tests/input/file-5.mjs new file mode 100644 index 00000000..2f064efc --- /dev/null +++ b/recipes/crypto-createCredentials/tests/input/file-5.mjs @@ -0,0 +1,6 @@ +import * as crypto from 'node:crypto'; + +const credentials = crypto.createCredentials({ + key: privateKey, + cert: certificate +}); diff --git a/recipes/crypto-createCredentials/tsconfig.json b/recipes/crypto-createCredentials/tsconfig.json new file mode 100644 index 00000000..92c12497 --- /dev/null +++ b/recipes/crypto-createCredentials/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "allowImportingTsExtensions": true, + "allowJs": true, + "alwaysStrict": true, + "baseUrl": "./", + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "lib": ["ESNext", "DOM"], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "noImplicitThis": true, + "removeComments": true, + "strict": true, + "stripInternal": true, + "target": "esnext" + }, + "include": ["./"], + "exclude": [ + "tests/**" + ] +} diff --git a/recipes/crypto-createCredentials/workflow.yaml b/recipes/crypto-createCredentials/workflow.yaml new file mode 100644 index 00000000..1ea4ebf5 --- /dev/null +++ b/recipes/crypto-createCredentials/workflow.yaml @@ -0,0 +1,25 @@ +version: "1" + +nodes: + - id: apply-transforms + name: Apply AST Transformations + type: automatic + runtime: + type: direct + steps: + - name: Replace `assert` import attribute to the `with` ECMAScript import attribute. + js-ast-grep: + js_file: src/workflow.ts + base_path: . + include: + - "**/*.js" + - "**/*.jsx" + - "**/*.mjs" + - "**/*.cjs" + - "**/*.cts" + - "**/*.mts" + - "**/*.ts" + - "**/*.tsx" + exclude: + - "**/node_modules/**" + language: typescript From 144c0066d9d93cbbeacf969889fde6c2d2f6471d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= <109351887+0hmX@users.noreply.github.com> Date: Mon, 4 Aug 2025 00:20:05 +0530 Subject: [PATCH 02/33] Update recipes/crypto-createCredentials/codemod.yaml Co-authored-by: Bruno Rodrigues --- recipes/crypto-createCredentials/codemod.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/recipes/crypto-createCredentials/codemod.yaml b/recipes/crypto-createCredentials/codemod.yaml index eded0cba..46f004b6 100644 --- a/recipes/crypto-createCredentials/codemod.yaml +++ b/recipes/crypto-createCredentials/codemod.yaml @@ -1,5 +1,5 @@ schema_version: "1.0" -name: "@nodejs/crypto-createCredentials" +name: "@nodejs/crypto-create-credentials" version: 1.0.0 description: Handle DEP0010 via transforming `crypto.createCredentials` to `tls.createSecureContext` author: 0hmx From f65c83909c55e110667435f7c7afe14e360f2a07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= <109351887+0hmX@users.noreply.github.com> Date: Mon, 4 Aug 2025 00:20:15 +0530 Subject: [PATCH 03/33] Update recipes/crypto-createCredentials/package.json Co-authored-by: Bruno Rodrigues --- recipes/crypto-createCredentials/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/recipes/crypto-createCredentials/package.json b/recipes/crypto-createCredentials/package.json index 8dbd7078..b14f4bf0 100644 --- a/recipes/crypto-createCredentials/package.json +++ b/recipes/crypto-createCredentials/package.json @@ -14,7 +14,7 @@ }, "author": "0hmx", "license": "MIT", - "homepage": "https://github.com/nodejs/userland-migrations/blob/main/recipes/rmdirs/README.md", + "homepage": "https://github.com/nodejs/userland-migrations/blob/main/recipes/crypto-create-credentials/README.md", "devDependencies": { "@types/node": "^24.0.3", "@codemod.com/jssg-types": "^1.0.3" From 8f7bae77736a756177dc052a54ec6046830d1be8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= <109351887+0hmX@users.noreply.github.com> Date: Mon, 4 Aug 2025 00:20:24 +0530 Subject: [PATCH 04/33] Update recipes/crypto-createCredentials/package.json Co-authored-by: Bruno Rodrigues --- recipes/crypto-createCredentials/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/recipes/crypto-createCredentials/package.json b/recipes/crypto-createCredentials/package.json index b14f4bf0..40c86a50 100644 --- a/recipes/crypto-createCredentials/package.json +++ b/recipes/crypto-createCredentials/package.json @@ -16,7 +16,6 @@ "license": "MIT", "homepage": "https://github.com/nodejs/userland-migrations/blob/main/recipes/crypto-create-credentials/README.md", "devDependencies": { - "@types/node": "^24.0.3", "@codemod.com/jssg-types": "^1.0.3" }, "dependencies": { From 0b4c417112e8e338f49253ac37a6372769029e64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= Date: Mon, 4 Aug 2025 00:59:04 +0530 Subject: [PATCH 05/33] rename to crypto-create-credentials --- .../README.md | 2 +- .../codemod.yaml | 1 + .../package.json | 4 ++-- .../src/workflow.ts | 2 +- .../tests/expected/file-1.js | 0 .../tests/expected/file-2.js | 0 .../tests/expected/file-3.js | 0 .../tests/expected/file-4.mjs | 0 recipes/crypto-create-credentials/tests/expected/file-5.mjs | 6 ++++++ .../tests/input/file-1.js | 0 .../tests/input/file-2.js | 0 .../tests/input/file-3.js | 0 .../tests/input/file-4.mjs | 0 .../tests/input}/file-5.mjs | 0 .../tsconfig.json | 0 .../workflow.yaml | 2 +- recipes/crypto-createCredentials/tests/input/file-5.mjs | 6 ------ 17 files changed, 12 insertions(+), 11 deletions(-) rename recipes/{crypto-createCredentials => crypto-create-credentials}/README.md (96%) rename recipes/{crypto-createCredentials => crypto-create-credentials}/codemod.yaml (99%) rename recipes/{crypto-createCredentials => crypto-create-credentials}/package.json (87%) rename recipes/{crypto-createCredentials => crypto-create-credentials}/src/workflow.ts (99%) rename recipes/{crypto-createCredentials => crypto-create-credentials}/tests/expected/file-1.js (100%) rename recipes/{crypto-createCredentials => crypto-create-credentials}/tests/expected/file-2.js (100%) rename recipes/{crypto-createCredentials => crypto-create-credentials}/tests/expected/file-3.js (100%) rename recipes/{crypto-createCredentials => crypto-create-credentials}/tests/expected/file-4.mjs (100%) create mode 100644 recipes/crypto-create-credentials/tests/expected/file-5.mjs rename recipes/{crypto-createCredentials => crypto-create-credentials}/tests/input/file-1.js (100%) rename recipes/{crypto-createCredentials => crypto-create-credentials}/tests/input/file-2.js (100%) rename recipes/{crypto-createCredentials => crypto-create-credentials}/tests/input/file-3.js (100%) rename recipes/{crypto-createCredentials => crypto-create-credentials}/tests/input/file-4.mjs (100%) rename recipes/{crypto-createCredentials/tests/expected => crypto-create-credentials/tests/input}/file-5.mjs (100%) rename recipes/{crypto-createCredentials => crypto-create-credentials}/tsconfig.json (100%) rename recipes/{crypto-createCredentials => crypto-create-credentials}/workflow.yaml (83%) delete mode 100644 recipes/crypto-createCredentials/tests/input/file-5.mjs diff --git a/recipes/crypto-createCredentials/README.md b/recipes/crypto-create-credentials/README.md similarity index 96% rename from recipes/crypto-createCredentials/README.md rename to recipes/crypto-create-credentials/README.md index b3bace49..a00f0f2d 100644 --- a/recipes/crypto-createCredentials/README.md +++ b/recipes/crypto-create-credentials/README.md @@ -1,4 +1,4 @@ -# `fs.rmdir` DEP0147 +# `crypto.createCredentials` DEP0010 This recipe provides a guide for migrating from the deprecated `crypto.createCredentials` method in Node.js. diff --git a/recipes/crypto-createCredentials/codemod.yaml b/recipes/crypto-create-credentials/codemod.yaml similarity index 99% rename from recipes/crypto-createCredentials/codemod.yaml rename to recipes/crypto-create-credentials/codemod.yaml index 46f004b6..3f628f3a 100644 --- a/recipes/crypto-createCredentials/codemod.yaml +++ b/recipes/crypto-create-credentials/codemod.yaml @@ -19,3 +19,4 @@ keywords: registry: access: public visibility: public + diff --git a/recipes/crypto-createCredentials/package.json b/recipes/crypto-create-credentials/package.json similarity index 87% rename from recipes/crypto-createCredentials/package.json rename to recipes/crypto-create-credentials/package.json index 40c86a50..4859828b 100644 --- a/recipes/crypto-createCredentials/package.json +++ b/recipes/crypto-create-credentials/package.json @@ -1,5 +1,5 @@ { - "name": "@nodejs/crypto-createCredentials", + "name": "@nodejs/crypto-create-credentials", "version": "1.0.0", "description": "Handle DEP0010 via transforming `crypto.createCredentials` to `tls.createSecureContext` with the appropriate options.", "type": "module", @@ -9,7 +9,7 @@ "repository": { "type": "git", "url": "git+https://github.com/nodejs/userland-migrations.git", - "directory": "recipes/rmdirs", + "directory": "recipes/crypto-create-credentials", "bugs": "https://github.com/nodejs/userland-migrations/issues" }, "author": "0hmx", diff --git a/recipes/crypto-createCredentials/src/workflow.ts b/recipes/crypto-create-credentials/src/workflow.ts similarity index 99% rename from recipes/crypto-createCredentials/src/workflow.ts rename to recipes/crypto-create-credentials/src/workflow.ts index 397b3044..9c9f0e86 100644 --- a/recipes/crypto-createCredentials/src/workflow.ts +++ b/recipes/crypto-create-credentials/src/workflow.ts @@ -170,7 +170,7 @@ function handleNamespaceImport( ) { const isEsm = importType === 'NAMESPACE_IMPORT'; const nameNode = isEsm - ? importOrDeclarator.find({ rule: { kind: 'namespace_import' } })?.field('name') + ? importOrDeclarator.find({ rule: { kind: 'namespace_import' } })?.find({ rule: { kind: 'identifier' }}) : importOrDeclarator.field('name'); if (!nameNode) { diff --git a/recipes/crypto-createCredentials/tests/expected/file-1.js b/recipes/crypto-create-credentials/tests/expected/file-1.js similarity index 100% rename from recipes/crypto-createCredentials/tests/expected/file-1.js rename to recipes/crypto-create-credentials/tests/expected/file-1.js diff --git a/recipes/crypto-createCredentials/tests/expected/file-2.js b/recipes/crypto-create-credentials/tests/expected/file-2.js similarity index 100% rename from recipes/crypto-createCredentials/tests/expected/file-2.js rename to recipes/crypto-create-credentials/tests/expected/file-2.js diff --git a/recipes/crypto-createCredentials/tests/expected/file-3.js b/recipes/crypto-create-credentials/tests/expected/file-3.js similarity index 100% rename from recipes/crypto-createCredentials/tests/expected/file-3.js rename to recipes/crypto-create-credentials/tests/expected/file-3.js diff --git a/recipes/crypto-createCredentials/tests/expected/file-4.mjs b/recipes/crypto-create-credentials/tests/expected/file-4.mjs similarity index 100% rename from recipes/crypto-createCredentials/tests/expected/file-4.mjs rename to recipes/crypto-create-credentials/tests/expected/file-4.mjs diff --git a/recipes/crypto-create-credentials/tests/expected/file-5.mjs b/recipes/crypto-create-credentials/tests/expected/file-5.mjs new file mode 100644 index 00000000..92231955 --- /dev/null +++ b/recipes/crypto-create-credentials/tests/expected/file-5.mjs @@ -0,0 +1,6 @@ +import * as tls from 'node:tls'; + +const credentials = tls.createSecureContext({ + key: privateKey, + cert: certificate +}); diff --git a/recipes/crypto-createCredentials/tests/input/file-1.js b/recipes/crypto-create-credentials/tests/input/file-1.js similarity index 100% rename from recipes/crypto-createCredentials/tests/input/file-1.js rename to recipes/crypto-create-credentials/tests/input/file-1.js diff --git a/recipes/crypto-createCredentials/tests/input/file-2.js b/recipes/crypto-create-credentials/tests/input/file-2.js similarity index 100% rename from recipes/crypto-createCredentials/tests/input/file-2.js rename to recipes/crypto-create-credentials/tests/input/file-2.js diff --git a/recipes/crypto-createCredentials/tests/input/file-3.js b/recipes/crypto-create-credentials/tests/input/file-3.js similarity index 100% rename from recipes/crypto-createCredentials/tests/input/file-3.js rename to recipes/crypto-create-credentials/tests/input/file-3.js diff --git a/recipes/crypto-createCredentials/tests/input/file-4.mjs b/recipes/crypto-create-credentials/tests/input/file-4.mjs similarity index 100% rename from recipes/crypto-createCredentials/tests/input/file-4.mjs rename to recipes/crypto-create-credentials/tests/input/file-4.mjs diff --git a/recipes/crypto-createCredentials/tests/expected/file-5.mjs b/recipes/crypto-create-credentials/tests/input/file-5.mjs similarity index 100% rename from recipes/crypto-createCredentials/tests/expected/file-5.mjs rename to recipes/crypto-create-credentials/tests/input/file-5.mjs diff --git a/recipes/crypto-createCredentials/tsconfig.json b/recipes/crypto-create-credentials/tsconfig.json similarity index 100% rename from recipes/crypto-createCredentials/tsconfig.json rename to recipes/crypto-create-credentials/tsconfig.json diff --git a/recipes/crypto-createCredentials/workflow.yaml b/recipes/crypto-create-credentials/workflow.yaml similarity index 83% rename from recipes/crypto-createCredentials/workflow.yaml rename to recipes/crypto-create-credentials/workflow.yaml index 1ea4ebf5..c9d11ab7 100644 --- a/recipes/crypto-createCredentials/workflow.yaml +++ b/recipes/crypto-create-credentials/workflow.yaml @@ -7,7 +7,7 @@ nodes: runtime: type: direct steps: - - name: Replace `assert` import attribute to the `with` ECMAScript import attribute. + - name: Handle DEP0010 via transforming `crypto.createCredentials` to `tls.createSecureContext`. js-ast-grep: js_file: src/workflow.ts base_path: . diff --git a/recipes/crypto-createCredentials/tests/input/file-5.mjs b/recipes/crypto-createCredentials/tests/input/file-5.mjs deleted file mode 100644 index 2f064efc..00000000 --- a/recipes/crypto-createCredentials/tests/input/file-5.mjs +++ /dev/null @@ -1,6 +0,0 @@ -import * as crypto from 'node:crypto'; - -const credentials = crypto.createCredentials({ - key: privateKey, - cert: certificate -}); From e36f7beb4e4e27c211a70ca42d022671110eaa07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= <109351887+0hmX@users.noreply.github.com> Date: Tue, 5 Aug 2025 08:43:13 +0530 Subject: [PATCH 06/33] Update recipes/crypto-create-credentials/workflow.yaml Co-authored-by: Bruno Rodrigues --- recipes/crypto-create-credentials/workflow.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/recipes/crypto-create-credentials/workflow.yaml b/recipes/crypto-create-credentials/workflow.yaml index c9d11ab7..8128d277 100644 --- a/recipes/crypto-create-credentials/workflow.yaml +++ b/recipes/crypto-create-credentials/workflow.yaml @@ -12,10 +12,10 @@ nodes: js_file: src/workflow.ts base_path: . include: + - "**/*.cjs" - "**/*.js" - "**/*.jsx" - "**/*.mjs" - - "**/*.cjs" - "**/*.cts" - "**/*.mts" - "**/*.ts" From 6525cfd80a3a5329e75d9e5b591827495a5f2e3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= <109351887+0hmX@users.noreply.github.com> Date: Tue, 5 Aug 2025 08:43:23 +0530 Subject: [PATCH 07/33] Update recipes/crypto-create-credentials/workflow.yaml Co-authored-by: Augustin Mauroy <97875033+AugustinMauroy@users.noreply.github.com> --- recipes/crypto-create-credentials/workflow.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/recipes/crypto-create-credentials/workflow.yaml b/recipes/crypto-create-credentials/workflow.yaml index 8128d277..f7184bb8 100644 --- a/recipes/crypto-create-credentials/workflow.yaml +++ b/recipes/crypto-create-credentials/workflow.yaml @@ -1,3 +1,5 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/codemod-com/codemod/refs/heads/main/schemas/workflow.json + version: "1" nodes: From a56c4bbc5a92dec44189dbe3185d418515c37499 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= <109351887+0hmX@users.noreply.github.com> Date: Tue, 5 Aug 2025 08:59:31 +0530 Subject: [PATCH 08/33] Update recipes/crypto-create-credentials/README.md Co-authored-by: Bruno Rodrigues --- recipes/crypto-create-credentials/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/recipes/crypto-create-credentials/README.md b/recipes/crypto-create-credentials/README.md index a00f0f2d..6d318f6c 100644 --- a/recipes/crypto-create-credentials/README.md +++ b/recipes/crypto-create-credentials/README.md @@ -1,6 +1,6 @@ # `crypto.createCredentials` DEP0010 -This recipe provides a guide for migrating from the deprecated `crypto.createCredentials` method in Node.js. +This recipe transforms `crypto.createCredentials` usage to use modern `node:tls` methods. See [DEP0010](https://nodejs.org/api/deprecations.html#DEP0010). From e6bf56ee53106f3dd0484c1059c9a4b02ef63787 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= Date: Tue, 5 Aug 2025 08:42:03 +0530 Subject: [PATCH 09/33] sync package-lock.json --- package-lock.json | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/package-lock.json b/package-lock.json index fdd18d3b..071e51d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1420,6 +1420,10 @@ "resolved": "recipes/create-require-from-path", "link": true }, + "node_modules/@nodejs/crypto-create-credentials": { + "resolved": "recipes/crypto-create-credentials", + "link": true + }, "node_modules/@nodejs/import-assertions-to-attributes": { "resolved": "recipes/import-assertions-to-attributes", "link": true @@ -4065,6 +4069,17 @@ "@types/node": "^24.0.3" } }, + "recipes/crypto-create-credentials": { + "name": "@nodejs/crypto-create-credentials", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@nodejs/codemod-utils": "*" + }, + "devDependencies": { + "@codemod.com/jssg-types": "^1.0.3" + } + }, "recipes/import-assertions-to-attributes": { "name": "@nodejs/import-assertions-to-attributes", "version": "1.0.0", From 9ac7b8b972516de89896d2074330b4a4335f864e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= Date: Tue, 5 Aug 2025 09:37:06 +0530 Subject: [PATCH 10/33] refactor workflow.ts --- .../crypto-create-credentials/src/workflow.ts | 270 +++++++++++------- 1 file changed, 169 insertions(+), 101 deletions(-) diff --git a/recipes/crypto-create-credentials/src/workflow.ts b/recipes/crypto-create-credentials/src/workflow.ts index 9c9f0e86..c7a8451f 100644 --- a/recipes/crypto-create-credentials/src/workflow.ts +++ b/recipes/crypto-create-credentials/src/workflow.ts @@ -1,5 +1,22 @@ import type { SgRoot, Edit, SgNode } from "@codemod.com/jssg-types/main"; +type TlsImportState = { + added: boolean; + identifier: string; +}; + +type HandlerResult = { + edits: Edit[]; + wasTransformed: boolean; + newState: TlsImportState; +}; + +type EnsureTlsResult = { + edits: Edit[]; + identifier: string; // The identifier that was used or found + newState: TlsImportState; +}; + type ImportType = | 'DESTRUCTURED_REQUIRE' | 'NAMESPACE_REQUIRE' @@ -8,12 +25,15 @@ type ImportType = function ensureTlsImport( rootNode: SgNode, - edits: Edit[], importType: ImportType, - tlsImportState: { added: boolean; identifier: string }, -): string { - if (tlsImportState.added) { - return tlsImportState.identifier; + currentState: TlsImportState, +): EnsureTlsResult { + if (currentState.added) { + return { + edits: [], + identifier: currentState.identifier, + newState: currentState, + }; } const isEsm = @@ -34,39 +54,58 @@ function ensureTlsImport( if (existingTlsImport) { const nameNode = existingTlsImport.field('name'); if (nameNode?.is('identifier')) { - tlsImportState.identifier = nameNode.text(); - tlsImportState.added = true; - return tlsImportState.identifier; + const foundIdentifier = nameNode.text(); + return { + edits: [], + identifier: foundIdentifier, + newState: { + added: true, + identifier: foundIdentifier, + }, + }; } } const firstNode = rootNode.children()[0]; - if (firstNode) { - const newImportText = isEsm - ? `import * as ${tlsImportState.identifier} from '${moduleSpecifier}';\n` - : `const ${tlsImportState.identifier} = require('${moduleSpecifier}');\n`; - - const edit = { - startPos: firstNode.range().start.index, - endPos: firstNode.range().start.index, - insertedText: newImportText, + + if (!firstNode) { + return { + edits: [], + identifier: currentState.identifier, + newState: currentState, }; - edits.push(edit); } - tlsImportState.added = true; - return tlsImportState.identifier; + const tlsIdentifier = currentState.identifier; + const newImportText = isEsm + ? `import * as ${tlsIdentifier} from '${moduleSpecifier}';\n` + : `const ${tlsIdentifier} = require('${moduleSpecifier}');\n`; + + const edit: Edit = { + startPos: firstNode.range().start.index, + endPos: firstNode.range().start.index, + insertedText: newImportText, + }; + + return { + edits: [edit], + identifier: tlsIdentifier, + newState: { + added: true, + identifier: tlsIdentifier, + }, + }; } function handleDestructuredImport( statement: SgNode, rootNode: SgNode, - edits: Edit[], - tlsImportState: { added: boolean }, -) { + currentState: TlsImportState, +): HandlerResult { + const localEdits: Edit[] = []; const isEsm = statement.kind() === 'import_statement'; - let specifiersNode; + let specifiersNode: SgNode | null | undefined; if (isEsm) { specifiersNode = statement.find({ rule: { kind: 'named_imports' } }) ?? @@ -90,7 +129,7 @@ function handleDestructuredImport( } if (!specifiersNode) { - return false; + return { edits: [], wasTransformed: false, newState: currentState }; } const findPropsRule = { @@ -108,7 +147,7 @@ function handleDestructuredImport( ); if (!createCredentialsNode) { - return false; + return { edits: [], wasTransformed: false, newState: currentState }; } const usagesFindRule = { @@ -125,7 +164,7 @@ function handleDestructuredImport( for (const usage of usages) { const functionIdentifier = usage.field('function'); if (functionIdentifier) { - edits.push(functionIdentifier.replace('createSecureContext')); + localEdits.push(functionIdentifier.replace('createSecureContext')); } } @@ -135,46 +174,52 @@ function handleDestructuredImport( ? `import { ${newImportFunction} } from '${newImportModule}';` : `const { ${newImportFunction} } = require('${newImportModule}');`; + let finalState = currentState; + if (destructuredProps.length === 1) { - edits.push(statement.replace(newImportStatement)); - tlsImportState.added = true; + localEdits.push(statement.replace(newImportStatement)); + finalState = { ...currentState, added: true }; } else { const otherProps = destructuredProps .filter((id) => id.text() !== 'createCredentials') .map((id) => id.text()); const newDestructuredString = `{ ${otherProps.join(', ')} }`; - edits.push(specifiersNode.replace(newDestructuredString)); + localEdits.push(specifiersNode.replace(newDestructuredString)); - if (!tlsImportState.added) { + if (!currentState.added) { const newEdit = { startPos: statement.range().end.index, endPos: statement.range().end.index, insertedText: `\n${newImportStatement}`, }; - edits.push(newEdit); - tlsImportState.added = true; - } else { + localEdits.push(newEdit); + finalState = { ...currentState, added: true }; } } - return true; + return { + edits: localEdits, + wasTransformed: true, + newState: finalState, + }; } function handleNamespaceImport( importOrDeclarator: SgNode, rootNode: SgNode, - edits: Edit[], importType: ImportType, - tlsImportState: { added: boolean; identifier: string }, -) { + currentState: TlsImportState, +): HandlerResult { + const localEdits: Edit[] = []; const isEsm = importType === 'NAMESPACE_IMPORT'; + const nameNode = isEsm - ? importOrDeclarator.find({ rule: { kind: 'namespace_import' } })?.find({ rule: { kind: 'identifier' }}) + ? importOrDeclarator.find({ rule: { kind: 'namespace_import' } })?.find({ rule: { kind: 'identifier' } }) : importOrDeclarator.field('name'); if (!nameNode) { - return false; + return { edits: [], wasTransformed: false, newState: currentState }; } const namespaceName = nameNode.text(); @@ -189,57 +234,71 @@ function handleNamespaceImport( }; const memberAccessUsages = rootNode.findAll(memberAccessFindRule); - if (memberAccessUsages.length > 0) { + if (memberAccessUsages.length === 0) { + return { edits: [], wasTransformed: false, newState: currentState }; + } - const allUsagesFindRule = { - rule: { - kind: 'member_expression', - has: { field: 'object', regex: `^${namespaceName}$` }, - }, - }; - const allUsages = rootNode.findAll(allUsagesFindRule); - - if (allUsages.length === memberAccessUsages.length) { - const newTlsIdentifier = tlsImportState.identifier; - const newImportModule = 'node:tls'; - const newImportStatement = isEsm - ? `import * as ${newTlsIdentifier} from '${newImportModule}';` - : `const ${newTlsIdentifier} = require('${newImportModule}');`; - - const nodeToReplace = isEsm - ? importOrDeclarator - : importOrDeclarator.parent(); - edits.push(nodeToReplace.replace(newImportStatement)); - tlsImportState.added = true; - - for (const usage of memberAccessUsages) { - const replacementText = `${newTlsIdentifier}.createSecureContext`; - edits.push(usage.replace(replacementText)); - } - } else { - let tlsIdentifier = ensureTlsImport( - rootNode, - edits, - importType, - tlsImportState, - ); - - for (const usage of memberAccessUsages) { - const replacementText = `${tlsIdentifier}.createSecureContext`; - edits.push(usage.replace(replacementText)); - } + const allUsagesFindRule = { + rule: { + kind: 'member_expression', + has: { field: 'object', regex: `^${namespaceName}$` }, + }, + }; + const allUsages = rootNode.findAll(allUsagesFindRule); + + let finalState = currentState; + + if (allUsages.length === memberAccessUsages.length) { + const newTlsIdentifier = currentState.identifier; + const newImportModule = 'node:tls'; + const newImportStatement = isEsm + ? `import * as ${newTlsIdentifier} from '${newImportModule}';` + : `const ${newTlsIdentifier} = require('${newImportModule}');`; + + const nodeToReplace = isEsm + ? importOrDeclarator + : importOrDeclarator.parent(); + + if (nodeToReplace) { + localEdits.push(nodeToReplace.replace(newImportStatement)); + } + + finalState = { ...currentState, added: true }; + + for (const usage of memberAccessUsages) { + const replacementText = `${newTlsIdentifier}.createSecureContext`; + localEdits.push(usage.replace(replacementText)); } - return true; } else { - return false; + const ensureResult = ensureTlsImport( + rootNode, + importType, + currentState, + ); + localEdits.push(...ensureResult.edits); + finalState = ensureResult.newState; + + for (const usage of memberAccessUsages) { + const replacementText = `${ensureResult.identifier}.createSecureContext`; + localEdits.push(usage.replace(replacementText)); + } } + + return { + edits: localEdits, + wasTransformed: true, + newState: finalState, + }; } export default function transform(root: SgRoot): string | null { - const edits: Edit[] = []; const rootNode = root.root(); + const allEdits: Edit[] = []; let wasTransformed = false; - const tlsImportState = { added: false, identifier: 'tls' }; + let currentTlsImportState: TlsImportState = { + added: false, + identifier: 'tls', + }; const cryptoImportsRule = { rule: { @@ -257,7 +316,10 @@ export default function transform(root: SgRoot): string | null { }, { kind: 'import_statement', - has: { field: 'source', regex: "^['\"](node:)?crypto['\"]$" }, + has: { + field: 'source', + regex: "^['\"](node:)?crypto['\"]$", + }, }, ], }, @@ -265,7 +327,8 @@ export default function transform(root: SgRoot): string | null { const cryptoImports = rootNode.findAll(cryptoImportsRule); for (const importMatch of cryptoImports) { - const nameNode = importMatch.field('imports') ?? importMatch.field('name'); + const nameNode = + importMatch.field('imports') ?? importMatch.field('name'); let importType: ImportType | undefined; if (importMatch.kind() === 'import_statement') { @@ -275,7 +338,6 @@ export default function transform(root: SgRoot): string | null { importType = 'DESTRUCTURED_IMPORT'; } } else { - // variable_declarator for require if (nameNode?.is('object_pattern')) { importType = 'DESTRUCTURED_REQUIRE'; } else if (nameNode?.is('identifier')) { @@ -287,42 +349,48 @@ export default function transform(root: SgRoot): string | null { continue; } - let transformedByType = false; + let result: HandlerResult | undefined; + switch (importType) { case 'DESTRUCTURED_REQUIRE': - case 'DESTRUCTURED_IMPORT': + case 'DESTRUCTURED_IMPORT': { const statement = importType === 'DESTRUCTURED_REQUIRE' ? importMatch.parent() : importMatch; - transformedByType = handleDestructuredImport( - statement, - rootNode, - edits, - tlsImportState, - ); + if (statement) { + result = handleDestructuredImport( + statement, + rootNode, + currentTlsImportState, + ); + } break; + } case 'NAMESPACE_REQUIRE': case 'NAMESPACE_IMPORT': - transformedByType = handleNamespaceImport( + result = handleNamespaceImport( importMatch, rootNode, - edits, importType, - tlsImportState, + currentTlsImportState, ); break; } - if (transformedByType) { - wasTransformed = true; + if (result) { + allEdits.push(...result.edits); + currentTlsImportState = result.newState; + if (result.wasTransformed) { + wasTransformed = true; + } } } if (wasTransformed) { - return rootNode.commitEdits(edits); - } else { - return null; + return rootNode.commitEdits(allEdits); } + + return null; } From 74d80255612c479a4610697b9e9fac716be92690 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= Date: Tue, 5 Aug 2025 09:38:02 +0530 Subject: [PATCH 11/33] fix lint codemod.yml --- recipes/crypto-create-credentials/codemod.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/recipes/crypto-create-credentials/codemod.yaml b/recipes/crypto-create-credentials/codemod.yaml index 3f628f3a..46f004b6 100644 --- a/recipes/crypto-create-credentials/codemod.yaml +++ b/recipes/crypto-create-credentials/codemod.yaml @@ -19,4 +19,3 @@ keywords: registry: access: public visibility: public - From 81b848008f8c36f98301f94e8fe8f15693ee56d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= <109351887+0hmX@users.noreply.github.com> Date: Wed, 6 Aug 2025 08:57:58 +0530 Subject: [PATCH 12/33] Update recipes/crypto-create-credentials/README.md Co-authored-by: Augustin Mauroy <97875033+AugustinMauroy@users.noreply.github.com> --- recipes/crypto-create-credentials/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/recipes/crypto-create-credentials/README.md b/recipes/crypto-create-credentials/README.md index 6d318f6c..43906512 100644 --- a/recipes/crypto-create-credentials/README.md +++ b/recipes/crypto-create-credentials/README.md @@ -7,7 +7,6 @@ See [DEP0010](https://nodejs.org/api/deprecations.html#DEP0010). ## Examples **Before:** - ```js // Using the deprecated createCredentials from node:crypto const { createCredentials } = require('node:crypto'); From 7b8633fb8b2acc5f3259874e45a0b6e289133233 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= <109351887+0hmX@users.noreply.github.com> Date: Wed, 6 Aug 2025 08:58:18 +0530 Subject: [PATCH 13/33] Update recipes/crypto-create-credentials/README.md Co-authored-by: Augustin Mauroy <97875033+AugustinMauroy@users.noreply.github.com> --- recipes/crypto-create-credentials/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/recipes/crypto-create-credentials/README.md b/recipes/crypto-create-credentials/README.md index 43906512..badc3c83 100644 --- a/recipes/crypto-create-credentials/README.md +++ b/recipes/crypto-create-credentials/README.md @@ -28,7 +28,6 @@ const credentials = createCredentials({ ``` **After:** - ```js // Updated to use createSecureContext from node:tls const { createSecureContext } = require('node:tls'); From 55bddce955a18beba49e157edfa0158fb00079b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= <109351887+0hmX@users.noreply.github.com> Date: Wed, 6 Aug 2025 08:58:34 +0530 Subject: [PATCH 14/33] Update recipes/crypto-create-credentials/package.json Co-authored-by: Augustin Mauroy <97875033+AugustinMauroy@users.noreply.github.com> --- recipes/crypto-create-credentials/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/recipes/crypto-create-credentials/package.json b/recipes/crypto-create-credentials/package.json index 4859828b..98a91b0c 100644 --- a/recipes/crypto-create-credentials/package.json +++ b/recipes/crypto-create-credentials/package.json @@ -9,7 +9,7 @@ "repository": { "type": "git", "url": "git+https://github.com/nodejs/userland-migrations.git", - "directory": "recipes/crypto-create-credentials", + "directory": "recipes/crypto-create-credentials", "bugs": "https://github.com/nodejs/userland-migrations/issues" }, "author": "0hmx", From c08282f9d67248b39e1efb3779216ab2ed46a17b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= Date: Thu, 7 Aug 2025 08:24:12 +0530 Subject: [PATCH 15/33] added os.EOL --- recipes/crypto-create-credentials/src/workflow.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/recipes/crypto-create-credentials/src/workflow.ts b/recipes/crypto-create-credentials/src/workflow.ts index c7a8451f..a676fd04 100644 --- a/recipes/crypto-create-credentials/src/workflow.ts +++ b/recipes/crypto-create-credentials/src/workflow.ts @@ -1,3 +1,4 @@ +import * as os from 'os'; import type { SgRoot, Edit, SgNode } from "@codemod.com/jssg-types/main"; type TlsImportState = { @@ -78,8 +79,8 @@ function ensureTlsImport( const tlsIdentifier = currentState.identifier; const newImportText = isEsm - ? `import * as ${tlsIdentifier} from '${moduleSpecifier}';\n` - : `const ${tlsIdentifier} = require('${moduleSpecifier}');\n`; + ? `import * as ${tlsIdentifier} from '${moduleSpecifier}';${os.EOL}` + : `const ${tlsIdentifier} = require('${moduleSpecifier}');${os.EOL}`; const edit: Edit = { startPos: firstNode.range().start.index, @@ -191,7 +192,7 @@ function handleDestructuredImport( const newEdit = { startPos: statement.range().end.index, endPos: statement.range().end.index, - insertedText: `\n${newImportStatement}`, + insertedText: `${os.EOL}${newImportStatement}`, }; localEdits.push(newEdit); finalState = { ...currentState, added: true }; From 202b99fe9edccad768c15acaef83588df029c855 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= Date: Thu, 7 Aug 2025 08:26:59 +0530 Subject: [PATCH 16/33] lint fix --- recipes/crypto-create-credentials/src/workflow.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/recipes/crypto-create-credentials/src/workflow.ts b/recipes/crypto-create-credentials/src/workflow.ts index a676fd04..f912ad90 100644 --- a/recipes/crypto-create-credentials/src/workflow.ts +++ b/recipes/crypto-create-credentials/src/workflow.ts @@ -1,4 +1,4 @@ -import * as os from 'os'; +import * as os from 'node:os'; import type { SgRoot, Edit, SgNode } from "@codemod.com/jssg-types/main"; type TlsImportState = { From 67579ea382fba690ae13e810924b463174cb4c00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= <109351887+0hmX@users.noreply.github.com> Date: Mon, 11 Aug 2025 19:37:41 +0530 Subject: [PATCH 17/33] Update recipes/crypto-create-credentials/README.md Co-authored-by: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> --- recipes/crypto-create-credentials/README.md | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/recipes/crypto-create-credentials/README.md b/recipes/crypto-create-credentials/README.md index badc3c83..1bc6a903 100644 --- a/recipes/crypto-create-credentials/README.md +++ b/recipes/crypto-create-credentials/README.md @@ -10,14 +10,7 @@ See [DEP0010](https://nodejs.org/api/deprecations.html#DEP0010). ```js // Using the deprecated createCredentials from node:crypto const { createCredentials } = require('node:crypto'); - -const credentials = createCredentials({ - key: privateKey, - cert: certificate, - ca: [caCertificate] -}); - -// Using destructuring with ES module imports +// OR import { createCredentials } from 'node:crypto'; const credentials = createCredentials({ From f36f0ee54e364326b5c21f169c09d1dea2f718cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= <109351887+0hmX@users.noreply.github.com> Date: Mon, 11 Aug 2025 19:38:04 +0530 Subject: [PATCH 18/33] Update recipes/crypto-create-credentials/README.md Co-authored-by: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> --- recipes/crypto-create-credentials/README.md | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/recipes/crypto-create-credentials/README.md b/recipes/crypto-create-credentials/README.md index 1bc6a903..adacd800 100644 --- a/recipes/crypto-create-credentials/README.md +++ b/recipes/crypto-create-credentials/README.md @@ -24,14 +24,7 @@ const credentials = createCredentials({ ```js // Updated to use createSecureContext from node:tls const { createSecureContext } = require('node:tls'); - -const credentials = createSecureContext({ - key: privateKey, - cert: certificate, - ca: [caCertificate] -}); - -// Updated ES module import +// OR import { createSecureContext } from 'node:tls'; const credentials = createSecureContext({ From 21ce469ae3164b781fcabb4f03fc3939e26284f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= <109351887+0hmX@users.noreply.github.com> Date: Mon, 11 Aug 2025 19:44:14 +0530 Subject: [PATCH 19/33] Update recipes/crypto-create-credentials/src/workflow.ts Co-authored-by: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> --- recipes/crypto-create-credentials/src/workflow.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/recipes/crypto-create-credentials/src/workflow.ts b/recipes/crypto-create-credentials/src/workflow.ts index f912ad90..0e9547de 100644 --- a/recipes/crypto-create-credentials/src/workflow.ts +++ b/recipes/crypto-create-credentials/src/workflow.ts @@ -181,9 +181,11 @@ function handleDestructuredImport( localEdits.push(statement.replace(newImportStatement)); finalState = { ...currentState, added: true }; } else { - const otherProps = destructuredProps - .filter((id) => id.text() !== 'createCredentials') - .map((id) => id.text()); + const otherProps = []; + for (const d of destructuredProps) { + const text = d.text(); + if (text !== 'createCredentials') otherProps.push(text); + } const newDestructuredString = `{ ${otherProps.join(', ')} }`; localEdits.push(specifiersNode.replace(newDestructuredString)); From 1a8b6406bac0d068e1829001f315c792eda9e459 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= <109351887+0hmX@users.noreply.github.com> Date: Tue, 12 Aug 2025 09:23:26 +0530 Subject: [PATCH 20/33] Update recipes/crypto-create-credentials/src/workflow.ts Co-authored-by: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> --- recipes/crypto-create-credentials/src/workflow.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/recipes/crypto-create-credentials/src/workflow.ts b/recipes/crypto-create-credentials/src/workflow.ts index 0e9547de..9ada0a65 100644 --- a/recipes/crypto-create-credentials/src/workflow.ts +++ b/recipes/crypto-create-credentials/src/workflow.ts @@ -282,8 +282,7 @@ function handleNamespaceImport( finalState = ensureResult.newState; for (const usage of memberAccessUsages) { - const replacementText = `${ensureResult.identifier}.createSecureContext`; - localEdits.push(usage.replace(replacementText)); + localEdits.push(usage.replace(`${ensureResult.identifier}.createSecureContext`)); } } From dfeb47656a91bb45df579d2d722d7bd3b42ea7c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= <109351887+0hmX@users.noreply.github.com> Date: Tue, 12 Aug 2025 09:23:47 +0530 Subject: [PATCH 21/33] Update recipes/crypto-create-credentials/src/workflow.ts Co-authored-by: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> --- recipes/crypto-create-credentials/src/workflow.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/recipes/crypto-create-credentials/src/workflow.ts b/recipes/crypto-create-credentials/src/workflow.ts index 9ada0a65..f6be8d30 100644 --- a/recipes/crypto-create-credentials/src/workflow.ts +++ b/recipes/crypto-create-credentials/src/workflow.ts @@ -193,6 +193,7 @@ function handleDestructuredImport( if (!currentState.added) { const newEdit = { startPos: statement.range().end.index, + // appending, so use `end` for both endPos: statement.range().end.index, insertedText: `${os.EOL}${newImportStatement}`, }; From b3f8bef37466ff9cfed4d03c2f55db9ee4c58407 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= <109351887+0hmX@users.noreply.github.com> Date: Tue, 12 Aug 2025 09:24:14 +0530 Subject: [PATCH 22/33] Update recipes/crypto-create-credentials/src/workflow.ts Co-authored-by: Jacob Smith <3012099+JakobJingleheimer@users.noreply.github.com> --- recipes/crypto-create-credentials/src/workflow.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/recipes/crypto-create-credentials/src/workflow.ts b/recipes/crypto-create-credentials/src/workflow.ts index f6be8d30..8dd059e2 100644 --- a/recipes/crypto-create-credentials/src/workflow.ts +++ b/recipes/crypto-create-credentials/src/workflow.ts @@ -335,11 +335,9 @@ export default function transform(root: SgRoot): string | null { let importType: ImportType | undefined; if (importMatch.kind() === 'import_statement') { - if (importMatch.find({ rule: { kind: 'namespace_import' } })) { - importType = 'NAMESPACE_IMPORT'; - } else { - importType = 'DESTRUCTURED_IMPORT'; - } + importType = importMatch.find({ rule: { kind: 'namespace_import' } }) + ? 'NAMESPACE_IMPORT' + : 'DESTRUCTURED_IMPORT'; } else { if (nameNode?.is('object_pattern')) { importType = 'DESTRUCTURED_REQUIRE'; From f3dde78b22632a810695b77d5e9007cdf14d873c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= Date: Wed, 13 Aug 2025 08:15:27 +0530 Subject: [PATCH 23/33] rename to createCredentials-to-createSecureContext --- .../README.md | 0 .../codemod.yaml | 2 +- .../package.json | 2 +- .../src/workflow.ts | 271 ++++++++++++ .../tests/expected/file-1.js | 0 .../tests/expected/file-2.js | 0 .../tests/expected/file-3.js | 0 .../tests/expected/file-4.mjs | 0 .../tests/expected/file-5.mjs | 0 .../tests/input/file-1.js | 0 .../tests/input/file-2.js | 0 .../tests/input/file-3.js | 0 .../tests/input/file-4.mjs | 0 .../tests/input/file-5.mjs | 0 .../tsconfig.json | 0 .../workflow.yaml | 0 .../crypto-create-credentials/src/workflow.ts | 397 ------------------ 17 files changed, 273 insertions(+), 399 deletions(-) rename recipes/{crypto-create-credentials => createCredentials-to-createSecureContext}/README.md (100%) rename recipes/{crypto-create-credentials => createCredentials-to-createSecureContext}/codemod.yaml (86%) rename recipes/{crypto-create-credentials => createCredentials-to-createSecureContext}/package.json (92%) create mode 100644 recipes/createCredentials-to-createSecureContext/src/workflow.ts rename recipes/{crypto-create-credentials => createCredentials-to-createSecureContext}/tests/expected/file-1.js (100%) rename recipes/{crypto-create-credentials => createCredentials-to-createSecureContext}/tests/expected/file-2.js (100%) rename recipes/{crypto-create-credentials => createCredentials-to-createSecureContext}/tests/expected/file-3.js (100%) rename recipes/{crypto-create-credentials => createCredentials-to-createSecureContext}/tests/expected/file-4.mjs (100%) rename recipes/{crypto-create-credentials => createCredentials-to-createSecureContext}/tests/expected/file-5.mjs (100%) rename recipes/{crypto-create-credentials => createCredentials-to-createSecureContext}/tests/input/file-1.js (100%) rename recipes/{crypto-create-credentials => createCredentials-to-createSecureContext}/tests/input/file-2.js (100%) rename recipes/{crypto-create-credentials => createCredentials-to-createSecureContext}/tests/input/file-3.js (100%) rename recipes/{crypto-create-credentials => createCredentials-to-createSecureContext}/tests/input/file-4.mjs (100%) rename recipes/{crypto-create-credentials => createCredentials-to-createSecureContext}/tests/input/file-5.mjs (100%) rename recipes/{crypto-create-credentials => createCredentials-to-createSecureContext}/tsconfig.json (100%) rename recipes/{crypto-create-credentials => createCredentials-to-createSecureContext}/workflow.yaml (100%) delete mode 100644 recipes/crypto-create-credentials/src/workflow.ts diff --git a/recipes/crypto-create-credentials/README.md b/recipes/createCredentials-to-createSecureContext/README.md similarity index 100% rename from recipes/crypto-create-credentials/README.md rename to recipes/createCredentials-to-createSecureContext/README.md diff --git a/recipes/crypto-create-credentials/codemod.yaml b/recipes/createCredentials-to-createSecureContext/codemod.yaml similarity index 86% rename from recipes/crypto-create-credentials/codemod.yaml rename to recipes/createCredentials-to-createSecureContext/codemod.yaml index 46f004b6..be3cfefe 100644 --- a/recipes/crypto-create-credentials/codemod.yaml +++ b/recipes/createCredentials-to-createSecureContext/codemod.yaml @@ -1,5 +1,5 @@ schema_version: "1.0" -name: "@nodejs/crypto-create-credentials" +name: "@nodejs/createCredentials-to-createSecureContext" version: 1.0.0 description: Handle DEP0010 via transforming `crypto.createCredentials` to `tls.createSecureContext` author: 0hmx diff --git a/recipes/crypto-create-credentials/package.json b/recipes/createCredentials-to-createSecureContext/package.json similarity index 92% rename from recipes/crypto-create-credentials/package.json rename to recipes/createCredentials-to-createSecureContext/package.json index 98a91b0c..f416677f 100644 --- a/recipes/crypto-create-credentials/package.json +++ b/recipes/createCredentials-to-createSecureContext/package.json @@ -1,5 +1,5 @@ { - "name": "@nodejs/crypto-create-credentials", + "name": "@nodejs/createCredentials-to-createSecureContext", "version": "1.0.0", "description": "Handle DEP0010 via transforming `crypto.createCredentials` to `tls.createSecureContext` with the appropriate options.", "type": "module", diff --git a/recipes/createCredentials-to-createSecureContext/src/workflow.ts b/recipes/createCredentials-to-createSecureContext/src/workflow.ts new file mode 100644 index 00000000..2c9ec4ff --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/src/workflow.ts @@ -0,0 +1,271 @@ +import * as os from 'node:os'; +import type { SgRoot, Edit, SgNode } from "@codemod.com/jssg-types/main"; + +type TlsImportState = { + added: boolean; + identifier: string; +}; + +type HandlerResult = { + edits: Edit[]; + wasTransformed: boolean; + newState: TlsImportState; +}; + +type EnsureTlsResult = { + edits: Edit[]; + identifier: string; // The identifier that was used or found + newState: TlsImportState; +}; + +type ImportType = + | 'DESTRUCTURED_REQUIRE' + | 'NAMESPACE_REQUIRE' + | 'DESTRUCTURED_IMPORT' + | 'NAMESPACE_IMPORT'; + +const newImportModule = 'node:tls'; +const newImportFunction = 'createSecureContext'; + +function getImportHelpers(importType: ImportType) { + const isEsm = + importType === 'DESTRUCTURED_IMPORT' || importType === 'NAMESPACE_IMPORT'; + + return { + isEsm, + createNamespaceImport: (id: string, mod: string) => + isEsm + ? `import * as ${id} from '${mod}'` + : `const ${id} = require('${mod}')`, + createDestructuredImport: (specifier: string, mod: string) => + isEsm + ? `import { ${specifier} } from '${mod}'` + : `const { ${specifier} } = require('${mod}')`, + getStatementNode: (match: SgNode): SgNode | null => + isEsm ? match : match.parent(), + }; +} + + +function ensureTlsImport( + rootNode: SgNode, + importType: ImportType, + currentState: TlsImportState, +): EnsureTlsResult { + if (currentState.added) { + return { edits: [], identifier: currentState.identifier, newState: currentState }; + } + + const helpers = getImportHelpers(importType); + const findRule = { + rule: { + kind: helpers.isEsm ? 'import_statement' : 'variable_declarator', + has: { + field: helpers.isEsm ? 'source' : 'value', + has: { regex: `^['"](node:)?tls['"]$` }, + }, + }, + }; + const existingTlsImport = rootNode.find(findRule); + + if (existingTlsImport) { + const nameNode = existingTlsImport.field('name'); + if (nameNode?.is('identifier')) { + const foundIdentifier = nameNode.text(); + return { + edits: [], + identifier: foundIdentifier, + newState: { added: true, identifier: foundIdentifier }, + }; + } + } + + const firstNode = rootNode.children()[0]; + if (!firstNode) { + return { edits: [], identifier: currentState.identifier, newState: currentState }; + } + + const tlsIdentifier = currentState.identifier; + const newImportText = `${helpers.createNamespaceImport(tlsIdentifier, 'node:tls')};${os.EOL}`; + const edit: Edit = { + startPos: firstNode.range().start.index, + endPos: firstNode.range().start.index, + insertedText: newImportText, + }; + + return { + edits: [edit], + identifier: tlsIdentifier, + newState: { added: true, identifier: tlsIdentifier }, + }; +} + +function handleDestructuredImport( + statement: SgNode, + rootNode: SgNode, + currentState: TlsImportState, + importType: ImportType, +): HandlerResult { + const localEdits: Edit[] = []; + const helpers = getImportHelpers(importType); + + const specifiersNode = helpers.isEsm + ? statement.find({ rule: { kind: 'named_imports' } }) ?? statement.field('imports') + : statement.find({ rule: { kind: 'variable_declarator' } })?.field('name'); + + if (!specifiersNode) { + return { edits: [], wasTransformed: false, newState: currentState }; + } + + const destructuredProps = specifiersNode.findAll({ rule: { any: [{ kind: 'shorthand_property_identifier_pattern' }, { kind: 'import_specifier' }]}}); + const createCredentialsNode = destructuredProps.find((id) => id.text() === 'createCredentials'); + + if (!createCredentialsNode) { + return { edits: [], wasTransformed: false, newState: currentState }; + } + + const usages = rootNode.findAll({ rule: { kind: 'call_expression', has: { field: 'function', kind: 'identifier', regex: '^createCredentials$' }}}); + for (const usage of usages) { + usage.field('function')?.replace('createSecureContext'); + } + + const newImportStatement = `${helpers.createDestructuredImport(newImportFunction, newImportModule)};`; + let finalState = currentState; + + if (destructuredProps.length === 1) { + localEdits.push(statement.replace(newImportStatement)); + finalState = { ...currentState, added: true }; + } else { + const otherProps = destructuredProps + .map(d => d.text()) + .filter(text => text !== 'createCredentials'); + + localEdits.push(specifiersNode.replace(`{ ${otherProps.join(', ')} }`)); + + if (!currentState.added) { + const newEdit = { + startPos: statement.range().end.index, + endPos: statement.range().end.index, + insertedText: `${os.EOL}${newImportStatement}`, + }; + localEdits.push(newEdit); + finalState = { ...currentState, added: true }; + } + } + + return { edits: localEdits, wasTransformed: true, newState: finalState }; +} + +function handleNamespaceImport( + importOrDeclarator: SgNode, + rootNode: SgNode, + importType: ImportType, + currentState: TlsImportState, +): HandlerResult { + const localEdits: Edit[] = []; + const helpers = getImportHelpers(importType); + + const nameNode = helpers.isEsm + ? importOrDeclarator.find({ rule: { kind: 'namespace_import' } })?.find({ rule: { kind: 'identifier' } }) + : importOrDeclarator.field('name'); + + if (!nameNode) { + return { edits: [], wasTransformed: false, newState: currentState }; + } + const namespaceName = nameNode.text(); + + const memberAccessUsages = rootNode.findAll({ rule: { kind: 'member_expression', all: [ { has: { field: 'object', regex: `^${namespaceName}$` } }, { has: { field: 'property', regex: '^createCredentials$' } }]}}); + if (memberAccessUsages.length === 0) { + return { edits: [], wasTransformed: false, newState: currentState }; + } + + const allUsages = rootNode.findAll({ rule: { kind: 'member_expression', has: { field: 'object', regex: `^${namespaceName}$` }}}); + let finalState = currentState; + + if (allUsages.length === memberAccessUsages.length) { + const newTlsIdentifier = currentState.identifier; + const newImportStatement = `${helpers.createNamespaceImport(newTlsIdentifier, newImportModule)};`; + const nodeToReplace = helpers.getStatementNode(importOrDeclarator); + + if (nodeToReplace) { + localEdits.push(nodeToReplace.replace(newImportStatement)); + } + finalState = { ...currentState, added: true }; + for (const usage of memberAccessUsages) { + localEdits.push(usage.replace(`${newTlsIdentifier}.createSecureContext`)); + } + } else { + const ensureResult = ensureTlsImport(rootNode, importType, currentState); + localEdits.push(...ensureResult.edits); + finalState = ensureResult.newState; + for (const usage of memberAccessUsages) { + localEdits.push(usage.replace(`${ensureResult.identifier}.createSecureContext`)); + } + } + + return { edits: localEdits, wasTransformed: true, newState: finalState }; +} + +export default function transform(root: SgRoot): string | null { + const rootNode = root.root(); + const allEdits: Edit[] = []; + let wasTransformed = false; + let currentTlsImportState: TlsImportState = { + added: false, + identifier: 'tls', + }; + + const cryptoImportsRule = { + rule: { + any: [ + { kind: 'variable_declarator', has: { field: 'value', kind: 'call_expression', has: { field: 'arguments', has: { regex: "^['\"](node:)?crypto['\"]$" }}}}, + { kind: 'import_statement', has: { field: 'source', regex: "^['\"](node:)?crypto['\"]$" }}, + ], + }, + }; + const cryptoImports = rootNode.findAll(cryptoImportsRule); + + for (const importMatch of cryptoImports) { + const nameNode = importMatch.field('imports') ?? importMatch.field('name'); + let importType: ImportType | undefined; + + if (importMatch.kind() === 'import_statement') { + importType = importMatch.find({ rule: { kind: 'namespace_import' } }) + ? 'NAMESPACE_IMPORT' : 'DESTRUCTURED_IMPORT'; + } else if (nameNode?.is('object_pattern')) { + importType = 'DESTRUCTURED_REQUIRE'; + } else if (nameNode?.is('identifier')) { + importType = 'NAMESPACE_REQUIRE'; + } + + if (importType === undefined) continue; + + let result: HandlerResult | undefined; + const helpers = getImportHelpers(importType); + + switch (importType) { + case 'DESTRUCTURED_REQUIRE': + case 'DESTRUCTURED_IMPORT': { + const statement = helpers.getStatementNode(importMatch); + if (statement) { + result = handleDestructuredImport(statement, rootNode, currentTlsImportState, importType); + } + break; + } + case 'NAMESPACE_REQUIRE': + case 'NAMESPACE_IMPORT': + result = handleNamespaceImport(importMatch, rootNode, importType, currentTlsImportState); + break; + } + + if (result) { + allEdits.push(...result.edits); + currentTlsImportState = result.newState; + if (result.wasTransformed) { + wasTransformed = true; + } + } + } + + return wasTransformed ? rootNode.commitEdits(allEdits) : null; +} diff --git a/recipes/crypto-create-credentials/tests/expected/file-1.js b/recipes/createCredentials-to-createSecureContext/tests/expected/file-1.js similarity index 100% rename from recipes/crypto-create-credentials/tests/expected/file-1.js rename to recipes/createCredentials-to-createSecureContext/tests/expected/file-1.js diff --git a/recipes/crypto-create-credentials/tests/expected/file-2.js b/recipes/createCredentials-to-createSecureContext/tests/expected/file-2.js similarity index 100% rename from recipes/crypto-create-credentials/tests/expected/file-2.js rename to recipes/createCredentials-to-createSecureContext/tests/expected/file-2.js diff --git a/recipes/crypto-create-credentials/tests/expected/file-3.js b/recipes/createCredentials-to-createSecureContext/tests/expected/file-3.js similarity index 100% rename from recipes/crypto-create-credentials/tests/expected/file-3.js rename to recipes/createCredentials-to-createSecureContext/tests/expected/file-3.js diff --git a/recipes/crypto-create-credentials/tests/expected/file-4.mjs b/recipes/createCredentials-to-createSecureContext/tests/expected/file-4.mjs similarity index 100% rename from recipes/crypto-create-credentials/tests/expected/file-4.mjs rename to recipes/createCredentials-to-createSecureContext/tests/expected/file-4.mjs diff --git a/recipes/crypto-create-credentials/tests/expected/file-5.mjs b/recipes/createCredentials-to-createSecureContext/tests/expected/file-5.mjs similarity index 100% rename from recipes/crypto-create-credentials/tests/expected/file-5.mjs rename to recipes/createCredentials-to-createSecureContext/tests/expected/file-5.mjs diff --git a/recipes/crypto-create-credentials/tests/input/file-1.js b/recipes/createCredentials-to-createSecureContext/tests/input/file-1.js similarity index 100% rename from recipes/crypto-create-credentials/tests/input/file-1.js rename to recipes/createCredentials-to-createSecureContext/tests/input/file-1.js diff --git a/recipes/crypto-create-credentials/tests/input/file-2.js b/recipes/createCredentials-to-createSecureContext/tests/input/file-2.js similarity index 100% rename from recipes/crypto-create-credentials/tests/input/file-2.js rename to recipes/createCredentials-to-createSecureContext/tests/input/file-2.js diff --git a/recipes/crypto-create-credentials/tests/input/file-3.js b/recipes/createCredentials-to-createSecureContext/tests/input/file-3.js similarity index 100% rename from recipes/crypto-create-credentials/tests/input/file-3.js rename to recipes/createCredentials-to-createSecureContext/tests/input/file-3.js diff --git a/recipes/crypto-create-credentials/tests/input/file-4.mjs b/recipes/createCredentials-to-createSecureContext/tests/input/file-4.mjs similarity index 100% rename from recipes/crypto-create-credentials/tests/input/file-4.mjs rename to recipes/createCredentials-to-createSecureContext/tests/input/file-4.mjs diff --git a/recipes/crypto-create-credentials/tests/input/file-5.mjs b/recipes/createCredentials-to-createSecureContext/tests/input/file-5.mjs similarity index 100% rename from recipes/crypto-create-credentials/tests/input/file-5.mjs rename to recipes/createCredentials-to-createSecureContext/tests/input/file-5.mjs diff --git a/recipes/crypto-create-credentials/tsconfig.json b/recipes/createCredentials-to-createSecureContext/tsconfig.json similarity index 100% rename from recipes/crypto-create-credentials/tsconfig.json rename to recipes/createCredentials-to-createSecureContext/tsconfig.json diff --git a/recipes/crypto-create-credentials/workflow.yaml b/recipes/createCredentials-to-createSecureContext/workflow.yaml similarity index 100% rename from recipes/crypto-create-credentials/workflow.yaml rename to recipes/createCredentials-to-createSecureContext/workflow.yaml diff --git a/recipes/crypto-create-credentials/src/workflow.ts b/recipes/crypto-create-credentials/src/workflow.ts deleted file mode 100644 index 8dd059e2..00000000 --- a/recipes/crypto-create-credentials/src/workflow.ts +++ /dev/null @@ -1,397 +0,0 @@ -import * as os from 'node:os'; -import type { SgRoot, Edit, SgNode } from "@codemod.com/jssg-types/main"; - -type TlsImportState = { - added: boolean; - identifier: string; -}; - -type HandlerResult = { - edits: Edit[]; - wasTransformed: boolean; - newState: TlsImportState; -}; - -type EnsureTlsResult = { - edits: Edit[]; - identifier: string; // The identifier that was used or found - newState: TlsImportState; -}; - -type ImportType = - | 'DESTRUCTURED_REQUIRE' - | 'NAMESPACE_REQUIRE' - | 'DESTRUCTURED_IMPORT' - | 'NAMESPACE_IMPORT'; - -function ensureTlsImport( - rootNode: SgNode, - importType: ImportType, - currentState: TlsImportState, -): EnsureTlsResult { - if (currentState.added) { - return { - edits: [], - identifier: currentState.identifier, - newState: currentState, - }; - } - - const isEsm = - importType === 'DESTRUCTURED_IMPORT' || importType === 'NAMESPACE_IMPORT'; - const moduleSpecifier = 'node:tls'; - - const findRule = { - rule: { - kind: isEsm ? 'import_statement' : 'variable_declarator', - has: { - field: isEsm ? 'source' : 'value', - has: { regex: `^['"](node:)?tls['"]$` }, - }, - }, - }; - const existingTlsImport = rootNode.find(findRule); - - if (existingTlsImport) { - const nameNode = existingTlsImport.field('name'); - if (nameNode?.is('identifier')) { - const foundIdentifier = nameNode.text(); - return { - edits: [], - identifier: foundIdentifier, - newState: { - added: true, - identifier: foundIdentifier, - }, - }; - } - } - - const firstNode = rootNode.children()[0]; - - if (!firstNode) { - return { - edits: [], - identifier: currentState.identifier, - newState: currentState, - }; - } - - const tlsIdentifier = currentState.identifier; - const newImportText = isEsm - ? `import * as ${tlsIdentifier} from '${moduleSpecifier}';${os.EOL}` - : `const ${tlsIdentifier} = require('${moduleSpecifier}');${os.EOL}`; - - const edit: Edit = { - startPos: firstNode.range().start.index, - endPos: firstNode.range().start.index, - insertedText: newImportText, - }; - - return { - edits: [edit], - identifier: tlsIdentifier, - newState: { - added: true, - identifier: tlsIdentifier, - }, - }; -} - -function handleDestructuredImport( - statement: SgNode, - rootNode: SgNode, - currentState: TlsImportState, -): HandlerResult { - const localEdits: Edit[] = []; - const isEsm = statement.kind() === 'import_statement'; - - let specifiersNode: SgNode | null | undefined; - if (isEsm) { - specifiersNode = - statement.find({ rule: { kind: 'named_imports' } }) ?? - statement.field('imports'); - } else { - const declaratorFindRule = { - rule: { - kind: 'variable_declarator', - has: { - field: 'value', - kind: 'call_expression', - has: { - field: 'arguments', - has: { regex: "^['\"](node:)?crypto['\"]$" }, - }, - }, - }, - }; - const declarator = statement.find(declaratorFindRule); - specifiersNode = declarator?.field('name'); - } - - if (!specifiersNode) { - return { edits: [], wasTransformed: false, newState: currentState }; - } - - const findPropsRule = { - rule: { - any: [ - { kind: 'shorthand_property_identifier_pattern' }, - { kind: 'import_specifier' }, - ], - }, - }; - const destructuredProps = specifiersNode.findAll(findPropsRule); - - const createCredentialsNode = destructuredProps.find( - (id) => id.text() === 'createCredentials', - ); - - if (!createCredentialsNode) { - return { edits: [], wasTransformed: false, newState: currentState }; - } - - const usagesFindRule = { - rule: { - kind: 'call_expression', - has: { - field: 'function', - kind: 'identifier', - regex: '^createCredentials$', - }, - }, - }; - const usages = rootNode.findAll(usagesFindRule); - for (const usage of usages) { - const functionIdentifier = usage.field('function'); - if (functionIdentifier) { - localEdits.push(functionIdentifier.replace('createSecureContext')); - } - } - - const newImportModule = 'node:tls'; - const newImportFunction = 'createSecureContext'; - const newImportStatement = isEsm - ? `import { ${newImportFunction} } from '${newImportModule}';` - : `const { ${newImportFunction} } = require('${newImportModule}');`; - - let finalState = currentState; - - if (destructuredProps.length === 1) { - localEdits.push(statement.replace(newImportStatement)); - finalState = { ...currentState, added: true }; - } else { - const otherProps = []; - for (const d of destructuredProps) { - const text = d.text(); - if (text !== 'createCredentials') otherProps.push(text); - } - - const newDestructuredString = `{ ${otherProps.join(', ')} }`; - localEdits.push(specifiersNode.replace(newDestructuredString)); - - if (!currentState.added) { - const newEdit = { - startPos: statement.range().end.index, - // appending, so use `end` for both - endPos: statement.range().end.index, - insertedText: `${os.EOL}${newImportStatement}`, - }; - localEdits.push(newEdit); - finalState = { ...currentState, added: true }; - } - } - - return { - edits: localEdits, - wasTransformed: true, - newState: finalState, - }; -} - -function handleNamespaceImport( - importOrDeclarator: SgNode, - rootNode: SgNode, - importType: ImportType, - currentState: TlsImportState, -): HandlerResult { - const localEdits: Edit[] = []; - const isEsm = importType === 'NAMESPACE_IMPORT'; - - const nameNode = isEsm - ? importOrDeclarator.find({ rule: { kind: 'namespace_import' } })?.find({ rule: { kind: 'identifier' } }) - : importOrDeclarator.field('name'); - - if (!nameNode) { - return { edits: [], wasTransformed: false, newState: currentState }; - } - const namespaceName = nameNode.text(); - - const memberAccessFindRule = { - rule: { - kind: 'member_expression', - all: [ - { has: { field: 'object', regex: `^${namespaceName}$` } }, - { has: { field: 'property', regex: '^createCredentials$' } }, - ], - }, - }; - const memberAccessUsages = rootNode.findAll(memberAccessFindRule); - - if (memberAccessUsages.length === 0) { - return { edits: [], wasTransformed: false, newState: currentState }; - } - - const allUsagesFindRule = { - rule: { - kind: 'member_expression', - has: { field: 'object', regex: `^${namespaceName}$` }, - }, - }; - const allUsages = rootNode.findAll(allUsagesFindRule); - - let finalState = currentState; - - if (allUsages.length === memberAccessUsages.length) { - const newTlsIdentifier = currentState.identifier; - const newImportModule = 'node:tls'; - const newImportStatement = isEsm - ? `import * as ${newTlsIdentifier} from '${newImportModule}';` - : `const ${newTlsIdentifier} = require('${newImportModule}');`; - - const nodeToReplace = isEsm - ? importOrDeclarator - : importOrDeclarator.parent(); - - if (nodeToReplace) { - localEdits.push(nodeToReplace.replace(newImportStatement)); - } - - finalState = { ...currentState, added: true }; - - for (const usage of memberAccessUsages) { - const replacementText = `${newTlsIdentifier}.createSecureContext`; - localEdits.push(usage.replace(replacementText)); - } - } else { - const ensureResult = ensureTlsImport( - rootNode, - importType, - currentState, - ); - localEdits.push(...ensureResult.edits); - finalState = ensureResult.newState; - - for (const usage of memberAccessUsages) { - localEdits.push(usage.replace(`${ensureResult.identifier}.createSecureContext`)); - } - } - - return { - edits: localEdits, - wasTransformed: true, - newState: finalState, - }; -} - -export default function transform(root: SgRoot): string | null { - const rootNode = root.root(); - const allEdits: Edit[] = []; - let wasTransformed = false; - let currentTlsImportState: TlsImportState = { - added: false, - identifier: 'tls', - }; - - const cryptoImportsRule = { - rule: { - any: [ - { - kind: 'variable_declarator', - has: { - field: 'value', - kind: 'call_expression', - has: { - field: 'arguments', - has: { regex: "^['\"](node:)?crypto['\"]$" }, - }, - }, - }, - { - kind: 'import_statement', - has: { - field: 'source', - regex: "^['\"](node:)?crypto['\"]$", - }, - }, - ], - }, - }; - const cryptoImports = rootNode.findAll(cryptoImportsRule); - - for (const importMatch of cryptoImports) { - const nameNode = - importMatch.field('imports') ?? importMatch.field('name'); - let importType: ImportType | undefined; - - if (importMatch.kind() === 'import_statement') { - importType = importMatch.find({ rule: { kind: 'namespace_import' } }) - ? 'NAMESPACE_IMPORT' - : 'DESTRUCTURED_IMPORT'; - } else { - if (nameNode?.is('object_pattern')) { - importType = 'DESTRUCTURED_REQUIRE'; - } else if (nameNode?.is('identifier')) { - importType = 'NAMESPACE_REQUIRE'; - } - } - - if (importType === undefined) { - continue; - } - - let result: HandlerResult | undefined; - - switch (importType) { - case 'DESTRUCTURED_REQUIRE': - case 'DESTRUCTURED_IMPORT': { - const statement = - importType === 'DESTRUCTURED_REQUIRE' - ? importMatch.parent() - : importMatch; - if (statement) { - result = handleDestructuredImport( - statement, - rootNode, - currentTlsImportState, - ); - } - break; - } - - case 'NAMESPACE_REQUIRE': - case 'NAMESPACE_IMPORT': - result = handleNamespaceImport( - importMatch, - rootNode, - importType, - currentTlsImportState, - ); - break; - } - - if (result) { - allEdits.push(...result.edits); - currentTlsImportState = result.newState; - if (result.wasTransformed) { - wasTransformed = true; - } - } - } - - if (wasTransformed) { - return rootNode.commitEdits(allEdits); - } - - return null; -} From 884a44d713a11de56d8216f1f068273e045e08ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= Date: Wed, 13 Aug 2025 08:20:32 +0530 Subject: [PATCH 24/33] updated to use destructred import --- .../src/workflow.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/recipes/createCredentials-to-createSecureContext/src/workflow.ts b/recipes/createCredentials-to-createSecureContext/src/workflow.ts index 2c9ec4ff..8a72f98b 100644 --- a/recipes/createCredentials-to-createSecureContext/src/workflow.ts +++ b/recipes/createCredentials-to-createSecureContext/src/workflow.ts @@ -1,4 +1,4 @@ -import * as os from 'node:os'; +import { EOL } from 'node:os'; import type { SgRoot, Edit, SgNode } from "@codemod.com/jssg-types/main"; type TlsImportState = { @@ -86,7 +86,7 @@ function ensureTlsImport( } const tlsIdentifier = currentState.identifier; - const newImportText = `${helpers.createNamespaceImport(tlsIdentifier, 'node:tls')};${os.EOL}`; + const newImportText = `${helpers.createNamespaceImport(tlsIdentifier, 'node:tls')};${EOL}`; const edit: Edit = { startPos: firstNode.range().start.index, endPos: firstNode.range().start.index, @@ -146,7 +146,7 @@ function handleDestructuredImport( const newEdit = { startPos: statement.range().end.index, endPos: statement.range().end.index, - insertedText: `${os.EOL}${newImportStatement}`, + insertedText: `${EOL}${newImportStatement}`, }; localEdits.push(newEdit); finalState = { ...currentState, added: true }; From 015cd212ab4e223d5879254ad13327d04649a148 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= Date: Thu, 14 Aug 2025 20:22:33 +0530 Subject: [PATCH 25/33] stable --- .../src/workflow.ts | 749 ++++++++++++------ 1 file changed, 490 insertions(+), 259 deletions(-) diff --git a/recipes/createCredentials-to-createSecureContext/src/workflow.ts b/recipes/createCredentials-to-createSecureContext/src/workflow.ts index 8a72f98b..4ed68d25 100644 --- a/recipes/createCredentials-to-createSecureContext/src/workflow.ts +++ b/recipes/createCredentials-to-createSecureContext/src/workflow.ts @@ -1,271 +1,502 @@ import { EOL } from 'node:os'; -import type { SgRoot, Edit, SgNode } from "@codemod.com/jssg-types/main"; - -type TlsImportState = { - added: boolean; - identifier: string; -}; - -type HandlerResult = { - edits: Edit[]; - wasTransformed: boolean; - newState: TlsImportState; -}; - -type EnsureTlsResult = { - edits: Edit[]; - identifier: string; // The identifier that was used or found - newState: TlsImportState; -}; - -type ImportType = - | 'DESTRUCTURED_REQUIRE' - | 'NAMESPACE_REQUIRE' - | 'DESTRUCTURED_IMPORT' - | 'NAMESPACE_IMPORT'; - -const newImportModule = 'node:tls'; -const newImportFunction = 'createSecureContext'; - -function getImportHelpers(importType: ImportType) { - const isEsm = - importType === 'DESTRUCTURED_IMPORT' || importType === 'NAMESPACE_IMPORT'; - - return { - isEsm, - createNamespaceImport: (id: string, mod: string) => - isEsm - ? `import * as ${id} from '${mod}'` - : `const ${id} = require('${mod}')`, - createDestructuredImport: (specifier: string, mod: string) => - isEsm - ? `import { ${specifier} } from '${mod}'` - : `const { ${specifier} } = require('${mod}')`, - getStatementNode: (match: SgNode): SgNode | null => - isEsm ? match : match.parent(), - }; -} +import type { SgRoot, Edit, SgNode, Kinds, TypesMap } from "@codemod.com/jssg-types/main"; +import { getNodeRequireCalls } from "@nodejs/codemod-utils/ast-grep/require-call"; +import { getNodeImportCalls, getNodeImportStatements } from "@nodejs/codemod-utils/ast-grep/import-statement"; +const newImportFunction = 'createSecureContext' +const newImportModule = 'node:tls' +const oldFunctionName = 'createCredentials'; +const oldImportModule = 'node:crypto' -function ensureTlsImport( - rootNode: SgNode, - importType: ImportType, - currentState: TlsImportState, -): EnsureTlsResult { - if (currentState.added) { - return { edits: [], identifier: currentState.identifier, newState: currentState }; - } - - const helpers = getImportHelpers(importType); - const findRule = { - rule: { - kind: helpers.isEsm ? 'import_statement' : 'variable_declarator', - has: { - field: helpers.isEsm ? 'source' : 'value', - has: { regex: `^['"](node:)?tls['"]$` }, - }, - }, - }; - const existingTlsImport = rootNode.find(findRule); - - if (existingTlsImport) { - const nameNode = existingTlsImport.field('name'); - if (nameNode?.is('identifier')) { - const foundIdentifier = nameNode.text(); - return { - edits: [], - identifier: foundIdentifier, - newState: { added: true, identifier: foundIdentifier }, - }; - } - } - - const firstNode = rootNode.children()[0]; - if (!firstNode) { - return { edits: [], identifier: currentState.identifier, newState: currentState }; - } - - const tlsIdentifier = currentState.identifier; - const newImportText = `${helpers.createNamespaceImport(tlsIdentifier, 'node:tls')};${EOL}`; - const edit: Edit = { - startPos: firstNode.range().start.index, - endPos: firstNode.range().start.index, - insertedText: newImportText, - }; - - return { - edits: [edit], - identifier: tlsIdentifier, - newState: { added: true, identifier: tlsIdentifier }, - }; +function handleNamespaceImport( + rootNode: SgRoot, + localNamespace: string, + declaration: SgNode>, + importType: 'require' | 'static' | 'dynamic-await' +): Edit[] { + const allEdits: Edit[] = []; + const newNamespace = 'tls'; + + const usages = rootNode.root().findAll({ + rule: { + kind: 'call_expression', + has: { + field: 'function', + kind: 'member_expression', + all: [ + { has: { field: 'object', regex: `^${localNamespace}$` } }, + { has: { field: 'property', regex: `^${oldFunctionName}$` } } + ] + } + } + }); + + if (usages.length === 0) { + return []; + } + + for (const usage of usages) { + const func = usage.field('function'); + if (func) { + allEdits.push(func.replace(`${newNamespace}.${newImportFunction}`)); + } + } + + let newImportStatement = ''; + switch (importType) { + case 'require': + newImportStatement = `const ${newNamespace} = require('${newImportModule}');`; + break; + case 'static': + newImportStatement = `import * as ${newNamespace} from '${newImportModule}';`; + break; + case 'dynamic-await': + newImportStatement = `const ${newNamespace} = await import('${newImportModule}');`; + break; + } + + allEdits.push(declaration.replace(newImportStatement)); + + return allEdits; } function handleDestructuredImport( - statement: SgNode, - rootNode: SgNode, - currentState: TlsImportState, - importType: ImportType, -): HandlerResult { - const localEdits: Edit[] = []; - const helpers = getImportHelpers(importType); - - const specifiersNode = helpers.isEsm - ? statement.find({ rule: { kind: 'named_imports' } }) ?? statement.field('imports') - : statement.find({ rule: { kind: 'variable_declarator' } })?.field('name'); - - if (!specifiersNode) { - return { edits: [], wasTransformed: false, newState: currentState }; - } - - const destructuredProps = specifiersNode.findAll({ rule: { any: [{ kind: 'shorthand_property_identifier_pattern' }, { kind: 'import_specifier' }]}}); - const createCredentialsNode = destructuredProps.find((id) => id.text() === 'createCredentials'); - - if (!createCredentialsNode) { - return { edits: [], wasTransformed: false, newState: currentState }; - } - - const usages = rootNode.findAll({ rule: { kind: 'call_expression', has: { field: 'function', kind: 'identifier', regex: '^createCredentials$' }}}); - for (const usage of usages) { - usage.field('function')?.replace('createSecureContext'); - } - - const newImportStatement = `${helpers.createDestructuredImport(newImportFunction, newImportModule)};`; - let finalState = currentState; - - if (destructuredProps.length === 1) { - localEdits.push(statement.replace(newImportStatement)); - finalState = { ...currentState, added: true }; - } else { - const otherProps = destructuredProps - .map(d => d.text()) - .filter(text => text !== 'createCredentials'); - - localEdits.push(specifiersNode.replace(`{ ${otherProps.join(', ')} }`)); - - if (!currentState.added) { - const newEdit = { - startPos: statement.range().end.index, - endPos: statement.range().end.index, - insertedText: `${EOL}${newImportStatement}`, - }; - localEdits.push(newEdit); - finalState = { ...currentState, added: true }; - } - } - - return { edits: localEdits, wasTransformed: true, newState: finalState }; + rootNode: SgRoot, + idNode: SgNode>, + declaration: SgNode>, + importType: 'require' | 'static' | 'dynamic-await' +): Edit[] { + let localFunctionName: string | null = null; + let targetSpecifierNode: SgNode | null = null; + let isAliased = false; + + const relevantSpecifiers = idNode.children().filter( + child => ['pair_pattern', 'shorthand_property_identifier_pattern', 'import_specifier'].includes(child.kind() as string) + ); + + for (const spec of relevantSpecifiers) { + let keyNode, aliasNode; + + if (spec.kind() === 'import_specifier') { + keyNode = spec.field('name'); + aliasNode = spec.field('alias'); + } else if (spec.kind() === 'pair_pattern') { + keyNode = spec.field('key'); + aliasNode = spec.field('value'); + } else { + keyNode = spec; + } + + if (keyNode?.text() === oldFunctionName) { + targetSpecifierNode = spec; + isAliased = Boolean(aliasNode); + localFunctionName = isAliased ? aliasNode!.text() : keyNode!.text(); + break; + } + } + + if (localFunctionName && targetSpecifierNode) { + const allEdits: Edit[] = []; + + if (!isAliased) { + const usageEdits = findAndReplaceUsages(rootNode, localFunctionName, newImportFunction); + allEdits.push(...usageEdits); + } + + const aliasSeparator = importType === 'static' ? ' as' : ':'; + const newImportSpecifier = isAliased + ? `{ ${newImportFunction}${aliasSeparator} ${localFunctionName} }` + : `{ ${newImportFunction} }`; + + let newImportStatement = ''; + switch (importType) { + case 'require': + newImportStatement = `const ${newImportSpecifier} = require('${newImportModule}');`; + break; + case 'static': + newImportStatement = `import ${newImportSpecifier} from '${newImportModule}';`; + break; + case 'dynamic-await': + newImportStatement = `const ${newImportSpecifier} = await import('${newImportModule}');`; + break; + } + + const otherSpecifiers = relevantSpecifiers.filter(s => s !== targetSpecifierNode); + if (otherSpecifiers.length > 0) { + const otherSpecifiersText = otherSpecifiers.map(s => s.text()).join(', '); + let modifiedOldImport = ''; + switch (importType) { + case 'require': + modifiedOldImport = `const { ${otherSpecifiersText} } = require('${oldImportModule}');`; + break; + case 'static': + modifiedOldImport = `import { ${otherSpecifiersText} } from '${oldImportModule}';`; + break; + case 'dynamic-await': + modifiedOldImport = `const { ${otherSpecifiersText} } = await import('${oldImportModule}');`; + break; + } + const replacementText = `${modifiedOldImport}${EOL}${newImportStatement}`; + allEdits.push(declaration.replace(replacementText)); + } else { + allEdits.push(declaration.replace(newImportStatement)); + } + + return allEdits; + } + + return []; } -function handleNamespaceImport( - importOrDeclarator: SgNode, - rootNode: SgNode, - importType: ImportType, - currentState: TlsImportState, -): HandlerResult { - const localEdits: Edit[] = []; - const helpers = getImportHelpers(importType); - - const nameNode = helpers.isEsm - ? importOrDeclarator.find({ rule: { kind: 'namespace_import' } })?.find({ rule: { kind: 'identifier' } }) - : importOrDeclarator.field('name'); - - if (!nameNode) { - return { edits: [], wasTransformed: false, newState: currentState }; - } - const namespaceName = nameNode.text(); - - const memberAccessUsages = rootNode.findAll({ rule: { kind: 'member_expression', all: [ { has: { field: 'object', regex: `^${namespaceName}$` } }, { has: { field: 'property', regex: '^createCredentials$' } }]}}); - if (memberAccessUsages.length === 0) { - return { edits: [], wasTransformed: false, newState: currentState }; - } - - const allUsages = rootNode.findAll({ rule: { kind: 'member_expression', has: { field: 'object', regex: `^${namespaceName}$` }}}); - let finalState = currentState; - - if (allUsages.length === memberAccessUsages.length) { - const newTlsIdentifier = currentState.identifier; - const newImportStatement = `${helpers.createNamespaceImport(newTlsIdentifier, newImportModule)};`; - const nodeToReplace = helpers.getStatementNode(importOrDeclarator); - - if (nodeToReplace) { - localEdits.push(nodeToReplace.replace(newImportStatement)); - } - finalState = { ...currentState, added: true }; - for (const usage of memberAccessUsages) { - localEdits.push(usage.replace(`${newTlsIdentifier}.createSecureContext`)); - } - } else { - const ensureResult = ensureTlsImport(rootNode, importType, currentState); - localEdits.push(...ensureResult.edits); - finalState = ensureResult.newState; - for (const usage of memberAccessUsages) { - localEdits.push(usage.replace(`${ensureResult.identifier}.createSecureContext`)); - } - } - - return { edits: localEdits, wasTransformed: true, newState: finalState }; +function findAndReplaceUsages( + rootNode: SgRoot, + localFunctionName: string, + newFunctionName: string, + object: string | null = null +): Edit[] { + const edits: Edit[] = []; + + if (object) { + const usages = rootNode.root().findAll({ + rule: { + kind: 'call_expression', + has: { + field: 'function', + kind: 'member_expression', + all: [ + { has: { field: 'object', regex: `^${object}$` } }, + { has: { field: 'property', regex: `^${localFunctionName}$` } } + ] + } + } + }); + + for (const usage of usages) { + const memberExpressionNode = usage.field('function'); + const propertyNode = memberExpressionNode?.field('property'); + if (propertyNode) { + edits.push(propertyNode.replace(newFunctionName)); + } + } + } else { + const usages = rootNode.root().findAll({ + rule: { + kind: 'call_expression', + has: { field: 'function', kind: 'identifier', regex: `^${localFunctionName}$` }, + }, + }); + + for (const usage of usages) { + const functionNode = usage.field('function'); + if (functionNode) { + edits.push(functionNode.replace(newFunctionName)); + } + } + } + return edits; +} + +function handleRequire( + statement: SgNode>, + rootNode: SgRoot, +): Edit[] { + const idNode = statement.child(0); + const declaration = statement.parent(); + + if (!idNode || !declaration) { + return []; + } + + // Handle Namespace Imports: const crypto = require('...') + if (idNode.kind() === 'identifier') { + const localNamespace = idNode.text(); + return handleNamespaceImport(rootNode, localNamespace, declaration, 'require'); + } + + // Handle Destructured Imports: const { ... } = require('...') + if (idNode.kind() === 'object_pattern') { + return handleDestructuredImport(rootNode, idNode, declaration, 'require'); + } + + return []; +} +function handleStaticImport( + statement: SgNode>, + rootNode: SgRoot, +): Edit[] { + + const modulePathNode = statement.field('source'); + const importClause = statement.child(1); + + if (importClause?.kind() !== 'import_clause' || !modulePathNode) { + return []; + } + + const clauseContent = importClause.child(0); + + if (!clauseContent) { + return []; + } + + // Handle Namespace Imports: import * as crypto from '...' + if (clauseContent.kind() === 'namespace_import') { + const localNamespace = clauseContent.find({ rule: { kind: 'identifier' } })?.text(); + const allEdits: Edit[] = []; + + const usages = rootNode.root().findAll({ + rule: { + kind: 'call_expression', + has: { + field: 'function', + kind: 'member_expression', + all: [ + { has: { field: 'object', regex: `^${localNamespace}$` } }, + { has: { field: 'property', regex: `^${oldFunctionName}$` } } + ] + } + } + }); + + if (usages.length > 0) { + const newNamespace = 'tls'; + + for (const usage of usages) { + const func = usage.field('function'); + if (func) { + allEdits.push(func.replace(`${newNamespace}.${newImportFunction}`)); + } + } + + const newImportStatement = `import * as ${newNamespace} from '${newImportModule}';`; + allEdits.push(statement.replace(newImportStatement)); + + return allEdits; + } + } + + // Handle Named Imports: import { ... } from '...' + if (clauseContent.kind() === 'named_imports') { + const namedImportsNode = clauseContent; + let localFunctionName: string | null = null; + let targetSpecifierNode: SgNode | null = null; + let isAliased = false; + + const relevantSpecifiers = namedImportsNode.children().filter( + child => child.kind() === 'import_specifier' + ); + + for (const spec of relevantSpecifiers) { + const nameNode = spec.field('name'); + const aliasNode = spec.field('alias'); + + + if (nameNode?.text() === oldFunctionName) { + targetSpecifierNode = spec; + isAliased = Boolean(aliasNode); + localFunctionName = isAliased ? aliasNode!.text() : nameNode!.text(); + break; + } + } + + if (localFunctionName && targetSpecifierNode) { + const allEdits: Edit[] = []; + const declaration = statement; + + if (!isAliased) { + const usageEdits = findAndReplaceUsages(rootNode, localFunctionName, newImportFunction); + allEdits.push(...usageEdits); + } else { + } + + const newImportSpecifier = isAliased + ? `{ ${newImportFunction} as ${localFunctionName} }` + : `{ ${newImportFunction} }`; + const newImportStatement = `import ${newImportSpecifier} from '${newImportModule}';`; + + const otherSpecifiers = relevantSpecifiers.filter(s => s !== targetSpecifierNode); + if (otherSpecifiers.length > 0) { + const modifiedOldImport = `import { ${otherSpecifiers.map(s => s.text()).join(', ')} } from '${oldImportModule}';`; + const replacementText = `${modifiedOldImport}${EOL}${newImportStatement}`; + allEdits.push(declaration.replace(replacementText)); + } else { + allEdits.push(declaration.replace(newImportStatement)); + } + + return allEdits; + } + } + + return []; +} + +function handleDynamicImport( + statement: SgNode>, + rootNode: SgRoot, +): Edit[] { + + const valueNode = statement.field('value'); + const idNode = statement.child(0); + const declaration = statement.parent(); + + if (valueNode?.kind() === 'call_expression' && idNode?.kind() === 'identifier') { + const functionNode = valueNode.field('function'); + const isThenCall = functionNode?.kind() === 'member_expression' && functionNode.field('property')?.text() === 'then'; + const awaitNode = functionNode?.field('object')?.find({ rule: { kind: 'await_expression' } }); + + const importModuleStringNode = awaitNode?.find({ + rule: { + kind: 'string', + has: { kind: 'string_fragment', regex: `^${oldImportModule}$` } + } + }); + + if (isThenCall && importModuleStringNode) { + const allEdits: Edit[] = []; + + const usageEdits = findAndReplaceUsages(rootNode, idNode.text(), newImportFunction); + + allEdits.push(...usageEdits); + allEdits.push(idNode.replace(newImportFunction)); + allEdits.push(importModuleStringNode.replace(`'${newImportModule}'`)); + + const thenCallback = valueNode.field('arguments')?.child(0); + const callbackBody = thenCallback?.field('body'); + if (callbackBody?.kind() === 'member_expression') { + const propertyNode = callbackBody.field('property'); + if (propertyNode?.text() === oldFunctionName) { + allEdits.push(propertyNode.replace(newImportFunction)); + } + } + + return allEdits; + } + } + + if (valueNode?.kind() === 'await_expression') { + if (!declaration) { + return []; + } + + if (idNode?.kind() === 'identifier') { + const localNamespace = idNode.text(); + const allEdits: Edit[] = []; + + const usages = rootNode.root().findAll({ + rule: { + kind: 'call_expression', + has: { + field: 'function', + kind: 'member_expression', + all: [ + { has: { field: 'object', regex: `^${localNamespace}$` } }, + { has: { field: 'property', regex: `^${oldFunctionName}$` } } + ] + } + } + }); + + if (usages.length > 0) { + const newNamespace = 'tls'; + + for (const usage of usages) { + const func = usage.field('function'); + if (func) { + allEdits.push(func.replace(`${newNamespace}.${newImportFunction}`)); + } + } + + const newImportStatement = `const ${newNamespace} = await import('${newImportModule}');`; + allEdits.push(declaration.replace(newImportStatement)); + + return allEdits; + } + } + + if (idNode?.kind() === 'object_pattern') { + let localFunctionName: string | null = null; + let targetSpecifierNode: SgNode | null = null; + let isAliased = false; + + const relevantSpecifiers = idNode.children().filter( + child => child.kind() === 'pair_pattern' || child.kind() === 'shorthand_property_identifier_pattern' + ); + + for (const spec of relevantSpecifiers) { + const key = spec.kind() === 'pair_pattern' ? spec.field('key') : spec; + if (key?.text() === oldFunctionName) { + targetSpecifierNode = spec; + isAliased = spec.kind() === 'pair_pattern'; + localFunctionName = isAliased ? spec.field('value')!.text() : key.text(); + break; + } + } + + if (localFunctionName && targetSpecifierNode) { + const allEdits: Edit[] = []; + + if (!isAliased) { + const usageEdits = findAndReplaceUsages(rootNode, localFunctionName, newImportFunction); + allEdits.push(...usageEdits); + } else { + } + + const newImportSpecifier = isAliased + ? `{ ${newImportFunction}: ${localFunctionName} }` + : `{ ${newImportFunction} }`; + const newImportStatement = `const ${newImportSpecifier} = await import('${newImportModule}');`; + + const otherSpecifiers = relevantSpecifiers.filter(s => s !== targetSpecifierNode); + if (otherSpecifiers.length > 0) { + const modifiedOldImport = `const { ${otherSpecifiers.map(s => s.text()).join(', ')} } = await import('${oldImportModule}');`; + const replacementText = `${modifiedOldImport}${EOL}${newImportStatement}`; + allEdits.push(declaration.replace(replacementText)); + } else { + allEdits.push(declaration.replace(newImportStatement)); + } + + return allEdits; + } + } + } + + return []; } export default function transform(root: SgRoot): string | null { - const rootNode = root.root(); - const allEdits: Edit[] = []; - let wasTransformed = false; - let currentTlsImportState: TlsImportState = { - added: false, - identifier: 'tls', - }; - - const cryptoImportsRule = { - rule: { - any: [ - { kind: 'variable_declarator', has: { field: 'value', kind: 'call_expression', has: { field: 'arguments', has: { regex: "^['\"](node:)?crypto['\"]$" }}}}, - { kind: 'import_statement', has: { field: 'source', regex: "^['\"](node:)?crypto['\"]$" }}, - ], - }, - }; - const cryptoImports = rootNode.findAll(cryptoImportsRule); - - for (const importMatch of cryptoImports) { - const nameNode = importMatch.field('imports') ?? importMatch.field('name'); - let importType: ImportType | undefined; - - if (importMatch.kind() === 'import_statement') { - importType = importMatch.find({ rule: { kind: 'namespace_import' } }) - ? 'NAMESPACE_IMPORT' : 'DESTRUCTURED_IMPORT'; - } else if (nameNode?.is('object_pattern')) { - importType = 'DESTRUCTURED_REQUIRE'; - } else if (nameNode?.is('identifier')) { - importType = 'NAMESPACE_REQUIRE'; - } - - if (importType === undefined) continue; - - let result: HandlerResult | undefined; - const helpers = getImportHelpers(importType); - - switch (importType) { - case 'DESTRUCTURED_REQUIRE': - case 'DESTRUCTURED_IMPORT': { - const statement = helpers.getStatementNode(importMatch); - if (statement) { - result = handleDestructuredImport(statement, rootNode, currentTlsImportState, importType); - } - break; - } - case 'NAMESPACE_REQUIRE': - case 'NAMESPACE_IMPORT': - result = handleNamespaceImport(importMatch, rootNode, importType, currentTlsImportState); - break; - } - - if (result) { - allEdits.push(...result.edits); - currentTlsImportState = result.newState; - if (result.wasTransformed) { - wasTransformed = true; - } - } - } - - return wasTransformed ? rootNode.commitEdits(allEdits) : null; + const rootNode = root.root(); + const allEdits: Edit[] = []; + let wasTransformed = false; + + // @ts-ignore + const requireImports = getNodeRequireCalls(root, 'crypto'); + // @ts-ignore + const staticImports = getNodeImportStatements(root, 'crypto'); + // @ts-ignore + const dynamicImports = getNodeImportCalls(root, 'crypto'); + + for (const requireCall of requireImports) { + const edits = handleRequire(requireCall, root); + if (edits.length > 0) { + wasTransformed = true; + allEdits.push(...edits); + } + } + + for (const staticImport of staticImports) { + const edits = handleStaticImport(staticImport, root); + if (edits.length > 0) { + wasTransformed = true; + allEdits.push(...edits); + } + } + + for (const dynamicImport of dynamicImports) { + const edits = handleDynamicImport(dynamicImport, root); + if (edits.length > 0) { + wasTransformed = true; + allEdits.push(...edits); + } + } + + return wasTransformed ? rootNode.commitEdits(allEdits) : null; } From 152e90f5bbc2ebf5079159a06a03755f6258d92a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= Date: Thu, 14 Aug 2025 20:59:25 +0530 Subject: [PATCH 26/33] fix --- .../src/workflow.ts | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/recipes/createCredentials-to-createSecureContext/src/workflow.ts b/recipes/createCredentials-to-createSecureContext/src/workflow.ts index 4ed68d25..df1ed582 100644 --- a/recipes/createCredentials-to-createSecureContext/src/workflow.ts +++ b/recipes/createCredentials-to-createSecureContext/src/workflow.ts @@ -7,6 +7,7 @@ const newImportFunction = 'createSecureContext' const newImportModule = 'node:tls' const oldFunctionName = 'createCredentials'; const oldImportModule = 'node:crypto' +const newNamespace = 'tls'; function handleNamespaceImport( rootNode: SgRoot, @@ -15,7 +16,6 @@ function handleNamespaceImport( importType: 'require' | 'static' | 'dynamic-await' ): Edit[] { const allEdits: Edit[] = []; - const newNamespace = 'tls'; const usages = rootNode.root().findAll({ rule: { @@ -75,7 +75,8 @@ function handleDestructuredImport( ); for (const spec of relevantSpecifiers) { - let keyNode, aliasNode; + let keyNode: SgNode> | null = null; + let aliasNode: SgNode> | null = null; if (spec.kind() === 'import_specifier') { keyNode = spec.field('name'); @@ -220,20 +221,18 @@ function handleRequire( return []; } + function handleStaticImport( statement: SgNode>, rootNode: SgRoot, ): Edit[] { - const modulePathNode = statement.field('source'); const importClause = statement.child(1); - - if (importClause?.kind() !== 'import_clause' || !modulePathNode) { + if (importClause?.kind() !== 'import_clause') { return []; } - + // Detects: import * as crypto from '...' const clauseContent = importClause.child(0); - if (!clauseContent) { return []; } @@ -258,7 +257,6 @@ function handleStaticImport( }); if (usages.length > 0) { - const newNamespace = 'tls'; for (const usage of usages) { const func = usage.field('function'); @@ -338,6 +336,7 @@ function handleDynamicImport( const idNode = statement.child(0); const declaration = statement.parent(); + // Detects: const x = (await import(...)).then(...) if (valueNode?.kind() === 'call_expression' && idNode?.kind() === 'identifier') { const functionNode = valueNode.field('function'); const isThenCall = functionNode?.kind() === 'member_expression' && functionNode.field('property')?.text() === 'then'; @@ -377,6 +376,7 @@ function handleDynamicImport( return []; } + // Detects: const crypto = await import(...) if (idNode?.kind() === 'identifier') { const localNamespace = idNode.text(); const allEdits: Edit[] = []; @@ -396,8 +396,6 @@ function handleDynamicImport( }); if (usages.length > 0) { - const newNamespace = 'tls'; - for (const usage of usages) { const func = usage.field('function'); if (func) { @@ -412,6 +410,7 @@ function handleDynamicImport( } } + // Detects: const { ... } = await import(...) if (idNode?.kind() === 'object_pattern') { let localFunctionName: string | null = null; let targetSpecifierNode: SgNode | null = null; From c0fd8e05f5bfb220204d6bedda7d9c2c8f8dbcb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= Date: Thu, 14 Aug 2025 21:16:35 +0530 Subject: [PATCH 27/33] update to test --- .../tests/expected/file-1-pair.cjs | 7 +++++++ .../tests/expected/file-1-pair.mjs | 7 +++++++ .../tests/expected/{file-1.js => file-1.cjs} | 2 +- .../tests/expected/{file-4.mjs => file-1.mjs} | 0 .../tests/expected/{file-2.js => file-2.cjs} | 2 +- .../tests/expected/file-2.mjs | 6 ++++++ .../tests/expected/file-3-pair.cjs | 10 ++++++++++ .../tests/expected/file-3-pair.mjs | 10 ++++++++++ .../tests/expected/{file-3.js => file-3.cjs} | 2 +- .../tests/expected/file-3.mjs | 10 ++++++++++ .../tests/expected/file-5.cjs | 6 ++++++ .../tests/expected/file-5.mjs | 2 +- .../tests/expected/file-6-pair.mjs | 6 ++++++ .../tests/expected/file-6.mjs | 6 ++++++ .../tests/input/file-1-pair.cjs | 7 +++++++ .../tests/input/file-1-pair.mjs | 7 +++++++ .../tests/input/{file-1.js => file-1.cjs} | 2 +- .../tests/input/{file-4.mjs => file-1.mjs} | 0 .../tests/input/{file-2.js => file-2.cjs} | 2 +- .../tests/input/file-2.mjs | 6 ++++++ .../tests/input/file-3-pair.cjs | 9 +++++++++ .../tests/input/file-3-pair.mjs | 9 +++++++++ .../tests/input/{file-3.js => file-3.cjs} | 2 +- .../tests/input/file-3.mjs | 9 +++++++++ .../tests/input/file-5.cjs | 6 ++++++ .../tests/input/file-5.mjs | 2 +- .../tests/input/file-6-pair.mjs | 6 ++++++ .../tests/input/file-6.mjs | 6 ++++++ 28 files changed, 141 insertions(+), 8 deletions(-) create mode 100644 recipes/createCredentials-to-createSecureContext/tests/expected/file-1-pair.cjs create mode 100644 recipes/createCredentials-to-createSecureContext/tests/expected/file-1-pair.mjs rename recipes/createCredentials-to-createSecureContext/tests/expected/{file-1.js => file-1.cjs} (97%) rename recipes/createCredentials-to-createSecureContext/tests/expected/{file-4.mjs => file-1.mjs} (100%) rename recipes/createCredentials-to-createSecureContext/tests/expected/{file-2.js => file-2.cjs} (97%) create mode 100644 recipes/createCredentials-to-createSecureContext/tests/expected/file-2.mjs create mode 100644 recipes/createCredentials-to-createSecureContext/tests/expected/file-3-pair.cjs create mode 100644 recipes/createCredentials-to-createSecureContext/tests/expected/file-3-pair.mjs rename recipes/createCredentials-to-createSecureContext/tests/expected/{file-3.js => file-3.cjs} (89%) create mode 100644 recipes/createCredentials-to-createSecureContext/tests/expected/file-3.mjs create mode 100644 recipes/createCredentials-to-createSecureContext/tests/expected/file-5.cjs create mode 100644 recipes/createCredentials-to-createSecureContext/tests/expected/file-6-pair.mjs create mode 100644 recipes/createCredentials-to-createSecureContext/tests/expected/file-6.mjs create mode 100644 recipes/createCredentials-to-createSecureContext/tests/input/file-1-pair.cjs create mode 100644 recipes/createCredentials-to-createSecureContext/tests/input/file-1-pair.mjs rename recipes/createCredentials-to-createSecureContext/tests/input/{file-1.js => file-1.cjs} (97%) rename recipes/createCredentials-to-createSecureContext/tests/input/{file-4.mjs => file-1.mjs} (100%) rename recipes/createCredentials-to-createSecureContext/tests/input/{file-2.js => file-2.cjs} (97%) create mode 100644 recipes/createCredentials-to-createSecureContext/tests/input/file-2.mjs create mode 100644 recipes/createCredentials-to-createSecureContext/tests/input/file-3-pair.cjs create mode 100644 recipes/createCredentials-to-createSecureContext/tests/input/file-3-pair.mjs rename recipes/createCredentials-to-createSecureContext/tests/input/{file-3.js => file-3.cjs} (87%) create mode 100644 recipes/createCredentials-to-createSecureContext/tests/input/file-3.mjs create mode 100644 recipes/createCredentials-to-createSecureContext/tests/input/file-5.cjs create mode 100644 recipes/createCredentials-to-createSecureContext/tests/input/file-6-pair.mjs create mode 100644 recipes/createCredentials-to-createSecureContext/tests/input/file-6.mjs diff --git a/recipes/createCredentials-to-createSecureContext/tests/expected/file-1-pair.cjs b/recipes/createCredentials-to-createSecureContext/tests/expected/file-1-pair.cjs new file mode 100644 index 00000000..4d399871 --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/expected/file-1-pair.cjs @@ -0,0 +1,7 @@ +const { createSecureContext: customCreateCredentials } = require('node:tls'); + +const credentials = customCreateCredentials({ + key: privateKey, + cert: certificate, + ca: [caCertificate] +}); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/expected/file-1-pair.mjs b/recipes/createCredentials-to-createSecureContext/tests/expected/file-1-pair.mjs new file mode 100644 index 00000000..efa9b601 --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/expected/file-1-pair.mjs @@ -0,0 +1,7 @@ +import { createSecureContext as customCreateCredentials } from 'node:tls'; + +const credentials = customCreateCredentials({ + key: privateKey, + cert: certificate, + ca: [caCertificate] +}); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/expected/file-1.js b/recipes/createCredentials-to-createSecureContext/tests/expected/file-1.cjs similarity index 97% rename from recipes/createCredentials-to-createSecureContext/tests/expected/file-1.js rename to recipes/createCredentials-to-createSecureContext/tests/expected/file-1.cjs index 059c04bf..5e12a058 100644 --- a/recipes/createCredentials-to-createSecureContext/tests/expected/file-1.js +++ b/recipes/createCredentials-to-createSecureContext/tests/expected/file-1.cjs @@ -4,4 +4,4 @@ const credentials = createSecureContext({ key: privateKey, cert: certificate, ca: [caCertificate] -}); +}); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/expected/file-4.mjs b/recipes/createCredentials-to-createSecureContext/tests/expected/file-1.mjs similarity index 100% rename from recipes/createCredentials-to-createSecureContext/tests/expected/file-4.mjs rename to recipes/createCredentials-to-createSecureContext/tests/expected/file-1.mjs diff --git a/recipes/createCredentials-to-createSecureContext/tests/expected/file-2.js b/recipes/createCredentials-to-createSecureContext/tests/expected/file-2.cjs similarity index 97% rename from recipes/createCredentials-to-createSecureContext/tests/expected/file-2.js rename to recipes/createCredentials-to-createSecureContext/tests/expected/file-2.cjs index 74454515..3d2fa4dd 100644 --- a/recipes/createCredentials-to-createSecureContext/tests/expected/file-2.js +++ b/recipes/createCredentials-to-createSecureContext/tests/expected/file-2.cjs @@ -3,4 +3,4 @@ const tls = require('node:tls'); const credentials = tls.createSecureContext({ key: fs.readFileSync('server-key.pem'), cert: fs.readFileSync('server-cert.pem') -}); +}); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/expected/file-2.mjs b/recipes/createCredentials-to-createSecureContext/tests/expected/file-2.mjs new file mode 100644 index 00000000..c7d63dcc --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/expected/file-2.mjs @@ -0,0 +1,6 @@ +import * as tls from 'node:tls'; + +const credentials = tls.createSecureContext({ + key: fs.readFileSync('server-key.pem'), + cert: fs.readFileSync('server-cert.pem') +}); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/expected/file-3-pair.cjs b/recipes/createCredentials-to-createSecureContext/tests/expected/file-3-pair.cjs new file mode 100644 index 00000000..88385b39 --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/expected/file-3-pair.cjs @@ -0,0 +1,10 @@ +const { createHash } = require('node:crypto'); +const { createSecureContext: customCreateCredentials } = require('node:tls'); + +const credentials = customCreateCredentials({ + key: privateKey, + cert: certificate +}); + +const hash = createHash('sha256'); +hash.update('some data'); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/expected/file-3-pair.mjs b/recipes/createCredentials-to-createSecureContext/tests/expected/file-3-pair.mjs new file mode 100644 index 00000000..4bf1eb7b --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/expected/file-3-pair.mjs @@ -0,0 +1,10 @@ +import { createHash } from 'node:crypto'; +import { createSecureContext as customCreateCredentials } from 'node:tls'; + +const credentials = customCreateCredentials({ + key: privateKey, + cert: certificate +}); + +const hash = createHash('sha256'); +hash.update('some data'); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/expected/file-3.js b/recipes/createCredentials-to-createSecureContext/tests/expected/file-3.cjs similarity index 89% rename from recipes/createCredentials-to-createSecureContext/tests/expected/file-3.js rename to recipes/createCredentials-to-createSecureContext/tests/expected/file-3.cjs index e12b91bb..9b0e83cf 100644 --- a/recipes/createCredentials-to-createSecureContext/tests/expected/file-3.js +++ b/recipes/createCredentials-to-createSecureContext/tests/expected/file-3.cjs @@ -7,4 +7,4 @@ const credentials = createSecureContext({ }); const hash = createHash('sha256'); -hash.update('some data'); +hash.update('some data'); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/expected/file-3.mjs b/recipes/createCredentials-to-createSecureContext/tests/expected/file-3.mjs new file mode 100644 index 00000000..ded95cba --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/expected/file-3.mjs @@ -0,0 +1,10 @@ +import { createHash } from 'node:crypto'; +import { createSecureContext } from 'node:tls'; + +const credentials = createSecureContext({ + key: privateKey, + cert: certificate +}); + +const hash = createHash('sha256'); +hash.update('some data'); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/expected/file-5.cjs b/recipes/createCredentials-to-createSecureContext/tests/expected/file-5.cjs new file mode 100644 index 00000000..0914b681 --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/expected/file-5.cjs @@ -0,0 +1,6 @@ +const tls = require('node:tls'); + +const credentials = tls.createSecureContext({ + key: privateKey, + cert: certificate +}); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/expected/file-5.mjs b/recipes/createCredentials-to-createSecureContext/tests/expected/file-5.mjs index 92231955..3a89f407 100644 --- a/recipes/createCredentials-to-createSecureContext/tests/expected/file-5.mjs +++ b/recipes/createCredentials-to-createSecureContext/tests/expected/file-5.mjs @@ -3,4 +3,4 @@ import * as tls from 'node:tls'; const credentials = tls.createSecureContext({ key: privateKey, cert: certificate -}); +}); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/expected/file-6-pair.mjs b/recipes/createCredentials-to-createSecureContext/tests/expected/file-6-pair.mjs new file mode 100644 index 00000000..14b6d68c --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/expected/file-6-pair.mjs @@ -0,0 +1,6 @@ +const { createSecureContext: customCreateCredentials } = await import('node:tls'); + +const credentials = customCreateCredentials({ + key: privateKey, + cert: certificate +}); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/expected/file-6.mjs b/recipes/createCredentials-to-createSecureContext/tests/expected/file-6.mjs new file mode 100644 index 00000000..76442a1c --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/expected/file-6.mjs @@ -0,0 +1,6 @@ +const { createSecureContext } = await import('node:tls'); + +const credentials = createSecureContext({ + key: privateKey, + cert: certificate +}); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/input/file-1-pair.cjs b/recipes/createCredentials-to-createSecureContext/tests/input/file-1-pair.cjs new file mode 100644 index 00000000..c28229a4 --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/input/file-1-pair.cjs @@ -0,0 +1,7 @@ +const { createCredentials: customCreateCredentials } = require('node:crypto'); + +const credentials = customCreateCredentials({ + key: privateKey, + cert: certificate, + ca: [caCertificate] +}); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/input/file-1-pair.mjs b/recipes/createCredentials-to-createSecureContext/tests/input/file-1-pair.mjs new file mode 100644 index 00000000..651ed81f --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/input/file-1-pair.mjs @@ -0,0 +1,7 @@ +import { createCredentials as customCreateCredentials } from 'node:crypto'; + +const credentials = customCreateCredentials({ + key: privateKey, + cert: certificate, + ca: [caCertificate] +}); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/input/file-1.js b/recipes/createCredentials-to-createSecureContext/tests/input/file-1.cjs similarity index 97% rename from recipes/createCredentials-to-createSecureContext/tests/input/file-1.js rename to recipes/createCredentials-to-createSecureContext/tests/input/file-1.cjs index 217368dc..e4f625f3 100644 --- a/recipes/createCredentials-to-createSecureContext/tests/input/file-1.js +++ b/recipes/createCredentials-to-createSecureContext/tests/input/file-1.cjs @@ -4,4 +4,4 @@ const credentials = createCredentials({ key: privateKey, cert: certificate, ca: [caCertificate] -}); +}); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/input/file-4.mjs b/recipes/createCredentials-to-createSecureContext/tests/input/file-1.mjs similarity index 100% rename from recipes/createCredentials-to-createSecureContext/tests/input/file-4.mjs rename to recipes/createCredentials-to-createSecureContext/tests/input/file-1.mjs diff --git a/recipes/createCredentials-to-createSecureContext/tests/input/file-2.js b/recipes/createCredentials-to-createSecureContext/tests/input/file-2.cjs similarity index 97% rename from recipes/createCredentials-to-createSecureContext/tests/input/file-2.js rename to recipes/createCredentials-to-createSecureContext/tests/input/file-2.cjs index dcaff4bf..70453796 100644 --- a/recipes/createCredentials-to-createSecureContext/tests/input/file-2.js +++ b/recipes/createCredentials-to-createSecureContext/tests/input/file-2.cjs @@ -3,4 +3,4 @@ const crypto = require('node:crypto'); const credentials = crypto.createCredentials({ key: fs.readFileSync('server-key.pem'), cert: fs.readFileSync('server-cert.pem') -}); +}); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/input/file-2.mjs b/recipes/createCredentials-to-createSecureContext/tests/input/file-2.mjs new file mode 100644 index 00000000..9e15d052 --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/input/file-2.mjs @@ -0,0 +1,6 @@ +import * as crypto from 'node:crypto'; + +const credentials = crypto.createCredentials({ + key: fs.readFileSync('server-key.pem'), + cert: fs.readFileSync('server-cert.pem') +}); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/input/file-3-pair.cjs b/recipes/createCredentials-to-createSecureContext/tests/input/file-3-pair.cjs new file mode 100644 index 00000000..059bbbce --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/input/file-3-pair.cjs @@ -0,0 +1,9 @@ +const { createCredentials : customCreateCredentials, createHash } = require('node:crypto'); + +const credentials = customCreateCredentials({ + key: privateKey, + cert: certificate +}); + +const hash = createHash('sha256'); +hash.update('some data'); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/input/file-3-pair.mjs b/recipes/createCredentials-to-createSecureContext/tests/input/file-3-pair.mjs new file mode 100644 index 00000000..5919d2f2 --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/input/file-3-pair.mjs @@ -0,0 +1,9 @@ +import { createCredentials as customCreateCredentials, createHash } from 'node:crypto'; + +const credentials = customCreateCredentials({ + key: privateKey, + cert: certificate +}); + +const hash = createHash('sha256'); +hash.update('some data'); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/input/file-3.js b/recipes/createCredentials-to-createSecureContext/tests/input/file-3.cjs similarity index 87% rename from recipes/createCredentials-to-createSecureContext/tests/input/file-3.js rename to recipes/createCredentials-to-createSecureContext/tests/input/file-3.cjs index 4077b761..2deb8583 100644 --- a/recipes/createCredentials-to-createSecureContext/tests/input/file-3.js +++ b/recipes/createCredentials-to-createSecureContext/tests/input/file-3.cjs @@ -6,4 +6,4 @@ const credentials = createCredentials({ }); const hash = createHash('sha256'); -hash.update('some data'); +hash.update('some data'); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/input/file-3.mjs b/recipes/createCredentials-to-createSecureContext/tests/input/file-3.mjs new file mode 100644 index 00000000..c2b34328 --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/input/file-3.mjs @@ -0,0 +1,9 @@ +import { createCredentials, createHash } from 'node:crypto'; + +const credentials = createCredentials({ + key: privateKey, + cert: certificate +}); + +const hash = createHash('sha256'); +hash.update('some data'); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/input/file-5.cjs b/recipes/createCredentials-to-createSecureContext/tests/input/file-5.cjs new file mode 100644 index 00000000..d8d42793 --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/input/file-5.cjs @@ -0,0 +1,6 @@ +const crypto = require('node:crypto'); + +const credentials = crypto.createCredentials({ + key: privateKey, + cert: certificate +}); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/input/file-5.mjs b/recipes/createCredentials-to-createSecureContext/tests/input/file-5.mjs index 2f064efc..5e7a5a97 100644 --- a/recipes/createCredentials-to-createSecureContext/tests/input/file-5.mjs +++ b/recipes/createCredentials-to-createSecureContext/tests/input/file-5.mjs @@ -3,4 +3,4 @@ import * as crypto from 'node:crypto'; const credentials = crypto.createCredentials({ key: privateKey, cert: certificate -}); +}); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/input/file-6-pair.mjs b/recipes/createCredentials-to-createSecureContext/tests/input/file-6-pair.mjs new file mode 100644 index 00000000..68e03ca7 --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/input/file-6-pair.mjs @@ -0,0 +1,6 @@ +const { createCredentials: customCreateCredentials } = await import('node:crypto'); + +const credentials = customCreateCredentials({ + key: privateKey, + cert: certificate +}); \ No newline at end of file diff --git a/recipes/createCredentials-to-createSecureContext/tests/input/file-6.mjs b/recipes/createCredentials-to-createSecureContext/tests/input/file-6.mjs new file mode 100644 index 00000000..209a4c5e --- /dev/null +++ b/recipes/createCredentials-to-createSecureContext/tests/input/file-6.mjs @@ -0,0 +1,6 @@ +const { createCredentials } = await import('node:crypto'); + +const credentials = createCredentials({ + key: privateKey, + cert: certificate +}); \ No newline at end of file From d057380923d304374ccecb1376cbfd3a390df4ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= Date: Thu, 14 Aug 2025 22:40:08 +0530 Subject: [PATCH 28/33] remove --- .../src/workflow.ts | 35 ------------------- 1 file changed, 35 deletions(-) diff --git a/recipes/createCredentials-to-createSecureContext/src/workflow.ts b/recipes/createCredentials-to-createSecureContext/src/workflow.ts index df1ed582..e59da95a 100644 --- a/recipes/createCredentials-to-createSecureContext/src/workflow.ts +++ b/recipes/createCredentials-to-createSecureContext/src/workflow.ts @@ -336,41 +336,6 @@ function handleDynamicImport( const idNode = statement.child(0); const declaration = statement.parent(); - // Detects: const x = (await import(...)).then(...) - if (valueNode?.kind() === 'call_expression' && idNode?.kind() === 'identifier') { - const functionNode = valueNode.field('function'); - const isThenCall = functionNode?.kind() === 'member_expression' && functionNode.field('property')?.text() === 'then'; - const awaitNode = functionNode?.field('object')?.find({ rule: { kind: 'await_expression' } }); - - const importModuleStringNode = awaitNode?.find({ - rule: { - kind: 'string', - has: { kind: 'string_fragment', regex: `^${oldImportModule}$` } - } - }); - - if (isThenCall && importModuleStringNode) { - const allEdits: Edit[] = []; - - const usageEdits = findAndReplaceUsages(rootNode, idNode.text(), newImportFunction); - - allEdits.push(...usageEdits); - allEdits.push(idNode.replace(newImportFunction)); - allEdits.push(importModuleStringNode.replace(`'${newImportModule}'`)); - - const thenCallback = valueNode.field('arguments')?.child(0); - const callbackBody = thenCallback?.field('body'); - if (callbackBody?.kind() === 'member_expression') { - const propertyNode = callbackBody.field('property'); - if (propertyNode?.text() === oldFunctionName) { - allEdits.push(propertyNode.replace(newImportFunction)); - } - } - - return allEdits; - } - } - if (valueNode?.kind() === 'await_expression') { if (!declaration) { return []; From f721b0005a63308e853000f04aa70d79a9193385 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= Date: Fri, 15 Aug 2025 00:02:01 +0530 Subject: [PATCH 29/33] resize --- .../src/workflow.ts | 756 ++++++++---------- 1 file changed, 331 insertions(+), 425 deletions(-) diff --git a/recipes/createCredentials-to-createSecureContext/src/workflow.ts b/recipes/createCredentials-to-createSecureContext/src/workflow.ts index e59da95a..85e41766 100644 --- a/recipes/createCredentials-to-createSecureContext/src/workflow.ts +++ b/recipes/createCredentials-to-createSecureContext/src/workflow.ts @@ -10,457 +10,363 @@ const oldImportModule = 'node:crypto' const newNamespace = 'tls'; function handleNamespaceImport( - rootNode: SgRoot, - localNamespace: string, - declaration: SgNode>, - importType: 'require' | 'static' | 'dynamic-await' + rootNode: SgRoot, + localNamespace: string, + declaration: SgNode>, + importType: 'require' | 'static' | 'dynamic-await' ): Edit[] { - const allEdits: Edit[] = []; - - const usages = rootNode.root().findAll({ - rule: { - kind: 'call_expression', - has: { - field: 'function', - kind: 'member_expression', - all: [ - { has: { field: 'object', regex: `^${localNamespace}$` } }, - { has: { field: 'property', regex: `^${oldFunctionName}$` } } - ] - } - } - }); - - if (usages.length === 0) { - return []; - } - - for (const usage of usages) { - const func = usage.field('function'); - if (func) { - allEdits.push(func.replace(`${newNamespace}.${newImportFunction}`)); - } - } - - let newImportStatement = ''; - switch (importType) { - case 'require': - newImportStatement = `const ${newNamespace} = require('${newImportModule}');`; - break; - case 'static': - newImportStatement = `import * as ${newNamespace} from '${newImportModule}';`; - break; - case 'dynamic-await': - newImportStatement = `const ${newNamespace} = await import('${newImportModule}');`; - break; - } - - allEdits.push(declaration.replace(newImportStatement)); - - return allEdits; + const newNamespace = 'tls'; + + const usages = rootNode.root().findAll({ + rule: { + kind: 'call_expression', + has: { + field: 'function', + kind: 'member_expression', + all: [ + { has: { field: 'object', regex: `^${localNamespace}$` } }, + { has: { field: 'property', regex: `^${oldFunctionName}$` } } + ] + } + } + }); + + if (usages.length === 0) { + return []; + } + + const usageEdits = usages + .map(usage => usage.field('function')) + .filter((func): func is SgNode> => Boolean(func)) + .map(func => func.replace(`${newNamespace}.${newImportFunction}`)); + + switch (importType) { + case 'require': + return [...usageEdits, declaration.replace(`const ${newNamespace} = require('${newImportModule}');`)]; + case 'static': + return [...usageEdits, declaration.replace(`import * as ${newNamespace} from '${newImportModule}';`)]; + case 'dynamic-await': + return [...usageEdits, declaration.replace(`const ${newNamespace} = await import('${newImportModule}');`)]; + } } function handleDestructuredImport( - rootNode: SgRoot, - idNode: SgNode>, - declaration: SgNode>, - importType: 'require' | 'static' | 'dynamic-await' + rootNode: SgRoot, + idNode: SgNode>, + declaration: SgNode>, + importType: 'require' | 'static' | 'dynamic-await' ): Edit[] { - let localFunctionName: string | null = null; - let targetSpecifierNode: SgNode | null = null; - let isAliased = false; - - const relevantSpecifiers = idNode.children().filter( - child => ['pair_pattern', 'shorthand_property_identifier_pattern', 'import_specifier'].includes(child.kind() as string) - ); - - for (const spec of relevantSpecifiers) { - let keyNode: SgNode> | null = null; + let localFunctionName: string | null = null; + let targetSpecifierNode: SgNode | null = null; + let isAliased = false; + + const relevantSpecifiers = idNode.children().filter( + child => child.kind() === 'pair_pattern' + || child.kind() === 'shorthand_property_identifier_pattern' + || child.kind() === 'import_specifier' + ); + + for (const spec of relevantSpecifiers) { + let keyNode: SgNode> | null = null; let aliasNode: SgNode> | null = null; - if (spec.kind() === 'import_specifier') { - keyNode = spec.field('name'); - aliasNode = spec.field('alias'); - } else if (spec.kind() === 'pair_pattern') { - keyNode = spec.field('key'); - aliasNode = spec.field('value'); - } else { - keyNode = spec; - } - - if (keyNode?.text() === oldFunctionName) { - targetSpecifierNode = spec; - isAliased = Boolean(aliasNode); - localFunctionName = isAliased ? aliasNode!.text() : keyNode!.text(); - break; - } - } - - if (localFunctionName && targetSpecifierNode) { - const allEdits: Edit[] = []; - - if (!isAliased) { - const usageEdits = findAndReplaceUsages(rootNode, localFunctionName, newImportFunction); - allEdits.push(...usageEdits); - } - - const aliasSeparator = importType === 'static' ? ' as' : ':'; - const newImportSpecifier = isAliased - ? `{ ${newImportFunction}${aliasSeparator} ${localFunctionName} }` - : `{ ${newImportFunction} }`; - - let newImportStatement = ''; - switch (importType) { - case 'require': - newImportStatement = `const ${newImportSpecifier} = require('${newImportModule}');`; - break; - case 'static': - newImportStatement = `import ${newImportSpecifier} from '${newImportModule}';`; - break; - case 'dynamic-await': - newImportStatement = `const ${newImportSpecifier} = await import('${newImportModule}');`; - break; - } - - const otherSpecifiers = relevantSpecifiers.filter(s => s !== targetSpecifierNode); - if (otherSpecifiers.length > 0) { - const otherSpecifiersText = otherSpecifiers.map(s => s.text()).join(', '); - let modifiedOldImport = ''; - switch (importType) { - case 'require': - modifiedOldImport = `const { ${otherSpecifiersText} } = require('${oldImportModule}');`; - break; - case 'static': - modifiedOldImport = `import { ${otherSpecifiersText} } from '${oldImportModule}';`; - break; - case 'dynamic-await': - modifiedOldImport = `const { ${otherSpecifiersText} } = await import('${oldImportModule}');`; - break; - } - const replacementText = `${modifiedOldImport}${EOL}${newImportStatement}`; - allEdits.push(declaration.replace(replacementText)); - } else { - allEdits.push(declaration.replace(newImportStatement)); - } - - return allEdits; - } - - return []; + if (spec.kind() === 'import_specifier') { + keyNode = spec.field('name'); + aliasNode = spec.field('alias'); + } else if (spec.kind() === 'pair_pattern') { + keyNode = spec.field('key'); + aliasNode = spec.field('value'); + } else { + keyNode = spec; + } + + if (keyNode?.text() === oldFunctionName) { + targetSpecifierNode = spec; + isAliased = Boolean(aliasNode); + localFunctionName = isAliased ? aliasNode!.text() : keyNode!.text(); + break; + } + } + + if (localFunctionName && targetSpecifierNode) { + const allEdits: Edit[] = []; + + if (!isAliased) { + allEdits.push(...findAndReplaceUsages(rootNode, localFunctionName, newImportFunction)); + } + + const aliasSeparator = importType === 'static' ? ' as' : ':'; + const newImportSpecifier = isAliased + ? `{ ${newImportFunction}${aliasSeparator} ${localFunctionName} }` + : `{ ${newImportFunction} }`; + + let newImportStatement = ''; + switch (importType) { + case 'require': + newImportStatement = `const ${newImportSpecifier} = require('${newImportModule}');`; + break; + case 'static': + newImportStatement = `import ${newImportSpecifier} from '${newImportModule}';`; + break; + case 'dynamic-await': + newImportStatement = `const ${newImportSpecifier} = await import('${newImportModule}');`; + break; + } + + const otherSpecifiers = relevantSpecifiers.filter(s => s !== targetSpecifierNode); + if (otherSpecifiers.length > 0) { + let modifiedOldImport = ''; + const otherSpecifiersText = otherSpecifiers.map(s => s.text()).join(', '); + switch (importType) { + case 'require': + modifiedOldImport = `const { ${otherSpecifiersText} } = require('${oldImportModule}');`; + break; + case 'static': + modifiedOldImport = `import { ${otherSpecifiersText} } from '${oldImportModule}';`; + break; + case 'dynamic-await': + modifiedOldImport = `const { ${otherSpecifiersText} } = await import('${oldImportModule}');`; + break; + } + allEdits.push(declaration.replace(`${modifiedOldImport}${EOL}${newImportStatement}`)); + } else { + allEdits.push(declaration.replace(newImportStatement)); + } + + return allEdits; + } + + return []; } - function findAndReplaceUsages( - rootNode: SgRoot, - localFunctionName: string, - newFunctionName: string, - object: string | null = null + rootNode: SgRoot, + localFunctionName: string, + newFunctionName: string, + object: string | null = null ): Edit[] { - const edits: Edit[] = []; - - if (object) { - const usages = rootNode.root().findAll({ - rule: { - kind: 'call_expression', - has: { - field: 'function', - kind: 'member_expression', - all: [ - { has: { field: 'object', regex: `^${object}$` } }, - { has: { field: 'property', regex: `^${localFunctionName}$` } } - ] - } - } - }); - - for (const usage of usages) { - const memberExpressionNode = usage.field('function'); - const propertyNode = memberExpressionNode?.field('property'); - if (propertyNode) { - edits.push(propertyNode.replace(newFunctionName)); - } - } - } else { - const usages = rootNode.root().findAll({ - rule: { - kind: 'call_expression', - has: { field: 'function', kind: 'identifier', regex: `^${localFunctionName}$` }, - }, - }); - - for (const usage of usages) { - const functionNode = usage.field('function'); - if (functionNode) { - edits.push(functionNode.replace(newFunctionName)); - } - } - } - return edits; + const functionRule = object + ? { + field: 'function', + kind: 'member_expression', + all: [ + { has: { field: 'object', regex: `^${object}$` } }, + { has: { field: 'property', regex: `^${localFunctionName}$` } } + ] + } + : { + field: 'function', + kind: 'identifier', + regex: `^${localFunctionName}$` + }; + + const usages = rootNode.root().findAll({ + rule: { + kind: 'call_expression', + has: functionRule + } + }); + + return usages + .map(usage => { + const functionNode = usage.field('function'); + if (!functionNode) return null; + + const nodeToReplace = object ? functionNode.field('property') : functionNode; + return nodeToReplace ? nodeToReplace.replace(newFunctionName) : null; + }) + .filter((edit): edit is Edit => Boolean(edit)); } function handleRequire( - statement: SgNode>, - rootNode: SgRoot, + statement: SgNode>, + rootNode: SgRoot, ): Edit[] { - const idNode = statement.child(0); - const declaration = statement.parent(); - - if (!idNode || !declaration) { - return []; - } + const idNode = statement.child(0); + const declaration = statement.parent(); + if (!idNode || !declaration) return []; - // Handle Namespace Imports: const crypto = require('...') - if (idNode.kind() === 'identifier') { - const localNamespace = idNode.text(); - return handleNamespaceImport(rootNode, localNamespace, declaration, 'require'); - } + if (idNode.kind() === 'identifier') + return handleNamespaceImport(rootNode, idNode.text(), declaration, 'require'); - // Handle Destructured Imports: const { ... } = require('...') - if (idNode.kind() === 'object_pattern') { - return handleDestructuredImport(rootNode, idNode, declaration, 'require'); - } + if (idNode.kind() === 'object_pattern') + return handleDestructuredImport(rootNode, idNode, declaration, 'require'); - return []; + return []; } function handleStaticImport( - statement: SgNode>, - rootNode: SgRoot, + statement: SgNode>, + rootNode: SgRoot ): Edit[] { - - const importClause = statement.child(1); - if (importClause?.kind() !== 'import_clause') { - return []; - } - // Detects: import * as crypto from '...' - const clauseContent = importClause.child(0); - if (!clauseContent) { - return []; - } - - // Handle Namespace Imports: import * as crypto from '...' - if (clauseContent.kind() === 'namespace_import') { - const localNamespace = clauseContent.find({ rule: { kind: 'identifier' } })?.text(); - const allEdits: Edit[] = []; - - const usages = rootNode.root().findAll({ - rule: { - kind: 'call_expression', - has: { - field: 'function', - kind: 'member_expression', - all: [ - { has: { field: 'object', regex: `^${localNamespace}$` } }, - { has: { field: 'property', regex: `^${oldFunctionName}$` } } - ] - } - } - }); - - if (usages.length > 0) { - - for (const usage of usages) { - const func = usage.field('function'); - if (func) { - allEdits.push(func.replace(`${newNamespace}.${newImportFunction}`)); - } - } - - const newImportStatement = `import * as ${newNamespace} from '${newImportModule}';`; - allEdits.push(statement.replace(newImportStatement)); - - return allEdits; - } - } - - // Handle Named Imports: import { ... } from '...' - if (clauseContent.kind() === 'named_imports') { - const namedImportsNode = clauseContent; - let localFunctionName: string | null = null; - let targetSpecifierNode: SgNode | null = null; - let isAliased = false; - - const relevantSpecifiers = namedImportsNode.children().filter( - child => child.kind() === 'import_specifier' - ); - - for (const spec of relevantSpecifiers) { - const nameNode = spec.field('name'); - const aliasNode = spec.field('alias'); - - - if (nameNode?.text() === oldFunctionName) { - targetSpecifierNode = spec; - isAliased = Boolean(aliasNode); - localFunctionName = isAliased ? aliasNode!.text() : nameNode!.text(); - break; - } - } - - if (localFunctionName && targetSpecifierNode) { - const allEdits: Edit[] = []; - const declaration = statement; - - if (!isAliased) { - const usageEdits = findAndReplaceUsages(rootNode, localFunctionName, newImportFunction); - allEdits.push(...usageEdits); - } else { - } - - const newImportSpecifier = isAliased - ? `{ ${newImportFunction} as ${localFunctionName} }` - : `{ ${newImportFunction} }`; - const newImportStatement = `import ${newImportSpecifier} from '${newImportModule}';`; - - const otherSpecifiers = relevantSpecifiers.filter(s => s !== targetSpecifierNode); - if (otherSpecifiers.length > 0) { - const modifiedOldImport = `import { ${otherSpecifiers.map(s => s.text()).join(', ')} } from '${oldImportModule}';`; - const replacementText = `${modifiedOldImport}${EOL}${newImportStatement}`; - allEdits.push(declaration.replace(replacementText)); - } else { - allEdits.push(declaration.replace(newImportStatement)); - } - - return allEdits; - } - } - - return []; + const importClause = statement.child(1); + if (importClause?.kind() !== 'import_clause') return []; + + const content = importClause.child(0); + if (!content) return []; + + // Namespace imports: import * as ns from '...' + if (content.kind() === 'namespace_import') { + const ns = content.find({ rule: { kind: 'identifier' } })?.text(); + if (!ns) return []; + + const usages = rootNode.root().findAll({ + rule: { + kind: 'call_expression', + has: { + field: 'function', + kind: 'member_expression', + all: [ + { has: { field: 'object', regex: `^${ns}$` } }, + { has: { field: 'property', regex: `^${oldFunctionName}$` } } + ] + } + } + }); + if (!usages.length) return []; + + const edits = usages + .map(u => u.field('function')?.replace(`${newNamespace}.${newImportFunction}`)) + .filter(Boolean); + edits.push(statement.replace(`import * as ${newNamespace} from '${newImportModule}';`)); + return edits as Edit[]; + } + + // Named imports: import { x } from '...' + if (content.kind() === 'named_imports') { + const specs = content.children().filter(c => c.kind() === 'import_specifier'); + const target = specs.find(s => s.field('name')?.text() === oldFunctionName); + if (!target) return []; + + const aliasNode = target.field('alias'); + const localName = aliasNode?.text() || target.field('name')?.text() || ""; + const edits = aliasNode ? [] : findAndReplaceUsages(rootNode, localName, newImportFunction); + + const newSpec = aliasNode ? `{ ${newImportFunction} as ${localName} }` : `{ ${newImportFunction} }`; + const newStmt = `import ${newSpec} from '${newImportModule}';`; + const others = specs.filter(s => s !== target); + + return [ + ...edits, + others.length + ? statement.replace( + `import { ${others.map(s => s.text()).join(', ')} } from '${oldImportModule}';${EOL}${newStmt}` + ) + : statement.replace(newStmt) + ]; + } + + return []; } + function handleDynamicImport( - statement: SgNode>, - rootNode: SgRoot, + statement: SgNode>, + rootNode: SgRoot, ): Edit[] { - - const valueNode = statement.field('value'); - const idNode = statement.child(0); - const declaration = statement.parent(); - - if (valueNode?.kind() === 'await_expression') { - if (!declaration) { - return []; - } - - // Detects: const crypto = await import(...) - if (idNode?.kind() === 'identifier') { - const localNamespace = idNode.text(); - const allEdits: Edit[] = []; - - const usages = rootNode.root().findAll({ - rule: { - kind: 'call_expression', - has: { - field: 'function', - kind: 'member_expression', - all: [ - { has: { field: 'object', regex: `^${localNamespace}$` } }, - { has: { field: 'property', regex: `^${oldFunctionName}$` } } - ] - } - } - }); - - if (usages.length > 0) { - for (const usage of usages) { - const func = usage.field('function'); - if (func) { - allEdits.push(func.replace(`${newNamespace}.${newImportFunction}`)); - } - } - - const newImportStatement = `const ${newNamespace} = await import('${newImportModule}');`; - allEdits.push(declaration.replace(newImportStatement)); - - return allEdits; - } - } - - // Detects: const { ... } = await import(...) - if (idNode?.kind() === 'object_pattern') { - let localFunctionName: string | null = null; - let targetSpecifierNode: SgNode | null = null; - let isAliased = false; - - const relevantSpecifiers = idNode.children().filter( - child => child.kind() === 'pair_pattern' || child.kind() === 'shorthand_property_identifier_pattern' - ); - - for (const spec of relevantSpecifiers) { - const key = spec.kind() === 'pair_pattern' ? spec.field('key') : spec; - if (key?.text() === oldFunctionName) { - targetSpecifierNode = spec; - isAliased = spec.kind() === 'pair_pattern'; - localFunctionName = isAliased ? spec.field('value')!.text() : key.text(); - break; - } - } - - if (localFunctionName && targetSpecifierNode) { - const allEdits: Edit[] = []; - - if (!isAliased) { - const usageEdits = findAndReplaceUsages(rootNode, localFunctionName, newImportFunction); - allEdits.push(...usageEdits); - } else { - } - - const newImportSpecifier = isAliased - ? `{ ${newImportFunction}: ${localFunctionName} }` - : `{ ${newImportFunction} }`; - const newImportStatement = `const ${newImportSpecifier} = await import('${newImportModule}');`; - - const otherSpecifiers = relevantSpecifiers.filter(s => s !== targetSpecifierNode); - if (otherSpecifiers.length > 0) { - const modifiedOldImport = `const { ${otherSpecifiers.map(s => s.text()).join(', ')} } = await import('${oldImportModule}');`; - const replacementText = `${modifiedOldImport}${EOL}${newImportStatement}`; - allEdits.push(declaration.replace(replacementText)); - } else { - allEdits.push(declaration.replace(newImportStatement)); - } - - return allEdits; - } - } - } - - return []; + const valueNode = statement.field('value'); + const idNode = statement.child(0); + const declaration = statement.parent(); + + // must be `const ... = await import(...)` and have a parent declaration + if (valueNode?.kind() !== 'await_expression' || !declaration) return []; + + // Case 1: `const ns = await import(...)` + if (idNode?.kind() === 'identifier') { + const localNamespace = idNode.text(); + if (!localNamespace) return []; + + const usages = rootNode.root().findAll({ + rule: { + kind: 'call_expression', + has: { + field: 'function', + kind: 'member_expression', + all: [ + { has: { field: 'object', regex: `^${localNamespace}$` } }, + { has: { field: 'property', regex: `^${oldFunctionName}$` } } + ] + } + } + }); + + if (!usages.length) return []; + + const edits = usages + .map(u => u.field('function')?.replace(`${newNamespace}.${newImportFunction}`)) + .filter(Boolean) as Edit[]; + + edits.push(declaration.replace(`const ${newNamespace} = await import('${newImportModule}');`)); + return edits; + } + + // Case 2: `const { ... } = await import(...)` + if (idNode?.kind() === 'object_pattern') { + const specifiers = idNode.children().filter( + c => c.kind() === 'pair_pattern' || c.kind() === 'shorthand_property_identifier_pattern' + ); + + let targetSpecifier: SgNode | null = null; + let localFunctionName: string | null | undefined = null; + let isAliased = false; + + for (const spec of specifiers) { + const keyNode = spec.kind() === 'pair_pattern' ? spec.field('key') : spec; + if (keyNode?.text() === oldFunctionName) { + targetSpecifier = spec; + isAliased = spec.kind() === 'pair_pattern'; + localFunctionName = isAliased ? spec.field('value')?.text() : keyNode.text(); + break; + } + } + + if (!localFunctionName || !targetSpecifier) return []; + + const edits: Edit[] = []; + if (!isAliased) edits.push(...findAndReplaceUsages(rootNode, localFunctionName, newImportFunction)); + + const newImportSpecifier = isAliased + ? `{ ${newImportFunction}: ${localFunctionName} }` + : `{ ${newImportFunction} }`; + + const newImportStmt = `const ${newImportSpecifier} = await import('${newImportModule}');`; + + const otherSpecifiers = specifiers.filter(s => s !== targetSpecifier); + if (otherSpecifiers.length) { + const remaining = `const { ${otherSpecifiers.map(s => s.text()).join(', ')} } = await import('${oldImportModule}');`; + edits.push(declaration.replace(`${remaining}${EOL}${newImportStmt}`)); + } else { + edits.push(declaration.replace(newImportStmt)); + } + + return edits; + } + + return []; } export default function transform(root: SgRoot): string | null { - const rootNode = root.root(); - const allEdits: Edit[] = []; - let wasTransformed = false; - - // @ts-ignore - const requireImports = getNodeRequireCalls(root, 'crypto'); - // @ts-ignore - const staticImports = getNodeImportStatements(root, 'crypto'); - // @ts-ignore - const dynamicImports = getNodeImportCalls(root, 'crypto'); - - for (const requireCall of requireImports) { - const edits = handleRequire(requireCall, root); - if (edits.length > 0) { - wasTransformed = true; - allEdits.push(...edits); - } - } - - for (const staticImport of staticImports) { - const edits = handleStaticImport(staticImport, root); - if (edits.length > 0) { - wasTransformed = true; - allEdits.push(...edits); - } - } - - for (const dynamicImport of dynamicImports) { - const edits = handleDynamicImport(dynamicImport, root); - if (edits.length > 0) { - wasTransformed = true; - allEdits.push(...edits); - } - } - - return wasTransformed ? rootNode.commitEdits(allEdits) : null; + const rootNode = root.root(); + const allEdits: Edit[] = []; + let wasTransformed = false; + + const sources: [SgNode[] | undefined, (n: SgNode, r: SgRoot) => Edit[]][] = [ + // @ts-ignore + [getNodeRequireCalls(root, 'crypto'), handleRequire], + // @ts-ignore + [getNodeImportStatements(root, 'crypto'), handleStaticImport], + // @ts-ignore + [getNodeImportCalls(root, 'crypto'), handleDynamicImport], + ]; + + for (const [nodes, handler] of sources) { + for (const node of nodes || []) { + const edits = handler(node, root); + if (edits.length) { + wasTransformed = true; + allEdits.push(...edits); + } + } + } + + return wasTransformed ? rootNode.commitEdits(allEdits) : null; } From adc08e3239ebc462bdfda386a56b723db45934be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= Date: Fri, 15 Aug 2025 08:05:00 +0530 Subject: [PATCH 30/33] remove line not needed --- .../src/workflow.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/recipes/createCredentials-to-createSecureContext/src/workflow.ts b/recipes/createCredentials-to-createSecureContext/src/workflow.ts index 85e41766..1f9ff16d 100644 --- a/recipes/createCredentials-to-createSecureContext/src/workflow.ts +++ b/recipes/createCredentials-to-createSecureContext/src/workflow.ts @@ -15,8 +15,6 @@ function handleNamespaceImport( declaration: SgNode>, importType: 'require' | 'static' | 'dynamic-await' ): Edit[] { - const newNamespace = 'tls'; - const usages = rootNode.root().findAll({ rule: { kind: 'call_expression', @@ -61,8 +59,8 @@ function handleDestructuredImport( let isAliased = false; const relevantSpecifiers = idNode.children().filter( - child => child.kind() === 'pair_pattern' - || child.kind() === 'shorthand_property_identifier_pattern' + child => child.kind() === 'pair_pattern' + || child.kind() === 'shorthand_property_identifier_pattern' || child.kind() === 'import_specifier' ); From 7dbb6b25f57fb0e754dee9a3b335df2cb40ee1fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= <109351887+0hmX@users.noreply.github.com> Date: Fri, 15 Aug 2025 12:16:57 +0530 Subject: [PATCH 31/33] Update recipes/createCredentials-to-createSecureContext/src/workflow.ts Co-authored-by: Augustin Mauroy <97875033+AugustinMauroy@users.noreply.github.com> --- .../createCredentials-to-createSecureContext/src/workflow.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/recipes/createCredentials-to-createSecureContext/src/workflow.ts b/recipes/createCredentials-to-createSecureContext/src/workflow.ts index 1f9ff16d..5e00dd4b 100644 --- a/recipes/createCredentials-to-createSecureContext/src/workflow.ts +++ b/recipes/createCredentials-to-createSecureContext/src/workflow.ts @@ -29,9 +29,7 @@ function handleNamespaceImport( } }); - if (usages.length === 0) { - return []; - } + if (usages.length === 0) return []; const usageEdits = usages .map(usage => usage.field('function')) From c3fc053134044592b898d24895ca882d68cb809a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= Date: Fri, 15 Aug 2025 12:20:10 +0530 Subject: [PATCH 32/33] remove wasTranformed var --- .../src/workflow.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/recipes/createCredentials-to-createSecureContext/src/workflow.ts b/recipes/createCredentials-to-createSecureContext/src/workflow.ts index 5e00dd4b..be93b211 100644 --- a/recipes/createCredentials-to-createSecureContext/src/workflow.ts +++ b/recipes/createCredentials-to-createSecureContext/src/workflow.ts @@ -343,8 +343,6 @@ function handleDynamicImport( export default function transform(root: SgRoot): string | null { const rootNode = root.root(); const allEdits: Edit[] = []; - let wasTransformed = false; - const sources: [SgNode[] | undefined, (n: SgNode, r: SgRoot) => Edit[]][] = [ // @ts-ignore [getNodeRequireCalls(root, 'crypto'), handleRequire], @@ -358,11 +356,12 @@ export default function transform(root: SgRoot): string | null { for (const node of nodes || []) { const edits = handler(node, root); if (edits.length) { - wasTransformed = true; allEdits.push(...edits); } } } - return wasTransformed ? rootNode.commitEdits(allEdits) : null; + if (!allEdits.length) return null; + + return rootNode.commitEdits(allEdits); } From 44015c185329ec8fc89e031067428a4ead7921dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?0hm=E2=98=98=EF=B8=8F?= Date: Fri, 15 Aug 2025 12:28:39 +0530 Subject: [PATCH 33/33] update package-lock.json --- package-lock.json | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 071e51d7..791afb9a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1420,8 +1420,8 @@ "resolved": "recipes/create-require-from-path", "link": true }, - "node_modules/@nodejs/crypto-create-credentials": { - "resolved": "recipes/crypto-create-credentials", + "node_modules/@nodejs/createCredentials-to-createSecureContext": { + "resolved": "recipes/createCredentials-to-createSecureContext", "link": true }, "node_modules/@nodejs/import-assertions-to-attributes": { @@ -4069,9 +4069,21 @@ "@types/node": "^24.0.3" } }, + "recipes/createCredentials-to-createSecureContext": { + "name": "@nodejs/createCredentials-to-createSecureContext", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@nodejs/codemod-utils": "*" + }, + "devDependencies": { + "@codemod.com/jssg-types": "^1.0.3" + } + }, "recipes/crypto-create-credentials": { "name": "@nodejs/crypto-create-credentials", "version": "1.0.0", + "extraneous": true, "license": "MIT", "dependencies": { "@nodejs/codemod-utils": "*"