Skip to content

Commit 05a1fcd

Browse files
committed
feat(redirect-arg-order): add migration codemod for res.redirect argument order
1 parent 35e5d27 commit 05a1fcd

File tree

7 files changed

+213
-0
lines changed

7 files changed

+213
-0
lines changed
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)`.
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: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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+
category: migration
9+
10+
targets:
11+
languages:
12+
- javascript
13+
- typescript
14+
15+
keywords:
16+
- transformation
17+
- migration
18+
- express
19+
- redirect
20+
- location
21+
22+
registry:
23+
access: public
24+
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: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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+
const edits: Edit[] = []
17+
18+
for (const call of nodes) {
19+
const obj = call.getMatch('OBJ')
20+
const args = call.getMultipleMatches('ARG')
21+
if (!obj || args.length < 3) continue
22+
23+
const objDef = obj.definition({ resolveExternal: false })
24+
if (!objDef) continue
25+
26+
// $$$ARG yields argument nodes interleaved with separators, so arg nodes are at 0,2,4...
27+
const first = args[0]
28+
const second = args[2]
29+
if (!first || !second) continue
30+
31+
// Only transform legacy form redirect(url, status) where second is number
32+
if (second.is('number') && !first.is('number')) {
33+
edits.push(call.replace(`${obj.text()}.redirect(${second.text()}, ${first.text()})`))
34+
}
35+
}
36+
37+
if (edits.length === 0) return null
38+
return rootNode.commitEdits(edits)
39+
}
40+
41+
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

0 commit comments

Comments
 (0)