From efbc2ffe6a1ee5ec38a4396305561d6515d0943e Mon Sep 17 00:00:00 2001 From: littleblack111 Date: Fri, 30 Aug 2024 17:21:18 +0800 Subject: node fixes, updates --- .../lib/assignment-expression.js | 96 ++ .../lib/conditional-expression.js | 39 + .../babel-plugin-minify-simplify/lib/helpers.js | 56 + .../lib/if-statement.js | 273 +++++ .../babel-plugin-minify-simplify/lib/index.js | 1179 ++++++++++++++++++++ .../lib/logical-expression.js | 75 ++ .../lib/pattern-match.js | 156 +++ 7 files changed, 1874 insertions(+) create mode 100644 node_modules/babel-plugin-minify-simplify/lib/assignment-expression.js create mode 100644 node_modules/babel-plugin-minify-simplify/lib/conditional-expression.js create mode 100644 node_modules/babel-plugin-minify-simplify/lib/helpers.js create mode 100644 node_modules/babel-plugin-minify-simplify/lib/if-statement.js create mode 100644 node_modules/babel-plugin-minify-simplify/lib/index.js create mode 100644 node_modules/babel-plugin-minify-simplify/lib/logical-expression.js create mode 100644 node_modules/babel-plugin-minify-simplify/lib/pattern-match.js (limited to 'node_modules/babel-plugin-minify-simplify/lib') diff --git a/node_modules/babel-plugin-minify-simplify/lib/assignment-expression.js b/node_modules/babel-plugin-minify-simplify/lib/assignment-expression.js new file mode 100644 index 00000000..3f9b0ce4 --- /dev/null +++ b/node_modules/babel-plugin-minify-simplify/lib/assignment-expression.js @@ -0,0 +1,96 @@ +"use strict"; + +const operators = new Set(["+", "-", "*", "%", "<<", ">>", ">>>", "&", "|", "^", "/", "**"]); +const updateOperators = new Set(["+", "-"]); + +module.exports = t => { + function simplify(path) { + const rightExpr = path.get("right"); + const leftExpr = path.get("left"); + + if (path.node.operator !== "=") { + return; + } + + const canBeUpdateExpression = rightExpr.get("right").isNumericLiteral() && rightExpr.get("right").node.value === 1 && updateOperators.has(rightExpr.node.operator); + + if (leftExpr.isMemberExpression()) { + const leftPropNames = getPropNames(leftExpr); + const rightPropNames = getPropNames(rightExpr.get("left")); + + if (!leftPropNames || leftPropNames.indexOf(undefined) > -1 || !rightPropNames || rightPropNames.indexOf(undefined) > -1 || !operators.has(rightExpr.node.operator) || !areArraysEqual(leftPropNames, rightPropNames)) { + return; + } + } else { + if (!rightExpr.isBinaryExpression() || !operators.has(rightExpr.node.operator) || leftExpr.node.name !== rightExpr.node.left.name) { + return; + } + } + + let newExpression; // special case x=x+1 --> ++x + + if (canBeUpdateExpression) { + newExpression = t.updateExpression(rightExpr.node.operator + rightExpr.node.operator, t.clone(leftExpr.node), true + /* prefix */ + ); + } else { + newExpression = t.assignmentExpression(rightExpr.node.operator + "=", t.clone(leftExpr.node), t.clone(rightExpr.node.right)); + } + + path.replaceWith(newExpression); + } + + return { + simplify + }; +}; + +function areArraysEqual(arr1, arr2) { + return arr1.every((value, index) => { + return String(value) === String(arr2[index]); + }); +} + +function getPropNames(path) { + if (!path.isMemberExpression()) { + return; + } + + let obj = path.get("object"); + const prop = path.get("property"); + const propNames = [getName(prop.node)]; + + while (obj.type === "MemberExpression") { + const node = obj.get("property").node; + + if (node) { + propNames.push(getName(node)); + } + + obj = obj.get("object"); + } + + propNames.push(getName(obj.node)); + return propNames; +} + +function getName(node) { + if (node.type === "ThisExpression") { + return "this"; + } + + if (node.type === "Super") { + return "super"; + } + + if (node.type === "NullLiteral") { + return "null"; + } // augment identifiers so that they don't match + // string/number literals + // but still match against each other + + + return node.name ? node.name + "_" : node.value + /* Literal */ + ; +} \ No newline at end of file diff --git a/node_modules/babel-plugin-minify-simplify/lib/conditional-expression.js b/node_modules/babel-plugin-minify-simplify/lib/conditional-expression.js new file mode 100644 index 00000000..9640e8c8 --- /dev/null +++ b/node_modules/babel-plugin-minify-simplify/lib/conditional-expression.js @@ -0,0 +1,39 @@ +"use strict"; + +const h = require("./helpers"); + +const PatternMatch = require("./pattern-match"); + +module.exports = t => { + // small abstractions + const not = node => t.unaryExpression("!", node); + + const notnot = node => not(not(node)); + + const or = (a, b) => t.logicalExpression("||", a, b); + + const and = (a, b) => t.logicalExpression("&&", a, b); + + function simplifyPatterns(path) { + const test = path.get("test"); + const consequent = path.get("consequent"); + const alternate = path.get("alternate"); + + const _h$typeSymbols = h.typeSymbols(t), + EX = _h$typeSymbols.Expression; // Convention: + // =============== + // for each pattern [test, consequent, alternate, handler(expr, cons, alt)] + + + const matcher = new PatternMatch([[EX, true, false, e => notnot(e)], [EX, false, true, e => not(e)], [EX, true, EX, (e, c, a) => or(notnot(e), a)], [EX, false, EX, (e, c, a) => and(not(e), a)], [EX, EX, true, (e, c) => or(not(e), c)], [EX, EX, false, (e, c) => and(notnot(e), c)]]); + const result = matcher.match([test, consequent, alternate], h.isPatternMatchesPath(t)); + + if (result.match) { + path.replaceWith(result.value(test.node, consequent.node, alternate.node)); + } + } + + return { + simplifyPatterns + }; +}; \ No newline at end of file diff --git a/node_modules/babel-plugin-minify-simplify/lib/helpers.js b/node_modules/babel-plugin-minify-simplify/lib/helpers.js new file mode 100644 index 00000000..dfbe7c2a --- /dev/null +++ b/node_modules/babel-plugin-minify-simplify/lib/helpers.js @@ -0,0 +1,56 @@ +"use strict"; + +const VOID_0 = t => t.unaryExpression("void", t.numericLiteral(0), true); + +const evaluate = require("babel-helper-evaluate-path"); // Types as Symbols - for comparing types + + +const types = {}; // This is a test key which is used to avoid Object.keys check +// Object.keys() check is really expensive +// https://gist.github.com/vigneshshanmugam/c766550ecd02292dcdfbf0bf013b9d3d + +const testKey = "Expression"; + +const typeSymbols = t => { + // don't recompute + if (types[testKey] !== undefined) { + return types; + } + + t.TYPES.forEach(type => { + types[type] = Symbol.for(type); + }); + return types; +}; + +const isNodeOfType = (t, node, typeSymbol) => typeof typeSymbol !== "symbol" ? false : t["is" + Symbol.keyFor(typeSymbol)](node); + +const isPatternMatchesPath = t => function _isPatternMatchesPath(patternValue, inputPath) { + if (Array.isArray(patternValue)) { + for (let i = 0; i < patternValue.length; i++) { + if (_isPatternMatchesPath(patternValue[i], inputPath)) { + return true; + } + } + + return false; + } + + if (typeof patternValue === "function") { + return patternValue(inputPath); + } + + if (isNodeOfType(t, inputPath.node, patternValue)) return true; + const evalResult = evaluate(inputPath); + if (!evalResult.confident || !inputPath.isPure()) return false; + return evalResult.value === patternValue; +}; + +module.exports = { + VOID_0, + // Types as Symbols + typeSymbols, + // This is required for resolving type aliases + isNodeOfType, + isPatternMatchesPath +}; \ No newline at end of file diff --git a/node_modules/babel-plugin-minify-simplify/lib/if-statement.js b/node_modules/babel-plugin-minify-simplify/lib/if-statement.js new file mode 100644 index 00000000..f6ad4de2 --- /dev/null +++ b/node_modules/babel-plugin-minify-simplify/lib/if-statement.js @@ -0,0 +1,273 @@ +"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 + }; +}; \ No newline at end of file diff --git a/node_modules/babel-plugin-minify-simplify/lib/index.js b/node_modules/babel-plugin-minify-simplify/lib/index.js new file mode 100644 index 00000000..98b6190d --- /dev/null +++ b/node_modules/babel-plugin-minify-simplify/lib/index.js @@ -0,0 +1,1179 @@ +"use strict"; + +function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); } + +function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } + +function _iterableToArrayLimit(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } + +function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } + +module.exports = ({ + types: t +}) => { + const flipExpressions = require("babel-helper-flip-expressions")(t); + + const toMultipleSequenceExpressions = require("babel-helper-to-multiple-sequence-expressions")(t); + + const ifStatement = require("./if-statement")(t); + + const conditionalExpression = require("./conditional-expression")(t); + + const logicalExpression = require("./logical-expression")(t); + + const assignmentExpression = require("./assignment-expression")(t); + + const VOID_0 = t.unaryExpression("void", t.numericLiteral(0), true); + const condExprSeen = Symbol("condExprSeen"); + const seqExprSeen = Symbol("seqExprSeen"); + const shouldRevisit = Symbol("shouldRevisit"); + return { + name: "minify-simplify", + visitor: { + Statement: { + exit(path) { + if (path.node[shouldRevisit]) { + delete path.node[shouldRevisit]; + path.visit(); + } + } + + }, + // CallExpression(path) { + // const { node } = path; + + /* (function() {})() -> !function() {}() + There is a bug in babel in printing this. Disabling for now. + if (t.isFunctionExpression(node.callee) && + (t.isExpressionStatement(parent) || + (t.isSequenceExpression(parent) && parent.expressions[0] === node)) + ) { + path.replaceWith( + t.callExpression( + t.unaryExpression("!", node.callee, true), + node.arguments + ) + ); + return; + }*/ + // }, + UnaryExpression: { + enter: [// Demorgans. + function (path) { + const node = path.node; + + if (node.operator !== "!" || flipExpressions.hasSeen(node)) { + return; + } + + const expr = node.argument; // We need to make sure that the return type will always be boolean. + + if (!(t.isLogicalExpression(expr) || t.isConditionalExpression(expr) || t.isBinaryExpression(expr))) { + return; + } + + if (t.isBinaryExpression(expr) && t.COMPARISON_BINARY_OPERATORS.indexOf(expr.operator) === -1) { + return; + } + + if (flipExpressions.shouldFlip(expr, 1)) { + const newNode = flipExpressions.flip(expr); + path.replaceWith(newNode); + } + }, // !(a, b, c) -> a, b, !c + function (path) { + const node = path.node; + + if (node.operator !== "!") { + return; + } + + if (!t.isSequenceExpression(node.argument)) { + return; + } + + const seq = node.argument.expressions; + const expr = seq[seq.length - 1]; + seq[seq.length - 1] = t.unaryExpression("!", expr, true); + path.replaceWith(node.argument); + }, // !(a ? b : c) -> a ? !b : !c + function (path) { + const node = path.node; + + if (node.operator !== "!") { + return; + } + + if (!t.isConditional(node.argument)) { + return; + } + + const cond = node.argument; + cond.alternate = t.unaryExpression("!", cond.alternate, true); + cond.consequent = t.unaryExpression("!", cond.consequent, true); + path.replaceWith(node.argument); + }] + }, + + BinaryExpression(path) { + if (["!=", "=="].indexOf(path.node.operator) !== -1) { + undefinedToNull(path.get("left")); + undefinedToNull(path.get("right")); + } + }, + + LogicalExpression: { + exit: logicalExpression.simplifyPatterns + }, + AssignmentExpression: assignmentExpression.simplify, + ConditionalExpression: { + enter: [// !foo ? 'foo' : 'bar' -> foo ? 'bar' : 'foo' + // foo !== 'lol' ? 'foo' : 'bar' -> foo === 'lol' ? 'bar' : 'foo' + function flipIfOrConditional(path) { + const node = path.node; + + if (!path.get("test").isLogicalExpression()) { + flipNegation(node); + return; + } + + if (flipExpressions.shouldFlip(node.test)) { + node.test = flipExpressions.flip(node.test); + var _ref = [node.consequent, node.alternate]; + node.alternate = _ref[0]; + node.consequent = _ref[1]; + } + }, conditionalExpression.simplifyPatterns], + exit: [// a ? x = foo : b ? x = bar : x = baz; + // x = a ? foo : b ? bar : baz; + function (topPath) { + if (!topPath.parentPath.isExpressionStatement() && !topPath.parentPath.isSequenceExpression()) { + return; + } + + const mutations = []; + let firstLeft = null; + let operator = null; + + function visit(path) { + if (path.isConditionalExpression()) { + let bail = visit(path.get("consequent")); + + if (bail) { + return true; + } + + bail = visit(path.get("alternate")); + return bail; + } + + if (operator == null) { + operator = path.node.operator; + } else if (path.node.operator !== operator) { + return true; + } + + if (!path.isAssignmentExpression() || !(path.get("left").isIdentifier() || path.get("left").isMemberExpression())) { + return true; + } + + const left = path.get("left").node; + + if (firstLeft == null) { + firstLeft = left; + } else if (!t.isNodesEquivalent(left, firstLeft)) { + return true; + } + + mutations.push(() => path.replaceWith(path.get("right").node)); + } + + const bail = visit(topPath); + + if (bail) { + return; + } + + mutations.forEach(f => f()); + topPath.replaceWith(t.assignmentExpression(operator, firstLeft, topPath.node)); + }, // bar ? void 0 : void 0 + // (bar, void 0) + // TODO: turn this into general equiv check + function (path) { + const node = path.node; + + if (isVoid0(node.consequent) && isVoid0(node.alternate)) { + path.replaceWith(t.sequenceExpression([path.node.test, VOID_0])); + } + }, // bar ? void 0 : foo ? void 0 : + // bar || foo : void 0 + // TODO: turn this into general equiv check + function (path) { + const node = path.node; + + if (node[condExprSeen] || !isVoid0(node.consequent)) { + return; + } + + node[condExprSeen] = true; + const tests = [node.test]; + const mutations = []; + let alt; + + for (let next = path.get("alternate"); next.isConditionalExpression(); next = next.get("alternate")) { + next.node[condExprSeen] = true; + alt = next.node.alternate; + + if (isVoid0(next.node.consequent)) { + tests.push(next.node.test); + mutations.push(() => next.remove()); + } else { + alt = next.node; + break; + } + } + + if (tests.length === 1) { + return; + } + + const test = tests.reduce((expr, curTest) => t.logicalExpression("||", expr, curTest)); + path.replaceWith(t.conditionalExpression(test, VOID_0, alt)); + }] + }, + // concat + VariableDeclaration: { + enter: [// Put vars with no init at the top. + function (path) { + const node = path.node; + + if (node.declarations.length < 2) { + return; + } + + const inits = []; + const empty = []; + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = node.declarations[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + const decl = _step.value; + + if (!decl.init) { + empty.push(decl); + } else { + inits.push(decl); + } + } // This is based on exprimintation but there is a significant + // imrpovement when we place empty vars at the top in smaller + // files. Whereas it hurts in larger files. + + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return != null) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } + } + + if (this.fitsInSlidingWindow) { + node.declarations = empty.concat(inits); + } else { + node.declarations = inits.concat(empty); + } + }] + }, + Function: { + exit(path) { + earlyReturnTransform(path); + + if (!path.node[shouldRevisit]) { + return; + } + + delete path.node[shouldRevisit]; + path.visit(); + } + + }, + For: { + enter: earlyContinueTransform, + exit: earlyContinueTransform + }, + ForStatement: { + // Merge previous expressions in the init part of the for. + enter(path) { + const node = path.node; + + if (!path.inList || node.init && !t.isExpression(node.init)) { + return; + } + + const prev = path.getSibling(path.key - 1); + let consumed = false; + + if (prev.isVariableDeclaration()) { + let referencedOutsideLoop = false; // we don't care if vars are referenced outside the loop as they are fn scope + + if (prev.node.kind === "let" || prev.node.kind === "const") { + const ids = Object.keys(prev.getBindingIdentifiers()); + + idloop: for (let i = 0; i < ids.length; i++) { + const binding = prev.scope.bindings[ids[i]]; // TODO + // Temporary Fix + // if there is no binding, we assume it is referenced outside + // and deopt to avoid bugs + + if (!binding) { + referencedOutsideLoop = true; + break idloop; + } + + const refs = binding.referencePaths; + + for (let j = 0; j < refs.length; j++) { + if (!isAncestor(path, refs[j])) { + referencedOutsideLoop = true; + break idloop; + } + } + } + } + + if (!node.init && !referencedOutsideLoop) { + node.init = prev.node; + consumed = true; + } + } else if (prev.isExpressionStatement()) { + const expr = prev.node.expression; + + if (node.init) { + if (t.isSequenceExpression(expr)) { + expr.expressions.push(node.init); + node.init = expr; + } else { + node.init = t.sequenceExpression([expr, node.init]); + } + } else { + node.init = expr; + } + + consumed = true; + } + + if (consumed) { + prev.remove(); + } + }, + + exit(path) { + const node = path.node; + + if (!node.test) { + return; + } + + if (!path.get("body").isBlockStatement()) { + const bodyNode = path.get("body").node; + + if (!t.isIfStatement(bodyNode)) { + return; + } + + if (t.isBreakStatement(bodyNode.consequent, { + label: null + })) { + node.test = t.logicalExpression("&&", node.test, t.unaryExpression("!", bodyNode.test, true)); + node.body = bodyNode.alternate || t.emptyStatement(); + return; + } + + if (t.isBreakStatement(bodyNode.alternate, { + label: null + })) { + node.test = t.logicalExpression("&&", node.test, bodyNode.test); + node.body = bodyNode.consequent || t.emptyStatement(); + return; + } + + return; + } + + const statements = node.body.body; + const exprs = []; + let ifStatement = null; + let breakAt = null; + let i = 0; + + for (let statement; statement = statements[i]; i++) { + if (t.isIfStatement(statement)) { + if (t.isBreakStatement(statement.consequent, { + label: null + })) { + ifStatement = statement; + breakAt = "consequent"; + } else if (t.isBreakStatement(statement.alternate, { + label: null + })) { + ifStatement = statement; + breakAt = "alternate"; + } + + break; + } // A statement appears before the break statement then bail. + + + if (!t.isExpressionStatement(statement)) { + return; + } + + exprs.push(statement.expression); + } + + if (!ifStatement) { + return; + } + + const rest = []; + + if (breakAt === "consequent") { + if (t.isBlockStatement(ifStatement.alternate)) { + rest.push(...ifStatement.alternate.body); + } else if (ifStatement.alternate) { + rest.push(ifStatement.alternate); + } + } else { + if (t.isBlockStatement(ifStatement.consequent)) { + rest.push(...ifStatement.consequent.body); + } else if (ifStatement.consequent) { + rest.push(ifStatement.consequent); + } + } + + rest.push(...statements.slice(i + 1)); + const test = breakAt === "consequent" ? t.unaryExpression("!", ifStatement.test, true) : ifStatement.test; + let expr; + + if (exprs.length === 1) { + expr = t.sequenceExpression([exprs[0], test]); + } else if (exprs.length) { + exprs.push(test); + expr = t.sequenceExpression(exprs); + } else { + expr = test; + } + + node.test = t.logicalExpression("&&", node.test, expr); + + if (rest.length === 1) { + node.body = rest[0]; + } else if (rest.length) { + node.body = t.blockStatement(rest); + } else { + node.body = t.emptyStatement(); + } + } + + }, + + Program(path) { + // An approximation of the resultant gzipped code after minification + this.fitsInSlidingWindow = path.getSource().length / 10 < 33000; + const node = path.node; + const statements = toMultipleSequenceExpressions(node.body); + + if (!statements.length) { + return; + } + + node.body = statements; + }, + + BlockStatement: { + enter(path) { + const node = path.node, + parent = path.parent; + const top = []; + const bottom = []; + + for (let i = 0; i < node.body.length; i++) { + const bodyNode = node.body[i]; + + if (t.isFunctionDeclaration(bodyNode)) { + top.push(bodyNode); + } else { + bottom.push(bodyNode); + } + } + + const statements = top.concat(toMultipleSequenceExpressions(bottom)); + + if (!statements.length) { + return; + } + + if (statements.length > 1 || needsBlock(node, parent) || node.directives) { + node.body = statements; + return; + } + + if (statements.length) { + path.replaceWith(statements[0]); + return; + } + }, + + exit(path) { + const node = path.node, + parent = path.parent; + + if (t.isArrowFunctionExpression(parent) && node.body.length === 1 && t.isReturnStatement(node.body[0]) && node.body[0].argument !== null) { + path.replaceWith(node.body[0].argument); + return; + } + + if (needsBlock(node, parent)) { + return; + } + + if (node.body.length === 1) { + path.get("body")[0].inList = false; + path.replaceWith(node.body[0]); + return; + } + + if (node.body.length === 0) { + path.replaceWith(t.emptyStatement()); + return; + } // Check if oppurtinties to merge statements are available. + + + const statements = node.body; + + if (!statements.length) { + return; + } + + var _iteratorNormalCompletion2 = true; + var _didIteratorError2 = false; + var _iteratorError2 = undefined; + + try { + for (var _iterator2 = statements[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { + const statement = _step2.value; + + if (!t.isExpressionStatement(statement)) { + return; + } + } + } catch (err) { + _didIteratorError2 = true; + _iteratorError2 = err; + } finally { + try { + if (!_iteratorNormalCompletion2 && _iterator2.return != null) { + _iterator2.return(); + } + } finally { + if (_didIteratorError2) { + throw _iteratorError2; + } + } + } + + path.visit(); + } + + }, + ThrowStatement: createPrevExpressionEater("throw"), + // Try to merge previous statements into a sequence + ReturnStatement: { + enter: [createPrevExpressionEater("return"), // Remove return if last statement with no argument. + // Replace return with `void` argument with argument. + function (path) { + const node = path.node; + + if (!path.parentPath.parentPath.isFunction() || path.getSibling(path.key + 1).node) { + return; + } + + if (!node.argument) { + path.remove(); + return; + } + + if (t.isUnaryExpression(node.argument, { + operator: "void" + })) { + path.replaceWith(node.argument.argument); + } + }] + }, + // turn blocked ifs into single statements + IfStatement: { + exit: [ifStatement.mergeNestedIfs, ifStatement.simplify, ifStatement.switchConsequent, ifStatement.conditionalReturnToGuards, createPrevExpressionEater("if")] + }, + + WhileStatement(path) { + const node = path.node; + path.replaceWith(t.forStatement(null, node.test, null, node.body)); + }, + + ForInStatement: createPrevExpressionEater("for-in"), + // Flatten sequence expressions. + SequenceExpression: { + exit(path) { + if (path.node[seqExprSeen]) { + return; + } + + function flatten(node) { + node[seqExprSeen] = true; + const ret = []; + var _iteratorNormalCompletion3 = true; + var _didIteratorError3 = false; + var _iteratorError3 = undefined; + + try { + for (var _iterator3 = node.expressions[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { + const n = _step3.value; + + if (t.isSequenceExpression(n)) { + ret.push(...flatten(n)); + } else { + ret.push(n); + } + } + } catch (err) { + _didIteratorError3 = true; + _iteratorError3 = err; + } finally { + try { + if (!_iteratorNormalCompletion3 && _iterator3.return != null) { + _iterator3.return(); + } + } finally { + if (_didIteratorError3) { + throw _iteratorError3; + } + } + } + + return ret; + } + + path.node.expressions = flatten(path.node); + } + + }, + + SwitchCase(path) { + const node = path.node; + + if (!node.consequent.length) { + return; + } + + node.consequent = toMultipleSequenceExpressions(node.consequent); + }, + + SwitchStatement: { + exit: [// Convert switch statements with all returns in their cases + // to return conditional. + function (path) { + const node = path.node; // Need to be careful of side-effects. + + if (!t.isIdentifier(node.discriminant)) { + return; + } + + if (!node.cases.length) { + return; + } + + const consTestPairs = []; + let fallThru = []; + let defaultRet; + var _iteratorNormalCompletion4 = true; + var _didIteratorError4 = false; + var _iteratorError4 = undefined; + + try { + for (var _iterator4 = node.cases[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { + const switchCase = _step4.value; + + if (switchCase.consequent.length > 1) { + return; + } + + const cons = switchCase.consequent[0]; // default case + + if (!switchCase.test) { + if (!t.isReturnStatement(cons)) { + return; + } + + defaultRet = cons; + continue; + } + + if (!switchCase.consequent.length) { + fallThru.push(switchCase.test); + continue; + } // TODO: can we void it? + + + if (!t.isReturnStatement(cons)) { + return; + } + + let test = t.binaryExpression("===", node.discriminant, switchCase.test); + + if (fallThru.length && !defaultRet) { + test = fallThru.reduceRight((right, test) => t.logicalExpression("||", t.binaryExpression("===", node.discriminant, test), right), test); + } + + fallThru = []; + consTestPairs.push([test, cons.argument || VOID_0]); + } // Bail if we have any remaining fallthrough + + } catch (err) { + _didIteratorError4 = true; + _iteratorError4 = err; + } finally { + try { + if (!_iteratorNormalCompletion4 && _iterator4.return != null) { + _iterator4.return(); + } + } finally { + if (_didIteratorError4) { + throw _iteratorError4; + } + } + } + + if (fallThru.length) { + return; + } // We need the default to be there to make sure there is an oppurtinity + // not to return. + + + if (!defaultRet) { + if (path.inList) { + const nextPath = path.getSibling(path.key + 1); + + if (nextPath.isReturnStatement()) { + defaultRet = nextPath.node; + nextPath.remove(); + } else if (!nextPath.node && path.parentPath.parentPath.isFunction()) { + // If this is the last statement in a function we just fake a void return. + defaultRet = t.returnStatement(VOID_0); + } else { + return; + } + } else { + return; + } + } + + const cond = consTestPairs.reduceRight((alt, [test, cons]) => t.conditionalExpression(test, cons, alt), defaultRet.argument || VOID_0); + path.replaceWith(t.returnStatement(cond)); // Maybe now we can merge with some previous switch statement. + + if (path.inList) { + const prev = path.getSibling(path.key - 1); + + if (prev.isSwitchStatement()) { + prev.visit(); + } + } + }, // Convert switches into conditionals. + function (path) { + const node = path.node; // Need to be careful of side-effects. + + if (!t.isIdentifier(node.discriminant)) { + return; + } + + if (!node.cases.length) { + return; + } + + const exprTestPairs = []; + let fallThru = []; + let defaultExpr; + var _iteratorNormalCompletion5 = true; + var _didIteratorError5 = false; + var _iteratorError5 = undefined; + + try { + for (var _iterator5 = node.cases[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) { + const switchCase = _step5.value; + + if (!switchCase.test) { + if (switchCase.consequent.length !== 1) { + return; + } + + if (!t.isExpressionStatement(switchCase.consequent[0])) { + return; + } + + defaultExpr = switchCase.consequent[0].expression; + continue; + } + + if (!switchCase.consequent.length) { + fallThru.push(switchCase.test); + continue; + } + + const _switchCase$consequen = _slicedToArray(switchCase.consequent, 2), + cons = _switchCase$consequen[0], + breakStatement = _switchCase$consequen[1]; + + if (switchCase === node.cases[node.cases.length - 1]) { + if (breakStatement && !t.isBreakStatement(breakStatement)) { + return; + } + } else if (!t.isBreakStatement(breakStatement)) { + return; + } + + if (!t.isExpressionStatement(cons) || switchCase.consequent.length > 2) { + return; + } + + let test = t.binaryExpression("===", node.discriminant, switchCase.test); + + if (fallThru.length && !defaultExpr) { + test = fallThru.reduceRight((right, test) => t.logicalExpression("||", t.binaryExpression("===", node.discriminant, test), right), test); + } + + fallThru = []; + exprTestPairs.push([test, cons.expression]); + } + } catch (err) { + _didIteratorError5 = true; + _iteratorError5 = err; + } finally { + try { + if (!_iteratorNormalCompletion5 && _iterator5.return != null) { + _iterator5.return(); + } + } finally { + if (_didIteratorError5) { + throw _iteratorError5; + } + } + } + + if (fallThru.length) { + return; + } + + const cond = exprTestPairs.reduceRight((alt, [test, cons]) => t.conditionalExpression(test, cons, alt), defaultExpr || VOID_0); + path.replaceWith(cond); + }, function (path) { + const node = path.node; + + if (!node.cases.length) { + return; + } + + const lastCase = path.get("cases")[node.cases.length - 1]; + + if (!lastCase.node.consequent.length) { + return; + } + + const potentialBreak = lastCase.get("consequent")[lastCase.node.consequent.length - 1]; + + if (t.isBreakStatement(potentialBreak) && potentialBreak.node.label === null) { + potentialBreak.remove(); + } + }, createPrevExpressionEater("switch")] + } + } + }; + + function flipNegation(node) { + if (!node.consequent || !node.alternate) { + return; + } + + const test = node.test; + let flip = false; + + if (t.isBinaryExpression(test)) { + if (test.operator === "!==") { + test.operator = "==="; + flip = true; + } + + if (test.operator === "!=") { + test.operator = "=="; + flip = true; + } + } + + if (t.isUnaryExpression(test, { + operator: "!" + })) { + node.test = test.argument; + flip = true; + } + + if (flip) { + const consequent = node.consequent; + node.consequent = node.alternate; + node.alternate = consequent; + } + } + + function needsBlock(node, parent) { + return t.isFunction(parent) && node === parent.body || t.isTryStatement(parent) || t.isCatchClause(parent) || t.isSwitchStatement(parent) || isSingleBlockScopeDeclaration(node) && (t.isIfStatement(parent) || t.isLoop(parent)); + } + + function isSingleBlockScopeDeclaration(block) { + return t.isBlockStatement(block) && block.body.length === 1 && (t.isVariableDeclaration(block.body[0], { + kind: "let" + }) || t.isVariableDeclaration(block.body[0], { + kind: "const" + }) || t.isFunctionDeclaration(block.body[0])); + } + + function isVoid0(expr) { + return expr === VOID_0 || t.isUnaryExpression(expr, { + operator: "void" + }) && t.isNumericLiteral(expr.argument, { + value: 0 + }); + } + + function earlyReturnTransform(path) { + const block = path.get("body"); + + if (!block.isBlockStatement()) { + return; + } + + const body = block.get("body"); + + for (let i = body.length - 1; i >= 0; i--) { + const statement = body[i]; + + if (t.isIfStatement(statement.node) && !statement.node.alternate && t.isReturnStatement(statement.node.consequent) && !statement.node.consequent.argument) { + genericEarlyExitTransform(statement); + } + } + } + + function earlyContinueTransform(path) { + const block = path.get("body"); + + if (!block.isBlockStatement()) { + return; + } + + let body = block.get("body"); + + for (let i = body.length - 1; i >= 0; i--) { + const statement = body[i]; + + if (t.isIfStatement(statement.node) && !statement.node.alternate && t.isContinueStatement(statement.node.consequent) && !statement.node.consequent.label) { + genericEarlyExitTransform(statement); + } + } // because we might have folded or removed statements + + + body = block.get("body"); // We may have reduced the body to a single statement. + + if (body.length === 1 && !needsBlock(block.node, path.node)) { + block.replaceWith(body[0].node); + } + } + + function genericEarlyExitTransform(path) { + const node = path.node; + const statements = path.parentPath.get(path.listKey).slice(path.key + 1).filter(stmt => !stmt.isFunctionDeclaration()); // deopt for any block scoped bindings + // issue#399 + + const deopt = !statements.every(stmt => { + if (!(stmt.isVariableDeclaration({ + kind: "let" + }) || stmt.isVariableDeclaration({ + kind: "const" + }))) { + return true; + } + + const ids = Object.keys(stmt.getBindingIdentifiers()); + + for (var _i2 = 0; _i2 < ids.length; _i2++) { + const id = ids[_i2]; + const binding = path.scope.getBinding(id); // TODO + // Temporary Fix + // if there is no binding, we assume it is referenced outside + // and deopt to avoid bugs + + if (!binding) { + return false; + } + + const refs = [...binding.referencePaths, ...binding.constantViolations]; + + for (var _i3 = 0; _i3 < refs.length; _i3++) { + const ref = refs[_i3]; + if (!ref.isIdentifier()) return false; + const fnParent = ref.getFunctionParent(); // TODO + // Usage of scopes and bindings in simplify plugin results in + // undefined bindings because scope changes are not updated in the + // scope tree. So, we deopt whenever we encounter one such issue + // and not perform the transformation + + if (!fnParent) { + return false; + } + + if (fnParent.scope !== path.scope) return false; + } + } + + return true; + }); + + if (deopt) { + path.visit(); + return false; + } + + if (!statements.length) { + path.replaceWith(t.expressionStatement(node.test)); + return; + } + + const test = node.test; + + if (t.isBinaryExpression(test) && test.operator === "!==") { + test.operator = "==="; + } else if (t.isBinaryExpression(test) && test.operator === "!=") { + test.operator = "=="; + } else if (t.isUnaryExpression(test, { + operator: "!" + })) { + node.test = test.argument; + } else { + node.test = t.unaryExpression("!", node.test, true); + } + + path.get("consequent").replaceWith(t.blockStatement(statements.map(stmt => t.clone(stmt.node)))); + let l = statements.length; + + while (l-- > 0) { + if (!statements[l].isFunctionDeclaration()) { + path.getSibling(path.key + 1).remove(); + } + } // this should take care of removing the block + + + path.visit(); + } + + function createPrevExpressionEater(keyword) { + let key; + + switch (keyword) { + case "switch": + key = "discriminant"; + break; + + case "throw": + case "return": + key = "argument"; + break; + + case "if": + key = "test"; + break; + + case "for-in": + key = "right"; + break; + } + + return function (path) { + if (!path.inList) { + return; + } + + const node = path.node; + const prev = path.getSibling(path.key - 1); + + if (!prev.isExpressionStatement()) { + return; + } + + let seq = prev.node.expression; + + if (node[key]) { + if (t.isSequenceExpression(seq)) { + seq.expressions.push(node[key]); + } else { + seq = t.sequenceExpression([seq, node[key]]); + } + } else { + if (t.isSequenceExpression(seq)) { + const lastExpr = seq.expressions[seq.expressions.length - 1]; + seq.expressions[seq.expressions.length - 1] = t.unaryExpression("void", lastExpr, true); + } else { + seq = t.unaryExpression("void", seq, true); + } + } + + if (seq) { + node[key] = seq; + prev.remove(); // Since we were able to merge some stuff it's possible that this has opened + // oppurtinties for other transforms to happen. + // TODO: Look into changing the traversal order from bottom to up to avoid + // having to revisit things. + + if (path.parentPath.parent) { + path.parentPath.parent[shouldRevisit] = true; + } + } + }; + } // path1 -> path2 + // is path1 an ancestor of path2 + + + function isAncestor(path1, path2) { + return !!path2.findParent(parent => parent === path1); + } + + function isPureVoid(path) { + return path.isUnaryExpression({ + operator: "void" + }) && path.isPure(); + } + + function isGlobalUndefined(path) { + return path.isIdentifier({ + name: "undefined" + }) && !path.scope.getBinding("undefined"); + } + + function undefinedToNull(path) { + if (isGlobalUndefined(path) || isPureVoid(path)) { + path.replaceWith(t.nullLiteral()); + } + } +}; \ No newline at end of file diff --git a/node_modules/babel-plugin-minify-simplify/lib/logical-expression.js b/node_modules/babel-plugin-minify-simplify/lib/logical-expression.js new file mode 100644 index 00000000..a91af230 --- /dev/null +++ b/node_modules/babel-plugin-minify-simplify/lib/logical-expression.js @@ -0,0 +1,75 @@ +"use strict"; + +const h = require("./helpers"); + +const PatternMatch = require("./pattern-match"); + +const evaluate = require("babel-helper-evaluate-path"); + +module.exports = t => { + const OP_AND = input => input === "&&"; + + const OP_OR = input => input === "||"; + + function simplifyPatterns(path) { + // cache of evaluate(path) + const evaluateMemo = new Map(); + + const TRUTHY = input => { + // !NaN and !undefined are truthy + // separate check here as they are considered impure by babel + if (input.isUnaryExpression() && input.get("argument").isIdentifier()) { + if (input.node.argument.name === "NaN" || input.node.argument.name === "undefined") { + return true; + } + } + + const evalResult = evaluate(input); + evaluateMemo.set(input, evalResult); + return evalResult.confident && input.isPure() && evalResult.value; + }; + + const FALSY = input => { + // NaN and undefined are falsy + // separate check here as they are considered impure by babel + if (input.isIdentifier()) { + if (input.node.name === "NaN" || input.node.name === "undefined") { + return true; + } + } + + const evalResult = evaluate(input); + evaluateMemo.set(input, evalResult); + return evalResult.confident && input.isPure() && !evalResult.value; + }; + + const _h$typeSymbols = h.typeSymbols(t), + EX = _h$typeSymbols.Expression; // Convention: + // [left, operator, right, handler(leftNode, rightNode)] + + + const matcher = new PatternMatch([[TRUTHY, OP_AND, EX, (l, r) => r], [FALSY, OP_AND, EX, l => l], [TRUTHY, OP_OR, EX, l => l], [FALSY, OP_OR, EX, (l, r) => r]]); + const left = path.get("left"); + const right = path.get("right"); + const operator = path.node.operator; + const result = matcher.match([left, operator, right], h.isPatternMatchesPath(t)); + + if (result.match) { + // here we are sure that left.evaluate is always confident becuase + // it satisfied one of TRUTHY/FALSY paths + let value; + + if (evaluateMemo.has(left)) { + value = evaluateMemo.get(left).value; + } else { + value = evaluate(left).value; + } + + path.replaceWith(result.value(t.valueToNode(value), right.node)); + } + } + + return { + simplifyPatterns + }; +}; \ No newline at end of file diff --git a/node_modules/babel-plugin-minify-simplify/lib/pattern-match.js b/node_modules/babel-plugin-minify-simplify/lib/pattern-match.js new file mode 100644 index 00000000..e5c34f6d --- /dev/null +++ b/node_modules/babel-plugin-minify-simplify/lib/pattern-match.js @@ -0,0 +1,156 @@ +"use strict"; + +function _toArray(arr) { return _arrayWithHoles(arr) || _iterableToArray(arr) || _nonIterableRest(); } + +function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } + +function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); } + +function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } + +const LEAF_NODE = Symbol("LEAF_NODE"); +module.exports = class PatternMatch { + constructor(patterns) { + this.decisionTree = this.makeDecisionTree(patterns); + } + + handle(input, isMatch) { + const result = this.match(input, isMatch); + + if (!result.match) { + throw new Error("No Match Found for " + input.toString()); + } + + if (typeof result.value !== "function") { + throw new Error("Expecting a function. Instead got - " + result.value.toString()); + } + + result.value.call(null, input, result.keys); + } + + match(input, isMatch = (a, b) => a === b) { + let current = this.decisionTree; + const result = { + match: false, + value: void 0, + keys: [] + }; // to handle falsy keys + + const NO_MATCH = Symbol("NO_MATCH"); + + for (let i = 0; i < input.length; i++) { + let matchedKey = NO_MATCH; // because map doesn't support custom key equal function + + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = current.keys()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + const key = _step.value; + + if (isMatch(key, input[i])) { + matchedKey = key; + result.keys.push(matchedKey); + break; + } + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return != null) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } + } + + if (matchedKey !== NO_MATCH) { + current = current.get(matchedKey); + + if (i === input.length - 1) { + if (current.has(LEAF_NODE)) { + result.match = true; + result.value = current.get(LEAF_NODE); + } + + break; + } + } else { + break; + } + } + + return result; + } + + makeDecisionTree(patterns) { + // order of keys in a Map is the order of insertion + const root = new Map(); + var _iteratorNormalCompletion2 = true; + var _didIteratorError2 = false; + var _iteratorError2 = undefined; + + try { + for (var _iterator2 = patterns[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { + const pattern = _step2.value; + make(root, pattern); + } + } catch (err) { + _didIteratorError2 = true; + _iteratorError2 = err; + } finally { + try { + if (!_iteratorNormalCompletion2 && _iterator2.return != null) { + _iterator2.return(); + } + } finally { + if (_didIteratorError2) { + throw _iteratorError2; + } + } + } + + return root; + + function make(parent, pattern) { + if (pattern.length < 2) { + throw new Error("at least 2 elements required in a pattern"); + } + + if (pattern.length === 2) { + if (parent.has(pattern[0])) { + const pattern0 = parent.get(pattern[0]); + + if (!pattern0.has(LEAF_NODE)) { + pattern0.set(LEAF_NODE, pattern[1]); + } // here we don't handle duplicates + // this pattern would have already been matched + + } else { + parent.set(pattern[0], new Map([[LEAF_NODE, pattern[1]]])); + } + + return parent; + } + + const _pattern = _toArray(pattern), + current = _pattern[0], + rest = _pattern.slice(1); + + if (parent.has(current)) { + make(parent.get(current), rest); + } else { + parent.set(current, make(new Map(), rest)); + } + + return parent; + } + } + +}; \ No newline at end of file -- cgit v1.2.3