From f6c6bb4b12eff6ad704d6571deacf25bf9d41d25 Mon Sep 17 00:00:00 2001 From: Mohamad Mohebifar Date: Sun, 14 Dec 2025 16:31:46 -0800 Subject: [PATCH 1/2] refactor: refactor the express codemod --- package-lock.json | 6 +- recipes/magic-redirect/src/workflow.ts | 119 ++++++++++--------------- recipes/magic-redirect/workflow.yaml | 1 + 3 files changed, 50 insertions(+), 76 deletions(-) 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..7a89e10 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 - // 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', - ]) + const fragments = node.findAll({ rule: { kind: 'string_fragment' } }) + if (fragments.length !== 1) return null + return fragments[0]?.text() ?? null +} - while (current) { - const parent = current.parent() - if (!parent) break +function findParentFunctionParameters(node: SgNode): SgNode | null { + let parent = node.parent() + while (parent) { + if (parent.is('formal_parameters')) return parent + parent = parent.parent() + } + return null +} - const kind = parent?.kind() - if (kind && funcKinds.has(kind)) { - const candidateFields = ['parameters', 'parameter', 'formal_parameters', 'params'] - let params: any = null +async function transform(root: SgRoot): Promise { + const rootNode = root.root() - for (const f of candidateFields) { - params = parent?.field(f) - if (params) break - } + const nodes = rootNode.findAll({ + rule: { + pattern: '$OBJ.$METHOD($ARG)', + }, + constraints: { + METHOD: { regex: '^(redirect|location)$' }, + ARG: { pattern: { context: "'back'", strictness: "relaxed" } }, + }, + }) - if (params) { - const children = typeof params.children === 'function' ? params.children() : [] - if (children && children.length > 0) { - const first = children[1] + const edits: Edit[] = [] - if (first?.kind() === 'required_parameter') { - const pattern = first?.field('pattern') - if (pattern && typeof pattern.kind === 'function' && pattern.kind() === 'identifier') { - return pattern.text() - } - } - } - } - } + for (const call of nodes) { + const arg = call.getMatch('ARG') + const obj = call.getMatch('OBJ') + if (!arg || !obj) continue - current = parent - } + if (getStringLiteralValue(arg) !== 'back') continue - return 'req' // default fallback - } + const objDef = obj.definition({ resolveExternal: false }) + if (!objDef) continue + + const isParameter = objDef.node.matches({ + rule: { inside: { kind: "formal_parameters", stopBy: "end" } }, + }) + if (!isParameter) continue - // Find all redirect and location - const nodes = rootNode.findAll({ - rule: { - any: [ - { - pattern: '$OBJ.redirect($ARG)', - }, - { - pattern: '$OBJ.location($ARG)', - }, - ], - }, - }) + const parameters = findParentFunctionParameters(objDef.node) + if (!parameters) continue - const edits = nodes.reduce((acc: any[], node: any) => { - const requestParamName = findRequestParamName(node) - const obj = node.getMatch('OBJ') - const arg = node.getMatch('ARG') + const firstParameter = parameters.find({ rule: { kind: "identifier" } }) + if (!firstParameter) continue - // 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 - } + const requestName = firstParameter.text() - // 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[]) + edits.push(arg.replace(`${requestName}.get("Referrer") || "/"`)) + } - const newSource = rootNode.commitEdits(edits) - return newSource + 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" From 851f3f1419ce4cfd48c0d7b4a58fd813032cce68 Mon Sep 17 00:00:00 2001 From: Sebastian Beltran Date: Sun, 14 Dec 2025 19:51:46 -0500 Subject: [PATCH 2/2] fix lint Signed-off-by: Sebastian Beltran --- recipes/magic-redirect/src/workflow.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/recipes/magic-redirect/src/workflow.ts b/recipes/magic-redirect/src/workflow.ts index 7a89e10..1364b5c 100644 --- a/recipes/magic-redirect/src/workflow.ts +++ b/recipes/magic-redirect/src/workflow.ts @@ -9,7 +9,7 @@ function getStringLiteralValue(node: SgNode): string | null { return fragments[0]?.text() ?? null } -function findParentFunctionParameters(node: SgNode): SgNode | null { +function findParentFunctionParameters(node: SgNode): SgNode | null { let parent = node.parent() while (parent) { if (parent.is('formal_parameters')) return parent @@ -27,7 +27,7 @@ async function transform(root: SgRoot): Promise { }, constraints: { METHOD: { regex: '^(redirect|location)$' }, - ARG: { pattern: { context: "'back'", strictness: "relaxed" } }, + ARG: { pattern: { context: "'back'", strictness: 'relaxed' } }, }, }) @@ -42,16 +42,16 @@ async function transform(root: SgRoot): Promise { const objDef = obj.definition({ resolveExternal: false }) if (!objDef) continue - + const isParameter = objDef.node.matches({ - rule: { inside: { kind: "formal_parameters", stopBy: "end" } }, + 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" } }) + const firstParameter = parameters.find({ rule: { kind: 'identifier' } }) if (!firstParameter) continue const requestName = firstParameter.text()