Add new.target transform (#5906)
* Add new.target transform * Catch new.target under only an arrow function * More unsupported reflect.construct cases * Fix node 4 test * Do not transform Methods * More tests * Properly setup function inheritance test * Tests tests tests * Fix ES6 class's new.target * Remove expected output thats supposed to throw.
This commit is contained in:
parent
003b8918c2
commit
628061c501
3
packages/babel-plugin-transform-new-target/.npmignore
Normal file
3
packages/babel-plugin-transform-new-target/.npmignore
Normal file
@ -0,0 +1,3 @@
|
||||
src
|
||||
test
|
||||
*.log
|
||||
104
packages/babel-plugin-transform-new-target/README.md
Normal file
104
packages/babel-plugin-transform-new-target/README.md
Normal file
@ -0,0 +1,104 @@
|
||||
# babel-plugin-transform-new-target
|
||||
|
||||
This plugins allows babel to transform `new.target` meta property into a
|
||||
(correct in most cases) `this.constructor` expression.
|
||||
|
||||
## Example
|
||||
|
||||
```js
|
||||
function Foo() {
|
||||
console.log(new.target);
|
||||
}
|
||||
|
||||
Foo(); // => undefined
|
||||
new Foo(); // => Foo
|
||||
```
|
||||
|
||||
```js
|
||||
class Foo {
|
||||
constructor() {
|
||||
console.log(new.target);
|
||||
}
|
||||
}
|
||||
|
||||
class Bar extends Foo {
|
||||
}
|
||||
|
||||
new Foo(); // => Foo
|
||||
new Bar(); // => Bar
|
||||
```
|
||||
|
||||
### Caveats
|
||||
|
||||
This plugin relies on `this.constructor`, which means `super` must
|
||||
already have been called when using untransformed classes.
|
||||
|
||||
```js
|
||||
class Foo {}
|
||||
|
||||
class Bar extends Foo {
|
||||
constructor() {
|
||||
// This will be a problem if classes aren't transformed to ES5
|
||||
new.target;
|
||||
super();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Additionally, this plugin cannot transform all `Reflect.construct` cases
|
||||
when using `newTarget` with ES5 function classes (transformed ES6 classes).
|
||||
|
||||
```js
|
||||
function Foo() {
|
||||
console.log(new.target);
|
||||
}
|
||||
|
||||
// Bar extends Foo in ES5
|
||||
function Bar() {
|
||||
Foo.call(this);
|
||||
}
|
||||
Bar.prototype = Object.create(Foo.prototype);
|
||||
Bar.prototype.constructor = Bar;
|
||||
|
||||
// Baz does not extend Foo
|
||||
function Baz() {}
|
||||
|
||||
Reflect.construct(Foo, []); // => Foo (correct)
|
||||
Reflect.construct(Foo, [], Bar); // => Bar (correct)
|
||||
|
||||
Reflect.construct(Bar, []); // => Bar (incorrect, though this is how ES5
|
||||
// inheritience is commonly implemented.)
|
||||
Reflect.construct(Foo, [], Baz); // => undefined (incorrect)
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
npm install --save-dev babel-plugin-transform-new-target
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Via `.babelrc` (Recommended)
|
||||
|
||||
**.babelrc**
|
||||
|
||||
```json
|
||||
{
|
||||
"plugins": ["transform-new-target"]
|
||||
}
|
||||
```
|
||||
|
||||
### Via CLI
|
||||
|
||||
```sh
|
||||
babel --plugins transform-new-target script.js
|
||||
```
|
||||
|
||||
### Via Node API
|
||||
|
||||
```javascript
|
||||
require("babel-core").transform("code", {
|
||||
plugins: ["transform-new-target"]
|
||||
});
|
||||
```
|
||||
18
packages/babel-plugin-transform-new-target/package.json
Normal file
18
packages/babel-plugin-transform-new-target/package.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "babel-plugin-transform-new-target",
|
||||
"version": "7.0.0-alpha.12",
|
||||
"description": "Transforms new.target meta property",
|
||||
"repository": "https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-new-target",
|
||||
"license": "MIT",
|
||||
"main": "lib/index.js",
|
||||
"keywords": [
|
||||
"babel-plugin"
|
||||
],
|
||||
"dependencies": {
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-helper-plugin-test-runner": "7.0.0-alpha.12",
|
||||
"babel-plugin-transform-class-properties": "7.0.0-alpha.12",
|
||||
"babel-plugin-transform-es2015-arrow-functions": "7.0.0-alpha.12"
|
||||
}
|
||||
}
|
||||
64
packages/babel-plugin-transform-new-target/src/index.js
Normal file
64
packages/babel-plugin-transform-new-target/src/index.js
Normal file
@ -0,0 +1,64 @@
|
||||
export default function({ types: t }) {
|
||||
return {
|
||||
name: "transform-new-target",
|
||||
|
||||
visitor: {
|
||||
MetaProperty(path) {
|
||||
const meta = path.get("meta");
|
||||
const property = path.get("property");
|
||||
const { scope } = path;
|
||||
|
||||
if (
|
||||
meta.isIdentifier({ name: "new" }) &&
|
||||
property.isIdentifier({ name: "target" })
|
||||
) {
|
||||
const func = path.findParent(path => {
|
||||
if (path.isClass()) return true;
|
||||
if (path.isFunction() && !path.isArrowFunctionExpression()) {
|
||||
if (path.isClassMethod({ kind: "constructor" })) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (!func) {
|
||||
throw path.buildCodeFrameError(
|
||||
"new.target must be under a (non-arrow) function or a class.",
|
||||
);
|
||||
}
|
||||
|
||||
const { node } = func;
|
||||
if (!node.id) {
|
||||
if (func.isMethod()) {
|
||||
path.replaceWith(scope.buildUndefinedNode());
|
||||
return;
|
||||
}
|
||||
|
||||
node.id = scope.generateUidIdentifier("target");
|
||||
}
|
||||
|
||||
const constructor = t.memberExpression(
|
||||
t.thisExpression(),
|
||||
t.identifier("constructor"),
|
||||
);
|
||||
|
||||
if (func.isClass()) {
|
||||
path.replaceWith(constructor);
|
||||
return;
|
||||
}
|
||||
|
||||
path.replaceWith(
|
||||
t.conditionalExpression(
|
||||
t.binaryExpression("instanceof", t.thisExpression(), node.id),
|
||||
constructor,
|
||||
scope.buildUndefinedNode(),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
5
packages/babel-plugin-transform-new-target/test/fixtures/errors/new-target-arrow/actual.js
vendored
Normal file
5
packages/babel-plugin-transform-new-target/test/fixtures/errors/new-target-arrow/actual.js
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
"use strict";
|
||||
|
||||
const a = () => {
|
||||
new.target;
|
||||
};
|
||||
@ -0,0 +1,3 @@
|
||||
{
|
||||
"throws": "new.target must be under a (non-arrow) function or a class."
|
||||
}
|
||||
22
packages/babel-plugin-transform-new-target/test/fixtures/exec/class-extended.js
vendored
Normal file
22
packages/babel-plugin-transform-new-target/test/fixtures/exec/class-extended.js
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
"use strict";
|
||||
|
||||
const targets = [];
|
||||
class Foo {
|
||||
constructor() {
|
||||
targets.push(new.target);
|
||||
}
|
||||
}
|
||||
|
||||
class Bar extends Foo {
|
||||
constructor() {
|
||||
super();
|
||||
targets.push(new.target);
|
||||
}
|
||||
}
|
||||
|
||||
new Foo;
|
||||
new Bar;
|
||||
|
||||
assert.equal(targets[0], Foo);
|
||||
assert.equal(targets[1], Bar);
|
||||
assert.equal(targets[2], Bar);
|
||||
12
packages/babel-plugin-transform-new-target/test/fixtures/exec/class.js
vendored
Normal file
12
packages/babel-plugin-transform-new-target/test/fixtures/exec/class.js
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
"use strict";
|
||||
|
||||
const targets = [];
|
||||
class Foo {
|
||||
constructor() {
|
||||
targets.push(new.target);
|
||||
}
|
||||
}
|
||||
|
||||
new Foo;
|
||||
|
||||
assert.equal(targets[0], Foo);
|
||||
16
packages/babel-plugin-transform-new-target/test/fixtures/exec/function-class-extended.js
vendored
Normal file
16
packages/babel-plugin-transform-new-target/test/fixtures/exec/function-class-extended.js
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
"use strict";
|
||||
|
||||
const targets = [];
|
||||
function Foo() {
|
||||
targets.push(new.target);
|
||||
}
|
||||
|
||||
function Bar() {
|
||||
Foo.call(this);
|
||||
}
|
||||
|
||||
new Foo;
|
||||
new Bar();
|
||||
|
||||
assert.equal(targets[0], Foo);
|
||||
assert.equal(targets[1], undefined);
|
||||
10
packages/babel-plugin-transform-new-target/test/fixtures/exec/function-class.js
vendored
Normal file
10
packages/babel-plugin-transform-new-target/test/fixtures/exec/function-class.js
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
"use strict";
|
||||
|
||||
const targets = [];
|
||||
function Foo() {
|
||||
targets.push(new.target);
|
||||
}
|
||||
|
||||
new Foo;
|
||||
|
||||
assert.equal(targets[0], Foo);
|
||||
12
packages/babel-plugin-transform-new-target/test/fixtures/exec/function.js
vendored
Normal file
12
packages/babel-plugin-transform-new-target/test/fixtures/exec/function.js
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
"use strict";
|
||||
|
||||
const targets = [];
|
||||
function foo() {
|
||||
targets.push(new.target);
|
||||
}
|
||||
|
||||
foo();
|
||||
foo.call({});
|
||||
|
||||
assert.equal(targets[0], undefined);
|
||||
assert.equal(targets[1], undefined);
|
||||
27
packages/babel-plugin-transform-new-target/test/fixtures/exec/reflect-class/exec.js
vendored
Normal file
27
packages/babel-plugin-transform-new-target/test/fixtures/exec/reflect-class/exec.js
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
const targets = [];
|
||||
class Foo {
|
||||
constructor() {
|
||||
targets.push(new.target);
|
||||
}
|
||||
}
|
||||
|
||||
class Bar extends Foo {
|
||||
}
|
||||
class Baz {
|
||||
}
|
||||
|
||||
Reflect.construct(Foo, []);
|
||||
Reflect.construct(Foo, [], Bar);
|
||||
Reflect.construct(Bar, []);
|
||||
Reflect.construct(Bar, [], Baz);
|
||||
Reflect.construct(Foo, [], Baz);
|
||||
|
||||
assert.equal(targets[0], Foo);
|
||||
|
||||
assert.equal(targets[1], Bar);
|
||||
|
||||
assert.equal(targets[2], Bar);
|
||||
|
||||
assert.equal(targets[3], Baz);
|
||||
|
||||
assert.equal(targets[4], Baz);
|
||||
3
packages/babel-plugin-transform-new-target/test/fixtures/exec/reflect-class/options.json
vendored
Normal file
3
packages/babel-plugin-transform-new-target/test/fixtures/exec/reflect-class/options.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"minNodeVersion": "6.0.0"
|
||||
}
|
||||
40
packages/babel-plugin-transform-new-target/test/fixtures/exec/reflect-function/exec.js
vendored
Normal file
40
packages/babel-plugin-transform-new-target/test/fixtures/exec/reflect-function/exec.js
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
const targets = [];
|
||||
function Foo() {
|
||||
targets.push(new.target);
|
||||
}
|
||||
|
||||
function Bar() {
|
||||
Foo.call(this);
|
||||
}
|
||||
Bar.prototype = Object.create(Foo.prototype, {
|
||||
constructor: {
|
||||
value: Bar,
|
||||
writable: true,
|
||||
configurable: true,
|
||||
}
|
||||
});
|
||||
|
||||
function Baz() {}
|
||||
|
||||
Reflect.construct(Foo, []);
|
||||
Reflect.construct(Foo, [], Bar);
|
||||
Reflect.construct(Bar, []);
|
||||
Reflect.construct(Bar, [], Baz);
|
||||
Reflect.construct(Foo, [], Baz);
|
||||
|
||||
assert.equal(targets[0], Foo);
|
||||
|
||||
assert.equal(targets[1], Bar);
|
||||
|
||||
assert.throws(() => {
|
||||
// Wish we could support this...
|
||||
// Then again, this is what a transformed class does.
|
||||
assert.equal(targets[2], undefined);
|
||||
});
|
||||
|
||||
assert.equal(targets[3], undefined);
|
||||
|
||||
assert.throws(() => {
|
||||
// Wish we could support this...
|
||||
assert.equal(targets[4], Baz);
|
||||
});
|
||||
3
packages/babel-plugin-transform-new-target/test/fixtures/exec/reflect-function/options.json
vendored
Normal file
3
packages/babel-plugin-transform-new-target/test/fixtures/exec/reflect-function/options.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"minNodeVersion": "6.0.0"
|
||||
}
|
||||
13
packages/babel-plugin-transform-new-target/test/fixtures/general/arrow/actual.js
vendored
Normal file
13
packages/babel-plugin-transform-new-target/test/fixtures/general/arrow/actual.js
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
function Foo() {
|
||||
const a = () => {
|
||||
new.target;
|
||||
};
|
||||
}
|
||||
|
||||
class Bar {
|
||||
constructor() {
|
||||
const a = () => {
|
||||
new.target;
|
||||
};
|
||||
}
|
||||
}
|
||||
18
packages/babel-plugin-transform-new-target/test/fixtures/general/arrow/expected.js
vendored
Normal file
18
packages/babel-plugin-transform-new-target/test/fixtures/general/arrow/expected.js
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
function Foo() {
|
||||
var _newtarget = this instanceof Foo ? this.constructor : void 0;
|
||||
|
||||
const a = function () {
|
||||
_newtarget;
|
||||
};
|
||||
}
|
||||
|
||||
class Bar {
|
||||
constructor() {
|
||||
var _newtarget2 = this.constructor;
|
||||
|
||||
const a = function () {
|
||||
_newtarget2;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
3
packages/babel-plugin-transform-new-target/test/fixtures/general/arrow/options.json
vendored
Normal file
3
packages/babel-plugin-transform-new-target/test/fixtures/general/arrow/options.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"plugins": ["transform-new-target", "transform-es2015-arrow-functions"]
|
||||
}
|
||||
9
packages/babel-plugin-transform-new-target/test/fixtures/general/class-properties/actual.js
vendored
Normal file
9
packages/babel-plugin-transform-new-target/test/fixtures/general/class-properties/actual.js
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
class Foo {
|
||||
test = function() {
|
||||
new.target;
|
||||
};
|
||||
|
||||
test2 = () => {
|
||||
new.target;
|
||||
}
|
||||
}
|
||||
14
packages/babel-plugin-transform-new-target/test/fixtures/general/class-properties/expected.js
vendored
Normal file
14
packages/babel-plugin-transform-new-target/test/fixtures/general/class-properties/expected.js
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
class Foo {
|
||||
constructor() {
|
||||
var _newtarget = this.constructor;
|
||||
|
||||
this.test = function _target() {
|
||||
this instanceof _target ? this.constructor : void 0;
|
||||
};
|
||||
|
||||
this.test2 = function () {
|
||||
_newtarget;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
{
|
||||
"plugins": ["transform-new-target", "transform-es2015-arrow-functions", "transform-class-properties"]
|
||||
}
|
||||
9
packages/babel-plugin-transform-new-target/test/fixtures/general/class/actual.js
vendored
Normal file
9
packages/babel-plugin-transform-new-target/test/fixtures/general/class/actual.js
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
class Foo {
|
||||
constructor() {
|
||||
new.target;
|
||||
}
|
||||
|
||||
test() {
|
||||
new.target;
|
||||
}
|
||||
}
|
||||
10
packages/babel-plugin-transform-new-target/test/fixtures/general/class/expected.js
vendored
Normal file
10
packages/babel-plugin-transform-new-target/test/fixtures/general/class/expected.js
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
class Foo {
|
||||
constructor() {
|
||||
this.constructor;
|
||||
}
|
||||
|
||||
test() {
|
||||
void 0;
|
||||
}
|
||||
|
||||
}
|
||||
20
packages/babel-plugin-transform-new-target/test/fixtures/general/extended-class/actual.js
vendored
Normal file
20
packages/babel-plugin-transform-new-target/test/fixtures/general/extended-class/actual.js
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
class Foo {
|
||||
constructor() {
|
||||
new.target;
|
||||
}
|
||||
}
|
||||
|
||||
class Bar extends Foo {
|
||||
constructor() {
|
||||
// This is probably bad...
|
||||
new.target;
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
class Baz extends Foo {
|
||||
constructor() {
|
||||
super();
|
||||
new.target;
|
||||
}
|
||||
}
|
||||
23
packages/babel-plugin-transform-new-target/test/fixtures/general/extended-class/expected.js
vendored
Normal file
23
packages/babel-plugin-transform-new-target/test/fixtures/general/extended-class/expected.js
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
class Foo {
|
||||
constructor() {
|
||||
this.constructor;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Bar extends Foo {
|
||||
constructor() {
|
||||
// This is probably bad...
|
||||
this.constructor;
|
||||
super();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Baz extends Foo {
|
||||
constructor() {
|
||||
super();
|
||||
this.constructor;
|
||||
}
|
||||
|
||||
}
|
||||
11
packages/babel-plugin-transform-new-target/test/fixtures/general/function/actual.js
vendored
Normal file
11
packages/babel-plugin-transform-new-target/test/fixtures/general/function/actual.js
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
function Foo() {
|
||||
new.target;
|
||||
}
|
||||
|
||||
Foo.prototype.test = function() {
|
||||
new.target;
|
||||
};
|
||||
|
||||
var Bar = function() {
|
||||
new.target;
|
||||
};
|
||||
11
packages/babel-plugin-transform-new-target/test/fixtures/general/function/expected.js
vendored
Normal file
11
packages/babel-plugin-transform-new-target/test/fixtures/general/function/expected.js
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
function Foo() {
|
||||
this instanceof Foo ? this.constructor : void 0;
|
||||
}
|
||||
|
||||
Foo.prototype.test = function _target() {
|
||||
this instanceof _target ? this.constructor : void 0;
|
||||
};
|
||||
|
||||
var Bar = function _target2() {
|
||||
this instanceof _target2 ? this.constructor : void 0;
|
||||
};
|
||||
11
packages/babel-plugin-transform-new-target/test/fixtures/general/object/actual.js
vendored
Normal file
11
packages/babel-plugin-transform-new-target/test/fixtures/general/object/actual.js
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
"use strict";
|
||||
|
||||
const object = {
|
||||
test() {
|
||||
new.target;
|
||||
},
|
||||
|
||||
test2: function() {
|
||||
new.target;
|
||||
},
|
||||
}
|
||||
11
packages/babel-plugin-transform-new-target/test/fixtures/general/object/expected.js
vendored
Normal file
11
packages/babel-plugin-transform-new-target/test/fixtures/general/object/expected.js
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
"use strict";
|
||||
|
||||
const object = {
|
||||
test() {
|
||||
void 0;
|
||||
},
|
||||
|
||||
test2: function _target() {
|
||||
this instanceof _target ? this.constructor : void 0;
|
||||
}
|
||||
};
|
||||
4
packages/babel-plugin-transform-new-target/test/fixtures/options.json
vendored
Normal file
4
packages/babel-plugin-transform-new-target/test/fixtures/options.json
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"plugins": ["transform-new-target"],
|
||||
"minNodeVersion": "4.0.0"
|
||||
}
|
||||
3
packages/babel-plugin-transform-new-target/test/index.js
Normal file
3
packages/babel-plugin-transform-new-target/test/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
import runner from "babel-helper-plugin-test-runner";
|
||||
|
||||
runner(__dirname);
|
||||
Loading…
x
Reference in New Issue
Block a user