From fb67ab9b5dca573e4e1a2de8b6d3ecf4086d545e Mon Sep 17 00:00:00 2001 From: Sebastian McKenzie Date: Fri, 10 Oct 2014 15:49:28 +1100 Subject: [PATCH] make `super` behaviour more spec compliant - fixes #32 --- .../templates/class-super-constructor-call.js | 2 + lib/6to5/transformers/classes.js | 55 ++++++++++--------- package.json | 2 +- test/bin.js | 50 +++++++++++++++++ .../classes/accessing-super-class/actual.js | 4 ++ .../classes/accessing-super-class/expected.js | 3 + .../actual.js | 1 + .../accessing-super-properties/expected.js | 18 ++++++ .../actual.js | 1 + .../calling-super-properties/expected.js | 16 ++++++ .../options.json | 3 - .../no-calling-super-properties/options.json | 3 - .../super-class-member-expression/expected.js | 2 + .../super-class-non-identifiers/expected.js | 4 +- test/fixtures/classes/super-class/expected.js | 1 + 15 files changed, 132 insertions(+), 33 deletions(-) create mode 100644 lib/6to5/templates/class-super-constructor-call.js create mode 100644 test/bin.js rename test/fixtures/classes/{no-accessing-super-properties => accessing-super-properties}/actual.js (82%) create mode 100644 test/fixtures/classes/accessing-super-properties/expected.js rename test/fixtures/classes/{no-calling-super-properties => calling-super-properties}/actual.js (80%) create mode 100644 test/fixtures/classes/calling-super-properties/expected.js delete mode 100644 test/fixtures/classes/no-accessing-super-properties/options.json delete mode 100644 test/fixtures/classes/no-calling-super-properties/options.json diff --git a/lib/6to5/templates/class-super-constructor-call.js b/lib/6to5/templates/class-super-constructor-call.js new file mode 100644 index 0000000000..a7eb7d15dd --- /dev/null +++ b/lib/6to5/templates/class-super-constructor-call.js @@ -0,0 +1,2 @@ +SUPER_NAME.call(this, arguments); + diff --git a/lib/6to5/transformers/classes.js b/lib/6to5/transformers/classes.js index 3c7e12f746..84334d031e 100644 --- a/lib/6to5/transformers/classes.js +++ b/lib/6to5/transformers/classes.js @@ -71,18 +71,22 @@ var buildClass = function (node, generateUid) { var buildClassBody = function (body, className, superName, node) { var instanceMutatorMap = {}; - var staticMutatorMap = {}; + var staticMutatorMap = {}; + var hasConstructor = false; + var construct = body[0]; var classBody = node.body.body; + _.each(classBody, function (node) { var methodName = node.key.name; var method = node.value; - replaceInstanceSuperReferences(superName, method); + replaceInstanceSuperReferences(superName, method, methodName); if (methodName === "constructor") { if (node.kind === "") { - addConstructor(body[0], method); + hasConstructor = true; + addConstructor(construct, method); } else { throw util.errorWithNode(node, "unknown kind for constructor method"); } @@ -103,6 +107,12 @@ var buildClassBody = function (body, className, superName, node) { } }); + if (!hasConstructor && superName) { + construct.body.body.push(util.template("class-super-constructor-call", { + SUPER_NAME: superName + }, true)); + } + if (!_.isEmpty(instanceMutatorMap)) { var protoId = util.template("prototype-identifier", { CLASS_NAME: className @@ -116,51 +126,46 @@ var buildClassBody = function (body, className, superName, node) { } }; -var superIdentifier = function (superName, node, parent) { +var superIdentifier = function (superName, methodName, node, parent) { if (parent.property === node) return; node.name = superName.name || superName.value; - // super(); -> ClassName.call(this); + // super(); -> ClassName.prototype.MethodName.call(this); if (parent.type === "CallExpression" && parent.callee === node) { - node.name += ".call"; + if (methodName === "constructor") { + // constructor() { super(); } + node.name += ".call"; + } else { + // foo() { super(); } + node.name += ".prototype." + methodName + ".call"; + } + parent.arguments.unshift(b.thisExpression()); + } else if (parent.type === "MemberExpression") { + // super.test -> ClassName.prototype.test + node.name += ".prototype"; } }; -var replaceInstanceSuperReferences = function (superName, method) { +var replaceInstanceSuperReferences = function (superName, method, methodName) { superName = superName || b.literal("Function"); traverse(method, function (node, parent) { if (node.type === "Identifier" && node.name === "super") { - superIdentifier(superName, node, parent); - } else if (node.type === "MemberExpression") { - // no accessing of super properties - - if (isAccessingSuperProperties(parent, node)) { - throw util.errorWithNode(node, "cannot access super properties"); - } else { - return; - } + superIdentifier(superName, methodName, node, parent); } else if (node.type === "CallExpression") { var callee = node.callee; if (callee.type !== "MemberExpression") return; if (callee.object.name !== "super") return; - callee.property.name = "prototype." + callee.property.name + ".call"; + // super.test(); -> Classname.prototype.MethodName.call(this); + callee.property.name = callee.property.name + ".call"; node.arguments.unshift(b.thisExpression()); - } else { - return; } }); }; -var isAccessingSuperProperties = function (parent, node) { - var obj = node.object; - return obj.type === "Identifier" && obj.name === "super" && - parent.object === node; -}; - var addConstructor = function (construct, method) { construct.defaults = method.defaults; construct.params = method.params; diff --git a/package.json b/package.json index fbfbe824cd..a947652c69 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "6to5", "description": "Turn ES6 code into vanilla ES5 with source maps and no runtime", - "version": "1.7.4", + "version": "1.7.5", "author": "Sebastian McKenzie ", "homepage": "https://github.com/sebmck/6to5", "repository": { diff --git a/test/bin.js b/test/bin.js new file mode 100644 index 0000000000..8099967e35 --- /dev/null +++ b/test/bin.js @@ -0,0 +1,50 @@ +var child = require("child_process"); +var fs = require("fs"); + +var tmpLoc = __dirname + "/tmp"; + +var readTree = function () { + +}; + +var run = function (name, args, callback) { + args = [__dirname + "/../bin." + name].concat(args); + var spawn = child.spawn(process.execPath, args); + + var data = ""; + + spawn.stdout.on("write", function (chunk) { + data += chunk; + }); + + spawn.on("close", function () { + callback(data); + }); +}; + +before(function () { + if (!fs.existsSync(tmpLoc)) fs.mkdirSync(tmpLoc); + process.chdir(tmpLoc); +}); + +suite("bin/6to5", function () { + test("--source-maps-inline"); + + test("--source-maps"); + + test("--whitelist"); + + test("--blacklist"); + + test("--out-file"); + + test("--out-dir"); + + test("stdout"); +}); + +suite("bin/6to5-node", function () { + test("--eval"); + + test("--print"); +}); diff --git a/test/fixtures/classes/accessing-super-class/actual.js b/test/fixtures/classes/accessing-super-class/actual.js index fee3fe2867..88c57722af 100644 --- a/test/fixtures/classes/accessing-super-class/actual.js +++ b/test/fixtures/classes/accessing-super-class/actual.js @@ -5,4 +5,8 @@ class Test extends Foo { super.test(); foob(super); } + + test() { + super(); + } } diff --git a/test/fixtures/classes/accessing-super-class/expected.js b/test/fixtures/classes/accessing-super-class/expected.js index 80530189c9..859d18e34e 100644 --- a/test/fixtures/classes/accessing-super-class/expected.js +++ b/test/fixtures/classes/accessing-super-class/expected.js @@ -14,5 +14,8 @@ var Test = function (Foo) { } }); Test.__proto__ = Foo; + Test.prototype.test = function () { + Foo.prototype.test.call(this); + }; return Test; }(Foo); diff --git a/test/fixtures/classes/no-accessing-super-properties/actual.js b/test/fixtures/classes/accessing-super-properties/actual.js similarity index 82% rename from test/fixtures/classes/no-accessing-super-properties/actual.js rename to test/fixtures/classes/accessing-super-properties/actual.js index 51195a3c6f..524a05eaa4 100644 --- a/test/fixtures/classes/no-accessing-super-properties/actual.js +++ b/test/fixtures/classes/accessing-super-properties/actual.js @@ -1,5 +1,6 @@ class Test extends Foo { constructor() { + super.test; super.test.whatever; } } diff --git a/test/fixtures/classes/accessing-super-properties/expected.js b/test/fixtures/classes/accessing-super-properties/expected.js new file mode 100644 index 0000000000..4c028e72e3 --- /dev/null +++ b/test/fixtures/classes/accessing-super-properties/expected.js @@ -0,0 +1,18 @@ +var Test = function(Foo) { + function Test() { + Foo.prototype.test; + Foo.prototype.test.whatever; + } + + Test.prototype = Object.create(Foo.prototype, { + constructor: { + value: Test, + enumerable: false, + writable: true, + configurable: true + } + }); + + Test.__proto__ = Foo; + return Test; +}(Foo); diff --git a/test/fixtures/classes/no-calling-super-properties/actual.js b/test/fixtures/classes/calling-super-properties/actual.js similarity index 80% rename from test/fixtures/classes/no-calling-super-properties/actual.js rename to test/fixtures/classes/calling-super-properties/actual.js index 7e8774ef63..2417f793dd 100644 --- a/test/fixtures/classes/no-calling-super-properties/actual.js +++ b/test/fixtures/classes/calling-super-properties/actual.js @@ -1,5 +1,6 @@ class Test extends Foo { constructor() { super.test.whatever(); + super.test(); } } diff --git a/test/fixtures/classes/calling-super-properties/expected.js b/test/fixtures/classes/calling-super-properties/expected.js new file mode 100644 index 0000000000..35502a75ca --- /dev/null +++ b/test/fixtures/classes/calling-super-properties/expected.js @@ -0,0 +1,16 @@ +var Test = function(Foo) { + function Test() { + Foo.prototype.test.whatever(); + Foo.prototype.test.call(this); + } + Test.prototype = Object.create(Foo.prototype, { + constructor: { + value: Test, + enumerable: false, + writable: true, + configurable: true + } + }); + Test.__proto__ = Foo; + return Test; +}(Foo); diff --git a/test/fixtures/classes/no-accessing-super-properties/options.json b/test/fixtures/classes/no-accessing-super-properties/options.json deleted file mode 100644 index 62d82f26d2..0000000000 --- a/test/fixtures/classes/no-accessing-super-properties/options.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "throws": "cannot access super properties" -} diff --git a/test/fixtures/classes/no-calling-super-properties/options.json b/test/fixtures/classes/no-calling-super-properties/options.json deleted file mode 100644 index 62d82f26d2..0000000000 --- a/test/fixtures/classes/no-calling-super-properties/options.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "throws": "cannot access super properties" -} diff --git a/test/fixtures/classes/super-class-member-expression/expected.js b/test/fixtures/classes/super-class-member-expression/expected.js index 5da82ceb90..627d32a72c 100644 --- a/test/fixtures/classes/super-class-member-expression/expected.js +++ b/test/fixtures/classes/super-class-member-expression/expected.js @@ -1,5 +1,6 @@ var BaseController = function (Chaplin) { function BaseController() { + Chaplin.Controller.call(this, arguments); } BaseController.prototype = Object.create(Chaplin.Controller.prototype, { constructor: { @@ -15,6 +16,7 @@ var BaseController = function (Chaplin) { var BaseController2 = function (Chaplin) { function BaseController2() { + Chaplin.Controller.Another.call(this, arguments); } BaseController2.prototype = Object.create(Chaplin.Controller.Another.prototype, { constructor: { diff --git a/test/fixtures/classes/super-class-non-identifiers/expected.js b/test/fixtures/classes/super-class-non-identifiers/expected.js index 75c981cdf5..92db8b595e 100644 --- a/test/fixtures/classes/super-class-non-identifiers/expected.js +++ b/test/fixtures/classes/super-class-non-identifiers/expected.js @@ -1,5 +1,7 @@ var Q = function(_ref) { - function Q() {} + function Q() { + _ref.call(this, arguments); + } Q.prototype = Object.create(_ref.prototype, { constructor: { diff --git a/test/fixtures/classes/super-class/expected.js b/test/fixtures/classes/super-class/expected.js index db70734e89..02d0a2d361 100644 --- a/test/fixtures/classes/super-class/expected.js +++ b/test/fixtures/classes/super-class/expected.js @@ -1,5 +1,6 @@ var Test = function (Foo) { function Test() { + Foo.call(this, arguments); } Test.prototype = Object.create(Foo.prototype, { constructor: {