diff --git a/package-lock.json b/package-lock.json index 4774932b..c8967845 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,8 +18,6 @@ }, "node_modules/@ast-grep/lang-bash": { "version": "0.0.8", - "resolved": "https://registry.npmjs.org/@ast-grep/lang-bash/-/lang-bash-0.0.8.tgz", - "integrity": "sha512-qFykSsOdBv35I1rtt2PstJ4luzV9J1YCiFZBgySI8UPOh+TFnmpdlq9CrryNOj0Q7f6zZhc1OIau+/mUSs6HAg==", "dev": true, "hasInstallScript": true, "license": "ISC", @@ -37,8 +35,6 @@ }, "node_modules/@ast-grep/lang-json": { "version": "0.0.7", - "resolved": "https://registry.npmjs.org/@ast-grep/lang-json/-/lang-json-0.0.7.tgz", - "integrity": "sha512-8wiH+B4Rih8/vHsUB4DOTXnQHMxfWhaBygUEZ036kfr1T9WFvx5E7QYd2WX2Wm1nFCzUmWbIfsoE+cR2G9SJlA==", "dev": true, "hasInstallScript": true, "license": "ISC", @@ -117,9 +113,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -137,9 +130,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -157,9 +147,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -177,9 +164,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -242,8 +226,6 @@ }, "node_modules/@ast-grep/setup-lang": { "version": "0.0.6", - "resolved": "https://registry.npmjs.org/@ast-grep/setup-lang/-/setup-lang-0.0.6.tgz", - "integrity": "sha512-aSYZNF5nGQMxnwiA3Lcc8zi0AtpGlug47ADgqCgP/2n7p922FbnW7vo1rbW4kKjW72B/3mDdN7uvYidv8JnPnw==", "dev": true, "license": "ISC" }, @@ -316,9 +298,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT OR Apache-2.0", "optional": true, "os": [ @@ -336,9 +315,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT OR Apache-2.0", "optional": true, "os": [ @@ -356,9 +332,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT OR Apache-2.0", "optional": true, "os": [ @@ -376,9 +349,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT OR Apache-2.0", "optional": true, "os": [ @@ -429,6 +399,12 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/@jssg/utils": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/@jssg/utils/-/utils-0.0.9.tgz", + "integrity": "sha512-dQDR5At7VEXfEuiwl7pIehm7MyIGJpDn4C0I015xMDHWeceJuWMMjmBcrDVfi75CLgfB90CSLi1tP1HErduznQ==", + "license": "Apache-2.0" + }, "node_modules/@nodejs/ansi-colors-to-styletext": { "resolved": "recipes/ansi-colors-to-styletext", "link": true @@ -590,9 +566,9 @@ "link": true }, "node_modules/@types/node": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.3.tgz", - "integrity": "sha512-dsqMQQoeTLqu9wynDD00q573mNzso3IdQOAfHRJqLCcmCFPoGo9A1bDpUcv/9tnKpErQWv9uKeGfl37EIS02Yg==", + "version": "26.6.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.4.tgz", + "integrity": "sha512-ldVPDCzj7fsaGZrLB0NuHuTvJcsNasysBAqMolr/cgxrLd1xbqxIr3XJiPnHHJUCxj5sNF1vnRj9aWnrVh5Jcg==", "dev": true, "license": "MIT", "dependencies": { @@ -618,6 +594,8 @@ }, "node_modules/@typescript/typescript-darwin-arm64": { "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", + "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==", "cpu": [ "arm64" ], @@ -939,8 +917,6 @@ }, "node_modules/dedent": { "version": "1.7.2", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", - "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", "license": "MIT", "peerDependencies": { "babel-plugin-macros": "^3.1.0" @@ -953,6 +929,8 @@ }, "node_modules/typescript": { "version": "7.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", + "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==", "dev": true, "license": "Apache-2.0", "bin": { @@ -1266,6 +1244,7 @@ "recipes/tls-create-secure-pair-to-tls-socket": { "name": "@nodejs/tls-create-secure-pair-to-tls-socket", "dependencies": { + "@jssg/utils": "^0.0.9", "@nodejs/codemod-utils": "*" }, "devDependencies": { diff --git a/recipes/tls-create-secure-pair-to-tls-socket/package.json b/recipes/tls-create-secure-pair-to-tls-socket/package.json index c5137cd0..1dfa0f8a 100644 --- a/recipes/tls-create-secure-pair-to-tls-socket/package.json +++ b/recipes/tls-create-secure-pair-to-tls-socket/package.json @@ -8,6 +8,7 @@ "@codemod.com/jssg-types": "^1.6.3" }, "dependencies": { + "@jssg/utils": "^0.0.9", "@nodejs/codemod-utils": "*" } } diff --git a/recipes/tls-create-secure-pair-to-tls-socket/src/workflow.ts b/recipes/tls-create-secure-pair-to-tls-socket/src/workflow.ts index f54f288c..af19f5ab 100644 --- a/recipes/tls-create-secure-pair-to-tls-socket/src/workflow.ts +++ b/recipes/tls-create-secure-pair-to-tls-socket/src/workflow.ts @@ -1,12 +1,118 @@ -import type { SgRoot, SgNode, Edit } from '@codemod.com/jssg-types/main'; -import type JS from '@codemod.com/jssg-types/langs/javascript'; +import type { SgNode, Edit, Codemod } from 'codemod:ast-grep'; +import type JS from '@codemod.com/jssg-types/langs/tsx'; import { resolveBindingPath } from '@nodejs/codemod-utils/ast-grep/resolve-binding-path'; import { getModuleDependencies } from '@nodejs/codemod-utils/ast-grep/module-dependencies'; import { updateBinding } from '@nodejs/codemod-utils/ast-grep/update-binding'; +import { + getAllImports, + addImport, + removeImport, +} from '@jssg/utils/javascript/imports'; + +type GetImportOptions = { + from: string[]; + name: string; +}; + +type ImportNode = ReturnType>[number]; + +function getImports( + rootNode: SgNode, + options: GetImportOptions, +): { + default: ImportNode[]; + named: ImportNode[]; +} { + let defaultImports: ImportNode[] = []; + let namedImports: ImportNode[] = []; + for (const from of options.from) { + const _defaultImports = getAllImports(rootNode, { + from: from, + type: 'default', + }); + + const _namedImports = getAllImports(rootNode, { + from: from, + type: 'named', + name: options.name, + }); + + defaultImports = defaultImports.concat(_defaultImports); + namedImports = namedImports.concat(_namedImports); + } + + return { + default: defaultImports, + named: namedImports, + }; +} -export default function transform(root: SgRoot): string | null { +const transform: Codemod = async (root) => { const rootNode = root.root(); const tlsStmts = getModuleDependencies(root, 'tls'); + const imp = getImports(rootNode, { + name: 'createSecurePair', + from: ['tls', 'node:tls'], + }); + + const calls = []; + const edits: Edit[] = []; + + for (const importNode of imp.named) { + const removeEdit = removeImport(rootNode, { + type: 'named', + from: 'node:tls', + specifiers: [importNode.node.text()], + }); + if (removeEdit) edits.push(removeEdit); + + const addEdit = addImport(rootNode, { + specifiers: [{ name: 'TLSSocket' }], + from: 'node:tls', + type: 'named', + moduleType: importNode.moduleType, + }); + + if (addEdit) edits.push(addEdit); + + const fileReferences = importNode.node.references()[0]; + for (const ref of fileReferences.nodes) { + const node = ref.find({ + rule: { + inside: { + kind: 'call_expression', + stopBy: 'end', + }, + }, + }); + + if (node) calls.push(node); + } + } + + for (const importNode of imp.default) { + const fileReferences = importNode.node.references()[0]; + for (const ref of fileReferences.nodes) { + const node = ref.find({ + rule: { + inside: { + kind: 'call_expression', + stopBy: 'end', + has: { + field: 'function', + kind: 'member_expression', + has: { + field: 'property', + regex: '^createSecurePair$', + }, + }, + }, + }, + }); + + if (node) calls.push(node); + } + } if (!tlsStmts.length) return null; @@ -19,10 +125,8 @@ export default function transform(root: SgRoot): string | null { if (!cspBindings.length) return null; - const edits: Edit[] = []; - // Transform all createSecurePair calls - const calls = rootNode.findAll({ rule: { kind: 'call_expression' } }); + // const calls = rootNode.findAll({ rule: { kind: 'call_expression' } }); for (const call of calls) { const callee = call.field('function'); @@ -56,27 +160,10 @@ export default function transform(root: SgRoot): string | null { // Rename variables named 'pair' to 'socket' edits.push(...renamePairVariables(rootNode, cspBindings)); - // Update imports - const importStmts = tlsStmts.filter( - (s) => s.is('import_statement') || s.is('variable_declarator'), - ); - - for (const importStmt of importStmts) { - const result = updateBinding(importStmt, { - old: 'createSecurePair', - new: 'TLSSocket', - removeAlias: true, - }); - - if (result?.edit) { - edits.push(result.edit); - } - } - if (!edits.length) return null; return rootNode.commitEdits(edits); -} +}; function getCallBinding(callee: SgNode): string | null { if (callee.is('member_expression')) { @@ -136,3 +223,5 @@ function renamePairVariables(rootNode: SgNode, bindings: string[]): Edit[] { return edits; } + +export default transform; diff --git a/recipes/tls-create-secure-pair-to-tls-socket/tests/usage-example-1/expected.js b/recipes/tls-create-secure-pair-to-tls-socket/tests/usage-example-1/expected.js new file mode 100644 index 00000000..b2c3cf14 --- /dev/null +++ b/recipes/tls-create-secure-pair-to-tls-socket/tests/usage-example-1/expected.js @@ -0,0 +1,28 @@ +const net = require('net') +const tls = require('tls') +const fs = require('fs') +const path = require('path') + +const certDir = path.join(__dirname, '..') +const options = { + key: fs.readFileSync(path.join(certDir, 'key.pem')), + cert: fs.readFileSync(path.join(certDir, 'cert.pem')), + isServer: true +} + +const server = net.createServer((socket) => { + const tlsSocket = new tls.TLSSocket(socket, options) + + tlsSocket.on('secure', () => { + tlsSocket.write('Hello from basic TLSSocket server!\n') + tlsSocket.end() + }) +}) + +const PORT = process.env.PORT || 8444 + +server.listen(PORT, () => { + console.log('[basic] Server listening on port', PORT) +}) + +module.exports = server diff --git a/recipes/tls-create-secure-pair-to-tls-socket/tests/usage-example-1/input.js b/recipes/tls-create-secure-pair-to-tls-socket/tests/usage-example-1/input.js new file mode 100644 index 00000000..6c8991b4 --- /dev/null +++ b/recipes/tls-create-secure-pair-to-tls-socket/tests/usage-example-1/input.js @@ -0,0 +1,36 @@ +const net = require('net') +const tls = require('tls') +const fs = require('fs') +const path = require('path') + +const certDir = path.join(__dirname, '..') +const options = { + key: fs.readFileSync(path.join(certDir, 'key.pem')), + cert: fs.readFileSync(path.join(certDir, 'cert.pem')) +} + +const server = net.createServer((socket) => { + // Simplest SecurePair usage: create, pipe, use cleartext + const pair = tls.createSecurePair( + tls.createSecureContext(options), + true, + false, + false + ) + + socket.pipe(pair.encrypted) + pair.encrypted.pipe(socket) + + pair.on('secure', () => { + pair.cleartext.write('Hello from basic SecurePair server!\n') + pair.cleartext.end() + }) +}) + +const PORT = process.env.PORT || 8444 + +server.listen(PORT, () => { + console.log('[basic] Server listening on port', PORT) +}) + +module.exports = server