Skip to content

Commit 5856cfd

Browse files
authored
feat(status-send-order): migrate logic of v4-deprecate-signatures to ast-grep (#97)
* feat(status-send-order): migrate logic of v4-deprecate-signatures to ast-grep * fix(package): update repository directory and homepage for status-send-order * Update codemods/status-send-order/codemod.yaml Signed-off-by: Sebastian Beltran <bjohansebas@gmail.com> * refactor(workflow): simplify early return conditions for nodes and edits --------- Signed-off-by: Sebastian Beltran <bjohansebas@gmail.com>
1 parent 8e20937 commit 5856cfd

File tree

12 files changed

+647
-4
lines changed

12 files changed

+647
-4
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Migrate legacy `res.send(obj, status)`, `res.send(status)`, `res.json(obj, status)` and `res.jsonp(obj, status)`
2+
3+
Migrates usage of the legacy APIs `res.send(obj, status)`, `res.json(obj, status)`, and `res.jsonp(obj, status)` to use the recommended approach of specifying the status code
4+
using the `res.status(status).send(obj)`, `res.status(status).json(obj)`, and
5+
`res.status(status).jsonp(obj)` methods respectively. The older APIs that allowed
6+
specifying the status code as a second argument have been deprecated.
7+
8+
## Example
9+
10+
### Migrating `res.send(obj, status)`
11+
12+
The migration involves replacing instances of `res.send(obj, status)` with `res.status(status).send(obj)`.
13+
14+
```diff
15+
app.get('/some-route', (req, res) => {
16+
// Some logic here
17+
- res.send(obj, status);
18+
+ res.status(status).send(obj);
19+
});
20+
```
21+
22+
### Migrating `res.json(obj, status)`
23+
24+
The migration involves replacing instances of `res.json(obj, status)` with `res.status(status).json(obj)`.
25+
26+
```diff
27+
app.get('/some-route', (req, res) => {
28+
// Some logic here
29+
- res.json(obj, status);
30+
+ res.status(status).json(obj);
31+
});
32+
```
33+
### Migrating `res.jsonp(obj, status)`
34+
35+
The migration involves replacing instances of `res.jsonp(obj, status)` with `res.status(status).jsonp(obj)`.
36+
37+
```diff
38+
app.get('/some-route', (req, res) => {
39+
// Some logic here
40+
- res.jsonp(obj, status);
41+
+ res.status(status).jsonp(obj);
42+
});
43+
```
44+
45+
### Migrating `res.send(status)`
46+
47+
The migration involves replacing instances of `res.send(status)` with `res.sendStatus(status)`.
48+
49+
```diff
50+
app.get('/some-route', (req, res) => {
51+
// Some logic here
52+
- res.send(status);
53+
+ res.sendStatus(status);
54+
});
55+
```
56+
57+
## References
58+
59+
- [Migration of res.send(status)](https://expressjs.com/en/guide/migrating-5.html#res.send.status)
60+
- [Migration of res.send(obj, status)](https://expressjs.com/en/guide/migrating-5.html#res.send.body)
61+
- [Migration of res.json(obj, status)](https://expressjs.com/en/guide/migrating-5.html#res.json)
62+
- [Migration of res.jsonp(obj, status)](https://expressjs.com/en/guide/migrating-5.html#res.jsonp)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
schema_version: "1.0"
2+
name: "@expressjs/status-send-order"
3+
version: "1.0.0"
4+
description: Migrates usage of the legacy APIs `res.send(status)`, `res.send(obj, status)`, `res.json(obj, status)` and `res.jsonp(obj, status)` to the current recommended approaches
5+
author: bjohansebas (Sebastian Beltran)
6+
license: MIT
7+
workflow: workflow.yaml
8+
repository: "https://github.com/expressjs/codemod/tree/HEAD/codemods/status-send-order"
9+
category: migration
10+
11+
targets:
12+
languages:
13+
- javascript
14+
- typescript
15+
16+
keywords:
17+
- transformation
18+
- migration
19+
- express
20+
- send
21+
- json
22+
- jsonp
23+
- status
24+
25+
registry:
26+
access: public
27+
visibility: public
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"name": "@expressjs/status-send-order",
3+
"private": true,
4+
"version": "1.0.0",
5+
"description": "Migrates usage of the legacy APIs `res.send(status)`, `res.send(obj, status)`, `res.json(obj, status)` and `res.jsonp(obj, status)` to the current recommended approaches",
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/status-send-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/status-send-order/README.md",
19+
"devDependencies": {
20+
"@codemod.com/jssg-types": "^1.3.1"
21+
}
22+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
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: '^(send|json|jsonp)$' },
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+
24+
if (args.length === 0 || !obj) continue
25+
26+
const objDef = obj.definition({ resolveExternal: false })
27+
if (!objDef) continue
28+
29+
const method = call.getMatch('METHOD')?.text()
30+
if (!method) continue
31+
32+
// Single-argument forms: res.send(status) -> res.sendStatus(status)
33+
if (args.length === 1) {
34+
const a0 = args[0]
35+
if (method === 'send' && a0.is('number')) {
36+
edits.push(call.replace(`${obj.text()}.sendStatus(${a0.text()})`))
37+
}
38+
continue
39+
}
40+
41+
// Two-argument forms: res.send(obj, status) -> res.status(status).send(obj)
42+
if (args.length >= 2) {
43+
const first = args[0]
44+
const second = args[2]
45+
46+
if (!second) continue
47+
48+
// support both orders: (obj, status) and (status, obj)
49+
if (first.is('number') && !second.is('number')) {
50+
const status = first
51+
const body = second
52+
if (method === 'send') {
53+
edits.push(call.replace(`${obj.text()}.status(${status.text()}).send(${body.text()})`))
54+
} else if (method === 'json') {
55+
edits.push(call.replace(`${obj.text()}.status(${status.text()}).json(${body.text()})`))
56+
} else if (method === 'jsonp') {
57+
edits.push(call.replace(`${obj.text()}.status(${status.text()}).jsonp(${body.text()})`))
58+
}
59+
} else if (second.is('number') && !first.is('number')) {
60+
const status = second
61+
const body = first
62+
if (method === 'send') {
63+
edits.push(call.replace(`${obj.text()}.status(${status.text()}).send(${body.text()})`))
64+
} else if (method === 'json') {
65+
edits.push(call.replace(`${obj.text()}.status(${status.text()}).json(${body.text()})`))
66+
} else if (method === 'jsonp') {
67+
edits.push(call.replace(`${obj.text()}.status(${status.text()}).jsonp(${body.text()})`))
68+
}
69+
}
70+
}
71+
}
72+
73+
if (!edits.length) return null
74+
75+
return rootNode.commitEdits(edits)
76+
}
77+
78+
export default transform
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import express from "express";
2+
3+
const app = express();
4+
5+
app.get("/json", function (...arg) {
6+
const [, res] = arg
7+
res.json();
8+
});
9+
10+
app.get("/json", function (...arg) {
11+
const [, res] = arg
12+
res.status(200).json({ user: "Username", isValid: true });
13+
});
14+
15+
app.get("/json", function (req, res) {
16+
res.json();
17+
});
18+
19+
app.get("/json", function (req, res) {
20+
res.status(200).json({ user: "Username", isValid: true });
21+
});
22+
23+
app.get("/json", function (req, response) {
24+
response.status(200).json({ user: "Username", isValid: true });
25+
});
26+
27+
app.get("/json", (req, res) => {
28+
res.status(200).json({ user: "Username", isValid: true });
29+
});
30+
31+
app.get("/json", (req, response) => {
32+
response.status(200).json({ user: "Username", isValid: true });
33+
});
34+
35+
app.get("/json", function (req, res) {
36+
res.status(200).json({});
37+
});
38+
39+
app.get("/json", function (req, response) {
40+
response.status(200).json({});
41+
});
42+
43+
app.get("/json", (req, res) => {
44+
res.status(200).json({});
45+
});
46+
47+
app.get("/json", (req, response) => {
48+
response.status(200).json({});
49+
});
50+
51+
// Still valid syntax -- START
52+
app.get("/json", function (req, res) {
53+
res.json(null)
54+
res.json({ user: 'tobi' })
55+
})
56+
57+
app.get("/json", function (req, response) {
58+
response.json(null)
59+
response.json({ user: 'tobi' })
60+
})
61+
62+
app.get("/json", function (req, res) {
63+
res.json(null)
64+
res.json({ user: 'tobi' })
65+
})
66+
67+
app.get("/json", function (req, response) {
68+
response.json(null)
69+
response.json({ user: 'tobi' })
70+
})
71+
// Still valid syntax -- END
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import express from "express";
2+
3+
const app = express();
4+
5+
app.get("/json", function (req, res) {
6+
res.json();
7+
});
8+
9+
app.get("/jsonp", function (req, res) {
10+
res.status(200).jsonp({ user: "Username", isValid: true });
11+
});
12+
13+
app.get("/jsonp", function (req, response) {
14+
response.status(200).jsonp({ user: "Username", isValid: true });
15+
});
16+
17+
app.get("/jsonp", (req, res) => {
18+
res.status(200).jsonp({ user: "Username", isValid: true });
19+
});
20+
21+
app.get("/jsonp", (req, response) => {
22+
response.status(200).jsonp({ user: "Username", isValid: true });
23+
});
24+
25+
app.get("/jsonp", function (req, res) {
26+
res.status(200).jsonp({});
27+
});
28+
29+
app.get("/jsonp", function (req, response) {
30+
response.status(200).jsonp({});
31+
});
32+
33+
app.get("/jsonp", (req, res) => {
34+
res.status(200).jsonp({})
35+
});
36+
37+
app.get("/jsonp", (req, response) => {
38+
response.status(200).jsonp({})
39+
});
40+
41+
// Still valid syntax -- START
42+
app.get("/jsonp", function (req, res) {
43+
res.jsonp(null)
44+
res.jsonp({ user: 'tobi' })
45+
})
46+
47+
app.get("/jsonp", function (req, response) {
48+
response.jsonp(null)
49+
response.jsonp({ user: 'tobi' })
50+
})
51+
52+
app.get("/jsonp", function (req, res) {
53+
res.jsonp(null)
54+
res.jsonp({ user: 'tobi' })
55+
})
56+
57+
app.get("/jsonp", function (req, response) {
58+
response.jsonp(null)
59+
response.jsonp({ user: 'tobi' })
60+
})
61+
// Still valid syntax -- END

0 commit comments

Comments
 (0)