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

1,438 lines (1,157 loc) 43.5 kB
"use strict"; const some = require("lodash/some"); const _require = require("babel-helper-mark-eval-scopes"), markEvalScopes = _require.markEvalScopes, hasEval = _require.hasEval; const removeUseStrict = require("./remove-use-strict"); const evaluate = require("babel-helper-evaluate-path"); function evaluateTruthy(path) { const res = evaluate(path); if (res.confident) return !!res.value; } function prevSiblings(path) { const parentPath = path.parentPath; const siblings = []; let key = parentPath.key; while ((path = parentPath.getSibling(--key)).type) { siblings.push(path); } return siblings; } function forEachAncestor(path, callback) { while (path = path.parentPath) { callback(path); } } module.exports = ({ types: t, traverse }) => { const removeOrVoid = require("babel-helper-remove-or-void")(t); const shouldRevisit = Symbol("shouldRevisit"); // this is used for tracking fn params that can be removed // as traversal takes place from left and // unused params can be removed only on the right const markForRemoval = Symbol("markForRemoval"); const main = { // remove side effectless statement ExpressionStatement(path) { if (path.get("expression").isPure()) { removeOrVoid(path); } }, Function: { // Let's take all the vars in a function that are not in the top level scope and hoist them // with the first var declaration in the top-level scope. This transform in itself may // not yield much returns (or even can be marginally harmful to size). However it's great // for taking away statements from blocks that can be only expressions which the `simplify` // plugin can turn into other things (e.g. if => conditional). exit(path) { // This hurts gzip size. if (!this.optimizeRawSize) { return; } const node = path.node, scope = path.scope; const seen = new Set(); const declars = []; const mutations = []; for (const name in scope.bindings) { const binding = scope.bindings[name]; if (!binding.path.isVariableDeclarator()) { continue; } const declarPath = binding.path.parentPath; if (seen.has(declarPath)) { continue; } seen.add(declarPath); if (declarPath.parentPath.isForInStatement()) { continue; } if (declarPath.parentPath.parentPath.isFunction()) { continue; } if (!declarPath.node || !declarPath.node.declarations) { continue; } const assignmentSequence = []; var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = declarPath.node.declarations[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { const declar = _step.value; declars.push(declar); if (declar.init) { assignmentSequence.push(t.assignmentExpression("=", declar.id, declar.init)); mutations.push(() => { declar.init = null; }); } } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return != null) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } if (assignmentSequence.length) { mutations.push(() => declarPath.replaceWith(t.sequenceExpression(assignmentSequence))); } else { mutations.push(() => removeOrVoid(declarPath)); } } if (declars.length) { mutations.forEach(f => f()); var _iteratorNormalCompletion2 = true; var _didIteratorError2 = false; var _iteratorError2 = undefined; try { for (var _iterator2 = node.body.body[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { const statement = _step2.value; if (t.isVariableDeclaration(statement)) { statement.declarations.push(...declars); return; } } } catch (err) { _didIteratorError2 = true; _iteratorError2 = err; } finally { try { if (!_iteratorNormalCompletion2 && _iterator2.return != null) { _iterator2.return(); } } finally { if (_didIteratorError2) { throw _iteratorError2; } } } const varDecl = t.variableDeclaration("var", declars); node.body.body.unshift(varDecl); } } }, // Remove bindings with no references. Scope: { exit(path) { if (path.node[shouldRevisit]) { delete path.node[shouldRevisit]; path.visit(); } }, enter(path) { if (path.isProgram()) { return; } if (hasEval(path.scope)) { return; } const scope = path.scope; // if the scope is created by a function, we obtain its // parameter list const canRemoveParams = path.isFunction() && path.node.kind !== "set"; const paramsList = canRemoveParams ? path.get("params") : []; for (let i = paramsList.length - 1; i >= 0; i--) { const param = paramsList[i]; if (param.isIdentifier()) { const binding = scope.bindings[param.node.name]; if (!binding) continue; if (binding.referenced) { // when the first binding is referenced (right to left) // exit without marking anything after this break; } binding[markForRemoval] = true; continue; } else if (param.isAssignmentPattern()) { const left = param.get("left"); const right = param.get("right"); if (left.isIdentifier() && right.isPure()) { const binding = scope.bindings[left.node.name]; if (binding.referenced) { // when the first binding is referenced (right to left) // exit without marking anything after this break; } binding[markForRemoval] = true; continue; } } // other patterns - assignment, object have side-effects // and cannot be safely removed break; } for (const name in scope.bindings) { const binding = scope.bindings[name]; if (!binding.referenced && binding.kind !== "module") { if (binding.kind === "param" && (this.keepFnArgs || !binding[markForRemoval])) { continue; } else if (binding.path.isVariableDeclarator()) { const declaration = binding.path.parentPath; const maybeBlockParent = declaration.parentPath; if (maybeBlockParent && maybeBlockParent.isForXStatement({ left: declaration.node })) { // Can't remove if in a for-in/for-of/for-await statement `for (var x in wat)`. continue; } } else if (!scope.isPure(binding.path.node)) { // TODO: AssignmentPattern are marked as impure and unused ids aren't removed yet continue; } else if (binding.path.isFunctionExpression() || binding.path.isClassExpression()) { // `bar(function foo() {})` foo is not referenced but it's used. continue; } else if ( // ClassDeclaration has binding in two scopes // 1. The scope in which it is declared // 2. The class's own scope binding.path.isClassDeclaration() && binding.path === scope.path) { continue; } const mutations = []; let bail = false; // Make sure none of the assignments value is used binding.constantViolations.forEach(p => { if (bail || p === binding.path) { return; } if (!p.parentPath.isExpressionStatement()) { bail = true; } if (p.isAssignmentExpression()) { if (t.isArrayPattern(p.node.left) || t.isObjectPattern(p.node.left)) { bail = true; } else if (p.get("right").isPure()) { mutations.push(() => removeOrVoid(p)); } else { mutations.push(() => p.replaceWith(p.get("right"))); } } }); if (bail) { continue; } if (binding.path.isVariableDeclarator()) { if (!binding.path.get("id").isIdentifier()) { // deopt for object and array pattern continue; } // if declarator has some impure init expression // var x = foo(); // => foo(); if (binding.path.node.init && !scope.isPure(binding.path.node.init) && binding.path.parentPath.node.declarations) { // binding path has more than one declarations if (binding.path.parentPath.node.declarations.length !== 1) { continue; } binding.path.parentPath.replaceWith(binding.path.node.init); } else { updateReferences(binding.path, this); removeOrVoid(binding.path); } } else { updateReferences(binding.path, this); removeOrVoid(binding.path); } mutations.forEach(f => f()); scope.removeBinding(name); } else if (binding.constant) { if (binding.path.isFunctionDeclaration() || binding.path.isVariableDeclarator() && binding.path.get("init").isFunction()) { const fun = binding.path.isFunctionDeclaration() ? binding.path : binding.path.get("init"); let allInside = true; var _iteratorNormalCompletion3 = true; var _didIteratorError3 = false; var _iteratorError3 = undefined; try { for (var _iterator3 = binding.referencePaths[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { const ref = _step3.value; if (!ref.find(p => p.node === fun.node)) { allInside = false; break; } } } catch (err) { _didIteratorError3 = true; _iteratorError3 = err; } finally { try { if (!_iteratorNormalCompletion3 && _iterator3.return != null) { _iterator3.return(); } } finally { if (_didIteratorError3) { throw _iteratorError3; } } } if (allInside) { scope.removeBinding(name); updateReferences(binding.path, this); removeOrVoid(binding.path); continue; } } if (binding.references === 1 && binding.kind !== "param" && binding.kind !== "module" && binding.constant) { let replacement = binding.path.node; let replacementPath = binding.path; let isReferencedBefore = false; const refPath = binding.referencePaths[0]; if (t.isVariableDeclarator(replacement)) { const _prevSiblings = prevSiblings(replacementPath); // traverse ancestors of a reference checking if it's before declaration forEachAncestor(refPath, ancestor => { if (_prevSiblings.indexOf(ancestor) > -1) { isReferencedBefore = true; } }); // deopt if reference is in different scope than binding // since we don't know if it's sync or async execution // (i.e. whether value has been assigned to a reference or not) if (isReferencedBefore && refPath.scope !== binding.scope) { continue; } // simulate hoisting by replacing value // with undefined if declaration is after reference replacement = isReferencedBefore ? t.unaryExpression("void", t.numericLiteral(0), true) : replacement.init; // Bail out for ArrayPattern and ObjectPattern // TODO: maybe a more intelligent approach instead of simply bailing out if (!replacementPath.get("id").isIdentifier()) { continue; } replacementPath = replacementPath.get("init"); } if (!replacement) { continue; } if (!scope.isPure(replacement, true) && !isReferencedBefore) { continue; } let bail = false; if (replacementPath.isIdentifier()) { const binding = scope.getBinding(replacement.name); // the reference should be in the same scope // and the replacement should be a constant - this is to // ensure that the duplication of replacement is not affected // https://github.com/babel/minify/issues/685 bail = !(binding && refPath.scope.getBinding(replacement.name) === binding && binding.constantViolations.length === 0); } else { replacementPath.traverse({ Function(path) { path.skip(); }, ReferencedIdentifier({ node }) { if (bail) { return; } const binding = scope.getBinding(node.name); if (binding && refPath.scope.getBinding(node.name) === binding) { bail = binding.constantViolations.length > 0; } } }); } if (bail) { continue; } let parent = binding.path.parent; if (t.isVariableDeclaration(parent)) { parent = binding.path.parentPath.parent; } // 1. Make sure we share the parent with the node. In other words it's lexically defined // and not in an if statement or otherwise. // 2. If the replacement is an object then we have to make sure we are not in a loop or a function // because otherwise we'll be inlining and doing a lot more allocation than we have to // which would also could affect correctness in that they are not the same reference. let mayLoop = false; const sharesRoot = refPath.find(({ node }) => { if (!mayLoop) { mayLoop = t.isWhileStatement(node) || t.isFor(node) || t.isFunction(node); } return node === parent; }); // Anything that inherits from Object. const isObj = n => t.isFunction(n) || t.isObjectExpression(n) || t.isArrayExpression(n); const isReplacementObj = isObj(replacement) || some(replacement, isObj); if (!sharesRoot || isReplacementObj && mayLoop) { continue; } // check if it's safe to replace // To solve https://github.com/babel/minify/issues/691 // Here we bail for property checks using the "in" operator // This is because - `in` is a side-effect-free operation but the property // could be deleted between the replacementPath and referencePath // It is expensive to compute the delete operation and we bail for // all the binary "in" operations let inExpression = replacementPath.isBinaryExpression({ operator: "in" }); if (!inExpression) { replacementPath.traverse({ Function(path) { path.skip(); }, BinaryExpression(path) { if (path.node.operator === "in") { inExpression = true; path.stop(); } } }); } if (inExpression) { continue; } const replaced = replace(binding.referencePaths[0], { binding, scope, replacement, replacementPath }); if (replaced) { scope.removeBinding(name); if (binding.path.node) { removeOrVoid(binding.path); } } } } } // end-for-of } }, // Remove unreachable code. BlockStatement(path) { const paths = path.get("body"); let purge = false; for (let i = 0; i < paths.length; i++) { const p = paths[i]; if (!purge && p.isCompletionStatement()) { purge = true; continue; } if (purge && !canExistAfterCompletion(p)) { removeOrVoid(p); } } }, // Double check unreachable code and remove return statements that // have no semantic meaning ReturnStatement(path) { const node = path.node; if (!path.inList) { return; } // Not last in its block? (See BlockStatement visitor) if (path.container.length - 1 !== path.key && !canExistAfterCompletion(path.getSibling(path.key + 1)) && path.parentPath.isBlockStatement()) { // This is probably a new oppurtinity by some other transform // let's call the block visitor on this again before proceeding. path.parentPath.pushContext(path.context); path.parentPath.visit(); path.parentPath.popContext(); return; } if (node.argument) { return; } let noNext = true; let parentPath = path.parentPath; while (parentPath && !parentPath.isFunction() && noNext) { // https://github.com/babel/minify/issues/265 if (hasLoopParent(parentPath)) { noNext = false; break; } const nextPath = parentPath.getSibling(parentPath.key + 1); if (nextPath.node) { if (nextPath.isReturnStatement()) { nextPath.pushContext(path.context); nextPath.visit(); nextPath.popContext(); if (parentPath.getSibling(parentPath.key + 1).node) { noNext = false; break; } } else { noNext = false; break; } } parentPath = parentPath.parentPath; } if (noNext) { removeOrVoid(path); } }, ConditionalExpression(path) { const node = path.node; const evaluateTest = evaluateTruthy(path.get("test")); if (evaluateTest === true) { path.replaceWith(node.consequent); } else if (evaluateTest === false) { path.replaceWith(node.alternate); } }, SwitchStatement: { exit(path) { const discriminantPath = path.get("discriminant"); const evaluated = evaluate(discriminantPath, { tdz: this.tdz }); if (!evaluated.confident) return; // the simplify transformation might have brought in the previous // expressions into the switch's test expression and instead of // bailing out of impure path, we collect the impurities of it's // a sequence expression and bail out if the primary test itself // is impure let beforeTest = []; if (t.isSequenceExpression(discriminantPath.node)) { const expressions = discriminantPath.get("expressions"); const lastExpression = expressions[expressions.length - 1]; if (!lastExpression.isPure()) { return; } beforeTest = [t.expressionStatement(t.sequenceExpression(expressions.slice(0, expressions.length - 1).map(path => path.node)))]; } else if (!discriminantPath.isPure()) { return; } const discriminant = evaluated.value; const cases = path.get("cases"); let matchingCaseIndex = -1; let defaultCaseIndex = -1; for (let i = 0; i < cases.length; i++) { const test = cases[i].get("test"); // handle default case if (test.node === null) { defaultCaseIndex = i; continue; } const testResult = evaluate(test, { tdz: this.tdz }); // if we are not able to deternine a test during // compile time, we terminate immediately if (!testResult.confident) return; if (testResult.value === discriminant) { matchingCaseIndex = i; break; } } let result; if (matchingCaseIndex === -1) { if (defaultCaseIndex === -1) { path.skip(); path.replaceWithMultiple(extractVars(path)); return; } else { result = getStatementsUntilBreak(defaultCaseIndex); } } else { result = getStatementsUntilBreak(matchingCaseIndex); } if (result.bail) return; // we extract vars from the entire switch statement // and there will be duplicates which // will be again removed by DCE replaceSwitch([...extractVars(path), ...beforeTest, ...result.statements]); function getStatementsUntilBreak(start) { const result = { bail: false, statements: [] }; for (let i = start; i < cases.length; i++) { const consequent = cases[i].get("consequent"); for (let j = 0; j < consequent.length; j++) { const _isBreaking = isBreaking(consequent[j], path); if (_isBreaking.bail) { result.bail = true; return result; } if (_isBreaking.break) { // compute no more // exit out of the loop return result; } else { result.statements.push(consequent[j].node); } } } return result; } function replaceSwitch(statements) { let isBlockRequired = false; for (let i = 0; i < statements.length; i++) { if (t.isVariableDeclaration(statements[i], { kind: "let" })) { isBlockRequired = true; break; } if (t.isVariableDeclaration(statements[i], { kind: "const" })) { isBlockRequired = true; break; } } if (isBlockRequired) { path.replaceWith(t.BlockStatement(statements)); } else { path.replaceWithMultiple(statements); } } } }, WhileStatement(path) { const test = path.get("test"); const result = evaluate(test, { tdz: this.tdz }); if (result.confident && test.isPure() && !result.value) { path.replaceWithMultiple(extractVars(path.get("body"))); } }, ForStatement(path) { const test = path.get("test"); if (!test.isPure()) return; const result = evaluate(test, { tdz: this.tdz }); if (result.confident) { if (result.value) { test.remove(); } else { const init = path.get("init"); if (init.node && !init.isPure()) { path.replaceWith(init); } else { path.remove(); } } } }, DoWhileStatement(path) { const test = path.get("test"); const result = evaluate(test, { tdz: this.tdz }); if (result.confident && test.isPure() && !result.value) { const body = path.get("body"); if (body.isBlockStatement()) { const stmts = body.get("body"); var _iteratorNormalCompletion4 = true; var _didIteratorError4 = false; var _iteratorError4 = undefined; try { for (var _iterator4 = stmts[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { const stmt = _step4.value; const _isBreaking = isBreaking(stmt, path); if (_isBreaking.bail || _isBreaking.break) return; const _isContinuing = isContinuing(stmt, path); if (_isContinuing.bail || isContinuing.continue) return; } } catch (err) { _didIteratorError4 = true; _iteratorError4 = err; } finally { try { if (!_iteratorNormalCompletion4 && _iterator4.return != null) { _iterator4.return(); } } finally { if (_didIteratorError4) { throw _iteratorError4; } } } path.replaceWith(body.node); } else if (body.isBreakStatement()) { const _isBreaking = isBreaking(body, path); if (_isBreaking.bail) return; if (_isBreaking.break) path.remove(); } else if (body.isContinueStatement()) { return; } else { path.replaceWith(body.node); } } }, // Join assignment and definition when in sequence. // var x; x = 1; -> var x = 1; AssignmentExpression(path) { if (!path.get("left").isIdentifier() || !path.parentPath.isExpressionStatement()) { return; } const prev = path.parentPath.getSibling(path.parentPath.key - 1); if (!(prev && prev.isVariableDeclaration())) { return; } const declars = prev.node.declarations; if (declars.length !== 1 || declars[0].init || declars[0].id.name !== path.get("left").node.name) { return; } declars[0].init = path.node.right; removeOrVoid(path); }, // Remove named function expression name. While this is dangerous as it changes // `function.name` all minifiers do it and hence became a standard. FunctionExpression(path) { if (!this.keepFnName) { removeUnreferencedId(path); } }, // remove class names ClassExpression(path) { if (!this.keepClassName) { removeUnreferencedId(path); } }, // Put the `var` in the left if feasible. ForInStatement(path) { const left = path.get("left"); if (!left.isIdentifier()) { return; } const binding = path.scope.getBinding(left.node.name); if (!binding) { return; } if (binding.scope.getFunctionParent() !== path.scope.getFunctionParent()) { return; } if (!binding.path.isVariableDeclarator()) { return; } if (binding.path.parentPath.parentPath.isForInStatement({ left: binding.path.parent })) { return; } // If it has company then it's probably more efficient to keep. if (binding.path.parent.declarations.length > 1) { return; } // meh if (binding.path.node.init) { return; } removeOrVoid(binding.path); path.node.left = t.variableDeclaration("var", [t.variableDeclarator(left.node)]); binding.path = path.get("left").get("declarations")[0]; } }; return { name: "minify-dead-code-elimination", visitor: { Function: { exit(path) { /** * Use exit handler to traverse in a dfs post-order fashion * to remove use strict */ const body = path.get("body"); if (body.isBlockStatement()) { removeUseStrict(body); } } }, IfStatement: { exit(path, { opts: { tdz = false } = {} }) { const consequent = path.get("consequent"); const alternate = path.get("alternate"); const test = path.get("test"); const evalResult = evaluate(test, { tdz }); const isPure = test.isPure(); const replacements = []; if (evalResult.confident && !isPure && test.isSequenceExpression()) { replacements.push(t.expressionStatement(extractSequenceImpure(test))); } // we can check if a test will be truthy 100% and if so then we can inline // the consequent and completely ignore the alternate // // if (true) { foo; } -> { foo; } // if ("foo") { foo; } -> { foo; } // if (evalResult.confident && evalResult.value) { path.replaceWithMultiple([...replacements, ...toStatements(consequent), ...extractVars(alternate)]); return; } // we can check if a test will be falsy 100% and if so we can inline the // alternate if there is one and completely remove the consequent // // if ("") { bar; } else { foo; } -> { foo; } // if ("") { bar; } -> // if (evalResult.confident && !evalResult.value) { if (alternate.node) { path.replaceWithMultiple([...replacements, ...toStatements(alternate), ...extractVars(consequent)]); return; } else { path.replaceWithMultiple([...replacements, ...extractVars(consequent)]); } } // remove alternate blocks that are empty // // if (foo) { foo; } else {} -> if (foo) { foo; } // if (alternate.isBlockStatement() && !alternate.node.body.length) { alternate.remove(); // For if-statements babel-traverse replaces with an empty block path.node.alternate = null; } // if the consequent block is empty turn alternate blocks into a consequent // and flip the test // // if (foo) {} else { bar; } -> if (!foo) { bar; } // if (consequent.isBlockStatement() && !consequent.node.body.length && alternate.isBlockStatement() && alternate.node.body.length) { consequent.replaceWith(alternate.node); alternate.remove(); // For if-statements babel-traverse replaces with an empty block path.node.alternate = null; test.replaceWith(t.unaryExpression("!", test.node, true)); } } }, EmptyStatement(path) { if (path.parentPath.isBlockStatement() || path.parentPath.isProgram()) { path.remove(); } }, Program: { exit(path, { opts: { // set defaults optimizeRawSize = false, keepFnName = false, keepClassName = false, keepFnArgs = false, tdz = false } = {} } = {}) { (traverse.clearCache || traverse.cache.clear)(); path.scope.crawl(); markEvalScopes(path); // We need to run this plugin in isolation. path.traverse(main, { functionToBindings: new Map(), optimizeRawSize, keepFnName, keepClassName, keepFnArgs, tdz }); } } } }; function toStatements(path) { const node = path.node; if (path.isBlockStatement()) { let hasBlockScoped = false; for (let i = 0; i < node.body.length; i++) { const bodyNode = node.body[i]; if (t.isBlockScoped(bodyNode)) { hasBlockScoped = true; } } if (!hasBlockScoped) { return node.body; } } return [node]; } // Extracts vars from a path // Useful for removing blocks or paths that can contain // variable declarations inside them // Note: // drops are inits // extractVars({ var x = 5, y = x }) => var x, y; function extractVars(path) { const declarators = []; if (path.isVariableDeclaration({ kind: "var" })) { var _iteratorNormalCompletion5 = true; var _didIteratorError5 = false; var _iteratorError5 = undefined; try { for (var _iterator5 = path.node.declarations[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) { const decl = _step5.value; const bindingIds = Object.keys(t.getBindingIdentifiers(decl.id)); declarators.push(...bindingIds.map(name => t.variableDeclarator(t.identifier(name)))); } } catch (err) { _didIteratorError5 = true; _iteratorError5 = err; } finally { try { if (!_iteratorNormalCompletion5 && _iterator5.return != null) { _iterator5.return(); } } finally { if (_didIteratorError5) { throw _iteratorError5; } } } } else { path.traverse({ VariableDeclaration(varPath) { if (!varPath.isVariableDeclaration({ kind: "var" })) return; if (!isSameFunctionScope(varPath, path)) return; var _iteratorNormalCompletion6 = true; var _didIteratorError6 = false; var _iteratorError6 = undefined; try { for (var _iterator6 = varPath.node.declarations[Symbol.iterator](), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) { const decl = _step6.value; const bindingIds = Object.keys(t.getBindingIdentifiers(decl.id)); declarators.push(...bindingIds.map(name => t.variableDeclarator(t.identifier(name)))); } } catch (err) { _didIteratorError6 = true; _iteratorError6 = err; } finally { try { if (!_iteratorNormalCompletion6 && _iterator6.return != null) { _iterator6.return(); } } finally { if (_didIteratorError6) { throw _iteratorError6; } } } } }); } if (declarators.length <= 0) return []; return [t.variableDeclaration("var", declarators)]; } function replace(path, options) { const replacement = options.replacement, replacementPath = options.replacementPath, scope = options.scope, binding = options.binding; // Same name, different binding. if (scope.getBinding(path.node.name) !== binding) { return; } // We don't want to move code around to different scopes because: // 1. Original bindings that is referenced could be shadowed // 2. Moving defintions to potentially hot code is bad if (scope !== path.scope) { if (t.isClass(replacement) || t.isFunction(replacement)) { return; } let bail = false; traverse(replacement, { Function(path) { if (bail) { return; } bail = true; path.stop(); } }, scope); if (bail) { return; } } // Avoid recursion. if (path.find(({ node }) => node === replacement)) { return; } // https://github.com/babel/minify/issues/611 // this is valid only for FunctionDeclaration where we convert // function declaration to expression in the next step if (replacementPath.isFunctionDeclaration()) { const fnName = replacementPath.get("id").node.name; for (let name in replacementPath.scope.bindings) { if (name === fnName) { return; } } } // https://github.com/babel/minify/issues/130 if (!t.isExpression(replacement)) { t.toExpression(replacement); } // We don't remove fn name here, we let the FnExpr & ClassExpr visitors // check its references and remove unreferenced ones // if (t.isFunction(replacement)) { // replacement.id = null; // } path.replaceWith(replacement); return true; } function updateReferences(fnToDeletePath) { if (!fnToDeletePath.isFunction()) { return; } fnToDeletePath.traverse({ ReferencedIdentifier(path) { const node = path.node, scope = path.scope; const binding = scope.getBinding(node.name); if (!binding || !binding.path.isFunction() || binding.scope === scope || !binding.constant) { return; } const index = binding.referencePaths.indexOf(path); if (index === -1) { return; } binding.references--; binding.referencePaths.splice(index, 1); if (binding.references === 0) { binding.referenced = false; } if (binding.references <= 1 && binding.scope.path.node) { binding.scope.path.node[shouldRevisit] = true; } } }); } function removeUnreferencedId(path) { const id = path.get("id").node; if (!id) { return; } const node = path.node, scope = path.scope; const binding = scope.getBinding(id.name); // Check if shadowed or is not referenced. if (binding && (binding.path.node !== node || !binding.referenced)) { node.id = null; } } // path1 -> path2 // is path1 an ancestor of path2 function isAncestor(path1, path2) { return !!path2.findParent(parent => parent === path1); } function isSameFunctionScope(path1, path2) { return path1.scope.getFunctionParent() === path2.scope.getFunctionParent(); } function isBreaking(stmt, path) { return isControlTransfer(stmt, path, "break"); } function isContinuing(stmt, path) { return isControlTransfer(stmt, path, "continue"); } // tells if a "stmt" is a break/continue statement function isControlTransfer(stmt, path, control = "break") { const _break$continue = { break: "BreakStatement", continue: "ContinueStatement" }, type = _break$continue[control]; if (!type) { throw new Error("Can only handle break and continue statements"); } const checker = `is${type}`; if (stmt[checker]()) { return _isControlTransfer(stmt, path); } let isTransferred = false; let result = { [control]: false, bail: false }; stmt.traverse({ [type](cPath) { // if we already detected a break/continue statement, if (isTransferred) return; result = _isControlTransfer(cPath, path); if (result.bail || result[control]) { isTransferred = true; } } }); return result; function _isControlTransfer(cPath, path) { const label = cPath.get("label"); if (label.node !== null) { // labels are fn scoped and not accessible by inner functions // path is the switch statement if (!isSameFunctionScope(path, cPath)) { // we don't have to worry about this break statement return { break: false, bail: false }; } // here we handle the break labels // if they are outside switch, we bail out // if they are within the case, we keep them let labelPath; if (path.scope.getLabel) { labelPath = getLabel(label.node.name, path); } else { labelPath = path.scope.getBinding(label.node.name).path; } const _isAncestor = isAncestor(labelPath, path); return { bail: _isAncestor, [control]: _isAncestor }; } // set the flag that it is indeed breaking let isCTransfer = true; // this flag is to capture // switch(0) { case 0: while(1) if (x) break; } let possibleRunTimeControlTransfer = false; // and compute if it's breaking the correct thing let parent = cPath.parentPath; while (parent !== stmt.parentPath) { // loops and nested switch cases if (parent.isLoop() || parent.isSwitchCase()) { // invalidate all the possible runtime breaks captured // while (1) { if (x) break; } possibleRunTimeControlTransfer = false; // and set that it's not breaking our switch statement isCTransfer = false; break; } // // this is a special case and depends on // the fact that SwitchStatement is handled in the // exit hook of the traverse // // switch (0) { // case 0: if (x) break; // } // // here `x` is runtime only. // in this case, we need to bail out. So we depend on exit hook // of switch so that, it would have visited the IfStatement first // before the SwitchStatement and would have removed the // IfStatement if it was a compile time determined // if (parent.isIfStatement()) { possibleRunTimeControlTransfer = true; } parent = parent.parentPath; } return { [control]: possibleRunTimeControlTransfer || isCTransfer, bail: possibleRunTimeControlTransfer }; } } // things that are hoisted function canExistAfterCompletion(path) { return path.isFunctionDeclaration() || path.isVariableDeclaration({ kind: "var" }); } function getLabel(name, _path) { let label, path = _path; do { label = path.scope.getLabel(name); if (label) { return label; } } while (path = path.parentPath); return null; } function hasLoopParent(path) { let parent = path; do { if (parent.isLoop()) { return true; } } while (parent = parent.parentPath); return false; } function extractSequenceImpure(seq) { const expressions = seq.get("expressions"); const result = []; for (let i = 0; i < expressions.length; i++) { if (!expressions[i].isPure()) { result.push(expressions[i].node); } } return t.sequenceExpression(result); } };