diff --git a/codemods/explicit-request-params/README.md b/codemods/explicit-request-params/README.md new file mode 100644 index 0000000..34cb527 --- /dev/null +++ b/codemods/explicit-request-params/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/explicit-request-params/codemod.yaml b/codemods/explicit-request-params/codemod.yaml new file mode 100644 index 0000000..a11cf3e --- /dev/null +++ b/codemods/explicit-request-params/codemod.yaml @@ -0,0 +1,24 @@ +schema_version: "1.0" +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: + languages: + - javascript + - typescript + +keywords: + - transformation + - migration + - express + - params + +registry: + access: public + visibility: public \ No newline at end of file diff --git a/codemods/explicit-request-params/package.json b/codemods/explicit-request-params/package.json new file mode 100644 index 0000000..817ffaf --- /dev/null +++ b/codemods/explicit-request-params/package.json @@ -0,0 +1,22 @@ +{ + "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.", + "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/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/explicit-request-params/README.md", + "devDependencies": { + "@codemod.com/jssg-types": "^1.3.1" + } +} diff --git a/codemods/explicit-request-params/src/workflow.ts b/codemods/explicit-request-params/src/workflow.ts new file mode 100644 index 0000000..3af7a37 --- /dev/null +++ b/codemods/explicit-request-params/src/workflow.ts @@ -0,0 +1,73 @@ +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)$' }, + }, + }) + + if (!nodes.length) return null + + 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`)) + } else if (argValue === 'query') { + edits.push(call.replace(`${requestName}.query`)) + } else if (argValue) { + edits.push(call.replace(`${requestName}.params.${argValue}`)) + } + } + + if (!edits.length) return null + + return rootNode.commitEdits(edits) +} + +export default transform diff --git a/codemods/explicit-request-params/tests/expected/params.ts b/codemods/explicit-request-params/tests/expected/params.ts new file mode 100644 index 0000000..2457634 --- /dev/null +++ b/codemods/explicit-request-params/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/explicit-request-params/tests/input/params.ts b/codemods/explicit-request-params/tests/input/params.ts new file mode 100644 index 0000000..45f1d0f --- /dev/null +++ b/codemods/explicit-request-params/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/explicit-request-params/workflow.yaml b/codemods/explicit-request-params/workflow.yaml new file mode 100644 index 0000000..b9ac0a6 --- /dev/null +++ b/codemods/explicit-request-params/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(name)` 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 diff --git a/package-lock.json b/package-lock.json index ddda229..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", @@ -80,6 +88,15 @@ "@codemod.com/jssg-types": "^1.3.1" } }, + "codemods/req-param": { + "name": "@expressjs/req-param", + "version": "1.0.0", + "extraneous": true, + "license": "MIT", + "devDependencies": { + "@codemod.com/jssg-types": "^1.3.1" + } + }, "codemods/route-del-to-delete": { "name": "@expressjs/route-del-to-delete", "version": "1.0.0", @@ -1061,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