Issue 1:
Bug Report:

When extending a native type possibleConstructorReturn helper returns the root prototype, instead of the correctly extended prototype.

Input Code

class ObjX extends Object{
logKeys(){
console.log(Object.keys(this));
}
}

const exObject = new ObjX;
exObject['key'] = 'val';
exObject.logKeys();

Babel/Babylon Configuration (.babelrc, package.json, cli command)

{
"presets":["es2015","stage-1"]
}

Expected Behavior

If you drop the above code directly in your browser console, it will quite happily output an array of ['key'].

Extending native types, while uncommon has been a standard capability in javascript for a really long time. This behavior should be preserved by babel.

Current Behavior

Currently, Babel will just bind the native constructor to the extended class. So calling new ObjX results in just a native object (none of the methods or properties of the extended class are preserved).

Possible Solution

I spent an hour or two digging through Babel core and the flaw in logic is pretty apparent. When you extend a native class, Babel does this:

return _possibleConstructorReturn(this, (ObjX.__proto__ || Object.getPrototypeOf(ObjX)).apply(this, arguments));

With possibleConstructorReturn relying on the following:

return call && (typeof call === "object" || typeof call === "function") ? call : self;

In the case of creating a new class in Babel, the origin class doesn't call possibleConstructorReturn, it just does the classCallCheck. When you extend that, possibleConstructorReturn is called on a class that extends a brand new prototype. In which case, it does its error check and returns nothing, you end up with the 2 arguments, one being the extended prototype, and one being undefined. All works as it should.

In the case of extending a native type, by calling apply on the prototype you end up with a newly assigned value, which leaves us with self and call defined in possibleConstructorReturn and the blank native gets returned instead of self, which gives you the native prototype instead of your extended class.

It's pretty easy to solve this in a small scope, because when extending a native type it should just return self (in which case everything works fine, in this limited scenario). I was able to play with the logic in possibleConstructorReturn to try to get it to both work, as well as pass all of the current test scenarios, but when I tried it out in practice it obviously impacted other things.

This tells me that the logic that currently exists is very purposeful, but is also tightly coupled to handle a lot of scenarios, and it felt like touching this code required somebody more familiar with how/why Babel does classes the way it does.

Perhaps a low impact solution would be when generating the code, check to see if the class is extending a native type, in which case skip the possibleConstructorReturn logic when compiling the class?

Issue 2:
Current Babel transform, when it comes to call the parent

function _possibleConstructorReturn(self, call) {
if (!self) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return call && (typeof call === "object" || typeof call === "function") ? call : self;
}

It's a way too poor implementation.

If we take the current basic ES6 compat syntax:

class List extends Array {}

We'll realize babel does a bad job.

var l = new List();
l instanceof List; // false

The reason is simple: Babel replace the returns and exit, without caring about userland expectations.

This is how above basic extend should desugar:

function List() {
return Object.setPrototypeOf(
Array.apply(this, arguments),
List.prototype
);
}

It's a very ad-hoc case for the initial example that currently fails, but it's good enough to understand that inheriting the prototype is the least of the problem.

Indeed, we have 3 ways to do that within a transpiled code:

// losing the initial prototype
// for polyfilled ES5+ or lower
List.prototype = Object.create(
Array.prototype,
{constructor: {
configurable: true,
writable: true,
value: List.prototype
}}
);

// using a cleaner way
// for ES5+ compliant targets
Object.setPrototypeOf(
List.prototype,
Array.prototype
);

// using a cleaner way
// that works well with
// partially polyfilled .setPrototypeOf
List.prototype = Object.setPrototypeOf(
List.prototype,
Array.prototype
);

Solved the inheritance bit, considering Babel also set the prototype of each constructor,

we need to address cases where a super call might "upgrade" the current context, like it is for HTMLELement or exotic native objects.

// native ES6 static example
class List extends Array {
constructor(a, b, c) {
super(a, b);
this.push(c);
}
}

Above case should desugar to something like the follwoing:

function List(a, b, c) {
// the super bit
var self = Object.setPrototypeOf(
Array.call(this, a, b),
List.prototype
);
// the rest with swapped context
self.push(c);
// at the end
return self;
}

which is also ad-hoc example code for the previous example.

Considering a transpiler will get the arguments part easily right, this is how previous case could be generically transpiled for arguments used in both constructors:

// to make it as generic as possible
function Child() {

// the super call bit desugar to ...
var
instance = Object.getPrototypeOf(Child)
.apply(this, arguments),
type = instance ? typeof instance : '',
// if Parent overwrote its default return
self = (type === 'object' || type === 'function') ?
// upgrade the instance to reflect Child constructor
Object.setPrototypeOf(instance, Child.prototype) :
// otherwise use the current context as is
this
;

// at this point, the rest of the constructor
// should use `self` instead of `this`
self.push(c);

// and return `self` reference at the end
return self;
}

The last problem is that modern syntax would use Reflect to create any sort of object, instead of old, ES3 friendly, .call or .apply way.

Following a past, present, and even future proof approach:

var
reConstruct = typeof Reflect === 'object' ?
Reflect.construct :
function (Parent, args, Child) {
return Parent.apply(this, args);
}
;

function Child() {

// the super call bit
var
instance = reConstruct.call(
this,
Object.getPrototypeOf(Child),
arguments,
Child
),
type = instance ? typeof instance : '',
self = (type === 'object' || type === 'function') ?
Object.setPrototypeOf(instance, Child.prototype) :
this
;

// the rest of the constructor body
self.push(c);

// at the end or instead of empty returns
return self;
}

Above solution would work with userland, exotic, or DOM constructors, and in both native and transpiled engines.