Merge remote-tracking branch 'origin/master' into salt-hashes
diff --git a/angular_analyzer_plugin/lib/plugin.dart b/angular_analyzer_plugin/lib/plugin.dart
index 825f8b8..0d2cf0e 100644
--- a/angular_analyzer_plugin/lib/plugin.dart
+++ b/angular_analyzer_plugin/lib/plugin.dart
@@ -14,11 +14,14 @@
 import 'package:analyzer_plugin/protocol/protocol_generated.dart' as plugin;
 import 'package:analyzer_plugin/plugin/completion_mixin.dart';
 import 'package:analyzer_plugin/utilities/completion/completion_core.dart';
+import 'package:analyzer_plugin/src/utilities/completion/completion_core.dart';
+import 'package:angular_analyzer_plugin/src/noop_driver.dart';
 import 'package:angular_analyzer_plugin/src/notification_manager.dart';
 import 'package:angular_analyzer_plugin/src/completion_request.dart';
 import 'package:angular_analyzer_plugin/src/angular_driver.dart';
 import 'package:angular_analyzer_plugin/src/completion.dart';
 import 'package:analyzer_plugin/protocol/protocol.dart' as plugin;
+import 'package:yaml/yaml.dart';
 
 class AngularAnalyzerPlugin extends ServerPlugin with CompletionMixin {
   AngularAnalyzerPlugin(ResourceProvider provider) : super(provider);
@@ -36,9 +39,33 @@
   String get contactInfo =>
       'Please file issues at https://github.com/dart-lang/angular_analyzer_plugin';
 
+  bool isEnabled(String optionsFilePath) {
+    if (optionsFilePath == null || optionsFilePath.isEmpty) {
+      return null;
+    }
+
+    final file = resourceProvider.getFile(optionsFilePath);
+
+    if (!file.exists) {
+      return null;
+    }
+
+    final contents = file.readAsStringSync();
+    final options = loadYaml(contents);
+
+    return options['plugins'] != null &&
+        options['plugins']['angular'] != null &&
+        options['plugins']['angular']['enabled'] == true;
+  }
+
   @override
   AnalysisDriverGeneric createAnalysisDriver(plugin.ContextRoot contextRoot) {
-    final root = new ContextRoot(contextRoot.root, contextRoot.exclude);
+    final root = new ContextRoot(contextRoot.root, contextRoot.exclude)
+      ..optionsFilePath = contextRoot.optionsFile;
+    if (!isEnabled(root.optionsFilePath)) {
+      return new NoopDriver();
+    }
+
     // TODO new API to get this path safely?
     final logger = new PerformanceLog(new StringBuffer());
     final builder = new ContextBuilder(resourceProvider, sdkManager, null)
@@ -85,15 +112,22 @@
     }
   }
 
+  AngularDriver angularDriverForPath(String path) {
+    var driver = super.driverForPath(path);
+    if (driver is AngularDriver) {
+      return driver;
+    }
+    return null;
+  }
+
   @override
   void contentChanged(String path) {
-    final contextRoot = contextRootContaining(path);
-
-    if (contextRoot == null) {
+    final driver = angularDriverForPath(path);
+    if (driver == null) {
       return;
     }
 
-    final driver = (driverMap[contextRoot] as AngularDriver)
+    driver
       ..addFile(path) // TODO new API to only do this on file add
       ..fileChanged(path);
 
@@ -127,8 +161,13 @@
   Future<CompletionRequest> getCompletionRequest(
       plugin.CompletionGetSuggestionsParams parameters) async {
     final path = parameters.file;
-    final AngularDriver driver = driverForPath(path);
+    final driver = angularDriverForPath(path);
     final offset = parameters.offset;
+
+    if (driver == null) {
+      return new DartCompletionRequestImpl(resourceProvider, offset, null);
+    }
+
     final templates = await driver.getTemplatesForFile(path);
     final standardHtml = await driver.getStandardHtml();
     assert(standardHtml != null);
@@ -137,11 +176,16 @@
   }
 
   @override
-  List<CompletionContributor> getCompletionContributors(String path) =>
-      <CompletionContributor>[
-        new AngularCompletionContributor(),
-        new NgInheritedReferenceContributor(),
-        new NgTypeMemberContributor(),
-        new NgOffsetLengthContributor(),
-      ];
+  List<CompletionContributor> getCompletionContributors(String path) {
+    if (angularDriverForPath(path) == null) {
+      return [];
+    }
+
+    return <CompletionContributor>[
+      new AngularCompletionContributor(),
+      new NgInheritedReferenceContributor(),
+      new NgTypeMemberContributor(),
+      new NgOffsetLengthContributor(),
+    ];
+  }
 }
diff --git a/angular_analyzer_plugin/lib/src/angular_driver.dart b/angular_analyzer_plugin/lib/src/angular_driver.dart
index a6c3ddb..cd04e82 100644
--- a/angular_analyzer_plugin/lib/src/angular_driver.dart
+++ b/angular_analyzer_plugin/lib/src/angular_driver.dart
@@ -367,7 +367,7 @@
     for (final dartContext
         in _fileTracker.getDartPathsReferencingHtml(htmlPath)) {
       final pairResult = await resolveHtmlFrom(htmlPath, dartContext);
-      result.directives.addAll(pairResult.directives);
+      result.angularAnnotatedClasses.addAll(pairResult.angularAnnotatedClasses);
       result.errors.addAll(pairResult.errors);
     }
 
@@ -421,7 +421,7 @@
 
   Future<DirectivesResult> resolveHtmlFrom(
       String htmlPath, String dartPath) async {
-    final result = await getDirectives(dartPath);
+    final result = await getAngularAnnotatedClasses(dartPath);
     final directives = result.directives;
     final unit = (await dartDriver.getUnitElement(dartPath)).element;
     final htmlSource = _sourceFactory.forUri('file:$htmlPath');
@@ -590,7 +590,7 @@
       }
     }
 
-    final result = await getDirectives(path);
+    final result = await getAngularAnnotatedClasses(path);
     final directives = result.directives;
     final unit = (await dartDriver.getUnitElement(path)).element;
     if (unit == null) {
@@ -695,15 +695,15 @@
   Future<CompilationUnitElement> getUnit(String path) async =>
       (await dartDriver.getUnitElement(path)).element;
 
-  Future<List<AbstractDirective>> resynthesizeDirectives(
+  Future<List<AngularAnnotatedClass>> resynthesizeDirectives(
           UnlinkedDartSummary unlinked, String path) async =>
       new DirectiveLinker(this).resynthesizeDirectives(unlinked, path);
 
   @override
-  Future<List<AbstractDirective>> getUnlinkedDirectives(path) async =>
-      (await getDirectives(path)).directives;
+  Future<List<AngularAnnotatedClass>> getUnlinkedClasses(path) async =>
+      (await getAngularAnnotatedClasses(path)).angularAnnotatedClasses;
 
-  Future<DirectivesResult> getDirectives(String path) async {
+  Future<DirectivesResult> getAngularAnnotatedClasses(String path) async {
     final baseKey = _fileTracker.getContentSignature(path).toHex();
     final key = '$baseKey.ngunlinked';
     final bytes = byteStore.get(key);
@@ -723,8 +723,9 @@
     final source = dartResult.unit.element.source;
     final extractor =
         new DirectiveExtractor(ast, context.typeProvider, source, context);
-    final directives =
-        new List<AbstractDirective>.from(extractor.getDirectives());
+    final classes = extractor.getAngularAnnotatedClasses();
+    final directives = new List<AbstractDirective>.from(
+        classes.where((c) => c is AbstractDirective));
 
     final viewExtractor = new ViewExtractor(ast, directives, context, source)
       ..getViews();
@@ -755,7 +756,7 @@
 
     final errors = new List<AnalysisError>.from(extractor.errorListener.errors)
       ..addAll(viewExtractor.errorListener.errors);
-    final result = new DirectivesResult(directives, errors);
+    final result = new DirectivesResult(classes, errors);
     final summary = serializeDartResult(result);
     final newBytes = summary.toBuffer();
     byteStore.put(key, newBytes);
@@ -764,8 +765,12 @@
 
   UnlinkedDartSummaryBuilder serializeDartResult(DirectivesResult result) {
     final dirSums = serializeDirectives(result.directives);
+    final classSums = result.angularAnnotatedClasses
+        .where((c) => c is! AbstractDirective)
+        .map(serializeAnnotatedClass);
     final summary = new UnlinkedDartSummaryBuilder()
       ..directiveSummaries = dirSums
+      ..annotatedClasses = classSums
       ..errors = summarizeErrors(result.errors);
     return summary;
   }
@@ -774,38 +779,11 @@
       List<AbstractDirective> directives) {
     final dirSums = <SummarizedDirectiveBuilder>[];
     for (final directive in directives) {
-      final className = directive.classElement.name;
       final selector = directive.selector.originalString;
       final selectorOffset = directive.selector.offset;
       final exportAs = directive?.exportAs?.name;
       final exportAsOffset = directive?.exportAs?.nameOffset;
-      final inputs = <SummarizedBindableBuilder>[];
-      final outputs = <SummarizedBindableBuilder>[];
       final exports = <SummarizedExportedIdentifierBuilder>[];
-      final contentChildFields = <SummarizedContentChildFieldBuilder>[];
-      final contentChildrenFields = <SummarizedContentChildFieldBuilder>[];
-      for (final input in directive.inputs) {
-        final name = input.name;
-        final nameOffset = input.nameOffset;
-        final propName = input.setter.name.replaceAll('=', '');
-        final propNameOffset = input.setterRange.offset;
-        inputs.add(new SummarizedBindableBuilder()
-          ..name = name
-          ..nameOffset = nameOffset
-          ..propName = propName
-          ..propNameOffset = propNameOffset);
-      }
-      for (final output in directive.outputs) {
-        final name = output.name;
-        final nameOffset = output.nameOffset;
-        final propName = output.getter.name.replaceAll('=', '');
-        final propNameOffset = output.getterRange.offset;
-        outputs.add(new SummarizedBindableBuilder()
-          ..name = name
-          ..nameOffset = nameOffset
-          ..propName = propName
-          ..propNameOffset = propNameOffset);
-      }
       if (directive is Component) {
         for (final export in directive?.view?.exports ?? []) {
           exports.add(new SummarizedExportedIdentifierBuilder()
@@ -815,22 +793,6 @@
             ..length = export.span.length);
         }
       }
-      for (final childField in directive.contentChildFields) {
-        contentChildFields.add(new SummarizedContentChildFieldBuilder()
-          ..fieldName = childField.fieldName
-          ..nameOffset = childField.nameRange.offset
-          ..nameLength = childField.nameRange.length
-          ..typeOffset = childField.typeRange.offset
-          ..typeLength = childField.typeRange.length);
-      }
-      for (final childrenField in directive.contentChildrenFields) {
-        contentChildrenFields.add(new SummarizedContentChildFieldBuilder()
-          ..fieldName = childrenField.fieldName
-          ..nameOffset = childrenField.nameRange.offset
-          ..nameLength = childrenField.nameRange.length
-          ..typeOffset = childrenField.typeRange.offset
-          ..typeLength = childrenField.typeRange.length);
-      }
       final dirUseSums = <SummarizedDirectiveUseBuilder>[];
       final ngContents = <SummarizedNgContentBuilder>[];
       String templateUrl;
@@ -857,10 +819,10 @@
       }
 
       dirSums.add(new SummarizedDirectiveBuilder()
+        ..classAnnotations = serializeAnnotatedClass(directive)
         ..isComponent = directive is Component
         ..selectorStr = selector
         ..selectorOffset = selectorOffset
-        ..decoratedClassName = className
         ..exportAs = exportAs
         ..exportAsOffset = exportAsOffset
         ..templateText = templateText
@@ -869,17 +831,66 @@
         ..templateUrlOffset = templateUrlOffset
         ..templateUrlLength = templateUrlLength
         ..ngContents = ngContents
-        ..inputs = inputs
-        ..outputs = outputs
         ..exports = exports
-        ..subdirectives = dirUseSums
-        ..contentChildFields = contentChildFields
-        ..contentChildrenFields = contentChildrenFields);
+        ..subdirectives = dirUseSums);
     }
 
     return dirSums;
   }
 
+  SummarizedClassAnnotationsBuilder serializeAnnotatedClass(
+      AngularAnnotatedClass clazz) {
+    final className = clazz.classElement.name;
+    final inputs = <SummarizedBindableBuilder>[];
+    final outputs = <SummarizedBindableBuilder>[];
+    final contentChildFields = <SummarizedContentChildFieldBuilder>[];
+    final contentChildrenFields = <SummarizedContentChildFieldBuilder>[];
+    for (final input in clazz.inputs) {
+      final name = input.name;
+      final nameOffset = input.nameOffset;
+      final propName = input.setter.name.replaceAll('=', '');
+      final propNameOffset = input.setterRange.offset;
+      inputs.add(new SummarizedBindableBuilder()
+        ..name = name
+        ..nameOffset = nameOffset
+        ..propName = propName
+        ..propNameOffset = propNameOffset);
+    }
+    for (final output in clazz.outputs) {
+      final name = output.name;
+      final nameOffset = output.nameOffset;
+      final propName = output.getter.name.replaceAll('=', '');
+      final propNameOffset = output.getterRange.offset;
+      outputs.add(new SummarizedBindableBuilder()
+        ..name = name
+        ..nameOffset = nameOffset
+        ..propName = propName
+        ..propNameOffset = propNameOffset);
+    }
+    for (final childField in clazz.contentChildFields) {
+      contentChildFields.add(new SummarizedContentChildFieldBuilder()
+        ..fieldName = childField.fieldName
+        ..nameOffset = childField.nameRange.offset
+        ..nameLength = childField.nameRange.length
+        ..typeOffset = childField.typeRange.offset
+        ..typeLength = childField.typeRange.length);
+    }
+    for (final childrenField in clazz.contentChildrenFields) {
+      contentChildrenFields.add(new SummarizedContentChildFieldBuilder()
+        ..fieldName = childrenField.fieldName
+        ..nameOffset = childrenField.nameRange.offset
+        ..nameLength = childrenField.nameRange.length
+        ..typeOffset = childrenField.typeRange.offset
+        ..typeLength = childrenField.typeRange.length);
+    }
+    return new SummarizedClassAnnotationsBuilder()
+      ..className = className
+      ..inputs = inputs
+      ..outputs = outputs
+      ..contentChildFields = contentChildFields
+      ..contentChildrenFields = contentChildrenFields;
+  }
+
   List<SummarizedNgContentBuilder> serializeNgContents(
           List<NgContent> ngContents) =>
       ngContents
@@ -892,7 +903,9 @@
 }
 
 class DirectivesResult {
-  List<AbstractDirective> directives;
+  List<AbstractDirective> get directives =>
+      angularAnnotatedClasses.where((c) => c is AbstractDirective).toList();
+  List<AngularAnnotatedClass> angularAnnotatedClasses;
   List<AnalysisError> errors;
-  DirectivesResult(this.directives, this.errors);
+  DirectivesResult(this.angularAnnotatedClasses, this.errors);
 }
diff --git a/angular_analyzer_plugin/lib/src/directive_extraction.dart b/angular_analyzer_plugin/lib/src/directive_extraction.dart
index 461793d..cf4154e 100644
--- a/angular_analyzer_plugin/lib/src/directive_extraction.dart
+++ b/angular_analyzer_plugin/lib/src/directive_extraction.dart
@@ -32,15 +32,13 @@
     initAnnotationProcessor(_source);
   }
 
-  List<AbstractDirective> getDirectives() {
-    final directives = <AbstractDirective>[];
+  List<AngularAnnotatedClass> getAngularAnnotatedClasses() {
+    final directives = <AngularAnnotatedClass>[];
     for (final unitMember in _unit.declarations) {
       if (unitMember is ast.ClassDeclaration) {
-        for (final annotationNode in unitMember.metadata) {
-          final directive = _createDirective(unitMember, annotationNode);
-          if (directive != null) {
-            directives.add(directive);
-          }
+        final directive = _getAngularAnnotatedClass(unitMember);
+        if (directive != null) {
+          directives.add(directive);
         }
       }
     }
@@ -50,33 +48,38 @@
 
   /// Returns an Angular [AbstractDirective] for to the given [node].
   /// Returns `null` if not an Angular annotation.
-  AbstractDirective _createDirective(
-      ast.ClassDeclaration classDeclaration, ast.Annotation node) {
+  AngularAnnotatedClass _getAngularAnnotatedClass(
+      ast.ClassDeclaration classDeclaration) {
     _currentClassElement = classDeclaration.element;
     _bindingTypeSynthesizer = new BindingTypeSynthesizer(
         _currentClassElement, _typeProvider, _context, errorReporter);
     // TODO(scheglov) add support for all the arguments
-    final isComponent = isAngularAnnotation(node, 'Component');
-    final isDirective = isAngularAnnotation(node, 'Directive');
-    if (isComponent || isDirective) {
+    final componentNode = classDeclaration.metadata.firstWhere(
+        (ann) => isAngularAnnotation(ann, 'Component'),
+        orElse: () => null);
+    final directiveNode = classDeclaration.metadata.firstWhere(
+        (ann) => isAngularAnnotation(ann, 'Directive'),
+        orElse: () => null);
+    final annotationNode = componentNode ?? directiveNode;
+
+    final inputElements = <InputElement>[];
+    final outputElements = <OutputElement>[];
+    final contentChilds = <ContentChildField>[];
+    final contentChildrens = <ContentChildField>[];
+    _parseContentChilds(classDeclaration, contentChilds, contentChildrens);
+
+    if (annotationNode != null) {
       // Don't fail to create a Component just because of a broken or missing
       // selector, that results in cascading errors.
-      final selector = _parseSelector(node) ?? new AndSelector([]);
-      final exportAs = _parseExportAs(node);
-      final inputElements = <InputElement>[];
-      final outputElements = <OutputElement>[];
-      {
-        inputElements.addAll(_parseHeaderInputs(node));
-        outputElements.addAll(_parseHeaderOutputs(node));
-        _parseMemberInputsAndOutputs(
-            classDeclaration, inputElements, outputElements);
-      }
-      final contentChilds = <ContentChildField>[];
-      final contentChildrens = <ContentChildField>[];
-      _parseContentChilds(classDeclaration, contentChilds, contentChildrens);
+      final selector = _parseSelector(annotationNode) ?? new AndSelector([]);
+      final exportAs = _parseExportAs(annotationNode);
       final elementTags = <ElementNameSelector>[];
+      inputElements.addAll(_parseHeaderInputs(annotationNode));
+      outputElements.addAll(_parseHeaderOutputs(annotationNode));
+      _parseMemberInputsAndOutputs(
+          classDeclaration, inputElements, outputElements);
       selector.recordElementNameSelectors(elementTags);
-      if (isComponent) {
+      if (componentNode != null) {
         return new Component(_currentClassElement,
             exportAs: exportAs,
             inputs: inputElements,
@@ -87,7 +90,7 @@
             contentChildFields: contentChilds,
             contentChildrenFields: contentChildrens);
       }
-      if (isDirective) {
+      if (directiveNode != null) {
         return new Directive(_currentClassElement,
             exportAs: exportAs,
             inputs: inputElements,
@@ -98,6 +101,17 @@
             contentChildrenFields: contentChildrens);
       }
     }
+
+    _parseMemberInputsAndOutputs(
+        classDeclaration, inputElements, outputElements);
+    if (inputElements.isNotEmpty ||
+        outputElements.isNotEmpty ||
+        contentChilds.isNotEmpty ||
+        contentChildrens.isNotEmpty) {
+      return new AngularAnnotatedClass(_currentClassElement, inputElements,
+          outputElements, contentChilds, contentChildrens);
+    }
+
     return null;
   }
 
diff --git a/angular_analyzer_plugin/lib/src/directive_linking.dart b/angular_analyzer_plugin/lib/src/directive_linking.dart
index 63149f9..55c91b7 100644
--- a/angular_analyzer_plugin/lib/src/directive_linking.dart
+++ b/angular_analyzer_plugin/lib/src/directive_linking.dart
@@ -19,7 +19,7 @@
 import 'summary/idl.dart';
 
 abstract class FileDirectiveProvider {
-  Future<List<AbstractDirective>> getUnlinkedDirectives(String path);
+  Future<List<AngularAnnotatedClass>> getUnlinkedClasses(String path);
   Future<List<NgContent>> getHtmlNgContent(String path);
 }
 
@@ -38,7 +38,7 @@
 
   DirectiveLinker(this._directiveLinkerEnablement);
 
-  Future<List<AbstractDirective>> resynthesizeDirectives(
+  Future<List<AngularAnnotatedClass>> resynthesizeDirectives(
       UnlinkedDartSummary unlinked, String path) async {
     if (unlinked == null) {
       return [];
@@ -48,10 +48,10 @@
 
     final source = unit.source;
 
-    final directives = <AbstractDirective>[];
+    final annotatedClasses = <AngularAnnotatedClass>[];
 
     for (final dirSum in unlinked.directiveSummaries) {
-      final classElem = unit.getType(dirSum.decoratedClassName);
+      final classElem = unit.getType(dirSum.classAnnotations.className);
       final bindingSynthesizer = new BindingTypeSynthesizer(
           classElem,
           unit.context.typeProvider,
@@ -66,46 +66,14 @@
               .parse();
       final elementTags = <ElementNameSelector>[];
       selector.recordElementNameSelectors(elementTags);
-      final inputs = <InputElement>[];
-      for (final inputSum in dirSum.inputs) {
-        // is this correct lookup?
-        final setter =
-            classElem.lookUpSetter(inputSum.propName, classElem.library);
-        if (setter == null) {
-          continue;
-        }
-        inputs.add(new InputElement(
-            inputSum.name,
-            inputSum.nameOffset,
-            inputSum.name.length,
-            source,
-            setter,
-            new SourceRange(inputSum.propNameOffset, inputSum.propName.length),
-            bindingSynthesizer
-                .getSetterType(setter))); // Don't think type is correct
-      }
-      final outputs = <OutputElement>[];
-      for (final outputSum in dirSum.outputs) {
-        // is this correct lookup?
-        final getter =
-            classElem.lookUpGetter(outputSum.propName, classElem.library);
-        if (getter == null) {
-          continue;
-        }
-        outputs.add(new OutputElement(
-            outputSum.name,
-            outputSum.nameOffset,
-            outputSum.name.length,
-            source,
-            getter,
-            new SourceRange(
-                outputSum.propNameOffset, outputSum.propName.length),
-            bindingSynthesizer.getEventType(getter, getter.name)));
-      }
-      final contentChildFields =
-          deserializeContentChildFields(dirSum.contentChildFields);
-      final contentChildrenFields =
-          deserializeContentChildFields(dirSum.contentChildrenFields);
+      final inputs = deserializeInputs(
+          dirSum.classAnnotations, classElem, source, bindingSynthesizer);
+      final outputs = deserializeOutputs(
+          dirSum.classAnnotations, classElem, source, bindingSynthesizer);
+      final contentChildFields = deserializeContentChildFields(
+          dirSum.classAnnotations.contentChildFields);
+      final contentChildrenFields = deserializeContentChildFields(
+          dirSum.classAnnotations.contentChildrenFields);
       if (dirSum.isComponent) {
         final ngContents = deserializeNgContents(dirSum.ngContents, source);
         final exports = deserializeExports(dirSum.exports);
@@ -119,7 +87,7 @@
             elementTags: elementTags,
             contentChildFields: contentChildFields,
             contentChildrenFields: contentChildrenFields);
-        directives.add(component);
+        annotatedClasses.add(component);
         final subDirectives = <DirectiveReference>[];
         for (final useSum in dirSum.subdirectives) {
           subDirectives.add(new DirectiveReference(useSum.name, useSum.prefix,
@@ -141,19 +109,90 @@
             directiveReferences: subDirectives,
             exports: exports);
       } else {
-        final directive = new Directive(classElem,
+        annotatedClasses.add(new Directive(classElem,
             exportAs: exportAs,
             selector: selector,
             inputs: inputs,
             outputs: outputs,
             elementTags: elementTags,
             contentChildFields: contentChildFields,
-            contentChildrenFields: contentChildrenFields);
-        directives.add(directive);
+            contentChildrenFields: contentChildrenFields));
       }
     }
 
-    return directives;
+    for (final annotations in unlinked.annotatedClasses) {
+      final classElem = unit.getType(annotations.className);
+      final bindingSynthesizer = new BindingTypeSynthesizer(
+          classElem,
+          unit.context.typeProvider,
+          unit.context,
+          new ErrorReporter(new IgnoringErrorListener(), unit.source));
+      final inputs =
+          deserializeInputs(annotations, classElem, source, bindingSynthesizer);
+      final outputs = deserializeOutputs(
+          annotations, classElem, source, bindingSynthesizer);
+      final contentChildFields =
+          deserializeContentChildFields(annotations.contentChildFields);
+      final contentChildrenFields =
+          deserializeContentChildFields(annotations.contentChildrenFields);
+      annotatedClasses.add(new AngularAnnotatedClass(classElem, inputs, outputs,
+          contentChildFields, contentChildrenFields));
+    }
+
+    return annotatedClasses;
+  }
+
+  List<InputElement> deserializeInputs(
+      SummarizedClassAnnotations annotations,
+      ClassElement classElem,
+      Source source,
+      BindingTypeSynthesizer bindingSynthesizer) {
+    final inputs = <InputElement>[];
+    for (final inputSum in annotations.inputs) {
+      // is this correct lookup?
+      final setter =
+          classElem.lookUpSetter(inputSum.propName, classElem.library);
+      if (setter == null) {
+        continue;
+      }
+      inputs.add(new InputElement(
+          inputSum.name,
+          inputSum.nameOffset,
+          inputSum.name.length,
+          source,
+          setter,
+          new SourceRange(inputSum.propNameOffset, inputSum.propName.length),
+          bindingSynthesizer
+              .getSetterType(setter))); // Don't think type is correct
+    }
+
+    return inputs;
+  }
+
+  List<OutputElement> deserializeOutputs(
+      SummarizedClassAnnotations annotations,
+      ClassElement classElem,
+      Source source,
+      BindingTypeSynthesizer bindingSynthesizer) {
+    final outputs = <OutputElement>[];
+    for (final outputSum in annotations.outputs) {
+      // is this correct lookup?
+      final getter =
+          classElem.lookUpGetter(outputSum.propName, classElem.library);
+      if (getter == null) {
+        continue;
+      }
+      outputs.add(new OutputElement(
+          outputSum.name,
+          outputSum.nameOffset,
+          outputSum.name.length,
+          source,
+          getter,
+          new SourceRange(outputSum.propNameOffset, outputSum.propName.length),
+          bindingSynthesizer.getEventType(getter, getter.name)));
+    }
+
+    return outputs;
   }
 
   List<NgContent> deserializeNgContents(
@@ -264,6 +303,32 @@
           export.identifier, export.span.offset + offset));
 }
 
+class InheritedMetadataLinker {
+  AbstractDirective directive;
+  FileDirectiveProvider _fileDirectiveProvider;
+
+  InheritedMetadataLinker(this.directive, this._fileDirectiveProvider);
+
+  Future link() async {
+    for (final supertype in directive.classElement.allSupertypes) {
+      final result = await _fileDirectiveProvider
+          .getUnlinkedClasses(supertype.element.source.fullName);
+      final match = result.firstWhere(
+          (c) => c.classElement == supertype.element,
+          orElse: () => null);
+
+      if (match == null) {
+        continue;
+      }
+
+      directive.inputs.addAll(match.inputs);
+      directive.outputs.addAll(match.outputs);
+      directive.contentChildFields.addAll(match.contentChildFields);
+      directive.contentChildrenFields.addAll(match.contentChildrenFields);
+    }
+  }
+}
+
 class ChildDirectiveLinker implements DirectiveMatcher {
   final FileDirectiveProvider _fileDirectiveProvider;
   final ErrorReporter _errorReporter;
@@ -283,8 +348,7 @@
         for (final reference in directive.view.directiveReferences) {
           final referent = lookupByName(reference, directivesToLink);
           if (referent != null) {
-            directive.view.directives
-                .add(await withNgContentAndChildren(referent));
+            directive.view.directives.add(await linkedAsChild(referent));
           } else {
             await lookupFromLibrary(
                 reference, scope, directive.view.directives);
@@ -293,6 +357,8 @@
       }
 
       exportLinker.linkExportsFor(directive);
+      await new InheritedMetadataLinker(directive, _fileDirectiveProvider)
+          .link();
 
       await new ContentChildLinker(
               directive, this, _standardAngular, _errorReporter)
@@ -325,7 +391,7 @@
         final directive = await matchDirective(type);
 
         if (directive != null) {
-          directives.add(await withNgContentAndChildren(directive));
+          directives.add(await linkedAsChild(directive));
         } else {
           _errorReporter.reportErrorForOffset(
               AngularWarningCode.TYPE_IS_NOT_A_DIRECTIVE,
@@ -361,10 +427,11 @@
 
   @override
   Future<AbstractDirective> matchDirective(ClassElement clazz) async {
-    final fileDirectives = await _fileDirectiveProvider
-        .getUnlinkedDirectives(clazz.source.fullName);
-    final options =
-        fileDirectives.where((d) => d.classElement.name == clazz.name);
+    final fileDirectives =
+        await _fileDirectiveProvider.getUnlinkedClasses(clazz.source.fullName);
+    final options = fileDirectives
+        .where((d) => d.classElement.name == clazz.name)
+        .where((d) => d is AbstractDirective);
 
     if (options.length == 1) {
       return options.first;
@@ -385,7 +452,7 @@
       if (typeValue is InterfaceType && typeValue.element is ClassElement) {
         final directive = await matchDirective(typeValue.element);
         if (directive != null) {
-          directives.add(await withNgContentAndChildren(directive));
+          directives.add(await linkedAsChild(directive));
         } else {
           _errorReporter.reportErrorForOffset(
               AngularWarningCode.TYPE_IS_NOT_A_DIRECTIVE,
@@ -409,14 +476,17 @@
     }
   }
 
-  Future<AbstractDirective> withNgContentAndChildren(
-      AbstractDirective directive) async {
+  Future<AbstractDirective> linkedAsChild(AbstractDirective directive) async {
     if (directive is Component && directive?.view?.templateUriSource != null) {
       final source = directive.view.templateUriSource;
       directive.ngContents.addAll(
           await _fileDirectiveProvider.getHtmlNgContent(source.fullName));
     }
 
+    // Important: Link inherited metadata before content child fields, as
+    // the directive may import unlinked content childs
+    await new InheritedMetadataLinker(directive, _fileDirectiveProvider).link();
+
     // NOTE: Require the Exact type TemplateRef because that's what the
     // injector does.
     directive.looksLikeTemplate = directive.classElement.constructors.any(
diff --git a/angular_analyzer_plugin/lib/src/model.dart b/angular_analyzer_plugin/lib/src/model.dart
index d7fed13..724c3c4 100644
--- a/angular_analyzer_plugin/lib/src/model.dart
+++ b/angular_analyzer_plugin/lib/src/model.dart
@@ -13,19 +13,13 @@
 import 'package:angular_analyzer_plugin/ast.dart';
 import 'package:angular_analyzer_plugin/errors.dart';
 
-/// An abstract model of an Angular directive.
-abstract class AbstractDirective {
+/// Might be a directive, or a component, or neither. It might simply have
+/// annotated @Inputs, @Outputs() intended to be inherited.
+class AngularAnnotatedClass {
   /// The [ClassElement] this annotation is associated with.
   final dart.ClassElement classElement;
-
-  final AngularElement exportAs;
   final List<InputElement> inputs;
   final List<OutputElement> outputs;
-  final Selector selector;
-  final List<ElementNameSelector> elementTags;
-  final attributes = <AngularElement>[];
-
-  bool get isHtml;
 
   /// Which fields have been marked `@ContentChild`, and the range of the type
   /// argument. The element model contains the rest. This should be stored in the
@@ -33,6 +27,22 @@
   /// against the range we saw it the AST.
   List<ContentChildField> contentChildrenFields;
   List<ContentChildField> contentChildFields;
+
+  AngularAnnotatedClass(this.classElement, this.inputs, this.outputs,
+      this.contentChildFields, this.contentChildrenFields);
+}
+
+/// An abstract model of an Angular directive.
+abstract class AbstractDirective extends AngularAnnotatedClass {
+  final AngularElement exportAs;
+  final Selector selector;
+  final List<ElementNameSelector> elementTags;
+  final attributes = <AngularElement>[];
+
+  bool get isHtml;
+
+  // See [AngularAnnotatedClassMembers.contentChildrenFields]. These are the
+  // linked versions.
   final contentChilds = <ContentChild>[];
   final contentChildren = <ContentChild>[];
 
@@ -42,14 +52,16 @@
   /// whatever validation we can, and autocomplete suggestions.
   bool looksLikeTemplate = false;
 
-  AbstractDirective(this.classElement,
+  AbstractDirective(dart.ClassElement classElement,
       {this.exportAs,
-      this.inputs,
-      this.outputs,
+      List<InputElement> inputs,
+      List<OutputElement> outputs,
       this.selector,
       this.elementTags,
-      this.contentChildFields,
-      this.contentChildrenFields});
+      List<ContentChildField> contentChildFields,
+      List<ContentChildField> contentChildrenFields})
+      : super(classElement, inputs, outputs, contentChildFields,
+            contentChildrenFields);
 
   /// The source that contains this directive.
   Source get source => classElement.source;
diff --git a/angular_analyzer_plugin/lib/src/noop_driver.dart b/angular_analyzer_plugin/lib/src/noop_driver.dart
new file mode 100644
index 0000000..469dbc9
--- /dev/null
+++ b/angular_analyzer_plugin/lib/src/noop_driver.dart
@@ -0,0 +1,22 @@
+import 'dart:async';
+import 'package:analyzer/src/dart/analysis/driver.dart';
+
+class NoopDriver implements AnalysisDriverGeneric {
+  @override
+  void addFile(String path) => null;
+
+  @override
+  void dispose() => null;
+
+  @override
+  Future<Null> performWork() async => null;
+
+  @override
+  bool get hasFilesToAnalyze => false;
+
+  @override
+  AnalysisDriverPriority get workPriority => AnalysisDriverPriority.nothing;
+
+  @override
+  set priorityFiles(Object o) => null; // ignore: avoid_setters_without_getters
+}
diff --git a/angular_analyzer_plugin/lib/src/resolver.dart b/angular_analyzer_plugin/lib/src/resolver.dart
index e3f089b..10540ef 100644
--- a/angular_analyzer_plugin/lib/src/resolver.dart
+++ b/angular_analyzer_plugin/lib/src/resolver.dart
@@ -1332,7 +1332,15 @@
           // `setAttribute("width", "10")`, which is ok. But for directives and
           // components, this becomes `.someIntProp = "10"` which doesn't work.
           final inputType = input.setterType;
+
+          // Some attr `foo` by itself, no brackets, as such, and no value, will
+          // be bound "true" when its a boolean, which requires no typecheck.
+          final booleanException =
+              input.setterType.isSubtypeOf(typeProvider.boolType) &&
+                  attribute.value == null;
+
           if (!directiveBinding.boundDirective.isHtml &&
+              !booleanException &&
               !typeProvider.stringType.isAssignableTo(inputType)) {
             errorListener.onError(new AnalysisError(
                 templateSource,
diff --git a/angular_analyzer_plugin/lib/src/summary/format.dart b/angular_analyzer_plugin/lib/src/summary/format.dart
index 4821935..a4f7adb 100644
--- a/angular_analyzer_plugin/lib/src/summary/format.dart
+++ b/angular_analyzer_plugin/lib/src/summary/format.dart
@@ -5,6 +5,8 @@
 // This file has been automatically generated.  Please do not edit it manually.
 // To regenerate the file, use the script "pkg/analyzer/tool/generate_files".
 
+library analyzer.src.summary.format;
+
 import 'dart:convert' as convert;
 
 import 'package:front_end/src/base/api_signature.dart' as api_sig;
@@ -589,6 +591,7 @@
     with _UnlinkedDartSummaryMixin
     implements idl.UnlinkedDartSummary {
   List<SummarizedDirectiveBuilder> _directiveSummaries;
+  List<SummarizedClassAnnotationsBuilder> _annotatedClasses;
   List<SummarizedAnalysisErrorBuilder> _errors;
 
   @override
@@ -600,6 +603,14 @@
   }
 
   @override
+  List<SummarizedClassAnnotationsBuilder> get annotatedClasses =>
+      _annotatedClasses ??= <SummarizedClassAnnotationsBuilder>[];
+
+  void set annotatedClasses(List<SummarizedClassAnnotationsBuilder> value) {
+    this._annotatedClasses = value;
+  }
+
+  @override
   List<SummarizedAnalysisErrorBuilder> get errors =>
       _errors ??= <SummarizedAnalysisErrorBuilder>[];
 
@@ -609,8 +620,10 @@
 
   UnlinkedDartSummaryBuilder(
       {List<SummarizedDirectiveBuilder> directiveSummaries,
+      List<SummarizedClassAnnotationsBuilder> annotatedClasses,
       List<SummarizedAnalysisErrorBuilder> errors})
       : _directiveSummaries = directiveSummaries,
+        _annotatedClasses = annotatedClasses,
         _errors = errors;
 
   /**
@@ -618,6 +631,7 @@
    */
   void flushInformative() {
     _directiveSummaries?.forEach((b) => b.flushInformative());
+    _annotatedClasses?.forEach((b) => b.flushInformative());
     _errors?.forEach((b) => b.flushInformative());
   }
 
@@ -633,6 +647,14 @@
         x?.collectApiSignature(signature);
       }
     }
+    if (this._annotatedClasses == null) {
+      signature.addInt(0);
+    } else {
+      signature.addInt(this._annotatedClasses.length);
+      for (var x in this._annotatedClasses) {
+        x?.collectApiSignature(signature);
+      }
+    }
     if (this._errors == null) {
       signature.addInt(0);
     } else {
@@ -650,11 +672,16 @@
 
   fb.Offset finish(fb.Builder fbBuilder) {
     fb.Offset offset_directiveSummaries;
+    fb.Offset offset_annotatedClasses;
     fb.Offset offset_errors;
     if (!(_directiveSummaries == null || _directiveSummaries.isEmpty)) {
       offset_directiveSummaries = fbBuilder.writeList(
           _directiveSummaries.map((b) => b.finish(fbBuilder)).toList());
     }
+    if (!(_annotatedClasses == null || _annotatedClasses.isEmpty)) {
+      offset_annotatedClasses = fbBuilder.writeList(
+          _annotatedClasses.map((b) => b.finish(fbBuilder)).toList());
+    }
     if (!(_errors == null || _errors.isEmpty)) {
       offset_errors =
           fbBuilder.writeList(_errors.map((b) => b.finish(fbBuilder)).toList());
@@ -663,8 +690,11 @@
     if (offset_directiveSummaries != null) {
       fbBuilder.addOffset(0, offset_directiveSummaries);
     }
+    if (offset_annotatedClasses != null) {
+      fbBuilder.addOffset(1, offset_annotatedClasses);
+    }
     if (offset_errors != null) {
-      fbBuilder.addOffset(1, offset_errors);
+      fbBuilder.addOffset(2, offset_errors);
     }
     return fbBuilder.endTable();
   }
@@ -693,6 +723,7 @@
   _UnlinkedDartSummaryImpl(this._bc, this._bcOffset);
 
   List<idl.SummarizedDirective> _directiveSummaries;
+  List<idl.SummarizedClassAnnotations> _annotatedClasses;
   List<idl.SummarizedAnalysisError> _errors;
 
   @override
@@ -704,10 +735,18 @@
   }
 
   @override
+  List<idl.SummarizedClassAnnotations> get annotatedClasses {
+    _annotatedClasses ??= const fb.ListReader<idl.SummarizedClassAnnotations>(
+            const _SummarizedClassAnnotationsReader())
+        .vTableGet(_bc, _bcOffset, 1, const <idl.SummarizedClassAnnotations>[]);
+    return _annotatedClasses;
+  }
+
+  @override
   List<idl.SummarizedAnalysisError> get errors {
     _errors ??= const fb.ListReader<idl.SummarizedAnalysisError>(
             const _SummarizedAnalysisErrorReader())
-        .vTableGet(_bc, _bcOffset, 1, const <idl.SummarizedAnalysisError>[]);
+        .vTableGet(_bc, _bcOffset, 2, const <idl.SummarizedAnalysisError>[]);
     return _errors;
   }
 }
@@ -719,6 +758,9 @@
     if (directiveSummaries.isNotEmpty)
       _result["directiveSummaries"] =
           directiveSummaries.map((_value) => _value.toJson()).toList();
+    if (annotatedClasses.isNotEmpty)
+      _result["annotatedClasses"] =
+          annotatedClasses.map((_value) => _value.toJson()).toList();
     if (errors.isNotEmpty)
       _result["errors"] = errors.map((_value) => _value.toJson()).toList();
     return _result;
@@ -727,6 +769,7 @@
   @override
   Map<String, Object> toMap() => {
         "directiveSummaries": directiveSummaries,
+        "annotatedClasses": annotatedClasses,
         "errors": errors,
       };
 
@@ -734,13 +777,267 @@
   String toString() => convert.JSON.encode(toJson());
 }
 
+class SummarizedClassAnnotationsBuilder extends Object
+    with _SummarizedClassAnnotationsMixin
+    implements idl.SummarizedClassAnnotations {
+  String _className;
+  List<SummarizedBindableBuilder> _inputs;
+  List<SummarizedBindableBuilder> _outputs;
+  List<SummarizedContentChildFieldBuilder> _contentChildFields;
+  List<SummarizedContentChildFieldBuilder> _contentChildrenFields;
+
+  @override
+  String get className => _className ??= '';
+
+  void set className(String value) {
+    this._className = value;
+  }
+
+  @override
+  List<SummarizedBindableBuilder> get inputs =>
+      _inputs ??= <SummarizedBindableBuilder>[];
+
+  void set inputs(List<SummarizedBindableBuilder> value) {
+    this._inputs = value;
+  }
+
+  @override
+  List<SummarizedBindableBuilder> get outputs =>
+      _outputs ??= <SummarizedBindableBuilder>[];
+
+  void set outputs(List<SummarizedBindableBuilder> value) {
+    this._outputs = value;
+  }
+
+  @override
+  List<SummarizedContentChildFieldBuilder> get contentChildFields =>
+      _contentChildFields ??= <SummarizedContentChildFieldBuilder>[];
+
+  void set contentChildFields(List<SummarizedContentChildFieldBuilder> value) {
+    this._contentChildFields = value;
+  }
+
+  @override
+  List<SummarizedContentChildFieldBuilder> get contentChildrenFields =>
+      _contentChildrenFields ??= <SummarizedContentChildFieldBuilder>[];
+
+  void set contentChildrenFields(
+      List<SummarizedContentChildFieldBuilder> value) {
+    this._contentChildrenFields = value;
+  }
+
+  SummarizedClassAnnotationsBuilder(
+      {String className,
+      List<SummarizedBindableBuilder> inputs,
+      List<SummarizedBindableBuilder> outputs,
+      List<SummarizedContentChildFieldBuilder> contentChildFields,
+      List<SummarizedContentChildFieldBuilder> contentChildrenFields})
+      : _className = className,
+        _inputs = inputs,
+        _outputs = outputs,
+        _contentChildFields = contentChildFields,
+        _contentChildrenFields = contentChildrenFields;
+
+  /**
+   * Flush [informative] data recursively.
+   */
+  void flushInformative() {
+    _inputs?.forEach((b) => b.flushInformative());
+    _outputs?.forEach((b) => b.flushInformative());
+    _contentChildFields?.forEach((b) => b.flushInformative());
+    _contentChildrenFields?.forEach((b) => b.flushInformative());
+  }
+
+  /**
+   * Accumulate non-[informative] data into [signature].
+   */
+  void collectApiSignature(api_sig.ApiSignature signature) {
+    signature.addString(this._className ?? '');
+    if (this._inputs == null) {
+      signature.addInt(0);
+    } else {
+      signature.addInt(this._inputs.length);
+      for (var x in this._inputs) {
+        x?.collectApiSignature(signature);
+      }
+    }
+    if (this._outputs == null) {
+      signature.addInt(0);
+    } else {
+      signature.addInt(this._outputs.length);
+      for (var x in this._outputs) {
+        x?.collectApiSignature(signature);
+      }
+    }
+    if (this._contentChildFields == null) {
+      signature.addInt(0);
+    } else {
+      signature.addInt(this._contentChildFields.length);
+      for (var x in this._contentChildFields) {
+        x?.collectApiSignature(signature);
+      }
+    }
+    if (this._contentChildrenFields == null) {
+      signature.addInt(0);
+    } else {
+      signature.addInt(this._contentChildrenFields.length);
+      for (var x in this._contentChildrenFields) {
+        x?.collectApiSignature(signature);
+      }
+    }
+  }
+
+  fb.Offset finish(fb.Builder fbBuilder) {
+    fb.Offset offset_className;
+    fb.Offset offset_inputs;
+    fb.Offset offset_outputs;
+    fb.Offset offset_contentChildFields;
+    fb.Offset offset_contentChildrenFields;
+    if (_className != null) {
+      offset_className = fbBuilder.writeString(_className);
+    }
+    if (!(_inputs == null || _inputs.isEmpty)) {
+      offset_inputs =
+          fbBuilder.writeList(_inputs.map((b) => b.finish(fbBuilder)).toList());
+    }
+    if (!(_outputs == null || _outputs.isEmpty)) {
+      offset_outputs = fbBuilder
+          .writeList(_outputs.map((b) => b.finish(fbBuilder)).toList());
+    }
+    if (!(_contentChildFields == null || _contentChildFields.isEmpty)) {
+      offset_contentChildFields = fbBuilder.writeList(
+          _contentChildFields.map((b) => b.finish(fbBuilder)).toList());
+    }
+    if (!(_contentChildrenFields == null || _contentChildrenFields.isEmpty)) {
+      offset_contentChildrenFields = fbBuilder.writeList(
+          _contentChildrenFields.map((b) => b.finish(fbBuilder)).toList());
+    }
+    fbBuilder.startTable();
+    if (offset_className != null) {
+      fbBuilder.addOffset(0, offset_className);
+    }
+    if (offset_inputs != null) {
+      fbBuilder.addOffset(1, offset_inputs);
+    }
+    if (offset_outputs != null) {
+      fbBuilder.addOffset(2, offset_outputs);
+    }
+    if (offset_contentChildFields != null) {
+      fbBuilder.addOffset(3, offset_contentChildFields);
+    }
+    if (offset_contentChildrenFields != null) {
+      fbBuilder.addOffset(4, offset_contentChildrenFields);
+    }
+    return fbBuilder.endTable();
+  }
+}
+
+class _SummarizedClassAnnotationsReader
+    extends fb.TableReader<_SummarizedClassAnnotationsImpl> {
+  const _SummarizedClassAnnotationsReader();
+
+  @override
+  _SummarizedClassAnnotationsImpl createObject(
+          fb.BufferContext bc, int offset) =>
+      new _SummarizedClassAnnotationsImpl(bc, offset);
+}
+
+class _SummarizedClassAnnotationsImpl extends Object
+    with _SummarizedClassAnnotationsMixin
+    implements idl.SummarizedClassAnnotations {
+  final fb.BufferContext _bc;
+  final int _bcOffset;
+
+  _SummarizedClassAnnotationsImpl(this._bc, this._bcOffset);
+
+  String _className;
+  List<idl.SummarizedBindable> _inputs;
+  List<idl.SummarizedBindable> _outputs;
+  List<idl.SummarizedContentChildField> _contentChildFields;
+  List<idl.SummarizedContentChildField> _contentChildrenFields;
+
+  @override
+  String get className {
+    _className ??= const fb.StringReader().vTableGet(_bc, _bcOffset, 0, '');
+    return _className;
+  }
+
+  @override
+  List<idl.SummarizedBindable> get inputs {
+    _inputs ??= const fb.ListReader<idl.SummarizedBindable>(
+            const _SummarizedBindableReader())
+        .vTableGet(_bc, _bcOffset, 1, const <idl.SummarizedBindable>[]);
+    return _inputs;
+  }
+
+  @override
+  List<idl.SummarizedBindable> get outputs {
+    _outputs ??= const fb.ListReader<idl.SummarizedBindable>(
+            const _SummarizedBindableReader())
+        .vTableGet(_bc, _bcOffset, 2, const <idl.SummarizedBindable>[]);
+    return _outputs;
+  }
+
+  @override
+  List<idl.SummarizedContentChildField> get contentChildFields {
+    _contentChildFields ??=
+        const fb.ListReader<idl.SummarizedContentChildField>(
+                const _SummarizedContentChildFieldReader())
+            .vTableGet(
+                _bc, _bcOffset, 3, const <idl.SummarizedContentChildField>[]);
+    return _contentChildFields;
+  }
+
+  @override
+  List<idl.SummarizedContentChildField> get contentChildrenFields {
+    _contentChildrenFields ??=
+        const fb.ListReader<idl.SummarizedContentChildField>(
+                const _SummarizedContentChildFieldReader())
+            .vTableGet(
+                _bc, _bcOffset, 4, const <idl.SummarizedContentChildField>[]);
+    return _contentChildrenFields;
+  }
+}
+
+abstract class _SummarizedClassAnnotationsMixin
+    implements idl.SummarizedClassAnnotations {
+  @override
+  Map<String, Object> toJson() {
+    Map<String, Object> _result = <String, Object>{};
+    if (className != '') _result["className"] = className;
+    if (inputs.isNotEmpty)
+      _result["inputs"] = inputs.map((_value) => _value.toJson()).toList();
+    if (outputs.isNotEmpty)
+      _result["outputs"] = outputs.map((_value) => _value.toJson()).toList();
+    if (contentChildFields.isNotEmpty)
+      _result["contentChildFields"] =
+          contentChildFields.map((_value) => _value.toJson()).toList();
+    if (contentChildrenFields.isNotEmpty)
+      _result["contentChildrenFields"] =
+          contentChildrenFields.map((_value) => _value.toJson()).toList();
+    return _result;
+  }
+
+  @override
+  Map<String, Object> toMap() => {
+        "className": className,
+        "inputs": inputs,
+        "outputs": outputs,
+        "contentChildFields": contentChildFields,
+        "contentChildrenFields": contentChildrenFields,
+      };
+
+  @override
+  String toString() => convert.JSON.encode(toJson());
+}
+
 class SummarizedDirectiveBuilder extends Object
     with _SummarizedDirectiveMixin
     implements idl.SummarizedDirective {
+  SummarizedClassAnnotationsBuilder _classAnnotations;
   bool _isComponent;
   String _selectorStr;
   int _selectorOffset;
-  String _decoratedClassName;
   String _exportAs;
   int _exportAsOffset;
   String _templateUrl;
@@ -749,14 +1046,17 @@
   String _templateText;
   int _templateOffset;
   List<SummarizedNgContentBuilder> _ngContents;
-  List<SummarizedBindableBuilder> _inputs;
-  List<SummarizedBindableBuilder> _outputs;
   List<SummarizedDirectiveUseBuilder> _subdirectives;
-  List<SummarizedContentChildFieldBuilder> _contentChildFields;
-  List<SummarizedContentChildFieldBuilder> _contentChildrenFields;
   List<SummarizedExportedIdentifierBuilder> _exports;
 
   @override
+  SummarizedClassAnnotationsBuilder get classAnnotations => _classAnnotations;
+
+  void set classAnnotations(SummarizedClassAnnotationsBuilder value) {
+    this._classAnnotations = value;
+  }
+
+  @override
   bool get isComponent => _isComponent ??= false;
 
   void set isComponent(bool value) {
@@ -779,13 +1079,6 @@
   }
 
   @override
-  String get decoratedClassName => _decoratedClassName ??= '';
-
-  void set decoratedClassName(String value) {
-    this._decoratedClassName = value;
-  }
-
-  @override
   String get exportAs => _exportAs ??= '';
 
   void set exportAs(String value) {
@@ -847,22 +1140,6 @@
   }
 
   @override
-  List<SummarizedBindableBuilder> get inputs =>
-      _inputs ??= <SummarizedBindableBuilder>[];
-
-  void set inputs(List<SummarizedBindableBuilder> value) {
-    this._inputs = value;
-  }
-
-  @override
-  List<SummarizedBindableBuilder> get outputs =>
-      _outputs ??= <SummarizedBindableBuilder>[];
-
-  void set outputs(List<SummarizedBindableBuilder> value) {
-    this._outputs = value;
-  }
-
-  @override
   List<SummarizedDirectiveUseBuilder> get subdirectives =>
       _subdirectives ??= <SummarizedDirectiveUseBuilder>[];
 
@@ -871,23 +1148,6 @@
   }
 
   @override
-  List<SummarizedContentChildFieldBuilder> get contentChildFields =>
-      _contentChildFields ??= <SummarizedContentChildFieldBuilder>[];
-
-  void set contentChildFields(List<SummarizedContentChildFieldBuilder> value) {
-    this._contentChildFields = value;
-  }
-
-  @override
-  List<SummarizedContentChildFieldBuilder> get contentChildrenFields =>
-      _contentChildrenFields ??= <SummarizedContentChildFieldBuilder>[];
-
-  void set contentChildrenFields(
-      List<SummarizedContentChildFieldBuilder> value) {
-    this._contentChildrenFields = value;
-  }
-
-  @override
   List<SummarizedExportedIdentifierBuilder> get exports =>
       _exports ??= <SummarizedExportedIdentifierBuilder>[];
 
@@ -896,10 +1156,10 @@
   }
 
   SummarizedDirectiveBuilder(
-      {bool isComponent,
+      {SummarizedClassAnnotationsBuilder classAnnotations,
+      bool isComponent,
       String selectorStr,
       int selectorOffset,
-      String decoratedClassName,
       String exportAs,
       int exportAsOffset,
       String templateUrl,
@@ -908,16 +1168,12 @@
       String templateText,
       int templateOffset,
       List<SummarizedNgContentBuilder> ngContents,
-      List<SummarizedBindableBuilder> inputs,
-      List<SummarizedBindableBuilder> outputs,
       List<SummarizedDirectiveUseBuilder> subdirectives,
-      List<SummarizedContentChildFieldBuilder> contentChildFields,
-      List<SummarizedContentChildFieldBuilder> contentChildrenFields,
       List<SummarizedExportedIdentifierBuilder> exports})
-      : _isComponent = isComponent,
+      : _classAnnotations = classAnnotations,
+        _isComponent = isComponent,
         _selectorStr = selectorStr,
         _selectorOffset = selectorOffset,
-        _decoratedClassName = decoratedClassName,
         _exportAs = exportAs,
         _exportAsOffset = exportAsOffset,
         _templateUrl = templateUrl,
@@ -926,23 +1182,16 @@
         _templateText = templateText,
         _templateOffset = templateOffset,
         _ngContents = ngContents,
-        _inputs = inputs,
-        _outputs = outputs,
         _subdirectives = subdirectives,
-        _contentChildFields = contentChildFields,
-        _contentChildrenFields = contentChildrenFields,
         _exports = exports;
 
   /**
    * Flush [informative] data recursively.
    */
   void flushInformative() {
+    _classAnnotations?.flushInformative();
     _ngContents?.forEach((b) => b.flushInformative());
-    _inputs?.forEach((b) => b.flushInformative());
-    _outputs?.forEach((b) => b.flushInformative());
     _subdirectives?.forEach((b) => b.flushInformative());
-    _contentChildFields?.forEach((b) => b.flushInformative());
-    _contentChildrenFields?.forEach((b) => b.flushInformative());
     _exports?.forEach((b) => b.flushInformative());
   }
 
@@ -950,10 +1199,11 @@
    * Accumulate non-[informative] data into [signature].
    */
   void collectApiSignature(api_sig.ApiSignature signature) {
+    signature.addBool(this._classAnnotations != null);
+    this._classAnnotations?.collectApiSignature(signature);
     signature.addBool(this._isComponent == true);
     signature.addString(this._selectorStr ?? '');
     signature.addInt(this._selectorOffset ?? 0);
-    signature.addString(this._decoratedClassName ?? '');
     signature.addString(this._exportAs ?? '');
     signature.addInt(this._exportAsOffset ?? 0);
     signature.addString(this._templateUrl ?? '');
@@ -969,22 +1219,6 @@
         x?.collectApiSignature(signature);
       }
     }
-    if (this._inputs == null) {
-      signature.addInt(0);
-    } else {
-      signature.addInt(this._inputs.length);
-      for (var x in this._inputs) {
-        x?.collectApiSignature(signature);
-      }
-    }
-    if (this._outputs == null) {
-      signature.addInt(0);
-    } else {
-      signature.addInt(this._outputs.length);
-      for (var x in this._outputs) {
-        x?.collectApiSignature(signature);
-      }
-    }
     if (this._subdirectives == null) {
       signature.addInt(0);
     } else {
@@ -993,22 +1227,6 @@
         x?.collectApiSignature(signature);
       }
     }
-    if (this._contentChildFields == null) {
-      signature.addInt(0);
-    } else {
-      signature.addInt(this._contentChildFields.length);
-      for (var x in this._contentChildFields) {
-        x?.collectApiSignature(signature);
-      }
-    }
-    if (this._contentChildrenFields == null) {
-      signature.addInt(0);
-    } else {
-      signature.addInt(this._contentChildrenFields.length);
-      for (var x in this._contentChildrenFields) {
-        x?.collectApiSignature(signature);
-      }
-    }
     if (this._exports == null) {
       signature.addInt(0);
     } else {
@@ -1020,24 +1238,20 @@
   }
 
   fb.Offset finish(fb.Builder fbBuilder) {
+    fb.Offset offset_classAnnotations;
     fb.Offset offset_selectorStr;
-    fb.Offset offset_decoratedClassName;
     fb.Offset offset_exportAs;
     fb.Offset offset_templateUrl;
     fb.Offset offset_templateText;
     fb.Offset offset_ngContents;
-    fb.Offset offset_inputs;
-    fb.Offset offset_outputs;
     fb.Offset offset_subdirectives;
-    fb.Offset offset_contentChildFields;
-    fb.Offset offset_contentChildrenFields;
     fb.Offset offset_exports;
+    if (_classAnnotations != null) {
+      offset_classAnnotations = _classAnnotations.finish(fbBuilder);
+    }
     if (_selectorStr != null) {
       offset_selectorStr = fbBuilder.writeString(_selectorStr);
     }
-    if (_decoratedClassName != null) {
-      offset_decoratedClassName = fbBuilder.writeString(_decoratedClassName);
-    }
     if (_exportAs != null) {
       offset_exportAs = fbBuilder.writeString(_exportAs);
     }
@@ -1051,42 +1265,26 @@
       offset_ngContents = fbBuilder
           .writeList(_ngContents.map((b) => b.finish(fbBuilder)).toList());
     }
-    if (!(_inputs == null || _inputs.isEmpty)) {
-      offset_inputs =
-          fbBuilder.writeList(_inputs.map((b) => b.finish(fbBuilder)).toList());
-    }
-    if (!(_outputs == null || _outputs.isEmpty)) {
-      offset_outputs = fbBuilder
-          .writeList(_outputs.map((b) => b.finish(fbBuilder)).toList());
-    }
     if (!(_subdirectives == null || _subdirectives.isEmpty)) {
       offset_subdirectives = fbBuilder
           .writeList(_subdirectives.map((b) => b.finish(fbBuilder)).toList());
     }
-    if (!(_contentChildFields == null || _contentChildFields.isEmpty)) {
-      offset_contentChildFields = fbBuilder.writeList(
-          _contentChildFields.map((b) => b.finish(fbBuilder)).toList());
-    }
-    if (!(_contentChildrenFields == null || _contentChildrenFields.isEmpty)) {
-      offset_contentChildrenFields = fbBuilder.writeList(
-          _contentChildrenFields.map((b) => b.finish(fbBuilder)).toList());
-    }
     if (!(_exports == null || _exports.isEmpty)) {
       offset_exports = fbBuilder
           .writeList(_exports.map((b) => b.finish(fbBuilder)).toList());
     }
     fbBuilder.startTable();
+    if (offset_classAnnotations != null) {
+      fbBuilder.addOffset(0, offset_classAnnotations);
+    }
     if (_isComponent == true) {
-      fbBuilder.addBool(0, true);
+      fbBuilder.addBool(1, true);
     }
     if (offset_selectorStr != null) {
-      fbBuilder.addOffset(1, offset_selectorStr);
+      fbBuilder.addOffset(2, offset_selectorStr);
     }
     if (_selectorOffset != null && _selectorOffset != 0) {
-      fbBuilder.addUint32(2, _selectorOffset);
-    }
-    if (offset_decoratedClassName != null) {
-      fbBuilder.addOffset(3, offset_decoratedClassName);
+      fbBuilder.addUint32(3, _selectorOffset);
     }
     if (offset_exportAs != null) {
       fbBuilder.addOffset(4, offset_exportAs);
@@ -1112,23 +1310,11 @@
     if (offset_ngContents != null) {
       fbBuilder.addOffset(11, offset_ngContents);
     }
-    if (offset_inputs != null) {
-      fbBuilder.addOffset(12, offset_inputs);
-    }
-    if (offset_outputs != null) {
-      fbBuilder.addOffset(13, offset_outputs);
-    }
     if (offset_subdirectives != null) {
-      fbBuilder.addOffset(14, offset_subdirectives);
-    }
-    if (offset_contentChildFields != null) {
-      fbBuilder.addOffset(15, offset_contentChildFields);
-    }
-    if (offset_contentChildrenFields != null) {
-      fbBuilder.addOffset(16, offset_contentChildrenFields);
+      fbBuilder.addOffset(12, offset_subdirectives);
     }
     if (offset_exports != null) {
-      fbBuilder.addOffset(17, offset_exports);
+      fbBuilder.addOffset(13, offset_exports);
     }
     return fbBuilder.endTable();
   }
@@ -1151,10 +1337,10 @@
 
   _SummarizedDirectiveImpl(this._bc, this._bcOffset);
 
+  idl.SummarizedClassAnnotations _classAnnotations;
   bool _isComponent;
   String _selectorStr;
   int _selectorOffset;
-  String _decoratedClassName;
   String _exportAs;
   int _exportAsOffset;
   String _templateUrl;
@@ -1163,39 +1349,35 @@
   String _templateText;
   int _templateOffset;
   List<idl.SummarizedNgContent> _ngContents;
-  List<idl.SummarizedBindable> _inputs;
-  List<idl.SummarizedBindable> _outputs;
   List<idl.SummarizedDirectiveUse> _subdirectives;
-  List<idl.SummarizedContentChildField> _contentChildFields;
-  List<idl.SummarizedContentChildField> _contentChildrenFields;
   List<idl.SummarizedExportedIdentifier> _exports;
 
   @override
+  idl.SummarizedClassAnnotations get classAnnotations {
+    _classAnnotations ??= const _SummarizedClassAnnotationsReader()
+        .vTableGet(_bc, _bcOffset, 0, null);
+    return _classAnnotations;
+  }
+
+  @override
   bool get isComponent {
-    _isComponent ??= const fb.BoolReader().vTableGet(_bc, _bcOffset, 0, false);
+    _isComponent ??= const fb.BoolReader().vTableGet(_bc, _bcOffset, 1, false);
     return _isComponent;
   }
 
   @override
   String get selectorStr {
-    _selectorStr ??= const fb.StringReader().vTableGet(_bc, _bcOffset, 1, '');
+    _selectorStr ??= const fb.StringReader().vTableGet(_bc, _bcOffset, 2, '');
     return _selectorStr;
   }
 
   @override
   int get selectorOffset {
-    _selectorOffset ??= const fb.Uint32Reader().vTableGet(_bc, _bcOffset, 2, 0);
+    _selectorOffset ??= const fb.Uint32Reader().vTableGet(_bc, _bcOffset, 3, 0);
     return _selectorOffset;
   }
 
   @override
-  String get decoratedClassName {
-    _decoratedClassName ??=
-        const fb.StringReader().vTableGet(_bc, _bcOffset, 3, '');
-    return _decoratedClassName;
-  }
-
-  @override
   String get exportAs {
     _exportAs ??= const fb.StringReader().vTableGet(_bc, _bcOffset, 4, '');
     return _exportAs;
@@ -1249,55 +1431,19 @@
   }
 
   @override
-  List<idl.SummarizedBindable> get inputs {
-    _inputs ??= const fb.ListReader<idl.SummarizedBindable>(
-            const _SummarizedBindableReader())
-        .vTableGet(_bc, _bcOffset, 12, const <idl.SummarizedBindable>[]);
-    return _inputs;
-  }
-
-  @override
-  List<idl.SummarizedBindable> get outputs {
-    _outputs ??= const fb.ListReader<idl.SummarizedBindable>(
-            const _SummarizedBindableReader())
-        .vTableGet(_bc, _bcOffset, 13, const <idl.SummarizedBindable>[]);
-    return _outputs;
-  }
-
-  @override
   List<idl.SummarizedDirectiveUse> get subdirectives {
     _subdirectives ??= const fb.ListReader<idl.SummarizedDirectiveUse>(
             const _SummarizedDirectiveUseReader())
-        .vTableGet(_bc, _bcOffset, 14, const <idl.SummarizedDirectiveUse>[]);
+        .vTableGet(_bc, _bcOffset, 12, const <idl.SummarizedDirectiveUse>[]);
     return _subdirectives;
   }
 
   @override
-  List<idl.SummarizedContentChildField> get contentChildFields {
-    _contentChildFields ??=
-        const fb.ListReader<idl.SummarizedContentChildField>(
-                const _SummarizedContentChildFieldReader())
-            .vTableGet(
-                _bc, _bcOffset, 15, const <idl.SummarizedContentChildField>[]);
-    return _contentChildFields;
-  }
-
-  @override
-  List<idl.SummarizedContentChildField> get contentChildrenFields {
-    _contentChildrenFields ??=
-        const fb.ListReader<idl.SummarizedContentChildField>(
-                const _SummarizedContentChildFieldReader())
-            .vTableGet(
-                _bc, _bcOffset, 16, const <idl.SummarizedContentChildField>[]);
-    return _contentChildrenFields;
-  }
-
-  @override
   List<idl.SummarizedExportedIdentifier> get exports {
     _exports ??= const fb.ListReader<idl.SummarizedExportedIdentifier>(
             const _SummarizedExportedIdentifierReader())
         .vTableGet(
-            _bc, _bcOffset, 17, const <idl.SummarizedExportedIdentifier>[]);
+            _bc, _bcOffset, 13, const <idl.SummarizedExportedIdentifier>[]);
     return _exports;
   }
 }
@@ -1306,11 +1452,11 @@
   @override
   Map<String, Object> toJson() {
     Map<String, Object> _result = <String, Object>{};
+    if (classAnnotations != null)
+      _result["classAnnotations"] = classAnnotations.toJson();
     if (isComponent != false) _result["isComponent"] = isComponent;
     if (selectorStr != '') _result["selectorStr"] = selectorStr;
     if (selectorOffset != 0) _result["selectorOffset"] = selectorOffset;
-    if (decoratedClassName != '')
-      _result["decoratedClassName"] = decoratedClassName;
     if (exportAs != '') _result["exportAs"] = exportAs;
     if (exportAsOffset != 0) _result["exportAsOffset"] = exportAsOffset;
     if (templateUrl != '') _result["templateUrl"] = templateUrl;
@@ -1323,19 +1469,9 @@
     if (ngContents.isNotEmpty)
       _result["ngContents"] =
           ngContents.map((_value) => _value.toJson()).toList();
-    if (inputs.isNotEmpty)
-      _result["inputs"] = inputs.map((_value) => _value.toJson()).toList();
-    if (outputs.isNotEmpty)
-      _result["outputs"] = outputs.map((_value) => _value.toJson()).toList();
     if (subdirectives.isNotEmpty)
       _result["subdirectives"] =
           subdirectives.map((_value) => _value.toJson()).toList();
-    if (contentChildFields.isNotEmpty)
-      _result["contentChildFields"] =
-          contentChildFields.map((_value) => _value.toJson()).toList();
-    if (contentChildrenFields.isNotEmpty)
-      _result["contentChildrenFields"] =
-          contentChildrenFields.map((_value) => _value.toJson()).toList();
     if (exports.isNotEmpty)
       _result["exports"] = exports.map((_value) => _value.toJson()).toList();
     return _result;
@@ -1343,10 +1479,10 @@
 
   @override
   Map<String, Object> toMap() => {
+        "classAnnotations": classAnnotations,
         "isComponent": isComponent,
         "selectorStr": selectorStr,
         "selectorOffset": selectorOffset,
-        "decoratedClassName": decoratedClassName,
         "exportAs": exportAs,
         "exportAsOffset": exportAsOffset,
         "templateUrl": templateUrl,
@@ -1355,11 +1491,7 @@
         "templateText": templateText,
         "templateOffset": templateOffset,
         "ngContents": ngContents,
-        "inputs": inputs,
-        "outputs": outputs,
         "subdirectives": subdirectives,
-        "contentChildFields": contentChildFields,
-        "contentChildrenFields": contentChildrenFields,
         "exports": exports,
       };
 
diff --git a/angular_analyzer_plugin/lib/src/summary/format.fbs b/angular_analyzer_plugin/lib/src/summary/format.fbs
index 2c0359b..0fc36f4 100644
--- a/angular_analyzer_plugin/lib/src/summary/format.fbs
+++ b/angular_analyzer_plugin/lib/src/summary/format.fbs
@@ -33,17 +33,31 @@
 table UnlinkedDartSummary {
   directiveSummaries:[SummarizedDirective] (id: 0);
 
-  errors:[SummarizedAnalysisError] (id: 1);
+  annotatedClasses:[SummarizedClassAnnotations] (id: 1);
+
+  errors:[SummarizedAnalysisError] (id: 2);
+}
+
+table SummarizedClassAnnotations {
+  className:string (id: 0);
+
+  inputs:[SummarizedBindable] (id: 1);
+
+  outputs:[SummarizedBindable] (id: 2);
+
+  contentChildFields:[SummarizedContentChildField] (id: 3);
+
+  contentChildrenFields:[SummarizedContentChildField] (id: 4);
 }
 
 table SummarizedDirective {
-  isComponent:bool (id: 0);
+  classAnnotations:SummarizedClassAnnotations (id: 0);
 
-  selectorStr:string (id: 1);
+  isComponent:bool (id: 1);
 
-  selectorOffset:uint (id: 2);
+  selectorStr:string (id: 2);
 
-  decoratedClassName:string (id: 3);
+  selectorOffset:uint (id: 3);
 
   exportAs:string (id: 4);
 
@@ -61,17 +75,9 @@
 
   ngContents:[SummarizedNgContent] (id: 11);
 
-  inputs:[SummarizedBindable] (id: 12);
+  subdirectives:[SummarizedDirectiveUse] (id: 12);
 
-  outputs:[SummarizedBindable] (id: 13);
-
-  subdirectives:[SummarizedDirectiveUse] (id: 14);
-
-  contentChildFields:[SummarizedContentChildField] (id: 15);
-
-  contentChildrenFields:[SummarizedContentChildField] (id: 16);
-
-  exports:[SummarizedExportedIdentifier] (id: 17);
+  exports:[SummarizedExportedIdentifier] (id: 13);
 }
 
 table SummarizedAnalysisError {
diff --git a/angular_analyzer_plugin/lib/src/summary/idl.dart b/angular_analyzer_plugin/lib/src/summary/idl.dart
index b978e64..82c907b 100644
--- a/angular_analyzer_plugin/lib/src/summary/idl.dart
+++ b/angular_analyzer_plugin/lib/src/summary/idl.dart
@@ -57,20 +57,34 @@
 
   @Id(0)
   List<SummarizedDirective> get directiveSummaries;
-
   @Id(1)
+  List<SummarizedClassAnnotations> get annotatedClasses;
+  @Id(2)
   List<SummarizedAnalysisError> get errors;
 }
 
+abstract class SummarizedClassAnnotations extends base.SummaryClass {
+  @Id(0)
+  String get className;
+  @Id(1)
+  List<SummarizedBindable> get inputs;
+  @Id(2)
+  List<SummarizedBindable> get outputs;
+  @Id(3)
+  List<SummarizedContentChildField> get contentChildFields;
+  @Id(4)
+  List<SummarizedContentChildField> get contentChildrenFields;
+}
+
 abstract class SummarizedDirective extends base.SummaryClass {
   @Id(0)
-  bool get isComponent;
+  SummarizedClassAnnotations get classAnnotations;
   @Id(1)
-  String get selectorStr;
+  bool get isComponent;
   @Id(2)
-  int get selectorOffset;
+  String get selectorStr;
   @Id(3)
-  String get decoratedClassName;
+  int get selectorOffset;
   @Id(4)
   String get exportAs;
   @Id(5)
@@ -88,16 +102,8 @@
   @Id(11)
   List<SummarizedNgContent> get ngContents;
   @Id(12)
-  List<SummarizedBindable> get inputs;
-  @Id(13)
-  List<SummarizedBindable> get outputs;
-  @Id(14)
   List<SummarizedDirectiveUse> get subdirectives;
-  @Id(15)
-  List<SummarizedContentChildField> get contentChildFields;
-  @Id(16)
-  List<SummarizedContentChildField> get contentChildrenFields;
-  @Id(17)
+  @Id(13)
   List<SummarizedExportedIdentifier> get exports;
 }
 
diff --git a/angular_analyzer_plugin/pubspec.yaml b/angular_analyzer_plugin/pubspec.yaml
index d2c7193..3e9f2c9 100644
--- a/angular_analyzer_plugin/pubspec.yaml
+++ b/angular_analyzer_plugin/pubspec.yaml
@@ -20,8 +20,6 @@
     path: ../deps/sdk/pkg/analyzer_plugin
   front_end:
     path: ../deps/sdk/pkg/front_end
-  analysis_server:
-    path: ../deps/sdk/pkg/analysis_server
 dev_dependencies:
   test_reflective_loader: '^0.1.0'
   typed_mock: '^0.0.4'
diff --git a/angular_analyzer_plugin/test/angular_driver_test.dart b/angular_analyzer_plugin/test/angular_driver_test.dart
index 97c986b..4998b0d 100644
--- a/angular_analyzer_plugin/test/angular_driver_test.dart
+++ b/angular_analyzer_plugin/test/angular_driver_test.dart
@@ -22,9 +22,10 @@
   defineReflectiveSuite(() {
     defineReflectiveTests(AngularParseHtmlTest);
     defineReflectiveTests(BuildStandardHtmlComponentsTest);
-    defineReflectiveTests(BuildUnitDirectivesTest);
+    defineReflectiveTests(GatherAnnotationsTest);
     defineReflectiveTests(BuildUnitViewsTest);
     defineReflectiveTests(ResolveDartTemplatesTest);
+    defineReflectiveTests(LinkDirectivesTest);
     defineReflectiveTests(ResolveHtmlTemplatesTest);
     defineReflectiveTests(ResolveHtmlTemplateTest);
   });
@@ -238,14 +239,15 @@
 }
 
 @reflectiveTest
-class BuildUnitDirectivesTest extends AbstractAngularTest {
+class GatherAnnotationsTest extends AbstractAngularTest {
   List<AbstractDirective> directives;
   List<AnalysisError> errors;
 
   Future getDirectives(final source) async {
     final dartResult = await dartDriver.getResult(source.fullName);
     fillErrorListener(dartResult.errors);
-    final result = await angularDriver.getDirectives(source.fullName);
+    final result =
+        await angularDriver.getAngularAnnotatedClasses(source.fullName);
     directives = result.directives;
     errors = result.errors;
     fillErrorListener(errors);
@@ -1272,6 +1274,24 @@
   }
 
   // ignore: non_constant_identifier_names
+  Future test_finalPropertyInputErrorNonDirective() async {
+    final code = r'''
+import 'package:angular2/angular2.dart';
+
+class MyNonDirective {
+  @Input() final int immutable = 1;
+}
+''';
+    final source = newSource('/test.dart', code);
+    await getDirectives(source);
+    // validate
+    assertErrorInCodeAtPosition(
+        AngularWarningCode.INPUT_ANNOTATION_PLACEMENT_INVALID,
+        code,
+        "@Input()");
+  }
+
+  // ignore: non_constant_identifier_names
   Future test_finalPropertyInputStringError() async {
     final code = r'''
 import 'package:angular2/angular2.dart';
@@ -1602,7 +1622,8 @@
   Future getViews(final source) async {
     final dartResult = await dartDriver.getResult(source.fullName);
     fillErrorListener(dartResult.errors);
-    final result = await angularDriver.getDirectives(source.fullName);
+    final result =
+        await angularDriver.getAngularAnnotatedClasses(source.fullName);
     directives = result.directives;
 
     final linker = new ChildDirectiveLinker(
@@ -2830,7 +2851,7 @@
 }
 
 @reflectiveTest
-class ResolveDartTemplatesTest extends AbstractAngularTest {
+class LinkDirectivesTest extends AbstractAngularTest {
   List<AbstractDirective> directives;
   List<Template> templates;
   List<AnalysisError> errors;
@@ -2849,6 +2870,243 @@
   }
 
   // ignore: non_constant_identifier_names
+  Future test_inheritMetadata() async {
+    final code = r'''
+import 'package:angular2/angular2.dart';
+
+@Component(selector: 'foo', template: '')
+class BaseComponent {
+  @Input()
+  String input;
+  @Output()
+  EventEmitter<String> output;
+
+  @ViewChild(BaseComponent)
+  BaseComponent queryView;
+  @ViewChildren(BaseComponent)
+  QueryList<BaseComponent> queryListView;
+  @ContentChild(BaseComponent)
+  BaseComponent queryContent;
+  @ContentChildren(BaseComponent)
+  QueryList<BaseComponent> queryListContent;
+
+  // TODO host properties & listeners
+}
+
+@Component( selector: 'my-component', template: '<p></p>')
+class MyComponent extends BaseComponent {
+}
+''';
+    final source = newSource('/test.dart', code);
+    await getDirectives(source);
+    final component =
+        directives.firstWhere((d) => d.classElement.name == 'MyComponent');
+    final compInputs = component.inputs;
+    expect(compInputs, hasLength(1));
+    {
+      final input = compInputs[0];
+      expect(input.name, 'input');
+      expect(input.setterType, isNotNull);
+      expect(input.setterType.toString(), equals("String"));
+    }
+
+    final compOutputs = component.outputs;
+    expect(compOutputs, hasLength(1));
+    {
+      final output = compOutputs[0];
+      expect(output.name, 'output');
+      expect(output.eventType, isNotNull);
+      expect(output.eventType.toString(), equals("String"));
+    }
+
+    final compChildrenFields = component.contentChildrenFields;
+    expect(compChildrenFields, hasLength(1));
+    {
+      final children = compChildrenFields[0];
+      expect(children.fieldName, 'queryListContent');
+    }
+
+    final compChildFields = component.contentChildFields;
+    expect(compChildFields, hasLength(1));
+    {
+      final child = compChildFields[0];
+      expect(child.fieldName, 'queryContent');
+    }
+
+    // TODO asert viewchild is inherited once that's supported
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_inheritMetadataChildDirective() async {
+    final childCode = r'''
+import 'package:angular2/angular2.dart';
+
+@Component(selector: 'foo', template: '')
+class BaseComponent {
+  @Input()
+  String input;
+  @Output()
+  EventEmitter<String> output;
+
+  @ViewChild(BaseComponent)
+  BaseComponent queryView;
+  @ViewChildren(BaseComponent)
+  QueryList<BaseComponent> queryListView;
+  @ContentChild(BaseComponent)
+  BaseComponent queryContent;
+  @ContentChildren(BaseComponent)
+  QueryList<BaseComponent> queryListContent;
+
+  // TODO host properties & listeners
+}
+
+@Component( selector: 'child-component', template: '<p></p>')
+class ChildComponent extends BaseComponent {
+}
+''';
+    newSource('/child.dart', childCode);
+
+    final code = r'''
+import 'package:angular2/angular2.dart';
+import 'child.dart';
+
+@Component(selector: 'my-component', template: '<p></p>',
+    directives: const [ChildComponent])
+class MyComponent {
+}
+''';
+    final source = newSource('/test.dart', code);
+    await getDirectives(source);
+    final component =
+        (directives.firstWhere((d) => d.classElement.name == 'MyComponent')
+                as Component)
+            .view
+            .directives
+            .first;
+    final compInputs = component.inputs;
+    expect(compInputs, hasLength(1));
+    {
+      final input = compInputs[0];
+      expect(input.name, 'input');
+      expect(input.setterType, isNotNull);
+      expect(input.setterType.toString(), equals("String"));
+    }
+
+    final compOutputs = component.outputs;
+    expect(compOutputs, hasLength(1));
+    {
+      final output = compOutputs[0];
+      expect(output.name, 'output');
+      expect(output.eventType, isNotNull);
+      expect(output.eventType.toString(), equals("String"));
+    }
+
+    final compChildren = component.contentChildren;
+    expect(compChildren, hasLength(1));
+    {
+      final children = compChildren[0];
+      expect(children.field.fieldName, 'queryListContent');
+    }
+
+    final compChilds = component.contentChilds;
+    expect(compChilds, hasLength(1));
+    {
+      final child = compChilds[0];
+      expect(child.field.fieldName, 'queryContent');
+    }
+
+    // TODO asert viewchild is inherited once that's supported
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_inheritMetadataInheritanceDeep() async {
+    final code = r'''
+import 'package:angular2/angular2.dart';
+
+class BaseBaseComponent {
+  @Input()
+  int someInput;
+}
+
+class BaseComponent extends BaseBaseComponent {
+}
+
+@Component(selector: 'my-component', template: '<p></p>')
+class FinalComponent
+   extends BaseComponent {
+}
+''';
+    final source = newSource('/test.dart', code);
+    await getDirectives(source);
+    final component =
+        directives.firstWhere((d) => d.classElement.name == 'FinalComponent');
+    final compInputs = component.inputs;
+    expect(compInputs, hasLength(1));
+    {
+      final input = compInputs[0];
+      expect(input.name, 'someInput');
+      expect(input.setterType, isNotNull);
+      expect(input.setterType.toString(), equals("int"));
+    }
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_inheritMetadataMixinsInterfaces() async {
+    final code = r'''
+import 'package:angular2/angular2.dart';
+
+class MixinComponent1 {
+  @Input()
+  int mixin1Input;
+}
+
+class MixinComponent2 {
+  @Input()
+  int mixin2Input;
+}
+
+class ComponentInterface1 {
+  @Input()
+  int interface1Input;
+}
+
+class ComponentInterface2 {
+  @Input()
+  int interface2Input;
+}
+
+@Component( selector: 'my-component', template: '<p></p>')
+class FinalComponent
+   extends Object
+   with MixinComponent1, MixinComponent2
+   implements ComponentInterface1, ComponentInterface2 {
+}
+''';
+    final source = newSource('/test.dart', code);
+    await getDirectives(source);
+    final component =
+        directives.firstWhere((d) => d.classElement.name == 'FinalComponent');
+    final compInputs = component.inputs;
+    expect(compInputs, hasLength(4));
+    {
+      final input = compInputs[0];
+      expect(input.name, 'mixin1Input');
+    }
+    {
+      final input = compInputs[1];
+      expect(input.name, 'mixin2Input');
+    }
+    {
+      final input = compInputs[2];
+      expect(input.name, 'interface1Input');
+    }
+    {
+      final input = compInputs[3];
+      expect(input.name, 'interface2Input');
+    }
+  }
+
+  // ignore: non_constant_identifier_names
   Future test_hasError_DirectiveTypeLiteralExpected() async {
     final source = newSource('/test.dart', r'''
 import 'package:angular2/angular2.dart';
@@ -2861,6 +3119,26 @@
     errorListener.assertErrorsWithCodes(
         <ErrorCode>[AngularWarningCode.TYPE_IS_NOT_A_DIRECTIVE]);
   }
+}
+
+@reflectiveTest
+class ResolveDartTemplatesTest extends AbstractAngularTest {
+  List<AbstractDirective> directives;
+  List<Template> templates;
+  List<AnalysisError> errors;
+
+  Future getDirectives(final source) async {
+    final dartResult = await dartDriver.getResult(source.fullName);
+    fillErrorListener(dartResult.errors);
+    final ngResult = await angularDriver.resolveDart(source.fullName);
+    directives = ngResult.directives;
+    errors = ngResult.errors;
+    fillErrorListener(errors);
+    templates = directives
+        .map((d) => d is Component ? d.view?.template : null)
+        .where((d) => d != null)
+        .toList();
+  }
 
   // ignore: non_constant_identifier_names
   Future test_componentReference() async {
diff --git a/angular_analyzer_plugin/test/plugin_test.dart b/angular_analyzer_plugin/test/plugin_test.dart
index 8f2842d..2eb9e43 100644
--- a/angular_analyzer_plugin/test/plugin_test.dart
+++ b/angular_analyzer_plugin/test/plugin_test.dart
@@ -1,12 +1,17 @@
+import 'dart:async';
 import 'package:angular_analyzer_plugin/src/angular_driver.dart';
+import 'package:analyzer_plugin/protocol/protocol_common.dart' as protocol;
 import 'package:analyzer_plugin/protocol/protocol_generated.dart' as protocol;
 import 'package:analyzer/file_system/file_system.dart';
-import 'package:analyzer/file_system/physical_file_system.dart';
+import 'package:analyzer/file_system/memory_file_system.dart';
 import 'package:angular_analyzer_plugin/plugin.dart';
+import 'package:angular_analyzer_plugin/src/noop_driver.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 import 'package:unittest/unittest.dart';
 import 'package:typed_mock/typed_mock.dart';
 
+import 'mock_sdk.dart';
+
 void main() {
   defineReflectiveSuite(() {
     defineReflectiveTests(PluginIntegrationTest);
@@ -16,19 +21,35 @@
 @reflectiveTest
 class PluginIntegrationTest {
   AngularAnalyzerPlugin plugin;
-  ResourceProvider resourceProvider;
+  MemoryResourceProvider resourceProvider;
+  protocol.ContextRoot root;
 
   void setUp() {
-    resourceProvider = PhysicalResourceProvider.INSTANCE;
+    resourceProvider = new MemoryResourceProvider();
+    new MockSdk(resourceProvider: resourceProvider);
     plugin = new AngularAnalyzerPlugin(resourceProvider);
     final versionCheckParams = new protocol.PluginVersionCheckParams(
-        "~/.dartServer/.analysis-driver", "../deps/sdk/sdk", "1.0.0");
+        "~/.dartServer/.analysis-driver", "/sdk", "1.0.0");
     plugin.handlePluginVersionCheck(versionCheckParams);
+    root = new protocol.ContextRoot("/test", [],
+        optionsFile: '/test/analysis_options.yaml');
+  }
+
+  void enableInOptionsFile() {
+    setOptionsFileContent('''
+plugins:
+  angular:
+    enabled: true
+''');
+  }
+
+  void setOptionsFileContent(String content) {
+    resourceProvider.newFile('/test/analysis_options.yaml', content);
   }
 
   // ignore: non_constant_identifier_names
   void test_createAnalysisDriver() {
-    final root = new protocol.ContextRoot("/test", []);
+    enableInOptionsFile();
     final AngularDriver driver = plugin.createAnalysisDriver(root);
 
     expect(driver, isNotNull);
@@ -37,7 +58,7 @@
 
   // ignore: non_constant_identifier_names
   void test_createAnalysisDriver_containsDartDriver() {
-    final root = new protocol.ContextRoot("/test", []);
+    enableInOptionsFile();
     final AngularDriver driver = plugin.createAnalysisDriver(root);
 
     expect(driver, isNotNull);
@@ -48,6 +69,105 @@
     expect(driver.dartDriver.sourceFactory, isNotNull);
     expect(driver.dartDriver.contextRoot, isNotNull);
   }
+
+  // ignore: non_constant_identifier_names
+  void test_createAnalysisDriver_noAnalysisOptionsMeansDisabled() {
+    root.optionsFile = null;
+    final AngularDriver driver = plugin.createAnalysisDriver(root);
+
+    expect(driver, const isInstanceOf<NoopDriver>());
+  }
+
+  // ignore: non_constant_identifier_names
+  void test_createAnalysisDriver_emptyAnalysisOptionsMeansDisabled() {
+    root.optionsFile = '';
+    final AngularDriver driver = plugin.createAnalysisDriver(root);
+
+    expect(driver, const isInstanceOf<NoopDriver>());
+  }
+
+  // ignore: non_constant_identifier_names
+  void test_createAnalysisDriver_analysisOptionsNotExistsMeansDisabled() {
+    final AngularDriver driver = plugin.createAnalysisDriver(root);
+    // and then don't set up analysis_options.yaml
+
+    expect(driver, const isInstanceOf<NoopDriver>());
+  }
+
+  // ignore: non_constant_identifier_names
+  void test_createAnalysisDriver_analysisOptionsNotEnabled() {
+    setOptionsFileContent('''
+analyzer:
+  strong-mode: true
+
+plugins:
+  other-plugin:
+    that: "is irrelevant"
+''');
+    final AngularDriver driver = plugin.createAnalysisDriver(root);
+
+    expect(driver, const isInstanceOf<NoopDriver>());
+  }
+
+  // ignore: non_constant_identifier_names
+  void test_createAnalysisDriver_analysisOptionsDisabled() {
+    setOptionsFileContent('''
+plugins:
+  angular:
+    enabled: false
+''');
+    final AngularDriver driver = plugin.createAnalysisDriver(root);
+
+    expect(driver, const isInstanceOf<NoopDriver>());
+  }
+
+  // ignore: non_constant_identifier_names
+  void test_noAnalysisDriver_getAngularDriverIsNull() {
+    root.optionsFile = null;
+    plugin.createAnalysisDriver(root);
+
+    expect(plugin.angularDriverForPath(root.root), isNull);
+  }
+
+  // ignore: non_constant_identifier_names
+  void test_noAnalysisDriver_getCompletionContributors_Empty() {
+    root.optionsFile = null;
+    plugin.createAnalysisDriver(root);
+
+    expect(plugin.getCompletionContributors(root.root), hasLength(0));
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_noAnalysisDriver_updateFilesOk() async {
+    root.optionsFile = null;
+    plugin.createAnalysisDriver(root);
+
+    await plugin.handleAnalysisUpdateContent(
+        new protocol.AnalysisUpdateContentParams(
+            {'/test/test.dart': new protocol.AddContentOverlay('foo')}));
+    await plugin
+        .handleAnalysisUpdateContent(new protocol.AnalysisUpdateContentParams({
+      '/test/test.dart': new protocol.ChangeContentOverlay(
+          [new protocol.SourceEdit(1, 2, 'foo')])
+    }));
+    await plugin.handleAnalysisUpdateContent(
+        new protocol.AnalysisUpdateContentParams(
+            {'/test/test.dart': new protocol.RemoveContentOverlay()}));
+    // should not have thrown
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_noAnalysisDriver_getCompletionOk() async {
+    root.optionsFile = null;
+    plugin.createAnalysisDriver(root);
+
+    final resp = await plugin.handleCompletionGetSuggestions(
+        new protocol.CompletionGetSuggestionsParams('/test/test.dart', 0));
+
+    expect(resp.replacementOffset, equals(0));
+    expect(resp.replacementLength, equals(0));
+    expect(resp.results, isEmpty);
+  }
 }
 
 class MockResourceProvider extends TypedMock implements ResourceProvider {}
diff --git a/angular_analyzer_plugin/test/resolver_test.dart b/angular_analyzer_plugin/test/resolver_test.dart
index b04811d..7ec3b69 100644
--- a/angular_analyzer_plugin/test/resolver_test.dart
+++ b/angular_analyzer_plugin/test/resolver_test.dart
@@ -298,6 +298,51 @@
   }
 
   // ignore: non_constant_identifier_names
+  Future test_expression_inputBinding_asBoool_noError() async {
+    _addDartSource(r'''
+@Component(selector: 'test-panel',
+    directives: const [TitleComponent], templateUrl: 'test_panel.html')
+class TestPanel {
+}
+@Component(selector: 'title-comp', template: '')
+class TitleComponent {
+  @Input() bool boolInput;
+}
+''');
+
+    final code = r"""
+<title-comp boolInput></title-comp>
+""";
+    _addHtmlSource(code);
+    await _resolveSingleTemplate(dartSource);
+    errorListener.assertNoErrors();
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_expression_inputBinding_asBool_typeError() async {
+    _addDartSource(r'''
+@Component(selector: 'test-panel',
+    directives: const [TitleComponent], templateUrl: 'test_panel.html')
+class TestPanel {
+}
+@Component(selector: 'title-comp', template: '')
+class TitleComponent {
+  @Input() bool boolInput;
+}
+''');
+
+    final code = r"""
+<title-comp boolInput="foo bar baz"></title-comp>
+""";
+    _addHtmlSource(code);
+    await _resolveSingleTemplate(dartSource);
+    assertErrorInCodeAtPosition(
+        AngularWarningCode.STRING_STYLE_INPUT_BINDING_INVALID,
+        code,
+        "boolInput");
+  }
+
+  // ignore: non_constant_identifier_names
   Future test_expression_inputBinding_nativeHtml_asString_notTypeError() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel',
diff --git a/playground/analysis_options.yaml b/playground/analysis_options.yaml
new file mode 100644
index 0000000..7f12c14
--- /dev/null
+++ b/playground/analysis_options.yaml
@@ -0,0 +1,2 @@
+angular:
+  enabled: true