|
1 | 1 | 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' |
3 | 3 |
|
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 | +} |
6 | 11 |
|
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() |
51 | 17 | } |
| 18 | + return null |
| 19 | +} |
| 20 | + |
| 21 | +async function transform(root: SgRoot<Js>): Promise<string | null> { |
| 22 | + const rootNode = root.root() |
52 | 23 |
|
53 | | - // Find all redirect and location |
54 | 24 | const nodes = rootNode.findAll({ |
55 | 25 | 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' } }, |
64 | 31 | }, |
65 | 32 | }) |
66 | 33 |
|
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) |
87 | 64 | } |
88 | 65 |
|
89 | 66 | export default transform |
0 commit comments