feat(core): make transformers handle @Input/@Output/@HostBinding/@HostListener
Closes #5080
This commit is contained in:
parent
045919b595
commit
16bc238f10
|
@ -225,6 +225,95 @@ class _DirectiveMetadataVisitor extends Object
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Object visitFieldDeclaration(FieldDeclaration node) {
|
||||||
|
for (var variable in node.fields.variables) {
|
||||||
|
for (var meta in node.metadata) {
|
||||||
|
if (_isAnnotation(meta, 'Output')) {
|
||||||
|
final renamed = _getRenamedValue(meta);
|
||||||
|
if (renamed != null) {
|
||||||
|
_outputs.add('${variable.name}: ${renamed}');
|
||||||
|
} else {
|
||||||
|
_outputs.add('${variable.name}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_isAnnotation(meta, 'Input')) {
|
||||||
|
final renamed = _getRenamedValue(meta);
|
||||||
|
if (renamed != null) {
|
||||||
|
_inputs.add('${variable.name}: ${renamed}');
|
||||||
|
} else {
|
||||||
|
_inputs.add('${variable.name}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_isAnnotation(meta, 'HostBinding')) {
|
||||||
|
final renamed = _getRenamedValue(meta);
|
||||||
|
if (renamed != null) {
|
||||||
|
_host['[${renamed}]'] = '${variable.name}';
|
||||||
|
} else {
|
||||||
|
_host['[${variable.name}]'] = '${variable.name}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Object visitMethodDeclaration(MethodDeclaration node) {
|
||||||
|
for (var meta in node.metadata) {
|
||||||
|
if (_isAnnotation(meta, 'HostListener')) {
|
||||||
|
if (meta.arguments.arguments.length == 0 || meta.arguments.arguments.length > 2) {
|
||||||
|
throw new ArgumentError(
|
||||||
|
'Incorrect value passed to HostListener. Expected 1 or 2.');
|
||||||
|
}
|
||||||
|
|
||||||
|
final eventName = _getHostListenerEventName(meta);
|
||||||
|
final params = _getHostListenerParams(meta);
|
||||||
|
_host['(${eventName})'] = '${node.name}($params)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO Use AnnotationMatcher instead of string matching
|
||||||
|
bool _isAnnotation(Annotation node, String annotationName) {
|
||||||
|
var id = node.name;
|
||||||
|
final name = id is PrefixedIdentifier ? '${id.identifier}' : '$id';
|
||||||
|
return name == annotationName;
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getRenamedValue(Annotation node) {
|
||||||
|
if (node.arguments.arguments.length == 1) {
|
||||||
|
final renamed = naiveEval(node.arguments.arguments.single);
|
||||||
|
if (renamed is! String) {
|
||||||
|
throw new ArgumentError(
|
||||||
|
'Incorrect value. Expected a String, but got "${renamed}".');
|
||||||
|
}
|
||||||
|
return renamed;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getHostListenerEventName(Annotation node) {
|
||||||
|
final name = naiveEval(node.arguments.arguments.first);
|
||||||
|
if (name is! String) {
|
||||||
|
throw new ArgumentError(
|
||||||
|
'Incorrect event name. Expected a String, but got "${name}".');
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getHostListenerParams(Annotation node) {
|
||||||
|
if (node.arguments.arguments.length == 2) {
|
||||||
|
return naiveEval(node.arguments.arguments[1]).join(',');
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Object visitClassDeclaration(ClassDeclaration node) {
|
Object visitClassDeclaration(ClassDeclaration node) {
|
||||||
node.metadata.accept(this);
|
node.metadata.accept(this);
|
||||||
|
@ -237,6 +326,8 @@ class _DirectiveMetadataVisitor extends Object
|
||||||
_lifecycleHooks = node.implementsClause != null
|
_lifecycleHooks = node.implementsClause != null
|
||||||
? node.implementsClause.accept(_lifecycleVisitor)
|
? node.implementsClause.accept(_lifecycleVisitor)
|
||||||
: const [];
|
: const [];
|
||||||
|
|
||||||
|
node.members.accept(this);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -494,6 +494,34 @@ void allTests() {
|
||||||
..prefix = 'dep2');
|
..prefix = 'dep2');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should merge `outputs` from the annotation and fields.',
|
||||||
|
() async {
|
||||||
|
var model = await _testCreateModel('directives_files/components.dart');
|
||||||
|
expect(model.types['ComponentWithOutputs'].outputs).
|
||||||
|
toEqual({'a': 'a', 'b': 'b', 'c': 'renamed'});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should merge `inputs` from the annotation and fields.',
|
||||||
|
() async {
|
||||||
|
var model = await _testCreateModel('directives_files/components.dart');
|
||||||
|
expect(model.types['ComponentWithInputs'].inputs).
|
||||||
|
toEqual({'a': 'a', 'b': 'b', 'c': 'renamed'});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should merge host bindings from the annotation and fields.',
|
||||||
|
() async {
|
||||||
|
var model = await _testCreateModel('directives_files/components.dart');
|
||||||
|
expect(model.types['ComponentWithHostBindings'].hostProperties).
|
||||||
|
toEqual({'a': 'a', 'b': 'b', 'renamed': 'c'});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should merge host listeners from the annotation and fields.',
|
||||||
|
() async {
|
||||||
|
var model = await _testCreateModel('directives_files/components.dart');
|
||||||
|
expect(model.types['ComponentWithHostListeners'].hostListeners).
|
||||||
|
toEqual({'a': 'onA()', 'b': 'onB()', 'c': 'onC(\$event.target,\$event.target.value)'});
|
||||||
|
});
|
||||||
|
|
||||||
it('should warn if @Component has a `template` and @View is present.',
|
it('should warn if @Component has a `template` and @View is present.',
|
||||||
() async {
|
() async {
|
||||||
final logger = new RecordingLogger();
|
final logger = new RecordingLogger();
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
library angular2.test.transform.directive_processor.directive_files.components;
|
library angular2.test.transform.directive_processor.directive_files.components;
|
||||||
|
|
||||||
import 'package:angular2/angular2.dart'
|
import 'package:angular2/angular2.dart'
|
||||||
show Component, Directive, View, NgElement;
|
show Component, Directive, View, NgElement, Output, Input;
|
||||||
import 'dep1.dart';
|
import 'dep1.dart';
|
||||||
import 'dep2.dart' as dep2;
|
import 'dep2.dart' as dep2;
|
||||||
|
|
||||||
|
@ -18,3 +18,47 @@ class ViewFirst {}
|
||||||
template: '<dep1></dep1><dep2></dep2>',
|
template: '<dep1></dep1><dep2></dep2>',
|
||||||
directives: [Dep, dep2.Dep])
|
directives: [Dep, dep2.Dep])
|
||||||
class ComponentOnly {}
|
class ComponentOnly {}
|
||||||
|
|
||||||
|
@Component(
|
||||||
|
selector: 'component-with-outputs',
|
||||||
|
template: '<dep1></dep1><dep2></dep2>',
|
||||||
|
outputs: ['a']
|
||||||
|
)
|
||||||
|
class ComponentWithOutputs {
|
||||||
|
@Output() Object b;
|
||||||
|
@Output('renamed') Object c;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component(
|
||||||
|
selector: 'component-with-inputs',
|
||||||
|
template: '<dep1></dep1><dep2></dep2>',
|
||||||
|
inputs: ['a']
|
||||||
|
)
|
||||||
|
class ComponentWithInputs {
|
||||||
|
@Input() Object b;
|
||||||
|
@Input('renamed') Object c;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component(
|
||||||
|
selector: 'component-with-inputs',
|
||||||
|
template: '<dep1></dep1><dep2></dep2>',
|
||||||
|
host: {
|
||||||
|
'[a]':'a'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
class ComponentWithHostBindings {
|
||||||
|
@HostBinding() Object b;
|
||||||
|
@HostBinding('renamed') Object c;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component(
|
||||||
|
selector: 'component-with-inputs',
|
||||||
|
template: '<dep1></dep1><dep2></dep2>',
|
||||||
|
host: {
|
||||||
|
'(a)':'onA()'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
class ComponentWithHostListeners {
|
||||||
|
@HostListener('b') void onB() {}
|
||||||
|
@HostListener('c', ['\$event.target', '\$event.target.value']) void onC(t,v) {}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue