diff --git a/package-lock.json b/package-lock.json index d74b3c9..c125cc8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -80,7 +80,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", "license": "MIT", - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.0", @@ -1829,7 +1828,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001669", "electron-to-chromium": "^1.5.41", @@ -2943,7 +2941,6 @@ "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -4643,7 +4640,6 @@ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -4832,7 +4828,7 @@ "version": "1.0.0", "license": "MIT", "devDependencies": { - "@codemod.com/jssg-types": "^1.3.0" + "@codemod.com/jssg-types": "^1.3.1" } } } diff --git a/recipes/magic-redirect/src/workflow.ts b/recipes/magic-redirect/src/workflow.ts index 4a71241..1364b5c 100644 --- a/recipes/magic-redirect/src/workflow.ts +++ b/recipes/magic-redirect/src/workflow.ts @@ -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): Promise { - const rootNode = root.root() +function getStringLiteralValue(node: SgNode): 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): SgNode | null { + let parent = node.parent() + while (parent) { + if (parent.is('formal_parameters')) return parent + parent = parent.parent() } + return null +} + +async function transform(root: SgRoot): Promise { + 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 diff --git a/recipes/magic-redirect/workflow.yaml b/recipes/magic-redirect/workflow.yaml index ddecea1..15bb5c2 100644 --- a/recipes/magic-redirect/workflow.yaml +++ b/recipes/magic-redirect/workflow.yaml @@ -13,6 +13,7 @@ nodes: js-ast-grep: js_file: src/workflow.ts base_path: . + semantic_analysis: file include: - "**/*.cjs" - "**/*.js"