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
6 changes: 1 addition & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

129 changes: 53 additions & 76 deletions recipes/magic-redirect/src/workflow.ts
Original file line number Diff line number Diff line change
@@ -1,89 +1,66 @@
import type Js from '@codemod.com/jssg-types/src/langs/javascript'
import type { SgRoot } from '@codemod.com/jssg-types/src/main'
import type { Edit, SgNode, SgRoot } from '@codemod.com/jssg-types/src/main'

async function transform(root: SgRoot<Js>): Promise<string> {
const rootNode = root.root()
function getStringLiteralValue(node: SgNode<Js>): string | null {
if (!node.is('string')) return null

const fragments = node.findAll({ rule: { kind: 'string_fragment' } })
if (fragments.length !== 1) return null
return fragments[0]?.text() ?? null
}

// Helper function to find the request parameter name
function findRequestParamName(node: any): string {
let current = node
const funcKinds = new Set([
'function_declaration',
'function_expression',
'function',
'arrow_function',
'method_definition',
])

while (current) {
const parent = current.parent()
if (!parent) break

const kind = parent?.kind()
if (kind && funcKinds.has(kind)) {
const candidateFields = ['parameters', 'parameter', 'formal_parameters', 'params']
let params: any = null

for (const f of candidateFields) {
params = parent?.field(f)
if (params) break
}

if (params) {
const children = typeof params.children === 'function' ? params.children() : []
if (children && children.length > 0) {
const first = children[1]

if (first?.kind() === 'required_parameter') {
const pattern = first?.field('pattern')
if (pattern && typeof pattern.kind === 'function' && pattern.kind() === 'identifier') {
return pattern.text()
}
}
}
}
}

current = parent
}

return 'req' // default fallback
function findParentFunctionParameters(node: SgNode<Js>): SgNode<Js, 'formal_parameters'> | null {
let parent = node.parent()
while (parent) {
if (parent.is('formal_parameters')) return parent
parent = parent.parent()
}
return null
}

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

// Find all redirect and location
const nodes = rootNode.findAll({
rule: {
any: [
{
pattern: '$OBJ.redirect($ARG)',
},
{
pattern: '$OBJ.location($ARG)',
},
],
pattern: '$OBJ.$METHOD($ARG)',
},
constraints: {
METHOD: { regex: '^(redirect|location)$' },
ARG: { pattern: { context: "'back'", strictness: 'relaxed' } },
},
})

const edits = nodes.reduce((acc: any[], node: any) => {
const requestParamName = findRequestParamName(node)
const obj = node.getMatch('OBJ')
const arg = node.getMatch('ARG')

// Only transform when the argument is the literal 'back' (single or double quotes)
const argText = arg && typeof arg.text === 'function' ? arg.text() : null
if (argText !== "'back'" && argText !== '"back"' && argText !== '‘back’' && argText !== '“back”') {
return acc // skip this node, no edit
}

// Case: obj.redirect('back') or obj.location('back')
const objText = obj?.text()
const methodName = node.text().includes('.redirect(') ? 'redirect' : 'location'
acc.push(node.replace(`${objText}.${methodName}(${requestParamName}.get("Referrer") || "/")`))
return acc
}, [] as any[])

const newSource = rootNode.commitEdits(edits)
return newSource
const edits: Edit[] = []

for (const call of nodes) {
const arg = call.getMatch('ARG')
const obj = call.getMatch('OBJ')
if (!arg || !obj) continue

if (getStringLiteralValue(arg) !== 'back') continue

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

const isParameter = objDef.node.matches({
rule: { inside: { kind: 'formal_parameters', stopBy: 'end' } },
})
if (!isParameter) continue

const parameters = findParentFunctionParameters(objDef.node)
if (!parameters) continue

const firstParameter = parameters.find({ rule: { kind: 'identifier' } })
if (!firstParameter) continue

const requestName = firstParameter.text()

edits.push(arg.replace(`${requestName}.get("Referrer") || "/"`))
}

if (edits.length === 0) return null
return rootNode.commitEdits(edits)
}

export default transform
1 change: 1 addition & 0 deletions recipes/magic-redirect/workflow.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ nodes:
js-ast-grep:
js_file: src/workflow.ts
base_path: .
semantic_analysis: file
include:
- "**/*.cjs"
- "**/*.js"
Expand Down