diff --git a/package-lock.json b/package-lock.json index e9d4073a..ea6465f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1275,12 +1275,12 @@ } }, "node_modules/@inquirer/external-editor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.2.tgz", - "integrity": "sha512-yy9cOoBnx58TlsPrIxauKIFQTiyH+0MK4e97y4sV9ERbI+zDxw7i2hxHLCIEGIE/8PPvDxGhgzIOTSOWcs6/MQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", + "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", "license": "MIT", "dependencies": { - "chardet": "^2.1.0", + "chardet": "^2.1.1", "iconv-lite": "^0.7.0" }, "engines": { @@ -1296,9 +1296,9 @@ } }, "node_modules/@inquirer/figures": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.14.tgz", - "integrity": "sha512-DbFgdt+9/OZYFM+19dbpXOSeAstPy884FPy1KjDu4anWwymZeOYhMY1mdFri172htv6mvc/uvIAAi7b7tvjJBQ==", + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", "license": "MIT", "engines": { "node": ">=18" @@ -1520,6 +1520,10 @@ "resolved": "recipes/node-url-to-whatwg-url", "link": true }, + "node_modules/@nodejs/process-assert-to-node-assert": { + "resolved": "recipes/process-assert-to-node-assert", + "link": true + }, "node_modules/@nodejs/process-main-module": { "resolved": "recipes/process-main-module", "link": true @@ -1772,7 +1776,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -1788,28 +1791,28 @@ } }, "node_modules/@typescript/native-preview": { - "version": "7.0.0-dev.20251117.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview/-/native-preview-7.0.0-dev.20251117.1.tgz", - "integrity": "sha512-JgKY4Q6jRCszCJ46c8tVrGVnmdiRPSKTW0UQvcyxdI7LG9NYMchJ/W7iUyFZVjG8BV1iUTl3DYml1xErPHLKeg==", + "version": "7.0.0-dev.20251124.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview/-/native-preview-7.0.0-dev.20251124.1.tgz", + "integrity": "sha512-mDdN4nVZJy6n4P9Z7LnLhBnQ8MibQKtZ2LnLz7ggv9TFTTAIuCquoJdWIp636i9MmVmbaYEKKeK+7w4F/7dvzw==", "dev": true, "license": "Apache-2.0", "bin": { "tsgo": "bin/tsgo.js" }, "optionalDependencies": { - "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20251117.1", - "@typescript/native-preview-darwin-x64": "7.0.0-dev.20251117.1", - "@typescript/native-preview-linux-arm": "7.0.0-dev.20251117.1", - "@typescript/native-preview-linux-arm64": "7.0.0-dev.20251117.1", - "@typescript/native-preview-linux-x64": "7.0.0-dev.20251117.1", - "@typescript/native-preview-win32-arm64": "7.0.0-dev.20251117.1", - "@typescript/native-preview-win32-x64": "7.0.0-dev.20251117.1" + "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20251124.1", + "@typescript/native-preview-darwin-x64": "7.0.0-dev.20251124.1", + "@typescript/native-preview-linux-arm": "7.0.0-dev.20251124.1", + "@typescript/native-preview-linux-arm64": "7.0.0-dev.20251124.1", + "@typescript/native-preview-linux-x64": "7.0.0-dev.20251124.1", + "@typescript/native-preview-win32-arm64": "7.0.0-dev.20251124.1", + "@typescript/native-preview-win32-x64": "7.0.0-dev.20251124.1" } }, "node_modules/@typescript/native-preview-darwin-arm64": { - "version": "7.0.0-dev.20251117.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-arm64/-/native-preview-darwin-arm64-7.0.0-dev.20251117.1.tgz", - "integrity": "sha512-O7Hhb9m8AZJCAUSBbGmZs7Vm890Kh5Z3xAAASs+L4thtPM0oRckeaoXLvHeE9Qy1p8qG//EmZ3+uSdtUTV4wqg==", + "version": "7.0.0-dev.20251124.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-arm64/-/native-preview-darwin-arm64-7.0.0-dev.20251124.1.tgz", + "integrity": "sha512-g9B61A+90UumecYvTC+dPCw3NRYUB04QV8Svht9iK0LGjOVCzzr+2FjBSl/k6Q31DADuJ9oXUPDLqzWbcpWP7g==", "cpu": [ "arm64" ], @@ -1821,9 +1824,9 @@ ] }, "node_modules/@typescript/native-preview-darwin-x64": { - "version": "7.0.0-dev.20251117.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-x64/-/native-preview-darwin-x64-7.0.0-dev.20251117.1.tgz", - "integrity": "sha512-/I/iWWvUvuy8BK0bXn5Kz6z2QwknwD2kl2estQxgsz9VgHHyLSyjAg7c18pX/re0Z9ISPz7wutEKabzdtRW8Uw==", + "version": "7.0.0-dev.20251124.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-x64/-/native-preview-darwin-x64-7.0.0-dev.20251124.1.tgz", + "integrity": "sha512-QT1hqb9b0eUVQUwMsbKH/xf3YBFX9HQlmq1dqLfHThWYkP4Npc0Aaxv0XS4cynXa5ipf56UPWOs/sikpEI1rzQ==", "cpu": [ "x64" ], @@ -1835,9 +1838,9 @@ ] }, "node_modules/@typescript/native-preview-linux-arm": { - "version": "7.0.0-dev.20251117.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm/-/native-preview-linux-arm-7.0.0-dev.20251117.1.tgz", - "integrity": "sha512-Mfnc8CytGICsYJCMbu3FwE/KDcVg4/QTFix6O31oUkj9ERp3zbSePVMQulkJTH2vuhDvJnVISHzIYawtq5QPTQ==", + "version": "7.0.0-dev.20251124.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm/-/native-preview-linux-arm-7.0.0-dev.20251124.1.tgz", + "integrity": "sha512-eKi6S+dSLJhjO3RmTl7VYD4Has0FTWcAqF4i/LAifoPEN69wlKs6Eq4JIEa/8jG5zL7RwuA/VRZ3iLeLmKTpGA==", "cpu": [ "arm" ], @@ -1849,9 +1852,9 @@ ] }, "node_modules/@typescript/native-preview-linux-arm64": { - "version": "7.0.0-dev.20251117.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm64/-/native-preview-linux-arm64-7.0.0-dev.20251117.1.tgz", - "integrity": "sha512-YSkmJb4/WrS6ZMEJSDbv5o2Garms3+3yKsH+Y3JLUab0namf1Br7T53ydW7ijV2rE7j9DgJs9P+GNu8753St3Q==", + "version": "7.0.0-dev.20251124.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm64/-/native-preview-linux-arm64-7.0.0-dev.20251124.1.tgz", + "integrity": "sha512-79F9QMy9g4GhNcLG8cMCn7rVqHYEJ/AG1qbiaY0u1Egx5Nh4CekJzanS0sO6tLdjqdzrnA7zXpL7NrqrpAxpMQ==", "cpu": [ "arm64" ], @@ -1863,9 +1866,9 @@ ] }, "node_modules/@typescript/native-preview-linux-x64": { - "version": "7.0.0-dev.20251117.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-x64/-/native-preview-linux-x64-7.0.0-dev.20251117.1.tgz", - "integrity": "sha512-R5KvnKuGsbozjHbmA+zPa4xVkQSutvtU9/PQJ7vjJL0xsvSsRUgOE2V2jlT+KnfjAhYVoIg2njtHdf0uv5k9Ow==", + "version": "7.0.0-dev.20251124.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-x64/-/native-preview-linux-x64-7.0.0-dev.20251124.1.tgz", + "integrity": "sha512-ShiiA0Uf4M6WWyQAur+G7AJWz3KDz6egpt89G4dL2vV/fDY2Hm+wSBBU79MFXHV5J0dCPHI+EQdiB4Z6IUyf1Q==", "cpu": [ "x64" ], @@ -1877,9 +1880,9 @@ ] }, "node_modules/@typescript/native-preview-win32-arm64": { - "version": "7.0.0-dev.20251117.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-arm64/-/native-preview-win32-arm64-7.0.0-dev.20251117.1.tgz", - "integrity": "sha512-xfEwDD9BwCm2gFf0AePfvXxjgQ/EDBDLRbSejtShTSFwrgdnRJ7iW63/ns/i31qLesTzGZaLxeAV8zgh6C2Ibg==", + "version": "7.0.0-dev.20251124.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-arm64/-/native-preview-win32-arm64-7.0.0-dev.20251124.1.tgz", + "integrity": "sha512-ElJw29hI4MKfIj5kanEjaKHeJomutI0Jh7+r5BU+zW4R6NtMTHmSFRAafyGkfpDHG8YI5cmJmbmcao9CRjRAhQ==", "cpu": [ "arm64" ], @@ -1891,9 +1894,9 @@ ] }, "node_modules/@typescript/native-preview-win32-x64": { - "version": "7.0.0-dev.20251117.1", - "resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-x64/-/native-preview-win32-x64-7.0.0-dev.20251117.1.tgz", - "integrity": "sha512-GhJ4GIygHSU86gZw6NkOnJKi/XW0Yw+1quanZ6BaOAZ+HY6aftuESy+NlbC6nUSGE2xmbvxqJgqchCIlC6YPoA==", + "version": "7.0.0-dev.20251124.1", + "resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-x64/-/native-preview-win32-x64-7.0.0-dev.20251124.1.tgz", + "integrity": "sha512-hY0y5VQRg9ZXcxc/NH1L/YIpegZgwuDUc72SxjLXq20ZavOuec+Z1vIQK6La5qTJylGy/XgfNk5dpQsSW2HvwQ==", "cpu": [ "x64" ], @@ -2026,9 +2029,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.24", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.24.tgz", - "integrity": "sha512-uUhTRDPXamakPyghwrUcjaGvvBqGrWvBHReoiULMIpOJVM9IYzQh83Xk2Onx5HlGI2o10NNCzcs9TG/S3TkwrQ==", + "version": "2.8.31", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.31.tgz", + "integrity": "sha512-a28v2eWrrRWPpJSzxc+mKwm0ZtVx/G8SepdQZDArnXYU/XS+IF6mp8aB/4E+hH1tyGCoDo3KlUCdlSxGDsRkAw==", "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.js" @@ -2073,9 +2076,9 @@ } }, "node_modules/browserslist": { - "version": "4.27.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz", - "integrity": "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==", + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", + "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==", "funding": [ { "type": "opencollective", @@ -2093,10 +2096,10 @@ "license": "MIT", "peer": true, "dependencies": { - "baseline-browser-mapping": "^2.8.19", - "caniuse-lite": "^1.0.30001751", - "electron-to-chromium": "^1.5.238", - "node-releases": "^2.0.26", + "baseline-browser-mapping": "^2.8.25", + "caniuse-lite": "^1.0.30001754", + "electron-to-chromium": "^1.5.249", + "node-releases": "^2.0.27", "update-browserslist-db": "^1.1.4" }, "bin": { @@ -2150,9 +2153,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001753", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001753.tgz", - "integrity": "sha512-Bj5H35MD/ebaOV4iDLqPEtiliTN29qkGtEHCwawWn4cYm+bPJM2NsaP30vtZcnERClMzp52J4+aw2UNbK4o+zw==", + "version": "1.0.30001757", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz", + "integrity": "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==", "funding": [ { "type": "opencollective", @@ -2471,9 +2474,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.244", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.244.tgz", - "integrity": "sha512-OszpBN7xZX4vWMPJwB9illkN/znA8M36GQqQxi6MNy9axWxhOfJyZZJtSLQCpEFLHP2xK33BiWx9aIuIEXVCcw==", + "version": "1.5.259", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.259.tgz", + "integrity": "sha512-I+oLXgpEJzD6Cwuwt1gYjxsDmu/S/Kd41mmLA3O+/uH2pFRO/DvOjUyGozL8j3KeLV6WyZ7ssPwELMsXCcsJAQ==", "license": "ISC" }, "node_modules/emoji-regex": { @@ -2636,9 +2639,9 @@ } }, "node_modules/flow-parser": { - "version": "0.289.0", - "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.289.0.tgz", - "integrity": "sha512-w4sVnH6ddNAIxokoz0mGyiIIdzvqncFhAYW+RmkPbPSSTYozG6yhqAixzaWeBCQf2qqXJTlHkoKPnf/BAj8Ofw==", + "version": "0.291.0", + "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.291.0.tgz", + "integrity": "sha512-MLqjFn72Dvndqrkjy280HaIs4AV9Z6nxVRmNPO3TjbYcipg4hR7QX7tEYZYsVvaaZWZPGe6Mithluk2aPGlDOw==", "license": "MIT", "engines": { "node": ">=0.4.0" @@ -2661,9 +2664,9 @@ } }, "node_modules/form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -2785,9 +2788,9 @@ } }, "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", @@ -4377,6 +4380,17 @@ "@codemod.com/jssg-types": "^1.0.3" } }, + "recipes/process-assert-to-node-assert": { + "name": "@nodejs/process-assert-to-node-assert", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@nodejs/codemod-utils": "*" + }, + "devDependencies": { + "@codemod.com/jssg-types": "^1.0.3" + } + }, "recipes/process-main-module": { "name": "@nodejs/process-main-module", "version": "1.0.1", diff --git a/recipes/process-assert-to-node-assert/README.md b/recipes/process-assert-to-node-assert/README.md new file mode 100644 index 00000000..cae74613 --- /dev/null +++ b/recipes/process-assert-to-node-assert/README.md @@ -0,0 +1,24 @@ +# `process.assert` to `node:assert` DEP0100 + +This recipe transforms the usage of `process.assert` to use `node:assert` module. + +See [DEP0100](https://nodejs.org/api/deprecations.html#DEP0100). + +## Example + +**Before:** + +```js +process.assert(condition, "Assertion failed"); +``` + +**After:** + +```js +import assert from "node:assert"; +assert(condition, "Assertion failed"); +``` + +## Additional Notes + +This codemod use [`fs` capability](https://docs.codemod.com/jssg/security) to read the `package.json` file and determine if the project is using ES modules or CommonJS. Based on this information, it adds the appropriate import statement for the `assert` module. diff --git a/recipes/process-assert-to-node-assert/codemod.yaml b/recipes/process-assert-to-node-assert/codemod.yaml new file mode 100644 index 00000000..ff21d993 --- /dev/null +++ b/recipes/process-assert-to-node-assert/codemod.yaml @@ -0,0 +1,25 @@ +schema_version: "1.0" +name: "@nodejs/process-assert-to-node-assert" +version: 1.0.0 +description: Handle DEP0100 via transforming `process.assert` to `node:assert`. +author: matheusmorett2 +license: MIT +workflow: workflow.yaml +category: migration + +targets: + languages: + - javascript + - typescript + +keywords: + - transformation + - migration + +registry: + access: public + visibility: public + +# https://docs.codemod.com/jssg/security#enabling-capabilities +capabilities: + - fs diff --git a/recipes/process-assert-to-node-assert/package.json b/recipes/process-assert-to-node-assert/package.json new file mode 100644 index 00000000..7d6999e0 --- /dev/null +++ b/recipes/process-assert-to-node-assert/package.json @@ -0,0 +1,24 @@ +{ + "name": "@nodejs/process-assert-to-node-assert", + "version": "1.0.0", + "description": "Handle DEP0100 via transforming `process.assert` to `node:assert`.", + "type": "module", + "scripts": { + "test": "npx codemod jssg test -l typescript --ignore-whitespace ./src/workflow.ts ./" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/nodejs/userland-migrations.git", + "directory": "recipes/process-assert-to-node-assert", + "bugs": "https://github.com/nodejs/userland-migrations/issues" + }, + "author": "matheusmorett2", + "license": "MIT", + "homepage": "https://github.com/nodejs/userland-migrations/blob/main/recipes/process-assert-to-node-assert/README.md", + "devDependencies": { + "@codemod.com/jssg-types": "^1.0.3" + }, + "dependencies": { + "@nodejs/codemod-utils": "*" + } +} diff --git a/recipes/process-assert-to-node-assert/src/workflow.ts b/recipes/process-assert-to-node-assert/src/workflow.ts new file mode 100644 index 00000000..568358ce --- /dev/null +++ b/recipes/process-assert-to-node-assert/src/workflow.ts @@ -0,0 +1,240 @@ +import { EOL } from 'node:os'; +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; +import { getNodeRequireCalls } from '@nodejs/codemod-utils/ast-grep/require-call'; +import { + getNodeImportCalls, + getNodeImportStatements, +} from '@nodejs/codemod-utils/ast-grep/import-statement'; +import { resolveBindingPath } from '@nodejs/codemod-utils/ast-grep/resolve-binding-path'; +import { removeBinding } from '@nodejs/codemod-utils/ast-grep/remove-binding'; +import { removeLines } from '@nodejs/codemod-utils/ast-grep/remove-lines'; +import type { + Edit, + Range, + Rule, + SgNode, + SgRoot, +} from '@codemod.com/jssg-types/main'; +import type JS from '@codemod.com/jssg-types/langs/javascript'; + +type ReplaceRule = { + importNode?: SgNode; + binding?: string; + rule: Rule; + replaceWith?: string; +}; + +/** + * Transform function that converts deprecated `process.assert` usage to the `node:assert` module. + * + * Handles: + * 1. Replaces `process.assert(...)` member expressions/calls with `assert(...)` or `assert.xxx` as appropriate. + * 2. Handles cases where `process` is imported/required under a different binding (resolves binding paths). + * 3. Removes the original `process` import/require when it's only used for `assert` and removes the import line when empty. + * 4. Adds `import assert from "node:assert";` or `const assert = require("node:assert");` at the top + * when the file does not already import/require `assert`. + * + * Steps: + * - Find all `process` import/require statements and resolve any binding for `assert`. + * - Replace call and member-expression usages that reference `process.assert` (or the resolved binding) with `assert`. + * - Remove or update the original import/require for `process` when it's no longer needed. + * - If `assert` is not already present, insert the appropriate `import` or `require` line depending on the module style. + * + * @param root - The AST root node provided by jssg for the file being transformed. + * @returns The transformed source code as a string, or `null` when no edits are required. + */ +export default function transform(root: SgRoot): string | null { + const rootNode = root.root(); + const edits: Edit[] = []; + const linesToRemove: Range[] = []; + const replaceRules: ReplaceRule[] = [ + { + rule: { + kind: 'member_expression', + pattern: 'process.assert', + }, + replaceWith: 'assert', + }, + ]; + + const processImportsToRemove = new Set>(); + + const requireCalls = getNodeRequireCalls(root, 'process'); + const importStatements = getNodeImportStatements(root, 'process'); + const importCalls = getNodeImportCalls(root, 'process'); + const allImports = [...requireCalls, ...importStatements, ...importCalls]; + const processUsages = rootNode.findAll({ + rule: { + kind: 'member_expression', + has: { + kind: 'identifier', + pattern: 'process', + }, + }, + }); + + for (const processImport of allImports) { + const binding = resolveBindingPath(processImport, '$.assert'); + + if (binding) { + // Handle member expressions like nodeAssert.strictEqual + replaceRules.push({ + importNode: processImport, + binding, + rule: { + kind: 'member_expression', + has: { + kind: binding.includes('.') ? 'member_expression' : 'identifier', + pattern: binding, + }, + }, + replaceWith: 'assert', + }); + + // Handle standalone calls like nodeAssert(...) + replaceRules.push({ + importNode: processImport, + binding, + rule: { + kind: 'call_expression', + has: { + kind: 'identifier', + field: 'function', + pattern: binding, + }, + }, + replaceWith: 'assert', + }); + } + + let hasNonAssertUsage = false; + for (const usage of processUsages) { + const propertyNode = usage.field('property'); + if (propertyNode && propertyNode.text() !== 'assert') { + hasNonAssertUsage = true; + break; + } + } + + if (!hasNonAssertUsage && processUsages.length > 0) { + processImportsToRemove.add(processImport); + linesToRemove.push(processImport.range()); + } + } + + const processedImports = new Set>(); + + for (const replaceRule of replaceRules) { + const nodes = rootNode.findAll({ + rule: replaceRule.rule, + }); + + for (const node of nodes) { + if ( + replaceRule.importNode && + !processedImports.has(replaceRule.importNode) + ) { + if (!processImportsToRemove.has(replaceRule.importNode)) { + const removeBind = removeBinding( + replaceRule.importNode, + replaceRule.binding, + ); + + if (removeBind.edit) { + edits.push(removeBind.edit); + } + + if (removeBind.lineToRemove) { + linesToRemove.push(removeBind.lineToRemove); + } + } + processedImports.add(replaceRule.importNode); + } + + if ( + replaceRule.rule.kind === 'member_expression' && + replaceRule.binding + ) { + // Replace the object part of member expressions (e.g., nodeAssert.strictEqual -> assert.strictEqual) + const objectNode = node.field('object'); + + if (objectNode) { + edits.push(objectNode.replace('assert')); + } + } else if ( + replaceRule.rule.kind === 'call_expression' && + replaceRule.binding + ) { + // Replace the function identifier in call expressions (e.g., nodeAssert(...) -> assert(...)) + const functionNode = node.field('function'); + + if (functionNode) { + edits.push(functionNode.replace('assert')); + } + } else { + const replaceText = replaceRule.replaceWith || 'assert'; + edits.push(node.replace(replaceText)); + } + } + } + + const sourceCode = removeLines(rootNode.commitEdits(edits), linesToRemove); + + if (edits.length === 0 && linesToRemove) return sourceCode; + + const alreadyRequiringAssert = getNodeRequireCalls(root, 'assert'); + const alreadyImportingAssert = getNodeImportStatements(root, 'assert'); + + if (alreadyRequiringAssert.length || alreadyImportingAssert.length) + return sourceCode; + + /** + * Re add the appropriate import or require statement for `node:assert` + * + * 1. pre-existing import statement(s) + * 2. pre-existing require() calls + * 3. authoritative file extension + * 4. pjson.type + */ + + const usingRequire = rootNode.find({ + rule: { + kind: 'call_expression', + has: { + kind: 'identifier', + field: 'function', + regex: 'require', + }, + }, + }); + const usingImport = rootNode.find({ + rule: { + kind: 'import_statement', + }, + }); + const filename = root.filename(); + + const isCjsFile = filename.endsWith('.cjs') || filename.endsWith('.cts'); + const isMjsFile = filename.endsWith('.mjs') || filename.endsWith('.mts'); + + // CommonJS `require` into an ES module source (even if the file references + // `createRequire`). + if (usingImport || isMjsFile) { + return `import assert from "node:assert";${EOL}${sourceCode}`; + } + + if (usingRequire || isCjsFile) { + return `const assert = require("node:assert");${EOL}${sourceCode}`; + } + + const packageJsonPath = join(process.cwd(), 'package.json'); + const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')); + const isEsm = packageJson.type === 'module'; + + if (isEsm) { + return `import assert from "node:assert";${EOL}${sourceCode}`; + } + + return `const assert = require("node:assert");${EOL}${sourceCode}`; +} diff --git a/recipes/process-assert-to-node-assert/tests/expected/01-basic-global-process-assert.js b/recipes/process-assert-to-node-assert/tests/expected/01-basic-global-process-assert.js new file mode 100644 index 00000000..08f8977f --- /dev/null +++ b/recipes/process-assert-to-node-assert/tests/expected/01-basic-global-process-assert.js @@ -0,0 +1,3 @@ +import assert from "node:assert"; +assert(condition, "Basic assertion"); +assert.strictEqual(a, b, "Values should be equal"); diff --git a/recipes/process-assert-to-node-assert/tests/expected/02-basic-commonjs.cjs b/recipes/process-assert-to-node-assert/tests/expected/02-basic-commonjs.cjs new file mode 100644 index 00000000..3ae76b87 --- /dev/null +++ b/recipes/process-assert-to-node-assert/tests/expected/02-basic-commonjs.cjs @@ -0,0 +1,3 @@ +const assert = require("node:assert"); +assert(condition, "Basic assertion"); +assert.strictEqual(a, b, "Values should be equal"); diff --git a/recipes/process-assert-to-node-assert/tests/expected/03-esm-import-process.mjs b/recipes/process-assert-to-node-assert/tests/expected/03-esm-import-process.mjs new file mode 100644 index 00000000..18f54fdb --- /dev/null +++ b/recipes/process-assert-to-node-assert/tests/expected/03-esm-import-process.mjs @@ -0,0 +1,3 @@ +import assert from "node:assert"; +assert(value, "Process assertion"); +assert.strictEqual(obj1, obj2); diff --git a/recipes/process-assert-to-node-assert/tests/expected/04-mixed-process-usage.js b/recipes/process-assert-to-node-assert/tests/expected/04-mixed-process-usage.js new file mode 100644 index 00000000..4d46b15f --- /dev/null +++ b/recipes/process-assert-to-node-assert/tests/expected/04-mixed-process-usage.js @@ -0,0 +1,5 @@ +import assert from "node:assert"; +import process from "node:process"; +assert(value, "Process assertion"); +process.env.NODE_ENV = "test"; +console.log(process.pid); diff --git a/recipes/process-assert-to-node-assert/tests/expected/05-require-process.js b/recipes/process-assert-to-node-assert/tests/expected/05-require-process.js new file mode 100644 index 00000000..e54c017d --- /dev/null +++ b/recipes/process-assert-to-node-assert/tests/expected/05-require-process.js @@ -0,0 +1,3 @@ +const assert = require("node:assert"); +assert(value, "Process assertion"); +assert.throws(() => { throw new Error(); }); diff --git a/recipes/process-assert-to-node-assert/tests/expected/06-destructured-assert-only.js b/recipes/process-assert-to-node-assert/tests/expected/06-destructured-assert-only.js new file mode 100644 index 00000000..24569401 --- /dev/null +++ b/recipes/process-assert-to-node-assert/tests/expected/06-destructured-assert-only.js @@ -0,0 +1,3 @@ +import assert from "node:assert"; +assert(condition, "Assertion from destructured import"); +assert.throws(() => { throw new Error("test"); }); diff --git a/recipes/process-assert-to-node-assert/tests/expected/07-destructured-mixed-bindings.js b/recipes/process-assert-to-node-assert/tests/expected/07-destructured-mixed-bindings.js new file mode 100644 index 00000000..0aff0c40 --- /dev/null +++ b/recipes/process-assert-to-node-assert/tests/expected/07-destructured-mixed-bindings.js @@ -0,0 +1,4 @@ +import assert from "node:assert"; +import { env } from "node:process"; +assert(value, "Using destructured assert"); +console.log(env.NODE_ENV); diff --git a/recipes/process-assert-to-node-assert/tests/expected/08-aliased-destructured-assert.js b/recipes/process-assert-to-node-assert/tests/expected/08-aliased-destructured-assert.js new file mode 100644 index 00000000..cf8634e6 --- /dev/null +++ b/recipes/process-assert-to-node-assert/tests/expected/08-aliased-destructured-assert.js @@ -0,0 +1,3 @@ +import assert from "node:assert"; +assert(value, "Using aliased assert"); +assert.notStrictEqual(a, b); diff --git a/recipes/process-assert-to-node-assert/tests/expected/09-aliased-destructured-mixed.js b/recipes/process-assert-to-node-assert/tests/expected/09-aliased-destructured-mixed.js new file mode 100644 index 00000000..e1b0edd5 --- /dev/null +++ b/recipes/process-assert-to-node-assert/tests/expected/09-aliased-destructured-mixed.js @@ -0,0 +1,5 @@ +import assert from "node:assert"; +import { env } from "node:process"; +assert(value, "Using aliased assert"); +assert.strictEqual(a, b); +console.log(env.NODE_ENV); diff --git a/recipes/process-assert-to-node-assert/tests/expected/10-require-destructured.js b/recipes/process-assert-to-node-assert/tests/expected/10-require-destructured.js new file mode 100644 index 00000000..78b4324f --- /dev/null +++ b/recipes/process-assert-to-node-assert/tests/expected/10-require-destructured.js @@ -0,0 +1,3 @@ +const assert = require("node:assert"); +assert(value, "Destructured assert from require"); +assert.strictEqual(a, b, "Should be equal"); diff --git a/recipes/process-assert-to-node-assert/tests/expected/11-already-has-assert-import.js b/recipes/process-assert-to-node-assert/tests/expected/11-already-has-assert-import.js new file mode 100644 index 00000000..db459c1a --- /dev/null +++ b/recipes/process-assert-to-node-assert/tests/expected/11-already-has-assert-import.js @@ -0,0 +1,3 @@ +import assert from "node:assert"; +assert(value, "This should be transformed"); +assert.strictEqual(a, b, "This should remain unchanged"); diff --git a/recipes/process-assert-to-node-assert/tests/expected/12-already-has-assert-require.js b/recipes/process-assert-to-node-assert/tests/expected/12-already-has-assert-require.js new file mode 100644 index 00000000..a0f7c1b4 --- /dev/null +++ b/recipes/process-assert-to-node-assert/tests/expected/12-already-has-assert-require.js @@ -0,0 +1,3 @@ +const assert = require("node:assert"); +assert(value, "This should be transformed"); +assert.strictEqual(a, b, "This should remain unchanged"); diff --git a/recipes/process-assert-to-node-assert/tests/expected/13-no-process-assert.js b/recipes/process-assert-to-node-assert/tests/expected/13-no-process-assert.js new file mode 100644 index 00000000..d7717c73 --- /dev/null +++ b/recipes/process-assert-to-node-assert/tests/expected/13-no-process-assert.js @@ -0,0 +1,6 @@ +const config = { + port: 3000, + host: "localhost" +}; + +console.log("Server config:", config); diff --git a/recipes/process-assert-to-node-assert/tests/expected/14-nested-in-function.js b/recipes/process-assert-to-node-assert/tests/expected/14-nested-in-function.js new file mode 100644 index 00000000..92d74048 --- /dev/null +++ b/recipes/process-assert-to-node-assert/tests/expected/14-nested-in-function.js @@ -0,0 +1,10 @@ +import assert from "node:assert"; +function testFunction() { + assert(condition, "Assertion inside function"); + + if (someCondition) { + assert.deepStrictEqual(obj1, obj2, "Deep comparison"); + } + + return assert.ok(value) && true; +} diff --git a/recipes/process-assert-to-node-assert/tests/expected/15-multiple-assert-methods.js b/recipes/process-assert-to-node-assert/tests/expected/15-multiple-assert-methods.js new file mode 100644 index 00000000..5db73614 --- /dev/null +++ b/recipes/process-assert-to-node-assert/tests/expected/15-multiple-assert-methods.js @@ -0,0 +1,6 @@ +import assert from "node:assert"; +assert(condition); +assert.ok(value); +assert.strictEqual(a, b); +assert.notStrictEqual(a, c); +assert.throws(() => { throw new Error(); }); diff --git a/recipes/process-assert-to-node-assert/tests/expected/16-mixed-require-and-assert.js b/recipes/process-assert-to-node-assert/tests/expected/16-mixed-require-and-assert.js new file mode 100644 index 00000000..e0389c84 --- /dev/null +++ b/recipes/process-assert-to-node-assert/tests/expected/16-mixed-require-and-assert.js @@ -0,0 +1,9 @@ +const assert = require("node:assert"); +const fs = require("fs"); + +function readConfig(path) { + assert(fs.existsSync(path), "Config file must exist"); + const data = fs.readFileSync(path, "utf8"); + assert.ok(data.length > 0, "Config file cannot be empty"); + return JSON.parse(data); +} diff --git a/recipes/process-assert-to-node-assert/tests/expected/17-complex-nested-class.js b/recipes/process-assert-to-node-assert/tests/expected/17-complex-nested-class.js new file mode 100644 index 00000000..1cd40c48 --- /dev/null +++ b/recipes/process-assert-to-node-assert/tests/expected/17-complex-nested-class.js @@ -0,0 +1,19 @@ +import assert from "node:assert"; +class Validator { + static validate(data) { + assert(data, "Data is required"); + + try { + assert.strictEqual(typeof data, "object", "Data must be object"); + } catch (error) { + assert.fail("Validation failed"); + } + + const results = [1, 2, 3].map(item => { + assert.ok(item > 0, "Item must be positive"); + return item * 2; + }); + + return results; + } +} diff --git a/recipes/process-assert-to-node-assert/tests/expected/18-dynamic-import.mjs b/recipes/process-assert-to-node-assert/tests/expected/18-dynamic-import.mjs new file mode 100644 index 00000000..d8503ece --- /dev/null +++ b/recipes/process-assert-to-node-assert/tests/expected/18-dynamic-import.mjs @@ -0,0 +1,3 @@ +import assert from "node:assert"; + +assert(true); diff --git a/recipes/process-assert-to-node-assert/tests/input/01-basic-global-process-assert.js b/recipes/process-assert-to-node-assert/tests/input/01-basic-global-process-assert.js new file mode 100644 index 00000000..f96e309c --- /dev/null +++ b/recipes/process-assert-to-node-assert/tests/input/01-basic-global-process-assert.js @@ -0,0 +1,2 @@ +process.assert(condition, "Basic assertion"); +process.assert.strictEqual(a, b, "Values should be equal"); diff --git a/recipes/process-assert-to-node-assert/tests/input/02-basic-commonjs.cjs b/recipes/process-assert-to-node-assert/tests/input/02-basic-commonjs.cjs new file mode 100644 index 00000000..f96e309c --- /dev/null +++ b/recipes/process-assert-to-node-assert/tests/input/02-basic-commonjs.cjs @@ -0,0 +1,2 @@ +process.assert(condition, "Basic assertion"); +process.assert.strictEqual(a, b, "Values should be equal"); diff --git a/recipes/process-assert-to-node-assert/tests/input/03-esm-import-process.mjs b/recipes/process-assert-to-node-assert/tests/input/03-esm-import-process.mjs new file mode 100644 index 00000000..8137a947 --- /dev/null +++ b/recipes/process-assert-to-node-assert/tests/input/03-esm-import-process.mjs @@ -0,0 +1,3 @@ +import process from "node:process"; +process.assert(value, "Process assertion"); +process.assert.strictEqual(obj1, obj2); diff --git a/recipes/process-assert-to-node-assert/tests/input/04-mixed-process-usage.js b/recipes/process-assert-to-node-assert/tests/input/04-mixed-process-usage.js new file mode 100644 index 00000000..4c0286f3 --- /dev/null +++ b/recipes/process-assert-to-node-assert/tests/input/04-mixed-process-usage.js @@ -0,0 +1,4 @@ +import process from "node:process"; +process.assert(value, "Process assertion"); +process.env.NODE_ENV = "test"; +console.log(process.pid); diff --git a/recipes/process-assert-to-node-assert/tests/input/05-require-process.js b/recipes/process-assert-to-node-assert/tests/input/05-require-process.js new file mode 100644 index 00000000..8467b5b4 --- /dev/null +++ b/recipes/process-assert-to-node-assert/tests/input/05-require-process.js @@ -0,0 +1,3 @@ +const process = require("node:process"); +process.assert(value, "Process assertion"); +process.assert.throws(() => { throw new Error(); }); diff --git a/recipes/process-assert-to-node-assert/tests/input/06-destructured-assert-only.js b/recipes/process-assert-to-node-assert/tests/input/06-destructured-assert-only.js new file mode 100644 index 00000000..bfd159da --- /dev/null +++ b/recipes/process-assert-to-node-assert/tests/input/06-destructured-assert-only.js @@ -0,0 +1,3 @@ +import { assert } from "node:process"; +assert(condition, "Assertion from destructured import"); +assert.throws(() => { throw new Error("test"); }); diff --git a/recipes/process-assert-to-node-assert/tests/input/07-destructured-mixed-bindings.js b/recipes/process-assert-to-node-assert/tests/input/07-destructured-mixed-bindings.js new file mode 100644 index 00000000..dcb14937 --- /dev/null +++ b/recipes/process-assert-to-node-assert/tests/input/07-destructured-mixed-bindings.js @@ -0,0 +1,3 @@ +import { assert, env } from "node:process"; +assert(value, "Using destructured assert"); +console.log(env.NODE_ENV); diff --git a/recipes/process-assert-to-node-assert/tests/input/08-aliased-destructured-assert.js b/recipes/process-assert-to-node-assert/tests/input/08-aliased-destructured-assert.js new file mode 100644 index 00000000..3fe72101 --- /dev/null +++ b/recipes/process-assert-to-node-assert/tests/input/08-aliased-destructured-assert.js @@ -0,0 +1,3 @@ +import { assert as nodeAssert } from "node:process"; +nodeAssert(value, "Using aliased assert"); +nodeAssert.notStrictEqual(a, b); diff --git a/recipes/process-assert-to-node-assert/tests/input/09-aliased-destructured-mixed.js b/recipes/process-assert-to-node-assert/tests/input/09-aliased-destructured-mixed.js new file mode 100644 index 00000000..ee1ad631 --- /dev/null +++ b/recipes/process-assert-to-node-assert/tests/input/09-aliased-destructured-mixed.js @@ -0,0 +1,4 @@ +import { assert as nodeAssert, env } from "node:process"; +nodeAssert(value, "Using aliased assert"); +nodeAssert.strictEqual(a, b); +console.log(env.NODE_ENV); diff --git a/recipes/process-assert-to-node-assert/tests/input/10-require-destructured.js b/recipes/process-assert-to-node-assert/tests/input/10-require-destructured.js new file mode 100644 index 00000000..aeda6e77 --- /dev/null +++ b/recipes/process-assert-to-node-assert/tests/input/10-require-destructured.js @@ -0,0 +1,3 @@ +const { assert } = require("node:process"); +assert(value, "Destructured assert from require"); +assert.strictEqual(a, b, "Should be equal"); diff --git a/recipes/process-assert-to-node-assert/tests/input/11-already-has-assert-import.js b/recipes/process-assert-to-node-assert/tests/input/11-already-has-assert-import.js new file mode 100644 index 00000000..f1c2bd59 --- /dev/null +++ b/recipes/process-assert-to-node-assert/tests/input/11-already-has-assert-import.js @@ -0,0 +1,3 @@ +import assert from "node:assert"; +process.assert(value, "This should be transformed"); +assert.strictEqual(a, b, "This should remain unchanged"); diff --git a/recipes/process-assert-to-node-assert/tests/input/12-already-has-assert-require.js b/recipes/process-assert-to-node-assert/tests/input/12-already-has-assert-require.js new file mode 100644 index 00000000..3d98a928 --- /dev/null +++ b/recipes/process-assert-to-node-assert/tests/input/12-already-has-assert-require.js @@ -0,0 +1,3 @@ +const assert = require("node:assert"); +process.assert(value, "This should be transformed"); +assert.strictEqual(a, b, "This should remain unchanged"); diff --git a/recipes/process-assert-to-node-assert/tests/input/13-no-process-assert.js b/recipes/process-assert-to-node-assert/tests/input/13-no-process-assert.js new file mode 100644 index 00000000..d7717c73 --- /dev/null +++ b/recipes/process-assert-to-node-assert/tests/input/13-no-process-assert.js @@ -0,0 +1,6 @@ +const config = { + port: 3000, + host: "localhost" +}; + +console.log("Server config:", config); diff --git a/recipes/process-assert-to-node-assert/tests/input/14-nested-in-function.js b/recipes/process-assert-to-node-assert/tests/input/14-nested-in-function.js new file mode 100644 index 00000000..336d1695 --- /dev/null +++ b/recipes/process-assert-to-node-assert/tests/input/14-nested-in-function.js @@ -0,0 +1,9 @@ +function testFunction() { + process.assert(condition, "Assertion inside function"); + + if (someCondition) { + process.assert.deepStrictEqual(obj1, obj2, "Deep comparison"); + } + + return process.assert.ok(value) && true; +} diff --git a/recipes/process-assert-to-node-assert/tests/input/15-multiple-assert-methods.js b/recipes/process-assert-to-node-assert/tests/input/15-multiple-assert-methods.js new file mode 100644 index 00000000..086caef7 --- /dev/null +++ b/recipes/process-assert-to-node-assert/tests/input/15-multiple-assert-methods.js @@ -0,0 +1,5 @@ +process.assert(condition); +process.assert.ok(value); +process.assert.strictEqual(a, b); +process.assert.notStrictEqual(a, c); +process.assert.throws(() => { throw new Error(); }); diff --git a/recipes/process-assert-to-node-assert/tests/input/16-mixed-require-and-assert.js b/recipes/process-assert-to-node-assert/tests/input/16-mixed-require-and-assert.js new file mode 100644 index 00000000..34229dbf --- /dev/null +++ b/recipes/process-assert-to-node-assert/tests/input/16-mixed-require-and-assert.js @@ -0,0 +1,8 @@ +const fs = require("fs"); + +function readConfig(path) { + process.assert(fs.existsSync(path), "Config file must exist"); + const data = fs.readFileSync(path, "utf8"); + process.assert.ok(data.length > 0, "Config file cannot be empty"); + return JSON.parse(data); +} diff --git a/recipes/process-assert-to-node-assert/tests/input/17-complex-nested-class.js b/recipes/process-assert-to-node-assert/tests/input/17-complex-nested-class.js new file mode 100644 index 00000000..7306d2e7 --- /dev/null +++ b/recipes/process-assert-to-node-assert/tests/input/17-complex-nested-class.js @@ -0,0 +1,18 @@ +class Validator { + static validate(data) { + process.assert(data, "Data is required"); + + try { + process.assert.strictEqual(typeof data, "object", "Data must be object"); + } catch (error) { + process.assert.fail("Validation failed"); + } + + const results = [1, 2, 3].map(item => { + process.assert.ok(item > 0, "Item must be positive"); + return item * 2; + }); + + return results; + } +} diff --git a/recipes/process-assert-to-node-assert/tests/input/18-dynamic-import.mjs b/recipes/process-assert-to-node-assert/tests/input/18-dynamic-import.mjs new file mode 100644 index 00000000..030db403 --- /dev/null +++ b/recipes/process-assert-to-node-assert/tests/input/18-dynamic-import.mjs @@ -0,0 +1,3 @@ +const { assert } = await import('process'); + +assert(true); diff --git a/recipes/process-assert-to-node-assert/workflow.yaml b/recipes/process-assert-to-node-assert/workflow.yaml new file mode 100644 index 00000000..7aa67a1d --- /dev/null +++ b/recipes/process-assert-to-node-assert/workflow.yaml @@ -0,0 +1,25 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/codemod-com/codemod/refs/heads/main/schemas/workflow.json + +version: "1" + +nodes: + - id: apply-transforms + name: Apply AST Transformations + type: automatic + steps: + - name: Handle DEP0100 via transforming `process.assert` to `node:assert`. + js-ast-grep: + js_file: src/workflow.ts + base_path: . + include: + - "**/*.cjs" + - "**/*.js" + - "**/*.jsx" + - "**/*.mjs" + - "**/*.cts" + - "**/*.mts" + - "**/*.ts" + - "**/*.tsx" + exclude: + - "**/node_modules/**" + language: typescript