From 4cb2b1a80a11af8de2beb1999248c7e15877500b Mon Sep 17 00:00:00 2001 From: Sebastian Beltran Date: Tue, 23 Dec 2025 22:09:12 -0500 Subject: [PATCH 1/4] feat(req-param): migrate req-param to codemod.com --- codemods/req-param/codemod.yaml | 23 +++++++ codemods/req-param/package.json | 22 ++++++ codemods/req-param/src/workflow.ts | 75 +++++++++++++++++++++ codemods/req-param/tests/expected/params.ts | 39 +++++++++++ codemods/req-param/tests/input/params.ts | 39 +++++++++++ codemods/req-param/workflow.yaml | 28 ++++++++ 6 files changed, 226 insertions(+) create mode 100644 codemods/req-param/codemod.yaml create mode 100644 codemods/req-param/package.json create mode 100644 codemods/req-param/src/workflow.ts create mode 100644 codemods/req-param/tests/expected/params.ts create mode 100644 codemods/req-param/tests/input/params.ts create mode 100644 codemods/req-param/workflow.yaml diff --git a/codemods/req-param/codemod.yaml b/codemods/req-param/codemod.yaml new file mode 100644 index 0000000..78a5630 --- /dev/null +++ b/codemods/req-param/codemod.yaml @@ -0,0 +1,23 @@ +schema_version: "1.0" +name: "@expressjs/req-param" +version: "1.0.0" +description: Migrates usage of the legacy APIs `req.param('body')`, `req.param('query')` and `req.param('params')` to the current recommended approaches +author: bjohansebas (Sebastian Beltran) +license: MIT +workflow: workflow.yaml +category: migration + +targets: + languages: + - javascript + - typescript + +keywords: + - transformation + - migration + - express + - params + +registry: + access: public + visibility: public \ No newline at end of file diff --git a/codemods/req-param/package.json b/codemods/req-param/package.json new file mode 100644 index 0000000..1e452f0 --- /dev/null +++ b/codemods/req-param/package.json @@ -0,0 +1,22 @@ +{ + "name": "@expressjs/req-param", + "private": true, + "version": "1.0.0", + "description": "Migrates usage of the legacy APIs `req.param('body')`, `req.param('query')` and `req.param('params')` to the current recommended approaches.", + "type": "module", + "scripts": { + "test": "npx codemod jssg test -l typescript ./src/workflow.ts ./" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/expressjs/codemod.git", + "directory": "codemods/req-param", + "bugs": "https://github.com/expressjs/codemod/issues" + }, + "author": "bjohansebas (Sebastian Beltran)", + "license": "MIT", + "homepage": "https://github.com/expressjs/codemod/blob/main/codemods/req-param/README.md", + "devDependencies": { + "@codemod.com/jssg-types": "^1.3.1" + } +} \ No newline at end of file diff --git a/codemods/req-param/src/workflow.ts b/codemods/req-param/src/workflow.ts new file mode 100644 index 0000000..97796a5 --- /dev/null +++ b/codemods/req-param/src/workflow.ts @@ -0,0 +1,75 @@ +import type Js from '@codemod.com/jssg-types/src/langs/javascript' +import type { Edit, SgNode, SgRoot } from '@codemod.com/jssg-types/src/main' + +function getStringLiteralValue(node: SgNode): string | null { + if (!node.is('string')) return null + + const fragments = node.findAll({ rule: { kind: 'string_fragment' } }) + if (fragments.length !== 1) return null + return fragments[0]?.text() ?? null +} + +function findParentFunctionParameters(node: SgNode): SgNode | null { + let parent = node.parent() + while (parent) { + if (parent.is('formal_parameters')) return parent + parent = parent.parent() + } + return null +} + +async function transform(root: SgRoot): Promise { + const rootNode = root.root() + + const nodes = rootNode.findAll({ + rule: { + pattern: '$OBJ.$METHOD($ARG)', + }, + constraints: { + METHOD: { regex: '^(param)$' }, + }, + }) + + const edits: Edit[] = [] + + for (const call of nodes) { + const arg = call.getMatch('ARG') + const obj = call.getMatch('OBJ') + + if (!arg || !obj) continue + const objDef = obj.definition({ resolveExternal: false }) + if (!objDef) continue + + const isParameter = objDef.node.matches({ + rule: { inside: { kind: 'formal_parameters', stopBy: 'end' } }, + }) + if (!isParameter) continue + + const parameters = findParentFunctionParameters(objDef.node) + if (!parameters) continue + const firstParameter = parameters.find({ rule: { kind: 'identifier' } }) + + if (!firstParameter) continue + const requestName = firstParameter.text() + + const argValue = getStringLiteralValue(arg) + + if (argValue === "body") { + edits.push(call.replace(`${requestName}.body`)) + } + + if(argValue === "query") { + edits.push(call.replace(`${requestName}.query`)) + } + + if(argValue) { + edits.push(call.replace(`${requestName}.params.${argValue}`)) + } + } + + if (edits.length === 0) return null + + return rootNode.commitEdits(edits) +} + +export default transform \ No newline at end of file diff --git a/codemods/req-param/tests/expected/params.ts b/codemods/req-param/tests/expected/params.ts new file mode 100644 index 0000000..2457634 --- /dev/null +++ b/codemods/req-param/tests/expected/params.ts @@ -0,0 +1,39 @@ +import express from "express"; + +const app = express(); + +app.get("/", function (req, res) { + const reqBody = req.body; + const reqQuery = req.query; + const reqQueryTest = req.query.test; + const reqOther = req.params.other; + const reqOtherNested = req.params.other.nested; +}); + +app.get("/", function (request, response) { + const reqBody = request.body; + const reqQuery = request.query; + const reqQueryTest = request.query.test; + const reqOther = request.params.other; + const reqOtherNested = request.params.other.nested; +}); + +app.get("/", (req, res) => { + const reqBody = req.body; + const reqQuery = req.query; + const reqQueryTest = req.query.test; + const reqOther = req.params.other; + const reqOtherNested = req.params.other.nested; +}); + +app.get("/", (request, response) => { + const reqBody = request.body; + const reqQuery = request.query; + const reqQueryTest = request.query.test; + const reqOther = request.params.other; + const reqOtherNested = request.params.other.nested; +}); + +app.param(function () { + // my important logic +}) \ No newline at end of file diff --git a/codemods/req-param/tests/input/params.ts b/codemods/req-param/tests/input/params.ts new file mode 100644 index 0000000..45f1d0f --- /dev/null +++ b/codemods/req-param/tests/input/params.ts @@ -0,0 +1,39 @@ +import express from "express"; + +const app = express(); + +app.get("/", function (req, res) { + const reqBody = req.param('body'); + const reqQuery = req.param('query'); + const reqQueryTest = req.param('query').test; + const reqOther = req.param('other'); + const reqOtherNested = req.param('other').nested; +}); + +app.get("/", function (request, response) { + const reqBody = request.param('body'); + const reqQuery = request.param('query'); + const reqQueryTest = request.param('query').test; + const reqOther = request.param('other'); + const reqOtherNested = request.param('other').nested; +}); + +app.get("/", (req, res) => { + const reqBody = req.param('body'); + const reqQuery = req.param('query'); + const reqQueryTest = req.param('query').test; + const reqOther = req.param('other'); + const reqOtherNested = req.param('other').nested; +}); + +app.get("/", (request, response) => { + const reqBody = request.param('body'); + const reqQuery = request.param('query'); + const reqQueryTest = request.param('query').test; + const reqOther = request.param('other'); + const reqOtherNested = request.param('other').nested; +}); + +app.param(function () { + // my important logic +}) \ No newline at end of file diff --git a/codemods/req-param/workflow.yaml b/codemods/req-param/workflow.yaml new file mode 100644 index 0000000..f5d8c67 --- /dev/null +++ b/codemods/req-param/workflow.yaml @@ -0,0 +1,28 @@ +# 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 + runtime: + type: direct + steps: + - name: Migrates usage of the legacy APIs `req.param('body')`, `req.param('query')` and `req.param('params')` to the current recommended approaches + js-ast-grep: + js_file: src/workflow.ts + base_path: . + semantic_analysis: file + include: + - "**/*.cjs" + - "**/*.js" + - "**/*.jsx" + - "**/*.mjs" + - "**/*.cts" + - "**/*.mts" + - "**/*.ts" + - "**/*.tsx" + exclude: + - "**/node_modules/**" + language: typescript \ No newline at end of file From dc86e47896f0c13f7b64872f21d9a7d804cd144e Mon Sep 17 00:00:00 2001 From: Sebastian Beltran Date: Tue, 23 Dec 2025 22:25:50 -0500 Subject: [PATCH 2/4] feat(req-param): update migration documentation --- codemods/req-param/README.md | 38 ++++++++++++++++++++++++++++++ codemods/req-param/codemod.yaml | 2 +- codemods/req-param/package.json | 4 ++-- codemods/req-param/src/workflow.ts | 16 +++++-------- codemods/req-param/workflow.yaml | 2 +- 5 files changed, 48 insertions(+), 14 deletions(-) create mode 100644 codemods/req-param/README.md diff --git a/codemods/req-param/README.md b/codemods/req-param/README.md new file mode 100644 index 0000000..34cb527 --- /dev/null +++ b/codemods/req-param/README.md @@ -0,0 +1,38 @@ +# Migrate legacy `req.param(name)` + +The `req.param(name)` helper that used to magically look up values from multiple places has been removed. This potentially confusing and dangerous method of retrieving form data has been removed. You will now need to specifically look for the submitted parameter name in the `req.params`, `req.body`, or `req.query` object. + +## Examples + +### Replacing `req.param('body')` and `req.param('query')` + +Replace `req.param('body')` with `req.body` and +`req.param('query')` with `req.query`. + +```diff +app.get('/', (req, res) => { + // Before +- const reqBody = req.param('body'); +- const reqQuery = req.param('query'); + // After ++ const reqBody = req.body; ++ const reqQuery = req.query; +}); +``` + +### Replacing `req.param('paramName')` + +Replace `req.param('paramName')` with `req.params.paramName`. + +```diff +app.get('/user/:id', (req, res) => { + // Before +- const userId = req.param('id'); + // After ++ const userId = req.params.id; +}); +``` + +## References + +- [Migration of `req.param()`](https://expressjs.com/en/guide/migrating-5.html#req.param) diff --git a/codemods/req-param/codemod.yaml b/codemods/req-param/codemod.yaml index 78a5630..ff5d789 100644 --- a/codemods/req-param/codemod.yaml +++ b/codemods/req-param/codemod.yaml @@ -1,7 +1,7 @@ schema_version: "1.0" name: "@expressjs/req-param" version: "1.0.0" -description: Migrates usage of the legacy APIs `req.param('body')`, `req.param('query')` and `req.param('params')` to the current recommended approaches +description: Migrates usage of the legacy APIs `req.param(name)` to the current recommended approaches author: bjohansebas (Sebastian Beltran) license: MIT workflow: workflow.yaml diff --git a/codemods/req-param/package.json b/codemods/req-param/package.json index 1e452f0..54516b1 100644 --- a/codemods/req-param/package.json +++ b/codemods/req-param/package.json @@ -2,7 +2,7 @@ "name": "@expressjs/req-param", "private": true, "version": "1.0.0", - "description": "Migrates usage of the legacy APIs `req.param('body')`, `req.param('query')` and `req.param('params')` to the current recommended approaches.", + "description": "Migrates usage of the legacy APIs `req.param(name)` to the current recommended approaches.", "type": "module", "scripts": { "test": "npx codemod jssg test -l typescript ./src/workflow.ts ./" @@ -19,4 +19,4 @@ "devDependencies": { "@codemod.com/jssg-types": "^1.3.1" } -} \ No newline at end of file +} diff --git a/codemods/req-param/src/workflow.ts b/codemods/req-param/src/workflow.ts index 97796a5..6aa71dd 100644 --- a/codemods/req-param/src/workflow.ts +++ b/codemods/req-param/src/workflow.ts @@ -22,7 +22,7 @@ async function transform(root: SgRoot): Promise { const rootNode = root.root() const nodes = rootNode.findAll({ - rule: { + rule: { pattern: '$OBJ.$METHOD($ARG)', }, constraints: { @@ -35,7 +35,7 @@ async function transform(root: SgRoot): Promise { for (const call of nodes) { const arg = call.getMatch('ARG') const obj = call.getMatch('OBJ') - + if (!arg || !obj) continue const objDef = obj.definition({ resolveExternal: false }) if (!objDef) continue @@ -54,15 +54,11 @@ async function transform(root: SgRoot): Promise { const argValue = getStringLiteralValue(arg) - if (argValue === "body") { + if (argValue === 'body') { edits.push(call.replace(`${requestName}.body`)) - } - - if(argValue === "query") { + } else if (argValue === 'query') { edits.push(call.replace(`${requestName}.query`)) - } - - if(argValue) { + } else if (argValue) { edits.push(call.replace(`${requestName}.params.${argValue}`)) } } @@ -72,4 +68,4 @@ async function transform(root: SgRoot): Promise { return rootNode.commitEdits(edits) } -export default transform \ No newline at end of file +export default transform diff --git a/codemods/req-param/workflow.yaml b/codemods/req-param/workflow.yaml index f5d8c67..b9ac0a6 100644 --- a/codemods/req-param/workflow.yaml +++ b/codemods/req-param/workflow.yaml @@ -9,7 +9,7 @@ nodes: runtime: type: direct steps: - - name: Migrates usage of the legacy APIs `req.param('body')`, `req.param('query')` and `req.param('params')` to the current recommended approaches + - name: Migrates usage of the legacy APIs `req.param(name)` to the current recommended approaches js-ast-grep: js_file: src/workflow.ts base_path: . From 9732e6e98f0711098f4d7ce19ecb2f791e54c20a Mon Sep 17 00:00:00 2001 From: Sebastian Beltran Date: Fri, 9 Jan 2026 17:58:25 -0500 Subject: [PATCH 3/4] fix(transform): handle empty nodes and edits cases in transformation logic --- codemods/req-param/src/workflow.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/codemods/req-param/src/workflow.ts b/codemods/req-param/src/workflow.ts index 6aa71dd..3af7a37 100644 --- a/codemods/req-param/src/workflow.ts +++ b/codemods/req-param/src/workflow.ts @@ -30,6 +30,8 @@ async function transform(root: SgRoot): Promise { }, }) + if (!nodes.length) return null + const edits: Edit[] = [] for (const call of nodes) { @@ -63,7 +65,7 @@ async function transform(root: SgRoot): Promise { } } - if (edits.length === 0) return null + if (!edits.length) return null return rootNode.commitEdits(edits) } From d2c336349240b1682cd10430e031bae4a288cb7d Mon Sep 17 00:00:00 2001 From: Sebastian Beltran Date: Tue, 13 Jan 2026 10:30:58 -0500 Subject: [PATCH 4/4] rename codemod to explicit-request-params --- .../README.md | 0 .../codemod.yaml | 3 ++- .../package.json | 6 +++--- .../src/workflow.ts | 0 .../tests/expected/params.ts | 0 .../tests/input/params.ts | 0 .../workflow.yaml | 0 package-lock.json | 17 +++++++++++++---- 8 files changed, 18 insertions(+), 8 deletions(-) rename codemods/{req-param => explicit-request-params}/README.md (100%) rename codemods/{req-param => explicit-request-params}/codemod.yaml (75%) rename codemods/{req-param => explicit-request-params}/package.json (81%) rename codemods/{req-param => explicit-request-params}/src/workflow.ts (100%) rename codemods/{req-param => explicit-request-params}/tests/expected/params.ts (100%) rename codemods/{req-param => explicit-request-params}/tests/input/params.ts (100%) rename codemods/{req-param => explicit-request-params}/workflow.yaml (100%) diff --git a/codemods/req-param/README.md b/codemods/explicit-request-params/README.md similarity index 100% rename from codemods/req-param/README.md rename to codemods/explicit-request-params/README.md diff --git a/codemods/req-param/codemod.yaml b/codemods/explicit-request-params/codemod.yaml similarity index 75% rename from codemods/req-param/codemod.yaml rename to codemods/explicit-request-params/codemod.yaml index ff5d789..a11cf3e 100644 --- a/codemods/req-param/codemod.yaml +++ b/codemods/explicit-request-params/codemod.yaml @@ -1,10 +1,11 @@ schema_version: "1.0" -name: "@expressjs/req-param" +name: "@expressjs/explicit-request-params" version: "1.0.0" description: Migrates usage of the legacy APIs `req.param(name)` to the current recommended approaches author: bjohansebas (Sebastian Beltran) license: MIT workflow: workflow.yaml +repository: "https://github.com/expressjs/codemod/tree/HEAD/codemods/explicit-request-params" category: migration targets: diff --git a/codemods/req-param/package.json b/codemods/explicit-request-params/package.json similarity index 81% rename from codemods/req-param/package.json rename to codemods/explicit-request-params/package.json index 54516b1..817ffaf 100644 --- a/codemods/req-param/package.json +++ b/codemods/explicit-request-params/package.json @@ -1,5 +1,5 @@ { - "name": "@expressjs/req-param", + "name": "@expressjs/explicit-request-params", "private": true, "version": "1.0.0", "description": "Migrates usage of the legacy APIs `req.param(name)` to the current recommended approaches.", @@ -10,12 +10,12 @@ "repository": { "type": "git", "url": "git+https://github.com/expressjs/codemod.git", - "directory": "codemods/req-param", + "directory": "codemods/explicit-request-params", "bugs": "https://github.com/expressjs/codemod/issues" }, "author": "bjohansebas (Sebastian Beltran)", "license": "MIT", - "homepage": "https://github.com/expressjs/codemod/blob/main/codemods/req-param/README.md", + "homepage": "https://github.com/expressjs/codemod/blob/main/codemods/explicit-request-params/README.md", "devDependencies": { "@codemod.com/jssg-types": "^1.3.1" } diff --git a/codemods/req-param/src/workflow.ts b/codemods/explicit-request-params/src/workflow.ts similarity index 100% rename from codemods/req-param/src/workflow.ts rename to codemods/explicit-request-params/src/workflow.ts diff --git a/codemods/req-param/tests/expected/params.ts b/codemods/explicit-request-params/tests/expected/params.ts similarity index 100% rename from codemods/req-param/tests/expected/params.ts rename to codemods/explicit-request-params/tests/expected/params.ts diff --git a/codemods/req-param/tests/input/params.ts b/codemods/explicit-request-params/tests/input/params.ts similarity index 100% rename from codemods/req-param/tests/input/params.ts rename to codemods/explicit-request-params/tests/input/params.ts diff --git a/codemods/req-param/workflow.yaml b/codemods/explicit-request-params/workflow.yaml similarity index 100% rename from codemods/req-param/workflow.yaml rename to codemods/explicit-request-params/workflow.yaml diff --git a/package-lock.json b/package-lock.json index 093e8fe..f9dd8a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55,6 +55,14 @@ "@codemod.com/jssg-types": "^1.3.1" } }, + "codemods/explicit-request-params": { + "name": "@expressjs/explicit-request-params", + "version": "1.0.0", + "license": "MIT", + "devDependencies": { + "@codemod.com/jssg-types": "^1.3.1" + } + }, "codemods/magic-redirect": { "name": "@expressjs/back-redirect-deprecated", "version": "1.0.0", @@ -83,6 +91,7 @@ "codemods/req-param": { "name": "@expressjs/req-param", "version": "1.0.0", + "extraneous": true, "license": "MIT", "devDependencies": { "@codemod.com/jssg-types": "^1.3.1" @@ -1069,6 +1078,10 @@ "resolved": "codemods/camelcase-sendfile", "link": true }, + "node_modules/@expressjs/explicit-request-params": { + "resolved": "codemods/explicit-request-params", + "link": true + }, "node_modules/@expressjs/pluralize-method-names": { "resolved": "codemods/pluralize-method-names", "link": true @@ -1077,10 +1090,6 @@ "resolved": "codemods/redirect-arg-order", "link": true }, - "node_modules/@expressjs/req-param": { - "resolved": "codemods/req-param", - "link": true - }, "node_modules/@expressjs/route-del-to-delete": { "resolved": "codemods/route-del-to-delete", "link": true