Skip to content

Commit c9603ca

Browse files
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]>
1 parent 716d99e commit c9603ca

File tree

3 files changed

+55
-81
lines changed

3 files changed

+55
-81
lines changed

package-lock.json

Lines changed: 1 addition & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 53 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,66 @@
11
import type Js from '@codemod.com/jssg-types/src/langs/javascript'
2-
import type { SgRoot } from '@codemod.com/jssg-types/src/main'
2+
import type { Edit, SgNode, SgRoot } from '@codemod.com/jssg-types/src/main'
33

4-
async function transform(root: SgRoot<Js>): Promise<string> {
5-
const rootNode = root.root()
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+
}
611

7-
// Helper function to find the request parameter name
8-
function findRequestParamName(node: any): string {
9-
let current = node
10-
const funcKinds = new Set([
11-
'function_declaration',
12-
'function_expression',
13-
'function',
14-
'arrow_function',
15-
'method_definition',
16-
])
17-
18-
while (current) {
19-
const parent = current.parent()
20-
if (!parent) break
21-
22-
const kind = parent?.kind()
23-
if (kind && funcKinds.has(kind)) {
24-
const candidateFields = ['parameters', 'parameter', 'formal_parameters', 'params']
25-
let params: any = null
26-
27-
for (const f of candidateFields) {
28-
params = parent?.field(f)
29-
if (params) break
30-
}
31-
32-
if (params) {
33-
const children = typeof params.children === 'function' ? params.children() : []
34-
if (children && children.length > 0) {
35-
const first = children[1]
36-
37-
if (first?.kind() === 'required_parameter') {
38-
const pattern = first?.field('pattern')
39-
if (pattern && typeof pattern.kind === 'function' && pattern.kind() === 'identifier') {
40-
return pattern.text()
41-
}
42-
}
43-
}
44-
}
45-
}
46-
47-
current = parent
48-
}
49-
50-
return 'req' // default fallback
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()
5117
}
18+
return null
19+
}
20+
21+
async function transform(root: SgRoot<Js>): Promise<string | null> {
22+
const rootNode = root.root()
5223

53-
// Find all redirect and location
5424
const nodes = rootNode.findAll({
5525
rule: {
56-
any: [
57-
{
58-
pattern: '$OBJ.redirect($ARG)',
59-
},
60-
{
61-
pattern: '$OBJ.location($ARG)',
62-
},
63-
],
26+
pattern: '$OBJ.$METHOD($ARG)',
27+
},
28+
constraints: {
29+
METHOD: { regex: '^(redirect|location)$' },
30+
ARG: { pattern: { context: "'back'", strictness: 'relaxed' } },
6431
},
6532
})
6633

67-
const edits = nodes.reduce((acc: any[], node: any) => {
68-
const requestParamName = findRequestParamName(node)
69-
const obj = node.getMatch('OBJ')
70-
const arg = node.getMatch('ARG')
71-
72-
// Only transform when the argument is the literal 'back' (single or double quotes)
73-
const argText = arg && typeof arg.text === 'function' ? arg.text() : null
74-
if (argText !== "'back'" && argText !== '"back"' && argText !== '‘back’' && argText !== '“back”') {
75-
return acc // skip this node, no edit
76-
}
77-
78-
// Case: obj.redirect('back') or obj.location('back')
79-
const objText = obj?.text()
80-
const methodName = node.text().includes('.redirect(') ? 'redirect' : 'location'
81-
acc.push(node.replace(`${objText}.${methodName}(${requestParamName}.get("Referrer") || "/")`))
82-
return acc
83-
}, [] as any[])
84-
85-
const newSource = rootNode.commitEdits(edits)
86-
return newSource
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)
8764
}
8865

8966
export default transform

recipes/magic-redirect/workflow.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ nodes:
1313
js-ast-grep:
1414
js_file: src/workflow.ts
1515
base_path: .
16+
semantic_analysis: file
1617
include:
1718
- "**/*.cjs"
1819
- "**/*.js"

0 commit comments

Comments
 (0)