Skip to content

Commit ba25f2f

Browse files
feat(magic-redirect): migrate magic-redirect to codemod.com (#91)
* feat(magic-redirect): migrate magic-redirect to codemod.com * fix lint * feat(tests): update test commands and add legacy test support * feat(redirect): enhance redirect handlers with additional test cases * refactor: refactor the magic-redirect-codemod (#92) * refactor: refactor the express codemod * fix lint Signed-off-by: Sebastian Beltran <[email protected]> --------- Signed-off-by: Sebastian Beltran <[email protected]> Co-authored-by: Sebastian Beltran <[email protected]> * docs: improve documentation * chore: rename folder to codemods * rename codemod * fix: update workspace paths to use codemods directory * rename folder --------- Signed-off-by: Sebastian Beltran <[email protected]> Co-authored-by: Mohamad Mohebifar <[email protected]>
1 parent 51a7c65 commit ba25f2f

File tree

16 files changed

+384
-10
lines changed

16 files changed

+384
-10
lines changed

.github/workflows/ci.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,12 @@ jobs:
7171
echo "Node.js version: $(node -v)"
7272
echo "NPM version: $(npm -v)"
7373
74-
- name: Run tests
74+
- name: Run tests-legacy cli
7575
shell: bash
7676
run: |
77-
npm run test:ci
77+
npm run test-legacy:ci
78+
79+
- name: Run test
80+
shell: bash
81+
run: |
82+
npm run test

.github/workflows/publish.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ jobs:
5959
VERSION="${TAG%@*}" # Everything before @
6060
VERSION="${VERSION#v}" # Remove v prefix
6161
CODEMOD_NAME="${TAG#*@}" # Everything after @
62-
CODEMOD_PATH="recipes/$CODEMOD_NAME"
62+
CODEMOD_PATH="codemods/$CODEMOD_NAME"
6363
6464
# Set outputs
6565
echo "version=$VERSION" >> $GITHUB_OUTPUT
@@ -72,8 +72,8 @@ jobs:
7272
run: |
7373
if [[ ! -d "$CODEMOD_PATH" ]]; then
7474
echo "❌ Codemod directory not found: $CODEMOD_PATH"
75-
echo "Available directories in recipes/:"
76-
ls -lah recipes/ || echo "No recipes directory found"
75+
echo "Available directories in codemods/:"
76+
ls -lah codemods/ || echo "No codemods directory found"
7777
exit 1
7878
fi
7979

biome.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"useIgnoreFile": true
77
},
88
"files": {
9-
"ignore": ["__testfixtures__"]
9+
"ignore": ["__testfixtures__", "tests"]
1010
},
1111
"linter": {
1212
"enabled": true,
@@ -15,6 +15,9 @@
1515
"correctness": {
1616
"noUnusedImports": "error",
1717
"useExhaustiveDependencies": "off"
18+
},
19+
"suspicious": {
20+
"noExplicitAny": "off"
1821
}
1922
}
2023
},
File renamed without changes.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Migrate legacy `res.redirect('back')` and `res.location('back')`
2+
3+
Migrates usage of the legacy APIs `res.redirect('back')` and `res.location('back')`
4+
to use the recommended approach of accessing the `Referer` header directly from
5+
the request object. Versions of Express before 5 allowed the use of the string
6+
"back" as a shortcut to redirect to the referring page, but this has been
7+
deprecated.
8+
9+
## Example
10+
11+
### Migrating `res.redirect('back')`
12+
13+
The migration involves replacing instances of `res.redirect('back')` with `res.redirect(req.get('Referer') || '/')`.
14+
15+
```diff
16+
app.get('/some-route', (req, res) => {
17+
// Some logic here
18+
- res.redirect('back');
19+
+ res.redirect(req.get('Referer') || '/');
20+
});
21+
```
22+
23+
### Migrating `res.location('back')`
24+
25+
The migration involves replacing instances of `res.location('back')` with `res.location(req.get('Referer') || '/')`.
26+
27+
```diff
28+
app.get('/some-route', (req, res) => {
29+
// Some logic here
30+
- res.location('back');
31+
+ res.location(req.get('Referer') || '/');
32+
});
33+
```
34+
35+
## References
36+
37+
- [Migration of res.redirect('back') and res.location('back')](https://expressjs.com/en/guide/migrating-5.html#magic-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/back-redirect-deprecated"
3+
version: "1.0.0"
4+
description: Migrates usage of the legacy APIs `res.redirect('back')` and `res.location('back')` to the current recommended approaches
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/back-redirect-deprecated",
3+
"private": true,
4+
"version": "1.0.0",
5+
"description": "Migrates usage of the legacy APIs `res.redirect('back')` and `res.location('back')`.",
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/back-redirect-deprecated",
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/back-redirect-deprecated/README.md",
19+
"devDependencies": {
20+
"@codemod.com/jssg-types": "^1.3.1"
21+
}
22+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import type Js from '@codemod.com/jssg-types/src/langs/javascript'
2+
import type { Edit, SgNode, SgRoot } from '@codemod.com/jssg-types/src/main'
3+
4+
function getStringLiteralValue(node: SgNode<Js>): string | null {
5+
if (!node.is('string')) return null
6+
7+
const fragments = node.findAll({ rule: { kind: 'string_fragment' } })
8+
if (fragments.length !== 1) return null
9+
return fragments[0]?.text() ?? null
10+
}
11+
12+
function findParentFunctionParameters(node: SgNode<Js>): SgNode<Js, 'formal_parameters'> | null {
13+
let parent = node.parent()
14+
while (parent) {
15+
if (parent.is('formal_parameters')) return parent
16+
parent = parent.parent()
17+
}
18+
return null
19+
}
20+
21+
async function transform(root: SgRoot<Js>): Promise<string | null> {
22+
const rootNode = root.root()
23+
24+
const nodes = rootNode.findAll({
25+
rule: {
26+
pattern: '$OBJ.$METHOD($ARG)',
27+
},
28+
constraints: {
29+
METHOD: { regex: '^(redirect|location)$' },
30+
ARG: { pattern: { context: "'back'", strictness: 'relaxed' } },
31+
},
32+
})
33+
34+
const edits: Edit[] = []
35+
36+
for (const call of nodes) {
37+
const arg = call.getMatch('ARG')
38+
const obj = call.getMatch('OBJ')
39+
if (!arg || !obj) continue
40+
41+
if (getStringLiteralValue(arg) !== 'back') continue
42+
43+
const objDef = obj.definition({ resolveExternal: false })
44+
if (!objDef) continue
45+
46+
const isParameter = objDef.node.matches({
47+
rule: { inside: { kind: 'formal_parameters', stopBy: 'end' } },
48+
})
49+
if (!isParameter) continue
50+
51+
const parameters = findParentFunctionParameters(objDef.node)
52+
if (!parameters) continue
53+
54+
const firstParameter = parameters.find({ rule: { kind: 'identifier' } })
55+
if (!firstParameter) continue
56+
57+
const requestName = firstParameter.text()
58+
59+
edits.push(arg.replace(`${requestName}.get("Referrer") || "/"`))
60+
}
61+
62+
if (edits.length === 0) return null
63+
return rootNode.commitEdits(edits)
64+
}
65+
66+
export default transform
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import express from "express";
2+
import { location } from "somelibrary";
3+
4+
const app = express();
5+
6+
app.get("/", function (req, res) {
7+
res.location(req.get("Referrer") || "/");
8+
});
9+
app.get("/", (req, res) => {
10+
res.location(req.get("Referrer") || "/");
11+
});
12+
app.get("/", (req, res) => {
13+
res.location("testing");
14+
});
15+
app.get("/", (req, res) => {
16+
res.location();
17+
});
18+
app.get("/articles", function (request, response) {
19+
response.location(request.get("Referrer") || "/");
20+
});
21+
app.get("/articles", function (request, response) {
22+
response.location("testing");
23+
});
24+
app.get("/articles", (request, response) => {
25+
response.location(request.get("Referrer") || "/");
26+
});
27+
app.get("/articles", function (_req, _res) {
28+
location("back");
29+
});
30+
31+
export function handleLocation(req, res) {
32+
res.location(req.get("Referrer") || "/");
33+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import express from "express";
2+
import { redirect } from "somelibrary";
3+
4+
const app = express();
5+
6+
app.get("/", function (req, res) {
7+
res.redirect(req.get("Referrer") || "/");
8+
});
9+
app.get("/", (req, res) => {
10+
res.redirect(req.get("Referrer") || "/");
11+
});
12+
app.get("/", (req, res) => {
13+
res.redirect("testing");
14+
});
15+
app.get("/", (req, res) => {
16+
res.redirect();
17+
});
18+
app.get("/articles", function (request, response) {
19+
response.redirect(request.get("Referrer") || "/");
20+
});
21+
app.get("/articles", (request, response) => {
22+
response.redirect(request.get("Referrer") || "/");
23+
});
24+
app.get("/articles", function (request, response) {
25+
response.redirect("testing");
26+
});
27+
app.get("/articles", function (_req, _res) {
28+
redirect("back");
29+
});
30+
31+
export function handler(requests, response) {
32+
response.redirect(requests.get("Referrer") || "/");
33+
}
34+
35+
export function handleRedirect(req: any) {
36+
req.redirect(req.get("Referrer") || "/");
37+
}
38+
39+
export function handlerWith(req: any, res: any) {
40+
res.redirect(req.get("Referrer") || "/");
41+
}

0 commit comments

Comments
 (0)