Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions codemods/status-send-order/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Migrate legacy `res.send(obj, status)`, `res.send(status)`, `res.json(obj, status)` and `res.jsonp(obj, status)`

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
using the `res.status(status).send(obj)`, `res.status(status).json(obj)`, and
`res.status(status).jsonp(obj)` methods respectively. The older APIs that allowed
specifying the status code as a second argument have been deprecated.

## Example

### Migrating `res.send(obj, status)`

The migration involves replacing instances of `res.send(obj, status)` with `res.status(status).send(obj)`.

```diff
app.get('/some-route', (req, res) => {
// Some logic here
- res.send(obj, status);
+ res.status(status).send(obj);
});
```

### Migrating `res.json(obj, status)`

The migration involves replacing instances of `res.json(obj, status)` with `res.status(status).json(obj)`.

```diff
app.get('/some-route', (req, res) => {
// Some logic here
- res.json(obj, status);
+ res.status(status).json(obj);
});
```
### Migrating `res.jsonp(obj, status)`

The migration involves replacing instances of `res.jsonp(obj, status)` with `res.status(status).jsonp(obj)`.

```diff
app.get('/some-route', (req, res) => {
// Some logic here
- res.jsonp(obj, status);
+ res.status(status).jsonp(obj);
});
```

### Migrating `res.send(status)`

The migration involves replacing instances of `res.send(status)` with `res.sendStatus(status)`.

```diff
app.get('/some-route', (req, res) => {
// Some logic here
- res.send(status);
+ res.sendStatus(status);
});
```

## References

- [Migration of res.send(status)](https://expressjs.com/en/guide/migrating-5.html#res.send.status)
- [Migration of res.send(obj, status)](https://expressjs.com/en/guide/migrating-5.html#res.send.body)
- [Migration of res.json(obj, status)](https://expressjs.com/en/guide/migrating-5.html#res.json)
- [Migration of res.jsonp(obj, status)](https://expressjs.com/en/guide/migrating-5.html#res.jsonp)
27 changes: 27 additions & 0 deletions codemods/status-send-order/codemod.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
schema_version: "1.0"
name: "@expressjs/status-send-order"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good name !

version: "1.0.0"
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
author: bjohansebas (Sebastian Beltran)
license: MIT
workflow: workflow.yaml
repository: "https://github.com/expressjs/codemod/tree/HEAD/codemods/status-send-order"
category: migration

targets:
languages:
- javascript
- typescript

keywords:
- transformation
- migration
- express
- send
- json
- jsonp
- status

registry:
access: public
visibility: public
22 changes: 22 additions & 0 deletions codemods/status-send-order/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "@expressjs/status-send-order",
"private": true,
"version": "1.0.0",
"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",
"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/status-send-order",
"bugs": "https://github.com/expressjs/codemod/issues"
},
"author": "bjohansebas (Sebastian Beltran)",
"license": "MIT",
"homepage": "https://github.com/expressjs/codemod/blob/main/codemods/status-send-order/README.md",
"devDependencies": {
"@codemod.com/jssg-types": "^1.3.1"
}
}
78 changes: 78 additions & 0 deletions codemods/status-send-order/src/workflow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import type Js from '@codemod.com/jssg-types/src/langs/javascript'
import type { Edit, SgRoot } from '@codemod.com/jssg-types/src/main'

async function transform(root: SgRoot<Js>): Promise<string | null> {
const rootNode = root.root()

const nodes = rootNode.findAll({
rule: {
pattern: '$OBJ.$METHOD($$$ARG)',
},
constraints: {
METHOD: { regex: '^(send|json|jsonp)$' },
},
})

if (!nodes.length) return null

const edits: Edit[] = []

for (const call of nodes) {
const obj = call.getMatch('OBJ')
const args = call.getMultipleMatches('ARG')

if (args.length === 0 || !obj) continue

const objDef = obj.definition({ resolveExternal: false })
if (!objDef) continue

const method = call.getMatch('METHOD')?.text()
if (!method) continue

// Single-argument forms: res.send(status) -> res.sendStatus(status)
if (args.length === 1) {
const a0 = args[0]
if (method === 'send' && a0.is('number')) {
edits.push(call.replace(`${obj.text()}.sendStatus(${a0.text()})`))
}
continue
}

// Two-argument forms: res.send(obj, status) -> res.status(status).send(obj)
if (args.length >= 2) {
const first = args[0]
const second = args[2]

if (!second) continue

// support both orders: (obj, status) and (status, obj)
if (first.is('number') && !second.is('number')) {
const status = first
const body = second
if (method === 'send') {
edits.push(call.replace(`${obj.text()}.status(${status.text()}).send(${body.text()})`))
} else if (method === 'json') {
edits.push(call.replace(`${obj.text()}.status(${status.text()}).json(${body.text()})`))
} else if (method === 'jsonp') {
edits.push(call.replace(`${obj.text()}.status(${status.text()}).jsonp(${body.text()})`))
}
} else if (second.is('number') && !first.is('number')) {
const status = second
const body = first
if (method === 'send') {
edits.push(call.replace(`${obj.text()}.status(${status.text()}).send(${body.text()})`))
} else if (method === 'json') {
edits.push(call.replace(`${obj.text()}.status(${status.text()}).json(${body.text()})`))
} else if (method === 'jsonp') {
edits.push(call.replace(`${obj.text()}.status(${status.text()}).jsonp(${body.text()})`))
}
}
}
}

if (!edits.length) return null

return rootNode.commitEdits(edits)
}

export default transform
71 changes: 71 additions & 0 deletions codemods/status-send-order/tests/expected/json.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import express from "express";

const app = express();

app.get("/json", function (...arg) {
const [, res] = arg
res.json();
});

app.get("/json", function (...arg) {
const [, res] = arg
res.status(200).json({ user: "Username", isValid: true });
});

app.get("/json", function (req, res) {
res.json();
});

app.get("/json", function (req, res) {
res.status(200).json({ user: "Username", isValid: true });
});

app.get("/json", function (req, response) {
response.status(200).json({ user: "Username", isValid: true });
});

app.get("/json", (req, res) => {
res.status(200).json({ user: "Username", isValid: true });
});

app.get("/json", (req, response) => {
response.status(200).json({ user: "Username", isValid: true });
});

app.get("/json", function (req, res) {
res.status(200).json({});
});

app.get("/json", function (req, response) {
response.status(200).json({});
});

app.get("/json", (req, res) => {
res.status(200).json({});
});

app.get("/json", (req, response) => {
response.status(200).json({});
});

// Still valid syntax -- START
app.get("/json", function (req, res) {
res.json(null)
res.json({ user: 'tobi' })
})

app.get("/json", function (req, response) {
response.json(null)
response.json({ user: 'tobi' })
})

app.get("/json", function (req, res) {
res.json(null)
res.json({ user: 'tobi' })
})

app.get("/json", function (req, response) {
response.json(null)
response.json({ user: 'tobi' })
})
// Still valid syntax -- END
61 changes: 61 additions & 0 deletions codemods/status-send-order/tests/expected/jsonp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import express from "express";

const app = express();

app.get("/json", function (req, res) {
res.json();
});

app.get("/jsonp", function (req, res) {
res.status(200).jsonp({ user: "Username", isValid: true });
});

app.get("/jsonp", function (req, response) {
response.status(200).jsonp({ user: "Username", isValid: true });
});

app.get("/jsonp", (req, res) => {
res.status(200).jsonp({ user: "Username", isValid: true });
});

app.get("/jsonp", (req, response) => {
response.status(200).jsonp({ user: "Username", isValid: true });
});

app.get("/jsonp", function (req, res) {
res.status(200).jsonp({});
});

app.get("/jsonp", function (req, response) {
response.status(200).jsonp({});
});

app.get("/jsonp", (req, res) => {
res.status(200).jsonp({})
});

app.get("/jsonp", (req, response) => {
response.status(200).jsonp({})
});

// Still valid syntax -- START
app.get("/jsonp", function (req, res) {
res.jsonp(null)
res.jsonp({ user: 'tobi' })
})

app.get("/jsonp", function (req, response) {
response.jsonp(null)
response.jsonp({ user: 'tobi' })
})

app.get("/jsonp", function (req, res) {
res.jsonp(null)
res.jsonp({ user: 'tobi' })
})

app.get("/jsonp", function (req, response) {
response.jsonp(null)
response.jsonp({ user: 'tobi' })
})
// Still valid syntax -- END
Loading