Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Decorators 2 Transform [WIP] #6107

Closed
wants to merge 23 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
57cdfaf
Add syntax plugin for decorators2
peey Jul 21, 2017
9904b61
WIP decorator transform initial commit
peey Jul 21, 2017
1d64084
Merge branch '7.0' into decorators-2-transform-wip
peey Jul 21, 2017
f214d1d
wip commiting code before major changes
peey Jul 31, 2017
edb7ff2
Transform works great, move stuff to helpers
peey Jul 31, 2017
10d763e
Clean up code
peey Jul 31, 2017
358cf70
Add feature to pass array of deps to a helper
peey Aug 1, 2017
626b92e
Add exec tests
peey Aug 6, 2017
d8cebba
Add tests for evaluation order and fix transformation
peey Aug 7, 2017
ef4eba5
Add tests for class decorators; minor fixes
peey Aug 9, 2017
b1092ca
Add test for enumerability decorator
peey Aug 11, 2017
a55e374
Add examples, & tests for extras, finishers
peey Aug 15, 2017
5f7bf93
Merge branch '7.0' into decorators-2-transform-wip
peey Aug 15, 2017
8707f4e
Update versions to make tests pass
peey Aug 16, 2017
6ec36a1
Improvements in handling of computed keys
peey Aug 20, 2017
2e7c6a1
Add predicate hasDecorators (for early exit)
peey Aug 20, 2017
8e28ca7
use path.scope.parent.push & other review suggestions
peey Aug 20, 2017
1776e24
Account for getters and setters
peey Aug 24, 2017
4e527a3
Pass elementDescriptor to decorators not propertyDescriptor
peey Aug 24, 2017
44465f2
configurability checks
peey Aug 29, 2017
f86b327
Merge getters and setters; Map->object in decorate
peey Sep 16, 2017
96f51aa
Add failing test for instanceof
peey Sep 16, 2017
76f5d3c
Fix parentage tests. It's not an issue
peey Sep 20, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 15 additions & 3 deletions packages/babel-core/src/transformation/file/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -223,13 +223,12 @@ export default class File extends Store {
return id;
}

addHelper(name: string): Object {
addHelper(name: string, deps: Array<string>): Object {
const declar = this.declarations[name];
if (declar) return declar;

if (!this.usedHelpers[name]) {
this.metadata.usedHelpers.push(name);
this.usedHelpers[name] = true;
}

const generator = this.get("helperGenerator");
Expand All @@ -241,7 +240,18 @@ export default class File extends Store {
return t.memberExpression(runtime, t.identifier(name));
}

const ref = getHelper(name);
const opts = {};
if (deps) {
for (const dep in deps) {
if (!this.usedHelpers[name]) {
throw "Helper dependencies must be added to the file first";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you wrap this with new Error()? Otherwise babel throws something like Cannot add property _babel to "Helper dependencies must be added to the file first" instead of just Helper dependencies must be added to the file first. (Because of

)

} else {
opts["babelHelpers." + dep] = this.usedHelpers[name];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think babel-template only replaces identifiers, so babelHelpers.helperName in helpers won't be replaced. You might use something like babelHelpers$helperName, but then it would need to be replaced when generating external helpers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey. I completely missed this, I guess since it was working on babel tests I assumed it would be generating the correct code. Apparently not. Anyways, I'll track the issues with this feature separately in #6058. Also I don't intend to implement too complex features for this - just enough to make it work, because #5706 already is aimed at providing a superset of the functionality

}
}
}

const ref = getHelper(name, opts);
const uid = (this.declarations[name] = this.scope.generateUidIdentifier(
name,
));
Expand All @@ -260,6 +270,8 @@ export default class File extends Store {
});
}

this.usedHelpers[name] = uid.name;

return uid;
}

Expand Down
216 changes: 216 additions & 0 deletions packages/babel-helpers/src/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,174 @@ helpers.createClass = template(`
})()
`);

//NOTE: convention is 'descriptor' is used for element descriptors, while 'propertyDescriptor' is used for
//property descriptor in all helpers related to decorators-2
helpers.decorate = template(`
(function (constructor, undecorated, memberDecorators, heritage) {
const prototype = constructor.prototype;
let finishers = [];
const elementDescriptors = {}; // elementDescriptors is meant to be an array, so this will be converted later
const staticElementDescriptors = {};

for (const [key, isStatic] of undecorated) {
const map = isStatic? staticElementDescriptors : elementDescriptors;
const target = isStatic ? constructor : prototype;
const propertyDescriptor = Object.getOwnPropertyDescriptor(target, key);
map[key] = babelHelpers.makeElementDescriptor(
"property",
key,
isStatic,
propertyDescriptor,
);
}

// decorate and store in elementDescriptors or staticElementDescriptors
for (const [key, decorators, isStatic] of memberDecorators) {
const map = isStatic ? staticElementDescriptors : elementDescriptors;
const target = isStatic ? constructor : prototype;
const propertyDescriptor =
map[key] && map[key].descriptor || Object.getOwnPropertyDescriptor(target, key);

const elementDescriptor = babelHelpers.makeElementDescriptor(
"property",
key,
isStatic,
propertyDescriptor,
);

const decorated = babelHelpers.decorateElement(elementDescriptor, decorators);

map[key] = decorated.descriptor;

for (const extra of decorated.extras) {
// extras is an array of element descriptors
const map = extra.isStatic? staticElementDescriptors : elementDescriptors;
// TODO: refactor to use proper merging logic here. currently it's just overriding
map[extra.key] = extra;
}

finishers = finishers.concat(decorated.finishers);
}

return function(classDecorators) {
const result = babelHelpers.decorateClass(
constructor,
classDecorators,
heritage,
Object.values(elementDescriptors).concat(Object.values(staticElementDescriptors))
);

finishers = finishers.concat(result.finishers);
//TODO: heritage hacks so result.constructor has the correct prototype and instanceof results
//NOTE: step 38 and 39 in spec, refer to some "initialize" state which hasn't been implemented,
//because it's unlikely to stay in future versions of the spec

for (const elementDescriptor of result.elements) {
const target = elementDescriptor.isStatic ? constructor : prototype;
Object.defineProperty(
target,
elementDescriptor.key,
elementDescriptor.descriptor
);
}

for (let finisher of finishers) {
finisher.call(undefined, result.constructor);
}

return result.constructor;
};
});
`);

//TODO guarding against malformed descriptors returned by decorators
helpers.decorateElement = template(`
(function (descriptor, decorators) {
//spec uses the param "element" instead of "descriptor" and finds descriptor from it
let extras = [];
let finishers = [];

let previousDescriptor = descriptor;

for (let i = decorators.length - 1; i >= 0; i--) {
const decorator = decorators[i];
const shallowClone = {
configurable: previousDescriptor.descriptor.configurable,
enumerable: previousDescriptor.descriptor.enumerable,
writable: previousDescriptor.descriptor.writable,
value: previousDescriptor.descriptor.value
}

const result = decorator(previousDescriptor);

if (!shallowClone.configurable) {
let check =
result.descriptor.configurable === shallowClone.configurable &&
result.descriptor.enumerable === shallowClone.enumerable &&
result.descriptor.writable === shallowClone.writable &&
result.descriptor.value === shallowClone.value;

if (!check) {
throw new Error("Decorator tried to change unconfigurable property descriptor");
}
}

//TODO: why does .finisher exist on an elementDescriptor? the following conditional deviates from
//the spec because it uses result.finishers rather than result.descriptor.finisher
if (result.finishers) {
finishers = finishers.concat(result.finishers);
}

previousDescriptor = result.descriptor;

const extrasObject = result.extras;

if (extrasObject) {
for (const extra of extrasObject) {
extras.push(extra);
}
}
}

extras = babelHelpers.mergeDuplicateElements(extras);

return { descriptor: previousDescriptor, extras, finishers };
});
`);

helpers.decorateClass = template(`
(function (constructor, decorators, heritage, elementDescriptors) {
let elements = elementDescriptors;
let finishers = [];

let previousConstructor = constructor;

for (let i = decorators.length - 1; i >= 0; i--) {
const decorator = decorators[i];
const result = decorator(
previousConstructor,
heritage,
elements
);

previousConstructor = result.constructor;

if (result.finishers) {
// result.finishers is called 'finisher' in the spec
finishers = finishers.concat(result.finishers);
}

if (result.elements) {
elements = elements.concat(result.elements); //FIXME: for some reason using for of exhausts heap here
}

elements = babelHelpers.mergeDuplicateElements(elements);
}

return { constructor: previousConstructor, elements, finishers };
});
`);

helpers.defineEnumerableProperties = template(`
(function (obj, descs) {
for (var key in descs) {
Expand Down Expand Up @@ -417,6 +585,54 @@ helpers.interopRequireWildcard = template(`
})
`);

helpers.makeElementDescriptor = template(`
(function (kind, key, isStatic, descriptor, finisher) {
var elementDescriptor = { kind, key, isStatic, descriptor};
if (finisher) {
elementDescriptor.finisher = finisher;
}
return elementDescriptor;
});
`);

/**
* NOTE: Details for merging haven't been decided on by TC39.
* The following function implements a reasonable merging strategy - which
* is to merge getters and setters, and to override everything else
**/
helpers.mergeDuplicateElements = template(`
(function (elements) {
let elementMap = {};
let staticElementMap = {};

for (let elementDescriptor of elements) {
let map = elementDescriptor.isStatic? staticElementMap : elementMap;

if (!map[elementDescriptor.key]) {
map[elementDescriptor.key] = elementDescriptor;
} else {
let prev = map[elementDescriptor.key];
let isDataDescriptor = !!prev.descriptor.value;
let newIsDataDescriptor = !!elementDescriptor.descriptor.value;

if (!newIsDataDescriptor && !isDataDescriptor) {
// if both are accessor descriptors, merge getters and setters
let get = elementDescriptor.descriptor.get || prev.descriptor.get;
let set = elementDescriptor.descriptor.set || prev.descriptor.set;

let descriptor = Object.assign({}, elementDescriptor.descriptor, {get, set});

map[elementDescriptor.key] = Object.assign({}, elementDescriptor, {descriptor});
} else {
map[elementDescriptor.key] = elementDescriptor; // overrideprevious descriptor
}
}
}

return Object.values(elementMap).concat(Object.values(staticElementMap));
});
`);

helpers.newArrowCheck = template(`
(function (innerThis, boundThis) {
if (innerThis !== boundThis) {
Expand Down
4 changes: 2 additions & 2 deletions packages/babel-helpers/src/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import helpers from "./helpers";

export function get(name) {
export function get(name, opts) {
const fn = helpers[name];
if (!fn) throw new ReferenceError(`Unknown helper ${name}`);

return fn().expression;
return fn(opts).expression;
}

export const list = Object.keys(helpers)
Expand Down
3 changes: 3 additions & 0 deletions packages/babel-plugin-syntax-decorators-2/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
src
test
*.log
35 changes: 35 additions & 0 deletions packages/babel-plugin-syntax-decorators-2/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# babel-plugin-syntax-decorators-2

> Updated parsing of decorators.

## Installation

```sh
npm install --save-dev babel-plugin-syntax-decorators-2
```

## Usage

### Via `.babelrc` (Recommended)

**.babelrc**

```json
{
"plugins": ["syntax-decorators-2"]
}
```

### Via CLI

```sh
babel --plugins syntax-decorators-2 script.js
```

### Via Node API

```javascript
require("babel-core").transform("code", {
plugins: ["syntax-decorators-2"]
});
```
13 changes: 13 additions & 0 deletions packages/babel-plugin-syntax-decorators-2/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "babel-plugin-syntax-decorators-2",
"version": "7.0.0-alpha.19",
"description": "Updated parsing of decorators",
"repository": "https://github.com/babel/babel/tree/master/packages/babel-plugin-syntax-decorators-2",
"license": "MIT",
"main": "lib/index.js",
"keywords": [
"babel-plugin"
],
"dependencies": {},
"devDependencies": {}
}
7 changes: 7 additions & 0 deletions packages/babel-plugin-syntax-decorators-2/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function() {
return {
manipulateOptions(opts, parserOpts) {
parserOpts.plugins.push("decorators2");
},
};
}
22 changes: 22 additions & 0 deletions packages/babel-plugin-transform-decorators-2/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "babel-plugin-transform-decorators-2",
"version": "7.0.0-alpha.19",
"author": "Peeyush Kushwaha <peeyush.p97@gmail.com>",
"license": "MIT",
"description": "Transformer for stage-2 decorators",
"repository": "https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-decorators-2",
"main": "lib/index.js",
"keywords": [
"babel",
"babel-plugin",
"decorators"
],
"dependencies": {
"babel-plugin-syntax-decorators-2": "7.0.0-alpha.19"
},
"devDependencies": {
"babel-helper-plugin-test-runner": "7.0.0-alpha.19",
"babel-helper-transform-fixture-test-runner": "7.0.0-alpha.19",
"babel-plugin-transform-decorators-2": "7.0.0-alpha.19"
}
}