Kill the "shadow-functions.js" internal plugin in favor of an explicit helper (#5677)
* Handle arrow function processing via shared API rather than default plugin. * Fix a few small PR comments. * Preserve existing spec arrow 'this' rewrites, and support spec in subclass constructors.
This commit is contained in:
@@ -17,7 +17,6 @@ import buildDebug from "debug";
|
||||
import loadConfig, { type ResolvedConfig } from "../../config";
|
||||
|
||||
import blockHoistPlugin from "../internal-plugins/block-hoist";
|
||||
import shadowFunctionsPlugin from "../internal-plugins/shadow-functions";
|
||||
|
||||
const babelDebug = buildDebug("babel:file");
|
||||
|
||||
@@ -42,11 +41,11 @@ const errorVisitor = {
|
||||
export default class File extends Store {
|
||||
constructor({ options, passes }: ResolvedConfig) {
|
||||
if (!INTERNAL_PLUGINS) {
|
||||
// Lazy-init the internal plugins to remove the init-time circular dependency between plugins being
|
||||
// Lazy-init the internal plugin to remove the init-time circular dependency between plugins being
|
||||
// passed babel-core's export object, which loads this file, and this 'loadConfig' loading plugins.
|
||||
INTERNAL_PLUGINS = loadConfig({
|
||||
babelrc: false,
|
||||
plugins: [ blockHoistPlugin, shadowFunctionsPlugin ],
|
||||
plugins: [ blockHoistPlugin ],
|
||||
}).passes[0];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
import * as t from "babel-types";
|
||||
|
||||
const SUPER_THIS_BOUND = Symbol("super this bound");
|
||||
|
||||
const superVisitor = {
|
||||
CallExpression(path) {
|
||||
if (!path.get("callee").isSuper()) return;
|
||||
|
||||
const { node } = path;
|
||||
if (node[SUPER_THIS_BOUND]) return;
|
||||
node[SUPER_THIS_BOUND] = true;
|
||||
|
||||
path.replaceWith(t.assignmentExpression("=", this.id, node));
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "internal.shadowFunctions",
|
||||
|
||||
visitor: {
|
||||
ThisExpression(path) {
|
||||
remap(path, "this");
|
||||
},
|
||||
|
||||
ReferencedIdentifier(path) {
|
||||
if (path.node.name === "arguments") {
|
||||
remap(path, "arguments");
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function shouldShadow(path, shadowPath) {
|
||||
if (path.is("_forceShadow")) {
|
||||
return true;
|
||||
} else {
|
||||
return shadowPath;
|
||||
}
|
||||
}
|
||||
|
||||
function remap(path, key) {
|
||||
// ensure that we're shadowed
|
||||
const shadowPath = path.inShadow(key);
|
||||
if (!shouldShadow(path, shadowPath)) return;
|
||||
|
||||
const shadowFunction = path.node._shadowedFunctionLiteral;
|
||||
|
||||
let currentFunction;
|
||||
let passedShadowFunction = false;
|
||||
|
||||
let fnPath = path.find(function (innerPath) {
|
||||
if (innerPath.parentPath && innerPath.parentPath.isClassProperty() && innerPath.key === "value") {
|
||||
return true;
|
||||
}
|
||||
if (path === innerPath) return false;
|
||||
if (innerPath.isProgram() || innerPath.isFunction()) {
|
||||
// catch current function in case this is the shadowed one and we can ignore it
|
||||
currentFunction = currentFunction || innerPath;
|
||||
}
|
||||
|
||||
if (innerPath.isProgram()) {
|
||||
passedShadowFunction = true;
|
||||
|
||||
return true;
|
||||
} else if (innerPath.isFunction() && !innerPath.isArrowFunctionExpression()) {
|
||||
if (shadowFunction) {
|
||||
if (innerPath === shadowFunction || innerPath.node === shadowFunction.node) return true;
|
||||
} else {
|
||||
if (!innerPath.is("shadow")) return true;
|
||||
}
|
||||
|
||||
passedShadowFunction = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
if (shadowFunction && fnPath.isProgram() && !shadowFunction.isProgram()) {
|
||||
// If the shadow wasn't found, take the closest function as a backup.
|
||||
// This is a bit of a hack, but it will allow the parameter transforms to work properly
|
||||
// without introducing yet another shadow-controlling flag.
|
||||
fnPath = path.findParent((p) => p.isProgram() || p.isFunction());
|
||||
}
|
||||
|
||||
// no point in realiasing if we're in this function
|
||||
if (fnPath === currentFunction) return;
|
||||
|
||||
// If the only functions that were encountered are arrow functions, skip remapping the
|
||||
// binding since arrow function syntax already does that.
|
||||
if (!passedShadowFunction) return;
|
||||
|
||||
const cached = fnPath.getData(key);
|
||||
if (cached) return path.replaceWith(cached);
|
||||
|
||||
const id = path.scope.generateUidIdentifier(key);
|
||||
|
||||
fnPath.setData(key, id);
|
||||
|
||||
const classPath = fnPath.findParent((p) => p.isClass());
|
||||
const hasSuperClass = !!(classPath && classPath.node && classPath.node.superClass);
|
||||
|
||||
if (key === "this" && fnPath.isMethod({ kind: "constructor" }) && hasSuperClass) {
|
||||
fnPath.scope.push({ id });
|
||||
|
||||
fnPath.traverse(superVisitor, { id });
|
||||
} else {
|
||||
const init = key === "this" ? t.thisExpression() : t.identifier(key);
|
||||
|
||||
// Forward the shadowed function, so that the identifiers do not get hoisted
|
||||
// up to the first non shadow function but rather up to the bound shadow function
|
||||
if (shadowFunction) init._shadowedFunctionLiteral = shadowFunction;
|
||||
|
||||
fnPath.scope.push({ id, init });
|
||||
}
|
||||
|
||||
return path.replaceWith(id);
|
||||
}
|
||||
@@ -27,10 +27,6 @@ const namedBuildWrapper = template(`
|
||||
|
||||
const awaitVisitor = {
|
||||
Function(path) {
|
||||
if (path.isArrowFunctionExpression() && !path.node.async) {
|
||||
path.arrowFunctionToShadowed();
|
||||
return;
|
||||
}
|
||||
path.skip();
|
||||
},
|
||||
|
||||
@@ -84,7 +80,6 @@ function classOrObjectMethod(path: NodePath, callId: Object) {
|
||||
node.async = false;
|
||||
|
||||
const container = t.functionExpression(null, [], t.blockStatement(body.body), true);
|
||||
container.shadow = true;
|
||||
body.body = [
|
||||
t.returnStatement(t.callExpression(
|
||||
t.callExpression(callId, [container]),
|
||||
@@ -95,6 +90,9 @@ function classOrObjectMethod(path: NodePath, callId: Object) {
|
||||
// Regardless of whether or not the wrapped function is a an async method
|
||||
// or generator the outer function should not be
|
||||
node.generator = false;
|
||||
|
||||
// Unwrap the wrapper IIFE's environment so super and this and such still work.
|
||||
path.get("body.body.0.argument.callee.arguments.0").unwrapFunctionEnvironment();
|
||||
}
|
||||
|
||||
function plainFunction(path: NodePath, callId: Object) {
|
||||
@@ -104,7 +102,7 @@ function plainFunction(path: NodePath, callId: Object) {
|
||||
let wrapper = buildWrapper;
|
||||
|
||||
if (path.isArrowFunctionExpression()) {
|
||||
path.arrowFunctionToShadowed();
|
||||
path.arrowFunctionToExpression();
|
||||
} else if (!isDeclaration && asyncFnId) {
|
||||
wrapper = namedBuildWrapper;
|
||||
}
|
||||
@@ -120,7 +118,7 @@ function plainFunction(path: NodePath, callId: Object) {
|
||||
|
||||
const built = t.callExpression(callId, [node]);
|
||||
const container = wrapper({
|
||||
NAME: asyncFnId,
|
||||
NAME: asyncFnId || null,
|
||||
REF: path.scope.generateUidIdentifier("ref"),
|
||||
FUNCTION: built,
|
||||
PARAMS: node.params.reduce((acc, param) => {
|
||||
|
||||
@@ -46,15 +46,11 @@ function getPrototypeOfExpression(objectRef, isStatic) {
|
||||
|
||||
const visitor = {
|
||||
Function(path) {
|
||||
if (!path.inShadow("this")) {
|
||||
path.skip();
|
||||
}
|
||||
if (!path.isArrowFunctionExpression()) path.skip();
|
||||
},
|
||||
|
||||
ReturnStatement(path, state) {
|
||||
if (!path.inShadow("this")) {
|
||||
state.returns.push(path);
|
||||
}
|
||||
state.returns.push(path);
|
||||
},
|
||||
|
||||
ThisExpression(path, state) {
|
||||
|
||||
@@ -40,7 +40,7 @@ export const MESSAGES = {
|
||||
pluginNotObject: "Plugin $2 specified in $1 was expected to return an object when invoked but returned $3",
|
||||
pluginNotFunction: "Plugin $2 specified in $1 was expected to return a function but returned $3",
|
||||
pluginUnknown: "Unknown plugin $1 specified in $2 at $3, attempted to resolve relative to $4",
|
||||
pluginInvalidProperty: "Plugin $1 provided an invalid property of $3",
|
||||
pluginInvalidProperty: "Plugin $1 provided an invalid property of $2",
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,9 +2,7 @@ let g = (() => {
|
||||
var _ref = babelHelpers.asyncGenerator.wrap(function* () {
|
||||
var _this = this;
|
||||
|
||||
(function () {
|
||||
return _this;
|
||||
});
|
||||
() => this;
|
||||
function f() {
|
||||
() => this;
|
||||
}
|
||||
|
||||
@@ -11,11 +11,12 @@ let s = (() => {
|
||||
var _ref2 = babelHelpers.asyncToGenerator(function* (y, a) {
|
||||
let r = (() => {
|
||||
var _ref3 = babelHelpers.asyncToGenerator(function* (z, b) {
|
||||
yield z;
|
||||
|
||||
for (var _len2 = arguments.length, innerArgs = Array(_len2 > 2 ? _len2 - 2 : 0), _key2 = 2; _key2 < _len2; _key2++) {
|
||||
innerArgs[_key2 - 2] = arguments[_key2];
|
||||
}
|
||||
|
||||
yield z;
|
||||
console.log(_this, innerArgs, _arguments);
|
||||
return _this.x;
|
||||
});
|
||||
|
||||
@@ -4,14 +4,10 @@ class Class {
|
||||
|
||||
return babelHelpers.asyncToGenerator(function* () {
|
||||
_this;
|
||||
(function () {
|
||||
return _this;
|
||||
});
|
||||
(function () {
|
||||
() => _this;
|
||||
() => {
|
||||
_this;
|
||||
(function () {
|
||||
return _this;
|
||||
});
|
||||
() => _this;
|
||||
function x() {
|
||||
var _this2 = this;
|
||||
|
||||
@@ -23,7 +19,7 @@ class Class {
|
||||
_this2;
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
function x() {
|
||||
var _this3 = this;
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
class Foo extends class {} {
|
||||
async method() {
|
||||
super.method();
|
||||
|
||||
var arrow = () => super.method();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
class Foo extends class {} {
|
||||
method() {
|
||||
var _superprop_callMethod = (..._args) => super.method(..._args);
|
||||
|
||||
return babelHelpers.asyncToGenerator(function* () {
|
||||
_superprop_callMethod();
|
||||
|
||||
var arrow = () => _superprop_callMethod();
|
||||
})();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"plugins": [
|
||||
"transform-async-to-generator",
|
||||
"external-helpers"
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
let foo = (() => {
|
||||
var _ref = _asyncToGenerator(function* () {
|
||||
yield new _Promise(function (resolve) {
|
||||
yield new _Promise(resolve => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,9 +8,6 @@
|
||||
"keywords": [
|
||||
"babel-plugin"
|
||||
],
|
||||
"dependencies": {
|
||||
"babel-helper-function-name": "7.0.0-alpha.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-helper-plugin-test-runner": "7.0.0-alpha.9",
|
||||
"babel-traverse": "7.0.0-alpha.9",
|
||||
|
||||
@@ -1,43 +1,21 @@
|
||||
// @flow
|
||||
|
||||
import nameFunction from "babel-helper-function-name";
|
||||
import { type NodePath } from "babel-traverse";
|
||||
import typeof * as babelTypes from "babel-types";
|
||||
|
||||
export default function ({ types: t }: { types: babelTypes }) {
|
||||
export default function () {
|
||||
return {
|
||||
visitor: {
|
||||
ArrowFunctionExpression(path: NodePath<BabelNodeArrowFunctionExpression>, state: Object) {
|
||||
if (state.opts.spec) {
|
||||
const { node } = path;
|
||||
if (node.shadow) return;
|
||||
// In some conversion cases, it may have already been converted to a function while this callback
|
||||
// was queued up.
|
||||
if (!path.isArrowFunctionExpression()) return;
|
||||
|
||||
node.shadow = { this: false };
|
||||
node.type = "FunctionExpression";
|
||||
|
||||
const boundThis: any = t.thisExpression();
|
||||
boundThis._forceShadow = path;
|
||||
|
||||
// make sure that arrow function won't be instantiated
|
||||
path.ensureBlock();
|
||||
path.get("body").unshiftContainer(
|
||||
"body",
|
||||
t.expressionStatement(t.callExpression(state.addHelper("newArrowCheck"), [
|
||||
t.thisExpression(),
|
||||
boundThis,
|
||||
]))
|
||||
);
|
||||
|
||||
const replacement = nameFunction(path);
|
||||
const named = replacement || node;
|
||||
|
||||
path.replaceWith(t.callExpression(
|
||||
t.memberExpression(named, t.identifier("bind")),
|
||||
[t.thisExpression()]
|
||||
));
|
||||
} else {
|
||||
path.arrowFunctionToShadowed();
|
||||
}
|
||||
path.arrowFunctionToExpression({
|
||||
// While other utils may be fine inserting other arrows to make more transforms possible,
|
||||
// the arrow transform itself absolutely cannot insert new arrow functions.
|
||||
allowInsertArrow: false,
|
||||
specCompliant: !!state.opts.spec,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -416,29 +416,19 @@ class BlockScoping {
|
||||
// build the closure that we're going to wrap the block with, possible wrapping switch(){}
|
||||
const fn = t.functionExpression(null, params,
|
||||
t.blockStatement(isSwitch ? [block] : block.body));
|
||||
fn.shadow = true;
|
||||
|
||||
// continuation
|
||||
this.addContinuations(fn);
|
||||
|
||||
let ref = fn;
|
||||
|
||||
if (this.loop) {
|
||||
ref = this.scope.generateUidIdentifier("loop");
|
||||
this.loopPath.insertBefore(t.variableDeclaration("var", [
|
||||
t.variableDeclarator(ref, fn),
|
||||
]));
|
||||
}
|
||||
|
||||
// build a call and a unique id that we can assign the return value to
|
||||
let call = t.callExpression(ref, args);
|
||||
const ret = this.scope.generateUidIdentifier("ret");
|
||||
let call = t.callExpression(t.nullLiteral(), args);
|
||||
let basePath = ".callee";
|
||||
|
||||
// handle generators
|
||||
const hasYield = traverse.hasType(fn.body, this.scope, "YieldExpression", t.FUNCTION_TYPES);
|
||||
if (hasYield) {
|
||||
fn.generator = true;
|
||||
call = t.yieldExpression(call, true);
|
||||
basePath = ".argument" + basePath;
|
||||
}
|
||||
|
||||
// handlers async functions
|
||||
@@ -446,26 +436,57 @@ class BlockScoping {
|
||||
if (hasAsync) {
|
||||
fn.async = true;
|
||||
call = t.awaitExpression(call);
|
||||
basePath = ".argument" + basePath;
|
||||
}
|
||||
|
||||
this.buildClosure(ret, call);
|
||||
let placeholderPath;
|
||||
let index;
|
||||
if (this.has.hasReturn || this.has.hasBreakContinue) {
|
||||
const ret = this.scope.generateUidIdentifier("ret");
|
||||
|
||||
// replace the current block body with the one we're going to build
|
||||
if (isSwitch) this.blockPath.replaceWithMultiple(this.body);
|
||||
else block.body = this.body;
|
||||
}
|
||||
this.body.push(t.variableDeclaration("var", [
|
||||
t.variableDeclarator(ret, call),
|
||||
]));
|
||||
placeholderPath = "declarations.0.init" + basePath;
|
||||
index = this.body.length - 1;
|
||||
|
||||
/**
|
||||
* Push the closure to the body.
|
||||
*/
|
||||
|
||||
buildClosure(ret: { type: "Identifier" }, call: { type: "CallExpression" }) {
|
||||
const has = this.has;
|
||||
if (has.hasReturn || has.hasBreakContinue) {
|
||||
this.buildHas(ret, call);
|
||||
this.buildHas(ret);
|
||||
} else {
|
||||
this.body.push(t.expressionStatement(call));
|
||||
placeholderPath = "expression" + basePath;
|
||||
index = this.body.length - 1;
|
||||
}
|
||||
|
||||
let callPath;
|
||||
// replace the current block body with the one we're going to build
|
||||
if (isSwitch) {
|
||||
const { parentPath, listKey, key } = this.blockPath;
|
||||
|
||||
this.blockPath.replaceWithMultiple(this.body);
|
||||
callPath = parentPath.get(listKey)[key + index];
|
||||
} else {
|
||||
block.body = this.body;
|
||||
callPath = this.blockPath.get("body")[index];
|
||||
}
|
||||
|
||||
const placeholder = callPath.get(placeholderPath);
|
||||
|
||||
let fnPath;
|
||||
if (this.loop) {
|
||||
const ref = this.scope.generateUidIdentifier("loop");
|
||||
const p = this.loopPath.insertBefore(t.variableDeclaration("var", [
|
||||
t.variableDeclarator(ref, fn),
|
||||
]));
|
||||
|
||||
placeholder.replaceWith(ref);
|
||||
fnPath = p[0].get("declarations.0.init");
|
||||
} else {
|
||||
placeholder.replaceWith(fn);
|
||||
fnPath = placeholder;
|
||||
}
|
||||
|
||||
// Ensure "this", "arguments", and "super" continue to work in the wrapped function.
|
||||
fnPath.unwrapFunctionEnvironment();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -643,13 +664,9 @@ class BlockScoping {
|
||||
return replace;
|
||||
}
|
||||
|
||||
buildHas(ret: { type: "Identifier" }, call: { type: "CallExpression" }) {
|
||||
buildHas(ret: { type: "Identifier" }) {
|
||||
const body = this.body;
|
||||
|
||||
body.push(t.variableDeclaration("var", [
|
||||
t.variableDeclarator(ret, call),
|
||||
]));
|
||||
|
||||
let retCheck;
|
||||
const has = this.has;
|
||||
const cases = [];
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
(function () {
|
||||
var _loop = function (i) {
|
||||
var _loop2 = function (i) {
|
||||
fns.push(function () {
|
||||
return i;
|
||||
});
|
||||
@@ -14,18 +14,18 @@
|
||||
}
|
||||
};
|
||||
|
||||
_loop2: for (var i in nums) {
|
||||
var _ret = _loop(i);
|
||||
_loop: for (var i in nums) {
|
||||
var _ret = _loop2(i);
|
||||
|
||||
switch (_ret) {
|
||||
case "continue":
|
||||
continue;
|
||||
|
||||
case "break":
|
||||
break _loop2;
|
||||
break _loop;
|
||||
|
||||
default:
|
||||
if (typeof _ret === "object") return _ret.v;
|
||||
}
|
||||
}
|
||||
})();
|
||||
})();
|
||||
@@ -46,6 +46,10 @@ export default function ({ types: t }) {
|
||||
if (state.opts.loose) Constructor = LooseTransformer;
|
||||
|
||||
path.replaceWith(new Constructor(path, state.file).run());
|
||||
|
||||
if (path.isCallExpression() && path.get("callee").isArrowFunctionExpression()) {
|
||||
path.get("callee").arrowFunctionToExpression();
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -14,9 +14,7 @@ const buildDerivedConstructor = template(`
|
||||
|
||||
const noMethodVisitor = {
|
||||
"FunctionExpression|FunctionDeclaration"(path) {
|
||||
if (!path.is("shadow")) {
|
||||
path.skip();
|
||||
}
|
||||
path.skip();
|
||||
},
|
||||
|
||||
Method(path) {
|
||||
@@ -48,7 +46,9 @@ const verifyConstructorVisitor = visitors.merge([noMethodVisitor, {
|
||||
|
||||
ThisExpression(path) {
|
||||
if (this.isDerived && !this.hasBareSuper) {
|
||||
if (!path.inShadow("this")) {
|
||||
const fn = path.find((p) => p.isFunction());
|
||||
|
||||
if (!fn || !fn.isArrowFunctionExpression()) {
|
||||
throw path.buildCodeFrameError("'this' is not allowed before super()");
|
||||
}
|
||||
}
|
||||
@@ -142,8 +142,7 @@ export default class ClassTransformer {
|
||||
//
|
||||
body.push(t.returnStatement(this.classRef));
|
||||
|
||||
const container = t.functionExpression(null, closureParams, t.blockStatement(body));
|
||||
container.shadow = true;
|
||||
const container = t.arrowFunctionExpression(closureParams, t.blockStatement(body));
|
||||
return t.callExpression(container, closureArgs);
|
||||
}
|
||||
|
||||
|
||||
@@ -139,8 +139,7 @@ export default function () {
|
||||
|
||||
if (
|
||||
state.opts.allowTopLevelThis !== true &&
|
||||
!path.findParent((path) => !path.is("shadow") &&
|
||||
THIS_BREAK_KEYS.indexOf(path.type) >= 0)
|
||||
!path.findParent((path) => THIS_BREAK_KEYS.indexOf(path.type) >= 0)
|
||||
) {
|
||||
path.replaceWith(t.identifier("undefined"));
|
||||
}
|
||||
|
||||
@@ -1,46 +1,39 @@
|
||||
import ReplaceSupers from "babel-helper-replace-supers";
|
||||
|
||||
function replacePropertySuper(path, node, scope, getObjectRef, file) {
|
||||
const replaceSupers = new ReplaceSupers({
|
||||
getObjectRef: getObjectRef,
|
||||
methodNode: node,
|
||||
methodPath: path,
|
||||
isStatic: true,
|
||||
scope: scope,
|
||||
file: file,
|
||||
});
|
||||
|
||||
replaceSupers.replace();
|
||||
}
|
||||
|
||||
export default function ({ types: t }) {
|
||||
function Property(path, node, scope, getObjectRef, file) {
|
||||
const replaceSupers = new ReplaceSupers({
|
||||
getObjectRef: getObjectRef,
|
||||
methodNode: node,
|
||||
methodPath: path,
|
||||
isStatic: true,
|
||||
scope: scope,
|
||||
file: file,
|
||||
});
|
||||
|
||||
replaceSupers.replace();
|
||||
}
|
||||
|
||||
const CONTAINS_SUPER = Symbol();
|
||||
|
||||
return {
|
||||
visitor: {
|
||||
Super(path) {
|
||||
const parentObj = path.findParent((path) => path.isObjectExpression());
|
||||
if (parentObj) parentObj.node[CONTAINS_SUPER] = true;
|
||||
},
|
||||
ObjectExpression(path, state) {
|
||||
let objectRef;
|
||||
const getObjectRef = () => objectRef = objectRef || path.scope.generateUidIdentifier("obj");
|
||||
|
||||
ObjectExpression: {
|
||||
exit(path, file) {
|
||||
if (!path.node[CONTAINS_SUPER]) return;
|
||||
|
||||
let objectRef;
|
||||
const getObjectRef = () => objectRef = objectRef || path.scope.generateUidIdentifier("obj");
|
||||
path.get("properties").forEach((propertyPath) => {
|
||||
if (!propertyPath.isMethod()) return;
|
||||
|
||||
const propPaths: Array = path.get("properties");
|
||||
for (let propPath of propPaths) {
|
||||
if (propPath.isObjectProperty()) propPath = propPath.get("value");
|
||||
Property(propPath, propPath.node, path.scope, getObjectRef, file);
|
||||
replacePropertySuper(propPath, propPath.node, path.scope, getObjectRef, state);
|
||||
}
|
||||
});
|
||||
|
||||
if (objectRef) {
|
||||
path.scope.push({ id: objectRef });
|
||||
path.replaceWith(t.assignmentExpression("=", objectRef, path.node));
|
||||
}
|
||||
},
|
||||
if (objectRef) {
|
||||
path.scope.push({ id: objectRef });
|
||||
path.replaceWith(t.assignmentExpression("=", objectRef, path.node));
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -60,7 +60,6 @@ export const visitor = {
|
||||
|
||||
//
|
||||
const argsIdentifier = t.identifier("arguments");
|
||||
argsIdentifier._shadowedFunctionLiteral = path;
|
||||
|
||||
// push a default parameter definition
|
||||
function pushDefNode(left, right, i) {
|
||||
|
||||
@@ -9,11 +9,15 @@ export default function () {
|
||||
return {
|
||||
visitor: visitors.merge([{
|
||||
ArrowFunctionExpression(path) {
|
||||
// In some conversion cases, it may have already been converted to a function while this callback
|
||||
// was queued up.
|
||||
if (!path.isArrowFunctionExpression()) return;
|
||||
|
||||
// default/rest visitors require access to `arguments`
|
||||
const params: Array<NodePath> = path.get("params");
|
||||
for (const param of params) {
|
||||
if (param.isRestElement() || param.isAssignmentPattern()) {
|
||||
path.arrowFunctionToShadowed();
|
||||
path.arrowFunctionToExpression();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,9 +208,6 @@ export const visitor = {
|
||||
|
||||
const argsId = t.identifier("arguments");
|
||||
|
||||
// otherwise `arguments` will be remapped in arrow functions
|
||||
argsId._shadowedFunctionLiteral = path;
|
||||
|
||||
// check and optimise for extremely common cases
|
||||
const state = {
|
||||
references: [],
|
||||
@@ -263,9 +260,6 @@ export const visitor = {
|
||||
state.candidates.map(({ path }) => path)
|
||||
);
|
||||
|
||||
// deopt shadowed functions as transforms like regenerator may try touch the allocation loop
|
||||
state.deopted = state.deopted || !!node.shadow;
|
||||
|
||||
const start = t.numericLiteral(node.params.length);
|
||||
const key = scope.generateUidIdentifier("key");
|
||||
const len = scope.generateUidIdentifier("len");
|
||||
|
||||
@@ -30,11 +30,12 @@ function demo1() {
|
||||
}
|
||||
|
||||
var x = function () {
|
||||
if (noNeedToWork) return 0;
|
||||
|
||||
for (var _len2 = arguments.length, rest = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
|
||||
rest[_key2] = arguments[_key2];
|
||||
}
|
||||
|
||||
if (noNeedToWork) return 0;
|
||||
return rest;
|
||||
};
|
||||
|
||||
|
||||
@@ -11,11 +11,12 @@ var concat = function () {
|
||||
|
||||
var x = function () {
|
||||
var _ref2 = babelHelpers.asyncToGenerator(function* () {
|
||||
if (noNeedToWork) return 0;
|
||||
|
||||
for (var _len = arguments.length, rest = Array(_len), _key = 0; _key < _len; _key++) {
|
||||
rest[_key] = arguments[_key];
|
||||
}
|
||||
|
||||
if (noNeedToWork) return 0;
|
||||
return rest;
|
||||
});
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
var _obj;
|
||||
|
||||
var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } };
|
||||
|
||||
var _set = function set(object, property, value, receiver) { var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent !== null) { set(parent, property, value, receiver); } } else if ("value" in desc && desc.writable) { desc.value = value; } else { var setter = desc.set; if (setter !== undefined) { setter.call(receiver, value); } } return value; };
|
||||
|
||||
var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } };
|
||||
|
||||
foo = _obj = {
|
||||
bar() {
|
||||
return _set(_obj.__proto__ || Object.getPrototypeOf(_obj), "baz", Math.pow(_get(_obj.__proto__ || Object.getPrototypeOf(_obj), "baz", this), 12), this);
|
||||
var _ref;
|
||||
|
||||
return _ref = _get(_obj.__proto__ || Object.getPrototypeOf(_obj), "baz", this), _set(_obj.__proto__ || Object.getPrototypeOf(_obj), "baz", Math.pow(_ref, 12), this);
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -1,15 +1,16 @@
|
||||
"use strict";
|
||||
|
||||
var _this = undefined;
|
||||
"1" + String(a);
|
||||
|
||||
(function () {
|
||||
babelHelpers.newArrowCheck(undefined, undefined);
|
||||
babelHelpers.newArrowCheck(this, _this);
|
||||
}).bind(undefined);
|
||||
|
||||
function a() {
|
||||
var _this = this;
|
||||
var _this2 = this;
|
||||
|
||||
(function () {
|
||||
babelHelpers.newArrowCheck(this, _this);
|
||||
babelHelpers.newArrowCheck(this, _this2);
|
||||
}).bind(this);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"main": "lib/index.js",
|
||||
"dependencies": {
|
||||
"babel-code-frame": "7.0.0-alpha.9",
|
||||
"babel-helper-function-name": "7.0.0-alpha.7",
|
||||
"babel-messages": "7.0.0-alpha.9",
|
||||
"babel-types": "7.0.0-alpha.9",
|
||||
"babylon": "7.0.0-beta.8",
|
||||
|
||||
@@ -200,59 +200,3 @@ export function inType() {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the binding for 'key' is a local binding in its current function context.
|
||||
*
|
||||
* Checks if the current path either is, or has a direct parent function that is, inside
|
||||
* of a function that is marked for shadowing of a binding matching 'key'. Also returns
|
||||
* the parent path if the parent path is an arrow, since arrow functions pass through
|
||||
* binding values to their parent, meaning they have no local bindings.
|
||||
*
|
||||
* Shadowing means that when the given binding is transformed, it will read the binding
|
||||
* value from the container containing the shadow function, rather than from inside the
|
||||
* shadow function.
|
||||
*
|
||||
* Function shadowing is acheieved by adding a "shadow" property on "FunctionExpression"
|
||||
* and "FunctionDeclaration" node types.
|
||||
*
|
||||
* Node's "shadow" props have the following behavior:
|
||||
*
|
||||
* - Boolean true will cause the function to shadow both "this" and "arguments".
|
||||
* - {this: false} Shadows "arguments" but not "this".
|
||||
* - {arguments: false} Shadows "this" but not "arguments".
|
||||
*
|
||||
* Separately, individual identifiers can be flagged with two flags:
|
||||
*
|
||||
* - _forceShadow - If truthy, this specific identifier will be bound in the closest
|
||||
* Function that is not flagged "shadow", or the Program.
|
||||
* - _shadowedFunctionLiteral - When set to a NodePath, this specific identifier will be bound
|
||||
* to this NodePath/Node or the Program. If this path is not found relative to the
|
||||
* starting location path, the closest function will be used.
|
||||
*
|
||||
* Please Note, these flags are for private internal use only and should be avoided.
|
||||
* Only "shadow" is a public property that other transforms may manipulate.
|
||||
*/
|
||||
|
||||
export function inShadow(key?) {
|
||||
const parentFn = this.isFunction() ? this : this.findParent((p) => p.isFunction());
|
||||
if (!parentFn) return;
|
||||
|
||||
if (parentFn.isFunctionExpression() || parentFn.isFunctionDeclaration()) {
|
||||
const shadow = parentFn.node.shadow;
|
||||
|
||||
// this is because sometimes we may have a `shadow` value of:
|
||||
//
|
||||
// { this: false }
|
||||
//
|
||||
// we need to catch this case if `inShadow` has been passed a `key`
|
||||
if (shadow && (!key || shadow[key] !== false)) {
|
||||
return parentFn;
|
||||
}
|
||||
} else if (parentFn.isArrowFunctionExpression()) {
|
||||
return parentFn;
|
||||
}
|
||||
|
||||
// normal function, we've found our function context
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// This file contains methods that convert the path node into another node or some other type of data.
|
||||
|
||||
import * as t from "babel-types";
|
||||
import nameFunction from "babel-helper-function-name";
|
||||
|
||||
export function toComputedKey(): Object {
|
||||
const node = this.node;
|
||||
@@ -25,14 +26,433 @@ export function ensureBlock() {
|
||||
return t.ensureBlock(this.node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Keeping this for backward-compatibility. You should use arrowFunctionToExpression() for >=7.x.
|
||||
*/
|
||||
export function arrowFunctionToShadowed() {
|
||||
// todo: maybe error
|
||||
if (!this.isArrowFunctionExpression()) return;
|
||||
|
||||
this.ensureBlock();
|
||||
|
||||
const { node } = this;
|
||||
node.expression = false;
|
||||
node.type = "FunctionExpression";
|
||||
node.shadow = node.shadow || true;
|
||||
this.arrowFunctionToExpression();
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an arbitrary function, process its content as if it were an arrow function, moving references
|
||||
* to "this", "arguments", "super", and such into the function's parent scope. This method is useful if
|
||||
* you have wrapped some set of items in an IIFE or other function, but want "this", "arguments", and super"
|
||||
* to continue behaving as expected.
|
||||
*/
|
||||
export function unwrapFunctionEnvironment() {
|
||||
if (!this.isArrowFunctionExpression() && !this.isFunctionExpression() && !this.isFunctionDeclaration()) {
|
||||
throw this.buildCodeFrameError("Can only unwrap the environment of a function.");
|
||||
}
|
||||
|
||||
hoistFunctionEnvironment(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a given arrow function into a normal ES5 function expression.
|
||||
*/
|
||||
export function arrowFunctionToExpression({
|
||||
allowInsertArrow = true,
|
||||
specCompliant = false,
|
||||
} = {}) {
|
||||
if (!this.isArrowFunctionExpression()) {
|
||||
throw this.buildCodeFrameError("Cannot convert non-arrow function to a function expression.");
|
||||
}
|
||||
|
||||
const thisBinding = hoistFunctionEnvironment(this, specCompliant, allowInsertArrow);
|
||||
|
||||
this.ensureBlock();
|
||||
this.node.type = "FunctionExpression";
|
||||
if (specCompliant) {
|
||||
const checkBinding = thisBinding ? null : this.parentPath.scope.generateUidIdentifier("arrowCheckId");
|
||||
if (checkBinding) this.parentPath.scope.push({ id: checkBinding, init: t.objectExpression([]) });
|
||||
|
||||
this.get("body").unshiftContainer(
|
||||
"body",
|
||||
t.expressionStatement(t.callExpression(this.hub.file.addHelper("newArrowCheck"), [
|
||||
t.thisExpression(),
|
||||
checkBinding ? t.identifier(checkBinding.name) : t.identifier(thisBinding),
|
||||
]))
|
||||
);
|
||||
|
||||
this.replaceWith(
|
||||
t.callExpression(
|
||||
t.memberExpression(nameFunction(this) || this.node, t.identifier("bind")),
|
||||
[checkBinding ? t.identifier(checkBinding.name) : t.thisExpression()]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a function, traverse its contents, and if there are references to "this", "arguments", "super",
|
||||
* or "new.target", ensure that these references reference the parent environment around this function.
|
||||
*/
|
||||
function hoistFunctionEnvironment(fnPath, specCompliant = false, allowInsertArrow = true) {
|
||||
const thisEnvFn = fnPath.findParent(
|
||||
(p) => (p.isFunction() && !p.isArrowFunctionExpression()) || p.isProgram() || p.isClassProperty());
|
||||
const inConstructor = thisEnvFn && thisEnvFn.node.kind === "constructor";
|
||||
|
||||
if (thisEnvFn.isClassProperty()) {
|
||||
throw fnPath.buildCodeFrameError("Unable to transform arrow inside class property");
|
||||
}
|
||||
|
||||
const {
|
||||
thisPaths,
|
||||
argumentsPaths,
|
||||
newTargetPaths,
|
||||
superProps,
|
||||
superCalls,
|
||||
} = getScopeInformation(fnPath);
|
||||
|
||||
// Convert all super() calls in the constructor, if super is used in an arrow.
|
||||
if (inConstructor && superCalls.length > 0) {
|
||||
if (!allowInsertArrow) {
|
||||
throw superCalls[0].buildCodeFrameError("Unable to handle nested super() usage in arrow");
|
||||
}
|
||||
|
||||
const allSuperCalls = [];
|
||||
thisEnvFn.traverse({
|
||||
Function: (child) => {
|
||||
if (child.isArrowFunctionExpression() || child.isClassProperty() || child === fnPath) return;
|
||||
child.skip();
|
||||
},
|
||||
CallExpression(child) {
|
||||
if (!child.get("callee").isSuper()) return;
|
||||
|
||||
allSuperCalls.push(child);
|
||||
},
|
||||
});
|
||||
|
||||
const superBinding = getSuperBinding(thisEnvFn);
|
||||
|
||||
allSuperCalls.forEach((superCall) => superCall.get("callee").replaceWith(t.identifier(superBinding)));
|
||||
}
|
||||
|
||||
// Convert all "this" references in the arrow to point at the alias.
|
||||
let thisBinding;
|
||||
if (thisPaths.length > 0 || specCompliant) {
|
||||
thisBinding = getThisBinding(thisEnvFn, inConstructor);
|
||||
|
||||
if (
|
||||
!specCompliant ||
|
||||
|
||||
// In subclass constructors, still need to rewrite because "this" can't be bound in spec mode
|
||||
// because it might not have been initialized yet.
|
||||
(inConstructor && hasSuperClass(thisEnvFn))
|
||||
) {
|
||||
thisPaths.forEach((thisChild) => {
|
||||
thisChild.replaceWith(thisChild.isJSX() ? t.jSXIdentifier(thisBinding) : t.identifier(thisBinding));
|
||||
});
|
||||
|
||||
if (specCompliant) thisBinding = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert all "arguments" references in the arrow to point at the alias.
|
||||
if (argumentsPaths.length > 0) {
|
||||
const argumentsBinding = getBinding(thisEnvFn, "arguments",
|
||||
() => t.identifier("arguments"));
|
||||
|
||||
argumentsPaths.forEach((argumentsChild) => {
|
||||
argumentsChild.replaceWith(t.identifier(argumentsBinding));
|
||||
});
|
||||
}
|
||||
|
||||
// Convert all "new.target" references in the arrow to point at the alias.
|
||||
if (newTargetPaths.length > 0) {
|
||||
const newTargetBinding = getBinding(thisEnvFn, "newtarget",
|
||||
() => t.metaProperty(t.identifier("new"), t.identifier("target")));
|
||||
|
||||
newTargetPaths.forEach((argumentsChild) => {
|
||||
argumentsChild.replaceWith(t.identifier(newTargetBinding));
|
||||
});
|
||||
}
|
||||
|
||||
// Convert all "super.prop" references to point at aliases.
|
||||
if (superProps.length > 0) {
|
||||
if (!allowInsertArrow) {
|
||||
throw superProps[0].buildCodeFrameError("Unable to handle nested super.prop usage");
|
||||
}
|
||||
|
||||
const flatSuperProps = superProps.reduce(
|
||||
(acc, superProp) => acc.concat(standardizeSuperProperty(superProp)), []);
|
||||
|
||||
flatSuperProps.forEach((superProp) => {
|
||||
const key = superProp.node.computed ? "" : superProp.get("property").node.name;
|
||||
|
||||
if (superProp.parentPath.isCallExpression({ callee: superProp.node })) {
|
||||
const superBinding = getSuperPropCallBinding(thisEnvFn, key);
|
||||
|
||||
if (superProp.node.computed) {
|
||||
const prop = superProp.get("property").node;
|
||||
superProp.replaceWith(t.identifier(superBinding));
|
||||
superProp.parentPath.node.arguments.unshift(prop);
|
||||
} else {
|
||||
superProp.replaceWith(t.identifier(superBinding));
|
||||
}
|
||||
} else {
|
||||
const isAssignment = superProp.parentPath.isAssignmentExpression({ left: superProp.node });
|
||||
const superBinding = getSuperPropBinding(thisEnvFn, isAssignment, key);
|
||||
|
||||
const args = [];
|
||||
if (superProp.node.computed) {
|
||||
args.push(superProp.get("property").node);
|
||||
}
|
||||
|
||||
if (isAssignment) {
|
||||
const value = superProp.parentPath.node.right;
|
||||
args.push(value);
|
||||
superProp.parentPath.replaceWith(t.callExpression(t.identifier(superBinding), args));
|
||||
} else {
|
||||
superProp.replaceWith(t.callExpression(t.identifier(superBinding), args));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return thisBinding;
|
||||
}
|
||||
|
||||
function standardizeSuperProperty(superProp) {
|
||||
if (superProp.parentPath.isAssignmentExpression() && superProp.parentPath.node.operator !== "=") {
|
||||
const assignmentPath = superProp.parentPath;
|
||||
|
||||
const op = assignmentPath.node.operator.slice(0, -1);
|
||||
const value = assignmentPath.node.right;
|
||||
|
||||
assignmentPath.node.operator = "=";
|
||||
if (superProp.node.computed) {
|
||||
const tmp = superProp.scope.generateDeclaredUidIdentifier("tmp");
|
||||
|
||||
assignmentPath.get("left").replaceWith(
|
||||
t.memberExpression(superProp.node.object,
|
||||
t.assignmentExpression("=", tmp, superProp.node.property), true /* computed */)
|
||||
);
|
||||
|
||||
assignmentPath.get("right").replaceWith(
|
||||
t.binaryExpression(op,
|
||||
t.memberExpression(superProp.node.object, t.identifier(tmp.name), true /* computed */),
|
||||
value,
|
||||
)
|
||||
);
|
||||
} else {
|
||||
assignmentPath.get("left").replaceWith(
|
||||
t.memberExpression(superProp.node.object, superProp.node.property),
|
||||
);
|
||||
|
||||
assignmentPath.get("right").replaceWith(
|
||||
t.binaryExpression(op,
|
||||
t.memberExpression(superProp.node.object, t.identifier(superProp.node.property.name)),
|
||||
value,
|
||||
)
|
||||
);
|
||||
}
|
||||
return [ assignmentPath.get("left"), assignmentPath.get("right").get("left") ];
|
||||
|
||||
} else if (superProp.parentPath.isUpdateExpression()) {
|
||||
const updateExpr = superProp.parentPath;
|
||||
|
||||
const tmp = superProp.scope.generateDeclaredUidIdentifier("tmp");
|
||||
const computedKey = superProp.node.computed ?
|
||||
superProp.scope.generateDeclaredUidIdentifier("prop") : null;
|
||||
|
||||
const parts = [
|
||||
t.assignmentExpression(
|
||||
"=",
|
||||
tmp,
|
||||
t.memberExpression(
|
||||
superProp.node.object,
|
||||
computedKey ?
|
||||
t.assignmentExpression("=", computedKey, superProp.node.property) :
|
||||
superProp.node.property,
|
||||
superProp.node.computed,
|
||||
),
|
||||
),
|
||||
t.assignmentExpression(
|
||||
"=",
|
||||
t.memberExpression(
|
||||
superProp.node.object,
|
||||
computedKey ? t.identifier(computedKey.name) : superProp.node.property,
|
||||
superProp.node.computed,
|
||||
),
|
||||
t.binaryExpression("+", t.identifier(tmp.name), t.numericLiteral(1)),
|
||||
),
|
||||
];
|
||||
|
||||
if (!superProp.parentPath.node.prefix) {
|
||||
parts.push(t.identifier(tmp.name));
|
||||
}
|
||||
|
||||
updateExpr.replaceWith(t.sequenceExpression(parts));
|
||||
|
||||
const left = updateExpr.get("expressions.0.right");
|
||||
const right = updateExpr.get("expressions.1.left");
|
||||
return [ left, right ];
|
||||
}
|
||||
|
||||
return [ superProp ];
|
||||
}
|
||||
|
||||
function hasSuperClass(thisEnvFn) {
|
||||
return thisEnvFn.isClassMethod() && !!thisEnvFn.parentPath.parentPath.node.superClass;
|
||||
}
|
||||
|
||||
// Create a binding that evaluates to the "this" of the given function.
|
||||
function getThisBinding(thisEnvFn, inConstructor) {
|
||||
return getBinding(thisEnvFn, "this", (thisBinding) => {
|
||||
if (!inConstructor || !hasSuperClass(thisEnvFn)) return t.thisExpression();
|
||||
|
||||
const supers = new WeakSet();
|
||||
thisEnvFn.traverse({
|
||||
Function: (child) => {
|
||||
if (child.isArrowFunctionExpression() || child.isClassProperty() || child === this) return;
|
||||
|
||||
child.skip();
|
||||
},
|
||||
CallExpression(child) {
|
||||
if (!child.get("callee").isSuper()) return;
|
||||
if (supers.has(child.node)) return;
|
||||
supers.add(child.node);
|
||||
|
||||
child.replaceWith(t.assignmentExpression("=", t.identifier(thisBinding), child.node));
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Create a binding for a function that will call "super()" with arguments passed through.
|
||||
function getSuperBinding(thisEnvFn) {
|
||||
return getBinding(thisEnvFn, "supercall", () => {
|
||||
const argsBinding = thisEnvFn.scope.generateUidIdentifier("args");
|
||||
return t.arrowFunctionExpression([t.restElement(argsBinding)], t.callExpression(t.super(), [
|
||||
t.spreadElement(t.identifier(argsBinding.name)),
|
||||
]));
|
||||
});
|
||||
}
|
||||
|
||||
// Create a binding for a function that will call "super.foo()" or "super[foo]()".
|
||||
function getSuperPropCallBinding(thisEnvFn, propName) {
|
||||
return getBinding(thisEnvFn, `superprop_call:${propName || ""}`, () => {
|
||||
const argsBinding = thisEnvFn.scope.generateUidIdentifier("args");
|
||||
const argsList = [ t.restElement(argsBinding) ];
|
||||
|
||||
let fnBody;
|
||||
if (propName) {
|
||||
// (...args) => super.foo(...args)
|
||||
fnBody = t.callExpression(
|
||||
t.memberExpression(t.super(), t.identifier(propName)),
|
||||
[ t.spreadElement(t.identifier(argsBinding.name)) ],
|
||||
);
|
||||
} else {
|
||||
const method = thisEnvFn.scope.generateUidIdentifier("prop");
|
||||
// (method, ...args) => super[method](...args)
|
||||
argsList.unshift(method);
|
||||
fnBody = t.callExpression(
|
||||
t.memberExpression(t.super(), t.identifier(method.name), true /* computed */),
|
||||
[ t.spreadElement(t.identifier(argsBinding.name)) ],
|
||||
);
|
||||
}
|
||||
|
||||
return t.arrowFunctionExpression(argsList, fnBody);
|
||||
});
|
||||
}
|
||||
|
||||
// Create a binding for a function that will call "super.foo" or "super[foo]".
|
||||
function getSuperPropBinding(thisEnvFn, isAssignment, propName) {
|
||||
const op = isAssignment ? "set" : "get";
|
||||
|
||||
return getBinding(thisEnvFn, `superprop_${op}:${propName || ""}`, () => {
|
||||
const argsList = [];
|
||||
|
||||
let fnBody;
|
||||
if (propName) {
|
||||
// () => super.foo
|
||||
fnBody = t.memberExpression(t.super(), t.identifier(propName));
|
||||
} else {
|
||||
const method = thisEnvFn.scope.generateUidIdentifier("prop");
|
||||
// (method) => super[method]
|
||||
argsList.unshift(method);
|
||||
fnBody = t.memberExpression(t.super(), t.identifier(method.name), true /* computed */);
|
||||
}
|
||||
|
||||
if (isAssignment) {
|
||||
const valueIdent = thisEnvFn.scope.generateUidIdentifier("value");
|
||||
argsList.push(valueIdent);
|
||||
|
||||
fnBody = t.assignmentExpression("=", fnBody, t.identifier(valueIdent.name));
|
||||
}
|
||||
|
||||
return t.arrowFunctionExpression(argsList, fnBody);
|
||||
});
|
||||
}
|
||||
|
||||
function getBinding(thisEnvFn, key, init) {
|
||||
const cacheKey = "binding:" + key;
|
||||
let data = thisEnvFn.getData(cacheKey);
|
||||
if (!data) {
|
||||
const id = thisEnvFn.scope.generateUidIdentifier(key);
|
||||
data = id.name;
|
||||
thisEnvFn.setData(cacheKey, data);
|
||||
|
||||
thisEnvFn.scope.push({
|
||||
id: id,
|
||||
init: init(data),
|
||||
});
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
function getScopeInformation(fnPath) {
|
||||
const thisPaths = [];
|
||||
const argumentsPaths = [];
|
||||
const newTargetPaths = [];
|
||||
const superProps = [];
|
||||
const superCalls = [];
|
||||
|
||||
fnPath.traverse({
|
||||
Function(child) {
|
||||
if (child.isArrowFunctionExpression() || child.isClassProperty()) return;
|
||||
child.skip();
|
||||
},
|
||||
ThisExpression(child) {
|
||||
thisPaths.push(child);
|
||||
},
|
||||
JSXIdentifier(child) {
|
||||
if (child.node.name !== "this") return;
|
||||
if (
|
||||
!child.parentPath.isJSXMemberExpression({ object: child.node }) &&
|
||||
!child.parentPath.isJSXOpeningElement({ name: child.node })
|
||||
) return;
|
||||
|
||||
thisPaths.push(child);
|
||||
},
|
||||
CallExpression(child) {
|
||||
if (child.get("callee").isSuper()) superCalls.push(child);
|
||||
},
|
||||
MemberExpression(child) {
|
||||
if (child.get("object").isSuper()) superProps.push(child);
|
||||
},
|
||||
ReferencedIdentifier(child) {
|
||||
if (child.node.name !== "arguments") return;
|
||||
|
||||
argumentsPaths.push(child);
|
||||
},
|
||||
MetaProperty(child) {
|
||||
if (!child.get("meta").isIdentifier({ name: "new" })) return;
|
||||
if (!child.get("property").isIdentifier({ name: "target" })) return;
|
||||
|
||||
newTargetPaths.push(child);
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
thisPaths,
|
||||
argumentsPaths,
|
||||
newTargetPaths,
|
||||
superProps,
|
||||
superCalls,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -207,8 +207,7 @@ export function replaceExpressionWithStatements(nodes: Array<Object>) {
|
||||
} else if (toSequenceExpression) {
|
||||
this.replaceWith(toSequenceExpression);
|
||||
} else {
|
||||
const container = t.functionExpression(null, [], t.blockStatement(nodes));
|
||||
container.shadow = true;
|
||||
const container = t.arrowFunctionExpression([], t.blockStatement(nodes));
|
||||
|
||||
this.replaceWith(t.callExpression(container, []));
|
||||
this.traverse(hoistVariablesVisitor);
|
||||
@@ -239,6 +238,8 @@ export function replaceExpressionWithStatements(nodes: Array<Object>) {
|
||||
}
|
||||
}
|
||||
|
||||
this.get("callee").arrowFunctionToExpression();
|
||||
|
||||
return this.node;
|
||||
}
|
||||
}
|
||||
|
||||
498
packages/babel-traverse/test/arrow-transform.js
Normal file
498
packages/babel-traverse/test/arrow-transform.js
Normal file
@@ -0,0 +1,498 @@
|
||||
import { NodePath } from "../lib";
|
||||
import assert from "assert";
|
||||
import { parse } from "babylon";
|
||||
import generate from "babel-generator";
|
||||
import * as t from "babel-types";
|
||||
|
||||
function assertConversion(input, output, {
|
||||
methodName = "method",
|
||||
extend = false,
|
||||
arrowOpts,
|
||||
} = {}) {
|
||||
const inputAst = wrapMethod(input, methodName, extend);
|
||||
const outputAst = wrapMethod(output, methodName, extend);
|
||||
|
||||
const rootPath = NodePath.get({
|
||||
hub: {
|
||||
file: {
|
||||
addHelper(helperName) {
|
||||
return t.memberExpression(t.identifier("babelHelpers"), t.identifier(helperName));
|
||||
},
|
||||
},
|
||||
},
|
||||
parentPath: null,
|
||||
parent: inputAst,
|
||||
container: inputAst,
|
||||
key: "program",
|
||||
}).setContext();
|
||||
|
||||
rootPath.traverse({
|
||||
ClassMethod(path) {
|
||||
path.get("body.body.0.expression").arrowFunctionToExpression(arrowOpts);
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(generate(inputAst).code, generate(outputAst).code);
|
||||
}
|
||||
|
||||
function wrapMethod(body, methodName, extend) {
|
||||
return parse(`
|
||||
class Example ${extend ? ("extends class {}") : ""} {
|
||||
${methodName}() {${body} }
|
||||
}
|
||||
`, { plugins: ["jsx"] });
|
||||
}
|
||||
|
||||
describe("arrow function conversion", () => {
|
||||
it("should convert super calls in constructors", () => {
|
||||
assertConversion(`
|
||||
() => {
|
||||
super(345);
|
||||
};
|
||||
super();
|
||||
() => super();
|
||||
`, `
|
||||
var _supercall = (..._args) => super(..._args);
|
||||
|
||||
(function () {
|
||||
_supercall(345);
|
||||
});
|
||||
_supercall();
|
||||
() => _supercall();
|
||||
`, { methodName: "constructor" });
|
||||
});
|
||||
|
||||
it("should convert super calls and this references in constructors", () => {
|
||||
assertConversion(`
|
||||
() => {
|
||||
super(345);
|
||||
this;
|
||||
};
|
||||
super();
|
||||
this;
|
||||
() => super();
|
||||
() => this;
|
||||
`, `
|
||||
var _supercall = (..._args) => _this = super(..._args),
|
||||
_this;
|
||||
|
||||
(function () {
|
||||
_supercall(345);
|
||||
_this;
|
||||
});
|
||||
_supercall();
|
||||
this;
|
||||
() => _supercall();
|
||||
() => this;
|
||||
`, { methodName: "constructor", extend: true });
|
||||
});
|
||||
|
||||
it("should convert this references in constructors", () => {
|
||||
assertConversion(`
|
||||
() => {
|
||||
this;
|
||||
};
|
||||
super();
|
||||
this;
|
||||
() => super();
|
||||
() => this;
|
||||
`, `
|
||||
var _this;
|
||||
|
||||
(function () {
|
||||
_this;
|
||||
});
|
||||
_this = super();
|
||||
this;
|
||||
() => _this = super();
|
||||
() => this;
|
||||
`, { methodName: "constructor", extend: true });
|
||||
});
|
||||
|
||||
it("should convert this references in constructors with spec compliance", () => {
|
||||
assertConversion(`
|
||||
() => {
|
||||
this;
|
||||
};
|
||||
super();
|
||||
this;
|
||||
() => super();
|
||||
() => this;
|
||||
`, `
|
||||
var _this,
|
||||
_arrowCheckId = {};
|
||||
|
||||
(function () {
|
||||
babelHelpers.newArrowCheck(this, _arrowCheckId);
|
||||
|
||||
_this;
|
||||
}).bind(_arrowCheckId);
|
||||
_this = super();
|
||||
this;
|
||||
() => _this = super();
|
||||
() => this;
|
||||
`, { methodName: "constructor", extend: true, arrowOpts: { specCompliant: true } });
|
||||
});
|
||||
|
||||
it("should convert this references in constructors without extension", () => {
|
||||
assertConversion(`
|
||||
() => {
|
||||
this;
|
||||
};
|
||||
this;
|
||||
() => this;
|
||||
`, `
|
||||
var _this = this;
|
||||
|
||||
(function () {
|
||||
_this;
|
||||
});
|
||||
this;
|
||||
() => this;
|
||||
`, { methodName: "constructor" });
|
||||
});
|
||||
|
||||
it("should convert this references in constructors with spec compliance without extension", () => {
|
||||
assertConversion(`
|
||||
() => {
|
||||
this;
|
||||
};
|
||||
this;
|
||||
() => this;
|
||||
`, `
|
||||
var _this = this;
|
||||
|
||||
(function () {
|
||||
babelHelpers.newArrowCheck(this, _this);
|
||||
|
||||
this;
|
||||
}).bind(this);
|
||||
this;
|
||||
() => this;
|
||||
`, { methodName: "constructor", arrowOpts: { specCompliant: true } });
|
||||
});
|
||||
|
||||
it("should convert this references in methods", () => {
|
||||
assertConversion(`
|
||||
() => {
|
||||
this;
|
||||
};
|
||||
this;
|
||||
() => this;
|
||||
`, `
|
||||
var _this = this;
|
||||
|
||||
(function () {
|
||||
_this;
|
||||
});
|
||||
this;
|
||||
() => this;
|
||||
`);
|
||||
});
|
||||
|
||||
it("should convert this references in methods with spec compliance", () => {
|
||||
assertConversion(`
|
||||
() => {
|
||||
this;
|
||||
};
|
||||
this;
|
||||
() => this;
|
||||
`, `
|
||||
var _this = this;
|
||||
|
||||
(function () {
|
||||
babelHelpers.newArrowCheck(this, _this);
|
||||
|
||||
this;
|
||||
}).bind(this);
|
||||
this;
|
||||
() => this;
|
||||
`, { arrowOpts: { specCompliant: true } });
|
||||
});
|
||||
|
||||
it("should convert this references inside JSX in methods", () => {
|
||||
assertConversion(`
|
||||
() => {
|
||||
<this.this this="" />;
|
||||
};
|
||||
<this.this this="" />;
|
||||
() => <this.this this="" />;
|
||||
`, `
|
||||
var _this = this;
|
||||
|
||||
(function () {
|
||||
<_this.this this="" />;
|
||||
});
|
||||
<this.this this="" />;
|
||||
() => <this.this this="" />;
|
||||
`);
|
||||
});
|
||||
|
||||
it("should convert arguments references", () => {
|
||||
assertConversion(`
|
||||
() => {
|
||||
arguments;
|
||||
};
|
||||
arguments;
|
||||
() => arguments;
|
||||
`, `
|
||||
var _arguments = arguments;
|
||||
|
||||
(function () {
|
||||
_arguments;
|
||||
});
|
||||
arguments;
|
||||
() => arguments;
|
||||
`);
|
||||
});
|
||||
|
||||
it("should convert new.target references", () => {
|
||||
assertConversion(`
|
||||
() => {
|
||||
new.target;
|
||||
};
|
||||
new.target;
|
||||
() => new.target;
|
||||
`, `
|
||||
var _newtarget = new.target;
|
||||
|
||||
(function () {
|
||||
_newtarget;
|
||||
});
|
||||
new.target;
|
||||
() => new.target;
|
||||
`);
|
||||
});
|
||||
|
||||
it("should convert super.prop references", () => {
|
||||
assertConversion(`
|
||||
() => {
|
||||
var tmp = super.foo;
|
||||
};
|
||||
super.foo;
|
||||
() => super.foo;
|
||||
`, `
|
||||
var _superprop_getFoo = () => super.foo;
|
||||
|
||||
(function () {
|
||||
var tmp = _superprop_getFoo();
|
||||
});
|
||||
super.foo;
|
||||
() => super.foo;
|
||||
`);
|
||||
});
|
||||
|
||||
it("should convert super[prop] references", () => {
|
||||
assertConversion(`
|
||||
() => {
|
||||
var tmp = super[foo];
|
||||
};
|
||||
super[foo];
|
||||
() => super[foo];
|
||||
`, `
|
||||
var _superprop_get = _prop => super[_prop];
|
||||
|
||||
(function () {
|
||||
var tmp = _superprop_get(foo);
|
||||
});
|
||||
super[foo];
|
||||
() => super[foo];
|
||||
`);
|
||||
});
|
||||
|
||||
it("should convert super.prop assignment", () => {
|
||||
assertConversion(`
|
||||
() => {
|
||||
super.foo = 4;
|
||||
};
|
||||
super.foo = 4;
|
||||
() => super.foo = 4;
|
||||
`, `
|
||||
var _superprop_setFoo = _value => super.foo = _value;
|
||||
|
||||
(function () {
|
||||
_superprop_setFoo(4);
|
||||
});
|
||||
super.foo = 4;
|
||||
() => super.foo = 4;
|
||||
`);
|
||||
});
|
||||
|
||||
it("should convert super[prop] assignment", () => {
|
||||
assertConversion(`
|
||||
() => {
|
||||
super[foo] = 4;
|
||||
};
|
||||
super[foo] = 4;
|
||||
() => super[foo] = 4;
|
||||
`, `
|
||||
var _superprop_set = (_prop, _value) => super[_prop] = _value;
|
||||
|
||||
(function () {
|
||||
_superprop_set(foo, 4);
|
||||
});
|
||||
super[foo] = 4;
|
||||
() => super[foo] = 4;
|
||||
`);
|
||||
});
|
||||
|
||||
it("should convert super.prop operator assign", () => {
|
||||
assertConversion(`
|
||||
() => {
|
||||
super.foo **= 4;
|
||||
};
|
||||
super.foo **= 4;
|
||||
() => super.foo **= 4;
|
||||
`, `
|
||||
var _superprop_setFoo = _value => super.foo = _value,
|
||||
_superprop_getFoo = () => super.foo;
|
||||
|
||||
(function () {
|
||||
_superprop_setFoo(_superprop_getFoo() ** 4);
|
||||
});
|
||||
super.foo **= 4;
|
||||
() => super.foo **= 4;
|
||||
`);
|
||||
});
|
||||
|
||||
it("should convert super[prop] operator assign", () => {
|
||||
assertConversion(`
|
||||
() => {
|
||||
super[foo] **= 4;
|
||||
};
|
||||
super[foo] **= 4;
|
||||
() => super[foo] **= 4;
|
||||
`, `
|
||||
var _superprop_set = (_prop, _value) => super[_prop] = _value,
|
||||
_superprop_get = _prop2 => super[_prop2];
|
||||
|
||||
(function () {
|
||||
var _tmp;
|
||||
|
||||
_superprop_set(_tmp = foo, _superprop_get(_tmp) ** 4);
|
||||
});
|
||||
super[foo] **= 4;
|
||||
() => super[foo] **= 4;
|
||||
`);
|
||||
});
|
||||
|
||||
it("should convert super.prop prefix update", () => {
|
||||
assertConversion(`
|
||||
() => {
|
||||
++super.foo;
|
||||
};
|
||||
++super.foo;
|
||||
() => ++super.foo;
|
||||
`, `
|
||||
var _superprop_getFoo = () => super.foo,
|
||||
_superprop_setFoo = _value => super.foo = _value;
|
||||
|
||||
(function () {
|
||||
var _tmp;
|
||||
|
||||
_tmp = _superprop_getFoo(), _superprop_setFoo(_tmp + 1);
|
||||
});
|
||||
++super.foo;
|
||||
() => ++super.foo;
|
||||
`);
|
||||
});
|
||||
|
||||
it("should convert super[prop] prefix update", () => {
|
||||
assertConversion(`
|
||||
() => {
|
||||
++super[foo];
|
||||
};
|
||||
++super[foo];
|
||||
() => ++super[foo];
|
||||
`, `
|
||||
var _superprop_get = _prop2 => super[_prop2],
|
||||
_superprop_set = (_prop3, _value) => super[_prop3] = _value;
|
||||
|
||||
(function () {
|
||||
var _tmp, _prop;
|
||||
|
||||
_tmp = _superprop_get(_prop = foo), _superprop_set(_prop, _tmp + 1);
|
||||
});
|
||||
++super[foo];
|
||||
() => ++super[foo];
|
||||
`);
|
||||
});
|
||||
|
||||
it("should convert super.prop suffix update", () => {
|
||||
assertConversion(`
|
||||
() => {
|
||||
super.foo++;
|
||||
};
|
||||
super.foo++;
|
||||
() => super.foo++;
|
||||
`, `
|
||||
var _superprop_getFoo = () => super.foo,
|
||||
_superprop_setFoo = _value => super.foo = _value;
|
||||
|
||||
(function () {
|
||||
var _tmp;
|
||||
|
||||
_tmp = _superprop_getFoo(), _superprop_setFoo(_tmp + 1), _tmp;
|
||||
});
|
||||
super.foo++;
|
||||
() => super.foo++;
|
||||
`);
|
||||
});
|
||||
|
||||
it("should convert super[prop] suffix update", () => {
|
||||
assertConversion(`
|
||||
() => {
|
||||
super[foo]++;
|
||||
};
|
||||
super[foo]++;
|
||||
() => super[foo]++;
|
||||
`, `
|
||||
var _superprop_get = _prop2 => super[_prop2],
|
||||
_superprop_set = (_prop3, _value) => super[_prop3] = _value;
|
||||
|
||||
(function () {
|
||||
var _tmp, _prop;
|
||||
|
||||
_tmp = _superprop_get(_prop = foo), _superprop_set(_prop, _tmp + 1), _tmp;
|
||||
});
|
||||
super[foo]++;
|
||||
() => super[foo]++;
|
||||
`);
|
||||
});
|
||||
|
||||
it("should convert super.prop() calls", () => {
|
||||
assertConversion(`
|
||||
() => {
|
||||
super.foo();
|
||||
};
|
||||
super.foo();
|
||||
() => super.foo();
|
||||
`, `
|
||||
var _superprop_callFoo = (..._args) => super.foo(..._args);
|
||||
|
||||
(function () {
|
||||
_superprop_callFoo();
|
||||
});
|
||||
super.foo();
|
||||
() => super.foo();
|
||||
`);
|
||||
});
|
||||
|
||||
it("should convert super[prop]() calls", () => {
|
||||
assertConversion(`
|
||||
() => {
|
||||
super[foo]();
|
||||
};
|
||||
super[foo]();
|
||||
() => super[foo]();
|
||||
`, `
|
||||
var _superprop_call = (_prop, ..._args) => super[_prop](..._args);
|
||||
|
||||
(function () {
|
||||
_superprop_call(foo);
|
||||
});
|
||||
super[foo]();
|
||||
() => super[foo]();
|
||||
`);
|
||||
});
|
||||
});
|
||||
@@ -245,10 +245,10 @@ defineType("MetaProperty", {
|
||||
fields: {
|
||||
// todo: limit to new.target
|
||||
meta: {
|
||||
validate: assertValueType("string"),
|
||||
validate: assertNodeType("Identifier"),
|
||||
},
|
||||
property: {
|
||||
validate: assertValueType("string"),
|
||||
validate: assertNodeType("Identifier"),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user