You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					306 lines
				
				8.6 KiB
			
		
		
			
		
	
	
					306 lines
				
				8.6 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								"use strict";
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Object.defineProperty(exports, "__esModule", {
							 | 
						||
| 
								 | 
							
								  value: true
							 | 
						||
| 
								 | 
							
								});
							 | 
						||
| 
								 | 
							
								exports.default = convertFunctionRest;
							 | 
						||
| 
								 | 
							
								var _core = require("@babel/core");
							 | 
						||
| 
								 | 
							
								var _shadowUtils = require("./shadow-utils");
							 | 
						||
| 
								 | 
							
								const buildRest = _core.template.statement(`
							 | 
						||
| 
								 | 
							
								  for (var LEN = ARGUMENTS.length,
							 | 
						||
| 
								 | 
							
								           ARRAY = new Array(ARRAY_LEN),
							 | 
						||
| 
								 | 
							
								           KEY = START;
							 | 
						||
| 
								 | 
							
								       KEY < LEN;
							 | 
						||
| 
								 | 
							
								       KEY++) {
							 | 
						||
| 
								 | 
							
								    ARRAY[ARRAY_KEY] = ARGUMENTS[KEY];
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								`);
							 | 
						||
| 
								 | 
							
								const restIndex = _core.template.expression(`
							 | 
						||
| 
								 | 
							
								  (INDEX < OFFSET || ARGUMENTS.length <= INDEX) ? undefined : ARGUMENTS[INDEX]
							 | 
						||
| 
								 | 
							
								`);
							 | 
						||
| 
								 | 
							
								const restIndexImpure = _core.template.expression(`
							 | 
						||
| 
								 | 
							
								  REF = INDEX, (REF < OFFSET || ARGUMENTS.length <= REF) ? undefined : ARGUMENTS[REF]
							 | 
						||
| 
								 | 
							
								`);
							 | 
						||
| 
								 | 
							
								const restLength = _core.template.expression(`
							 | 
						||
| 
								 | 
							
								  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;
							 | 
						||
| 
								 | 
							
								  const parent = path.parent;
							 | 
						||
| 
								 | 
							
								  if (_core.types.isNumericLiteral(parent.property)) {
							 | 
						||
| 
								 | 
							
								    index = _core.types.numericLiteral(parent.property.value + offset);
							 | 
						||
| 
								 | 
							
								  } else if (offset === 0) {
							 | 
						||
| 
								 | 
							
								    index = parent.property;
							 | 
						||
| 
								 | 
							
								  } else {
							 | 
						||
| 
								 | 
							
								    index = _core.types.binaryExpression("+", parent.property, _core.types.cloneNode(offsetLiteral));
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  const {
							 | 
						||
| 
								 | 
							
								    scope,
							 | 
						||
| 
								 | 
							
								    parentPath
							 | 
						||
| 
								 | 
							
								  } = path;
							 | 
						||
| 
								 | 
							
								  if (!scope.isPure(index)) {
							 | 
						||
| 
								 | 
							
								    const temp = scope.generateUidIdentifierBasedOnNode(index);
							 | 
						||
| 
								 | 
							
								    scope.push({
							 | 
						||
| 
								 | 
							
								      id: temp,
							 | 
						||
| 
								 | 
							
								      kind: "var"
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								    parentPath.replaceWith(restIndexImpure({
							 | 
						||
| 
								 | 
							
								      ARGUMENTS: argsId,
							 | 
						||
| 
								 | 
							
								      OFFSET: offsetLiteral,
							 | 
						||
| 
								 | 
							
								      INDEX: index,
							 | 
						||
| 
								 | 
							
								      REF: _core.types.cloneNode(temp)
							 | 
						||
| 
								 | 
							
								    }));
							 | 
						||
| 
								 | 
							
								  } else {
							 | 
						||
| 
								 | 
							
								    parentPath.replaceWith(restIndex({
							 | 
						||
| 
								 | 
							
								      ARGUMENTS: argsId,
							 | 
						||
| 
								 | 
							
								      OFFSET: offsetLiteral,
							 | 
						||
| 
								 | 
							
								      INDEX: index
							 | 
						||
| 
								 | 
							
								    }));
							 | 
						||
| 
								 | 
							
								    const replacedParentPath = parentPath;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const offsetTestPath = replacedParentPath.get("test");
							 | 
						||
| 
								 | 
							
								    const valRes = offsetTestPath.get("left").evaluate();
							 | 
						||
| 
								 | 
							
								    if (valRes.confident) {
							 | 
						||
| 
								 | 
							
								      if (valRes.value === true) {
							 | 
						||
| 
								 | 
							
								        replacedParentPath.replaceWith(scope.buildUndefinedNode());
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        offsetTestPath.replaceWith(offsetTestPath.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;
							 | 
						||
| 
								 | 
							
								  const restPath = path.get(`params.${node.params.length - 1}.argument`);
							 | 
						||
| 
								 | 
							
								  if (!restPath.isIdentifier()) {
							 | 
						||
| 
								 | 
							
								    const shadowedParams = new Set();
							 | 
						||
| 
								 | 
							
								    (0, _shadowUtils.collectShadowedParamsNames)(restPath, path.scope, shadowedParams);
							 | 
						||
| 
								 | 
							
								    let needsIIFE = shadowedParams.size > 0;
							 | 
						||
| 
								 | 
							
								    if (!needsIIFE) {
							 | 
						||
| 
								 | 
							
								      const state = {
							 | 
						||
| 
								 | 
							
								        needsOuterBinding: false,
							 | 
						||
| 
								 | 
							
								        scope
							 | 
						||
| 
								 | 
							
								      };
							 | 
						||
| 
								 | 
							
								      restPath.traverse(_shadowUtils.iifeVisitor, state);
							 | 
						||
| 
								 | 
							
								      needsIIFE = state.needsOuterBinding;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    if (needsIIFE) {
							 | 
						||
| 
								 | 
							
								      path.ensureBlock();
							 | 
						||
| 
								 | 
							
								      path.set("body", _core.types.blockStatement([(0, _shadowUtils.buildScopeIIFE)(shadowedParams, path.node.body)]));
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  let rest = restPath.node;
							 | 
						||
| 
								 | 
							
								  node.params.pop();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (_core.types.isPattern(rest)) {
							 | 
						||
| 
								 | 
							
								    const pattern = rest;
							 | 
						||
| 
								 | 
							
								    rest = scope.generateUidIdentifier("ref");
							 | 
						||
| 
								 | 
							
								    const declar = _core.types.variableDeclaration("let", [_core.types.variableDeclarator(pattern, rest)]);
							 | 
						||
| 
								 | 
							
								    path.ensureBlock();
							 | 
						||
| 
								 | 
							
								    node.body.body.unshift(declar);
							 | 
						||
| 
								 | 
							
								  } else if (rest.name === "arguments") {
							 | 
						||
| 
								 | 
							
								    scope.rename(rest.name);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  const argsId = _core.types.identifier("arguments");
							 | 
						||
| 
								 | 
							
								  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;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								//# sourceMappingURL=rest.js.map
							 |