Skip to content
This repository was archived by the owner on Jun 20, 2019. It is now read-only.

Commit 67960cc

Browse files
Allow Stream<T> and subtypes of it in outputs, refs #76 (#89)
* Allow Stream<T> and subtypes of it in outputs, refs #76 Use the mostSpecificTypeArgument method API to get the stream type regardless of the inheritance tree and generics defined within it. Plus style fixes including in some old code
1 parent b3b62b3 commit 67960cc

File tree

2 files changed

+173
-13
lines changed

2 files changed

+173
-13
lines changed

analyzer_plugin/lib/src/tasks.dart

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ class BuildStandardHtmlComponentsTask extends AnalysisTask {
228228
class BuildUnitDirectivesTask extends SourceBasedAnalysisTask
229229
with _AnnotationProcessorMixin {
230230
static const String UNIT_INPUT = 'UNIT_INPUT';
231+
static const String TYPE_PROVIDER_INPUT = 'TYPE_PROVIDER_INPUT';
231232

232233
static final TaskDescriptor DESCRIPTOR = new TaskDescriptor(
233234
'BuildUnitDirectivesTask',
@@ -241,8 +242,11 @@ class BuildUnitDirectivesTask extends SourceBasedAnalysisTask
241242
@override
242243
TaskDescriptor get descriptor => DESCRIPTOR;
243244

245+
TypeProvider typeProvider;
246+
244247
@override
245248
void internalPerform() {
249+
typeProvider = getRequiredInput(TYPE_PROVIDER_INPUT);
246250
initAnnotationProcessor(target);
247251
//
248252
// Prepare inputs.
@@ -435,11 +439,13 @@ class BuildUnitDirectivesTask extends SourceBasedAnalysisTask
435439

436440
DartType getEventType(PropertyAccessorElement getter, String name) {
437441
if (getter != null && getter.type != null) {
438-
var returntype = getter.type.returnType;
439-
if (returntype != null && returntype is InterfaceType) {
440-
// TODO allow subtypes of EventEmitter
441-
if (returntype.element.name == 'EventEmitter') {
442-
return returntype.typeArguments[0]; // may be null
442+
var returnType = getter.type.returnType;
443+
if (returnType != null && returnType is InterfaceType) {
444+
dart.DartType streamType = typeProvider.streamType;
445+
dart.DartType streamedType =
446+
context.typeSystem.mostSpecificTypeArgument(returnType, streamType);
447+
if (streamedType != null) {
448+
return streamedType;
443449
} else {
444450
errorReporter.reportErrorForNode(
445451
AngularWarningCode.OUTPUT_MUST_BE_EVENTEMITTER,
@@ -454,7 +460,7 @@ class BuildUnitDirectivesTask extends SourceBasedAnalysisTask
454460
}
455461
}
456462

457-
return null;
463+
return typeProvider.dynamicType;
458464
}
459465

460466
List<InputElement> _parseHeaderInputs(
@@ -650,7 +656,10 @@ class BuildUnitDirectivesTask extends SourceBasedAnalysisTask
650656
* given [target].
651657
*/
652658
static Map<String, TaskInput> buildInputs(LibrarySpecificUnit target) {
653-
return <String, TaskInput>{UNIT_INPUT: RESOLVED_UNIT.of(target)};
659+
return <String, TaskInput>{
660+
UNIT_INPUT: RESOLVED_UNIT.of(target),
661+
TYPE_PROVIDER_INPUT: TYPE_PROVIDER.of(AnalysisContextTarget.request)
662+
};
654663
}
655664

656665
/**
@@ -1381,18 +1390,18 @@ class _BuildStandardHtmlComponentsVisitor extends RecursiveAstVisitor {
13811390

13821391
if (!outputMap.containsKey(name)) {
13831392
if (accessor.isGetter) {
1384-
var returntype =
1393+
var returnType =
13851394
accessor.type == null ? null : accessor.type.returnType;
1386-
DartType eventtype = null;
1387-
if (returntype != null && returntype is InterfaceType) {
1395+
DartType eventType = null;
1396+
if (returnType != null && returnType is InterfaceType) {
13881397
// TODO allow subtypes of ElementStream? This is a generated file
13891398
// so might not be necessary.
1390-
if (returntype.element.name == 'ElementStream') {
1391-
eventtype = returntype.typeArguments[0]; // may be null
1399+
if (returnType.element.name == 'ElementStream') {
1400+
eventType = returnType.typeArguments[0]; // may be null
13921401
}
13931402
}
13941403
outputMap[name] = new OutputElement(name, accessor.nameOffset,
1395-
accessor.nameLength, accessor.source, accessor, null, eventtype);
1404+
accessor.nameLength, accessor.source, accessor, null, eventType);
13961405
}
13971406
}
13981407
});

analyzer_plugin/test/tasks_test.dart

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,12 +620,135 @@ class MyComponent {
620620
errorListener.assertNoErrors();
621621
}
622622

623+
void test_outputs_streamIsOk() {
624+
String code = r'''
625+
import '/angular2/angular2.dart';
626+
import 'dart:async';
627+
628+
@Component(
629+
selector: 'my-component',
630+
template: '<p></p>')
631+
class MyComponent {
632+
@Output()
633+
Stream<int> myOutput;
634+
}
635+
''';
636+
Source source = newSource('/test.dart', code);
637+
LibrarySpecificUnit target = new LibrarySpecificUnit(source, source);
638+
computeResult(target, DIRECTIVES_IN_UNIT);
639+
expect(task, new isInstanceOf<BuildUnitDirectivesTask>());
640+
// validate
641+
List<AbstractDirective> directives = outputs[DIRECTIVES_IN_UNIT];
642+
Component component = directives.single;
643+
List<OutputElement> compOutputs = component.outputs;
644+
expect(compOutputs, hasLength(1));
645+
{
646+
OutputElement output = compOutputs[0];
647+
expect(output.eventType, isNotNull);
648+
expect(output.eventType.toString(), equals("int"));
649+
}
650+
}
651+
652+
void test_outputs_extendStreamIsOk() {
653+
String code = r'''
654+
import '/angular2/angular2.dart';
655+
import 'dart:async';
656+
657+
abstract class MyStream<T> implements Stream<T> { }
658+
659+
@Component(
660+
selector: 'my-component',
661+
template: '<p></p>')
662+
class MyComponent {
663+
@Output()
664+
MyStream<int> myOutput;
665+
}
666+
''';
667+
Source source = newSource('/test.dart', code);
668+
LibrarySpecificUnit target = new LibrarySpecificUnit(source, source);
669+
computeResult(target, DIRECTIVES_IN_UNIT);
670+
expect(task, new isInstanceOf<BuildUnitDirectivesTask>());
671+
// validate
672+
List<AbstractDirective> directives = outputs[DIRECTIVES_IN_UNIT];
673+
Component component = directives.single;
674+
List<OutputElement> compOutputs = component.outputs;
675+
expect(compOutputs, hasLength(1));
676+
{
677+
OutputElement output = compOutputs[0];
678+
expect(output.eventType, isNotNull);
679+
expect(output.eventType.toString(), equals("int"));
680+
}
681+
}
682+
683+
void test_outputs_extendStreamSpecializedIsOk() {
684+
String code = r'''
685+
import '/angular2/angular2.dart';
686+
import 'dart:async';
687+
688+
class MyStream extends Stream<int> { }
689+
690+
@Component(
691+
selector: 'my-component',
692+
template: '<p></p>')
693+
class MyComponent {
694+
@Output()
695+
MyStream myOutput;
696+
}
697+
''';
698+
Source source = newSource('/test.dart', code);
699+
LibrarySpecificUnit target = new LibrarySpecificUnit(source, source);
700+
computeResult(target, DIRECTIVES_IN_UNIT);
701+
expect(task, new isInstanceOf<BuildUnitDirectivesTask>());
702+
// validate
703+
List<AbstractDirective> directives = outputs[DIRECTIVES_IN_UNIT];
704+
Component component = directives.single;
705+
List<OutputElement> compOutputs = component.outputs;
706+
expect(compOutputs, hasLength(1));
707+
{
708+
OutputElement output = compOutputs[0];
709+
expect(output.eventType, isNotNull);
710+
expect(output.eventType.toString(), equals("int"));
711+
}
712+
}
713+
714+
void test_outputs_extendStreamUntypedIsOk() {
715+
String code = r'''
716+
import '/angular2/angular2.dart';
717+
import 'dart:async';
718+
719+
class MyStream extends Stream { }
720+
721+
@Component(
722+
selector: 'my-component',
723+
template: '<p></p>')
724+
class MyComponent {
725+
@Output()
726+
MyStream myOutput;
727+
}
728+
''';
729+
Source source = newSource('/test.dart', code);
730+
LibrarySpecificUnit target = new LibrarySpecificUnit(source, source);
731+
computeResult(target, DIRECTIVES_IN_UNIT);
732+
expect(task, new isInstanceOf<BuildUnitDirectivesTask>());
733+
// validate
734+
List<AbstractDirective> directives = outputs[DIRECTIVES_IN_UNIT];
735+
Component component = directives.single;
736+
List<OutputElement> compOutputs = component.outputs;
737+
expect(compOutputs, hasLength(1));
738+
{
739+
OutputElement output = compOutputs[0];
740+
expect(output.eventType, isNotNull);
741+
expect(output.eventType.toString(), equals("dynamic"));
742+
}
743+
}
744+
623745
void test_outputs_notEventEmitterTypeError() {
624746
String code = r'''
625747
import '/angular2/angular2.dart';
626748
627749
@Component(
628750
selector: 'my-component',
751+
template: '<p></p>')
629752
class MyComponent {
630753
@Output()
631754
int badOutput;
@@ -639,6 +762,34 @@ class MyComponent {
639762
AngularWarningCode.OUTPUT_MUST_BE_EVENTEMITTER, code, "badOutput");
640763
}
641764

765+
void test_outputs_extendStreamNotStreamHasDynamicEventType() {
766+
String code = r'''
767+
import '/angular2/angular2.dart';
768+
769+
@Component(
770+
selector: 'my-component',
771+
template: '<p></p>')
772+
class MyComponent {
773+
@Output()
774+
int badOutput;
775+
}
776+
''';
777+
Source source = newSource('/test.dart', code);
778+
LibrarySpecificUnit target = new LibrarySpecificUnit(source, source);
779+
computeResult(target, DIRECTIVES_IN_UNIT);
780+
expect(task, new isInstanceOf<BuildUnitDirectivesTask>());
781+
// validate
782+
List<AbstractDirective> directives = outputs[DIRECTIVES_IN_UNIT];
783+
Component component = directives.single;
784+
List<OutputElement> compOutputs = component.outputs;
785+
expect(compOutputs, hasLength(1));
786+
{
787+
OutputElement output = compOutputs[0];
788+
expect(output.eventType, isNotNull);
789+
expect(output.eventType.toString(), equals("dynamic"));
790+
}
791+
}
792+
642793
void test_noDirectives() {
643794
Source source = newSource(
644795
'/test.dart',

0 commit comments

Comments
 (0)