UNPKG

luhn-generator

Version:

A generator of numbers that passes the validation of Luhn algorithm or Luhn formula, also known as the 'modulus 10' or 'mod 10' algorithm

273 lines (210 loc) 9.54 kB
"use strict"; const REPLACED = Symbol("replaced"); const h = require("./helpers"); module.exports = t => { function mergeNestedIfs(path) { const consequent = path.get("consequent"); const alternate = path.get("alternate"); // not nested if if (!consequent.isIfStatement()) return; // there are no alternate nodes in both the if statements (nested) if (alternate.node || consequent.get("alternate").node) return; const test = path.get("test"); test.replaceWith(t.logicalExpression("&&", test.node, consequent.get("test").node)); consequent.replaceWith(t.clone(consequent.get("consequent").node)); } // No alternate, make into a guarded expression function toGuardedExpression(path) { const node = path.node; if (node.consequent && !node.alternate && node.consequent.type === "ExpressionStatement") { let op = "&&"; if (t.isUnaryExpression(node.test, { operator: "!" })) { node.test = node.test.argument; op = "||"; } path.replaceWith(t.expressionStatement(t.logicalExpression(op, node.test, node.consequent.expression))); return REPLACED; } } // both consequent and alternate are expressions, turn into ternary function toTernary(path) { const node = path.node; if (t.isExpressionStatement(node.consequent) && t.isExpressionStatement(node.alternate)) { path.replaceWith(t.conditionalExpression(node.test, node.consequent.expression, node.alternate.expression)); return REPLACED; } } // consequent and alternate are return -- conditional. function toConditional(path) { const node = path.node; if (t.isReturnStatement(node.consequent) && t.isReturnStatement(node.alternate)) { if (!node.consequent.argument && !node.alternate.argument) { path.replaceWith(t.expressionStatement(node.test)); return REPLACED; } path.replaceWith(t.returnStatement(t.conditionalExpression(node.test, node.consequent.argument || h.VOID_0(t), node.alternate.argument || h.VOID_0(t)))); return REPLACED; } } // There is nothing after this If block. And one or both // of the consequent and alternate are either expression statment // or return statements. function toReturn(path) { const node = path.node; if (!path.getSibling(path.key + 1).node && path.parentPath && path.parentPath.parentPath && path.parentPath.parentPath.isFunction()) { // Only the consequent is a return, void the alternate. if (t.isReturnStatement(node.consequent) && t.isExpressionStatement(node.alternate)) { if (!node.consequent.argument) { path.replaceWith(t.expressionStatement(t.logicalExpression("||", node.test, node.alternate.expression))); return REPLACED; } path.replaceWith(t.returnStatement(t.conditionalExpression(node.test, node.consequent.argument || h.VOID_0(t), t.unaryExpression("void", node.alternate.expression, true)))); return REPLACED; } // Only the alternate is a return, void the consequent. if (t.isReturnStatement(node.alternate) && t.isExpressionStatement(node.consequent)) { if (!node.alternate.argument) { path.replaceWith(t.expressionStatement(t.logicalExpression("&&", node.test, node.consequent.expression))); return REPLACED; } path.replaceWith(t.returnStatement(t.conditionalExpression(node.test, t.unaryExpression("void", node.consequent.expression, true), node.alternate.argument || h.VOID_0(t)))); return REPLACED; } if (t.isReturnStatement(node.consequent) && !node.alternate) { if (!node.consequent.argument) { path.replaceWith(t.expressionStatement(node.test)); return REPLACED; } // This would only be worth it if the previous statement was an if // because then we may merge to create a conditional. if (path.getSibling(path.key - 1).isIfStatement()) { path.replaceWith(t.returnStatement(t.conditionalExpression(node.test, node.consequent.argument || h.VOID_0(t), h.VOID_0(t)))); return REPLACED; } } if (t.isReturnStatement(node.alternate) && !node.consequent) { if (!node.alternate.argument) { path.replaceWith(t.expressionStatement(node.test)); return REPLACED; } // Same as above. if (path.getSibling(path.key - 1).isIfStatement()) { path.replaceWith(t.returnStatement(t.conditionalExpression(node.test, node.alternate.argument || h.VOID_0(t), h.VOID_0(t)))); return REPLACED; } } } let next = path.getSibling(path.key + 1); // If the next satatement(s) is an if statement and we can simplify that // to potentailly be an expression (or a return) then this will make it // easier merge. if (next.isIfStatement()) { next.pushContext(path.context); next.visit(); next.popContext(); next = path.getSibling(path.key + 1); } // Some other visitor might have deleted our node. OUR NODE ;_; if (!path.node) { return; } // No alternate but the next statement is a return // also turn into a return conditional if (t.isReturnStatement(node.consequent) && !node.alternate && next.isReturnStatement()) { const nextArg = next.node.argument || h.VOID_0(t); next.remove(); path.replaceWith(t.returnStatement(t.conditionalExpression(node.test, node.consequent.argument || h.VOID_0(t), nextArg))); return REPLACED; } // Next is the last expression, turn into a return while void'ing the exprs if (path.parentPath && path.parentPath.parentPath && path.parentPath.parentPath.isFunction() && !path.getSibling(path.key + 2).node && t.isReturnStatement(node.consequent) && !node.alternate && next.isExpressionStatement()) { const nextExpr = next.node.expression; next.remove(); if (node.consequent.argument) { path.replaceWith(t.returnStatement(t.conditionalExpression(node.test, node.consequent.argument, t.unaryExpression("void", nextExpr, true)))); return REPLACED; } path.replaceWith(t.logicalExpression("||", node.test, nextExpr)); return REPLACED; } } // Remove else for if-return function removeUnnecessaryElse(path) { const node = path.node; const consequent = path.get("consequent"); const alternate = path.get("alternate"); if (consequent.node && alternate.node && (consequent.isReturnStatement() || consequent.isBlockStatement() && t.isReturnStatement(consequent.node.body[consequent.node.body.length - 1])) && ( // don't hoist declarations // TODO: validate declarations after fixing scope issues alternate.isBlockStatement() ? !alternate.get("body").some(stmt => stmt.isVariableDeclaration({ kind: "let" }) || stmt.isVariableDeclaration({ kind: "const" })) : true)) { path.insertAfter(alternate.isBlockStatement() ? alternate.node.body.map(el => t.clone(el)) : t.clone(alternate.node)); node.alternate = null; return REPLACED; } } function runTransforms(path) { // ordered const transforms = [toGuardedExpression, toTernary, toConditional, toReturn, removeUnnecessaryElse]; // run each of the replacement till we replace something // which is identified by the Symbol(REPLACED) that each of the // functions return when they replace something for (var _i = 0; _i < transforms.length; _i++) { const transform = transforms[_i]; if (transform(path) === REPLACED) { break; } } } // If the consequent is if and the altenrate is not then // switch them out. That way we know we don't have to print // a block.x function switchConsequent(path) { const node = path.node; if (!node.alternate) { return; } if (!t.isIfStatement(node.consequent)) { return; } if (t.isIfStatement(node.alternate)) { return; } node.test = t.unaryExpression("!", node.test, true); var _ref = [node.consequent, node.alternate]; node.alternate = _ref[0]; node.consequent = _ref[1]; } // Make if statements with conditional returns in the body into // an if statement that guards the rest of the block. function conditionalReturnToGuards(path) { const node = path.node; if (!path.inList || !path.get("consequent").isBlockStatement() || node.alternate) { return; } let ret; let test; const exprs = []; const statements = node.consequent.body; for (let i = 0, statement; statement = statements[i]; i++) { if (t.isExpressionStatement(statement)) { exprs.push(statement.expression); } else if (t.isIfStatement(statement)) { if (i < statements.length - 1) { // This isn't the last statement. Bail. return; } if (statement.alternate) { return; } if (!t.isReturnStatement(statement.consequent)) { return; } ret = statement.consequent; test = statement.test; } else { return; } } if (!test || !ret) { return; } exprs.push(test); const expr = exprs.length === 1 ? exprs[0] : t.sequenceExpression(exprs); const replacement = t.logicalExpression("&&", node.test, expr); path.replaceWith(t.ifStatement(replacement, ret, null)); } return { mergeNestedIfs, simplify: runTransforms, switchConsequent, conditionalReturnToGuards }; };