remove es20xx prefixes from plugins and rename folders (#6575)
This commit is contained in:
29
packages/babel-plugin-transform-parameters/src/index.js
Normal file
29
packages/babel-plugin-transform-parameters/src/index.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import convertFunctionParams from "./params";
|
||||
import convertFunctionRest from "./rest";
|
||||
|
||||
export default function(babel, options) {
|
||||
const { loose } = options;
|
||||
return {
|
||||
visitor: {
|
||||
Function(path) {
|
||||
if (
|
||||
path.isArrowFunctionExpression() &&
|
||||
path
|
||||
.get("params")
|
||||
.some(param => param.isRestElement() || param.isAssignmentPattern())
|
||||
) {
|
||||
// default/rest visitors require access to `arguments`, so it cannot be an arrow
|
||||
path.arrowFunctionToExpression();
|
||||
}
|
||||
|
||||
const convertedRest = convertFunctionRest(path);
|
||||
const convertedParams = convertFunctionParams(path, loose);
|
||||
|
||||
if (convertedRest || convertedParams) {
|
||||
// Manually reprocess this scope to ensure that the moved params are updated.
|
||||
path.scope.crawl();
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
150
packages/babel-plugin-transform-parameters/src/params.js
Normal file
150
packages/babel-plugin-transform-parameters/src/params.js
Normal file
@@ -0,0 +1,150 @@
|
||||
import callDelegate from "@babel/helper-call-delegate";
|
||||
import template from "@babel/template";
|
||||
import * as t from "@babel/types";
|
||||
|
||||
const buildDefaultParam = template(`
|
||||
let VARIABLE_NAME =
|
||||
arguments.length > ARGUMENT_KEY && arguments[ARGUMENT_KEY] !== undefined ?
|
||||
arguments[ARGUMENT_KEY]
|
||||
:
|
||||
DEFAULT_VALUE;
|
||||
`);
|
||||
|
||||
const buildLooseDefaultParam = template(`
|
||||
if (ASSIGNMENT_IDENTIFIER === UNDEFINED) {
|
||||
ASSIGNMENT_IDENTIFIER = DEFAULT_VALUE;
|
||||
}
|
||||
`);
|
||||
|
||||
const buildLooseDestructuredDefaultParam = template(`
|
||||
let ASSIGNMENT_IDENTIFIER = PARAMETER_NAME === UNDEFINED ? DEFAULT_VALUE : PARAMETER_NAME ;
|
||||
`);
|
||||
|
||||
const buildArgumentsAccess = template(`
|
||||
let $0 = arguments[$1];
|
||||
`);
|
||||
|
||||
function isSafeBinding(scope, node) {
|
||||
if (!scope.hasOwnBinding(node.name)) return true;
|
||||
const { kind } = scope.getOwnBinding(node.name);
|
||||
return kind === "param" || kind === "local";
|
||||
}
|
||||
|
||||
const iifeVisitor = {
|
||||
ReferencedIdentifier(path, state) {
|
||||
const { scope, node } = path;
|
||||
if (node.name === "eval" || !isSafeBinding(scope, node)) {
|
||||
state.iife = true;
|
||||
path.stop();
|
||||
}
|
||||
},
|
||||
|
||||
Scope(path) {
|
||||
// different bindings
|
||||
path.skip();
|
||||
},
|
||||
};
|
||||
|
||||
export default function convertFunctionParams(path, loose) {
|
||||
const { node, scope } = path;
|
||||
|
||||
const state = {
|
||||
iife: false,
|
||||
scope: scope,
|
||||
};
|
||||
|
||||
const body = [];
|
||||
const params = path.get("params");
|
||||
|
||||
let firstOptionalIndex = null;
|
||||
|
||||
for (let i = 0; i < params.length; i++) {
|
||||
const param = params[i];
|
||||
|
||||
if (param.isAssignmentPattern() && loose) {
|
||||
const left = param.get("left");
|
||||
const right = param.get("right");
|
||||
|
||||
const undefinedNode = scope.buildUndefinedNode();
|
||||
|
||||
if (left.isIdentifier()) {
|
||||
body.push(
|
||||
buildLooseDefaultParam({
|
||||
ASSIGNMENT_IDENTIFIER: left.node,
|
||||
DEFAULT_VALUE: right.node,
|
||||
UNDEFINED: undefinedNode,
|
||||
}),
|
||||
);
|
||||
param.replaceWith(left.node);
|
||||
} else if (left.isObjectPattern() || left.isArrayPattern()) {
|
||||
const paramName = scope.generateUidIdentifier();
|
||||
body.push(
|
||||
buildLooseDestructuredDefaultParam({
|
||||
ASSIGNMENT_IDENTIFIER: left.node,
|
||||
DEFAULT_VALUE: right.node,
|
||||
PARAMETER_NAME: paramName,
|
||||
UNDEFINED: undefinedNode,
|
||||
}),
|
||||
);
|
||||
param.replaceWith(paramName);
|
||||
}
|
||||
} else if (param.isAssignmentPattern()) {
|
||||
if (firstOptionalIndex === null) firstOptionalIndex = i;
|
||||
|
||||
const left = param.get("left");
|
||||
const right = param.get("right");
|
||||
|
||||
//
|
||||
if (!state.iife) {
|
||||
if (right.isIdentifier() && !isSafeBinding(scope, right.node)) {
|
||||
// the right hand side references a parameter
|
||||
state.iife = true;
|
||||
} else {
|
||||
right.traverse(iifeVisitor, state);
|
||||
}
|
||||
}
|
||||
|
||||
const defNode = buildDefaultParam({
|
||||
VARIABLE_NAME: left.node,
|
||||
DEFAULT_VALUE: right.node,
|
||||
ARGUMENT_KEY: t.numericLiteral(i),
|
||||
});
|
||||
body.push(defNode);
|
||||
} else if (firstOptionalIndex !== null) {
|
||||
const defNode = buildArgumentsAccess([param.node, t.numericLiteral(i)]);
|
||||
body.push(defNode);
|
||||
} else if (param.isObjectPattern() || param.isArrayPattern()) {
|
||||
const uid = path.scope.generateUidIdentifier("ref");
|
||||
|
||||
const defNode = t.variableDeclaration("let", [
|
||||
t.variableDeclarator(param.node, uid),
|
||||
]);
|
||||
body.push(defNode);
|
||||
|
||||
param.replaceWith(uid);
|
||||
}
|
||||
|
||||
if (!state.iife && !param.isIdentifier()) {
|
||||
param.traverse(iifeVisitor, state);
|
||||
}
|
||||
}
|
||||
|
||||
if (body.length === 0) return false;
|
||||
|
||||
// we need to cut off all trailing parameters
|
||||
if (firstOptionalIndex !== null) {
|
||||
node.params = node.params.slice(0, firstOptionalIndex);
|
||||
}
|
||||
|
||||
// ensure it's a block, useful for arrow functions
|
||||
path.ensureBlock();
|
||||
|
||||
if (state.iife) {
|
||||
body.push(callDelegate(path, scope));
|
||||
path.set("body", t.blockStatement(body));
|
||||
} else {
|
||||
path.get("body").unshiftContainer("body", body);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
340
packages/babel-plugin-transform-parameters/src/rest.js
Normal file
340
packages/babel-plugin-transform-parameters/src/rest.js
Normal file
@@ -0,0 +1,340 @@
|
||||
import template from "@babel/template";
|
||||
import * as t from "@babel/types";
|
||||
|
||||
const buildRest = template(`
|
||||
for (var LEN = ARGUMENTS.length,
|
||||
ARRAY = new Array(ARRAY_LEN),
|
||||
KEY = START;
|
||||
KEY < LEN;
|
||||
KEY++) {
|
||||
ARRAY[ARRAY_KEY] = ARGUMENTS[KEY];
|
||||
}
|
||||
`);
|
||||
|
||||
const restIndex = template(`
|
||||
(INDEX < OFFSET || ARGUMENTS.length <= INDEX) ? undefined : ARGUMENTS[INDEX]
|
||||
`);
|
||||
|
||||
const restIndexImpure = template(`
|
||||
REF = INDEX, (REF < OFFSET || ARGUMENTS.length <= REF) ? undefined : ARGUMENTS[REF]
|
||||
`);
|
||||
|
||||
const restLength = template(`
|
||||
ARGUMENTS.length <= OFFSET ? 0 : ARGUMENTS.length - OFFSET
|
||||
`);
|
||||
|
||||
function referencesRest(path, state) {
|
||||
if (path.node.name === state.name) {
|
||||
// Check rest parameter is not shadowed by a binding in another scope.
|
||||
return path.scope.bindingIdentifierEquals(state.name, state.outerBinding);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const memberExpressionOptimisationVisitor = {
|
||||
Scope(path, state) {
|
||||
// check if this scope has a local binding that will shadow the rest parameter
|
||||
if (!path.scope.bindingIdentifierEquals(state.name, state.outerBinding)) {
|
||||
path.skip();
|
||||
}
|
||||
},
|
||||
|
||||
Flow(path) {
|
||||
// Do not skip TypeCastExpressions as the contain valid non flow code
|
||||
if (path.isTypeCastExpression()) return;
|
||||
// don't touch reference in type annotations
|
||||
path.skip();
|
||||
},
|
||||
|
||||
"Function|ClassProperty": function(path, state) {
|
||||
// Detect whether any reference to rest is contained in nested functions to
|
||||
// determine if deopt is necessary.
|
||||
const oldNoOptimise = state.noOptimise;
|
||||
state.noOptimise = true;
|
||||
path.traverse(memberExpressionOptimisationVisitor, state);
|
||||
state.noOptimise = oldNoOptimise;
|
||||
|
||||
// Skip because optimizing references to rest would refer to the `arguments`
|
||||
// of the nested function.
|
||||
path.skip();
|
||||
},
|
||||
|
||||
ReferencedIdentifier(path, state) {
|
||||
const { node } = path;
|
||||
|
||||
// we can't guarantee the purity of arguments
|
||||
if (node.name === "arguments") {
|
||||
state.deopted = true;
|
||||
}
|
||||
|
||||
// is this a referenced identifier and is it referencing the rest parameter?
|
||||
if (!referencesRest(path, state)) return;
|
||||
|
||||
if (state.noOptimise) {
|
||||
state.deopted = true;
|
||||
} else {
|
||||
const { parentPath } = path;
|
||||
|
||||
// Is this identifier the right hand side of a default parameter?
|
||||
if (parentPath.listKey === "params" && parentPath.key < state.offset) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ex: `args[0]`
|
||||
// ex: `args.whatever`
|
||||
if (parentPath.isMemberExpression({ object: node })) {
|
||||
const grandparentPath = parentPath.parentPath;
|
||||
|
||||
const argsOptEligible =
|
||||
!state.deopted &&
|
||||
!// ex: `args[0] = "whatever"`
|
||||
(
|
||||
(grandparentPath.isAssignmentExpression() &&
|
||||
parentPath.node === grandparentPath.node.left) ||
|
||||
// ex: `[args[0]] = ["whatever"]`
|
||||
grandparentPath.isLVal() ||
|
||||
// ex: `for (rest[0] in this)`
|
||||
// ex: `for (rest[0] of this)`
|
||||
grandparentPath.isForXStatement() ||
|
||||
// ex: `++args[0]`
|
||||
// ex: `args[0]--`
|
||||
grandparentPath.isUpdateExpression() ||
|
||||
// ex: `delete args[0]`
|
||||
grandparentPath.isUnaryExpression({ operator: "delete" }) ||
|
||||
// ex: `args[0]()`
|
||||
// ex: `new args[0]()`
|
||||
// ex: `new args[0]`
|
||||
((grandparentPath.isCallExpression() ||
|
||||
grandparentPath.isNewExpression()) &&
|
||||
parentPath.node === grandparentPath.node.callee)
|
||||
);
|
||||
|
||||
if (argsOptEligible) {
|
||||
if (parentPath.node.computed) {
|
||||
// if we know that this member expression is referencing a number then
|
||||
// we can safely optimise it
|
||||
if (parentPath.get("property").isBaseType("number")) {
|
||||
state.candidates.push({ cause: "indexGetter", path });
|
||||
return;
|
||||
}
|
||||
} else if (parentPath.node.property.name === "length") {
|
||||
// args.length
|
||||
state.candidates.push({ cause: "lengthGetter", path });
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we can only do these optimizations if the rest variable would match
|
||||
// the arguments exactly
|
||||
// optimise single spread args in calls
|
||||
// ex: fn(...args)
|
||||
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);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Deopt on use of a binding identifier with the same name as our rest param.
|
||||
*
|
||||
* See https://github.com/babel/babel/issues/2091
|
||||
*/
|
||||
|
||||
BindingIdentifier(path, state) {
|
||||
if (referencesRest(path, state)) {
|
||||
state.deopted = true;
|
||||
}
|
||||
},
|
||||
};
|
||||
function hasRest(node) {
|
||||
const length = node.params.length;
|
||||
return length > 0 && t.isRestElement(node.params[length - 1]);
|
||||
}
|
||||
|
||||
function optimiseIndexGetter(path, argsId, offset) {
|
||||
const offsetLiteral = t.numericLiteral(offset);
|
||||
let index;
|
||||
|
||||
if (t.isNumericLiteral(path.parent.property)) {
|
||||
index = t.numericLiteral(path.parent.property.value + offset);
|
||||
} else if (offset === 0) {
|
||||
// Avoid unnecessary '+ 0'
|
||||
index = path.parent.property;
|
||||
} else {
|
||||
index = t.binaryExpression("+", path.parent.property, 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: temp,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
const parentPath = path.parentPath;
|
||||
parentPath.replaceWith(
|
||||
restIndex({
|
||||
ARGUMENTS: argsId,
|
||||
OFFSET: offsetLiteral,
|
||||
INDEX: index,
|
||||
}),
|
||||
);
|
||||
|
||||
// See if we can statically evaluate the first test (i.e. index < offset)
|
||||
// and optimize the AST accordingly.
|
||||
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: t.numericLiteral(offset),
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
path.replaceWith(argsId);
|
||||
}
|
||||
}
|
||||
|
||||
export default function convertFunctionRest(path) {
|
||||
const { node, scope } = path;
|
||||
if (!hasRest(node)) return false;
|
||||
|
||||
const rest = node.params.pop().argument;
|
||||
|
||||
const argsId = t.identifier("arguments");
|
||||
|
||||
// check and optimise for extremely common cases
|
||||
const state = {
|
||||
references: [],
|
||||
offset: node.params.length,
|
||||
|
||||
argumentsNode: argsId,
|
||||
outerBinding: scope.getBindingIdentifier(rest.name),
|
||||
|
||||
// candidate member expressions we could optimise if there are no other references
|
||||
candidates: [],
|
||||
|
||||
// local rest binding name
|
||||
name: rest.name,
|
||||
|
||||
/*
|
||||
It may be possible to optimize the output code in certain ways, such as
|
||||
not generating code to initialize an array (perhaps substituting direct
|
||||
references to arguments[i] or arguments.length for reads of the
|
||||
corresponding rest parameter property) or positioning the initialization
|
||||
code so that it may not have to execute depending on runtime conditions.
|
||||
|
||||
This property tracks eligibility for optimization. "deopted" means give up
|
||||
and don't perform optimization. For example, when any of rest's elements /
|
||||
properties is assigned to at the top level, or referenced at all in a
|
||||
nested function.
|
||||
*/
|
||||
deopted: false,
|
||||
};
|
||||
|
||||
path.traverse(memberExpressionOptimisationVisitor, state);
|
||||
|
||||
// There are only "shorthand" references
|
||||
if (!state.deopted && !state.references.length) {
|
||||
for (const { path, cause } of (state.candidates: Array)) {
|
||||
switch (cause) {
|
||||
case "indexGetter":
|
||||
optimiseIndexGetter(path, argsId, state.offset);
|
||||
break;
|
||||
case "lengthGetter":
|
||||
optimiseLengthGetter(path, argsId, state.offset);
|
||||
break;
|
||||
default:
|
||||
path.replaceWith(argsId);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
state.references = state.references.concat(
|
||||
state.candidates.map(({ path }) => path),
|
||||
);
|
||||
|
||||
const start = t.numericLiteral(node.params.length);
|
||||
const key = scope.generateUidIdentifier("key");
|
||||
const len = scope.generateUidIdentifier("len");
|
||||
|
||||
let arrKey = key;
|
||||
let arrLen = len;
|
||||
if (node.params.length) {
|
||||
// this method has additional params, so we need to subtract
|
||||
// the index of the current argument position from the
|
||||
// position in the array that we want to populate
|
||||
arrKey = t.binaryExpression("-", key, start);
|
||||
|
||||
// we need to work out the size of the array that we're
|
||||
// going to store all the rest parameters
|
||||
//
|
||||
// we need to add a check to avoid constructing the array
|
||||
// with <0 if there are less arguments than params as it'll
|
||||
// cause an error
|
||||
arrLen = t.conditionalExpression(
|
||||
t.binaryExpression(">", len, start),
|
||||
t.binaryExpression("-", len, start),
|
||||
t.numericLiteral(0),
|
||||
);
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
// don't perform the allocation inside a loop
|
||||
target.findParent(path => {
|
||||
if (path.isLoop()) {
|
||||
target = path;
|
||||
} else {
|
||||
// Stop crawling up if this is a function.
|
||||
return path.isFunction();
|
||||
}
|
||||
});
|
||||
|
||||
target.insertBefore(loop);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
Reference in New Issue
Block a user