diff --git a/packages/babel/src/transformation/file/index.js b/packages/babel/src/transformation/file/index.js index bef7bc07a1..eb5193d9ec 100644 --- a/packages/babel/src/transformation/file/index.js +++ b/packages/babel/src/transformation/file/index.js @@ -85,6 +85,7 @@ export default class File { "extends", "get", "set", + "new-arrow-check", "class-call-check", "object-destructuring-empty", "temporal-undefined", diff --git a/packages/babel/src/transformation/templates/helper-new-arrow-check.js b/packages/babel/src/transformation/templates/helper-new-arrow-check.js new file mode 100644 index 0000000000..3841b83f34 --- /dev/null +++ b/packages/babel/src/transformation/templates/helper-new-arrow-check.js @@ -0,0 +1,5 @@ +(function (innerThis, boundThis) { + if (innerThis !== boundThis) { + throw new TypeError("Cannot instantiate an arrow function"); + } +}); diff --git a/packages/babel/src/transformation/transformers/es6/arrow-functions.js b/packages/babel/src/transformation/transformers/es6/arrow-functions.js index afd375b682..c9e2ba6538 100644 --- a/packages/babel/src/transformation/transformers/es6/arrow-functions.js +++ b/packages/babel/src/transformation/transformers/es6/arrow-functions.js @@ -28,6 +28,6 @@ export var visitor = { this.ensureBlock(); node.expression = false; node.type = "FunctionExpression"; - node.shadow = true; + node.shadow = node.shadow || true; } }; diff --git a/packages/babel/src/transformation/transformers/es6/spec.arrow-functions.js b/packages/babel/src/transformation/transformers/es6/spec.arrow-functions.js new file mode 100644 index 0000000000..d4d5917f96 --- /dev/null +++ b/packages/babel/src/transformation/transformers/es6/spec.arrow-functions.js @@ -0,0 +1,25 @@ +import * as t from "../../../types"; + +export var metadata = { + group: "builtin-pre", + optional: true +}; + +export var visitor = { + ArrowFunctionExpression(node, parent, scope, file) { + if (node.shadow) return; + node.shadow = { this: false }; + + var boundThis = t.thisExpression(); + boundThis._forceShadow = this; + + // make sure that arrow function won't be instantiated + t.ensureBlock(node); + this.get("body").unshiftContainer("body", t.expressionStatement(t.callExpression(file.addHelper("new-arrow-check"), [ + t.thisExpression(), + boundThis + ]))); + + return t.callExpression(t.memberExpression(node, t.identifier("bind")), [t.thisExpression()]); + } +}; diff --git a/packages/babel/src/transformation/transformers/index.js b/packages/babel/src/transformation/transformers/index.js index 1620418d1c..ef2888357f 100644 --- a/packages/babel/src/transformation/transformers/index.js +++ b/packages/babel/src/transformation/transformers/index.js @@ -13,6 +13,7 @@ export default { "minification.deadCodeElimination": require("babel-plugin-dead-code-elimination"), _modules: require("./internal/modules"), "react.displayName": require("babel-plugin-react-display-name"), + "es6.spec.arrowFunctions": require("./es6/spec.arrow-functions"), "es6.spec.templateLiterals": require("./es6/spec.template-literals"), "es6.templateLiterals": require("./es6/template-literals"), "es6.literals": require("./es6/literals"), diff --git a/packages/babel/src/transformation/transformers/internal/shadow-functions.js b/packages/babel/src/transformation/transformers/internal/shadow-functions.js index 207315462c..dfda3b48f8 100644 --- a/packages/babel/src/transformation/transformers/internal/shadow-functions.js +++ b/packages/babel/src/transformation/transformers/internal/shadow-functions.js @@ -4,14 +4,22 @@ export var metadata = { group: "builtin-trailing" }; +function shouldShadow(path, shadowPath) { + if (path.is("_forceShadow")) { + return true; + } else { + return shadowPath && !shadowPath.isArrowFunctionExpression(); + } +} + /** * [Please add a description.] */ function remap(path, key, create) { // ensure that we're shadowed - var shadowPath = path.inShadow(); - if (!shadowPath || shadowPath.isArrowFunctionExpression()) return; + var shadowPath = path.inShadow(key); + if (!shouldShadow(path, shadowPath)) return; var shadowFunction = path.node._shadowedFunctionLiteral; var currentFunction; diff --git a/packages/babel/src/traversal/path/ancestry.js b/packages/babel/src/traversal/path/ancestry.js index 04285cd7b8..c1e4b6f128 100644 --- a/packages/babel/src/traversal/path/ancestry.js +++ b/packages/babel/src/traversal/path/ancestry.js @@ -181,15 +181,24 @@ export function inType() { * Check if we're inside a shadowed function. */ -export function inShadow() { +export function inShadow(key?) { var path = this; while (path) { if (path.isFunction()) { - if (path.node.shadow || path.isArrowFunctionExpression()) { - return path; - } else { - return null; + var shadow = path.node.shadow; + if (shadow || path.isArrowFunctionExpression()) { + // 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 (!key || shadow[key] !== false) { + return path; + } } + + // normal function, we've found our function context + return null; } path = path.parentPath; } diff --git a/packages/babel/test/fixtures/transformation/es6.arrow-functions/spec/actual.js b/packages/babel/test/fixtures/transformation/es6.arrow-functions/spec/actual.js new file mode 100644 index 0000000000..b6f6e218f9 --- /dev/null +++ b/packages/babel/test/fixtures/transformation/es6.arrow-functions/spec/actual.js @@ -0,0 +1,7 @@ +function foo() { + arr.map(x => x * x); + var f = (x, y) => x * y; + (function () { + return () => this; + })(); +} diff --git a/packages/babel/test/fixtures/transformation/es6.arrow-functions/spec/expected.js b/packages/babel/test/fixtures/transformation/es6.arrow-functions/spec/expected.js new file mode 100644 index 0000000000..d34e9d1ef3 --- /dev/null +++ b/packages/babel/test/fixtures/transformation/es6.arrow-functions/spec/expected.js @@ -0,0 +1,22 @@ +"use strict"; + +function foo() { + var _this = this; + + arr.map((function (x) { + babelHelpers.newArrowCheck(this, _this); + return x * x; + }).bind(this)); + var f = (function (x, y) { + babelHelpers.newArrowCheck(this, _this); + return x * y; + }).bind(this); + (function () { + var _this2 = this; + + return (function () { + babelHelpers.newArrowCheck(this, _this2); + return this; + }).bind(this); + })(); +} diff --git a/packages/babel/test/fixtures/transformation/es6.arrow-functions/spec/options.json b/packages/babel/test/fixtures/transformation/es6.arrow-functions/spec/options.json new file mode 100644 index 0000000000..c555242772 --- /dev/null +++ b/packages/babel/test/fixtures/transformation/es6.arrow-functions/spec/options.json @@ -0,0 +1,4 @@ +{ + "externalHelpers": true, + "optional": ["es6.spec.arrowFunctions"] +}