Skip to content

Commit a0a45a0

Browse files
authored
feat(redirect-arg-order): add migration codemod for res.redirect argument order (#99)
* feat(redirect-arg-order): add migration codemod for res.redirect argument order * refactor(workflow): simplify early return checks for nodes and edits * fix(codemod.yaml): update repository URL for redirect-arg-order migration * fix(docs): clarify deprecation notice for res.redirect argument order migration
1 parent ce5c9ad commit a0a45a0

8 files changed

Lines changed: 229 additions & 0 deletions

File tree

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Migrate legacy `res.redirect(url, status)`
2+
3+
Migrates usage of the legacy APIs `res.redirect(url, status)` to the new signature
4+
`res.redirect(status, url)`. This usage was deprecated in Express 4, in Express 5 you must use the new signature `res.redirect(status, url)`.
5+
6+
## Example
7+
8+
### Migrating `res.redirect(url, status)`
9+
10+
The migration involves replacing instances of `res.redirect(url, status)` with `res.redirect(status, url)`.
11+
12+
```diff
13+
app.get('/some-route', (req, res) => {
14+
// Some logic here
15+
- res.redirect(url, status);
16+
+ res.redirect(status, url);
17+
});
18+
```
19+
20+
## References
21+
22+
- [Migration of res.redirect(url, status)](https://expressjs.com/en/guide/migrating-5.html#res.redirect)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
schema_version: "1.0"
2+
name: "@expressjs/redirect-arg-order"
3+
version: "1.0.0"
4+
description: Migrates usage of the legacy APIs `res.redirect(url, status)` to use the recommended argument order `res.redirect(status, url)`.
5+
author: bjohansebas (Sebastian Beltran)
6+
license: MIT
7+
workflow: workflow.yaml
8+
repository: "https://github.com/expressjs/codemod/tree/HEAD/codemods/redirect-arg-order"
9+
category: migration
10+
11+
targets:
12+
languages:
13+
- javascript
14+
- typescript
15+
16+
keywords:
17+
- transformation
18+
- migration
19+
- express
20+
- redirect
21+
- location
22+
23+
registry:
24+
access: public
25+
visibility: public
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"name": "@expressjs/redirect-arg-order",
3+
"private": true,
4+
"version": "1.0.0",
5+
"description": "Migrates usage of the legacy APIs `res.redirect(url, status)` to use the recommended argument order `res.redirect(status, url)`.",
6+
"type": "module",
7+
"scripts": {
8+
"test": "npx codemod jssg test -l typescript ./src/workflow.ts ./"
9+
},
10+
"repository": {
11+
"type": "git",
12+
"url": "git+https://github.com/expressjs/codemod.git",
13+
"directory": "codemods/redirect-arg-order",
14+
"bugs": "https://github.com/expressjs/codemod/issues"
15+
},
16+
"author": "bjohansebas (Sebastian Beltran)",
17+
"license": "MIT",
18+
"homepage": "https://github.com/expressjs/codemod/blob/main/codemods/redirect-arg-order/README.md",
19+
"devDependencies": {
20+
"@codemod.com/jssg-types": "^1.3.1"
21+
}
22+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import type Js from '@codemod.com/jssg-types/src/langs/javascript'
2+
import type { Edit, SgRoot } from '@codemod.com/jssg-types/src/main'
3+
4+
async function transform(root: SgRoot<Js>): Promise<string | null> {
5+
const rootNode = root.root()
6+
7+
const nodes = rootNode.findAll({
8+
rule: {
9+
pattern: '$OBJ.$METHOD($$$ARG)',
10+
},
11+
constraints: {
12+
METHOD: { regex: '^(redirect)$' },
13+
},
14+
})
15+
16+
if (!nodes.length) return null
17+
18+
const edits: Edit[] = []
19+
20+
for (const call of nodes) {
21+
const obj = call.getMatch('OBJ')
22+
const args = call.getMultipleMatches('ARG')
23+
if (!obj || args.length < 3) continue
24+
25+
const objDef = obj.definition({ resolveExternal: false })
26+
if (!objDef) continue
27+
28+
// $$$ARG yields argument nodes interleaved with separators, so arg nodes are at 0,2,4...
29+
const first = args[0]
30+
const second = args[2]
31+
if (!first || !second) continue
32+
33+
// Only transform legacy form redirect(url, status) where second is number
34+
if (second.is('number') && !first.is('number')) {
35+
edits.push(call.replace(`${obj.text()}.redirect(${second.text()}, ${first.text()})`))
36+
}
37+
}
38+
39+
if (!edits.length) return null
40+
41+
return rootNode.commitEdits(edits)
42+
}
43+
44+
export default transform
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import express from "express";
2+
import { redirect } from "somelibrary";
3+
4+
const app = express();
5+
6+
app.get("/", function (...arg) {
7+
const [, res] = arg
8+
res.redirect();
9+
});
10+
11+
app.get("/", function (...arg) {
12+
const [, res] = arg
13+
res.redirect(301, "/other-page");
14+
});
15+
16+
app.get("/", function (req, res) {
17+
res.redirect();
18+
});
19+
20+
app.get("/", function (req, res) {
21+
res.redirect(301, "/other-page");
22+
});
23+
24+
app.get("/", function (req, response) {
25+
response.redirect(301, "/other-page");
26+
});
27+
28+
app.get("/", function (req, res) {
29+
res.redirect(301, "/other-page");
30+
});
31+
32+
app.get("/", function (req, res) {
33+
res.redirect("/other-page");
34+
});
35+
36+
app.get("/", function (req, res) {
37+
redirect(301, "/other-page");
38+
});
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import express from "express";
2+
import { redirect } from "somelibrary";
3+
4+
const app = express();
5+
6+
app.get("/", function (...arg) {
7+
const [, res] = arg
8+
res.redirect();
9+
});
10+
11+
app.get("/", function (...arg) {
12+
const [, res] = arg
13+
res.redirect("/other-page", 301);
14+
});
15+
16+
app.get("/", function (req, res) {
17+
res.redirect();
18+
});
19+
20+
app.get("/", function (req, res) {
21+
res.redirect("/other-page", 301);
22+
});
23+
24+
app.get("/", function (req, response) {
25+
response.redirect("/other-page", 301);
26+
});
27+
28+
app.get("/", function (req, res) {
29+
res.redirect(301, "/other-page");
30+
});
31+
32+
app.get("/", function (req, res) {
33+
res.redirect("/other-page");
34+
});
35+
36+
app.get("/", function (req, res) {
37+
redirect(301, "/other-page");
38+
});
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# yaml-language-server: $schema=https://raw.githubusercontent.com/codemod-com/codemod/refs/heads/main/schemas/workflow.json
2+
3+
version: "1"
4+
5+
nodes:
6+
- id: apply-transforms
7+
name: Apply AST Transformations
8+
type: automatic
9+
runtime:
10+
type: direct
11+
steps:
12+
- name: Migrates usage of the legacy APIs `res.redirect(url, status)` to use the recommended argument order `res.redirect(status, url)`.
13+
js-ast-grep:
14+
js_file: src/workflow.ts
15+
base_path: .
16+
semantic_analysis: file
17+
include:
18+
- "**/*.cjs"
19+
- "**/*.js"
20+
- "**/*.jsx"
21+
- "**/*.mjs"
22+
- "**/*.cts"
23+
- "**/*.mts"
24+
- "**/*.ts"
25+
- "**/*.tsx"
26+
exclude:
27+
- "**/node_modules/**"
28+
language: typescript

package-lock.json

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)