From 274a6e01dcab33ded9deeaea723752557be7d8cd Mon Sep 17 00:00:00 2001 From: Sebastian McKenzie Date: Sat, 11 Apr 2015 19:47:11 -0700 Subject: [PATCH] add support for inheriting from statically inlined native constructors - closes #1172 --- src/babel/transformation/templates/.babelrc | 2 +- .../class-super-native-constructor-call.js | 5 +++ .../transformers/es6/classes.js | 44 +++++++++++++++++-- .../es6.classes/native-constructors/actual.js | 10 +++++ .../es6.classes/native-constructors/exec.js | 27 ++++++++++++ .../native-constructors/expected.js | 35 +++++++++++++++ 6 files changed, 118 insertions(+), 5 deletions(-) create mode 100644 src/babel/transformation/templates/class-super-native-constructor-call.js create mode 100644 test/core/fixtures/transformation/es6.classes/native-constructors/actual.js create mode 100644 test/core/fixtures/transformation/es6.classes/native-constructors/exec.js create mode 100644 test/core/fixtures/transformation/es6.classes/native-constructors/expected.js diff --git a/src/babel/transformation/templates/.babelrc b/src/babel/transformation/templates/.babelrc index ad84f4d5b9..43cbf773c1 100644 --- a/src/babel/transformation/templates/.babelrc +++ b/src/babel/transformation/templates/.babelrc @@ -1,4 +1,4 @@ { - "blacklist": ["useStrict", "es6.blockScoping", "regenerator"], + "blacklist": ["useStrict", "es6.blockScoping", "regenerator", "es6.spread"], "loose": ["es6.modules"] } diff --git a/src/babel/transformation/templates/class-super-native-constructor-call.js b/src/babel/transformation/templates/class-super-native-constructor-call.js new file mode 100644 index 0000000000..596467e045 --- /dev/null +++ b/src/babel/transformation/templates/class-super-native-constructor-call.js @@ -0,0 +1,5 @@ +if (SUPER_NAME != null) { + var NATIVE_REF = new SUPER_NAME(...arguments); + NATIVE_REF.__proto__ = CLASS_NAME.prototype; + return NATIVE_REF; +} diff --git a/src/babel/transformation/transformers/es6/classes.js b/src/babel/transformation/transformers/es6/classes.js index ceed3114a3..47cb9777d2 100644 --- a/src/babel/transformation/transformers/es6/classes.js +++ b/src/babel/transformation/transformers/es6/classes.js @@ -79,6 +79,10 @@ var verifyConstructorVisitor = traverse.explode({ if (state.hasSuper && !state.hasBareSuper) { throw this.errorWithNode("'this' is not allowed before super()"); } + + if (state.isNativeSuper) { + return state.nativeSuperRef; + } } } }); @@ -133,6 +137,15 @@ class ClassTransformer { // + var superClass = this.node.superClass; + this.isNativeSuper = superClass && t.isIdentifier(superClass) && t.NATIVE_TYPE_NAMES.indexOf(superClass.name) >= 0; + + if (this.isNativeSuper) { + this.nativeSuperRef = this.scope.generateUidIdentifier("this"); + } + + // + var body = this.body; // @@ -203,6 +216,10 @@ class ClassTransformer { } } + if (this.isNativeSuper) { + constructorBody.body.push(t.returnStatement(this.nativeSuperRef)); + } + if (this.className) { // named class with only a constructor if (body.length === 1) return t.toExpression(body[0]); @@ -303,7 +320,9 @@ class ClassTransformer { if (!this.hasConstructor && this.hasSuper) { var helperName = "class-super-constructor-call"; if (this.isLoose) helperName += "-loose"; + if (this.isNativeSuper) helperName = "class-super-native-constructor-call"; constructorBody.body.push(util.template(helperName, { + NATIVE_REF: this.nativeSuperRef, CLASS_NAME: className, SUPER_NAME: this.superName }, true)); @@ -432,10 +451,12 @@ class ClassTransformer { verifyConstructor(path: TraversalPath) { var state = { - hasBareSuper: false, - bareSuper: null, - hasSuper: this.hasSuper, - file: this.file + nativeSuperRef: this.nativeSuperRef, + isNativeSuper: this.isNativeSuper, + hasBareSuper: false, + bareSuper: null, + hasSuper: this.hasSuper, + file: this.file }; path.get("value").traverse(verifyConstructorVisitor, state); @@ -445,6 +466,21 @@ class ClassTransformer { if (!state.hasBareSuper && this.hasSuper) { throw path.errorWithNode("Derived constructor must call super()"); } + + if (this.isNativeSuper && this.bareSuper) { + this.bareSuper.replaceWithMultiple([ + t.variableDeclaration("var", [ + t.variableDeclarator(this.nativeSuperRef, t.newExpression(this.superName, this.bareSuper.node.arguments)) + ]), + + t.expressionStatement(t.assignmentExpression( + "=", + t.memberExpression(this.nativeSuperRef, t.identifier("__proto__")), + t.memberExpression(this.classRef, t.identifier("prototype")) + )), + t.expressionStatement(this.nativeSuperRef) + ]); + } } /** diff --git a/test/core/fixtures/transformation/es6.classes/native-constructors/actual.js b/test/core/fixtures/transformation/es6.classes/native-constructors/actual.js new file mode 100644 index 0000000000..17e9a3582b --- /dev/null +++ b/test/core/fixtures/transformation/es6.classes/native-constructors/actual.js @@ -0,0 +1,10 @@ +class Foo extends Array { + +} + +class Bar extends Array { + constructor() { + super(); + this.foo = "bar"; + } +} diff --git a/test/core/fixtures/transformation/es6.classes/native-constructors/exec.js b/test/core/fixtures/transformation/es6.classes/native-constructors/exec.js new file mode 100644 index 0000000000..0bccf26aa8 --- /dev/null +++ b/test/core/fixtures/transformation/es6.classes/native-constructors/exec.js @@ -0,0 +1,27 @@ +class Foo extends Array { + +} + +class Bar extends Array { + constructor() { + super(); + this.foo = "bar"; + } +} + +var foo = new Foo; +assert.ok(Array.isArray(foo)); +foo.push(1); +foo.push(2); +assert.equal(foo[0], 1); +assert.equal(foo[1], 2); +assert.equal(foo.length, 2); + +var bar = new Bar; +assert.ok(Array.isArray(bar)); +assert.equal(bar.foo, "bar"); +bar.push(1); +bar.push(2); +assert.equal(bar[0], 1); +assert.equal(bar[1], 2); +assert.equal(bar.length, 2); diff --git a/test/core/fixtures/transformation/es6.classes/native-constructors/expected.js b/test/core/fixtures/transformation/es6.classes/native-constructors/expected.js new file mode 100644 index 0000000000..de637609fa --- /dev/null +++ b/test/core/fixtures/transformation/es6.classes/native-constructors/expected.js @@ -0,0 +1,35 @@ +"use strict"; + +var Foo = (function (_Array) { + function Foo() { + babelHelpers.classCallCheck(this, Foo); + + if (_Array != null) { + var _this = new (babelHelpers.bind.apply(_Array, [null].concat(babelHelpers.slice.call(arguments))))(); + + _this.__proto__ = Foo.prototype; + return _this; + } + + return _this; + } + + babelHelpers.inherits(Foo, _Array); + return Foo; +})(Array); + +var Bar = (function (_Array2) { + function Bar() { + babelHelpers.classCallCheck(this, Bar); + + var _this2 = new _Array2(); + + _this2.__proto__ = Bar.prototype; + + _this2.foo = "bar"; + return _this2; + } + + babelHelpers.inherits(Bar, _Array2); + return Bar; +})(Array);