Skip to content

Commit

Permalink
feat(parameter-decorators): support parameter decorators again!!!
Browse files Browse the repository at this point in the history
  • Loading branch information
shuhei committed May 4, 2016
1 parent 703611f commit 7221f85
Show file tree
Hide file tree
Showing 14 changed files with 74 additions and 313 deletions.
26 changes: 4 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

# babel-plugin-angular2-annotations

A babel transformer plugin for Angular 2 decorators and type annotations. **Parameter decorator is not supported because there's currently no way to extend Babel's parser.**
A babel transformer plugin for Angular 2 decorators and type annotations.

Use [babel-plugin-transform-decorators-legacy](https://github.com/loganfsmyth/babel-plugin-transform-decorators-legacy) to support Babel-5-compatible decorators.
Use [babel-plugin-transform-decorators-legacy](https://github.com/loganfsmyth/babel-plugin-transform-decorators-legacy) to support decorators.

Make sure to load [reflect-metadata](https://github.com/rbuckton/ReflectDecorators) for browser in order to polyfill Metadata Reflection API in your app.

Expand Down Expand Up @@ -54,8 +54,6 @@ Make sure to load [reflect-metadata](https://github.com/rbuckton/ReflectDecorato
}
```

### Not supported

- Decorators for constructor parameters

```js
Expand All @@ -68,23 +66,6 @@ Make sure to load [reflect-metadata](https://github.com/rbuckton/ReflectDecorato
}
```

- It is inevitable because the parameter decorator syntax is not in [the ES7 proposals](https://github.com/tc39/ecma262) or implemented by Babel's parser.
- This plugin used to support it by monkey-patching but [now it is forbidden to do so](https://github.com/babel/babel/pull/3204).
- You can still directly use parameter decorator metadata to achieve the same functionalities.

```js
@Component({ /* ... */ })
@Reflect.metadata('parameters', [[new AttributeMetadata()], [new OptionalMetadata()]])
class HelloComponent {
constructor(name, optional) {
this.name = name;
this.optional = optional;
}
}
```

More examples are in [the integration tests](test/integration/parameter-decorator-alternative.spec.js).

## Install

```sh
Expand Down Expand Up @@ -118,7 +99,7 @@ Before:
```js
class HelloComponent {
@Input() baz;
constructor(foo: Foo, bar: Bar) {
constructor(foo: Foo, @Optional() bar: Bar) {
}
}
```
Expand All @@ -130,6 +111,7 @@ class HelloComponent {
@Input() baz = this.baz;
}

Optional()(HelloComponent, null, 1);
Reflect.defineMetadata('design:paramtypes', [Foo, Bar]);
```

Expand Down
28 changes: 14 additions & 14 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,30 +27,30 @@
"author": "Shuhei Kagawa <shuhei.kagawa@gmail.com>",
"license": "ISC",
"devDependencies": {
"@angular/common": "2.0.0-rc.0",
"@angular/compiler": "2.0.0-rc.0",
"@angular/core": "2.0.0-rc.0",
"@angular/platform-browser": "2.0.0-rc.0",
"@angular/platform-browser-dynamic": "^2.0.0-rc.0",
"babel-cli": "^6.7.7",
"babel-core": "^6.7.7",
"@angular/common": "2.0.0-rc.1",
"@angular/compiler": "2.0.0-rc.1",
"@angular/core": "2.0.0-rc.1",
"@angular/platform-browser": "2.0.0-rc.1",
"@angular/platform-browser-dynamic": "^2.0.0-rc.1",
"babel-cli": "^6.8.0",
"babel-core": "^6.8.0",
"babel-eslint": "^6.0.4",
"babel-plugin-external-helpers-2": "^6.3.13",
"babel-plugin-transform-class-properties": "^6.6.0",
"babel-plugin-transform-class-properties": "^6.8.0",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-plugin-transform-flow-strip-types": "^6.7.0",
"babel-polyfill": "^6.7.4",
"babel-plugin-transform-flow-strip-types": "^6.8.0",
"babel-polyfill": "^6.8.0",
"babel-preset-es2015": "^6.6.0",
"babelify": "^7.3.0",
"babylon": "^6.7.0",
"babylon": "^6.8.0",
"browserify": "^13.0.0",
"es6-promise": "^3.1.2",
"es6-shim": "0.35.0",
"eslint": "^2.8.0",
"eslint": "^2.9.0",
"jasmine-core": "^2.4.1",
"karma": "^0.13.22",
"karma-browserify": "^5.0.4",
"karma-jasmine": "^0.3.8",
"karma-jasmine": "^1.0.2",
"karma-phantomjs-launcher": "^1.0.0",
"karma-source-map-support": "^1.1.0",
"phantomjs-prebuilt": "^2.1.7",
Expand All @@ -64,6 +64,6 @@
"babel-plugin-transform-decorators-legacy": "^1.3.4"
},
"dependencies": {
"babel-generator": "^6.7.7"
"babel-generator": "^6.8.0"
}
}
25 changes: 23 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,20 @@ export default function ({ types: t }) {
const classRef = node.id;
const classBody = node.body.body;

// Create additional statements for types annotations.
// Create additional statements for parameter decorators and type annotations.
let decorators = [];
let types = [];
classBody.forEach((bodyNode) => {
if (bodyNode.type === 'ClassMethod' && bodyNode.kind === 'constructor') {
decorators = parameterDecorators(bodyNode.params, classRef);
types = parameterTypes(bodyNode.params, classRef);
} else if (bodyNode.type === 'ClassProperty' && bodyNode.value === null && !bodyNode.static) {
// Handle class property without initializer.
// https://github.com/jeffmo/es-class-fields-and-static-properties
bodyNode.value = t.memberExpression(t.thisExpression(), bodyNode.key);
}
});
const additionalStatements = types.filter(Boolean);
const additionalStatements = [...decorators, ...types].filter(Boolean);

// If not found, do nothing.
if (additionalStatements.length === 0) {
Expand All @@ -40,6 +42,25 @@ export default function ({ types: t }) {
}
};

// Returns an array of parameter decorator call statements for a class.
function parameterDecorators(params, classRef) {
const decoratorLists = params.map((param, i) => {
const decorators = param.decorators;
if (!decorators) {
return [];
}
param.decorators = null;

return decorators.map((decorator) => {
const call = decorator.expression;
const args = [classRef, t.identifier('null'), t.identifier(i.toString())];
return t.expressionStatement(t.callExpression(call, args));
});
});
// Flatten.
return Array.prototype.concat.apply([], decoratorLists);
}

// Returns an array of define 'parameters' metadata statements for a class.
// The array may contain zero or one statements.
function parameterTypes(params, classRef) {
Expand Down
5 changes: 3 additions & 2 deletions test/integration/component.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
Component,
View,
Input,
Attribute,
ViewMetadata
} from '@angular/core';
import {
Expand All @@ -27,10 +28,10 @@ describe('component', () => {
template: '<p>{{message}}</p>'
})
class HelloWorld {
@Input() name;
@Input() greeting;

constructor(greeter: Greeter) {
constructor(@Attribute('name') name, greeter: Greeter) {
this.name = name;
this.greeter = greeter;
}

Expand Down
Loading

1 comment on commit 7221f85

@sonicoder86
Copy link
Contributor

Choose a reason for hiding this comment

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

This is AWESOME!!! Time to move them back :)

Please sign in to comment.