| "use strict"; |
| |
| Object.defineProperty(exports, "__esModule", { |
| value: true |
| }); |
| exports.default = convertFunctionRest; |
| |
| var _core = require("@babel/core"); |
| |
| const buildRest = (0, _core.template)(` |
| for (var LEN = ARGUMENTS.length, |
| ARRAY = new Array(ARRAY_LEN), |
| KEY = START; |
| KEY < LEN; |
| KEY++) { |
| ARRAY[ARRAY_KEY] = ARGUMENTS[KEY]; |
| } |
| `); |
| const restIndex = (0, _core.template)(` |
| (INDEX < OFFSET || ARGUMENTS.length <= INDEX) ? undefined : ARGUMENTS[INDEX] |
| `); |
| const restIndexImpure = (0, _core.template)(` |
| REF = INDEX, (REF < OFFSET || ARGUMENTS.length <= REF) ? undefined : ARGUMENTS[REF] |
| `); |
| const restLength = (0, _core.template)(` |
| ARGUMENTS.length <= OFFSET ? 0 : ARGUMENTS.length - OFFSET |
| `); |
| |
| function referencesRest(path, state) { |
| if (path.node.name === state.name) { |
| return path.scope.bindingIdentifierEquals(state.name, state.outerBinding); |
| } |
| |
| return false; |
| } |
| |
| const memberExpressionOptimisationVisitor = { |
| Scope(path, state) { |
| if (!path.scope.bindingIdentifierEquals(state.name, state.outerBinding)) { |
| path.skip(); |
| } |
| }, |
| |
| Flow(path) { |
| if (path.isTypeCastExpression()) return; |
| path.skip(); |
| }, |
| |
| Function(path, state) { |
| const oldNoOptimise = state.noOptimise; |
| state.noOptimise = true; |
| path.traverse(memberExpressionOptimisationVisitor, state); |
| state.noOptimise = oldNoOptimise; |
| path.skip(); |
| }, |
| |
| ReferencedIdentifier(path, state) { |
| const { |
| node |
| } = path; |
| |
| if (node.name === "arguments") { |
| state.deopted = true; |
| } |
| |
| if (!referencesRest(path, state)) return; |
| |
| if (state.noOptimise) { |
| state.deopted = true; |
| } else { |
| const { |
| parentPath |
| } = path; |
| |
| if (parentPath.listKey === "params" && parentPath.key < state.offset) { |
| return; |
| } |
| |
| if (parentPath.isMemberExpression({ |
| object: node |
| })) { |
| const grandparentPath = parentPath.parentPath; |
| const argsOptEligible = !state.deopted && !(grandparentPath.isAssignmentExpression() && parentPath.node === grandparentPath.node.left || grandparentPath.isLVal() || grandparentPath.isForXStatement() || grandparentPath.isUpdateExpression() || grandparentPath.isUnaryExpression({ |
| operator: "delete" |
| }) || (grandparentPath.isCallExpression() || grandparentPath.isNewExpression()) && parentPath.node === grandparentPath.node.callee); |
| |
| if (argsOptEligible) { |
| if (parentPath.node.computed) { |
| if (parentPath.get("property").isBaseType("number")) { |
| state.candidates.push({ |
| cause: "indexGetter", |
| path |
| }); |
| return; |
| } |
| } else if (parentPath.node.property.name === "length") { |
| state.candidates.push({ |
| cause: "lengthGetter", |
| path |
| }); |
| return; |
| } |
| } |
| } |
| |
| if (state.offset === 0 && parentPath.isSpreadElement()) { |
| const call = parentPath.parentPath; |
| |
| if (call.isCallExpression() && call.node.arguments.length === 1) { |
| state.candidates.push({ |
| cause: "argSpread", |
| path |
| }); |
| return; |
| } |
| } |
| |
| state.references.push(path); |
| } |
| }, |
| |
| BindingIdentifier(path, state) { |
| if (referencesRest(path, state)) { |
| state.deopted = true; |
| } |
| } |
| |
| }; |
| |
| function getParamsCount(node) { |
| let count = node.params.length; |
| |
| if (count > 0 && _core.types.isIdentifier(node.params[0], { |
| name: "this" |
| })) { |
| count -= 1; |
| } |
| |
| return count; |
| } |
| |
| function hasRest(node) { |
| const length = node.params.length; |
| return length > 0 && _core.types.isRestElement(node.params[length - 1]); |
| } |
| |
| function optimiseIndexGetter(path, argsId, offset) { |
| const offsetLiteral = _core.types.numericLiteral(offset); |
| |
| let index; |
| |
| if (_core.types.isNumericLiteral(path.parent.property)) { |
| index = _core.types.numericLiteral(path.parent.property.value + offset); |
| } else if (offset === 0) { |
| index = path.parent.property; |
| } else { |
| index = _core.types.binaryExpression("+", path.parent.property, _core.types.cloneNode(offsetLiteral)); |
| } |
| |
| const { |
| scope |
| } = path; |
| |
| if (!scope.isPure(index)) { |
| const temp = scope.generateUidIdentifierBasedOnNode(index); |
| scope.push({ |
| id: temp, |
| kind: "var" |
| }); |
| path.parentPath.replaceWith(restIndexImpure({ |
| ARGUMENTS: argsId, |
| OFFSET: offsetLiteral, |
| INDEX: index, |
| REF: _core.types.cloneNode(temp) |
| })); |
| } else { |
| const parentPath = path.parentPath; |
| parentPath.replaceWith(restIndex({ |
| ARGUMENTS: argsId, |
| OFFSET: offsetLiteral, |
| INDEX: index |
| })); |
| const offsetTestPath = parentPath.get("test").get("left"); |
| const valRes = offsetTestPath.evaluate(); |
| |
| if (valRes.confident) { |
| if (valRes.value === true) { |
| parentPath.replaceWith(parentPath.scope.buildUndefinedNode()); |
| } else { |
| parentPath.get("test").replaceWith(parentPath.get("test").get("right")); |
| } |
| } |
| } |
| } |
| |
| function optimiseLengthGetter(path, argsId, offset) { |
| if (offset) { |
| path.parentPath.replaceWith(restLength({ |
| ARGUMENTS: argsId, |
| OFFSET: _core.types.numericLiteral(offset) |
| })); |
| } else { |
| path.replaceWith(argsId); |
| } |
| } |
| |
| function convertFunctionRest(path) { |
| const { |
| node, |
| scope |
| } = path; |
| if (!hasRest(node)) return false; |
| let rest = node.params.pop().argument; |
| if (rest.name === "arguments") scope.rename(rest.name); |
| |
| const argsId = _core.types.identifier("arguments"); |
| |
| if (_core.types.isPattern(rest)) { |
| const pattern = rest; |
| rest = scope.generateUidIdentifier("ref"); |
| |
| const declar = _core.types.variableDeclaration("let", [_core.types.variableDeclarator(pattern, rest)]); |
| |
| node.body.body.unshift(declar); |
| } |
| |
| const paramsCount = getParamsCount(node); |
| const state = { |
| references: [], |
| offset: paramsCount, |
| argumentsNode: argsId, |
| outerBinding: scope.getBindingIdentifier(rest.name), |
| candidates: [], |
| name: rest.name, |
| deopted: false |
| }; |
| path.traverse(memberExpressionOptimisationVisitor, state); |
| |
| if (!state.deopted && !state.references.length) { |
| for (const { |
| path, |
| cause |
| } of state.candidates) { |
| const clonedArgsId = _core.types.cloneNode(argsId); |
| |
| switch (cause) { |
| case "indexGetter": |
| optimiseIndexGetter(path, clonedArgsId, state.offset); |
| break; |
| |
| case "lengthGetter": |
| optimiseLengthGetter(path, clonedArgsId, state.offset); |
| break; |
| |
| default: |
| path.replaceWith(clonedArgsId); |
| } |
| } |
| |
| return true; |
| } |
| |
| state.references.push(...state.candidates.map(({ |
| path |
| }) => path)); |
| |
| const start = _core.types.numericLiteral(paramsCount); |
| |
| const key = scope.generateUidIdentifier("key"); |
| const len = scope.generateUidIdentifier("len"); |
| let arrKey, arrLen; |
| |
| if (paramsCount) { |
| arrKey = _core.types.binaryExpression("-", _core.types.cloneNode(key), _core.types.cloneNode(start)); |
| arrLen = _core.types.conditionalExpression(_core.types.binaryExpression(">", _core.types.cloneNode(len), _core.types.cloneNode(start)), _core.types.binaryExpression("-", _core.types.cloneNode(len), _core.types.cloneNode(start)), _core.types.numericLiteral(0)); |
| } else { |
| arrKey = _core.types.identifier(key.name); |
| arrLen = _core.types.identifier(len.name); |
| } |
| |
| const loop = buildRest({ |
| ARGUMENTS: argsId, |
| ARRAY_KEY: arrKey, |
| ARRAY_LEN: arrLen, |
| START: start, |
| ARRAY: rest, |
| KEY: key, |
| LEN: len |
| }); |
| |
| if (state.deopted) { |
| node.body.body.unshift(loop); |
| } else { |
| let target = path.getEarliestCommonAncestorFrom(state.references).getStatementParent(); |
| target.findParent(path => { |
| if (path.isLoop()) { |
| target = path; |
| } else { |
| return path.isFunction(); |
| } |
| }); |
| target.insertBefore(loop); |
| } |
| |
| return true; |
| } |