Skip to content

Instantly share code, notes, and snippets.

@mohd-akram
Last active September 11, 2023 11:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mohd-akram/4374ee82b95f92362d816b0ac7a54a0c to your computer and use it in GitHub Desktop.
Save mohd-akram/4374ee82b95f92362d816b0ac7a54a0c to your computer and use it in GitHub Desktop.
jscodeshift mod to convert Array.forEach to for-of loop
/** @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