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',