From 15ae0b669b1cdf4c0911fcfdc38a8a5ac56c36a6 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Tue, 9 Jun 2015 18:18:44 +0300 Subject: [PATCH 1/2] Added spec arrow transformer. Enforces: * `new arrowFn(...)` is disallowed; * `arrowFn.prototype` does not exist. --- src/babel/transformation/file/index.js | 1 + .../templates/helper-new-arrow-check.js | 5 ++++ .../transformers/es6/arrow-functions.js | 2 +- .../transformers/es6/spec.arrow-functions.js | 26 +++++++++++++++++++ .../transformation/transformers/index.js | 1 + .../transformers/internal/shadow-functions.js | 8 +++--- src/babel/traversal/path/ancestry.js | 5 ++-- .../es6.arrow-functions/spec/actual.js | 5 ++++ .../es6.arrow-functions/spec/expected.js | 25 ++++++++++++++++++ .../es6.arrow-functions/spec/options.json | 3 +++ 10 files changed, 75 insertions(+), 6 deletions(-) create mode 100644 src/babel/transformation/templates/helper-new-arrow-check.js create mode 100644 src/babel/transformation/transformers/es6/spec.arrow-functions.js create mode 100644 test/core/fixtures/transformation/es6.arrow-functions/spec/actual.js create mode 100644 test/core/fixtures/transformation/es6.arrow-functions/spec/expected.js create mode 100644 test/core/fixtures/transformation/es6.arrow-functions/spec/options.json diff --git a/src/babel/transformation/file/index.js b/src/babel/transformation/file/index.js index 8abc0cf98d..f6b067e211 100644 --- a/src/babel/transformation/file/index.js +++ b/src/babel/transformation/file/index.js @@ -85,6 +85,7 @@ export default class File { "get", "set", "class-call-check", + "new-arrow-check", "object-destructuring-empty", "temporal-undefined", "temporal-assert-defined", diff --git a/src/babel/transformation/templates/helper-new-arrow-check.js b/src/babel/transformation/templates/helper-new-arrow-check.js new file mode 100644 index 0000000000..a6e9a64308 --- /dev/null +++ b/src/babel/transformation/templates/helper-new-arrow-check.js @@ -0,0 +1,5 @@ +(function (instance, arrowFn) { + if (instance instanceof arrowFn) { + throw new TypeError("Cannot instantiate an arrow function"); + } +}); diff --git a/src/babel/transformation/transformers/es6/arrow-functions.js b/src/babel/transformation/transformers/es6/arrow-functions.js index 39d93f829a..fdbc655231 100644 --- a/src/babel/transformation/transformers/es6/arrow-functions.js +++ b/src/babel/transformation/transformers/es6/arrow-functions.js @@ -5,5 +5,5 @@ export function ArrowFunctionExpression(node) { node.expression = false; node.type = "FunctionExpression"; - node.shadow = true; + node.shadow = node.shadow || true; } diff --git a/src/babel/transformation/transformers/es6/spec.arrow-functions.js b/src/babel/transformation/transformers/es6/spec.arrow-functions.js new file mode 100644 index 0000000000..5a5b07d595 --- /dev/null +++ b/src/babel/transformation/transformers/es6/spec.arrow-functions.js @@ -0,0 +1,26 @@ +import * as t from "../../../types"; + +export var metadata = { + optional: true +}; + +export function ArrowFunctionExpression(node, parent, scope, file) { + if (node.shadow) return; + node.shadow = { this: false }; + + var {id} = node; + var expr = node; + + if (!id) { + id = scope.parent.generateDeclaredUidIdentifier("arrow"); + expr = t.assignmentExpression("=", id, expr); + } + + // make sure that arrow function won't be instantiated + t.ensureBlock(node).body.unshift(t.expressionStatement(t.callExpression(file.addHelper("new-arrow-check"), [ + t.thisExpression(), + id + ]))); + + return t.callExpression(t.memberExpression(expr, t.identifier("bind")), [t.thisExpression()]); +} diff --git a/src/babel/transformation/transformers/index.js b/src/babel/transformation/transformers/index.js index 903bd084b1..6bff7a45c6 100644 --- a/src/babel/transformation/transformers/index.js +++ b/src/babel/transformation/transformers/index.js @@ -26,6 +26,7 @@ export default { "es7.decorators": require("./es7/decorators"), "validation.undeclaredVariableCheck": require("./validation/undeclared-variable-check"), "validation.react": require("./validation/react"), + "es6.spec.arrowFunctions": require("./es6/spec.arrow-functions"), "es6.arrowFunctions": require("./es6/arrow-functions"), "spec.blockScopedFunctions": require("./spec/block-scoped-functions"), "optimisation.react.constantElements": require("./optimisation/react.constant-elements"), diff --git a/src/babel/transformation/transformers/internal/shadow-functions.js b/src/babel/transformation/transformers/internal/shadow-functions.js index c01469eb84..17b3810e14 100644 --- a/src/babel/transformation/transformers/internal/shadow-functions.js +++ b/src/babel/transformation/transformers/internal/shadow-functions.js @@ -6,7 +6,7 @@ export var metadata = { function remap(path, key, create) { // ensure that we're shadowed - if (!path.inShadow()) return; + if (!path.inShadow(key)) return; var fnPath = path.findParent((path) => !path.is("shadow") && (path.isFunction() || path.isProgram())); @@ -22,8 +22,10 @@ function remap(path, key, create) { return id; } -export function ThisExpression() { - return remap(this, "this", () => t.thisExpression()); +export function ThisExpression(node) { + if (!node._shadowedFunctionLiteral) { + return remap(this, "this", () => t.thisExpression()); + } } export function ReferencedIdentifier(node) { diff --git a/src/babel/traversal/path/ancestry.js b/src/babel/traversal/path/ancestry.js index 5077a5ca66..61893d71c2 100644 --- a/src/babel/traversal/path/ancestry.js +++ b/src/babel/traversal/path/ancestry.js @@ -62,11 +62,12 @@ export function inType(types) { * Description */ -export function inShadow() { +export function inShadow(key) { var path = this; while (path) { if (path.isFunction()) { - if (path.node.shadow) { + var {shadow} = path.node; + if (shadow && (shadow === true || shadow[key] !== false)) { return path; } else { return null; diff --git a/test/core/fixtures/transformation/es6.arrow-functions/spec/actual.js b/test/core/fixtures/transformation/es6.arrow-functions/spec/actual.js new file mode 100644 index 0000000000..777f5373be --- /dev/null +++ b/test/core/fixtures/transformation/es6.arrow-functions/spec/actual.js @@ -0,0 +1,5 @@ +arr.map(x => x * x); +var f = (x, y) => x * y; +(function () { + return () => this; +})(); diff --git a/test/core/fixtures/transformation/es6.arrow-functions/spec/expected.js b/test/core/fixtures/transformation/es6.arrow-functions/spec/expected.js new file mode 100644 index 0000000000..3b11cfbb65 --- /dev/null +++ b/test/core/fixtures/transformation/es6.arrow-functions/spec/expected.js @@ -0,0 +1,25 @@ +"use strict"; + +var _arrow; + +function _newArrowCheck(instance, arrowFn) { if (instance instanceof arrowFn) { throw new TypeError("Cannot instantiate an arrow function"); } } + +arr.map((_arrow = function (x) { + _newArrowCheck(this, _arrow); + + return x * x; +}).bind(this)); +var f = (function f(x, y) { + _newArrowCheck(this, f); + + return x * y; +}).bind(this); +(function () { + var _arrow2; + + return (_arrow2 = function () { + _newArrowCheck(this, _arrow2); + + return this; + }).bind(this); +})(); diff --git a/test/core/fixtures/transformation/es6.arrow-functions/spec/options.json b/test/core/fixtures/transformation/es6.arrow-functions/spec/options.json new file mode 100644 index 0000000000..18c0abf7f1 --- /dev/null +++ b/test/core/fixtures/transformation/es6.arrow-functions/spec/options.json @@ -0,0 +1,3 @@ +{ + "optional": ["es6.spec.arrowFunctions"] +} From deaf03dd28a757fe4f20e5649360e659364fee8b Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Wed, 10 Jun 2015 12:28:10 +0300 Subject: [PATCH 2/2] Fixes & optimizations to es6.spec.arrowFunctions. --- .../templates/helper-new-arrow-check.js | 4 ++-- .../transformers/es6/spec.arrow-functions.js | 13 ++++--------- .../transformers/internal/shadow-functions.js | 6 ++---- src/babel/traversal/path/ancestry.js | 6 +++++- .../es6.arrow-functions/spec/expected.js | 19 +++++++------------ .../es6.arrow-functions/spec/options.json | 1 + 6 files changed, 21 insertions(+), 28 deletions(-) diff --git a/src/babel/transformation/templates/helper-new-arrow-check.js b/src/babel/transformation/templates/helper-new-arrow-check.js index a6e9a64308..3841b83f34 100644 --- a/src/babel/transformation/templates/helper-new-arrow-check.js +++ b/src/babel/transformation/templates/helper-new-arrow-check.js @@ -1,5 +1,5 @@ -(function (instance, arrowFn) { - if (instance instanceof arrowFn) { +(function (innerThis, boundThis) { + if (innerThis !== boundThis) { throw new TypeError("Cannot instantiate an arrow function"); } }); diff --git a/src/babel/transformation/transformers/es6/spec.arrow-functions.js b/src/babel/transformation/transformers/es6/spec.arrow-functions.js index 5a5b07d595..6a6b03b595 100644 --- a/src/babel/transformation/transformers/es6/spec.arrow-functions.js +++ b/src/babel/transformation/transformers/es6/spec.arrow-functions.js @@ -8,19 +8,14 @@ export function ArrowFunctionExpression(node, parent, scope, file) { if (node.shadow) return; node.shadow = { this: false }; - var {id} = node; - var expr = node; - - if (!id) { - id = scope.parent.generateDeclaredUidIdentifier("arrow"); - expr = t.assignmentExpression("=", id, expr); - } + var boundThis = t.thisExpression(); + boundThis._shadowedFunctionLiteral = false; // make sure that arrow function won't be instantiated t.ensureBlock(node).body.unshift(t.expressionStatement(t.callExpression(file.addHelper("new-arrow-check"), [ t.thisExpression(), - id + boundThis ]))); - return t.callExpression(t.memberExpression(expr, t.identifier("bind")), [t.thisExpression()]); + return t.callExpression(t.memberExpression(node, t.identifier("bind")), [t.thisExpression()]); } diff --git a/src/babel/transformation/transformers/internal/shadow-functions.js b/src/babel/transformation/transformers/internal/shadow-functions.js index 17b3810e14..17e0a5bce5 100644 --- a/src/babel/transformation/transformers/internal/shadow-functions.js +++ b/src/babel/transformation/transformers/internal/shadow-functions.js @@ -23,13 +23,11 @@ function remap(path, key, create) { } export function ThisExpression(node) { - if (!node._shadowedFunctionLiteral) { - return remap(this, "this", () => t.thisExpression()); - } + return remap(this, "this", () => t.thisExpression()); } export function ReferencedIdentifier(node) { - if (node.name === "arguments" && !node._shadowedFunctionLiteral) { + if (node.name === "arguments") { return remap(this, "arguments", () => t.identifier("arguments")); } } diff --git a/src/babel/traversal/path/ancestry.js b/src/babel/traversal/path/ancestry.js index 61893d71c2..61bac476ab 100644 --- a/src/babel/traversal/path/ancestry.js +++ b/src/babel/traversal/path/ancestry.js @@ -64,9 +64,13 @@ export function inType(types) { export function inShadow(key) { var path = this; + var dontShadow = path.node._shadowedFunctionLiteral; + if (dontShadow !== undefined) { + return !dontShadow; + } while (path) { if (path.isFunction()) { - var {shadow} = path.node; + var { shadow } = path.node; if (shadow && (shadow === true || shadow[key] !== false)) { return path; } else { diff --git a/test/core/fixtures/transformation/es6.arrow-functions/spec/expected.js b/test/core/fixtures/transformation/es6.arrow-functions/spec/expected.js index 3b11cfbb65..c1ecf7a56c 100644 --- a/test/core/fixtures/transformation/es6.arrow-functions/spec/expected.js +++ b/test/core/fixtures/transformation/es6.arrow-functions/spec/expected.js @@ -1,25 +1,20 @@ "use strict"; -var _arrow; - -function _newArrowCheck(instance, arrowFn) { if (instance instanceof arrowFn) { throw new TypeError("Cannot instantiate an arrow function"); } } - -arr.map((_arrow = function (x) { - _newArrowCheck(this, _arrow); +var _this = this; +arr.map((function (x) { + babelHelpers.newArrowCheck(this, _this); return x * x; }).bind(this)); var f = (function f(x, y) { - _newArrowCheck(this, f); - + babelHelpers.newArrowCheck(this, _this); return x * y; }).bind(this); (function () { - var _arrow2; - - return (_arrow2 = function () { - _newArrowCheck(this, _arrow2); + var _this2 = this; + return (function () { + babelHelpers.newArrowCheck(this, _this2); return this; }).bind(this); })(); diff --git a/test/core/fixtures/transformation/es6.arrow-functions/spec/options.json b/test/core/fixtures/transformation/es6.arrow-functions/spec/options.json index 18c0abf7f1..b6a0032986 100644 --- a/test/core/fixtures/transformation/es6.arrow-functions/spec/options.json +++ b/test/core/fixtures/transformation/es6.arrow-functions/spec/options.json @@ -1,3 +1,4 @@ { + "externalHelpers": true, "optional": ["es6.spec.arrowFunctions"] }