diff --git a/analyzer_plugin/lib/src/tasks.dart b/analyzer_plugin/lib/src/tasks.dart
index 1e99e9e3..803eb61a 100644
--- a/analyzer_plugin/lib/src/tasks.dart
+++ b/analyzer_plugin/lib/src/tasks.dart
@@ -260,6 +260,19 @@ class BuildUnitDirectivesTask extends SourceBasedAnalysisTask
TypeProvider typeProvider;
+ /**
+ * Since represents an instantiation of MyComp,
+ * especially when MyComp is generic or its superclasses are, we need
+ * this. Cache instead of passing around everywhere.
+ */
+ InterfaceType _instantiatedClassType;
+
+ /**
+ * The [ClassElement] being used to create the current component,
+ * stored here instead of passing around everywhere.
+ */
+ ClassElement _currentClassElement;
+
@override
void internalPerform() {
typeProvider = getRequiredInput(TYPE_PROVIDER_INPUT);
@@ -296,7 +309,8 @@ class BuildUnitDirectivesTask extends SourceBasedAnalysisTask
*/
AbstractDirective _createDirective(
ast.ClassDeclaration classDeclaration, ast.Annotation node) {
- ClassElement classElement = classDeclaration.element;
+ _currentClassElement = classDeclaration.element;
+ _instantiatedClassType = _instantiateClass(_currentClassElement);
// TODO(scheglov) add support for all the arguments
bool isComponent = _isAngularAnnotation(node, 'Component');
bool isDirective = _isAngularAnnotation(node, 'Directive');
@@ -309,20 +323,20 @@ class BuildUnitDirectivesTask extends SourceBasedAnalysisTask
List inputElements = [];
List outputElements = [];
{
- inputElements.addAll(_parseHeaderInputs(classElement, node));
- outputElements.addAll(_parseHeaderOutputs(classElement, node));
+ inputElements.addAll(_parseHeaderInputs(node));
+ outputElements.addAll(_parseHeaderOutputs(node));
_parseMemberInputsAndOutputs(
classDeclaration, inputElements, outputElements);
}
if (isComponent) {
- return new Component(classElement,
+ return new Component(_currentClassElement,
exportAs: exportAs,
inputs: inputElements,
outputs: outputElements,
selector: selector);
}
if (isDirective) {
- return new Directive(classElement,
+ return new Directive(_currentClassElement,
exportAs: exportAs,
inputs: inputElements,
outputs: outputElements,
@@ -409,8 +423,7 @@ class BuildUnitDirectivesTask extends SourceBasedAnalysisTask
}
}
- InputElement _parseHeaderInput(
- ClassElement classElement, ast.Expression expression) {
+ InputElement _parseHeaderInput(ast.Expression expression) {
Tuple4 nameValueAndRanges =
_parseHeaderNameValueSourceRanges(expression);
if (nameValueAndRanges != null) {
@@ -419,25 +432,17 @@ class BuildUnitDirectivesTask extends SourceBasedAnalysisTask
var name = nameValueAndRanges.item3;
var nameRange = nameValueAndRanges.item4;
- PropertyAccessorElement setter =
- _resolveSetter(classElement, expression, name);
-
- return new InputElement(
- boundName,
- boundRange.offset,
- boundRange.length,
- target.source,
- setter,
- nameRange,
- _getSetterType(classElement, setter));
+ PropertyAccessorElement setter = _resolveSetter(expression, name);
+
+ return new InputElement(boundName, boundRange.offset, boundRange.length,
+ target.source, setter, nameRange, _getSetterType(setter));
} else {
// TODO(mfairhurst) report a warning
return null;
}
}
- OutputElement _parseHeaderOutput(
- ClassElement classElement, ast.Expression expression) {
+ OutputElement _parseHeaderOutput(ast.Expression expression) {
Tuple4 nameValueAndRanges =
_parseHeaderNameValueSourceRanges(expression);
if (nameValueAndRanges != null) {
@@ -446,10 +451,9 @@ class BuildUnitDirectivesTask extends SourceBasedAnalysisTask
var name = nameValueAndRanges.item3;
var nameRange = nameValueAndRanges.item4;
- PropertyAccessorElement getter =
- _resolveGetter(classElement, expression, name);
+ PropertyAccessorElement getter = _resolveGetter(expression, name);
- var eventType = getEventType(classElement, getter, name);
+ var eventType = getEventType(getter, name);
return new OutputElement(boundName, boundRange.offset, boundRange.length,
target.source, getter, nameRange, eventType);
@@ -459,18 +463,26 @@ class BuildUnitDirectivesTask extends SourceBasedAnalysisTask
}
}
- DartType _getSetterType(
- ClassElement classElement, PropertyAccessorElement setter) {
+ DartType _getSetterType(PropertyAccessorElement setter) {
+ if (setter != null) {
+ setter = _instantiatedClassType.lookUpInheritedSetter(setter.name,
+ thisType: true);
+ }
+
if (setter != null && setter.variable != null) {
var type = setter.variable.type;
- return _deparameterizeType(classElement, type);
+ return type;
}
return null;
}
- DartType getEventType(
- ClassElement classElement, PropertyAccessorElement getter, String name) {
+ DartType getEventType(PropertyAccessorElement getter, String name) {
+ if (getter != null) {
+ getter = _instantiatedClassType.lookUpInheritedGetter(getter.name,
+ thisType: true);
+ }
+
if (getter != null && getter.type != null) {
var returnType = getter.type.returnType;
if (returnType != null && returnType is InterfaceType) {
@@ -478,7 +490,7 @@ class BuildUnitDirectivesTask extends SourceBasedAnalysisTask
dart.DartType streamedType =
context.typeSystem.mostSpecificTypeArgument(returnType, streamType);
if (streamedType != null) {
- return _deparameterizeType(classElement, streamedType);
+ return streamedType;
} else {
errorReporter.reportErrorForNode(
AngularWarningCode.OUTPUT_MUST_BE_EVENTEMITTER,
@@ -496,12 +508,8 @@ class BuildUnitDirectivesTask extends SourceBasedAnalysisTask
return typeProvider.dynamicType;
}
- DartType _deparameterizeType(
- ClassElement classElement, DartType parameterizedType) {
- if (parameterizedType == null) {
- return null;
- }
-
+ DartType _instantiateClass(ClassElement classElement) {
+ // TODO use `insantiateToBounds` for better all around support
// See #91 for discussion about bugs related to bounds
var getBound = (p) {
return p.bound == null
@@ -509,14 +517,11 @@ class BuildUnitDirectivesTask extends SourceBasedAnalysisTask
: p.bound.resolveToBound(typeProvider.dynamicType);
};
- var getType = (p) => p.type;
var bounds = classElement.typeParameters.map(getBound).toList();
- var parameters = classElement.typeParameters.map(getType).toList();
- return parameterizedType.substitute2(bounds, parameters);
+ return classElement.type.instantiate(bounds);
}
- List _parseHeaderInputs(
- ClassElement classElement, ast.Annotation node) {
+ List _parseHeaderInputs(ast.Annotation node) {
ast.ListLiteral descList = _getListLiteralNamedArgument(
node, const ['inputs', 'properties']);
if (descList == null) {
@@ -525,7 +530,7 @@ class BuildUnitDirectivesTask extends SourceBasedAnalysisTask
// Create an input for each element.
List inputElements = [];
for (ast.Expression element in descList.elements) {
- InputElement inputElement = _parseHeaderInput(classElement, element);
+ InputElement inputElement = _parseHeaderInput(element);
if (inputElement != null) {
inputElements.add(inputElement);
}
@@ -533,8 +538,7 @@ class BuildUnitDirectivesTask extends SourceBasedAnalysisTask
return inputElements;
}
- List _parseHeaderOutputs(
- ClassElement classElement, ast.Annotation node) {
+ List _parseHeaderOutputs(ast.Annotation node) {
ast.ListLiteral descList =
_getListLiteralNamedArgument(node, const ['outputs']);
if (descList == null) {
@@ -543,7 +547,7 @@ class BuildUnitDirectivesTask extends SourceBasedAnalysisTask
// Create an output for each element.
List outputs = [];
for (ast.Expression element in descList.elements) {
- OutputElement outputElement = _parseHeaderOutput(classElement, element);
+ OutputElement outputElement = _parseHeaderOutput(element);
if (outputElement != null) {
outputs.add(outputElement);
}
@@ -556,12 +560,8 @@ class BuildUnitDirectivesTask extends SourceBasedAnalysisTask
* the given `@Input` or `@Output` [annotation], and add it to the
* [inputElements] or [outputElements] array.
*/
- _parseMemberInputOrOutput(
- ClassElement classElement,
- ast.ClassMember node,
- ast.Annotation annotation,
- List inputElements,
- List outputElements) {
+ _parseMemberInputOrOutput(ast.ClassMember node, ast.Annotation annotation,
+ List inputElements, List outputElements) {
// analyze the annotation
final isInput = _isAngularAnnotation(annotation, 'Input');
final isOutput = _isAngularAnnotation(annotation, 'Output');
@@ -623,16 +623,10 @@ class BuildUnitDirectivesTask extends SourceBasedAnalysisTask
}
if (isInput) {
- inputElements.add(new InputElement(
- name,
- nameOffset,
- nameLength,
- target.source,
- property,
- null,
- _getSetterType(classElement, property)));
+ inputElements.add(new InputElement(name, nameOffset, nameLength,
+ target.source, property, null, _getSetterType(property)));
} else {
- var eventType = getEventType(classElement, property, name);
+ var eventType = getEventType(property, name);
outputElements.add(new OutputElement(name, nameOffset, nameLength,
target.source, property, null, eventType));
}
@@ -647,7 +641,7 @@ class BuildUnitDirectivesTask extends SourceBasedAnalysisTask
for (ast.ClassMember member in node.members) {
for (ast.Annotation annotation in member.metadata) {
_parseMemberInputOrOutput(
- node.element, member, annotation, inputElements, outputElements);
+ member, annotation, inputElements, outputElements);
}
}
}
@@ -683,31 +677,31 @@ class BuildUnitDirectivesTask extends SourceBasedAnalysisTask
}
/**
- * Resolve the input setter with the given [name] in [classElement].
+ * Resolve the input setter with the given [name] in [_currentClassElement].
* If undefined, report a warning and return `null`.
*/
PropertyAccessorElement _resolveSetter(
- ClassElement classElement, ast.SimpleStringLiteral literal, String name) {
+ ast.SimpleStringLiteral literal, String name) {
PropertyAccessorElement setter =
- classElement.lookUpSetter(name, classElement.library);
+ _currentClassElement.lookUpSetter(name, _currentClassElement.library);
if (setter == null) {
errorReporter.reportErrorForNode(StaticTypeWarningCode.UNDEFINED_SETTER,
- literal, [name, classElement.displayName]);
+ literal, [name, _currentClassElement.displayName]);
}
return setter;
}
/**
- * Resolve the output getter with the given [name] in [classElement].
+ * Resolve the output getter with the given [name] in [_currentClassElement].
* If undefined, report a warning and return `null`.
*/
PropertyAccessorElement _resolveGetter(
- ClassElement classElement, ast.SimpleStringLiteral literal, String name) {
+ ast.SimpleStringLiteral literal, String name) {
PropertyAccessorElement getter =
- classElement.lookUpGetter(name, classElement.library);
+ _currentClassElement.lookUpGetter(name, _currentClassElement.library);
if (getter == null) {
errorReporter.reportErrorForNode(StaticTypeWarningCode.UNDEFINED_GETTER,
- literal, [name, classElement.displayName]);
+ literal, [name, _currentClassElement.displayName]);
}
return getter;
}
diff --git a/analyzer_plugin/test/resolver_test.dart b/analyzer_plugin/test/resolver_test.dart
index e5d6a5d3..b6a6aa87 100644
--- a/analyzer_plugin/test/resolver_test.dart
+++ b/analyzer_plugin/test/resolver_test.dart
@@ -668,6 +668,60 @@ class GenericComponent {
errorListener.assertNoErrors();
}
+ void test_expression_inputAndOutputBinding_genericDirectiveChild_ok() {
+ _addDartSource(r'''
+@Component(selector: 'test-panel',
+ directives: const [GenericComponent], templateUrl: 'test_panel.html')
+class TestPanel {
+ String string;
+}
+class Generic {
+ EventEmitter output;
+ T input;
+
+ EventEmitter twoWayChange;
+ T twoWay;
+}
+@Component(selector: 'generic-comp', template: '',
+ inputs: ['input', 'twoWay'], outputs: ['output', 'twoWayChange'])
+class GenericComponent extends Generic {
+}
+''');
+ var code = r"""
+
+""";
+ _addHtmlSource(code);
+ _resolveSingleTemplate(dartSource);
+ errorListener.assertNoErrors();
+ }
+
+ void test_expression_inputAndOutputBinding_extendGenericUnbounded_ok() {
+ _addDartSource(r'''
+@Component(selector: 'test-panel',
+ directives: const [GenericComponent], templateUrl: 'test_panel.html')
+class TestPanel {
+ String string;
+}
+class Generic {
+ EventEmitter output;
+ T input;
+
+ EventEmitter twoWayChange;
+ T twoWay;
+}
+@Component(selector: 'generic-comp', template: '',
+ inputs: ['input', 'twoWay'], outputs: ['output', 'twoWayChange'])
+class GenericComponent extends Generic {
+}
+''');
+ var code = r"""
+
+""";
+ _addHtmlSource(code);
+ _resolveSingleTemplate(dartSource);
+ errorListener.assertNoErrors();
+ }
+
void test_expression_inputAndOutputBinding_genericDirective_chain_ok() {
_addDartSource(r'''
@Component(selector: 'test-panel',
diff --git a/analyzer_plugin/test/tasks_test.dart b/analyzer_plugin/test/tasks_test.dart
index 252bfe8d..b89c764e 100644
--- a/analyzer_plugin/test/tasks_test.dart
+++ b/analyzer_plugin/test/tasks_test.dart
@@ -907,6 +907,93 @@ class MyComponent {
errorListener.assertNoErrors();
}
+ void test_parameterizedInheritedInputsOutputs() {
+ String code = r'''
+import '/angular2/angular2.dart';
+
+class Generic {
+ T input;
+ EventEmitter output;
+}
+
+@Component(
+ selector: 'my-component',
+ template: '',
+ inputs: const ['input'],
+ outputs: const ['output'])
+class MyComponent extends Generic {
+}
+''';
+ Source source = newSource('/test.dart', code);
+ LibrarySpecificUnit target = new LibrarySpecificUnit(source, source);
+
+ computeResult(target, DIRECTIVES_IN_UNIT);
+ expect(task, new isInstanceOf());
+ // validate
+ List directives = outputs[DIRECTIVES_IN_UNIT];
+ Component component = directives.single;
+ List compInputs = component.inputs;
+ expect(compInputs, hasLength(1));
+ {
+ InputElement input = compInputs[0];
+ expect(input.name, 'input');
+ expect(input.setterType, isNotNull);
+ expect(input.setterType.toString(), equals("dynamic"));
+ }
+
+ List compOutputs = component.outputs;
+ expect(compOutputs, hasLength(1));
+ {
+ OutputElement output = compOutputs[0];
+ expect(output.name, 'output');
+ expect(output.eventType, isNotNull);
+ expect(output.eventType.toString(), equals("dynamic"));
+ }
+ }
+
+ void test_parameterizedInheritedInputsOutputsSpecified() {
+ String code = r'''
+import '/angular2/angular2.dart';
+
+class Generic {
+ T input;
+ EventEmitter output;
+}
+
+@Component(
+ selector: 'my-component',
+ template: '',
+ inputs: const ['input'],
+ outputs: const ['output'])
+class MyComponent extends Generic {
+}
+''';
+ Source source = newSource('/test.dart', code);
+ LibrarySpecificUnit target = new LibrarySpecificUnit(source, source);
+ computeResult(target, DIRECTIVES_IN_UNIT);
+ expect(task, new isInstanceOf());
+ // validate
+ List directives = outputs[DIRECTIVES_IN_UNIT];
+ Component component = directives.single;
+ List compInputs = component.inputs;
+ expect(compInputs, hasLength(1));
+ {
+ InputElement input = compInputs[0];
+ expect(input.name, 'input');
+ expect(input.setterType, isNotNull);
+ expect(input.setterType.toString(), equals("String"));
+ }
+
+ List compOutputs = component.outputs;
+ expect(compOutputs, hasLength(1));
+ {
+ OutputElement output = compOutputs[0];
+ expect(output.name, 'output');
+ expect(output.eventType, isNotNull);
+ expect(output.eventType.toString(), equals("String"));
+ }
+ }
+
void test_noDirectives() {
Source source = newSource(
'/test.dart',