Skip to content

Commit

Permalink
chore: can't remember what these are but i better save them
Browse files Browse the repository at this point in the history
  • Loading branch information
harsilspatel committed Sep 24, 2021
1 parent 0f33b4a commit d032ed6
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 247 deletions.
269 changes: 37 additions & 232 deletions async-waterfall.js
Original file line number Diff line number Diff line change
@@ -1,258 +1,63 @@
module.exports = (file, { jscodeshift: j }) {
const { isFnNode, hasCallback, awaitFn, filterImmediateFns, removeWrapperFn, convertParentFnAsync, createBlockStatement, removeWrappingParenthesis } = getFns(j);

return j(file.source)
.find(j.CallExpression, {
callee: {
object: {
name: "async"
},
property: { name: "waterfall" }
}
})
.forEach((wf) => {
const wfFns = j(wf.node.arguments[0]);
wfFns
.find(j.CallExpression)
.filter(hasCallback)
.forEach(awaitFn({ tryCatch: true }));
wfFns.find(j.FunctionExpression).filter(filterImmediateFns).replaceWith(removeWrapperFn);
wfFns.find(j.ArrowFunctionExpression).filter(filterImmediateFns).replaceWith(removeWrapperFn);
j(wf)
// convert async.waterfall's 2nd argument to await
.forEach(awaitFn({ tryCatch: false }))
.replaceWith((p) => {
// remove async.waterfall wrapper
const wfBody = p.node.arguments;
const asyncWaterfallFns = wfBody.shift();

// if it's an assignment then we get nodes from `asyncWaterfallFns.expression.right` if not it will be variable declaration
const fns = asyncWaterfallFns.elements || (asyncWaterfallFns.expression ? asyncWaterfallFns.expression.right : asyncWaterfallFns.declarations[0].init).argument.arguments[0].elements;
const x = createBlockStatement(fns.concat(wfBody));
return x
});

// remove the async.waterfall() contents from block statement
// and insert it in parent's body
// removeWrappingParenthesis(wf, wf.node.body);
})
.toSource();
}

// hoisting and definiing fns at the end so i don't have to keep scrolling down
function getFns(j) {
const isFnNode = (n) => ["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"].includes(n.type);
const hasCallback = (p) => {
const { node } = p;

// if it is async.waterfall(), skip them as we run a different transformer
if (isAsyncLibraryNode(node)) return false;

let _hasCallback;
try {
const argsLen = node.arguments.length;
const lastArg = node.arguments[argsLen - 1];
_hasCallback = isFnNode(lastArg) && ERROR_KEYWORDS.includes(lastArg.params[0].name);
} catch (e) {
_hasCallback = false;
}

let isThenCatchPromise;
try {
isThenCatchPromise = ["then", "catch"].includes(node.callee.property.name);
} catch (e) {
isThenCatchPromise = false;
}

let isEventHandler;
module.exports = (file, { jscodeshift: j }) => {
const isAsyncLib = (node) => {
try {
isEventHandler = node.callee.property.name === "on";
return node.callee.object.name.toLowerCase() === 'async' && node.callee.property.name.toLowerCase() === 'waterfall';
} catch (e) {
isEventHandler = false;
}

const isWithinAsync = j(p).closest(j.CallExpression, isAsyncLibraryNode).size() > 0;

// we do not transform all nodes within async.waterfall()
// we also do not want to transform then-catch promises
return _hasCallback && !isWithinAsync && !isThenCatchPromise && !isEventHandler;
};

const createBlockStatement = (contents) => {
// when j.blockStatement(contents) won't work
const block = j.blockStatement([]);
block.body = contents || []; // call me a hackerman
return block;
};

const convertParentFnAsync = (p) => {
let parent = p.parent;
while (parent) {
if (isFnNode(parent.node)) {
parent.node.async = true;
break;
}
parent = parent.parent;
return false;
}
return parent;
};

const removeWrappingParenthesis = (p, replacementContents) => {
const parentNode = p.parent.node;
const grandparentNodeBody = p.parent.parent.node.body;
const parentNodePosition = grandparentNodeBody.indexOf(parentNode);

// remove parentNode and replace it with `removeWrappingParenthesis`
grandparentNodeBody.splice(parentNodePosition, 1, ...replacementContents);
};

const addComment = (node, comment) => {
const comments = node.comments || [];
comments.push(j.commentLine(" TODO(codemods): No error clause found", true, false));
node.comments = comments;
const addComments = (node, comments = []) => {
const existingComments = node.comments || [];
existingComments.push(...comments.filter(Boolean).map((comment) => j.commentLine(comment, true, false)));
node.comments = existingComments;
};

const awaitFn = ({tryCatch}) => (p) => {
// ensure parent function is converted to an `await` fn
const parent = convertParentFnAsync(p);

// the CallExpression's arguments
const argLen = p.node.arguments.length;
// popping the last arg as its the callback fn
const callbackFn = p.node.arguments.pop();
const cbParams = callbackFn.params;
const removeAsyncLib = (p) => {
const { node } = p;

// get the error identifier
const firstParam = cbParams.shift();
const returnValuesCount = cbParams.length;
const hasReturnValue = !!returnValuesCount;
if (!(node.arguments.length === 2)) throw new Error('There are more args in async.waterfall()');
const tryFunctions = node.arguments[0].elements.map((fn) => fn.body.body).flat();

// check if there is an if statement checking for error
const callbackFn = node.arguments[1];
const firstParam = callbackFn.params[0];
const firstCbExpr = callbackFn.body.body[0];
const hasCatchClause = firstCbExpr.type === "IfStatement" && firstCbExpr.test.type === "Identifier" && firstCbExpr.test.name === firstParam.name;
// if there is, then all it's contents go to the body of `catch` clause
const catchBody = hasCatchClause ? firstCbExpr.consequent : j.blockStatement([]);

// create await expression
const awaitExpression = j.awaitExpression(j.callExpression(p.node.callee, p.node.arguments));
const expressionStatement = j.expressionStatement(awaitExpression);

// if more than 1 values then destructure variables from an
const variableDeclaratorId = returnValuesCount > 1 ? j.arrayPattern(cbParams) : cbParams[0];
const hasCatchClause =
firstCbExpr.type === 'IfStatement' && firstCbExpr.test.type === 'Identifier' && firstCbExpr.test.name === firstParam.name;

// if it is returning a value declare the variable(s)
// if it has a try-catch block, declare the variable outside the blocks and assign the return value
const variablesAssignment = j.expressionStatement(j.assignmentExpression("=", variableDeclaratorId, awaitExpression));
const variablesDeclaration = j.variableDeclaration("let", [j.variableDeclarator(variableDeclaratorId, awaitExpression)]);
const awaitWrapperExpr = hasReturnValue ? (hasCatchClause ? variablesAssignment : variablesDeclaration) : expressionStatement;
// if there is, then all it's contents go to the body of `catch` clause
const catchBody = hasCatchClause
? firstCbExpr.consequent.type === 'BlockStatement'
? firstCbExpr.consequent
: j.blockStatement([firstCbExpr.consequent])
: j.blockStatement([]);

const catchClause = j.catchClause(firstParam, null, catchBody);
let afterAwaitExprs = [];

// if the catch clause is found then everything inside `else` will go below await statement
if (hasCatchClause) {
// if the alternate an else-if then we have to concat `firstCbExpr.alternate` else it will be a blockStatement so concat `firstCbExpr.alternate.body`
afterAwaitExprs = afterAwaitExprs.concat(firstCbExpr.alternate ? firstCbExpr.alternate.body || firstCbExpr.alternate || [] : []);
afterAwaitExprs = afterAwaitExprs.concat(
firstCbExpr.alternate ? firstCbExpr.alternate.body || [firstCbExpr.alternate] : [],
);
// remove the `if-else`
callbackFn.body.body.shift();
} else {
addComment(awaitWrapperExpr, " TODO(codemods): No error clause found");
addComments(
catchClause,
(firstCbExpr.comments || []).map((c) => c.value),
);
}

// everything after `if-else` also goes after await call
afterAwaitExprs = afterAwaitExprs.concat(callbackFn.body.body);
const tryContents = createBlockStatement([awaitWrapperExpr, ...afterAwaitExprs]);
const tryStatement = j.tryStatement(tryContents, j.catchClause(firstParam, null, catchBody));

// when tryContents is false, ideally we should be returning tryContents.body but
// there is no way to do it so attaching it to parent fn's body
if (tryCatch && hasCatchClause) {
const x = createBlockStatement([j.variableDeclaration("let", [j.variableDeclarator(variableDeclaratorId)]), tryStatement]);
removeWrappingParenthesis(p, x.body);
} else {
removeWrappingParenthesis(p, tryContents.body);
}
const tryStatement = j.tryStatement(j.blockStatement([...tryFunctions, ...afterAwaitExprs].filter(Boolean)), catchClause);
!hasCatchClause && addComments(tryStatement, [' TODO(codemods): No error clause found']);
return tryStatement;
};

const filterImmediateFns = (p) => p.parent.node.type === "ArrayExpression";
const removeWrapperFn = (p) => {
const params = p.node.params;
// get fn's last arg, most cases it will be `next`
const lastParam = p.node.params[params.length - 1];

j(p.node.body)
.find(j.IfStatement, { consequent: { type: "ReturnStatement", argument: { type: "CallExpression", callee: { name: lastParam.name } } } })
.forEach((p) => {
const ifParentBody = p.parent.node.body;
const ifIndex = ifParentBody.indexOf(p.node);
const everythingAfterIf = ifParentBody.splice(ifIndex + 1, ifParentBody.length);
p.node.test = j.unaryExpression("!", p.node.test, true); // negate the if's test
p.node.consequent = createBlockStatement(everythingAfterIf);
});

const formattedBody = j(p.node.body)
// find all next() calls
.find(j.ExpressionStatement, { expression: { callee: { name: lastParam.name } } })
.replaceWith((p) => {
const cbHandlerArgs = p.node.expression.arguments;
const argsLength = cbHandlerArgs.length;
if (argsLength === 0) return null;

let replacementNode = null;
const errorArg = cbHandlerArgs[0];
// TODO(harsilspatel): handle return next(err, <>) instances
// if it is like next(<singleParam>) then it likely is an error
if (argsLength === 1 && !(errorArg.type === "Literal" && errorArg.value === null)) {
replacementNode = j.throwStatement(errorArg);
} else if (argsLength === 1) {
// if it's next(null) then simply remove it
replacementNode = null;
} else {
// return single argument or return array of multiple elements
// remove error from return
// cbHandlerArgs.shift(); // argsLength length changes here
// replacementNode = j.returnStatement(cbHandlerArgs.length === 1 ? cbHandlerArgs[0] : j.arrayExpression(cbHandlerArgs));
replacementNode = null;
}
// replacing parent as it's an ExpressionStatement i.e. one that ends with a semi-colon
return replacementNode;
});

// remove the stuff from fn body
return p.node.body.body;
};
return j(file.source).find(j.CallExpression, isAsyncLib).replaceWith(removeAsyncLib).toSource();
};

return {
isFnNode,
hasCallback,
awaitFn,
createBlockStatement,
filterImmediateFns,
removeWrapperFn,
convertParentFnAsync,
removeWrappingParenthesis
};
}

function c () {
console.log
function c(...args) {
console.log(...args);
}

/*
TODO handle async.waterfall() scenarios like:
function completed(err, project, attendant) {
if (err) {
console.log("shiz");
return callback(err);
}
return callback(null, true, {
project,
attendant,
attendantId,
strategy: common.AUTH_STRATEGY_KONG,
});
},
);
1. should not overwrite "shiz"
2. should be able to return instead of callback(<>, <>, <>)
*/
30 changes: 19 additions & 11 deletions scripts/1-vanilla-await.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
const ERROR_KEYWORDS = ["e", "err", "error", "formattedError"];
const ERROR_KEYWORDS = ['e', 'err', 'error', 'formattedError'];

module.exports = (file, api, options) => {
const { jscodeshift: j } = api;
const root = j(file.source);

const isFnNode = (n) => ["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"].includes(n.type);
const isFnNode = (n) => ['FunctionDeclaration', 'FunctionExpression', 'ArrowFunctionExpression'].includes(n.type);
const isAsyncLibraryNode = (n) => {
try {
return n.callee.object.name.toLowerCase() === "async";
return n.callee.object.name.toLowerCase() === 'async';
} catch (e) {
return false;
}
Expand All @@ -29,14 +29,14 @@ module.exports = (file, api, options) => {

let isThenCatchPromise;
try {
isThenCatchPromise = ["then", "catch"].includes(node.callee.property.name);
isThenCatchPromise = ['then', 'catch'].includes(node.callee.property.name);
} catch (e) {
isThenCatchPromise = false;
}

let isEventHandler;
try {
isEventHandler = node.callee.property.name === "on";
isEventHandler = node.callee.property.name === 'on';
} catch (e) {
isEventHandler = false;
}
Expand Down Expand Up @@ -76,11 +76,12 @@ module.exports = (file, api, options) => {
if (!callbackFn.body.body && callbackFn.body.callee) callbackFn.body.body = [j.returnStatement(callbackFn.body.callee)];

const firstCbExpr = callbackFn.body.body[0];
const hasCatchClause = firstCbExpr.type === "IfStatement" && firstCbExpr.test.type === "Identifier" && firstCbExpr.test.name === firstParam.name;
const hasCatchClause =
firstCbExpr.type === 'IfStatement' && firstCbExpr.test.type === 'Identifier' && firstCbExpr.test.name === firstParam.name;

// if there is, then all it's contents go to the body of `catch` clause
const catchBody = hasCatchClause
? firstCbExpr.consequent.type === "BlockStatement"
? firstCbExpr.consequent.type === 'BlockStatement'
? firstCbExpr.consequent
: j.blockStatement([firstCbExpr.consequent])
: j.blockStatement([]);
Expand All @@ -92,25 +93,32 @@ module.exports = (file, api, options) => {
// if more than 1 values then destructure variables from an
const variableDeclaratorId = returnValues > 1 ? j.arrayPattern(cbParams) : cbParams[0];
// if it is returning a value declare the variable
const awaitWrapperExpr = hasReturnValue ? j.variableDeclaration("let", [j.variableDeclarator(variableDeclaratorId, awaitExpression)]) : expressionStatement;
const awaitWrapperExpr = hasReturnValue
? j.variableDeclaration('let', [j.variableDeclarator(variableDeclaratorId, awaitExpression)])
: expressionStatement;

let afterAwaitExprs = [];

// if the catch clause is found then everything inside `else` will go below await statement
if (hasCatchClause) {
afterAwaitExprs = afterAwaitExprs.concat(firstCbExpr.alternate ? firstCbExpr.alternate.body || [firstCbExpr.alternate] : []);
afterAwaitExprs = afterAwaitExprs.concat(
firstCbExpr.alternate ? firstCbExpr.alternate.body || [firstCbExpr.alternate] : [],
);
// remove the `if-else`
callbackFn.body.body.shift();
} else {
const comments = awaitWrapperExpr.comments || [];
comments.push(j.commentLine(" TODO(codemods): No error clause found", true, false));
comments.push(j.commentLine(' TODO(codemods): No error clause found', true, false));
awaitWrapperExpr.comments = comments;
}

// everything after `if-else` also goes after await call
afterAwaitExprs = afterAwaitExprs.concat(callbackFn.body.body);

const tryStatement = j.tryStatement(j.blockStatement([awaitWrapperExpr, ...afterAwaitExprs].filter(Boolean)), j.catchClause(firstParam, null, catchBody));
const tryStatement = j.tryStatement(
j.blockStatement([awaitWrapperExpr, ...afterAwaitExprs].filter(Boolean)),
j.catchClause(firstParam, null, catchBody),
);
return tryStatement;
};

Expand Down
Loading

0 comments on commit d032ed6

Please sign in to comment.