Last active
September 11, 2023 11:00
-
-
Save mohd-akram/4374ee82b95f92362d816b0ac7a54a0c to your computer and use it in GitHub Desktop.
jscodeshift mod to convert Array.forEach to for-of loop
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** @type {import('jscodeshift').Transform} */ | |
module.exports = function (file, api) { | |
const j = api.jscodeshift; | |
const root = j(file.source); | |
return root | |
.find(j.CallExpression, { callee: { property: { name: "forEach" } } }) | |
.replaceWith((path) => { | |
const call = path.value; | |
const callee = /** @type {import('jscodeshift').MemberExpression} */ ( | |
call.callee | |
).object; | |
const func = | |
/** @type {import('jscodeshift').ArrowFunctionExpression} */ ( | |
call.arguments[0] | |
); | |
// Ignore calls that use the second argument (this) to forEach | |
// or that use the third parameter (array) in the callback function | |
if (call.arguments.length > 1 || func.params.length > 2) return call; | |
// Convert return statements to continue | |
j(func.body) | |
.find(j.ReturnStatement) | |
.filter((p) => { | |
let par = p.parentPath; | |
// If return was called in an inner function, ignore it | |
while (par.node !== func.body) { | |
if (par.node.type.includes("Function")) return false; | |
par = par.parentPath; | |
} | |
// Otherwise it's called inside the forEach and should be converted | |
return true; | |
}) | |
.replaceWith((path) => { | |
const node = path.value; | |
// If we return an expression, convert to statement and add continue | |
// If we return nothing just replace with continue | |
return node.argument | |
? [j.expressionStatement(node.argument), j.continueStatement()] | |
: j.continueStatement(); | |
}); | |
const body = func.body; | |
const newNode = j.forOfStatement( | |
j.variableDeclaration("const", [ | |
j.variableDeclarator( | |
// Add destructuring assignment when the index parameter is used | |
func.params.length > 1 | |
? j.arrayPattern([func.params[1], func.params[0]]) | |
: func.params[0] | |
), | |
]), | |
// Use Array.entries() when the index parameter is used | |
func.params.length > 1 | |
? j.callExpression( | |
j.memberExpression(callee, j.identifier("entries")), | |
[] | |
) | |
: callee, | |
// If the function body is an expression (like in an arrow function) | |
// convert it to a statement | |
body.type.endsWith("Expression") | |
? j.expressionStatement( | |
/** @type {import('ast-types/gen/kinds').ExpressionKind} */ (body) | |
) | |
: /** @type {import('jscodeshift').BlockStatement} */ (body) | |
); | |
// Wrap in braces if this is in an arrow function expression | |
return path.parentPath.node.type == "ArrowFunctionExpression" | |
? j.blockStatement([newNode]) | |
: newNode; | |
}) | |
.toSource(); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment