Latest sync to master (#319)

* Defend against files hidden by generated files (#271)

* Travis sdk fix (#272)

* Sync to origin master

* Fix travis build. Fix sdk related errors (ContextRoot and IdeOptions). Clean up imports to remove unused

* Tick to 8 (#277)

* Sync to origin master

* Fix travis build. Fix sdk related errors (ContextRoot and IdeOptions). Clean up imports to remove unused

* Tick to 0.0.8

* Track @Attribute annotated constructor parameters (#270)

* Resolve plain attributes -- in ranges, and strchecks for inputs. (#278)

* Resolve plain attributes -- in ranges, and strchecks for inputs.

Record resolved ranges for `x="y"` where `x` is an input or a constructor
`@Attribute`. If its an input, check that strings are assignable to it.
If not, give it its own error that hopefully explains the semi magical
situation.

* Track/typecheck std attrs

Eventually we should
* report errors for <a href="foo" [href]="bar">
* not suggest href after [href] has been used
* not suggest [href] after href has been used

* Template parsing fix (#279)

* Sync to origin master

* Fix travis build. Fix sdk related errors (ContextRoot and IdeOptions). Clean up imports to remove unused

* Fix issue that caused empty binding on template attribute to crash

* Now ready: Priority angular analysis (#273)

* Priority dart & html analysis

* Starter class, await supplementing errors or they're added too late.

* Report has work to analyze for priority requests

* Don't await for requesting files, do have a cache busting option

* Handle case of files being requested multiple times

* Priority html requests, requires serving errors with line infos.

* Report all html files for all dart contexts in one method, plus tests

I think before we were getting a last-one-wins effect, where the last
analyzed dart/html pair would cover up all the errors for every other
dart/html pair.

As such, I need to carefully test that this change doesn't introduce
new false errors (from them being covered up before).

But now with this, we can report html errors back for multiple contexts
from a single method when that's requested. Next I just have to use
that method on getErrors when we have multiple relationships tracked
already.

* Guess html/dart relationship when none exist, otherwise use what's known
  using isEmpty rather than == 0

* Get setterType by setter.parameters[0] instead of setter.variable.type (#283)

Seems like in the precense of a getter, setter.variable.type is the
getter type, not the setter's type. So use the first function argument
instead.

* Remove contextRoot, won't be required in master soon, and breaks dev (#284)

* Disable global attr plaintext typechecking until #280 has a clear solution (#286)

* Update test_reflective_loader, fix analyzer warnings&errors (#285)

* Update test_reflective_loader, fix analyzer warnings&errors

* Fix the single server test too, which prevents build errors

* Use var instead of types in angular_driver_test

* Add TODOs

* Percentage use fix in style.width and style.height. Test cases added (#282)

* Percentage use fix in style.width and style.height. Test cases added

* Added more property names that use percentage

* Update readme (#292)

* Dont rehash html contents unless they change (#293)

* update dependencies to point to the right place (#296)

* Fix moved files (#298)

* Fix moved files

* Fix server test imports

* Autocompletion fix (#297)

* Checkpoint

* html completion working, dart completion groundwork laid in, just started test

* temp revert

* Cleaned up completion functionality. Begin testing changes.

* Small revert

* Test cases fully passing

* Removing unused imports

* Remove linking of .dart completion - temporarily disabled

* view.elementTags is now lazily computed. 'computeSuggestions' will always ensure that standardHtml is calculated before usage

* getTemplateForFile now retuns a list of templates. computeSuggestions modified to adjust

* Naming change for better accuracy; minor bug fix

* @ContentChild and @ContentChildren validation. (#291)

* @ContentChild and @ContentChildren validation.

Validates:
- ElementRef
- TemplateRef
- Components
- Directives
- String-based let bindings (and checks property match)
- all others as errors
- property has QueryList assignability for ContentChildren
- ContentChild is only matched once

Changes:
- elements matching ContentChild of parent are counted as transcluded,
error wise
- DirectiveResolver is split into two phases, so that transclusions can
be checked _after_ inner content is directive resolved (for the sake of
ContentChild)
- added "StandardAngular" where we get certain special ClassElements
before running any other analysis, so that we can check if some random
type x is a supertype of angular's ElementRef.
- added ContentChildBindings so we can track the results of this
process to affect autocompletion suggestions, navigation, refactoring

Important edge cases:
- proper generic type checking for Iterable<T> as QueryList<T>
- using isSupertypeOf instead of isAssignableTo, because downcasting is
_known_ to be wrong (otherwise in dart downcasting is assumed
deliberate)
- don't supress transclusion errors for element A within B just because
some parent of B understands A as a contentChild -- has nothing to do
with A not belonging to B.
- don't report duplicate ContentChild errors when the duplicate is
inside a prior match (in fact, don't even report assignability errors!)
- directive matches require using exportAs with the #x="y" syntax.
- getting the "constant value" fields with inheritance requires using
`.getField("(super)")` the correct amount of times. This looks VERY
fragile to me.
- Native elements get Components, but they become ElementRefs not
TableElements. Ensure people don't use @ContentChild(TableElement).

Tests:
- changed to use 'package:angular2' instead of '/angular2/' because
that is required to build StandardAngular. However, not all tests are
changed, because its still ok to use '/angular2' too.
- added ElementRef and TemplateRef and QueryList classes.

Todos:
- ViewChild/ViewChildren
- track matched #attrs instead of just Elements
- revisit the highlighted ranges, can we make them more specific?
- instantiate classes to bounds

* Look for selector field with arbitrary inheritance

* Optimize content child scanning

* Fuzz test updates

* Another fuzz fix

* Avoid element.unit, very slow. Use enclosingElement

* Fix server tests failing (#300)

* Prevent inf loop of trying to resolve standardAngular in workspaces that don't have angular2 imported (#301)

* Implement new methods that will soon be part of AnalysisDriverGeneric (#299)

* Change setPriorityFiles to set priorityFiles (#304)

* Change event emitter to stream (#305)

Change event emitter to stream

* Plain attribute autocompletion (#303)

* Component and standard inputs - autocomplete as plain attribute

* Autocomplete bug fix

* Checkpoint

* test cases added

* Quick fix - still need to follow through null checks

* Fix travisCL complaining, also remove unnecessary whitespace

* Allows dynamic types to be autocompleted and improved flow

* get typeprovider from template

* Better validation of plain attributes (#306)

* Fix pathing for byte_store (#312)

* null check added on source (#311)

* Remove searchEngine from CompletionRequest reflecting changes made in… (#314)

* Remove searchEngine from CompletionRequest reflecting changes made in completion core, and remove index as per suggestion

* Cleaned up unused imports and unused override reflecting recent changes (#315)

* Cleaned up unused imports and unused override reflecting recent changes

* remove context from completionRequest

* Removing unnecessary null check

* Omega lint fixup (#313)

* Get new plugin architecture in a workable development stage (#316)

See readme for how to get set up....currently doesn't do anything,
though.

* More lint fixes, handle moved file (#317)

* Autocomplete stars (#310)

* Complete inputs inside of template elements

Complete, for instance, `trackBy` inside of an ngFor.

* Analyze and suggest directives that appear to want `*star` syntax.

If a `TemplateRef` is in the constructor, it works with star syntax.
Warn when not used with star syntax, and warn when star syntax is used
on a directive that does not have this special ctor as otherwise the
template will go unused.

Its not a foolproof system, I don't think. So detect that separately
from detection of ngIf and ngFor without stars. Especially so that in
the cases where this goes wrong, users can opt out of the error without
losing errors for those core directives' validation...and give
different messages.

Not sure what's more likely...that this expects too many things to be
used with stars, or not enough...

Also suggest this in autocomplete for great profit. Assume that these
directives rely on a property binding, so suggest *p for all p which
are property selectors.

Note this is also a less than perfect system, as it includes suggesting
`*ngForOf`, since that's part of `NgFor`s selector. Hardcode that out,
at least for now.

With this plus suggesting `trackBy:`, we now (I think) suggest
everything relevant for two way autocomplete except `template=`
attributes themselves and the `let` keyword. Wanted to do the latter
but it seemed like it might be a bit treacherous: think `ngIf="^"` vs
`ngFor="^"`, it would be the first time we'd suggest both dart and
non-dart things at once.

* Fix analysis errors

* prefer var and final over types

* More lint fixes

* Include colon in suggestion

* Move performance log (#318)

* Tests passing

* Sync to master; tests passing
diff --git a/.gitignore b/.gitignore
index abbff26..44ead56 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,3 +13,5 @@
 deps/.gclient
 
 server_plugin/bin/server.snapshot
+
+analyze_angular/tools/analysis_plugin/pubspec.yaml
diff --git a/README.md b/README.md
index 184a007..de34e8b 100644
--- a/README.md
+++ b/README.md
@@ -5,19 +5,12 @@
   To provide information for DAS clients the `server_plugin` plugin contributes several extensions.
 
 * Angular analysis errors are automatically merged into normal `errors` notifications for Dart and HTML files.
-* Navigation extension contributes navigation regions:
-    * In external HTML templates.
-    * In inline templates in Dart files.
-    * In Dart annotations:
-        * navigation from `templateUrl` to the corresponding HTML files.
-        * navigation from input declarations to setters, e.g. to `text` in `inputs: const ['text: my-text']`.
-* Occurrences extension reports regions where every Dart element or an input is used in a Dart or HTML file. So, clients of DAS can highlight all of the whe user select one.
 
 ![Preview gif](https://raw.githubusercontent.com/dart-lang/angular_analyzer_plugin/master/assets/angular-dart-intellij-plugin-demo.gif "Preview gif")
 
-## Installing
+**Check the pubspec.yaml in your project for transformers. They are not supported. You must manually add CORE_DIRECTIVES to your components right now for this plugin to work.**
 
-**Build is currently broken as we are changing the analyzer plugin API to something more formal, reliable, and convenient.** Only works with older versions of the SDK right now (1.22.0.dev.4), which is a royal pain to install with dependencies.
+## Building & Installing -- Version One (recommended but soon to be deprecated)
 
 Download chrome depot tools, and clone this repository.
 
@@ -30,11 +23,30 @@
 
 Back up `sdk_path/snapshots/analysis_server.dart.snapshot` and replace it with `server.snapshot`. Restart the dart analysis server by clicking the skull.
 
-**Check the pubspec.yaml in your project for transformers. They are not supported. You must manually add CORE_DIRECTIVES to your components right now for this plugin to work.**
+## Building -- Version Two (not usable yet, but this will soon be the future)
+
+Under the next system, you will not need to build to install (woo hoo!). However, these steps currently don't produce anything usable. Installation steps will come once its ready.
+
+Download chrome depot tools, and clone this repository.
+
+Then run 
+```
+./tools/get_deps.sh
+cd analyze_angular/tools/plugin
+cp pubspec.yaml.defaults pubspec.yaml
+```
+
+Modify `pubspec.yaml` in this folder to fix the absolute paths. They **must** be absolute for the moment! Once they can be relative this step will not be required.
+
+Then run `pub get`.
+
+You can now use this in projects on your local system which a correctly configured pubspec. For instance, `playground/`. Note that you must `import 'package:analyze_angular/'` in your project to get the analysis.
 
 ## Chart of Current Features
 
-All regular dart errros (that is to say, errors defined purely by the dart language spec) are not shown in this list.
+All regular dart errors (that is to say, errors defined purely by the dart language spec) are not shown in this list.
+
+Autocomplete is available in a branch, and only in editors that support it, and maybe with bugs (many don't replace the correct content during autocomplete within html, for instance).
 
 Bootstrapping | Validation | Auto-Complete | Navigation | Refactoring
 --------------|------------|---------------|------------|-------------
diff --git a/analyze_angular/lib/analyze_angular.dart b/analyze_angular/lib/analyze_angular.dart
new file mode 100644
index 0000000..8b1a393
--- /dev/null
+++ b/analyze_angular/lib/analyze_angular.dart
@@ -0,0 +1 @@
+// empty
diff --git a/analyze_angular/pubspec.yaml b/analyze_angular/pubspec.yaml
new file mode 100644
index 0000000..4e0c5d7
--- /dev/null
+++ b/analyze_angular/pubspec.yaml
@@ -0,0 +1,7 @@
+name: analyze_angular
+version: 0.0.0
+description: Temp means of loading angular analysis into an angular project.
+environment:
+  sdk: '>=1.24.0-dev.1.0'
+dependencies:
+dev_dependencies:
diff --git a/analyze_angular/tools/analysis_plugin/bin/plugin.dart b/analyze_angular/tools/analysis_plugin/bin/plugin.dart
new file mode 100644
index 0000000..0627e12
--- /dev/null
+++ b/analyze_angular/tools/analysis_plugin/bin/plugin.dart
@@ -0,0 +1,7 @@
+import 'dart:isolate';
+
+import 'package:angular_analysis_plugin/starter.dart';
+
+void main(List<String> args, SendPort sendPort) {
+  start(args, sendPort);
+}
diff --git a/analyze_angular/tools/analysis_plugin/pubspec.yaml.defaults b/analyze_angular/tools/analysis_plugin/pubspec.yaml.defaults
new file mode 100644
index 0000000..1fee900
--- /dev/null
+++ b/analyze_angular/tools/analysis_plugin/pubspec.yaml.defaults
@@ -0,0 +1,21 @@
+name: analyzed_angular_plugin_loader
+version: 0.0.0
+description: Temp means of loading angular analysis into an angular project.
+environment:
+  sdk: '>=1.24.0-dev.1.0'
+dependencies:
+  angular_analysis_plugin: '0.0.8'
+dev_dependencies:
+dependency_overrides:
+  angular_analysis_plugin:
+    path: ABSOLUTE_PATH_TO_REPO/new_plugin
+  analysis_server:
+    path: ABSOLUTE_PATH_TO_REPO/deps/sdk/pkg/analysis_server
+  front_end:
+    path: ABSOLUTE_PATH_TO_REPO/deps/sdk/pkg/front_end
+  analyzer_plugin:
+    path: ABSOLUTE_PATH_TO_REPO/deps/sdk/pkg/analyzer_plugin
+  angular_analyzer_plugin:
+    path: ABSOLUTE_PATH_TO_REPO/analyzer_plugin
+  angular_analyzer_server_plugin:
+    path: ABSOLUTE_PATH_TO_REPO/server_plugin
diff --git a/analyzer_plugin/.analysis_options b/analyzer_plugin/.analysis_options
deleted file mode 100644
index a10d4c5..0000000
--- a/analyzer_plugin/.analysis_options
+++ /dev/null
@@ -1,2 +0,0 @@
-analyzer:
-  strong-mode: true
diff --git a/analyzer_plugin/analysis_options.yaml b/analyzer_plugin/analysis_options.yaml
new file mode 100644
index 0000000..613e528
--- /dev/null
+++ b/analyzer_plugin/analysis_options.yaml
@@ -0,0 +1,79 @@
+analyzer:
+  strong-mode: true
+  exclude:
+    - lib/src/summary/format.dart
+    - lib/src/angular_html_parser.dart
+
+linter:
+  rules:
+    - prefer_final_locals
+    - always_declare_return_types
+    - always_put_control_body_on_new_line
+    - always_require_non_null_named_parameters
+    - annotate_overrides
+    - avoid_annotating_with_dynamic
+    - avoid_catching_errors
+    - avoid_classes_with_only_static_members
+    - avoid_empty_else
+    - avoid_init_to_null
+    - avoid_null_checks_in_equality_operators
+    - avoid_positional_boolean_parameters
+    - avoid_return_types_on_setters
+    - avoid_setters_without_getters
+    - avoid_slow_async_io
+    - avoid_types_on_closure_parameters
+    - await_only_futures
+    - camel_case_types
+    - cancel_subscriptions
+    - cascade_invocations
+    - close_sinks
+    - control_flow_in_finally
+    - directives_ordering
+    - empty_catches
+    - empty_constructor_bodies
+    - empty_statements
+    - iterable_contains_unrelated_type
+    - join_return_with_assignment
+    - library_names
+    - library_prefixes
+    - list_remove_unrelated_type
+    - no_adjacent_strings_in_list
+    - no_duplicate_case_values
+    - non_constant_identifier_names
+    - omit_local_variable_types
+    - only_throw_errors
+    - overridden_fields
+    - parameter_assignments
+    - prefer_adjacent_string_concatenation
+    - prefer_collection_literals
+    - prefer_conditional_assignment
+    - prefer_const_constructors
+    - prefer_constructors_over_static_methods
+    - prefer_contains
+    - prefer_expression_function_bodies
+    - prefer_final_fields
+    - prefer_final_locals
+    - prefer_function_declarations_over_variables
+    - prefer_interpolation_to_compose_strings
+    - prefer_is_empty
+    - prefer_is_not_empty
+    - recursive_getters
+    - slash_for_doc_comments
+    - super_goes_last
+    - test_types_in_equals
+    - throw_in_finally
+    - type_init_formals
+    - unawaited_futures
+    - unnecessary_brace_in_string_interps
+    - unnecessary_getters_setters
+    - unnecessary_lambdas
+    - unnecessary_null_aware_assignments
+    - unnecessary_null_in_if_null_operators
+    - unnecessary_overrides
+    - unnecessary_this
+    - unrelated_type_equality_checks
+    - use_rethrow_when_possible
+    - use_setters_to_change_properties
+    - use_string_buffers
+    - use_to_and_as_if_applicable
+    - valid_regexps
diff --git a/analyzer_plugin/lib/ast.dart b/analyzer_plugin/lib/ast.dart
index 2a20f61..ac555df 100644
--- a/analyzer_plugin/lib/ast.dart
+++ b/analyzer_plugin/lib/ast.dart
@@ -4,6 +4,7 @@
 import 'package:analyzer/src/generated/source.dart';
 import 'package:angular_analyzer_plugin/src/model.dart';
 import 'package:analyzer/src/dart/element/element.dart';
+import 'package:meta/meta.dart';
 
 enum ExpressionBoundType { input, twoWay, attr, clazz, style }
 
@@ -40,15 +41,13 @@
       _visitAllChildren(elementInfo);
 
   void _visitAllChildren(AngularAstNode node) {
-    for (AngularAstNode child in node.children) {
+    for (final child in node.children) {
       child.accept(this);
     }
   }
 }
 
-/**
- * Information about an attribute.
- */
+/// Information about an attribute.
 abstract class AttributeInfo extends AngularAstNode {
   HasDirectives parent;
 
@@ -61,7 +60,10 @@
   final String originalName;
   final int originalNameOffset;
 
+  @override
   int get offset => originalNameOffset;
+
+  @override
   int get length => valueOffset == null
       ? originalName.length
       : valueOffset + value.length - originalNameOffset;
@@ -72,10 +74,9 @@
   int get valueLength => value != null ? value.length : 0;
 
   @override
-  String toString() {
-    return '([$name, $nameOffset], [$value, $valueOffset, $valueLength], ' +
-        '[$originalName, $originalNameOffset])';
-  }
+  String toString() =>
+      '([$name, $nameOffset], [$value, $valueOffset, $valueLength], '
+      '[$originalName, $originalNameOffset])';
 }
 
 abstract class BoundAttributeInfo extends AttributeInfo {
@@ -87,35 +88,41 @@
       : super(name, nameOffset, value, valueOffset, originalName,
             originalNameOffset);
 
+  @override
   List<AngularAstNode> get children => const <AngularAstNode>[];
 
   @override
-  String toString() {
-    return '(' + super.toString() + ', [$children])';
-  }
+  String toString() => '(${super.toString()}, [$children])';
 }
 
 class TemplateAttribute extends BoundAttributeInfo implements HasDirectives {
   final List<AttributeInfo> virtualAttributes;
-  List<DirectiveBinding> boundDirectives = <DirectiveBinding>[];
-  List<OutputBinding> boundStandardOutputs = <OutputBinding>[];
-  List<InputBinding> boundStandardInputs = <InputBinding>[];
+  @override
+  final boundDirectives = <DirectiveBinding>[];
+  @override
+  final boundStandardOutputs = <OutputBinding>[];
+  @override
+  final boundStandardInputs = <InputBinding>[];
+
   List<AbstractDirective> get directives =>
-      boundDirectives.map((bd) => bd.boundDirective);
+      boundDirectives.map((bd) => bd.boundDirective).toList();
+
+  String prefix;
 
   TemplateAttribute(String name, int nameOffset, String value, int valueOffset,
-      String originalName, int originalNameOffset, this.virtualAttributes)
+      String originalName, int originalNameOffset, this.virtualAttributes,
+      {this.prefix})
       : super(name, nameOffset, value, valueOffset, originalName,
             originalNameOffset);
 
+  @override
   List<AngularAstNode> get children =>
       new List<AngularAstNode>.from(virtualAttributes);
 
   @override
-  String toString() {
-    return '(' + super.toString() + ', [$virtualAttributes])';
-  }
+  String toString() => '(${super.toString()}, [$virtualAttributes])';
 
+  @override
   void accept(AngularAstVisitor visitor) => visitor.visitTemplateAttr(this);
 }
 
@@ -135,10 +142,9 @@
             originalNameOffset);
 
   @override
-  String toString() {
-    return '(' + super.toString() + ', [$bound, $expression])';
-  }
+  String toString() => '(${super.toString()}, [$bound, $expression])';
 
+  @override
   void accept(AngularAstVisitor visitor) =>
       visitor.visitExpressionBoundAttr(this);
 }
@@ -157,16 +163,16 @@
             originalNameOffset);
 
   @override
-  String toString() {
-    return '(' + super.toString() + ', [$statements])';
-  }
+  String toString() => '(${super.toString()}, [$statements])';
 
+  @override
   void accept(AngularAstVisitor visitor) =>
       visitor.visitStatementsBoundAttr(this);
 }
 
 class TextAttribute extends AttributeInfo {
   final List<Mustache> mustaches;
+  @override
   List<AngularAstNode> get children => new List<AngularAstNode>.from(mustaches);
 
   TextAttribute(String name, int nameOffset, String value, int valueOffset,
@@ -184,12 +190,15 @@
       : super(name, nameOffset, value, valueOffset, originalName,
             originalNameOffset);
 
+  @override
   void accept(AngularAstVisitor visitor) => visitor.visitTextAttr(this);
 }
 
 class Mustache extends AngularAstNode {
   Expression expression;
+  @override
   final int offset;
+  @override
   final int length;
   final int exprBegin;
   final int exprEnd;
@@ -197,6 +206,7 @@
   Map<String, LocalVariable> localVariables =
       new HashMap<String, LocalVariable>();
 
+  @override
   List<AngularAstNode> get children => const <AngularAstNode>[];
 
   Mustache(
@@ -207,53 +217,61 @@
     this.exprEnd,
   );
 
+  @override
   void accept(AngularAstVisitor visitor) => visitor.visitMustache(this);
 }
 
-/**
- * The HTML elements in the tree
- */
+/// The HTML elements in the tree
 abstract class NodeInfo extends AngularAstNode {
   bool get isSynthetic;
 }
 
-/**
- * An AngularAstNode which has directives, such as [ElementInfo] and
- * [TemplateAttribute]. Contains an array of [DirectiveBinding]s because those
- * contain more info than just the bound directive.
- */
+/// An AngularAstNode which has directives, such as [ElementInfo] and
+/// [TemplateAttribute]. Contains an array of [DirectiveBinding]s because those
+/// contain more info than just the bound directive.
 abstract class HasDirectives {
   List<DirectiveBinding> get boundDirectives;
   List<OutputBinding> get boundStandardOutputs;
   List<InputBinding> get boundStandardInputs;
 }
 
-/**
- * A binding to an [AbstractDirective], either on an [ElementInfo] or a
- * [TemplateAttribute]. For each bound directive, there is a directive binding.
- * Has [InputBinding]s and [OutputBinding]s which themselves indicate an
- * [AttributeInfo] bound to an [InputElement] or [OutputElement] in the context
- * of this [DirectiveBinding].
- *
- * Naming here is important: "bound directive" != "directive binding." 
- */
+/// A binding to an [AbstractDirective], either on an [ElementInfo] or a
+/// [TemplateAttribute]. For each bound directive, there is a directive binding.
+/// Has [InputBinding]s and [OutputBinding]s which themselves indicate an
+/// [AttributeInfo] bound to an [InputElement] or [OutputElement] in the context
+/// of this [DirectiveBinding].
+///
+/// Naming here is important: "bound directive" != "directive binding."
 class DirectiveBinding {
   final AbstractDirective boundDirective;
-  final List<InputBinding> inputBindings = [];
-  final List<OutputBinding> outputBindings = [];
+  final inputBindings = <InputBinding>[];
+  final outputBindings = <OutputBinding>[];
+  final contentChildBindings = <ContentChild, ContentChildBinding>{};
+  final contentChildrenBindings = <ContentChild, ContentChildBinding>{};
 
   DirectiveBinding(this.boundDirective);
 }
 
-/**
- * A binding between an [AttributeInfo] and an [InputElement].  This is used in
- * the context of a [DirectiveBinding] because each instance of a bound
- * directive has different input bindings. Note that inputs can be bound via
- * bracket syntax (an [ExpressionBoundAttribute]), or via plain attribute syntax
- * (a [TextAttribute]).
- *
- * Naming here is important: "bound input" != "input binding." 
- */
+/// Allows us to track ranges for navigating ContentChild(ren), and detect when
+/// multiple ContentChilds are matched which is an error.
+
+/// Naming here is important: "bound content child" != "content child binding."
+class ContentChildBinding {
+  final AbstractDirective directive;
+  final ContentChild boundContentChild;
+  final Set<ElementInfo> boundElements = new HashSet<ElementInfo>();
+  // TODO: track bound attributes in #foo?
+
+  ContentChildBinding(this.directive, this.boundContentChild);
+}
+
+/// A binding between an [AttributeInfo] and an [InputElement].  This is used in
+/// the context of a [DirectiveBinding] because each instance of a bound
+/// directive has different input bindings. Note that inputs can be bound via
+/// bracket syntax (an [ExpressionBoundAttribute]), or via plain attribute syntax
+/// (a [TextAttribute]).
+///
+/// Naming here is important: "bound input" != "input binding."
 class InputBinding {
   final InputElement boundInput;
   final AttributeInfo attribute;
@@ -261,17 +279,15 @@
   InputBinding(this.boundInput, this.attribute);
 }
 
-/**
- * A binding between an [BoundAttributeInfo] and an [OutputElement]. This is
- * used in the context of a [DirectiveBinding] because each instance of a bound
- * directive has different output bindings.
- *
- * Binds to an [BoundAttributeInfo] and not a [StatementsBoundAttribute] because
- * it might be a two-way binding, and thats the greatest common subtype of
- * statements bound and expression bound attributes.
- *
- * Naming here is important: "bound output" != "output binding." 
- */
+/// A binding between an [BoundAttributeInfo] and an [OutputElement]. This is
+/// used in the context of a [DirectiveBinding] because each instance of a bound
+/// directive has different output bindings.
+///
+/// Binds to an [BoundAttributeInfo] and not a [StatementsBoundAttribute] because
+/// it might be a two-way binding, and thats the greatest common subtype of
+/// statements bound and expression bound attributes.
+///
+/// Naming here is important: "bound output" != "output binding."
 class OutputBinding {
   final OutputElement boundOutput;
   final BoundAttributeInfo attribute;
@@ -279,15 +295,15 @@
   OutputBinding(this.boundOutput, this.attribute);
 }
 
-/**
- * A text node in an HTML tree.
- */
+/// A text node in an HTML tree.
 class TextInfo extends NodeInfo {
   final List<Mustache> mustaches;
+  @override
   List<AngularAstNode> get children => new List<AngularAstNode>.from(mustaches);
   final ElementInfo parent;
 
   final String text;
+  @override
   final int offset;
   final bool _isSynthetic;
 
@@ -295,18 +311,18 @@
   bool get isSynthetic => _isSynthetic;
 
   TextInfo(this.offset, this.text, this.parent, this.mustaches,
-      {synthetic: false})
+      {bool synthetic: false})
       : _isSynthetic = synthetic;
 
+  @override
   int get length => text.length;
 
+  @override
   void accept(AngularAstVisitor visitor) => visitor.visitTextInfo(this);
 }
 
-/**
- * A wrapper for a given HTML document or
- * dart-angular inline HTML template.
- */
+/// A wrapper for a given HTML document or
+/// dart-angular inline HTML template.
 class DocumentInfo extends ElementInfo {
   factory DocumentInfo() = DocumentInfo._;
 
@@ -317,10 +333,10 @@
           new SourceRange(0, 0),
           new SourceRange(0, 0),
           new SourceRange(0, 0),
-          false,
           [],
           null,
           null,
+          isTemplate: false,
         );
 
   @override
@@ -333,9 +349,7 @@
   void accept(AngularAstVisitor visitor) => visitor.visitDocumentInfo(this);
 }
 
-/**
- * An element in an HTML tree.
- */
+/// An element in an HTML tree.
 class ElementInfo extends NodeInfo implements HasDirectives {
   final List<NodeInfo> childNodes = <NodeInfo>[];
 
@@ -349,13 +363,20 @@
   final TemplateAttribute templateAttribute;
   final ElementInfo parent;
 
-  List<DirectiveBinding> boundDirectives = <DirectiveBinding>[];
-  List<OutputBinding> boundStandardOutputs = <OutputBinding>[];
-  List<InputBinding> boundStandardInputs = <InputBinding>[];
+  @override
+  final boundDirectives = <DirectiveBinding>[];
+  @override
+  final boundStandardOutputs = <OutputBinding>[];
+  @override
+  final boundStandardInputs = <InputBinding>[];
+
   List<AbstractDirective> get directives =>
-      boundDirectives.map((bd) => bd.boundDirective);
+      boundDirectives.map((bd) => bd.boundDirective).toList();
+
   int childNodesMaxEnd;
   bool tagMatchedAsTransclusion = false;
+  bool tagMatchedAsDirective = false;
+  bool tagMatchedAsImmediateContentChild = false;
 
   ElementInfo(
       this.localName,
@@ -363,10 +384,10 @@
       this.closingSpan,
       this.openingNameSpan,
       this.closingNameSpan,
-      this.isTemplate,
       this.attributes,
       this.templateAttribute,
-      this.parent) {
+      this.parent,
+      {@required this.isTemplate}) {
     if (!isSynthetic) {
       childNodesMaxEnd = offset + length;
     }
@@ -379,15 +400,19 @@
       : (openingSpan.offset + openingSpan.length) ==
           (openingNameSpan.offset + openingNameSpan.length + ">".length);
 
+  @override
   int get offset => openingSpan.offset;
+
+  @override
   int get length => (closingSpan != null)
       ? closingSpan.offset + closingSpan.length - openingSpan.offset
       : ((childNodesMaxEnd != null)
           ? childNodesMaxEnd - offset
           : openingSpan.length);
 
+  @override
   List<AngularAstNode> get children {
-    var list = new List<AngularAstNode>.from(attributes);
+    final list = new List<AngularAstNode>.from(attributes);
     if (templateAttribute != null) {
       list.add(templateAttribute);
     }
@@ -396,12 +421,11 @@
 
   bool get isOrHasTemplateAttribute => isTemplate || templateAttribute != null;
 
+  @override
   void accept(AngularAstVisitor visitor) => visitor.visitElementInfo(this);
 }
 
-/**
- * A variable defined by a [AbstractDirective].
- */
+/// A variable defined by a [AbstractDirective].
 class LocalVariable extends AngularElementImpl {
   final LocalVariableElementImpl dartVariable;
 
diff --git a/analyzer_plugin/lib/plugin.dart b/analyzer_plugin/lib/plugin.dart
index 302360e..b5815b3 100644
--- a/analyzer_plugin/lib/plugin.dart
+++ b/analyzer_plugin/lib/plugin.dart
@@ -2,14 +2,10 @@
 
 import 'package:plugin/plugin.dart';
 
-/**
- * Contribute a plugin to the dart analyzer for analysis of
- * Angular 2 dart code.
- */
+/// Contribute a plugin to the dart analyzer for analysis of
+/// Angular 2 dart code.
 class AngularAnalyzerPlugin implements Plugin {
-  /**
-   * The unique identifier for this plugin.
-   */
+  /// The unique identifier for this plugin.
   static const String UNIQUE_IDENTIFIER = 'angular2.analysis.analyzer_plugin';
 
   @override
diff --git a/analyzer_plugin/lib/src/angular_driver.dart b/analyzer_plugin/lib/src/angular_driver.dart
index 97a06d2..88daaa4 100644
--- a/analyzer_plugin/lib/src/angular_driver.dart
+++ b/analyzer_plugin/lib/src/angular_driver.dart
@@ -1,18 +1,17 @@
-import 'dart:convert';
 import 'dart:async';
 import 'dart:collection';
+import 'dart:convert';
 import 'package:analysis_server/src/analysis_server.dart';
-import 'package:analyzer/context/context_root.dart';
-import 'package:analyzer/src/dart/analysis/byte_store.dart';
+import 'package:front_end/src/incremental/byte_store.dart';
 import 'package:analyzer/error/listener.dart';
 import 'package:analyzer/src/generated/sdk.dart';
 import 'package:analyzer/src/generated/source.dart';
 import 'package:analyzer/dart/element/element.dart';
-import 'package:analysis_server/plugin/protocol/protocol.dart' as protocol;
+import 'package:analysis_server/plugin/protocol/protocol_dart.dart' as protocol;
 import 'package:analysis_server/src/protocol_server.dart' as protocol;
 import 'package:analyzer/error/error.dart';
 import 'package:analyzer/src/dart/analysis/driver.dart';
-import 'package:analyzer/src/summary/api_signature.dart';
+import 'package:front_end/src/base/api_signature.dart';
 import 'package:angular_analyzer_plugin/tasks.dart';
 import 'package:angular_analyzer_plugin/src/file_tracker.dart';
 import 'package:angular_analyzer_plugin/src/from_file_prefixed_error.dart';
@@ -39,40 +38,60 @@
   final AnalysisDriverScheduler _scheduler;
   final AnalysisDriver dartDriver;
   final FileContentOverlay _contentOverlay;
-  StandardHtml standardHtml = null;
+  StandardHtml standardHtml;
+  StandardAngular standardAngular;
   SourceFactory _sourceFactory;
   final _addedFiles = new LinkedHashSet<String>();
   final _dartFiles = new LinkedHashSet<String>();
   final _changedFiles = new LinkedHashSet<String>();
-  final _requestedDartFiles = new Map<String, List<Completer>>();
-  final _requestedHtmlFiles = new Map<String, List<Completer>>();
+  final _requestedDartFiles = <String, List<Completer>>{};
+  final _requestedHtmlFiles = <String, List<Completer>>{};
   final _filesToAnalyze = new HashSet<String>();
   final _htmlFilesToAnalyze = new HashSet<String>();
   final ByteStore byteStore;
   FileTracker _fileTracker;
   final lastSignatures = <String, String>{};
+  bool _hasAngularImported = false;
 
   AngularDriver(this.server, this.dartDriver, this._scheduler, this.byteStore,
       SourceFactory sourceFactory, this._contentOverlay) {
     _sourceFactory = sourceFactory.clone();
     _scheduler.add(this);
     _fileTracker = new FileTracker(this);
+    _hasAngularImported =
+        _sourceFactory.resolveUri(null, "package:angular2/angular2.dart") !=
+            null;
   }
 
-  ContextRoot get contextRoot => dartDriver.contextRoot;
+  @override
+  ApiSignature getUnitElementHash(String path) =>
+      dartDriver.getUnitKeyByPath(path);
 
-  ApiSignature getUnitElementHash(String path) {
-    return dartDriver.getUnitKeyByPath(path);
-  }
-
+  @override
   bool get hasFilesToAnalyze =>
       _filesToAnalyze.isNotEmpty ||
       _htmlFilesToAnalyze.isNotEmpty ||
       _requestedDartFiles.isNotEmpty ||
       _requestedHtmlFiles.isNotEmpty;
 
-  bool _ownsFile(String path) {
-    return path.endsWith('.dart') || path.endsWith('.html');
+  bool _ownsFile(String path) =>
+      path.endsWith('.dart') || path.endsWith('.html');
+
+  /// This is implemented in order to satisfy the [AnalysisDriverGeneric]
+  /// interface. Ideally, we analyze these files first. For the moment, this lets
+  /// the analysis server team add this method to the interface without breaking
+  /// any code.
+  @override
+  set priorityFiles(List<String> priorityPaths) {
+    // TODO analyze these files first
+  }
+
+  List<String> get priorityFiles => [];
+
+  /// Notify the driver that the client is going to stop using it.
+  @override
+  void dispose() {
+    // TODO anything we need to do here?
   }
 
   void addFile(String path) {
@@ -88,6 +107,8 @@
   void fileChanged(String path) {
     if (_ownsFile(path)) {
       if (path.endsWith('.html')) {
+        _fileTracker.rehashHtmlContents(path);
+
         _htmlFilesToAnalyze.add(path);
         for (final path in _fileTracker.getHtmlPathsReferencingHtml(path)) {
           _htmlFilesToAnalyze.add(path);
@@ -103,7 +124,7 @@
   }
 
   Future<List<AnalysisError>> requestDartErrors(String path) {
-    var completer = new Completer<List<AnalysisError>>();
+    final completer = new Completer<List<AnalysisError>>();
     _requestedDartFiles
         .putIfAbsent(path, () => <Completer<List<AnalysisError>>>[])
         .add(completer);
@@ -112,7 +133,7 @@
   }
 
   Future<List<AnalysisError>> requestHtmlErrors(String path) {
-    var completer = new Completer<List<AnalysisError>>();
+    final completer = new Completer<List<AnalysisError>>();
     _requestedHtmlFiles
         .putIfAbsent(path, () => <Completer<List<AnalysisError>>>[])
         .add(completer);
@@ -120,6 +141,7 @@
     return completer.future;
   }
 
+  @override
   AnalysisDriverPriority get workPriority {
     if (standardHtml == null) {
       return AnalysisDriverPriority.interactive;
@@ -142,9 +164,15 @@
     return AnalysisDriverPriority.nothing;
   }
 
+  @override
   Future<Null> performWork() async {
     if (standardHtml == null) {
-      getStandardHtml();
+      getStandardHtml(); // ignore: unawaited_futures
+      return;
+    }
+
+    if (_hasAngularImported && standardAngular == null) {
+      getStandardAngular(); // ignore: unawaited_futures
       return;
     }
 
@@ -160,9 +188,10 @@
       // Note: We can't use await here, or the dart analysis becomes a future in
       // a queue that won't be completed until the scheduler schedules the dart
       // driver, which doesn't happen because its waiting for us.
+      // ignore: unawaited_futures
       resolveDart(path, onlyIfChangedSignature: false).then((result) {
-        completers
-            .forEach((completer) => completer.complete(result?.errors ?? []));
+        completers.forEach((completer) =>
+            completer.complete(result?.errors ?? <AnalysisError>[]));
       }, onError: (e) {
         completers.forEach((completer) => completer.completeError(e));
       });
@@ -190,9 +219,10 @@
       }
 
       // After whichever resolution is complete, push errors.
+      // ignore: unawaited_futures
       resolvedHtml.then((result) {
-        completers
-            .forEach((completer) => completer.complete(result?.errors ?? []));
+        completers.forEach((completer) =>
+            completer.complete(result?.errors ?? <AnalysisError>[]));
       }, onError: (e) {
         completers.forEach((completer) => completer.completeError(e));
       });
@@ -202,6 +232,7 @@
 
     if (_filesToAnalyze.isNotEmpty) {
       final path = _filesToAnalyze.first;
+      // ignore: unawaited_futures
       pushDartErrors(path);
       _filesToAnalyze.remove(path);
       return;
@@ -209,6 +240,7 @@
 
     if (_htmlFilesToAnalyze.isNotEmpty) {
       final path = _htmlFilesToAnalyze.first;
+      // ignore: unawaited_futures
       pushHtmlErrors(path);
       _htmlFilesToAnalyze.remove(path);
       return;
@@ -235,29 +267,48 @@
     return standardHtml;
   }
 
-  List<AnalysisError> deserializeFromPathErrors(
-      Source source, List<SummarizedAnalysisErrorFromPath> errors) {
-    return errors
-        .map((error) {
-          final originalError = deserializeError(source, error.originalError);
-          if (originalError == null) {
-            return null;
-          }
-          return new FromFilePrefixedError.fromPath(error.path, originalError);
-        })
-        .where((e) => e != null)
-        .toList();
+  Future<StandardAngular> getStandardAngular() async {
+    if (standardAngular == null) {
+      final source =
+          _sourceFactory.resolveUri(null, "package:angular2/angular2.dart");
+
+      if (source == null) {
+        return standardAngular;
+      }
+
+      final result = await dartDriver.getResult(source.fullName);
+
+      final namespace = result.unit.element.library.exportNamespace;
+
+      standardAngular = new StandardAngular(
+          queryList: namespace.get("QueryList"),
+          elementRef: namespace.get("ElementRef"),
+          templateRef: namespace.get("TemplateRef"));
+    }
+
+    return standardAngular;
   }
 
+  List<AnalysisError> deserializeFromPathErrors(
+          Source source, List<SummarizedAnalysisErrorFromPath> errors) =>
+      errors
+          .map((error) {
+            final originalError = deserializeError(source, error.originalError);
+            if (originalError == null) {
+              return null;
+            }
+            return new FromFilePrefixedError.fromPath(
+                error.path, originalError);
+          })
+          .where((e) => e != null)
+          .toList();
+
   List<AnalysisError> deserializeErrors(
-      Source source, List<SummarizedAnalysisError> errors) {
-    return errors
-        .map((error) {
-          return deserializeError(source, error);
-        })
-        .where((e) => e != null)
-        .toList();
-  }
+          Source source, List<SummarizedAnalysisError> errors) =>
+      errors
+          .map((error) => deserializeError(source, error))
+          .where((e) => e != null)
+          .toList();
 
   AnalysisError deserializeError(Source source, SummarizedAnalysisError error) {
     final errorName = error.errorCode;
@@ -272,27 +323,30 @@
 
   String getHtmlKey(String htmlPath) {
     final key = _fileTracker.getHtmlSignature(htmlPath);
-    return key.toHex() + '.ngresolved';
+    return '${key.toHex()}.ngresolved';
   }
 
+  @override
   ApiSignature getContentHash(String path) {
     final key = new ApiSignature();
-    List<int> contentBytes = UTF8.encode(getFileContent(path));
+    final contentBytes = UTF8.encode(getFileContent(path));
     key.addBytes(md5.convert(contentBytes).bytes);
     return key;
   }
 
-  String getFileContent(String path) {
-    return _contentOverlay[path] ??
-        ((source) =>
-            source.exists() ? source.contents.data : "")(getSource(path));
-  }
+  String getFileContent(String path) =>
+      _contentOverlay[path] ??
+      ((source) =>
+          source.exists() ? source.contents.data : "")(getSource(path));
 
-  Future<DirectivesResult> resolveHtml(String htmlPath) async {
+  Future<DirectivesResult> resolveHtml(
+    String htmlPath, {
+    bool ignoreCache: false,
+  }) async {
     final key = getHtmlKey(htmlPath);
-    final htmlSource = _sourceFactory.forUri("file:" + htmlPath);
-    final List<int> bytes = byteStore.get(key);
-    if (bytes != null) {
+    final htmlSource = _sourceFactory.forUri('file:$htmlPath');
+    final bytes = byteStore.get(key);
+    if (!ignoreCache && bytes != null) {
       final summary = new LinkedHtmlSummary.fromBuffer(bytes);
       final errors = new List<AnalysisError>.from(
           deserializeErrors(htmlSource, summary.errors))
@@ -310,30 +364,65 @@
     }
 
     final summary = new LinkedHtmlSummaryBuilder()
-      ..errors = summarizeErrors(
-          result.errors.where((error) => error is! FromFilePrefixedError))
+      ..errors = summarizeErrors(result.errors
+          .where((error) => error is! FromFilePrefixedError)
+          .toList())
       ..errorsFromPath = result.errors
           .where((error) => error is FromFilePrefixedError)
           .map((error) => new SummarizedAnalysisErrorFromPathBuilder()
             ..path = (error as FromFilePrefixedError).fromSourcePath
             ..originalError =
-                summarizeError((error as FromFilePrefixedError).originalError));
-    final List<int> newBytes = summary.toBuffer();
+                summarizeError((error as FromFilePrefixedError).originalError))
+          .toList();
+    final newBytes = summary.toBuffer();
     byteStore.put(key, newBytes);
 
     return result;
   }
 
+  Future<List<Template>> getTemplatesForFile(String filePath) async {
+    final templates = <Template>[];
+    final isDartFile = filePath.endsWith('.dart');
+    if (!isDartFile && !filePath.endsWith('.html')) {
+      return templates;
+    }
+    final directiveResults = isDartFile
+        ? await resolveDart(
+            filePath,
+            withDirectives: true,
+            onlyIfChangedSignature: false,
+          )
+        : await resolveHtml(filePath, ignoreCache: true);
+    final directives = directiveResults.directives;
+    if (directives == null) {
+      return templates;
+    }
+    for (var directive in directives) {
+      if (directive is Component) {
+        final view = directive.view;
+        final match = isDartFile
+            ? view.source.toString() == filePath
+            : view.templateUriSource?.fullName == filePath;
+        if (match) {
+          templates.add(view.template);
+        }
+      }
+    }
+    return templates;
+  }
+
   Future<DirectivesResult> resolveHtmlFrom(
       String htmlPath, String dartPath) async {
     final result = await getDirectives(dartPath);
     final directives = result.directives;
     final unit = (await dartDriver.getUnitElement(dartPath)).element;
-    final htmlSource = _sourceFactory.forUri("file:" + htmlPath);
+    final htmlSource = _sourceFactory.forUri('file:$htmlPath');
 
-    if (unit == null) return null;
+    if (unit == null) {
+      return null;
+    }
     final context = unit.context;
-    final dartSource = _sourceFactory.forUri("file:" + dartPath);
+    final dartSource = _sourceFactory.forUri('file:$dartPath');
     final htmlContent = getFileContent(htmlPath);
     final standardHtml = await getStandardHtml();
 
@@ -342,7 +431,8 @@
     final linkErrorListener = new IgnoringErrorListener();
     final linkErrorReporter = new ErrorReporter(linkErrorListener, dartSource);
 
-    final linker = new ChildDirectiveLinker(this, linkErrorReporter);
+    final linker = new ChildDirectiveLinker(
+        this, await getStandardAngular(), linkErrorReporter);
     await linker.linkDirectives(directives, unit.library);
     final attrValidator = new AttributeAnnotationValidator(linkErrorReporter);
     directives.forEach(attrValidator.validate);
@@ -355,11 +445,12 @@
           final errorReporter = new ErrorReporter(tplErrorListener, dartSource);
           final template = new Template(view);
           view.template = template;
-          final tplParser = new TemplateParser();
 
-          tplParser.parse(htmlContent, htmlSource);
+          final tplParser = new TemplateParser()
+            ..parse(htmlContent, htmlSource);
+
           final document = tplParser.rawAst;
-          final EmbeddedDartParser parser = new EmbeddedDartParser(
+          final parser = new EmbeddedDartParser(
               htmlSource, tplErrorListener, errorReporter);
 
           template.ast =
@@ -367,13 +458,14 @@
                   .convertFromAstList(tplParser.rawAst);
           template.ast.accept(new NgContentRecorder(directive, errorReporter));
           setIgnoredErrors(template, document);
-          final resolver = new TemplateResolver(
-              context.typeProvider,
-              standardHtml.components.values,
-              standardHtml.events,
-              standardHtml.attributes,
-              tplErrorListener);
-          resolver.resolve(template);
+          new TemplateResolver(
+                  context.typeProvider,
+                  standardHtml.components.values.toList(),
+                  standardHtml.events,
+                  standardHtml.attributes,
+                  await getStandardAngular(),
+                  tplErrorListener)
+              .resolve(template);
 
           bool rightErrorType(AnalysisError e) =>
               !view.template.ignoredErrors.contains(e.errorCode.name);
@@ -399,9 +491,10 @@
     return new DirectivesResult(directives, errors);
   }
 
+  @override
   Future<List<NgContent>> getHtmlNgContent(String path) async {
-    final key = getContentHash(path).toHex() + '.ngunlinked';
-    final List<int> bytes = byteStore.get(key);
+    final key = '${getContentHash(path).toHex()}.ngunlinked';
+    final bytes = byteStore.get(key);
     final source = getSource(path);
     if (bytes != null) {
       return new DirectiveLinker(this).deserializeNgContents(
@@ -412,10 +505,9 @@
     final tplErrorListener = new RecordingErrorListener();
     final errorReporter = new ErrorReporter(tplErrorListener, source);
 
-    final tplParser = new TemplateParser();
+    final tplParser = new TemplateParser()..parse(htmlContent, source);
 
-    tplParser.parse(htmlContent, source);
-    final EmbeddedDartParser parser =
+    final parser =
         new EmbeddedDartParser(source, tplErrorListener, errorReporter);
 
     final ast = new HtmlTreeConverter(parser, source, tplErrorListener)
@@ -425,7 +517,7 @@
 
     final summary = new UnlinkedHtmlSummaryBuilder()
       ..ngContents = serializeNgContents(contents);
-    final List<int> newBytes = summary.toBuffer();
+    final newBytes = summary.toBuffer();
     byteStore.put(key, newBytes);
 
     return contents;
@@ -446,7 +538,9 @@
 
   Future pushDartErrors(String path) async {
     final result = await resolveDart(path);
-    if (result == null) return;
+    if (result == null) {
+      return;
+    }
     final errors = result.errors;
     final lineInfo = new LineInfo.fromContent(getFileContent(path));
     final serverErrors = protocol.doAnalysisError_listFromEngine(
@@ -467,7 +561,7 @@
       return null;
     }
 
-    final key = baseKey + '.ngresolved';
+    final key = '$baseKey.ngresolved';
 
     if (lastSignatures[path] == key && onlyIfChangedSignature) {
       return null;
@@ -476,7 +570,7 @@
     lastSignatures[path] = key;
 
     if (!withDirectives) {
-      final List<int> bytes = byteStore.get(key);
+      final bytes = byteStore.get(key);
       if (bytes != null) {
         final summary = new LinkedDartSummary.fromBuffer(bytes);
 
@@ -484,9 +578,10 @@
           _htmlFilesToAnalyze.add(htmlPath);
         }
 
-        _fileTracker.setDartHasTemplate(path, summary.hasDartTemplates);
-        _fileTracker.setDartHtmlTemplates(path, summary.referencedHtmlFiles);
-        _fileTracker.setDartImports(path, summary.referencedDartFiles);
+        _fileTracker
+          ..setDartHasTemplate(path, summary.hasDartTemplates)
+          ..setDartHtmlTemplates(path, summary.referencedHtmlFiles)
+          ..setDartImports(path, summary.referencedDartFiles);
 
         return new DirectivesResult(
             [], deserializeErrors(getSource(path), summary.errors));
@@ -496,7 +591,9 @@
     final result = await getDirectives(path);
     final directives = result.directives;
     final unit = (await dartDriver.getUnitElement(path)).element;
-    if (unit == null) return null;
+    if (unit == null) {
+      return null;
+    }
     final context = unit.context;
     final source = unit.source;
 
@@ -506,16 +603,17 @@
     final linkErrorListener = new RecordingErrorListener();
     final linkErrorReporter = new ErrorReporter(linkErrorListener, source);
 
-    final linker = new ChildDirectiveLinker(this, linkErrorReporter);
+    final linker = new ChildDirectiveLinker(
+        this, await getStandardAngular(), linkErrorReporter);
     await linker.linkDirectives(directives, unit.library);
     final attrValidator = new AttributeAnnotationValidator(linkErrorReporter);
     directives.forEach(attrValidator.validate);
     errors.addAll(linkErrorListener.errors);
 
-    final List<String> htmlViews = [];
-    final List<String> usesDart = [];
+    final htmlViews = <String>[];
+    final usesDart = <String>[];
 
-    bool hasDartTemplate = false;
+    var hasDartTemplate = false;
     for (final directive in directives) {
       if (directive is Component) {
         final view = directive.view;
@@ -525,86 +623,87 @@
           final errorReporter = new ErrorReporter(tplErrorListener, source);
           final template = new Template(view);
           view.template = template;
-          final tplParser = new TemplateParser();
 
-          tplParser.parse(view.templateText, source,
-              offset: view.templateOffset);
+          final tplParser = new TemplateParser()
+            ..parse(view.templateText, source, offset: view.templateOffset);
+
           final document = tplParser.rawAst;
-          final EmbeddedDartParser parser =
+          final parser =
               new EmbeddedDartParser(source, tplErrorListener, errorReporter);
 
           template.ast = new HtmlTreeConverter(parser, source, tplErrorListener)
               .convertFromAstList(tplParser.rawAst);
           template.ast.accept(new NgContentRecorder(directive, errorReporter));
           setIgnoredErrors(template, document);
-          final resolver = new TemplateResolver(
-              context.typeProvider,
-              standardHtml.components.values,
-              standardHtml.events,
-              standardHtml.attributes,
-              tplErrorListener);
-          resolver.resolve(template);
-          errors.addAll(tplParser.parseErrors.where(
-              (e) => !view.template.ignoredErrors.contains(e.errorCode.name)));
-          errors.addAll(tplErrorListener.errors.where(
-              (e) => !view.template.ignoredErrors.contains(e.errorCode.name)));
+          new TemplateResolver(
+                  context.typeProvider,
+                  standardHtml.components.values.toList(),
+                  standardHtml.events,
+                  standardHtml.attributes,
+                  await getStandardAngular(),
+                  tplErrorListener)
+              .resolve(template);
+          errors
+            ..addAll(tplParser.parseErrors.where(
+                (e) => !view.template.ignoredErrors.contains(e.errorCode.name)))
+            ..addAll(tplErrorListener.errors.where((e) =>
+                !view.template.ignoredErrors.contains(e.errorCode.name)));
         } else if (view?.templateUriSource != null) {
           _htmlFilesToAnalyze.add(view.templateUriSource.fullName);
           htmlViews.add(view.templateUriSource.fullName);
         }
 
-        for (AbstractDirective subDirective in (view?.directives ?? [])) {
+        for (final subDirective in (view?.directives ?? [])) {
           usesDart.add(subDirective.classElement.source.fullName);
         }
       }
     }
 
-    _fileTracker.setDartHasTemplate(path, hasDartTemplate);
-    _fileTracker.setDartHtmlTemplates(path, htmlViews);
-    _fileTracker.setDartImports(path, usesDart);
+    _fileTracker
+      ..setDartHasTemplate(path, hasDartTemplate)
+      ..setDartHtmlTemplates(path, htmlViews)
+      ..setDartImports(path, usesDart);
 
     final summary = new LinkedDartSummaryBuilder()
       ..errors = summarizeErrors(errors)
       ..referencedHtmlFiles = htmlViews
       ..referencedDartFiles = usesDart
       ..hasDartTemplates = hasDartTemplate;
-    final List<int> newBytes = summary.toBuffer();
+    final newBytes = summary.toBuffer();
     byteStore.put(key, newBytes);
     return new DirectivesResult(directives, errors);
   }
 
-  List<SummarizedAnalysisError> summarizeErrors(List<AnalysisError> errors) {
-    return errors.map((error) => summarizeError(error)).toList();
-  }
+  List<SummarizedAnalysisError> summarizeErrors(List<AnalysisError> errors) =>
+      errors.map(summarizeError).toList();
 
-  SummarizedAnalysisError summarizeError(AnalysisError error) {
-    return new SummarizedAnalysisErrorBuilder(
-        offset: error.offset,
-        length: error.length,
-        errorCode: error.errorCode.uniqueName,
-        message: error.message,
-        correction: error.correction);
-  }
+  SummarizedAnalysisError summarizeError(AnalysisError error) =>
+      new SummarizedAnalysisErrorBuilder(
+          offset: error.offset,
+          length: error.length,
+          errorCode: error.errorCode.uniqueName,
+          message: error.message,
+          correction: error.correction);
 
+  @override
   Source getSource(String path) =>
-      _sourceFactory.resolveUri(null, 'file:' + path);
+      _sourceFactory.resolveUri(null, 'file:$path');
 
-  Future<CompilationUnitElement> getUnit(String path) async {
-    return (await dartDriver.getUnitElement(path)).element;
-  }
+  @override
+  Future<CompilationUnitElement> getUnit(String path) async =>
+      (await dartDriver.getUnitElement(path)).element;
 
   Future<List<AbstractDirective>> resynthesizeDirectives(
-      UnlinkedDartSummary unlinked, String path) async {
-    return new DirectiveLinker(this).resynthesizeDirectives(unlinked, path);
-  }
+          UnlinkedDartSummary unlinked, String path) async =>
+      new DirectiveLinker(this).resynthesizeDirectives(unlinked, path);
 
-  Future<List<AbstractDirective>> getUnlinkedDirectives(path) async {
-    return (await getDirectives(path)).directives;
-  }
+  @override
+  Future<List<AbstractDirective>> getUnlinkedDirectives(path) async =>
+      (await getDirectives(path)).directives;
 
-  Future<DirectivesResult> getDirectives(path) async {
-    final key = getContentHash(path).toHex() + '.ngunlinked';
-    final List<int> bytes = byteStore.get(key);
+  Future<DirectivesResult> getDirectives(String path) async {
+    final key = '${getContentHash(path).toHex()}.ngunlinked';
+    final bytes = byteStore.get(key);
     if (bytes != null) {
       final summary = new UnlinkedDartSummary.fromBuffer(bytes);
       return new DirectivesResult(await resynthesizeDirectives(summary, path),
@@ -624,8 +723,8 @@
     final directives =
         new List<AbstractDirective>.from(extractor.getDirectives());
 
-    final viewExtractor = new ViewExtractor(ast, directives, context, source);
-    viewExtractor.getViews();
+    final viewExtractor = new ViewExtractor(ast, directives, context, source)
+      ..getViews();
 
     final tplErrorListener = new RecordingErrorListener();
     final errorReporter = new ErrorReporter(tplErrorListener, source);
@@ -637,11 +736,11 @@
         if ((view.templateText ?? "") != "") {
           final template = new Template(view);
           view.template = template;
-          final tplParser = new TemplateParser();
 
-          tplParser.parse(view.templateText, source,
-              offset: view.templateOffset);
-          final EmbeddedDartParser parser =
+          final tplParser = new TemplateParser()
+            ..parse(view.templateText, source, offset: view.templateOffset);
+
+          final parser =
               new EmbeddedDartParser(source, tplErrorListener, errorReporter);
 
           template.ast = new HtmlTreeConverter(parser, source, tplErrorListener)
@@ -651,11 +750,11 @@
       }
     }
 
-    final errors = new List<AnalysisError>.from(extractor.errorListener.errors);
-    errors.addAll(viewExtractor.errorListener.errors);
+    final errors = new List<AnalysisError>.from(extractor.errorListener.errors)
+      ..addAll(viewExtractor.errorListener.errors);
     final result = new DirectivesResult(directives, errors);
     final summary = serializeDartResult(result);
-    final List<int> newBytes = summary.toBuffer();
+    final newBytes = summary.toBuffer();
     byteStore.put(key, newBytes);
     return result;
   }
@@ -679,6 +778,8 @@
       final exportAsOffset = directive?.exportAs?.nameOffset;
       final inputs = <SummarizedBindableBuilder>[];
       final outputs = <SummarizedBindableBuilder>[];
+      final contentChildFields = <SummarizedContentChildFieldBuilder>[];
+      final contentChildrenFields = <SummarizedContentChildFieldBuilder>[];
       for (final input in directive.inputs) {
         final name = input.name;
         final nameOffset = input.nameOffset;
@@ -701,6 +802,22 @@
           ..propName = propName
           ..propNameOffset = propNameOffset);
       }
+      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;
@@ -741,22 +858,23 @@
         ..ngContents = ngContents
         ..inputs = inputs
         ..outputs = outputs
-        ..subdirectives = dirUseSums);
+        ..subdirectives = dirUseSums
+        ..contentChildFields = contentChildFields
+        ..contentChildrenFields = contentChildrenFields);
     }
 
     return dirSums;
   }
 
   List<SummarizedNgContentBuilder> serializeNgContents(
-      List<NgContent> ngContents) {
-    return ngContents
-        .map((ngContent) => new SummarizedNgContentBuilder()
-          ..selectorStr = ngContent.selector?.originalString
-          ..selectorOffset = ngContent.selector?.offset
-          ..offset = ngContent.offset
-          ..length = ngContent.length)
-        .toList();
-  }
+          List<NgContent> ngContents) =>
+      ngContents
+          .map((ngContent) => new SummarizedNgContentBuilder()
+            ..selectorStr = ngContent.selector?.originalString
+            ..selectorOffset = ngContent.selector?.offset
+            ..offset = ngContent.offset
+            ..length = ngContent.length)
+          .toList();
 }
 
 class DirectivesResult {
diff --git a/analyzer_plugin/lib/src/converter.dart b/analyzer_plugin/lib/src/converter.dart
index 19bf3d5..28ee210 100644
--- a/analyzer_plugin/lib/src/converter.dart
+++ b/analyzer_plugin/lib/src/converter.dart
@@ -13,8 +13,9 @@
 import 'package:angular_analyzer_plugin/src/ignoring_error_listener.dart';
 import 'package:angular_analyzer_plugin/src/strings.dart';
 import 'package:angular_analyzer_plugin/tasks.dart';
-
 import 'package:meta/meta.dart';
+import 'package:tuple/tuple.dart';
+import 'package:source_span/source_span.dart';
 
 class HtmlTreeConverter {
   final EmbeddedDartParser dartParser;
@@ -33,12 +34,12 @@
   HtmlTreeConverter(this.dartParser, this.templateSource, this.errorListener);
 
   DocumentInfo convertFromAstList(List<StandaloneTemplateAst> asts) {
-    DocumentInfo root = new DocumentInfo();
+    final root = new DocumentInfo();
     if (asts.isEmpty) {
       root.childNodes.add(new TextInfo(0, '', root, []));
     }
-    for (StandaloneTemplateAst node in asts) {
-      var convertedNode = convert(node, parent: root);
+    for (final node in asts) {
+      final convertedNode = convert(node, parent: root);
       if (convertedNode != null) {
         root.childNodes.add(convertedNode);
       }
@@ -51,16 +52,15 @@
     @required ElementInfo parent,
   }) {
     if (node is ElementAst) {
-      String localName = node.name;
-      List<AttributeInfo> attributes = _convertAttributes(
+      final localName = node.name;
+      final attributes = _convertAttributes(
         attributes: node.attributes,
         bananas: node.bananas,
         events: node.events,
         properties: node.properties,
         references: node.references,
         stars: node.stars,
-      );
-      attributes.sort((a, b) => a.offset.compareTo(b.offset));
+      )..sort((a, b) => a.offset.compareTo(b.offset));
       final closeComponent = node.closeComplement;
       SourceRange openingSpan;
       SourceRange openingNameSpan;
@@ -86,22 +86,23 @@
             new SourceRange(closingSpan.offset + '</'.length, localName.length);
       }
 
-      ElementInfo element = new ElementInfo(
-          localName,
-          openingSpan,
-          closingSpan,
-          openingNameSpan,
-          closingNameSpan,
-          false,
-          attributes,
-          findTemplateAttribute(attributes),
-          parent);
+      final element = new ElementInfo(
+        localName,
+        openingSpan,
+        closingSpan,
+        openingNameSpan,
+        closingNameSpan,
+        attributes,
+        findTemplateAttribute(attributes),
+        parent,
+        isTemplate: false,
+      );
 
-      for (AttributeInfo attribute in attributes) {
+      for (final attribute in attributes) {
         attribute.parent = element;
       }
 
-      List<NodeInfo> children = _convertChildren(node, element);
+      final children = _convertChildren(node, element);
       element.childNodes.addAll(children);
 
       if (!element.isSynthetic &&
@@ -132,8 +133,8 @@
             node.beginToken.offset, node.endToken.end - node.beginToken.offset);
         openingNameSpan =
             new SourceRange(openingSpan.offset + '<'.length, localName.length);
-        var pnode = node as ParsedEmbeddedContentAst;
-        var valueToken = pnode.selectorValueToken;
+        final pnode = node as ParsedEmbeddedContentAst;
+        final valueToken = pnode.selectorValueToken;
         if (pnode.selectToken != null) {
           attributes.add(new TextAttribute(
             'select',
@@ -155,18 +156,27 @@
             new SourceRange(closingSpan.offset + '</'.length, localName.length);
       }
 
-      var ngContent = new ElementInfo(localName, openingSpan, closingSpan,
-          openingNameSpan, closingNameSpan, false, attributes, null, parent);
+      final ngContent = new ElementInfo(
+        localName,
+        openingSpan,
+        closingSpan,
+        openingNameSpan,
+        closingNameSpan,
+        attributes,
+        null,
+        parent,
+        isTemplate: false,
+      );
 
-      for (AttributeInfo attribute in attributes) {
+      for (final attribute in attributes) {
         attribute.parent = ngContent;
       }
 
       return ngContent;
     }
     if (node is EmbeddedTemplateAst) {
-      String localName = 'template';
-      List<AttributeInfo> attributes = _convertAttributes(
+      final localName = 'template';
+      final attributes = _convertAttributes(
         attributes: node.attributes,
         events: node.events,
         properties: node.properties,
@@ -200,22 +210,23 @@
         }
       }
 
-      ElementInfo element = new ElementInfo(
-          localName,
-          openingSpan,
-          closingSpan,
-          openingNameSpan,
-          closingNameSpan,
-          true,
-          attributes,
-          findTemplateAttribute(attributes),
-          parent);
+      final element = new ElementInfo(
+        localName,
+        openingSpan,
+        closingSpan,
+        openingNameSpan,
+        closingNameSpan,
+        attributes,
+        findTemplateAttribute(attributes),
+        parent,
+        isTemplate: true,
+      );
 
-      for (AttributeInfo attribute in attributes) {
+      for (final attribute in attributes) {
         attribute.parent = element;
       }
 
-      List<NodeInfo> children = _convertChildren(node, element);
+      final children = _convertChildren(node, element);
       element.childNodes.addAll(children);
 
       if (!element.isSynthetic &&
@@ -230,14 +241,14 @@
       return element;
     }
     if (node is TextAst) {
-      int offset = node.sourceSpan.start.offset;
-      String text = node.value;
+      final offset = node.sourceSpan.start.offset;
+      final text = node.value;
       return new TextInfo(
           offset, text, parent, dartParser.findMustaches(text, offset));
     }
     if (node is InterpolationAst) {
-      int offset = node.sourceSpan.start.offset;
-      String text = '{{' + node.value + '}}';
+      final offset = node.sourceSpan.start.offset;
+      final text = '{{${node.value}}}';
       return new TextInfo(
           offset, text, parent, dartParser.findMustaches(text, offset));
     }
@@ -252,9 +263,9 @@
     List<ParsedReferenceAst> references: const [],
     List<ParsedStarAst> stars: const [],
   }) {
-    List<AttributeInfo> returnAttributes = <AttributeInfo>[];
+    final returnAttributes = <AttributeInfo>[];
 
-    for (ParsedAttributeAst attribute in attributes) {
+    for (final attribute in attributes) {
       if (attribute.name == 'template') {
         returnAttributes.add(_convertTemplateAttribute(attribute));
       } else {
@@ -280,7 +291,7 @@
         .map(_convertExpressionBoundAttribute)
         .forEach(returnAttributes.add);
 
-    for (ParsedReferenceAst reference in references) {
+    for (final reference in references) {
       String value;
       int valueOffset;
       if (reference.valueToken != null) {
@@ -288,7 +299,7 @@
         valueOffset = reference.valueToken.innerValue.offset;
       }
       returnAttributes.add(new TextAttribute(
-          reference.prefixToken.lexeme + reference.nameToken.lexeme,
+          '${reference.prefixToken.lexeme}${reference.nameToken.lexeme}',
           reference.prefixToken.offset,
           value,
           valueOffset,
@@ -302,6 +313,7 @@
 
   TemplateAttribute _convertTemplateAttribute(TemplateAst ast) {
     String name;
+    String prefix;
     int nameOffset;
 
     String value;
@@ -316,7 +328,7 @@
       value = ast.value;
       valueOffset = ast.valueOffset;
 
-      origName = ast.prefixToken.lexeme + ast.nameToken.lexeme;
+      origName = '${ast.prefixToken.lexeme}${ast.nameToken.lexeme}';
       origNameOffset = ast.prefixToken.offset;
 
       name = ast.nameToken.lexeme;
@@ -324,15 +336,17 @@
 
       String fullAstName;
       if (value != null) {
-        fullAstName = ast.name +
-            (' ' * (ast.valueToken.innerValue.offset - ast.nameToken.end)) +
-            (value ?? '');
+        final whitespacePad =
+            ' ' * (ast.valueToken.innerValue.offset - ast.nameToken.end);
+        fullAstName = "${ast.name}$whitespacePad${value ?? ''}";
       } else {
-        fullAstName = ast.name + ' ';
+        fullAstName = '${ast.name} ';
       }
 
-      virtualAttributes =
+      final tuple =
           dartParser.parseTemplateVirtualAttributes(nameOffset, fullAstName);
+      virtualAttributes = tuple.item2;
+      prefix = tuple.item1;
     }
     if (ast is ParsedAttributeAst) {
       value = ast.value;
@@ -349,20 +363,15 @@
             origName.length, AngularWarningCode.EMPTY_BINDING, [origName]));
       } else {
         virtualAttributes =
-            dartParser.parseTemplateVirtualAttributes(valueOffset, value);
+            dartParser.parseTemplateVirtualAttributes(valueOffset, value).item2;
       }
     }
 
-    TemplateAttribute templateAttribute = new TemplateAttribute(
-        name,
-        nameOffset,
-        value,
-        valueOffset,
-        origName,
-        origNameOffset,
-        virtualAttributes);
+    final templateAttribute = new TemplateAttribute(name, nameOffset, value,
+        valueOffset, origName, origNameOffset, virtualAttributes,
+        prefix: prefix);
 
-    for (AttributeInfo virtualAttribute in virtualAttributes) {
+    for (final virtualAttribute in virtualAttributes) {
       virtualAttribute.parent = templateAttribute;
     }
 
@@ -371,26 +380,26 @@
 
   StatementsBoundAttribute _convertStatementsBoundAttribute(
       ParsedEventAst ast) {
-    var prefixComponent =
+    final prefixComponent =
         (ast.prefixToken.errorSynthetic ? '' : ast.prefixToken.lexeme);
-    var suffixComponent =
+    final suffixComponent =
         ((ast.suffixToken == null) || ast.suffixToken.errorSynthetic)
             ? ''
             : ast.suffixToken.lexeme;
-    var origName = prefixComponent + ast.name + suffixComponent;
-    var origNameOffset = ast.prefixToken.offset;
+    final origName = '$prefixComponent${ast.name}$suffixComponent';
+    final origNameOffset = ast.prefixToken.offset;
 
-    var value = ast.value;
+    final value = ast.value;
     if ((value == null || value.isEmpty) &&
         !ast.prefixToken.errorSynthetic &&
         !ast.suffixToken.errorSynthetic) {
       errorListener.onError(new AnalysisError(templateSource, origNameOffset,
           origName.length, AngularWarningCode.EMPTY_BINDING, [ast.name]));
     }
-    var valueOffset = ast.valueOffset;
+    final valueOffset = ast.valueOffset;
 
-    var propName = ast.nameToken.lexeme;
-    var propNameOffset = ast.nameToken.offset;
+    final propName = ast.nameToken.lexeme;
+    final propNameOffset = ast.nameToken.offset;
 
     return new StatementsBoundAttribute(
         propName,
@@ -404,24 +413,28 @@
 
   ExpressionBoundAttribute _convertExpressionBoundAttribute(TemplateAst ast) {
     // Default starting.
-    ExpressionBoundType bound = ExpressionBoundType.input;
+    var bound = ExpressionBoundType.input;
 
-    var parsed = ast as ParsedDecoratorAst;
-    var origName =
-        (parsed.prefixToken.errorSynthetic ? '' : parsed.prefixToken.lexeme) +
-            parsed.nameToken.lexeme +
-            ((parsed.suffixToken == null || parsed.suffixToken.errorSynthetic)
-                ? ''
-                : parsed.suffixToken.lexeme);
-    var origNameOffset = parsed.prefixToken.offset;
+    final parsed = ast as ParsedDecoratorAst;
+    String origName;
+    {
+      final _prefix =
+          parsed.prefixToken.errorSynthetic ? '' : parsed.prefixToken.lexeme;
+      final _suffix =
+          (parsed.suffixToken == null || parsed.suffixToken.errorSynthetic)
+              ? ''
+              : parsed.suffixToken.lexeme;
+      origName = '$_prefix${parsed.nameToken.lexeme}$_suffix';
+    }
+    final origNameOffset = parsed.prefixToken.offset;
 
     var propName = parsed.nameToken.lexeme;
     var propNameOffset = parsed.nameToken.offset;
 
     if (ast is ParsedPropertyAst) {
-      var name = ast.name;
+      final name = ast.name;
       if (ast.postfix != null) {
-        bool replacePropName = false;
+        var replacePropName = false;
         if (name == 'class') {
           bound = ExpressionBoundType.clazz;
           replacePropName = true;
@@ -433,7 +446,8 @@
           replacePropName = true;
         }
         if (replacePropName) {
-          propName = ast.postfix + (ast.unit == null ? '' : '.${ast.unit}');
+          final _unitName = ast.unit == null ? '' : '.${ast.unit}';
+          propName = '${ast.postfix}$_unitName';
           propNameOffset = parsed.nameToken.offset + name.length + '.'.length;
         }
       }
@@ -441,7 +455,7 @@
       bound = ExpressionBoundType.twoWay;
     }
 
-    var value = parsed.valueToken?.innerValue?.lexeme;
+    final value = parsed.valueToken?.innerValue?.lexeme;
     if ((value == null || value.isEmpty) &&
         !parsed.prefixToken.errorSynthetic &&
         !parsed.suffixToken.errorSynthetic) {
@@ -453,7 +467,7 @@
         [origName],
       ));
     }
-    var valueOffset = parsed.valueToken?.innerValue?.offset;
+    final valueOffset = parsed.valueToken?.innerValue?.offset;
 
     propName = attrToPropMap[propName] ?? propName;
 
@@ -464,15 +478,16 @@
         valueOffset,
         origName,
         origNameOffset,
-        dartParser.parseDartExpression(valueOffset, value, true),
+        dartParser.parseDartExpression(valueOffset, value,
+            detectTrailing: true),
         bound);
   }
 
   List<NodeInfo> _convertChildren(
       StandaloneTemplateAst node, ElementInfo parent) {
-    List<NodeInfo> children = <NodeInfo>[];
-    for (StandaloneTemplateAst child in node.childNodes) {
-      NodeInfo childNode = convert(child, parent: parent);
+    final children = <NodeInfo>[];
+    for (final child in node.childNodes) {
+      final childNode = convert(child, parent: parent);
       if (childNode != null) {
         children.add(childNode);
         if (childNode is ElementInfo) {
@@ -486,7 +501,7 @@
   }
 
   TemplateAttribute findTemplateAttribute(List<AttributeInfo> attributes) {
-    for (AttributeInfo attribute in attributes) {
+    for (final attribute in attributes) {
       if (attribute is TemplateAttribute) {
         return attribute;
       }
@@ -494,9 +509,8 @@
     return null;
   }
 
-  SourceRange _toSourceRange(int offset, int length) {
-    return new SourceRange(offset, length);
-  }
+  SourceRange _toSourceRange(int offset, int length) =>
+      new SourceRange(offset, length);
 }
 
 class EmbeddedDartParser {
@@ -507,15 +521,14 @@
   EmbeddedDartParser(
       this.templateSource, this.errorListener, this.errorReporter);
 
-  /**
-   * Parse the given Dart [code] that starts at [offset].
-   */
-  Expression parseDartExpression(int offset, String code, bool detectTrailing) {
+  /// Parse the given Dart [code] that starts at [offset].
+  Expression parseDartExpression(int offset, String code,
+      {@required bool detectTrailing}) {
     if (code == null) {
       return null;
     }
 
-    final Token token = _scanDartCode(offset, code);
+    final token = _scanDartCode(offset, code);
     Expression expression;
 
     // suppress errors for this. But still parse it so we can analyze it and stuff
@@ -527,7 +540,7 @@
     }
 
     if (detectTrailing && expression.endToken.next.type != TokenType.EOF) {
-      int trailingExpressionBegin = expression.endToken.next.offset;
+      final trailingExpressionBegin = expression.endToken.next.offset;
       errorListener.onError(new AnalysisError(
           templateSource,
           trailingExpressionBegin,
@@ -538,20 +551,21 @@
     return expression;
   }
 
-  /**
-   * Parse the given Dart [code] that starts ot [offset].
-   * Also removes and reports dangling closing brackets.
-   */
+  /// Parse the given Dart [code] that starts ot [offset].
+  /// Also removes and reports dangling closing brackets.
   List<Statement> parseDartStatements(int offset, String code) {
-    List<Statement> allStatements = new List<Statement>();
+    final allStatements = <Statement>[];
     if (code == null) {
       return allStatements;
     }
+
+    // ignore: parameter_assignments, prefer_interpolation_to_compose_strings
     code = code + ';';
-    Token token = _scanDartCode(offset, code);
+
+    var token = _scanDartCode(offset, code);
 
     while (token.type != TokenType.EOF) {
-      List<Statement> currentStatements = _parseDartStatementsAtToken(token);
+      final currentStatements = _parseDartStatementsAtToken(token);
 
       if (currentStatements.isNotEmpty) {
         allStatements.addAll(currentStatements);
@@ -561,11 +575,11 @@
         break;
       }
       if (token.type == TokenType.CLOSE_CURLY_BRACKET) {
-        int startCloseBracket = token.offset;
+        final startCloseBracket = token.offset;
         while (token.type == TokenType.CLOSE_CURLY_BRACKET) {
           token = token.next;
         }
-        int length = token.offset - startCloseBracket;
+        final length = token.offset - startCloseBracket;
         errorListener.onError(new AnalysisError(
             templateSource,
             startCloseBracket,
@@ -581,52 +595,46 @@
     return allStatements;
   }
 
-  /**
-   * Parse the Dart expression starting at the given [token].
-   */
+  /// Parse the Dart expression starting at the given [token].
   Expression _parseDartExpressionAtToken(Token token,
       {AnalysisErrorListener errorListener}) {
     errorListener ??= this.errorListener;
-    Parser parser = new NgExprParser(templateSource, errorListener);
+    final parser = new NgExprParser(templateSource, errorListener);
     return parser.parseExpression(token);
   }
 
-  /**
-   * Parse the Dart statement starting at the given [token].
-   */
+  /// Parse the Dart statement starting at the given [token].
   List<Statement> _parseDartStatementsAtToken(Token token) {
-    Parser parser = new Parser(templateSource, errorListener);
+    final parser = new Parser(templateSource, errorListener);
     return parser.parseStatements(token);
   }
 
-  /**
-   * Scan the given Dart [code] that starts at [offset].
-   */
+  /// Scan the given Dart [code] that starts at [offset].
   Token _scanDartCode(int offset, String code) {
-    String text = ' ' * offset + code;
-    CharSequenceReader reader = new CharSequenceReader(text);
-    Scanner scanner = new Scanner(templateSource, reader, errorListener);
+    // ignore: prefer_interpolation_to_compose_strings
+    final text = ' ' * offset + code;
+    final reader = new CharSequenceReader(text);
+    final scanner = new Scanner(templateSource, reader, errorListener);
     return scanner.tokenize();
   }
 
-  /**
-   * Scan the given [text] staring at the given [offset] and resolve all of
-   * its embedded expressions.
-   */
+  /// Scan the given [text] staring at the given [offset] and resolve all of
+  /// its embedded expressions.
   List<Mustache> findMustaches(String text, int fileOffset) {
-    List<Mustache> mustaches = <Mustache>[];
+    final mustaches = <Mustache>[];
     if (text == null || text.length < 2) {
       return mustaches;
     }
 
-    int textOffset = 0;
+    var textOffset = 0;
     while (true) {
       // begin
-      final int begin = text.indexOf('{{', textOffset);
-      final int nextBegin = text.indexOf('{{', begin + 2);
-      final int end = text.indexOf('}}', textOffset);
+      final begin = text.indexOf('{{', textOffset);
+      final nextBegin = text.indexOf('{{', begin + 2);
+      final end = text.indexOf('}}', textOffset);
+
       int exprBegin, exprEnd;
-      bool detectTrailing = false;
+      var detectTrailing = false;
 
       // Absolutely no mustaches - simple text.
       if (begin == -1 && end == -1) {
@@ -668,12 +676,13 @@
         detectTrailing = true;
       }
       // resolve
-      String code = text.substring(exprBegin, exprEnd);
-      Expression expression =
-          parseDartExpression(fileOffset + exprBegin, code, detectTrailing);
+      final code = text.substring(exprBegin, exprEnd);
+      final expression = parseDartExpression(fileOffset + exprBegin, code,
+          detectTrailing: detectTrailing);
 
-      var offset = fileOffset + begin;
-      var length;
+      final offset = fileOffset + begin;
+
+      int length;
       if (end == -1) {
         length = expression.offset + expression.length - offset;
       } else {
@@ -691,18 +700,18 @@
     return mustaches;
   }
 
-  bool _startsWithWhitespace(String string) {
-    // trim returns the original string when no changes were made
-    return !identical(string.trimLeft(), string);
-  }
+  bool _startsWithWhitespace(String string) =>
+      // trim returns the original string when no changes were made
+      !identical(string.trimLeft(), string);
 
-  /**
-   * Desugar a template="" or *blah="" attribute into its list of virtual [AttributeInfo]s
-   */
-  List<AttributeInfo> parseTemplateVirtualAttributes(int offset, String code) {
-    List<AttributeInfo> attributes = <AttributeInfo>[];
-    Token token = _scanDartCode(offset, code);
-    String prefix = null;
+  /// Desugar a template="" or *blah="" attribute into its list of virtual
+  /// [AttributeInfo]s
+  Tuple2<String, List<AttributeInfo>> parseTemplateVirtualAttributes(
+      int offset, String code) {
+    final attributes = <AttributeInfo>[];
+
+    var token = _scanDartCode(offset, code);
+    String prefix;
     while (token.type != TokenType.EOF) {
       // skip optional comma or semicolons
       if (_isDelimiter(token)) {
@@ -711,29 +720,34 @@
       }
       // maybe a local variable
       if (_isTemplateVarBeginToken(token)) {
+        final originalVarOffset = token.offset;
         if (token.type == TokenType.HASH) {
           errorReporter.reportErrorForToken(
               AngularWarningCode.UNEXPECTED_HASH_IN_TEMPLATE, token);
         }
-        int originalVarOffset = token.offset;
-        String originalName = token.lexeme;
-        token = token.next;
+
+        var originalName = token.lexeme;
+
         // get the local variable name
-        String localVarName = "";
-        int localVarOffset = token.offset;
+        token = token.next;
+        var localVarName = "";
+        var localVarOffset = token.offset;
         if (!_tokenMatchesIdentifier(token)) {
           errorReporter.reportErrorForToken(
               AngularWarningCode.EXPECTED_IDENTIFIER, token);
         } else {
           localVarOffset = token.offset;
           localVarName = token.lexeme;
+          // ignore: prefer_interpolation_to_compose_strings
           originalName +=
-              ' ' * (token.offset - originalVarOffset) + localVarName;
+              ' ' * (token.offset - originalVarOffset - 'let'.length) +
+                  localVarName;
           token = token.next;
         }
+
         // get an optional internal variable
-        int internalVarOffset = null;
-        String internalVarName = null;
+        int internalVarOffset;
+        String internalVarName;
         if (token.type == TokenType.EQ) {
           token = token.next;
           // get the internal variable
@@ -757,29 +771,32 @@
             originalVarOffset, []));
         continue;
       }
+
       // key
-      int keyOffset = token.offset;
-      String originalName = null;
-      int originalNameOffset = keyOffset;
-      String key = null;
+      String key;
+      final keyBuffer = new StringBuffer();
+      final keyOffset = token.offset;
+      String originalName;
+      final originalNameOffset = keyOffset;
       if (_tokenMatchesIdentifier(token)) {
         // scan for a full attribute name
-        key = '';
-        int lastEnd = token.offset;
+        var lastEnd = token.offset;
         while (token.offset == lastEnd &&
             (_tokenMatchesIdentifier(token) || token.type == TokenType.MINUS)) {
-          key += token.lexeme;
+          keyBuffer.write(token.lexeme);
           lastEnd = token.end;
           token = token.next;
         }
 
-        originalName = key;
+        originalName = keyBuffer.toString();
 
         // add the prefix
         if (prefix == null) {
-          prefix = key;
+          prefix = keyBuffer.toString();
+          key = keyBuffer.toString();
         } else {
-          key = prefix + capitalize(key);
+          // ignore: prefer_interpolation_to_compose_strings
+          key = prefix + capitalize(keyBuffer.toString());
         }
       } else {
         errorReporter.reportErrorForToken(
@@ -794,16 +811,17 @@
       if (!_isTemplateVarBeginToken(token) &&
           !_isDelimiter(token) &&
           token.type != TokenType.EOF) {
-        Expression expression = _parseDartExpressionAtToken(token);
-        var start = token.offset - offset;
+        final expression = _parseDartExpressionAtToken(token);
+        final start = token.offset - offset;
+
         token = expression.endToken.next;
-        var end = token.offset - offset;
-        var exprCode = code.substring(start, end);
+        final end = token.offset - offset;
+        final exprCode = code.substring(start, end);
         attributes.add(new ExpressionBoundAttribute(
             key,
             keyOffset,
             exprCode,
-            token.offset,
+            expression.offset,
             originalName,
             originalNameOffset,
             expression,
@@ -814,17 +832,16 @@
       }
     }
 
-    return attributes;
+    return new Tuple2(prefix, attributes);
   }
 
   static bool _isDelimiter(Token token) =>
       token.type == TokenType.COMMA || token.type == TokenType.SEMICOLON;
 
-  static bool _isTemplateVarBeginToken(Token token) {
-    return token is KeywordToken && token.keyword == Keyword.VAR ||
-        (token.type == TokenType.IDENTIFIER && token.lexeme == 'let') ||
-        token.type == TokenType.HASH;
-  }
+  static bool _isTemplateVarBeginToken(Token token) =>
+      token is KeywordToken && token.keyword == Keyword.VAR ||
+      (token.type == TokenType.IDENTIFIER && token.lexeme == 'let') ||
+      token.type == TokenType.HASH;
 
   static bool _tokenMatchesBuiltInIdentifier(Token token) =>
       token is KeywordToken && token.keyword.isBuiltInOrPseudo;
@@ -834,6 +851,10 @@
       _tokenMatchesBuiltInIdentifier(token);
 }
 
-class IgnorableHtmlInternalError extends StateError {
-  IgnorableHtmlInternalError(String msg) : super(msg);
+class IgnorableHtmlInternalException implements Exception {
+  String msg;
+  IgnorableHtmlInternalException(this.msg);
+
+  @override
+  String toString() => "IgnorableHtmlInternalException: $msg";
 }
diff --git a/analyzer_plugin/lib/src/directive_extraction.dart b/analyzer_plugin/lib/src/directive_extraction.dart
index 02de367..1eb7502 100644
--- a/analyzer_plugin/lib/src/directive_extraction.dart
+++ b/analyzer_plugin/lib/src/directive_extraction.dart
@@ -1,6 +1,3 @@
-import 'tasks.dart';
-import 'model.dart';
-
 import 'package:analyzer/error/listener.dart';
 import 'package:analyzer/dart/ast/ast.dart' as ast;
 import 'package:analyzer/dart/element/element.dart';
@@ -11,6 +8,7 @@
 import 'package:analyzer/src/generated/source.dart';
 import 'package:angular_analyzer_plugin/src/model.dart';
 import 'package:angular_analyzer_plugin/src/selector.dart';
+import 'package:angular_analyzer_plugin/src/tasks.dart';
 import 'package:angular_analyzer_plugin/tasks.dart';
 import 'package:tuple/tuple.dart';
 
@@ -20,17 +18,13 @@
   final Source _source;
   final AnalysisContext _context;
 
-  /**
-   * Since <my-comp></my-comp> represents an instantiation of MyComp,
-   * especially when MyComp is generic or its superclasses are, we need
-   * this. Cache instead of passing around everywhere.
-   */
+  /// Since <my-comp></my-comp> represents an instantiation of MyComp,
+  /// especially when MyComp is generic or its superclasses are, we need
+  /// this. Cache instead of passing around everywhere.
   BindingTypeSynthesizer _bindingTypeSynthesizer;
 
-  /**
-   * The [ClassElement] being used to create the current component,
-   * stored here instead of passing around everywhere.
-   */
+  /// The [ClassElement] being used to create the current component,
+  /// stored here instead of passing around everywhere.
   ClassElement _currentClassElement;
 
   DirectiveExtractor(
@@ -39,12 +33,11 @@
   }
 
   List<AbstractDirective> getDirectives() {
-    List<AbstractDirective> directives = <AbstractDirective>[];
-    for (ast.CompilationUnitMember unitMember in _unit.declarations) {
+    final directives = <AbstractDirective>[];
+    for (final unitMember in _unit.declarations) {
       if (unitMember is ast.ClassDeclaration) {
-        for (ast.Annotation annotationNode in unitMember.metadata) {
-          AbstractDirective directive =
-              _createDirective(unitMember, annotationNode);
+        for (final annotationNode in unitMember.metadata) {
+          final directive = _createDirective(unitMember, annotationNode);
           if (directive != null) {
             directives.add(directive);
           }
@@ -55,35 +48,33 @@
     return directives;
   }
 
-  /**
-   * Returns an Angular [AbstractDirective] for to the given [node].
-   * Returns `null` if not an Angular annotation.
-   */
+  /// Returns an Angular [AbstractDirective] for to the given [node].
+  /// Returns `null` if not an Angular annotation.
   AbstractDirective _createDirective(
       ast.ClassDeclaration classDeclaration, ast.Annotation node) {
     _currentClassElement = classDeclaration.element;
     _bindingTypeSynthesizer = new BindingTypeSynthesizer(
         _currentClassElement, _typeProvider, _context, errorReporter);
     // TODO(scheglov) add support for all the arguments
-    bool isComponent = isAngularAnnotation(node, 'Component');
-    bool isDirective = isAngularAnnotation(node, 'Directive');
+    final isComponent = isAngularAnnotation(node, 'Component');
+    final isDirective = isAngularAnnotation(node, 'Directive');
     if (isComponent || isDirective) {
-      Selector selector = _parseSelector(node);
-      if (selector == null) {
-        // empty selector. Don't fail to create a Component just because of a
-        // broken or missing selector, that results in cascading errors.
-        selector = new AndSelector([]);
-      }
-      AngularElement exportAs = _parseExportAs(node);
-      List<InputElement> inputElements = <InputElement>[];
-      List<OutputElement> outputElements = <OutputElement>[];
+      // 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);
       }
-      List<ElementNameSelector> elementTags = <ElementNameSelector>[];
+      final contentChilds = <ContentChildField>[];
+      final contentChildrens = <ContentChildField>[];
+      _parseContentChilds(classDeclaration, contentChilds, contentChildrens);
+      final elementTags = <ElementNameSelector>[];
       selector.recordElementNameSelectors(elementTags);
       if (isComponent) {
         return new Component(_currentClassElement,
@@ -92,7 +83,9 @@
             outputs: outputElements,
             selector: selector,
             elementTags: elementTags,
-            isHtml: false);
+            isHtml: false,
+            contentChildFields: contentChilds,
+            contentChildrenFields: contentChildrens);
       }
       if (isDirective) {
         return new Directive(_currentClassElement,
@@ -100,20 +93,21 @@
             inputs: inputElements,
             outputs: outputElements,
             selector: selector,
-            elementTags: elementTags);
+            elementTags: elementTags,
+            contentChildFields: contentChilds,
+            contentChildrenFields: contentChildrens);
       }
     }
     return null;
   }
 
-  /**
-   * Return the first named argument with one of the given names, or
-   * `null` if this argument is not [ast.ListLiteral] or no such arguments.
-   */
+  /// Return the first named argument with one of the given names, or
+  /// `null` if this argument is not [ast.ListLiteral] or no such arguments.
   ast.ListLiteral _getListLiteralNamedArgument(
       ast.Annotation node, List<String> names) {
-    for (var name in names) {
-      ast.Expression expression = getNamedArgument(node, name);
+    for (final name in names) {
+      // ignore: omit_local_variable_types
+      final ast.Expression expression = getNamedArgument(node, name);
       if (expression != null) {
         return expression is ast.ListLiteral ? expression : null;
       }
@@ -123,13 +117,14 @@
 
   AngularElement _parseExportAs(ast.Annotation node) {
     // Find the "exportAs" argument.
-    ast.Expression expression = getNamedArgument(node, 'exportAs');
+    // ignore: omit_local_variable_types
+    final ast.Expression expression = getNamedArgument(node, 'exportAs');
     if (expression == null) {
       return null;
     }
 
     // Extract its content.
-    String name = getExpressionString(expression);
+    final name = getExpressionString(expression);
     if (name == null) {
       return null;
     }
@@ -147,20 +142,20 @@
   Tuple4<String, SourceRange, String, SourceRange>
       _parseHeaderNameValueSourceRanges(ast.Expression expression) {
     if (expression is ast.SimpleStringLiteral) {
-      int offset = expression.contentsOffset;
-      String value = expression.value;
+      final offset = expression.contentsOffset;
+      final value = expression.value;
       // TODO(mfairhurst) support for pipes
-      int colonIndex = value.indexOf(':');
+      final colonIndex = value.indexOf(':');
       if (colonIndex == -1) {
-        String name = value;
-        SourceRange nameRange = new SourceRange(offset, name.length);
+        final name = value;
+        final nameRange = new SourceRange(offset, name.length);
         return new Tuple4<String, SourceRange, String, SourceRange>(
             name, nameRange, name, nameRange);
       } else {
         // Resolve the setter.
-        String setterName = value.substring(0, colonIndex).trimRight();
+        final setterName = value.substring(0, colonIndex).trimRight();
         // Find the name.
-        int boundOffset = colonIndex;
+        var boundOffset = colonIndex;
         while (true) {
           boundOffset++;
           if (boundOffset >= value.length) {
@@ -171,7 +166,7 @@
             break;
           }
         }
-        String boundName = value.substring(boundOffset);
+        final boundName = value.substring(boundOffset);
         // TODO(mfairhurst) test that a valid bound name
         return new Tuple4<String, SourceRange, String, SourceRange>(
             boundName,
@@ -186,15 +181,16 @@
   }
 
   InputElement _parseHeaderInput(ast.Expression expression) {
-    Tuple4<String, SourceRange, String, SourceRange> nameValueAndRanges =
+    // ignore: omit_local_variable_types
+    final Tuple4<String, SourceRange, String, SourceRange> nameValueAndRanges =
         _parseHeaderNameValueSourceRanges(expression);
     if (nameValueAndRanges != null) {
-      var boundName = nameValueAndRanges.item1;
-      var boundRange = nameValueAndRanges.item2;
-      var name = nameValueAndRanges.item3;
-      var nameRange = nameValueAndRanges.item4;
+      final boundName = nameValueAndRanges.item1;
+      final boundRange = nameValueAndRanges.item2;
+      final name = nameValueAndRanges.item3;
+      final nameRange = nameValueAndRanges.item4;
 
-      PropertyAccessorElement setter = _resolveSetter(expression, name);
+      final setter = _resolveSetter(expression, name);
       if (setter == null) {
         return null;
       }
@@ -214,20 +210,21 @@
   }
 
   OutputElement _parseHeaderOutput(ast.Expression expression) {
-    Tuple4<String, SourceRange, String, SourceRange> nameValueAndRanges =
+    // ignore: omit_local_variable_types
+    final Tuple4<String, SourceRange, String, SourceRange> nameValueAndRanges =
         _parseHeaderNameValueSourceRanges(expression);
     if (nameValueAndRanges != null) {
-      var boundName = nameValueAndRanges.item1;
-      var boundRange = nameValueAndRanges.item2;
-      var name = nameValueAndRanges.item3;
-      var nameRange = nameValueAndRanges.item4;
+      final boundName = nameValueAndRanges.item1;
+      final boundRange = nameValueAndRanges.item2;
+      final name = nameValueAndRanges.item3;
+      final nameRange = nameValueAndRanges.item4;
 
-      PropertyAccessorElement getter = _resolveGetter(expression, name);
+      final getter = _resolveGetter(expression, name);
       if (getter == null) {
         return null;
       }
 
-      var eventType = _bindingTypeSynthesizer.getEventType(getter, name);
+      final eventType = _bindingTypeSynthesizer.getEventType(getter, name);
 
       return new OutputElement(boundName, boundRange.offset, boundRange.length,
           _source, getter, nameRange, eventType);
@@ -238,15 +235,16 @@
   }
 
   List<InputElement> _parseHeaderInputs(ast.Annotation node) {
-    ast.ListLiteral descList = _getListLiteralNamedArgument(
+    final descList = _getListLiteralNamedArgument(
         node, const <String>['inputs', 'properties']);
     if (descList == null) {
       return InputElement.EMPTY_LIST;
     }
     // Create an input for each element.
-    List<InputElement> inputElements = <InputElement>[];
+    final inputElements = <InputElement>[];
+    // ignore: omit_local_variable_types
     for (ast.Expression element in descList.elements) {
-      InputElement inputElement = _parseHeaderInput(element);
+      final inputElement = _parseHeaderInput(element);
       if (inputElement != null) {
         inputElements.add(inputElement);
       }
@@ -255,15 +253,16 @@
   }
 
   List<OutputElement> _parseHeaderOutputs(ast.Annotation node) {
-    ast.ListLiteral descList =
+    final descList =
         _getListLiteralNamedArgument(node, const <String>['outputs']);
     if (descList == null) {
       return OutputElement.EMPTY_LIST;
     }
     // Create an output for each element.
-    List<OutputElement> outputs = <OutputElement>[];
+    final outputs = <OutputElement>[];
+    // ignore: omit_local_variable_types
     for (ast.Expression element in descList.elements) {
-      OutputElement outputElement = _parseHeaderOutput(element);
+      final outputElement = _parseHeaderOutput(element);
       if (outputElement != null) {
         outputs.add(outputElement);
       }
@@ -271,13 +270,14 @@
     return outputs;
   }
 
-  /**
-   * Create a new input or output for the given class member [node] with
-   * the given `@Input` or `@Output` [annotation], and add it to the
-   * [inputElements] or [outputElements] array.
-   */
-  _parseMemberInputOrOutput(ast.ClassMember node, ast.Annotation annotation,
-      List<InputElement> inputElements, List<OutputElement> outputElements) {
+  /// Create a new input or output for the given class member [node] with
+  /// the given `@Input` or `@Output` [annotation], and add it to the
+  /// [inputElements] or [outputElements] array.
+  void _parseMemberInputOrOutput(
+      ast.ClassMember node,
+      ast.Annotation annotation,
+      List<InputElement> inputElements,
+      List<OutputElement> outputElements) {
     // analyze the annotation
     final isInput = isAngularAnnotation(annotation, 'Input');
     final isOutput = isAngularAnnotation(annotation, 'Output');
@@ -288,8 +288,8 @@
     // analyze the class member
     PropertyAccessorElement property;
     if (node is ast.FieldDeclaration && node.fields.variables.length == 1) {
-      ast.VariableDeclaration variable = node.fields.variables.first;
-      FieldElement fieldElement = variable.element;
+      final variable = node.fields.variables.first;
+      final fieldElement = variable.element as FieldElement;
       property = isInput ? fieldElement.setter : fieldElement.getter;
     } else if (node is ast.MethodDeclaration) {
       if (isInput && node.isSetter) {
@@ -309,20 +309,22 @@
       return null;
     }
 
+    final setterOffset = property.nameOffset;
+    final setterLength = property.nameLength;
+    final arguments = annotation.arguments.arguments;
+
     // prepare the input name
     String name;
     int nameOffset;
     int nameLength;
-    int setterOffset = property.nameOffset;
-    int setterLength = property.nameLength;
-    List<ast.Expression> arguments = annotation.arguments.arguments;
     if (arguments.isEmpty) {
-      String propertyName = property.displayName;
+      final propertyName = property.displayName;
       name = propertyName;
       nameOffset = property.nameOffset;
       nameLength = name.length;
     } else {
-      ast.Expression nameArgument = arguments[0];
+      // ignore: omit_local_variable_types
+      final ast.Expression nameArgument = arguments[0];
       if (nameArgument is ast.SimpleStringLiteral) {
         name = nameArgument.value;
         nameOffset = nameArgument.contentsOffset;
@@ -346,7 +348,7 @@
           new SourceRange(setterOffset, setterLength),
           _bindingTypeSynthesizer.getSetterType(property)));
     } else {
-      var eventType = _bindingTypeSynthesizer.getEventType(property, name);
+      final eventType = _bindingTypeSynthesizer.getEventType(property, name);
       outputElements.add(new OutputElement(
           name,
           nameOffset,
@@ -358,23 +360,85 @@
     }
   }
 
-  /**
-   * Collect inputs and outputs for all class members with `@Input`
-   * or `@Output` annotations.
-   */
-  _parseMemberInputsAndOutputs(ast.ClassDeclaration node,
+  /// Collect inputs and outputs for all class members with `@Input`
+  /// or `@Output` annotations.
+  void _parseMemberInputsAndOutputs(ast.ClassDeclaration node,
       List<InputElement> inputElements, List<OutputElement> outputElements) {
-    for (ast.ClassMember member in node.members) {
-      for (ast.Annotation annotation in member.metadata) {
+    for (final member in node.members) {
+      for (final annotation in member.metadata) {
         _parseMemberInputOrOutput(
             member, annotation, inputElements, outputElements);
       }
     }
   }
 
+  /// Find all fields labeled with @ContentChild and the ranges of the type
+  /// argument. We will use this to create an unlinked summary which can, at link
+  /// time, check for errors and highlight the correct range. This is all we need
+  /// from the AST itself, so all we should do here.
+  void _parseContentChilds(
+      ast.ClassDeclaration node,
+      List<ContentChildField> contentChilds,
+      List<ContentChildField> contentChildrens) {
+    for (final member in node.members) {
+      for (final annotation in member.metadata) {
+        List<ContentChildField> targetList;
+        if (isAngularAnnotation(annotation, 'ContentChild')) {
+          targetList = contentChilds;
+        } else if (isAngularAnnotation(annotation, 'ContentChildren')) {
+          targetList = contentChildrens;
+        } else {
+          continue;
+        }
+
+        final annotationArgs = annotation?.arguments?.arguments;
+        if (annotationArgs == null) {
+          // This happens for invalid dart code. Ignore
+          continue;
+        }
+
+        if (annotationArgs.isEmpty) {
+          // No need to report an error, dart does that already.
+          continue;
+        }
+
+        final offset = annotationArgs[0].offset;
+        final length = annotationArgs[0].length;
+        var setterTypeOffset = member.offset; // fallback option
+        var setterTypeLength = member.length; // fallback option
+
+        String name;
+        if (member is ast.FieldDeclaration) {
+          name = member.fields.variables[0].name.toString();
+
+          if (member.fields.type != null) {
+            setterTypeOffset = member.fields.type.offset;
+            setterTypeLength = member.fields.type.length;
+          }
+        } else if (member is ast.MethodDeclaration) {
+          name = member.name.toString();
+
+          final parameters = member.parameters?.parameters;
+          if (parameters != null && parameters.isNotEmpty) {
+            final parameter = parameters[0];
+            if (parameter is ast.SimpleFormalParameter &&
+                parameter.type != null) {
+              setterTypeOffset = parameter.type.offset;
+              setterTypeLength = parameter.type.length;
+            }
+          }
+        }
+        targetList.add(new ContentChildField(name,
+            nameRange: new SourceRange(offset, length),
+            typeRange: new SourceRange(setterTypeOffset, setterTypeLength)));
+      }
+    }
+  }
+
   Selector _parseSelector(ast.Annotation node) {
     // Find the "selector" argument.
-    ast.Expression expression = getNamedArgument(node, 'selector');
+    // ignore: omit_local_variable_types
+    final ast.Expression expression = getNamedArgument(node, 'selector');
     if (expression == null) {
       errorReporter.reportErrorForNode(
           AngularWarningCode.ARGUMENT_SELECTOR_MISSING, node);
@@ -382,17 +446,18 @@
     }
     // Compute the selector text. Careful! Offsets may not be valid after this,
     // however, at the moment we don't use them anyway.
-    OffsettingConstantEvaluator constantEvaluation =
+    // ignore: omit_local_variable_types
+    final OffsettingConstantEvaluator constantEvaluation =
         calculateStringWithOffsets(expression);
-    if (constantEvaluation == null) {
+    if (constantEvaluation == null || constantEvaluation.value is! String) {
       return null;
     }
 
-    String selectorStr = constantEvaluation.value;
-    int selectorOffset = expression.offset;
+    final selectorStr = constantEvaluation.value;
+    final selectorOffset = expression.offset;
     // Parse the selector text.
     try {
-      Selector selector =
+      final selector =
           new SelectorParser(_source, selectorOffset, selectorStr).parse();
       if (selector == null) {
         errorReporter.reportErrorForNode(
@@ -412,13 +477,11 @@
     return null;
   }
 
-  /**
-   * Resolve the input setter with the given [name] in [_currentClassElement].
-   * If undefined, report a warning and return `null`.
-   */
+  /// Resolve the input setter with the given [name] in [_currentClassElement].
+  /// If undefined, report a warning and return `null`.
   PropertyAccessorElement _resolveSetter(
       ast.SimpleStringLiteral literal, String name) {
-    PropertyAccessorElement setter =
+    final setter =
         _currentClassElement.lookUpSetter(name, _currentClassElement.library);
     if (setter == null) {
       errorReporter.reportErrorForNode(StaticTypeWarningCode.UNDEFINED_SETTER,
@@ -427,13 +490,11 @@
     return setter;
   }
 
-  /**
-   * Resolve the output getter with the given [name] in [_currentClassElement].
-   * If undefined, report a warning and return `null`.
-   */
+  /// Resolve the output getter with the given [name] in [_currentClassElement].
+  /// If undefined, report a warning and return `null`.
   PropertyAccessorElement _resolveGetter(
       ast.SimpleStringLiteral literal, String name) {
-    PropertyAccessorElement getter =
+    final getter =
         _currentClassElement.lookUpGetter(name, _currentClassElement.library);
     if (getter == null) {
       errorReporter.reportErrorForNode(StaticTypeWarningCode.UNDEFINED_GETTER,
@@ -449,7 +510,7 @@
   AttributeAnnotationValidator(this.errorReporter);
 
   void validate(AbstractDirective directive) {
-    ClassElement classElement = directive.classElement;
+    final classElement = directive.classElement;
     for (final constructor in classElement.constructors) {
       for (final parameter in constructor.parameters) {
         for (final annotation in parameter.metadata) {
@@ -494,6 +555,7 @@
 
   DartType getSetterType(PropertyAccessorElement setter) {
     if (setter != null) {
+      // ignore: parameter_assignments
       setter = _instantiatedClassType.lookUpInheritedSetter(setter.name,
           thisType: true);
     }
@@ -507,28 +569,29 @@
 
   DartType getEventType(PropertyAccessorElement getter, String name) {
     if (getter != null) {
+      // ignore: parameter_assignments
       getter = _instantiatedClassType.lookUpInheritedGetter(getter.name,
           thisType: true);
     }
 
     if (getter != null && getter.type != null) {
-      var returnType = getter.type.returnType;
+      final returnType = getter.type.returnType;
       if (returnType != null && returnType is InterfaceType) {
-        DartType streamType = _typeProvider.streamType;
-        DartType streamedType = _context.typeSystem
+        final streamType = _typeProvider.streamType;
+        final streamedType = _context.typeSystem
             .mostSpecificTypeArgument(returnType, streamType);
         if (streamedType != null) {
           return streamedType;
         } else {
           _errorReporter.reportErrorForOffset(
-              AngularWarningCode.OUTPUT_MUST_BE_EVENTEMITTER,
+              AngularWarningCode.OUTPUT_MUST_BE_STREAM,
               getter.nameOffset,
               getter.name.length,
               [name]);
         }
       } else {
         _errorReporter.reportErrorForOffset(
-            AngularWarningCode.OUTPUT_MUST_BE_EVENTEMITTER,
+            AngularWarningCode.OUTPUT_MUST_BE_STREAM,
             getter.nameOffset,
             getter.name.length,
             [name]);
@@ -538,17 +601,15 @@
     return _typeProvider.dynamicType;
   }
 
-  static DartType _instantiateClass(
+  static InterfaceType _instantiateClass(
       ClassElement classElement, TypeProvider typeProvider) {
     // TODO use `insantiateToBounds` for better all around support
     // See #91 for discussion about bugs related to bounds
-    var getBound = (TypeParameterElement p) {
-      return p.bound == null
-          ? typeProvider.dynamicType
-          : p.bound.resolveToBound(typeProvider.dynamicType);
-    };
+    DartType getBound(TypeParameterElement p) => p.bound == null
+        ? typeProvider.dynamicType
+        : p.bound.resolveToBound(typeProvider.dynamicType);
 
-    var bounds = classElement.typeParameters.map(getBound).toList();
+    final bounds = classElement.typeParameters.map(getBound).toList();
     return classElement.type.instantiate(bounds);
   }
 }
diff --git a/analyzer_plugin/lib/src/directive_linking.dart b/analyzer_plugin/lib/src/directive_linking.dart
index ca5263b..0afb019 100644
--- a/analyzer_plugin/lib/src/directive_linking.dart
+++ b/analyzer_plugin/lib/src/directive_linking.dart
@@ -2,15 +2,17 @@
 import 'package:analyzer/error/listener.dart';
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/src/generated/source.dart';
+import 'package:analyzer/dart/element/type.dart';
+import 'package:analyzer/src/generated/engine.dart';
 import 'package:angular_analyzer_plugin/tasks.dart';
 import 'package:angular_analyzer_plugin/src/directive_extraction.dart';
 import 'package:angular_analyzer_plugin/src/model.dart';
 import 'package:angular_analyzer_plugin/src/selector.dart';
+import 'package:angular_analyzer_plugin/src/standard_components.dart';
 import 'package:analyzer/src/dart/resolver/scope.dart';
 import 'package:analyzer/dart/ast/standard_ast_factory.dart';
 import 'package:analyzer/src/generated/constant.dart';
 import 'package:analyzer/dart/constant/value.dart';
-import 'package:analyzer/dart/element/type.dart';
 import 'package:front_end/src/scanner/token.dart';
 import 'summary/idl.dart';
 
@@ -25,6 +27,7 @@
 }
 
 class IgnoringErrorListener implements AnalysisErrorListener {
+  @override
   void onError(Object o) {}
 }
 
@@ -59,14 +62,16 @@
       final selector =
           new SelectorParser(source, dirSum.selectorOffset, dirSum.selectorStr)
               .parse();
-      List<ElementNameSelector> elementTags = <ElementNameSelector>[];
+      final elementTags = <ElementNameSelector>[];
       selector.recordElementNameSelectors(elementTags);
-      final List<InputElement> inputs = [];
+      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;
+        if (setter == null) {
+          continue;
+        }
         inputs.add(new InputElement(
             inputSum.name,
             inputSum.nameOffset,
@@ -77,12 +82,14 @@
             bindingSynthesizer
                 .getSetterType(setter))); // Don't think type is correct
       }
-      final List<OutputElement> outputs = [];
+      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;
+        if (getter == null) {
+          continue;
+        }
         outputs.add(new OutputElement(
             outputSum.name,
             outputSum.nameOffset,
@@ -93,6 +100,10 @@
                 outputSum.propNameOffset, outputSum.propName.length),
             bindingSynthesizer.getEventType(getter, getter.name)));
       }
+      final contentChildFields =
+          deserializeContentChildFields(dirSum.contentChildFields);
+      final contentChildrenFields =
+          deserializeContentChildFields(dirSum.contentChildrenFields);
       if (dirSum.isComponent) {
         final ngContents = deserializeNgContents(dirSum.ngContents, source);
         final component = new Component(classElem,
@@ -101,15 +112,17 @@
             inputs: inputs,
             outputs: outputs,
             ngContents: ngContents,
-            elementTags: elementTags);
+            elementTags: elementTags,
+            contentChildFields: contentChildFields,
+            contentChildrenFields: contentChildrenFields);
         directives.add(component);
         final subDirectives = <DirectiveReference>[];
         for (final useSum in dirSum.subdirectives) {
           subDirectives.add(new DirectiveReference(useSum.name, useSum.prefix,
               new SourceRange(useSum.offset, useSum.length)));
         }
-        var templateUriSource = null;
-        var templateUrlRange = null;
+        Source templateUriSource;
+        SourceRange templateUrlRange;
         if (dirSum.templateUrl != '') {
           templateUriSource =
               _directiveLinkerEnablement.getSource(dirSum.templateUrl);
@@ -128,7 +141,9 @@
             selector: selector,
             inputs: inputs,
             outputs: outputs,
-            elementTags: elementTags);
+            elementTags: elementTags,
+            contentChildFields: contentChildFields,
+            contentChildrenFields: contentChildrenFields);
         directives.add(directive);
       }
     }
@@ -137,28 +152,39 @@
   }
 
   List<NgContent> deserializeNgContents(
-      List<SummarizedNgContent> ngContentSums, Source source) {
-    return ngContentSums.map((ngContentSum) {
-      final selector = ngContentSum.selectorStr == ""
-          ? null
-          : new SelectorParser(
-                  source, ngContentSum.selectorOffset, ngContentSum.selectorStr)
-              .parse();
-      return new NgContent.withSelector(
-          ngContentSum.offset,
-          ngContentSum.length,
-          selector,
-          selector?.offset,
-          ngContentSum.selectorStr.length);
-    }).toList();
-  }
+          List<SummarizedNgContent> ngContentSums, Source source) =>
+      ngContentSums.map((ngContentSum) {
+        final selector = ngContentSum.selectorStr == ""
+            ? null
+            : new SelectorParser(source, ngContentSum.selectorOffset,
+                    ngContentSum.selectorStr)
+                .parse();
+        return new NgContent.withSelector(
+            ngContentSum.offset,
+            ngContentSum.length,
+            selector,
+            selector?.offset,
+            ngContentSum.selectorStr.length);
+      }).toList();
+
+  List<ContentChildField> deserializeContentChildFields(
+          List<SummarizedContentChildField> fieldSums) =>
+      fieldSums
+          .map((fieldSum) => new ContentChildField(fieldSum.fieldName,
+              nameRange:
+                  new SourceRange(fieldSum.nameOffset, fieldSum.nameLength),
+              typeRange:
+                  new SourceRange(fieldSum.typeOffset, fieldSum.typeLength)))
+          .toList();
 }
 
-class ChildDirectiveLinker {
+class ChildDirectiveLinker implements DirectiveMatcher {
   final FileDirectiveProvider _fileDirectiveProvider;
   final ErrorReporter _errorReporter;
+  final StandardAngular _standardAngular;
 
-  ChildDirectiveLinker(this._fileDirectiveProvider, this._errorReporter);
+  ChildDirectiveLinker(
+      this._fileDirectiveProvider, this._standardAngular, this._errorReporter);
 
   Future linkDirectives(
     List<AbstractDirective> directivesToLink,
@@ -170,13 +196,18 @@
         for (final reference in directive.view.directiveReferences) {
           final referent = lookupByName(reference, directivesToLink);
           if (referent != null) {
-            directive.view.directives.add(await withNgContent(referent));
+            directive.view.directives
+                .add(await withNgContentAndChildren(referent));
           } else {
             await lookupFromLibrary(
                 reference, scope, directive.view.directives);
           }
         }
       }
+
+      await new ContentChildLinker(
+              directive, this, _standardAngular, _errorReporter)
+          .linkContentChildren();
     }
   }
 
@@ -200,7 +231,7 @@
             new StringToken(TokenType.IDENTIFIER, reference.name, 0)),
         null);
 
-    if (type != null) {
+    if (type != null && type.source != null) {
       final fileDirectives = await _fileDirectiveProvider
           .getUnlinkedDirectives(type.source.fullName);
 
@@ -208,7 +239,7 @@
         final directive = await matchDirective(type);
 
         if (directive != null) {
-          directives.add(await withNgContent(directive));
+          directives.add(await withNgContentAndChildren(directive));
         } else {
           _errorReporter.reportErrorForOffset(
               AngularWarningCode.TYPE_IS_NOT_A_DIRECTIVE,
@@ -242,6 +273,7 @@
         reference.range.length);
   }
 
+  @override
   Future<AbstractDirective> matchDirective(ClassElement clazz) async {
     final fileDirectives = await _fileDirectiveProvider
         .getUnlinkedDirectives(clazz.source.fullName);
@@ -255,22 +287,20 @@
     return null;
   }
 
-  /**
-   * Walk the given [value] and add directives into [directives].
-   * Return `true` if success, or `false` the [value] has items that don't
-   * correspond to a directive.
-   */
+  /// Walk the given [value] and add directives into [directives].
+  /// Return `true` if success, or `false` the [value] has items that don't
+  /// correspond to a directive.
   Future _addDirectivesAndElementTagsForDartObject(
       List<AbstractDirective> directives,
       List<AbstractDirective> fileDirectives,
       List<DartObject> values,
       DirectiveReference reference) async {
-    for (DartObject listItem in values) {
+    for (final listItem in values) {
       final typeValue = listItem.toTypeValue();
       if (typeValue is InterfaceType && typeValue.element is ClassElement) {
         final directive = await matchDirective(typeValue.element);
         if (directive != null) {
-          directives.add(await withNgContent(directive));
+          directives.add(await withNgContentAndChildren(directive));
         } else {
           _errorReporter.reportErrorForOffset(
               AngularWarningCode.TYPE_IS_NOT_A_DIRECTIVE,
@@ -288,12 +318,193 @@
     }
   }
 
-  Future<AbstractDirective> withNgContent(AbstractDirective directive) async {
+  Future<AbstractDirective> withNgContentAndChildren(
+      AbstractDirective directive) async {
     if (directive is Component && directive?.view?.templateUriSource != null) {
       final source = directive.view.templateUriSource;
       directive.ngContents.addAll(
           await _fileDirectiveProvider.getHtmlNgContent(source.fullName));
     }
+
+    // NOTE: Require the Exact type TemplateRef because that's what the
+    // injector does.
+    directive.looksLikeTemplate = directive.classElement.constructors.any(
+        (constructor) => constructor.parameters
+            .any((param) => param.type == _standardAngular.templateRef.type));
+
+    // ignore errors from linking subcomponents content childs
+    final errorIgnorer = new ErrorReporter(
+        new IgnoringErrorListener(), directive.classElement.source);
+    await new ContentChildLinker(
+            directive, this, _standardAngular, errorIgnorer)
+        .linkContentChildren();
     return directive;
   }
 }
+
+abstract class DirectiveMatcher {
+  Future<AbstractDirective> matchDirective(ClassElement clazz);
+}
+
+class ContentChildLinker {
+  final AnalysisContext _context;
+  final ErrorReporter _errorReporter;
+  final AbstractDirective _directive;
+  final DirectiveMatcher _directiveMatcher;
+  final StandardAngular _standardAngular;
+
+  ContentChildLinker(AbstractDirective directive, this._directiveMatcher,
+      this._standardAngular, this._errorReporter)
+      : _context =
+            directive.classElement.enclosingElement.enclosingElement.context,
+        _directive = directive;
+
+  Future linkContentChildren() async {
+    final unit = _directive.classElement.enclosingElement.enclosingElement;
+    final bindingSynthesizer = new BindingTypeSynthesizer(
+        _directive.classElement,
+        unit.context.typeProvider,
+        unit.context,
+        _errorReporter);
+
+    for (final childField in _directive.contentChildFields) {
+      await recordContentChildOrChildren(childField, unit.library,
+          bindingSynthesizer, transformSetterTypeSingular,
+          annotationName: "ContentChild",
+          destinationArray: _directive.contentChilds);
+    }
+    for (final childrenField in _directive.contentChildrenFields) {
+      await recordContentChildOrChildren(childrenField, unit.library,
+          bindingSynthesizer, transformSetterTypeMultiple,
+          annotationName: "ContentChildren",
+          destinationArray: _directive.contentChildren);
+    }
+  }
+
+  /// ConstantValue.getField() doesn't look up the inheritance tree. Rather than
+  /// hardcoding the inheritance tree in our code, look up the inheritance tree
+  /// until either it ends, or we find a "selector" field.
+  DartObject getSelectorWithInheritance(DartObject value) {
+    final selector = value.getField("selector");
+    if (selector != null) {
+      return selector;
+    }
+
+    final _super = value.getField("(super)");
+    if (_super != null) {
+      return getSelectorWithInheritance(_super);
+    }
+
+    return null;
+  }
+
+  Future recordContentChildOrChildren(
+      ContentChildField field,
+      LibraryElement library,
+      BindingTypeSynthesizer bindingSynthesizer,
+      TransformSetterTypeFn transformSetterTypeFn,
+      {List<ContentChild> destinationArray,
+      String annotationName}) async {
+    final member =
+        _directive.classElement.lookUpSetter(field.fieldName, library);
+    if (member == null) {
+      return;
+    }
+
+    final metadata = new List<ElementAnnotation>.from(member.metadata)
+      ..addAll(member.variable.metadata);
+    final annotations = metadata.where((annotation) =>
+        annotation.element?.enclosingElement?.name == annotationName);
+
+    // This can happen for invalid dart
+    if (annotations.length != 1) {
+      return;
+    }
+
+    final annotation = annotations.first;
+
+    // constantValue.getField() doesn't do inheritance. Do that ourself.
+    final value = getSelectorWithInheritance(annotation.computeConstantValue());
+    if (value?.toStringValue() != null) {
+      final setterType = transformSetterTypeFn(
+          bindingSynthesizer.getSetterType(member), field, annotationName);
+      destinationArray.add(new ContentChild(field,
+          new LetBoundQueriedChildType(value.toStringValue(), setterType)));
+    } else if (value?.toTypeValue() != null) {
+      final type = value.toTypeValue();
+      final referencedDirective =
+          await _directiveMatcher.matchDirective(type.element);
+      if (referencedDirective != null) {
+        destinationArray.add(new ContentChild(
+            field, new DirectiveQueriedChildType(referencedDirective)));
+      } else if (type.element.name == "ElementRef") {
+        destinationArray
+            .add(new ContentChild(field, new ElementRefQueriedChildType()));
+      } else if (type.element.name == "TemplateRef") {
+        destinationArray
+            .add(new ContentChild(field, new TemplateRefQueriedChildType()));
+      } else {
+        _errorReporter.reportErrorForOffset(
+            AngularWarningCode.UNKNOWN_CHILD_QUERY_TYPE,
+            field.nameRange.offset,
+            field.nameRange.length,
+            [field.fieldName, annotationName]);
+        return;
+      }
+
+      final setterType = transformSetterTypeFn(
+          bindingSynthesizer.getSetterType(member), field, annotationName);
+      checkQueriedTypeAssignableTo(setterType, type, field, annotationName);
+    } else {
+      _errorReporter.reportErrorForOffset(
+          AngularWarningCode.UNKNOWN_CHILD_QUERY_TYPE,
+          field.nameRange.offset,
+          field.nameRange.length,
+          [field.fieldName, annotationName]);
+    }
+  }
+
+  void checkQueriedTypeAssignableTo(DartType setterType, DartType annotatedType,
+      ContentChildField field, String annotationName) {
+    if (setterType != null && !setterType.isSupertypeOf(annotatedType)) {
+      _errorReporter.reportErrorForOffset(
+          AngularWarningCode.INVALID_TYPE_FOR_CHILD_QUERY,
+          field.typeRange.offset,
+          field.typeRange.length,
+          [field.fieldName, annotationName, annotatedType, setterType]);
+    }
+  }
+
+  DartType transformSetterTypeSingular(DartType setterType,
+          ContentChildField field, String annotationName) =>
+      setterType;
+
+  DartType transformSetterTypeMultiple(
+      DartType setterType, ContentChildField field, String annotationName) {
+    // construct QueryList<Bottom>, which is a supertype of all QueryList<T>
+    // NOTE: In most languages, you'd need QueryList<Object>, but not dart.
+    final queryListBottom = _standardAngular.queryList.type
+        .instantiate([_context.typeProvider.bottomType]);
+
+    final isQueryList = setterType.isSupertypeOf(queryListBottom);
+
+    if (!isQueryList) {
+      _errorReporter.reportErrorForOffset(
+          AngularWarningCode.CONTENT_OR_VIEW_CHILDREN_REQUIRES_QUERY_LIST,
+          field.typeRange.offset,
+          field.typeRange.length,
+          [field.fieldName, annotationName, setterType]);
+
+      return _context.typeProvider.dynamicType;
+    }
+
+    final iterableType = _context.typeProvider.iterableType;
+
+    // get T for setterTypes that extend Iterable<T>
+    return _context.typeSystem
+        .mostSpecificTypeArgument(setterType, iterableType);
+  }
+}
+
+typedef DartType TransformSetterTypeFn(
+    DartType setterType, ContentChildField field, String annotationName);
diff --git a/analyzer_plugin/lib/src/file_tracker.dart b/analyzer_plugin/lib/src/file_tracker.dart
index 85a6b89..d14df22 100644
--- a/analyzer_plugin/lib/src/file_tracker.dart
+++ b/analyzer_plugin/lib/src/file_tracker.dart
@@ -1,6 +1,6 @@
 import 'dart:collection';
 
-import 'package:analyzer/src/summary/api_signature.dart';
+import 'package:front_end/src/base/api_signature.dart';
 
 abstract class FileHasher {
   ApiSignature getContentHash(String path);
@@ -12,15 +12,28 @@
 
   FileTracker(this._fileHasher);
 
-  _RelationshipTracker _dartToDart = new _RelationshipTracker();
-  _RelationshipTracker _dartToHtml = new _RelationshipTracker();
+  final _dartToDart = new _RelationshipTracker();
+  final _dartToHtml = new _RelationshipTracker();
 
-  Set<String> _dartFilesWithDartTemplates = new HashSet<String>();
+  final _dartFilesWithDartTemplates = new HashSet<String>();
 
-  void setDartHtmlTemplates(String dartPath, List<String> htmlPaths) {
-    return _dartToHtml.setFileReferencesFiles(dartPath, htmlPaths);
+  final htmlContentHashes = <String, List<int>>{};
+
+  void rehashHtmlContents(String path) {
+    htmlContentHashes[path] = _fileHasher.getContentHash(path).toByteList();
   }
 
+  List<int> getHtmlContentHash(String path) {
+    if (htmlContentHashes[path] == null) {
+      rehashHtmlContents(path);
+    }
+    return htmlContentHashes[path];
+  }
+
+  void setDartHtmlTemplates(String dartPath, List<String> htmlPaths) =>
+      _dartToHtml.setFileReferencesFiles(dartPath, htmlPaths);
+
+  // ignore: avoid_positional_boolean_parameters
   void setDartHasTemplate(String dartPath, bool hasTemplate) {
     if (hasTemplate) {
       _dartFilesWithDartTemplates.add(dartPath);
@@ -29,35 +42,30 @@
     }
   }
 
-  List<String> getHtmlPathsReferencedByDart(String dartPath) {
-    return _dartToHtml.getFilesReferencedBy(dartPath);
-  }
+  List<String> getHtmlPathsReferencedByDart(String dartPath) =>
+      _dartToHtml.getFilesReferencedBy(dartPath);
 
-  List<String> getDartPathsReferencingHtml(String htmlPath) {
-    return _dartToHtml.getFilesReferencingFile(htmlPath);
-  }
+  List<String> getDartPathsReferencingHtml(String htmlPath) =>
+      _dartToHtml.getFilesReferencingFile(htmlPath);
 
   void setDartImports(String dartPath, List<String> imports) {
     _dartToDart.setFileReferencesFiles(dartPath, imports);
   }
 
-  List<String> getHtmlPathsReferencingHtml(String htmlPath) {
-    return _dartToHtml
-        .getFilesReferencingFile(htmlPath)
-        .map((dartPath) => _dartToDart.getFilesReferencingFile(dartPath))
-        .fold(<String>[], (list, acc) => list..addAll(acc))
-        .map((dartPath) => _dartToHtml.getFilesReferencedBy(dartPath))
-        .fold(<String>[], (list, acc) => list..addAll(acc));
-  }
+  List<String> getHtmlPathsReferencingHtml(String htmlPath) => _dartToHtml
+      .getFilesReferencingFile(htmlPath)
+      .map(_dartToDart.getFilesReferencingFile)
+      .fold(<String>[], (list, acc) => list..addAll(acc))
+      .map(_dartToHtml.getFilesReferencedBy)
+      .fold(<String>[], (list, acc) => list..addAll(acc))
+      .toList();
 
-  List<String> getDartPathsAffectedByHtml(String htmlPath) {
-    return _dartToHtml
-        .getFilesReferencingFile(htmlPath)
-        .map((dartPath) => _dartToDart.getFilesReferencingFile(dartPath))
-        .fold(<String>[], (list, acc) => list..addAll(acc))
-        .where((dartPath) => _dartFilesWithDartTemplates.contains(dartPath))
-        .toList();
-  }
+  List<String> getDartPathsAffectedByHtml(String htmlPath) => _dartToHtml
+      .getFilesReferencingFile(htmlPath)
+      .map(_dartToDart.getFilesReferencingFile)
+      .fold(<String>[], (list, acc) => list..addAll(acc))
+      .where(_dartFilesWithDartTemplates.contains)
+      .toList();
 
   List<String> getHtmlPathsAffectingDart(String dartPath) {
     if (_dartFilesWithDartTemplates.contains(dartPath)) {
@@ -67,30 +75,27 @@
     return [];
   }
 
-  List<String> getHtmlPathsAffectingDartContext(String dartPath) {
-    return _dartToDart
-        .getFilesReferencedBy(dartPath)
-        .map((dartPath) => _dartToHtml.getFilesReferencedBy(dartPath))
-        .fold(<String>[], (list, acc) => list..addAll(acc));
-  }
+  List<String> getHtmlPathsAffectingDartContext(String dartPath) => _dartToDart
+      .getFilesReferencedBy(dartPath)
+      .map(_dartToHtml.getFilesReferencedBy)
+      .fold(<String>[], (list, acc) => list..addAll(acc)).toList();
 
   ApiSignature getDartSignature(String dartPath) {
-    final signature = new ApiSignature();
-    signature.addBytes(_fileHasher.getUnitElementHash(dartPath).toByteList());
+    final signature = new ApiSignature()
+      ..addBytes(_fileHasher.getUnitElementHash(dartPath).toByteList());
     for (final htmlPath in getHtmlPathsAffectingDart(dartPath)) {
-      signature.addBytes(_fileHasher.getContentHash(htmlPath).toByteList());
+      signature.addBytes(getHtmlContentHash(htmlPath));
     }
     return signature;
   }
 
   ApiSignature getHtmlSignature(String htmlPath) {
-    final signature = new ApiSignature();
-    signature.addBytes(_fileHasher.getContentHash(htmlPath).toByteList());
+    final signature = new ApiSignature()
+      ..addBytes(getHtmlContentHash(htmlPath));
     for (final dartPath in getDartPathsReferencingHtml(htmlPath)) {
       signature.addBytes(_fileHasher.getUnitElementHash(dartPath).toByteList());
       for (final subHtmlPath in getHtmlPathsAffectingDartContext(dartPath)) {
-        signature
-            .addBytes(_fileHasher.getContentHash(subHtmlPath).toByteList());
+        signature.addBytes(getHtmlContentHash(subHtmlPath));
       }
     }
     return signature;
@@ -98,11 +103,11 @@
 }
 
 class _RelationshipTracker {
-  Map<String, List<String>> _filesReferencedByFile = <String, List<String>>{};
-  Map<String, List<String>> _filesReferencingFile = <String, List<String>>{};
+  final _filesReferencedByFile = <String, List<String>>{};
+  final _filesReferencingFile = <String, List<String>>{};
 
   void setFileReferencesFiles(String filePath, List<String> referencesPaths) {
-    Set<String> priorRelationships = new HashSet<String>();
+    final priorRelationships = new HashSet<String>();
     if (_filesReferencedByFile.containsKey(filePath)) {
       for (final referencesPath in _filesReferencedByFile[filePath]) {
         if (!referencesPaths.contains(referencesPath)) {
@@ -128,11 +133,9 @@
     }
   }
 
-  List<String> getFilesReferencedBy(String filePath) {
-    return _filesReferencedByFile[filePath] ?? [];
-  }
+  List<String> getFilesReferencedBy(String filePath) =>
+      _filesReferencedByFile[filePath] ?? [];
 
-  List<String> getFilesReferencingFile(String usesPath) {
-    return _filesReferencingFile[usesPath] ?? [];
-  }
+  List<String> getFilesReferencingFile(String usesPath) =>
+      _filesReferencingFile[usesPath] ?? [];
 }
diff --git a/analyzer_plugin/lib/src/from_file_prefixed_error.dart b/analyzer_plugin/lib/src/from_file_prefixed_error.dart
index 443658f..a44db3c 100644
--- a/analyzer_plugin/lib/src/from_file_prefixed_error.dart
+++ b/analyzer_plugin/lib/src/from_file_prefixed_error.dart
@@ -1,14 +1,12 @@
 import 'package:analyzer/error/error.dart';
 import 'package:analyzer/src/generated/source.dart';
 
-/**
- * A wrapper around AnalysisError which also links back to a "from" file for
- * context.
- *
- * Is a wrapper, not just an extension, so that it can have a different hashCode
- * than the error without a "from" path, in the case that a file is included
- * both sanely and strangely (which is common: prod and test).
- */
+// A wrapper around AnalysisError which also links back to a "from" file for
+// context.
+//
+// Is a wrapper, not just an extension, so that it can have a different hashCode
+// than the error without a "from" path, in the case that a file is included
+// both sanely and strangely (which is common: prod and test).
 class FromFilePrefixedError implements AnalysisError {
   final String fromSourcePath;
   final String originalMessage;
@@ -19,14 +17,14 @@
       : originalMessage = originalError.message,
         fromSourcePath = fromSource.fullName,
         originalError = originalError {
-    _message = "$originalMessage (from ${fromSourcePath})";
+    _message = "$originalMessage (from $fromSourcePath)";
   }
 
   FromFilePrefixedError.fromPath(
       this.fromSourcePath, AnalysisError originalError)
       : originalMessage = originalError.message,
         originalError = originalError {
-    _message = "$originalMessage (from ${fromSourcePath})";
+    _message = "$originalMessage (from $fromSourcePath)";
   }
 
   @override
@@ -61,7 +59,7 @@
 
   @override
   int get hashCode {
-    int hashCode = offset;
+    var hashCode = offset;
     hashCode ^= (_message != null) ? _message.hashCode : 0;
     hashCode ^= (source != null) ? source.hashCode : 0;
     return hashCode;
diff --git a/analyzer_plugin/lib/src/model.dart b/analyzer_plugin/lib/src/model.dart
index c24ac48..6f12fca 100644
--- a/analyzer_plugin/lib/src/model.dart
+++ b/analyzer_plugin/lib/src/model.dart
@@ -4,21 +4,20 @@
 import 'package:analyzer/dart/element/element.dart' as dart;
 import 'package:analyzer/dart/element/type.dart' as dart;
 import 'package:analyzer/dart/ast/ast.dart' as dart;
+import 'package:analyzer/error/listener.dart';
 import 'package:analyzer/src/generated/source.dart' show Source, SourceRange;
 import 'package:analyzer/src/generated/utilities_general.dart';
 import 'package:analyzer/task/model.dart' show AnalysisTarget;
 import 'package:angular_analyzer_plugin/src/selector.dart';
+import 'package:angular_analyzer_plugin/src/standard_components.dart';
 import 'package:angular_analyzer_plugin/ast.dart';
+import 'package:angular_analyzer_plugin/tasks.dart';
 
-/**
- * An abstract model of an Angular directive.
- */
+/// An abstract model of an Angular directive.
 abstract class AbstractDirective {
-  static const List<AbstractDirective> EMPTY_LIST = const <AbstractDirective>[];
+  static const EMPTY_LIST = const <AbstractDirective>[];
 
-  /**
-   * The [ClassElement] this annotation is associated with.
-   */
+  /// The [ClassElement] this annotation is associated with.
   final dart.ClassElement classElement;
 
   final AngularElement exportAs;
@@ -26,60 +25,67 @@
   final List<OutputElement> outputs;
   final Selector selector;
   final List<ElementNameSelector> elementTags;
-  final List<AngularElement> attributes = <AngularElement>[];
+  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
+  /// summary, so that at link time we can report errors discovered in the model
+  /// against the range we saw it the AST.
+  List<ContentChildField> contentChildrenFields;
+  List<ContentChildField> contentChildFields;
+  final contentChilds = <ContentChild>[];
+  final contentChildren = <ContentChild>[];
+
+  /// Its very hard to tell which directives are meant to be used with a *star.
+  /// However, any directives which have a `TemplateRef` as a constructor
+  /// parameter are almost certainly meant to be used with one. We use this for
+  /// whatever validation we can, and autocomplete suggestions.
+  bool looksLikeTemplate = false;
 
   AbstractDirective(this.classElement,
       {this.exportAs,
       this.inputs,
       this.outputs,
       this.selector,
-      this.elementTags});
+      this.elementTags,
+      this.contentChildFields,
+      this.contentChildrenFields});
 
-  /**
-   * The source that contains this directive.
-   */
+  /// The source that contains this directive.
   Source get source => classElement.source;
 
   @override
-  String toString() {
-    return '$runtimeType(${classElement.displayName} '
-        'selector=$selector '
-        'inputs=$inputs '
-        'outputs=$outputs '
-        'attributes=$attributes)';
-  }
+  String toString() => '$runtimeType(${classElement.displayName} '
+      'selector=$selector '
+      'inputs=$inputs '
+      'outputs=$outputs '
+      'attributes=$attributes)';
+
+  @override
+  bool operator ==(Object other) =>
+      other is AbstractDirective && other.classElement == classElement;
 }
 
-/**
- * The base class for all Angular elements.
- */
+/// The base class for all Angular elements.
 abstract class AngularElement {
-  /**
-   * Return the name of this element, not `null`.
-   */
+  /// Return the name of this element, not `null`.
   String get name;
 
-  /**
-   * Return the length of the name of this element in the file that contains
-   * the declaration of this element.
-   */
+  /// Return the length of the name of this element in the file that contains
+  /// the declaration of this element.
   int get nameLength;
 
-  /**
-   * Return the offset of the name of this element in the file that contains
-   * the declaration of this element.
-   */
+  /// Return the offset of the name of this element in the file that contains
+  /// the declaration of this element.
   int get nameOffset;
 
-  /**
-   * Return the [Source] of this element.
-   */
+  /// Return the [Source] of this element.
   Source get source;
 }
 
-/**
- * The base class for concrete implementations of an [AngularElement].
- */
+/// The base class for concrete implementations of an [AngularElement].
 class AngularElementImpl implements AngularElement {
   @override
   final String name;
@@ -96,35 +102,134 @@
   AngularElementImpl(this.name, this.nameOffset, this.nameLength, this.source);
 
   @override
-  int get hashCode {
-    return JenkinsSmiHash.hash4(
-        name.hashCode, nameOffset, nameLength, source.hashCode);
-  }
+  int get hashCode => JenkinsSmiHash.hash4(
+      name.hashCode, nameOffset, nameLength, source.hashCode);
 
-  bool operator ==(Object other) {
-    return other is AngularElement &&
-        other.runtimeType == runtimeType &&
-        other.nameOffset == nameOffset &&
-        other.nameLength == nameLength &&
-        other.name == name &&
-        other.source == source;
-  }
+  @override
+  bool operator ==(Object other) =>
+      other is AngularElement &&
+      other.runtimeType == runtimeType &&
+      other.nameOffset == nameOffset &&
+      other.nameLength == nameLength &&
+      other.name == name &&
+      other.source == source;
 
   @override
   String toString() => name;
 }
 
-/**
- * The model of an Angular component.
- */
+abstract class AbstractQueriedChildType {
+  bool match(
+      ElementInfo element, StandardAngular angular, ErrorReporter reporter);
+}
+
+class TemplateRefQueriedChildType extends AbstractQueriedChildType {
+  @override
+  bool match(NodeInfo element, StandardAngular _, ErrorReporter __) =>
+      element is ElementInfo && element.localName == 'template';
+}
+
+class ElementRefQueriedChildType extends AbstractQueriedChildType {
+  @override
+  bool match(NodeInfo element, StandardAngular _, ErrorReporter __) =>
+      element is ElementInfo &&
+      element.localName != 'template' &&
+      !element.directives.any((boundDirective) =>
+          boundDirective is Component && !boundDirective.isHtml);
+}
+
+class LetBoundQueriedChildType extends AbstractQueriedChildType {
+  final String letBoundName;
+  final dart.DartType containerType;
+  LetBoundQueriedChildType(this.letBoundName, this.containerType);
+  @override
+  bool match(NodeInfo element, StandardAngular angular,
+          ErrorReporter errorReporter) =>
+      element is ElementInfo &&
+      element.attributes.any((attribute) {
+        if (attribute is TextAttribute && attribute.name == '#$letBoundName') {
+          _validateMatch(element, attribute, angular, errorReporter);
+          return true;
+        }
+        return false;
+      });
+
+  /// Validate against a matching [TextAttribute] on a matching [ElementInfo],
+  /// for assignability to [containerType] errors.
+  void _validateMatch(ElementInfo element, TextAttribute attr,
+      StandardAngular angular, ErrorReporter errorReporter) {
+    dart.DartType matchType;
+
+    if (attr.value != "" && attr.value != null) {
+      final possibleDirectives =
+          element.directives.where((d) => d.exportAs.name == attr.value);
+      if (possibleDirectives.isEmpty || possibleDirectives.length > 1) {
+        // Don't validate based on an invalid state (that's reported as such).
+        return;
+      }
+      // TODO instantiate this type to bounds
+      matchType = possibleDirectives.first.classElement.type;
+    } else if (element.localName == 'template') {
+      matchType = angular.templateRef.type;
+    } else {
+      final possibleComponents =
+          element.directives.where((d) => d is Component && !d.isHtml);
+      if (possibleComponents.length > 1) {
+        // Don't validate based on an invalid state (that's reported as such).
+        return;
+      }
+
+      if (possibleComponents.isEmpty) {
+        matchType = angular.elementRef.type;
+      } else {
+        // TODO instantiate this type to bounds
+        matchType = possibleComponents.first.classElement.type;
+      }
+    }
+
+    // Don't do isAssignable. Because we KNOW downcasting makes no sense here.
+    if (!containerType.isSupertypeOf(matchType)) {
+      errorReporter.reportErrorForOffset(
+          AngularWarningCode.MATCHED_LET_BINDING_HAS_WRONG_TYPE,
+          element.offset,
+          element.length,
+          [letBoundName, containerType, matchType]);
+    }
+  }
+}
+
+class DirectiveQueriedChildType extends AbstractQueriedChildType {
+  final AbstractDirective directive;
+  DirectiveQueriedChildType(this.directive);
+  @override
+  bool match(NodeInfo element, StandardAngular _, ErrorReporter __) =>
+      element is ElementInfo &&
+      element.directives.any((boundDirective) => boundDirective == directive);
+}
+
+class ContentChildField {
+  final String fieldName;
+  final SourceRange nameRange;
+  final SourceRange typeRange;
+
+  ContentChildField(this.fieldName, {this.nameRange, this.typeRange});
+}
+
+class ContentChild {
+  final ContentChildField field;
+  final AbstractQueriedChildType query;
+
+  ContentChild(this.field, this.query);
+}
+
+/// The model of an Angular component.
 class Component extends AbstractDirective {
   View view;
+  @override
   final bool isHtml;
 
-  /**
-    * List of <ng-content> selectors in this component's view
-    */
-  List<NgContent> ngContents = <NgContent>[];
+  /// List of <ng-content> selectors in this component's view
+  final ngContents = <NgContent>[];
 
   Component(dart.ClassElement classElement,
       {AngularElement exportAs,
@@ -133,19 +238,22 @@
       Selector selector,
       List<ElementNameSelector> elementTags,
       this.isHtml,
-      List<NgContent> ngContents})
-      : ngContents = ngContents ?? [],
-        super(classElement,
+      List<NgContent> ngContents,
+      List<ContentChildField> contentChildFields,
+      List<ContentChildField> contentChildrenFields})
+      : super(classElement,
             exportAs: exportAs,
             inputs: inputs,
             outputs: outputs,
             selector: selector,
-            elementTags: elementTags);
+            elementTags: elementTags,
+            contentChildFields: contentChildFields,
+            contentChildrenFields: contentChildrenFields) {
+    this.ngContents.addAll(ngContents ?? []);
+  }
 }
 
-/**
- * An [AngularElement] representing a [dart.Element].
- */
+/// An [AngularElement] representing a [dart.Element].
 class DartElement extends AngularElementImpl {
   final dart.Element element;
 
@@ -155,53 +263,50 @@
             element.source);
 }
 
-/**
- * The model of an Angular directive.
- */
+/// The model of an Angular directive.
 class Directive extends AbstractDirective {
+  @override
+  bool get isHtml => false;
+
   Directive(dart.ClassElement classElement,
       {AngularElement exportAs,
       List<InputElement> inputs,
       List<OutputElement> outputs,
       Selector selector,
-      List<ElementNameSelector> elementTags})
+      List<ElementNameSelector> elementTags,
+      List<ContentChildField> contentChildFields,
+      List<ContentChildField> contentChildrenFields})
       : super(classElement,
             exportAs: exportAs,
             inputs: inputs,
             outputs: outputs,
             selector: selector,
-            elementTags: elementTags);
+            elementTags: elementTags,
+            contentChildFields: contentChildFields,
+            contentChildrenFields: contentChildrenFields);
 }
 
-/**
- * An Angular template in an HTML file.
- */
+/// An Angular template in an HTML file.
 class HtmlTemplate extends Template {
-  static const List<HtmlTemplate> EMPTY_LIST = const <HtmlTemplate>[];
+  static const EMPTY_LIST = const <HtmlTemplate>[];
 
-  /**
-   * The [Source] of the template.
-   */
+  /// The [Source] of the template.
   final Source source;
 
   HtmlTemplate(View view, this.source) : super(view);
 }
 
-/**
- * The model for an Angular input.
- */
+/// The model for an Angular input.
 class InputElement extends AngularElementImpl {
-  static const List<InputElement> EMPTY_LIST = const <InputElement>[];
+  static const EMPTY_LIST = const <InputElement>[];
 
   final dart.PropertyAccessorElement setter;
 
   final dart.DartType setterType;
 
-  /**
-   * The [SourceRange] where [setter] is referenced in the input declaration.
-   * May be the same as this element offset/length in shorthand variants where
-   * names of a input and the setter are the same.
-   */
+  /// The [SourceRange] where [setter] is referenced in the input declaration.
+  /// May be the same as this element offset/length in shorthand variants where
+  /// names of a input and the setter are the same.
   final SourceRange setterRange;
 
   InputElement(String name, int nameOffset, int nameLength, Source source,
@@ -209,26 +314,20 @@
       : super(name, nameOffset, nameLength, source);
 
   @override
-  String toString() {
-    return 'InputElement($name, $nameOffset, $nameLength, $setter)';
-  }
+  String toString() => 'InputElement($name, $nameOffset, $nameLength, $setter)';
 }
 
-/**
- * The model for an Angular output.
- */
+/// The model for an Angular output.
 class OutputElement extends AngularElementImpl {
-  static const List<OutputElement> EMPTY_LIST = const <OutputElement>[];
+  static const EMPTY_LIST = const <OutputElement>[];
 
   final dart.PropertyAccessorElement getter;
 
   final dart.DartType eventType;
 
-  /**
-   * The [SourceRange] where [getter] is referenced in the input declaration.
-   * May be the same as this element offset/length in shorthand variants where
-   * names of a input and the getter are the same.
-   */
+  /// The [SourceRange] where [getter] is referenced in the input declaration.
+  /// May be the same as this element offset/length in shorthand variants where
+  /// names of a input and the getter are the same.
   final SourceRange getterRange;
 
   OutputElement(String name, int nameOffset, int nameLength, Source source,
@@ -236,43 +335,32 @@
       : super(name, nameOffset, nameLength, source);
 
   @override
-  String toString() {
-    return 'OutputElement($name, $nameOffset, $nameLength, $getter)';
-  }
+  String toString() =>
+      'OutputElement($name, $nameOffset, $nameLength, $getter)';
 }
 
-/**
- * A pair of an [SourceRange] and the referenced [AngularElement].
- */
+/// A pair of an [SourceRange] and the referenced [AngularElement].
 class ResolvedRange {
-  /**
-   * The [SourceRange] where [element] is referenced.
-   */
+  /// The [SourceRange] where [element] is referenced.
   final SourceRange range;
 
-  /**
-   * The [AngularElement] referenced at [range].
-   */
+  /// The [AngularElement] referenced at [range].
   final AngularElement element;
 
   ResolvedRange(this.range, this.element);
 
   @override
-  String toString() {
-    return '$range=[$element, '
-        'nameOffset=${element.nameOffset}, '
-        'nameLength=${element.nameLength}, '
-        'source=${element.source}]';
-  }
+  String toString() => '$range=[$element, '
+      'nameOffset=${element.nameOffset}, '
+      'nameLength=${element.nameLength}, '
+      'source=${element.source}]';
 }
 
 class NgContent {
   final int offset;
   final int length;
 
-  /**
-   * NOTE: May contain Null. Null in this case means no selector (all content).
-   */
+  /// NOTE: May contain Null. Null in this case means no selector (all content).
   final Selector selector;
   final int selectorOffset;
   final int selectorLength;
@@ -288,38 +376,26 @@
   bool get matchesAll => selector == null;
 }
 
-/**
- * An Angular template.
- * Templates can be embedded into Dart.
- */
+/// An Angular template.
+/// Templates can be embedded into Dart.
 class Template {
-  static const List<Template> EMPTY_LIST = const <Template>[];
+  static const EMPTY_LIST = const <Template>[];
 
-  /**
-   * The [View] that describes the template.
-   */
+  /// The [View] that describes the template.
   final View view;
 
-  /**
-   * The [ResolvedRange]s of the template.
-   */
-  final List<ResolvedRange> ranges = <ResolvedRange>[];
+  /// The [ResolvedRange]s of the template.
+  final ranges = <ResolvedRange>[];
 
-  /**
-   * The [ElementInfo] that begins the AST of the resolved template
-   */
+  /// The [ElementInfo] that begins the AST of the resolved template
   ElementInfo _ast;
 
-  /**
-   * The errors that are ignored in this template
-   */
-  final Set<String> ignoredErrors = new HashSet<String>();
+  /// The errors that are ignored in this template
+  final ignoredErrors = new HashSet<String>();
 
   Template(this.view);
 
-  /**
-   * Records that the given [element] is referenced at the given [range].
-   */
+  /// Records that the given [element] is referenced at the given [range].
   void addRange(SourceRange range, AngularElement element) {
     assert(range != null);
     assert(range.offset != null);
@@ -328,9 +404,7 @@
   }
 
   @override
-  String toString() {
-    return 'Template(ranges=$ranges)';
-  }
+  String toString() => 'Template(ranges=$ranges)';
 
   ElementInfo get ast => _ast;
   set ast(ElementInfo ast) {
@@ -342,33 +416,43 @@
   }
 }
 
-/**
- * The model of an Angular view.
- */
+/// The model of an Angular view.
 class View implements AnalysisTarget {
-  static const List<View> EMPTY_LIST = const <View>[];
+  static const EMPTY_LIST = const <View>[];
 
-  /**
-   * The [ClassElement] this view is associated with.
-   */
+  /// The [ClassElement] this view is associated with.
   final dart.ClassElement classElement;
 
   final Component component;
   final List<AbstractDirective> directives;
   final List<DirectiveReference> directiveReferences;
-  final Map<String, List<AbstractDirective>> elementTagsInfo =
-      <String, List<AbstractDirective>>{};
   final String templateText;
   final int templateOffset;
   final Source templateUriSource;
   final SourceRange templateUrlRange;
   final dart.Annotation annotation;
 
+  Map<String, List<AbstractDirective>> _elementTagsInfo;
+
   int get end => templateOffset + templateText.length;
 
-  /**
-   * The [Template] of this view, `null` until built.
-   */
+  Map<String, List<AbstractDirective>> get elementTagsInfo {
+    if (_elementTagsInfo == null) {
+      _elementTagsInfo = <String, List<AbstractDirective>>{};
+      for (final directive in directives) {
+        if (directive.elementTags != null && directive.elementTags.isNotEmpty) {
+          for (final elementTag in directive.elementTags) {
+            final tagName = elementTag.toString();
+            _elementTagsInfo.putIfAbsent(tagName, () => <AbstractDirective>[]);
+            _elementTagsInfo[tagName].add(directive);
+          }
+        }
+      }
+    }
+    return _elementTagsInfo;
+  }
+
+  /// The [Template] of this view, `null` until built.
   Template template;
 
   View(this.classElement, this.component, this.directives,
@@ -382,14 +466,11 @@
     component?.view = this;
   }
 
-  /**
-   * The source that contains this view.
-   */
+  /// The source that contains this view.
+  @override
   Source get source => classElement.source;
 
-  /**
-   * The source that contains this template, [source] or [templateUriSource].
-   */
+  /// The source that contains this template, [source] or [templateUriSource].
   Source get templateSource => templateUriSource ?? source;
 
   @override
diff --git a/analyzer_plugin/lib/src/ng_expr_parser.dart b/analyzer_plugin/lib/src/ng_expr_parser.dart
index fc645ee..4ba0c78 100644
--- a/analyzer_plugin/lib/src/ng_expr_parser.dart
+++ b/analyzer_plugin/lib/src/ng_expr_parser.dart
@@ -12,34 +12,31 @@
 
   Token get _currentToken => super.currentToken;
 
-  /**
-   * Parse a bitwise or expression to be treated as a pipe.
-   * Return the resolved left-hand expression as a dynamic type.
-   *
-   *     bitwiseOrExpression ::=
-   *         bitwiseXorExpression ('|' pipeExpression)*
-   */
+  /// Parse a bitwise or expression to be treated as a pipe.
+  /// Return the resolved left-hand expression as a dynamic type.
+  ///
+  ///     bitwiseOrExpression ::=
+  ///         bitwiseXorExpression ('|' pipeExpression)*
   @override
   Expression parseBitwiseOrExpression() {
     Expression expression;
-    Token beforePipeToken = null;
+    Token beforePipeToken;
     expression = parseBitwiseXorExpression();
     while (_currentToken.type == TokenType.BAR) {
-      if (beforePipeToken == null) beforePipeToken = _currentToken.previous;
+      beforePipeToken ??= _currentToken.previous;
       getAndAdvance();
       parsePipeExpression();
     }
     if (beforePipeToken != null) {
-      Token asToken = new KeywordToken(Keyword.AS, 0);
-      Token dynamicIdToken =
+      final asToken = new KeywordToken(Keyword.AS, 0);
+      final dynamicIdToken =
           new StringToken(TokenType.IDENTIFIER, "dynamic", 0);
 
       beforePipeToken.setNext(asToken);
       asToken.setNext(dynamicIdToken);
       dynamicIdToken.setNext(_currentToken);
 
-      Expression dynamicIdentifier =
-          astFactory.simpleIdentifier(dynamicIdToken);
+      final dynamicIdentifier = astFactory.simpleIdentifier(dynamicIdToken);
 
       expression = astFactory.asExpression(
           expression, asToken, astFactory.typeName(dynamicIdentifier, null));
@@ -47,12 +44,10 @@
     return expression;
   }
 
-  /**
-   * Parse a bitwise or expression to be treated as a pipe.
-   * Return the resolved left-hand expression as a dynamic type.
-   *
-   *     pipeExpression ::= identifier[':' expression]*
-   */
+  /// Parse a bitwise or expression to be treated as a pipe.
+  /// Return the resolved left-hand expression as a dynamic type.
+  ///
+  ///     pipeExpression ::= identifier[':' expression]*
   void parsePipeExpression() {
     parseIdentifierList();
     while (_currentToken.type == TokenType.COLON) {
diff --git a/analyzer_plugin/lib/src/resolver.dart b/analyzer_plugin/lib/src/resolver.dart
index 1f253d3..778844a 100644
--- a/analyzer_plugin/lib/src/resolver.dart
+++ b/analyzer_plugin/lib/src/resolver.dart
@@ -1,6 +1,7 @@
 library angular2.src.analysis.analyzer_plugin.src.resolver;
 
 import 'dart:collection';
+import 'package:meta/meta.dart';
 
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/dart/ast/visitor.dart';
@@ -15,21 +16,20 @@
 import 'package:analyzer/src/generated/source.dart';
 import 'package:angular_analyzer_plugin/src/model.dart';
 import 'package:angular_analyzer_plugin/src/selector.dart';
+import 'package:angular_analyzer_plugin/src/standard_components.dart';
 import 'package:angular_analyzer_plugin/tasks.dart';
 import 'package:angular_analyzer_plugin/ast.dart';
 
-/**
- * The implementation of [ElementView] using [AttributeInfo]s.
- */
+/// The implementation of [ElementView] using [AttributeInfo]s.
 class ElementViewImpl implements ElementView {
   @override
-  Map<String, SourceRange> attributeNameSpans = <String, SourceRange>{};
+  final attributeNameSpans = <String, SourceRange>{};
 
   @override
-  Map<String, SourceRange> attributeValueSpans = <String, SourceRange>{};
+  final attributeValueSpans = <String, SourceRange>{};
 
   @override
-  Map<String, String> attributes = <String, String>{};
+  final attributes = <String, String>{};
 
   @override
   SourceRange closingSpan;
@@ -47,8 +47,11 @@
   SourceRange openingNameSpan;
 
   ElementViewImpl(List<AttributeInfo> attributeInfoList, ElementInfo element) {
-    for (AttributeInfo attribute in attributeInfoList) {
-      String name = attribute.name;
+    for (final attribute in attributeInfoList) {
+      if (attribute is TemplateAttribute) {
+        continue;
+      }
+      final name = attribute.name;
       attributeNameSpans[name] =
           new SourceRange(attribute.nameOffset, attribute.name.length);
       if (attribute.value != null) {
@@ -67,9 +70,7 @@
   }
 }
 
-/**
- * A variable defined by a [AbstractDirective].
- */
+/// A variable defined by a [AbstractDirective].
 class InternalVariable {
   final String name;
   final AngularElement element;
@@ -78,108 +79,108 @@
   InternalVariable(this.name, this.element, this.type);
 }
 
-/**
- * [TemplateResolver]s resolve [Template]s.
- */
+/// [TemplateResolver]s resolve [Template]s.
 class TemplateResolver {
   final TypeProvider typeProvider;
   final List<Component> standardHtmlComponents;
   final Map<String, OutputElement> standardHtmlEvents;
   final Map<String, InputElement> standardHtmlAttributes;
   final AnalysisErrorListener errorListener;
+  final StandardAngular standardAngular;
 
   Template template;
   View view;
   Source templateSource;
   ErrorReporter errorReporter;
 
-  /**
-   * The full map of names to internal variables in the current template.
-   */
-  Map<String, InternalVariable> internalVariables =
-      new HashMap<String, InternalVariable>();
+  /// The full map of names to internal variables in the current template.
+  var internalVariables = new HashMap<String, InternalVariable>();
 
-  /**
-   * The full map of names to local variables in the current template.
-   */
-  Map<String, LocalVariable> localVariables =
-      new HashMap<String, LocalVariable>();
+  /// The full map of names to local variables in the current template.
+  var localVariables = new HashMap<String, LocalVariable>();
 
-  TemplateResolver(this.typeProvider, this.standardHtmlComponents,
-      this.standardHtmlEvents, this.standardHtmlAttributes, this.errorListener);
+  TemplateResolver(
+      this.typeProvider,
+      this.standardHtmlComponents,
+      this.standardHtmlEvents,
+      this.standardHtmlAttributes,
+      this.standardAngular,
+      this.errorListener);
 
   void resolve(Template template) {
     this.template = template;
-    this.view = template.view;
-    this.templateSource = view.templateSource;
-    this.errorReporter = new ErrorReporter(errorListener, templateSource);
+    view = template.view;
+    templateSource = view.templateSource;
+    errorReporter = new ErrorReporter(errorListener, templateSource);
 
-    ElementInfo root = template.ast;
+    final root = template.ast;
 
-    var allDirectives = <AbstractDirective>[]
+    final allDirectives = <AbstractDirective>[]
       ..addAll(standardHtmlComponents)
       ..addAll(view.directives);
-    DirectiveResolver directiveResolver = new DirectiveResolver(
-        allDirectives, templateSource, template, errorListener);
+
+    final directiveResolver = new DirectiveResolver(
+        allDirectives,
+        templateSource,
+        template,
+        standardAngular,
+        errorReporter,
+        errorListener);
     root.accept(directiveResolver);
+    final contentResolver =
+        new ComponentContentResolver(templateSource, template, errorListener);
+    root.accept(contentResolver);
 
     _resolveScope(root);
   }
 
-  /**
-   * Resolve the given [element]. This will either be a template or the root of
-   * the template, meaning it has its own scope. We have to resolve the
-   * outermost scopes first so that ngFor variables have types.
-   *
-   * See the comment block for [PrepareScopeVisitor] for the most detailed
-   * breakdown of what we do and why.
-   *
-   * Requires that we've already resolved the directives down the tree. 
-   */
+  /// Resolve the given [element]. This will either be a template or the root of
+  /// the template, meaning it has its own scope. We have to resolve the
+  /// outermost scopes first so that ngFor variables have types.
+  ///
+  /// See the comment block for [PrepareScopeVisitor] for the most detailed
+  /// breakdown of what we do and why.
+  ///
+  /// Requires that we've already resolved the directives down the tree.
   void _resolveScope(ElementInfo element) {
     if (element == null) {
       return;
     }
     // apply template attributes
-    Map<String, LocalVariable> oldLocalVariables = localVariables;
-    Map<String, InternalVariable> oldInternalVariables = internalVariables;
+    final oldLocalVariables = localVariables;
+    final oldInternalVariables = internalVariables;
     internalVariables = new HashMap.from(internalVariables);
     localVariables = new HashMap.from(localVariables);
     try {
-      DartVariableManager dartVarManager =
+      final dartVarManager =
           new DartVariableManager(template, templateSource, errorListener);
       // Prepare the scopes
-      element.accept(new PrepareScopeVisitor(
-          internalVariables,
-          localVariables,
-          template,
-          templateSource,
-          typeProvider,
-          dartVarManager,
-          errorListener));
-      // Load $event into the scopes
-      element.accept(new PrepareEventScopeVisitor(
-          standardHtmlEvents,
-          template,
-          templateSource,
-          localVariables,
-          typeProvider,
-          dartVarManager,
-          errorListener));
-      // Resolve the scopes
-      element.accept(new SingleScopeResolver(
-          standardHtmlAttributes,
-          view,
-          template,
-          templateSource,
-          typeProvider,
-          errorListener,
-          errorReporter));
+      element
+        ..accept(new PrepareScopeVisitor(
+            internalVariables,
+            localVariables,
+            template,
+            templateSource,
+            typeProvider,
+            dartVarManager,
+            errorListener))
+        // Load $event into the scopes
+        ..accept(new PrepareEventScopeVisitor(
+            standardHtmlEvents,
+            template,
+            templateSource,
+            localVariables,
+            typeProvider,
+            dartVarManager,
+            errorListener))
+        // Resolve the scopes
+        ..accept(new SingleScopeResolver(standardHtmlAttributes, view, template,
+            templateSource, typeProvider, errorListener, errorReporter));
 
       // Now the next scope is ready to be resolved
-      var tplSearch = new NextTemplateElementsSearch();
+      final tplSearch = new NextTemplateElementsSearch();
       element.accept(tplSearch);
-      for (ElementInfo templateElement in tplSearch.results) {
+      for (final templateElement in tplSearch.results) {
         _resolveScope(templateElement);
       }
     } finally {
@@ -189,10 +190,8 @@
   }
 }
 
-/**
- * An [AstVisitor] that records references to Dart [Element]s into
- * the given [template].
- */
+/// An [AstVisitor] that records references to Dart [Element]s into
+/// the given [template].
 class _DartReferencesRecorder extends RecursiveAstVisitor {
   final Map<Element, AngularElement> dartToAngularMap;
   final Template template;
@@ -200,59 +199,57 @@
   _DartReferencesRecorder(this.template, this.dartToAngularMap);
 
   @override
-  visitSimpleIdentifier(SimpleIdentifier node) {
-    Element dartElement = node.bestElement;
+  void visitSimpleIdentifier(SimpleIdentifier node) {
+    final dartElement = node.bestElement;
     if (dartElement != null) {
-      AngularElement angularElement = dartToAngularMap[dartElement];
-      if (angularElement == null) {
-        angularElement = new DartElement(dartElement);
-      }
-      SourceRange range = new SourceRange(node.offset, node.length);
+      final angularElement =
+          dartToAngularMap[dartElement] ?? new DartElement(dartElement);
+      final range = new SourceRange(node.offset, node.length);
       template.addRange(range, angularElement);
     }
   }
 }
 
-/**
- * Probably the most important visitor to understand in how we process angular
- * templates.
- *
- * First its important to note how angular scopes are determined by templates;
- * that's how ngFor adds a variable below. Its also important to note that
- * unlike in most languages, angular template semantics lets you use a variable
- * before its declared, ie `<a>{{b}}</a><p #b></p>` so long as they share a
- * scope. Also note that a template both ends a scope and begins it: all
- * the bindings in the template are from the old scope, and yet let-vars add to
- * the new new scope.
- *
- * This means we need to have a multiple-pass process, and that means we spend
- * a good chunk of time merely following the rules of scoping. This visitor
- * will enforce that for you.
- *
- * Just don't @override visitElementInfo (or do so carefully), and this visitor
- * naturally walk over all the attributes in scope by what you give it. You can
- * also hook into what happens when it hits the elements by overriding:
- *
- * * visitBorderScopeTemplateAttribute(templateAttribute)
- * * visitScopeRootElementWithTemplateAttribute(element)
- * * visitBorderScopeTemplateElement(element)
- * * visitScopeRootTemplateElement(element)
- * * visitElementInScope(element)
- *
- * Which should allow you to do specialty things, such as what the
- * [PrepareScopeVisitor] does by using out-of-scope properties to affect the
- * in-scope ones.
- */
+/// Probably the most important visitor to understand in how we process angular
+/// templates.
+///
+/// First its important to note how angular scopes are determined by templates;
+/// that's how ngFor adds a variable below. Its also important to note that
+/// unlike in most languages, angular template semantics lets you use a variable
+/// before its declared, ie `<a>{{b}}</a><p #b></p>` so long as they share a
+/// scope. Also note that a template both ends a scope and begins it: all
+/// the bindings in the template are from the old scope, and yet let-vars add to
+/// the new new scope.
+///
+/// This means we need to have a multiple-pass process, and that means we spend
+/// a good chunk of time merely following the rules of scoping. This visitor
+/// will enforce that for you.
+///
+/// Just don't @override visitElementInfo (or do so carefully), and this visitor
+/// naturally walk over all the attributes in scope by what you give it. You can
+/// also hook into what happens when it hits the elements by overriding:
+///
+/// * visitBorderScopeTemplateAttribute(templateAttribute)
+/// * visitScopeRootElementWithTemplateAttribute(element)
+/// * visitBorderScopeTemplateElement(element)
+/// * visitScopeRootTemplateElement(element)
+/// * visitElementInScope(element)
+///
+/// Which should allow you to do specialty things, such as what the
+/// [PrepareScopeVisitor] does by using out-of-scope properties to affect the
+/// in-scope ones.
 class AngularScopeVisitor extends AngularAstVisitor {
   bool visitingRoot = true;
 
+  @override
   void visitDocumentInfo(DocumentInfo document) {
     visitingRoot = false;
     visitElementInScope(document);
   }
 
+  @override
   void visitElementInfo(ElementInfo element) {
-    var isRoot = visitingRoot;
+    final isRoot = visitingRoot;
     visitingRoot = false;
     if (element.templateAttribute != null) {
       if (!isRoot) {
@@ -274,22 +271,22 @@
 
   void visitScopeRootTemplateElement(ElementInfo element) {
     // the children are in this scope, the template itself is borderlands
-    for (NodeInfo child in element.childNodes) {
+    for (var child in element.childNodes) {
       child.accept(this);
     }
   }
 
   void visitBorderScopeTemplateElement(ElementInfo element) {
     // the attributes are in this scope, the children aren't
-    for (AttributeInfo attr in element.attributes) {
+    for (final attr in element.attributes) {
       attr.accept(this);
     }
   }
 
   void visitScopeRootElementWithTemplateAttribute(ElementInfo element) {
-    var children =
+    final children =
         element.children.where((child) => child is! TemplateAttribute);
-    for (AngularAstNode child in children) {
+    for (final child in children) {
       child.accept(this);
     }
   }
@@ -301,36 +298,30 @@
   }
 
   void visitElementInScope(ElementInfo element) {
-    for (NodeInfo child in element.children) {
+    for (final child in element.children) {
       child.accept(this);
     }
   }
 }
 
-/**
- * We have to collect all vars and their types before we can resolve the
- * bindings, since variables can be used before they are declared. This does
- * that. 
- *
- * It loads each node's [localVariables] property so that the resolver has
- * everything it needs, keeping those local variables around for autocomplete.
- * As the scope is built up it is attached to the nodes -- and thanks to
- * mutability + a shared reference, that works just fine.
- *
- * However, `$event` vars require a copy of the scope, not a shared reference,
- * so that the `$event` can be added. Therefore this visitor does not handle
- * output bindings. That is [PrepareEventScopeVisitor]'s job, only to be
- * performed after this step has completed.
- */
+/// We have to collect all vars and their types before we can resolve the
+/// bindings, since variables can be used before they are declared. This does
+/// that.
+///
+/// It loads each node's [localVariables] property so that the resolver has
+/// everything it needs, keeping those local variables around for autocomplete.
+/// As the scope is built up it is attached to the nodes -- and thanks to
+/// mutability + a shared reference, that works just fine.
+///
+/// However, `$event` vars require a copy of the scope, not a shared reference,
+/// so that the `$event` can be added. Therefore this visitor does not handle
+/// output bindings. That is [PrepareEventScopeVisitor]'s job, only to be
+/// performed after this step has completed.
 class PrepareScopeVisitor extends AngularScopeVisitor {
-  /**
-   * The full map of names to internal variables in the current scope
-   */
+  /// The full map of names to internal variables in the current scope
   final Map<String, InternalVariable> internalVariables;
 
-  /**
-   * The full map of names to local variables in the current scope
-   */
+  /// The full map of names to local variables in the current scope
   final Map<String, LocalVariable> localVariables;
 
   final Template template;
@@ -352,7 +343,7 @@
 
   @override
   void visitScopeRootTemplateElement(ElementInfo element) {
-    for (AbstractDirective directive in element.directives) {
+    for (final directive in element.directives) {
       _defineDirectiveVariables(element.attributes, directive);
       // This must be here for <template> tags.
       _defineNgForVariables(element.attributes, directive);
@@ -366,11 +357,11 @@
 
   @override
   void visitScopeRootElementWithTemplateAttribute(ElementInfo element) {
-    TemplateAttribute templateAttr = element.templateAttribute;
+    final templateAttr = element.templateAttribute;
 
     // If this is how our scope begins, like we're within an ngFor, then
     // let the ngFor alter the current scope.
-    for (AbstractDirective directive in templateAttr.directives) {
+    for (final directive in templateAttr.directives) {
       _defineDirectiveVariables(templateAttr.virtualAttributes, directive);
       _defineNgForVariables(templateAttr.virtualAttributes, directive);
     }
@@ -378,7 +369,7 @@
     _defineLocalVariablesForAttributes(templateAttr.virtualAttributes);
 
     // Make sure the regular element also alters the current scope
-    for (AbstractDirective directive in element.directives) {
+    for (final directive in element.directives) {
       _defineDirectiveVariables(element.attributes, directive);
       // This must be here for <template> tags.
       _defineNgForVariables(element.attributes, directive);
@@ -401,7 +392,7 @@
   @override
   void visitElementInScope(ElementInfo element) {
     // Regular element or component. Look for `#var`s.
-    for (AbstractDirective directive in element.directives) {
+    for (final directive in element.directives) {
       _defineDirectiveVariables(element.attributes, directive);
       // This must be here for <template> tags.
       _defineNgForVariables(element.attributes, directive);
@@ -414,12 +405,12 @@
   }
 
   @override
-  visitMustache(Mustache mustache) {
+  void visitMustache(Mustache mustache) {
     mustache.localVariables = localVariables;
   }
 
   @override
-  visitExpressionBoundAttr(ExpressionBoundAttribute attr) {
+  void visitExpressionBoundAttr(ExpressionBoundAttribute attr) {
     attr.localVariables = localVariables;
   }
 
@@ -439,11 +430,11 @@
           new InternalVariable('first', dartElem, typeProvider.boolType);
       internalVariables['last'] =
           new InternalVariable('last', dartElem, typeProvider.boolType);
-      for (AttributeInfo attribute in attributes) {
+      for (final attribute in attributes) {
         if (attribute is ExpressionBoundAttribute &&
             attribute.name == 'ngForOf' &&
             attribute.expression != null) {
-          DartType itemType = _getIterableItemType(attribute.expression);
+          final itemType = _getIterableItemType(attribute.expression);
           internalVariables[r'$implicit'] =
               new InternalVariable(r'$implicit', dartElem, itemType);
         }
@@ -451,56 +442,49 @@
     }
   }
 
-  /**
-   * Defines type of variables defined by the given [directive].
-   */
+  /// Defines type of variables defined by the given [directive].
   void _defineDirectiveVariables(
       List<AttributeInfo> attributes, AbstractDirective directive) {
     // add "exportAs"
     {
-      AngularElement exportAs = directive.exportAs;
+      final exportAs = directive.exportAs;
       if (exportAs != null) {
-        String name = exportAs.name;
-        InterfaceType type = directive.classElement.type;
+        final name = exportAs.name;
+        final type = directive.classElement.type;
         internalVariables[name] = new InternalVariable(name, exportAs, type);
       }
     }
     // add "$implicit
     if (directive is Component) {
-      ClassElement classElement = directive.classElement;
+      final classElement = directive.classElement;
       internalVariables[r'$implicit'] = new InternalVariable(
           r'$implicit', new DartElement(classElement), classElement.type);
     }
   }
 
-  /**
-   * Define new local variables into [localVariables] for `#name` attributes.
-   */
+  /// Define new local variables into [localVariables] for `#name` attributes.
   void _defineLocalVariablesForAttributes(List<AttributeInfo> attributes) {
-    for (AttributeInfo attribute in attributes) {
-      int offset = attribute.nameOffset;
-      String name = attribute.name;
+    for (final attribute in attributes) {
+      var offset = attribute.nameOffset;
+      var name = attribute.name;
 
       // check if defines local variable
-      var isLet = name.startsWith('let-'); // ng-for
-      var isRef = name.startsWith('ref-'); // not ng-for
-      var isHash = name.startsWith('#'); // not ng-for
-      var isVar =
+      final isLet = name.startsWith('let-'); // ng-for
+      final isRef = name.startsWith('ref-'); // not ng-for
+      final isHash = name.startsWith('#'); // not ng-for
+      final isVar =
           name.startsWith('var-'); // either (deprecated but still works)
       if (isHash || isLet || isVar || isRef) {
-        var prefixLen = isHash ? 1 : 4;
+        final prefixLen = isHash ? 1 : 4;
         name = name.substring(prefixLen);
         offset += prefixLen;
 
         // prepare internal variable name
-        String internalName = attribute.value;
-        if (internalName == null) {
-          internalName = r'$implicit';
-        }
+        final internalName = attribute.value ?? r'$implicit';
 
         // maybe an internal variable reference
         DartType type;
-        InternalVariable internalVar = internalVariables[internalName];
+        final internalVar = internalVariables[internalName];
         if (internalVar != null) {
           type = internalVar.type;
           // add internal variable reference
@@ -519,15 +503,13 @@
         }
 
         // any unmatched values should be dynamic to prevent secondary errors
-        if (type == null) {
-          type = typeProvider.dynamicType;
-        }
+        type ??= typeProvider.dynamicType;
 
         // add a new local variable with type
-        LocalVariableElement dartVariable =
+        final localVariableElement =
             dartVariableManager.newLocalVariableElement(-1, name, type);
-        LocalVariable localVariable = new LocalVariable(
-            name, offset, name.length, templateSource, dartVariable);
+        final localVariable = new LocalVariable(
+            name, offset, name.length, templateSource, localVariableElement);
         localVariables[name] = localVariable;
         // add local declaration
         template.addRange(
@@ -539,11 +521,11 @@
   }
 
   DartType _getIterableItemType(Expression expression) {
-    DartType itemsType = expression.bestType;
+    final itemsType = expression.bestType;
     if (itemsType is InterfaceType) {
-      DartType iteratorType = _lookupGetterReturnType(itemsType, 'iterator');
+      final iteratorType = _lookupGetterReturnType(itemsType, 'iterator');
       if (iteratorType is InterfaceType) {
-        DartType currentType = _lookupGetterReturnType(iteratorType, 'current');
+        final currentType = _lookupGetterReturnType(iteratorType, 'current');
         if (currentType != null) {
           return currentType;
         }
@@ -552,13 +534,10 @@
     return typeProvider.dynamicType;
   }
 
-  /**
-   * Return the return type of the executable element with the given [name].
-   * May return `null` if the [type] does not define one.
-   */
-  DartType _lookupGetterReturnType(InterfaceType type, String name) {
-    return type.lookUpInheritedGetter(name)?.returnType;
-  }
+  /// Return the return type of the executable element with the given [name].
+  /// May return `null` if the [type] does not define one.
+  DartType _lookupGetterReturnType(InterfaceType type, String name) =>
+      type.lookUpInheritedGetter(name)?.returnType;
 }
 
 class DartVariableManager {
@@ -577,23 +556,22 @@
     // ensure artificial Dart elements in the template source
     if (htmlMethodElement == null) {
       htmlCompilationUnitElement =
-          new CompilationUnitElementImpl(templateSource.fullName);
-      htmlCompilationUnitElement.source = templateSource;
+          new CompilationUnitElementImpl(templateSource.fullName)
+            ..source = templateSource;
       htmlClassElement = new ClassElementImpl('AngularTemplateClass', -1);
       htmlCompilationUnitElement.types = <ClassElement>[htmlClassElement];
       htmlMethodElement = new MethodElementImpl('angularTemplateMethod', -1);
       htmlClassElement.methods = <MethodElement>[htmlMethodElement];
     }
     // add a new local variable
-    LocalVariableElementImpl localVariable =
-        new LocalVariableElementImpl(name, offset);
+    final localVariable = new LocalVariableElementImpl(name, offset);
     localVariable.name.length;
     localVariable.type = type;
 
     // add the local variable to the enclosing element
-    var localVariables = new List<LocalVariableElement>();
-    localVariables.addAll(htmlMethodElement.localVariables);
-    localVariables.add(localVariable);
+    final localVariables = <LocalVariableElement>[]
+      ..addAll(htmlMethodElement.localVariables)
+      ..add(localVariable);
     htmlMethodElement.localVariables = localVariables;
     return localVariable;
   }
@@ -619,30 +597,29 @@
       this.errorListener);
 
   @override
-  visitElementInfo(ElementInfo elem) {
+  void visitElementInfo(ElementInfo elem) {
     directives = elem.directives;
     super.visitElementInfo(elem);
   }
 
   @override
-  visitTemplateAttr(TemplateAttribute templateAttr) {
+  void visitTemplateAttr(TemplateAttribute templateAttr) {
     directives = templateAttr.directives;
     super.visitTemplateAttr(templateAttr);
   }
 
   @override
-  visitStatementsBoundAttr(StatementsBoundAttribute attr) {
-    DartType eventType = typeProvider.dynamicType;
+  void visitStatementsBoundAttr(StatementsBoundAttribute attr) {
+    var eventType = typeProvider.dynamicType;
     var matched = false;
 
-    for (DirectiveBinding directiveBinding in attr.parent.boundDirectives) {
-      for (OutputElement output in directiveBinding.boundDirective.outputs) {
+    for (final directiveBinding in attr.parent.boundDirectives) {
+      for (final output in directiveBinding.boundDirective.outputs) {
         //TODO what if this matches two directives?
         if (output.name == attr.name) {
           eventType = output.eventType;
           matched = true;
-          SourceRange range =
-              new SourceRange(attr.nameOffset, attr.name.length);
+          final range = new SourceRange(attr.nameOffset, attr.name.length);
           template.addRange(range, output);
           directiveBinding.outputBindings.add(new OutputBinding(output, attr));
         }
@@ -651,11 +628,11 @@
 
     //standard HTML events bubble up, so everything supports them
     if (!matched) {
-      var standardHtmlEvent = standardHtmlEvents[attr.name];
+      final standardHtmlEvent = standardHtmlEvents[attr.name];
       if (standardHtmlEvent != null) {
         matched = true;
         eventType = standardHtmlEvent.eventType;
-        SourceRange range = new SourceRange(attr.nameOffset, attr.name.length);
+        final range = new SourceRange(attr.nameOffset, attr.name.length);
         template.addRange(range, standardHtmlEvent);
         attr.parent.boundStandardOutputs
             .add(new OutputBinding(standardHtmlEvent, attr));
@@ -672,22 +649,20 @@
     }
 
     attr.localVariables = new HashMap.from(localVariables);
-    LocalVariableElement dartVariable =
+    final localVariableElement =
         dartVariableManager.newLocalVariableElement(-1, r'$event', eventType);
-    LocalVariable localVariable =
-        new LocalVariable(r'$event', -1, 6, templateSource, dartVariable);
+    final localVariable = new LocalVariable(
+        r'$event', -1, 6, templateSource, localVariableElement);
     attr.localVariables[r'$event'] = localVariable;
   }
 }
 
-/**
- * Use this visitor to find the nested scopes within the [ElementInfo]
- * you visit.
- */
+/// Use this visitor to find the nested scopes within the [ElementInfo]
+/// you visit.
 class NextTemplateElementsSearch extends AngularAstVisitor {
   bool visitingRoot = true;
 
-  List<ElementInfo> results = [];
+  final results = <ElementInfo>[];
 
   @override
   void visitDocumentInfo(DocumentInfo document) {
@@ -705,7 +680,7 @@
     }
 
     visitingRoot = false;
-    for (NodeInfo child in element.childNodes) {
+    for (final child in element.childNodes) {
       child.accept(this);
     }
   }
@@ -716,71 +691,221 @@
   final Source templateSource;
   final Template template;
   final AnalysisErrorListener errorListener;
+  final ErrorReporter _errorReporter;
+  final StandardAngular _standardAngular;
+  final outerBindings = <DirectiveBinding>[];
+  final outerElements = <ElementInfo>[];
 
   DirectiveResolver(this.allDirectives, this.templateSource, this.template,
-      this.errorListener);
+      this._standardAngular, this._errorReporter, this.errorListener);
 
   @override
   void visitElementInfo(ElementInfo element) {
+    outerElements.add(element);
     if (element.templateAttribute != null) {
       visitTemplateAttr(element.templateAttribute);
     }
 
-    ElementView elementView = new ElementViewImpl(element.attributes, element);
-    bool tagIsResolved = element.tagMatchedAsTransclusion;
-    bool tagIsStandard = _isStandardTagName(element.localName);
-    Component component;
+    final elementView = new ElementViewImpl(element.attributes, element);
 
-    for (AbstractDirective directive in allDirectives) {
-      SelectorMatch match = directive.selector.match(elementView, template);
+    final containingDirectivesCount = outerBindings.length;
+    for (final directive in allDirectives) {
+      final match = directive.selector.match(elementView, template);
       if (match != SelectorMatch.NoMatch) {
-        element.boundDirectives.add(new DirectiveBinding(directive));
+        final binding = new DirectiveBinding(directive);
+        element.boundDirectives.add(binding);
         if (match == SelectorMatch.TagMatch) {
-          tagIsResolved = true;
+          element.tagMatchedAsDirective = true;
         }
 
-        if (directive is Component) {
-          component = directive;
-          // TODO better html tag detection, see #248
-          tagIsStandard = component.isHtml;
+        // optimization: only add the bindings that care about content child
+        if (directive.contentChilds.isNotEmpty ||
+            directive.contentChildren.isNotEmpty) {
+          outerBindings.add(binding);
+        }
+
+        // Specifically exclude NgIf and NgFor, they have their own error since
+        // we *know* they require a template.
+        if (directive.looksLikeTemplate &&
+            !element.isTemplate &&
+            directive.classElement.name != "NgIf" &&
+            directive.classElement.name != "NgFor") {
+          _reportErrorForRange(
+              element.openingSpan,
+              AngularWarningCode.CUSTOM_DIRECTIVE_MAY_REQUIRE_TEMPLATE,
+              [directive.classElement.name]);
         }
       }
     }
-    if (!tagIsStandard && !tagIsResolved) {
+
+    if (!element.isTemplate) {
+      _checkNoStructuralDirectives(element.attributes);
+    }
+
+    recordContentChildren(element);
+
+    for (final child in element.childNodes) {
+      child.accept(this);
+    }
+
+    outerBindings.removeRange(containingDirectivesCount, outerBindings.length);
+    outerElements.removeLast();
+  }
+
+  @override
+  void visitTemplateAttr(TemplateAttribute attr) {
+    final elementView = new ElementViewImpl(attr.virtualAttributes, null);
+    for (final directive in allDirectives) {
+      if (directive.selector.match(elementView, template) !=
+          SelectorMatch.NoMatch) {
+        attr.boundDirectives.add(new DirectiveBinding(directive));
+      }
+    }
+
+    final templateAttrIsUsed =
+        attr.directives.any((directive) => directive.looksLikeTemplate);
+
+    if (!templateAttrIsUsed) {
+      _reportErrorForRange(
+          new SourceRange(attr.originalNameOffset, attr.originalName.length),
+          AngularWarningCode.TEMPLATE_ATTR_NOT_USED);
+    }
+  }
+
+  void _checkNoStructuralDirectives(List<AttributeInfo> attributes) {
+    for (final attribute in attributes) {
+      if (attribute is! TextAttribute) {
+        continue;
+      }
+
+      if (attribute.name == 'ngFor' || attribute.name == 'ngIf') {
+        _reportErrorForRange(
+            new SourceRange(attribute.nameOffset, attribute.name.length),
+            AngularWarningCode.STRUCTURAL_DIRECTIVES_REQUIRE_TEMPLATE,
+            [attribute.name]);
+      }
+    }
+  }
+
+  void recordContentChildren(ElementInfo element) {
+    for (final binding in outerBindings) {
+      for (var contentChild in binding.boundDirective.contentChilds) {
+        // an already matched ContentChild shouldn't look inside that match
+        if (binding.contentChildBindings[contentChild]?.boundElements
+                ?.any(outerElements.contains) ==
+            true) {
+          continue;
+        }
+
+        if (contentChild.query
+            .match(element, _standardAngular, _errorReporter)) {
+          binding.contentChildBindings.putIfAbsent(
+              contentChild,
+              () => new ContentChildBinding(
+                  binding.boundDirective, contentChild));
+
+          if (binding
+              .contentChildBindings[contentChild].boundElements.isNotEmpty) {
+            _errorReporter.reportErrorForOffset(
+                AngularWarningCode.SINGULAR_CHILD_QUERY_MATCHED_MULTIPLE_TIMES,
+                element.offset,
+                element.length, [
+              binding.boundDirective.classElement.name,
+              contentChild.field.fieldName
+            ]);
+          }
+          binding.contentChildBindings[contentChild].boundElements.add(element);
+
+          if (element.parent.boundDirectives.contains(binding)) {
+            element.tagMatchedAsImmediateContentChild = true;
+          }
+        }
+      }
+
+      for (var contentChildren in binding.boundDirective.contentChildren) {
+        if (contentChildren.query
+            .match(element, _standardAngular, _errorReporter)) {
+          binding.contentChildrenBindings.putIfAbsent(
+              contentChildren,
+              () => new ContentChildBinding(
+                  binding.boundDirective, contentChildren));
+          binding.contentChildrenBindings[contentChildren].boundElements
+              .add(element);
+
+          if (element.parent.boundDirectives.contains(binding)) {
+            element.tagMatchedAsImmediateContentChild = true;
+          }
+        }
+      }
+    }
+  }
+
+  void _reportErrorForRange(SourceRange range, ErrorCode errorCode,
+      [List<Object> arguments]) {
+    errorListener.onError(new AnalysisError(
+        templateSource, range.offset, range.length, errorCode, arguments));
+  }
+}
+
+class ComponentContentResolver extends AngularAstVisitor {
+  final Source templateSource;
+  final Template template;
+  final AnalysisErrorListener errorListener;
+
+  ComponentContentResolver(
+      this.templateSource, this.template, this.errorListener);
+
+  @override
+  void visitElementInfo(ElementInfo element) {
+    // TODO should we visitTemplateAttr(element.templateAttribute) ??
+    var tagIsStandard = _isStandardTagName(element.localName);
+    Component component;
+
+    for (final directive in element.directives) {
+      if (directive is Component) {
+        component = directive;
+        // TODO better html tag detection, see #248
+        tagIsStandard = component.isHtml;
+      }
+    }
+
+    if (!tagIsStandard &&
+        !element.tagMatchedAsTransclusion &&
+        !element.tagMatchedAsDirective) {
       _reportErrorForRange(element.openingNameSpan,
           AngularWarningCode.UNRESOLVED_TAG, [element.localName]);
     }
 
-    if (!element.isOrHasTemplateAttribute) {
-      _checkNoStructuralDirectives(element.attributes);
-    }
-
     if (!tagIsStandard) {
-      _checkTranscludedContent(component, element.childNodes, tagIsStandard);
+      checkTransclusionsContentChildren(component, element.childNodes,
+          tagIsStandard: tagIsStandard);
     }
 
-    for (NodeInfo child in element.childNodes) {
+    for (final child in element.childNodes) {
       child.accept(this);
     }
   }
 
-  void _checkTranscludedContent(
-      Component component, List<NodeInfo> children, bool tagIsStandard) {
+  void checkTransclusionsContentChildren(
+      Component component, List<NodeInfo> children,
+      {@required bool tagIsStandard}) {
     if (component?.ngContents == null) {
       return;
     }
 
-    bool acceptAll = component.ngContents.any((s) => s.matchesAll);
-    for (NodeInfo child in children) {
+    final acceptAll = component.ngContents.any((s) => s.matchesAll);
+    for (final child in children) {
       if (child is TextInfo && !acceptAll && child.text.trim() != "") {
         _reportErrorForRange(new SourceRange(child.offset, child.length),
             AngularWarningCode.CONTENT_NOT_TRANSCLUDED);
       } else if (child is ElementInfo) {
-        ElementView view = new ElementViewImpl(child.attributes, child);
-        bool matched = acceptAll;
-        bool matchedTag = false;
-        for (NgContent ngContent in component.ngContents) {
-          SelectorMatch match = ngContent.matchesAll
+        final view = new ElementViewImpl(child.attributes, child);
+
+        var matched = acceptAll;
+        var matchedTag = false;
+
+        for (final ngContent in component.ngContents) {
+          final match = ngContent.matchesAll
               ? SelectorMatch.NonTagMatch
               : ngContent.selector.match(view, template);
           if (match != SelectorMatch.NoMatch) {
@@ -789,6 +914,8 @@
           }
         }
 
+        matched = matched || child.tagMatchedAsImmediateContentChild;
+
         if (!matched) {
           _reportErrorForRange(new SourceRange(child.offset, child.length),
               AngularWarningCode.CONTENT_NOT_TRANSCLUDED);
@@ -799,39 +926,15 @@
     }
   }
 
-  @override
-  void visitTemplateAttr(TemplateAttribute attr) {
-    // TODO: report error if no directives matched here?
-    ElementView elementView = new ElementViewImpl(attr.virtualAttributes, null);
-    for (AbstractDirective directive in allDirectives) {
-      if (directive.selector.match(elementView, template) !=
-          SelectorMatch.NoMatch) {
-        attr.boundDirectives.add(new DirectiveBinding(directive));
-      }
-    }
-  }
-
-  _checkNoStructuralDirectives(List<AttributeInfo> attributes) {
-    for (AttributeInfo attribute in attributes) {
-      if (attribute.name == 'ngFor' || attribute.name == 'ngIf') {
-        _reportErrorForRange(
-            new SourceRange(attribute.nameOffset, attribute.name.length),
-            AngularWarningCode.STRUCTURAL_DIRECTIVES_REQUIRE_TEMPLATE,
-            [attribute.name]);
-      }
-    }
-  }
-
   void _reportErrorForRange(SourceRange range, ErrorCode errorCode,
       [List<Object> arguments]) {
     errorListener.onError(new AnalysisError(
         templateSource, range.offset, range.length, errorCode, arguments));
   }
 
-  /**
-   * Check whether the given [name] is a standard HTML5 tag name.
-   */
+  /// Check whether the given [name] is a standard HTML5 tag name.
   static bool _isStandardTagName(String name) {
+    // ignore: parameter_assignments
     name = name.toLowerCase();
     return !name.contains('-') || name == 'ng-content';
   }
@@ -851,17 +954,25 @@
   @override
   void visitElementInfo(ElementInfo element) {
     if (element.localName != 'ng-content') {
-      for (NodeInfo child in element.childNodes) {
+      for (final child in element.childNodes) {
         child.accept(this);
       }
 
       return;
     }
 
-    List<AttributeInfo> selectorAttrs =
-        element.attributes.where((a) => a.name == 'select');
+    final selectorAttrs = element.attributes.where((a) => a.name == 'select');
 
-    if (selectorAttrs.length == 0) {
+    for (final child in element.childNodes) {
+      if (!child.isSynthetic) {
+        errorReporter.reportErrorForOffset(
+            AngularWarningCode.NG_CONTENT_MUST_BE_EMPTY,
+            element.openingSpan.offset,
+            element.openingSpan.length);
+      }
+    }
+
+    if (selectorAttrs.isEmpty) {
       ngContents.add(new NgContent(element.offset, element.length));
       return;
     }
@@ -869,7 +980,7 @@
     // We don't actually check if selectors.length > 2, because the parser
     // reports that.
     try {
-      AttributeInfo selectorAttr = selectorAttrs.first;
+      final selectorAttr = selectorAttrs.first;
       if (selectorAttr.value == null) {
         errorReporter.reportErrorForOffset(
             AngularWarningCode.CANNOT_PARSE_SELECTOR,
@@ -881,7 +992,7 @@
             selectorAttr.valueOffset - 1,
             2);
       } else {
-        Selector selector = new SelectorParser(
+        final selector = new SelectorParser(
                 source, selectorAttr.valueOffset, selectorAttr.value)
             .parse();
         ngContents.add(new NgContent.withSelector(
@@ -901,14 +1012,12 @@
   }
 }
 
-/**
- * Once all the scopes for all the expressions & statements are prepared, we're
- * ready to resolve all the expressions inside and typecheck everything.
- *
- * This will typecheck the contents of mustaches and attribute bindings against
- * their scopes, and ensure that all attribute bindings exist on a directive and
- * match the type of the binding where there is one. Then records references.
- */
+/// Once all the scopes for all the expressions & statements are prepared, we're
+/// ready to resolve all the expressions inside and typecheck everything.
+///
+/// This will typecheck the contents of mustaches and attribute bindings against
+/// their scopes, and ensure that all attribute bindings exist on a directive and
+/// match the type of the binding where there is one. Then records references.
 class SingleScopeResolver extends AngularScopeVisitor {
   final Map<String, InputElement> standardHtmlAttributes;
   List<AbstractDirective> directives;
@@ -919,7 +1028,7 @@
   AnalysisErrorListener errorListener;
   ErrorReporter errorReporter;
 
-  static var styleWithPercent = new Set<String>.from([
+  static var styleWithPercent = new Set<String>.from(<String>[
     'border-bottom-left-radius',
     'border-bottom-right-radius',
     'border-image-slice',
@@ -952,9 +1061,7 @@
     'width',
   ]);
 
-  /**
-   * The full map of names to local variables in the current context
-   */
+  /// The full map of names to local variables in the current context
   Map<String, LocalVariable> localVariables;
 
   SingleScopeResolver(
@@ -1006,23 +1113,19 @@
     }
   }
 
-  /**
-   * Resolve output-bound values of [attributes] as statements.
-   */
+  /// Resolve output-bound values of [attributes] as statements.
   @override
   void visitStatementsBoundAttr(StatementsBoundAttribute attribute) {
     localVariables = attribute.localVariables;
     _resolveDartExpressionStatements(attribute.statements);
-    for (Statement statement in attribute.statements) {
+    for (final statement in attribute.statements) {
       _recordAstNodeResolvedRanges(statement);
     }
   }
 
-  /**
-   * Resolve TwoWay-bound values of [attributes] as expressions.
-   */
+  /// Resolve TwoWay-bound values of [attributes] as expressions.
   void _resolveTwoWayBoundAttributeValues(ExpressionBoundAttribute attribute) {
-    bool outputMatched = false;
+    var outputMatched = false;
 
     // empty attribute error registered in converter. Just don't crash.
     if (attribute.expression != null && !attribute.expression.isAssignable) {
@@ -1033,12 +1136,11 @@
           AngularWarningCode.TWO_WAY_BINDING_NOT_ASSIGNABLE));
     }
 
-    for (DirectiveBinding directiveBinding
-        in attribute.parent.boundDirectives) {
-      for (OutputElement output in directiveBinding.boundDirective.outputs) {
-        if (output.name == attribute.name + "Change") {
+    for (final directiveBinding in attribute.parent.boundDirectives) {
+      for (final output in directiveBinding.boundDirective.outputs) {
+        if (output.name == "${attribute.name}Change") {
           outputMatched = true;
-          var eventType = output.eventType;
+          final eventType = output.eventType;
           directiveBinding.outputBindings
               .add(new OutputBinding(output, attribute));
 
@@ -1062,26 +1164,23 @@
           attribute.nameOffset,
           attribute.name.length,
           AngularWarningCode.NONEXIST_TWO_WAY_OUTPUT_BOUND,
-          [attribute.name, attribute.name + "Change"]));
+          [attribute.name, "${attribute.name}Change"]));
     }
 
     _resolveInputBoundAttributeValues(attribute);
   }
 
-  /**
-   * Resolve input-bound values of [attributes] as expressions.
-   * Also used by _resolveTwoWwayBoundAttributeValues.
-   */
+  /// Resolve input-bound values of [attributes] as expressions.
+  /// Also used by _resolveTwoWwayBoundAttributeValues.
   void _resolveInputBoundAttributeValues(ExpressionBoundAttribute attribute) {
-    bool inputMatched = false;
+    var inputMatched = false;
 
-    for (DirectiveBinding directiveBinding
-        in attribute.parent.boundDirectives) {
-      for (InputElement input in directiveBinding.boundDirective.inputs) {
+    for (final directiveBinding in attribute.parent.boundDirectives) {
+      for (final input in directiveBinding.boundDirective.inputs) {
         if (input.name == attribute.name) {
           _typecheckMatchingInput(attribute, input);
 
-          SourceRange range =
+          final range =
               new SourceRange(attribute.nameOffset, attribute.name.length);
           template.addRange(range, input);
           directiveBinding.inputBindings
@@ -1093,12 +1192,11 @@
     }
 
     if (!inputMatched) {
-      InputElement standardHtmlAttribute =
-          standardHtmlAttributes[attribute.name];
+      final standardHtmlAttribute = standardHtmlAttributes[attribute.name];
       if (standardHtmlAttribute != null) {
         _typecheckMatchingInput(attribute, standardHtmlAttribute);
 
-        SourceRange range =
+        final range =
             new SourceRange(attribute.nameOffset, attribute.name.length);
         template.addRange(range, standardHtmlAttribute);
         attribute.parent.boundStandardInputs
@@ -1118,19 +1216,21 @@
     }
   }
 
-  /**
-   * Resolve input-bound values of [attributes] as strings, if they match. Note,
-   * this does not report an error un unmatched attributes, but it will report
-   * the range, and ensure that input bindings are string-assingable.
-   */
+  /// Resolve input-bound values of [attributes] as strings, if they match. Note,
+  /// this does not report an error un unmatched attributes, but it will report
+  /// the range, and ensure that input bindings are string-assingable.
+  @override
   void visitTextAttr(TextAttribute attribute) {
-    for (DirectiveBinding directiveBinding
-        in attribute.parent.boundDirectives) {
-      for (InputElement input in directiveBinding.boundDirective.inputs) {
+    for (final directiveBinding in attribute.parent.boundDirectives) {
+      for (final input in directiveBinding.boundDirective.inputs) {
         if (input.name == attribute.name) {
-          var inputType = input.setterType;
+          final inputType = input.setterType;
 
-          if (!typeProvider.stringType.isAssignableTo(inputType)) {
+          // Typecheck all but HTML inputs. For those, `width="10"` becomes
+          // `setAttribute("width", "10")`, which is ok. But for directives and
+          // components, this becomes `.someIntProp = "10"` which doesn't work.
+          if (!directiveBinding.boundDirective.isHtml &&
+              !typeProvider.stringType.isAssignableTo(inputType)) {
             errorListener.onError(new AnalysisError(
                 templateSource,
                 attribute.nameOffset,
@@ -1139,7 +1239,7 @@
                 [input.name]));
           }
 
-          SourceRange range =
+          final range =
               new SourceRange(attribute.nameOffset, attribute.name.length);
           template.addRange(range, input);
           directiveBinding.inputBindings
@@ -1147,29 +1247,20 @@
         }
       }
 
-      for (AngularElement elem in directiveBinding.boundDirective.attributes) {
+      for (final elem in directiveBinding.boundDirective.attributes) {
         if (elem.name == attribute.name) {
-          SourceRange range =
+          final range =
               new SourceRange(attribute.nameOffset, attribute.name.length);
           template.addRange(range, elem);
         }
       }
     }
 
-    InputElement standardHtmlAttribute = standardHtmlAttributes[attribute.name];
+    final standardHtmlAttribute = standardHtmlAttributes[attribute.name];
     if (standardHtmlAttribute != null) {
-      // DISABLED per issue #280 until we know better how to validate this case
-      //var inputType = standardHtmlAttribute.setterType;
-      //if (!typeProvider.stringType.isAssignableTo(inputType)) {
-      //  errorListener.onError(new AnalysisError(
-      //      templateSource,
-      //      attribute.nameOffset,
-      //      attribute.name.length,
-      //      AngularWarningCode.STRING_STYLE_INPUT_BINDING_INVALID,
-      //      [attribute.name]));
-      //}
-
-      SourceRange range =
+      // Don't typecheck html inputs. Those become attributes, not properties,
+      // which means strings values are OK.
+      final range =
           new SourceRange(attribute.nameOffset, attribute.name.length);
       template.addRange(range, standardHtmlAttribute);
       attribute.parent.boundStandardInputs
@@ -1184,8 +1275,8 @@
       ExpressionBoundAttribute attr, InputElement input) {
     // half-complete-code case: ensure the expression is actually there
     if (attr.expression != null) {
-      var attrType = attr.expression.bestType;
-      var inputType = input.setterType;
+      final attrType = attr.expression.bestType;
+      final inputType = input.setterType;
 
       if (!attrType.isAssignableTo(inputType)) {
         errorListener.onError(new AnalysisError(
@@ -1198,25 +1289,19 @@
     }
   }
 
-  /**
-   * Quick regex to match the spec, but doesn't handle unicode. They can start
-   * with a dash, but if so must be followed by an alphabetic or underscore or
-   * escaped character. Cannot start with a number.
-   * https://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
-   */
+  /// Quick regex to match the spec, but doesn't handle unicode. They can start
+  /// with a dash, but if so must be followed by an alphabetic or underscore or
+  /// escaped character. Cannot start with a number.
+  /// https://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
   static final RegExp _cssIdentifierRegexp =
       new RegExp(r"^(-?[a-zA-Z_]|\\.)([a-zA-Z0-9\-_]|\\.)*$");
 
-  bool _isCssIdentifier(String input) {
-    return _cssIdentifierRegexp.hasMatch(input);
-  }
+  bool _isCssIdentifier(String input) => _cssIdentifierRegexp.hasMatch(input);
 
-  /**
-   * Resolve attributes of type [class.some-class]="someBoolExpr", ensuring
-   * the class is a valid css identifier and that the expression is of boolean
-   * type
-   */
-  _resolveClassAttribute(ExpressionBoundAttribute attribute) {
+  /// Resolve attributes of type [class.some-class]="someBoolExpr", ensuring
+  /// the class is a valid css identifier and that the expression is of boolean
+  /// type
+  void _resolveClassAttribute(ExpressionBoundAttribute attribute) {
     if (!_isCssIdentifier(attribute.name)) {
       errorListener.onError(new AnalysisError(
           templateSource,
@@ -1238,17 +1323,15 @@
     }
   }
 
-  /**
-   * Resolve attributes of type [style.color]="someExpr" and
-   * [style.background-width.px]="someNumExpr" which bind a css style property
-   * with optional units.
-   */
-  _resolveStyleAttribute(ExpressionBoundAttribute attribute) {
+  /// Resolve attributes of type [style.color]="someExpr" and
+  /// [style.background-width.px]="someNumExpr" which bind a css style property
+  /// with optional units.
+  void _resolveStyleAttribute(ExpressionBoundAttribute attribute) {
     var cssPropertyName = attribute.name;
-    var dotpos = attribute.name.indexOf('.');
+    final dotpos = attribute.name.indexOf('.');
     if (dotpos != -1) {
       cssPropertyName = attribute.name.substring(0, dotpos);
-      var cssUnitName = attribute.name.substring(dotpos + '.'.length);
+      final cssUnitName = attribute.name.substring(dotpos + '.'.length);
       var validUnitName =
           styleWithPercent.contains(cssPropertyName) && cssUnitName == '%';
       validUnitName = validUnitName || _isCssIdentifier(cssUnitName);
@@ -1281,59 +1364,53 @@
     }
   }
 
-  /**
-   * Resolve attributes of type [attribute.some-attribute]="someExpr"
-   */
-  _resolveAttributeBoundAttribute(ExpressionBoundAttribute attribute) {
+  /// Resolve attributes of type [attribute.some-attribute]="someExpr"
+  void _resolveAttributeBoundAttribute(ExpressionBoundAttribute attribute) {
     // TODO validate the type? Or against a dictionary?
     // note that the attribute name is valid by definition as it was discovered
     // within an attribute! (took me a while to realize why I couldn't make any
     // failing tests for this)
   }
 
-  /**
-   * Resolve the given [AstNode] ([expression] or [statement]) and report errors.
-   */
+  /// Resolve the given [AstNode] ([expression] or [statement]) and report errors.
   void _resolveDartAstNode(AstNode astNode, bool acceptAssignment) {
-    ClassElement classElement = view.classElement;
-    LibraryElement library = classElement.library;
+    final classElement = view.classElement;
+    final library = classElement.library;
     {
-      TypeResolverVisitor visitor = new TypeResolverVisitor(
+      final visitor = new TypeResolverVisitor(
           library, view.source, typeProvider, errorListener);
       astNode.accept(visitor);
     }
-    ResolverVisitor resolver = new AngularResolverVisitor(
-        library, templateSource, typeProvider, errorListener, acceptAssignment);
+    final resolver = new AngularResolverVisitor(
+        library, templateSource, typeProvider, errorListener,
+        acceptAssignment: acceptAssignment);
     // fill the name scope
-    ClassScope classScope = new ClassScope(resolver.nameScope, classElement);
-    EnclosedScope localScope = new EnclosedScope(classScope);
-    resolver.nameScope = localScope;
-    resolver.enclosingClass = classElement;
-    localVariables.values.forEach((LocalVariable local) {
-      localScope.define(local.dartVariable);
-    });
+    final classScope = new ClassScope(resolver.nameScope, classElement);
+    final localScope = new EnclosedScope(classScope);
+    resolver
+      ..nameScope = localScope
+      ..enclosingClass = classElement;
+    localVariables.values
+        .forEach((local) => localScope.define(local.dartVariable));
     // do resolve
     astNode.accept(resolver);
     // verify
-    ErrorVerifier verifier = new AngularErrorVerifier(errorReporter, library,
-        typeProvider, new InheritanceManager(library), acceptAssignment);
+    final verifier = new AngularErrorVerifier(
+        errorReporter, library, typeProvider, new InheritanceManager(library),
+        acceptAssignment: acceptAssignment);
     astNode.accept(verifier);
   }
 
-  /**
-   * Resolve the Dart expression with the given [code] at [offset].
-   */
-  _resolveDartExpression(Expression expression) {
+  /// Resolve the Dart expression with the given [code] at [offset].
+  void _resolveDartExpression(Expression expression) {
     if (expression != null) {
       _resolveDartAstNode(expression, false);
     }
   }
 
-  /**
-   * Resolve the Dart ExpressionStatement with the given [code] at [offset].
-   */
+  /// Resolve the Dart ExpressionStatement with the given [code] at [offset].
   void _resolveDartExpressionStatements(List<Statement> statements) {
-    for (Statement statement in statements) {
+    for (final statement in statements) {
       if (statement is! ExpressionStatement && statement is! EmptyStatement) {
         errorListener.onError(new AnalysisError(
             templateSource,
@@ -1349,27 +1426,22 @@
     }
   }
 
-  /**
-   * Get helpful description based on statement type to report in
-   * OUTPUT_STATEMENT_REQUIRES_EXPRESSION_STATEMENT
-   */
+  /// Get helpful description based on statement type to report in
+  /// OUTPUT_STATEMENT_REQUIRES_EXPRESSION_STATEMENT
   String _getOutputStatementErrorDescription(Statement stmt) {
-    String potentialToken = stmt.beginToken.keyword.toString().toLowerCase();
+    final potentialToken = stmt.beginToken.keyword.toString().toLowerCase();
     if (potentialToken != "null") {
-      return "token '" + potentialToken + "'";
+      return "token '$potentialToken'";
     } else {
       return stmt.runtimeType.toString().replaceFirst("Impl", "");
     }
   }
 
-  /**
-   * Record [ResolvedRange]s for the given [AstNode].
-   */
+  /// Record [ResolvedRange]s for the given [AstNode].
   void _recordAstNodeResolvedRanges(AstNode astNode) {
-    Map<LocalVariableElement, LocalVariable> dartVariables =
-        new HashMap<LocalVariableElement, LocalVariable>();
+    final dartVariables = new HashMap<LocalVariableElement, LocalVariable>();
 
-    for (LocalVariable localVariable in localVariables.values) {
+    for (final localVariable in localVariables.values) {
       dartVariables[localVariable.dartVariable] = localVariable;
     }
 
@@ -1379,34 +1451,27 @@
   }
 }
 
-/**
- * Workaround for "This mixin application is invalid because all of the
- * constructors in the base class 'ResolverVisitor' have optional parameters."
- * in the definition of [AngularResolverVisitor].
- *
- * See https://github.com/dart-lang/sdk/issues/15101 for details
- */
+/// Workaround for "This mixin application is invalid because all of the
+/// constructors in the base class 'ResolverVisitor' have optional parameters."
+/// in the definition of [AngularResolverVisitor].
+///
+/// See https://github.com/dart-lang/sdk/issues/15101 for details
 class _IntermediateResolverVisitor extends ResolverVisitor {
   _IntermediateResolverVisitor(LibraryElement library, Source source,
       TypeProvider typeProvider, AnalysisErrorListener errorListener)
       : super(library, source, typeProvider, errorListener);
 }
 
-/**
- * Override the standard [ResolverVisitor] class to report unacceptable nodes,
- * while suppressing secondary errors that would have been raised by
- * [ResolverVisitor] if we let it see the bogus definitions.
- */
+/// Override the standard [ResolverVisitor] class to report unacceptable nodes,
+/// while suppressing secondary errors that would have been raised by
+/// [ResolverVisitor] if we let it see the bogus definitions.
 class AngularResolverVisitor extends _IntermediateResolverVisitor
     with ReportUnacceptableNodesMixin {
   final bool acceptAssignment;
 
-  AngularResolverVisitor(
-      LibraryElement library,
-      Source source,
-      TypeProvider typeProvider,
-      AnalysisErrorListener errorListener,
-      this.acceptAssignment)
+  AngularResolverVisitor(LibraryElement library, Source source,
+      TypeProvider typeProvider, AnalysisErrorListener errorListener,
+      {@required this.acceptAssignment})
       : super(library, source, typeProvider, errorListener);
 
   @override
@@ -1436,9 +1501,10 @@
     // Only block reassignment of locals, not poperties. Resolve elements to
     // check that.
     exp.leftHandSide.accept(elementResolver);
-    VariableElement element = getOverridableStaticElement(exp.leftHandSide) ??
+    final variableElement = getOverridableStaticElement(exp.leftHandSide) ??
         getOverridablePropagatedElement(exp.leftHandSide);
-    if ((element == null || element is PropertyInducingElement) &&
+    if ((variableElement == null ||
+            variableElement is PropertyInducingElement) &&
         acceptAssignment) {
       return super.visitAssignmentExpression(exp);
     } else {
@@ -1471,31 +1537,29 @@
       _reportUnacceptableNode(exp, "Named arguments");
 }
 
-/**
- * Override the standard [ErrorVerifier] class to report unacceptable nodes,
- * while suppressing secondary errors that would have been raised by
- * [ErrorVerifier] if we let it see the bogus definitions.
- */
+/// Override the standard [ErrorVerifier] class to report unacceptable nodes,
+/// while suppressing secondary errors that would have been raised by
+/// [ErrorVerifier] if we let it see the bogus definitions.
 class AngularErrorVerifier extends ErrorVerifier
     with ReportUnacceptableNodesMixin {
   final bool acceptAssignment;
+
+  @override
   ErrorReporter errorReporter;
+  @override
   TypeProvider typeProvider;
-  AngularErrorVerifier(
-      ErrorReporter errorReporter,
-      LibraryElement library,
-      TypeProvider typeProvider,
-      InheritanceManager inheritanceManager,
-      this.acceptAssignment)
+
+  AngularErrorVerifier(ErrorReporter errorReporter, LibraryElement library,
+      TypeProvider typeProvider, InheritanceManager inheritanceManager,
+      {@required this.acceptAssignment})
       : errorReporter = errorReporter,
         typeProvider = typeProvider,
         super(errorReporter, library, typeProvider, inheritanceManager, false);
 
   @override
-  Object visitFunctionExpression(FunctionExpression exp) {
+  void visitFunctionExpression(FunctionExpression exp) {
     // error reported in [AngularResolverVisitor] but [ErrorVerifier] crashes
     // because it isn't resolved
-    return null;
   }
 
   @override
@@ -1533,9 +1597,10 @@
   @override
   Object visitAssignmentExpression(AssignmentExpression exp) {
     // match ResolverVisitor to prevent fallout errors
-    VariableElement element = getOverridableStaticElement(exp.leftHandSide) ??
+    final variableElement = getOverridableStaticElement(exp.leftHandSide) ??
         getOverridablePropagatedElement(exp.leftHandSide);
-    if ((element == null || element is PropertyInducingElement) &&
+    if ((variableElement == null ||
+            variableElement is PropertyInducingElement) &&
         acceptAssignment) {
       return super.visitAssignmentExpression(exp);
     } else {
@@ -1544,11 +1609,9 @@
     }
   }
 
-  /**
-   * Copied from ResolverVisitor
-   */
+  /// Copied from ResolverVisitor
   VariableElement getOverridablePropagatedElement(Expression expression) {
-    Element element = null;
+    Element element;
     if (expression is SimpleIdentifier) {
       element = expression.propagatedElement;
     } else if (expression is PrefixedIdentifier) {
@@ -1562,11 +1625,9 @@
     return null;
   }
 
-  /**
-   * Copied from ResolverVisitor
-   */
+  /// Copied from ResolverVisitor
   VariableElement getOverridableStaticElement(Expression expression) {
-    Element element = null;
+    Element element;
     if (expression is SimpleIdentifier) {
       element = expression.staticElement;
     } else if (expression is PrefixedIdentifier) {
diff --git a/analyzer_plugin/lib/src/selector.dart b/analyzer_plugin/lib/src/selector.dart
index ec3a4d6..28e09f4 100644
--- a/analyzer_plugin/lib/src/selector.dart
+++ b/analyzer_plugin/lib/src/selector.dart
@@ -5,12 +5,11 @@
 import 'package:analyzer/src/generated/source.dart';
 import 'package:angular_analyzer_plugin/src/model.dart';
 import 'package:angular_analyzer_plugin/src/strings.dart';
+import 'package:meta/meta.dart';
 
 enum SelectorMatch { NoMatch, NonTagMatch, TagMatch }
 
-/**
- * The [Selector] that matches all of the given [selectors].
- */
+/// The [Selector] that matches all of the given [selectors].
 class AndSelector extends Selector {
   final List<Selector> selectors;
 
@@ -19,20 +18,20 @@
   @override
   SelectorMatch match(ElementView element, Template template) {
     // Invalid selector case, should NOT match all.
-    if (selectors.length == 0) {
+    if (selectors.isEmpty) {
       return SelectorMatch.NoMatch;
     }
 
-    SelectorMatch onSuccess = SelectorMatch.NonTagMatch;
-    for (Selector selector in selectors) {
-      SelectorMatch theMatch = selector.match(element, null);
+    var onSuccess = SelectorMatch.NonTagMatch;
+    for (final selector in selectors) {
+      final theMatch = selector.match(element, null);
       if (theMatch == SelectorMatch.TagMatch) {
         onSuccess = theMatch;
       } else if (theMatch == SelectorMatch.NoMatch) {
         return SelectorMatch.NoMatch;
       }
     }
-    for (Selector selector in selectors) {
+    for (final selector in selectors) {
       selector.match(element, template);
     }
     return onSuccess;
@@ -41,36 +40,37 @@
   @override
   String toString() => selectors.join(' && ');
 
+  @override
   List<HtmlTagForSelector> refineTagSuggestions(
       List<HtmlTagForSelector> context) {
-    for (Selector selector in selectors) {
+    for (final selector in selectors) {
+      // ignore: parameter_assignments
       context = selector.refineTagSuggestions(context);
     }
     return context;
   }
 
+  @override
   void recordElementNameSelectors(List<ElementNameSelector> recordingList) {
     selectors.forEach(
         (selector) => selector.recordElementNameSelectors(recordingList));
   }
 }
 
-/**
- * The [Selector] that matches elements that have an attribute with the
- * given name, and (optionally) with the given value;
- */
+/// The [Selector] that matches elements that have an attribute with the
+/// given name, and (optionally) with the given value;
 class AttributeSelector extends Selector {
   final AngularElement nameElement;
   final bool isWildcard;
   final String value;
 
-  AttributeSelector(this.nameElement, this.value, this.isWildcard);
+  AttributeSelector(this.nameElement, this.value, {@required this.isWildcard});
 
   @override
   SelectorMatch match(ElementView element, Template template) {
-    String name = nameElement.name;
-    SourceRange attributeSpan = null;
-    String attributeValue = null;
+    final name = nameElement.name;
+    SourceRange attributeSpan;
+    String attributeValue;
 
     // standard case: exact match, use hash for fast lookup
     if (!isWildcard) {
@@ -81,7 +81,7 @@
       attributeValue = element.attributes[name];
     } else {
       // nonstandard case: wildcard, check if any start with specified name
-      for (String attrName in element.attributes.keys) {
+      for (final attrName in element.attributes.keys) {
         if (attrName.startsWith(name)) {
           attributeSpan = element.attributeNameSpans[attrName];
           attributeValue = element.attributes[attrName];
@@ -111,30 +111,30 @@
 
   @override
   String toString() {
-    String name = nameElement.name;
+    final name = nameElement.name;
     if (value != null) {
       return '[$name=$value]';
     }
     return '[$name]';
   }
 
+  @override
   List<HtmlTagForSelector> refineTagSuggestions(
       List<HtmlTagForSelector> context) {
-    for (HtmlTagForSelector tag in context) {
+    for (final tag in context) {
       tag.setAttribute(nameElement.name, value: value);
     }
     return context;
   }
 
+  @override
   void recordElementNameSelectors(List<ElementNameSelector> recordingList) {
     // empty
   }
 }
 
-/**
- * The [Selector] that matches elements that have an attribute with any name,
- * and with contents that match the given regex.
- */
+/// The [Selector] that matches elements that have an attribute with any name,
+/// and with contents that match the given regex.
 class AttributeValueRegexSelector extends Selector {
   final String regexpStr;
   final RegExp regexp;
@@ -143,7 +143,7 @@
 
   @override
   SelectorMatch match(ElementView element, Template template) {
-    for (String value in element.attributes.values) {
+    for (final value in element.attributes.values) {
       if (regexp.hasMatch(value)) {
         return SelectorMatch.NonTagMatch;
       }
@@ -153,23 +153,20 @@
   }
 
   @override
-  String toString() {
-    return '[*=$regexpStr]';
-  }
+  String toString() => '[*=$regexpStr]';
 
+  @override
   List<HtmlTagForSelector> refineTagSuggestions(
-      List<HtmlTagForSelector> context) {
-    return context;
-  }
+          List<HtmlTagForSelector> context) =>
+      context;
 
+  @override
   void recordElementNameSelectors(List<ElementNameSelector> recordingList) {
     // empty
   }
 }
 
-/**
- * The [Selector] that matches elements with the given (static) classes.
- */
+/// The [Selector] that matches elements with the given (static) classes.
 class ClassSelector extends Selector {
   final AngularElement nameElement;
 
@@ -177,8 +174,8 @@
 
   @override
   SelectorMatch match(ElementView element, Template template) {
-    String name = nameElement.name;
-    String val = element.attributes['class'];
+    final name = nameElement.name;
+    final val = element.attributes['class'];
     // no 'class' attribute
     if (val == null) {
       return SelectorMatch.NoMatch;
@@ -197,40 +194,40 @@
       index = val.indexOf(' $name ') + 1;
     }
     // add resolved range
-    int valueOffset = element.attributeValueSpans['class'].offset;
-    int offset = valueOffset + index;
+    final valueOffset = element.attributeValueSpans['class'].offset;
+    final offset = valueOffset + index;
     template.addRange(new SourceRange(offset, name.length), nameElement);
     return SelectorMatch.NonTagMatch;
   }
 
   @override
-  String toString() => '.' + nameElement.name;
+  String toString() => '.${nameElement.name}';
 
+  @override
   List<HtmlTagForSelector> refineTagSuggestions(
       List<HtmlTagForSelector> context) {
-    for (HtmlTagForSelector tag in context) {
+    for (final tag in context) {
       tag.addClass(nameElement.name);
     }
     return context;
   }
 
+  @override
   void recordElementNameSelectors(List<ElementNameSelector> recordingList) {
     // empty
   }
 }
 
-/**
- * The element name based selector.
- */
+/// The element name based selector.
 class ElementNameSelector extends Selector {
-  static List<ElementNameSelector> EMPTY_LIST = const <ElementNameSelector>[];
+  static const EMPTY_LIST = const <ElementNameSelector>[];
   final AngularElement nameElement;
 
   ElementNameSelector(this.nameElement);
 
   @override
   SelectorMatch match(ElementView element, Template template) {
-    String name = nameElement.name;
+    final name = nameElement.name;
     // match
     if (element.localName != name) {
       return SelectorMatch.NoMatch;
@@ -252,14 +249,16 @@
   @override
   String toString() => nameElement.name;
 
+  @override
   List<HtmlTagForSelector> refineTagSuggestions(
       List<HtmlTagForSelector> context) {
-    for (HtmlTagForSelector tag in context) {
+    for (final tag in context) {
       tag.name = nameElement.name;
     }
     return context;
   }
 
+  @override
   void recordElementNameSelectors(List<ElementNameSelector> recordingList) {
     recordingList.add(this);
   }
@@ -276,9 +275,7 @@
   SourceRange get openingSpan;
 }
 
-/**
- * The [Selector] that matches one of the given [selectors].
- */
+/// The [Selector] that matches one of the given [selectors].
 class OrSelector extends Selector {
   final List<Selector> selectors;
 
@@ -286,9 +283,9 @@
 
   @override
   SelectorMatch match(ElementView element, Template template) {
-    SelectorMatch onNoTagMatch = SelectorMatch.NoMatch;
-    for (Selector selector in selectors) {
-      SelectorMatch theMatch = selector.match(element, template);
+    var onNoTagMatch = SelectorMatch.NoMatch;
+    for (final selector in selectors) {
+      final theMatch = selector.match(element, template);
       if (theMatch == SelectorMatch.TagMatch) {
         return SelectorMatch.TagMatch;
       } else if (theMatch == SelectorMatch.NonTagMatch) {
@@ -301,118 +298,105 @@
   @override
   String toString() => selectors.join(' || ');
 
+  @override
   List<HtmlTagForSelector> refineTagSuggestions(
       List<HtmlTagForSelector> context) {
-    List<HtmlTagForSelector> response = <HtmlTagForSelector>[];
-    for (Selector selector in selectors) {
-      List<HtmlTagForSelector> newContext =
-          context.map((t) => t.clone()).toList();
+    final response = <HtmlTagForSelector>[];
+    for (final selector in selectors) {
+      final newContext = context.map((t) => t.clone()).toList();
       response.addAll(selector.refineTagSuggestions(newContext));
     }
 
     return response;
   }
 
+  @override
   void recordElementNameSelectors(List<ElementNameSelector> recordingList) {
     selectors.forEach(
         (selector) => selector.recordElementNameSelectors(recordingList));
   }
 }
 
-/**
- * The [Selector] that confirms the inner [Selector] condition does NOT match
- */
+/// The [Selector] that confirms the inner [Selector] condition does NOT match
 class NotSelector extends Selector {
   final Selector condition;
 
   NotSelector(this.condition);
 
   @override
-  SelectorMatch match(ElementView element, Template template) {
-    return condition.match(element, template) == SelectorMatch.NoMatch
-        ? SelectorMatch.NonTagMatch
-        : SelectorMatch.NoMatch;
-  }
+  SelectorMatch match(ElementView element, Template template) =>
+      condition.match(element, template) == SelectorMatch.NoMatch
+          ? SelectorMatch.NonTagMatch
+          : SelectorMatch.NoMatch;
 
   @override
   String toString() => ":not($condition)";
 
+  @override
   List<HtmlTagForSelector> refineTagSuggestions(
-      List<HtmlTagForSelector> context) {
-    return context;
-  }
+          List<HtmlTagForSelector> context) =>
+      context;
 
+  @override
   void recordElementNameSelectors(List<ElementNameSelector> recordingList) {
     // empty
   }
 }
 
-/**
- * The [Selector] that checks a TextNode for contents by a regex
- */
+/// The [Selector] that checks a TextNode for contents by a regex
 class ContainsSelector extends Selector {
   final String regex;
 
   ContainsSelector(this.regex);
 
+  /// TODO check against actual text contents so we know which :contains
+  /// directives were used (for when we want to advise removal of unused
+  /// directives).
+  ///
+  /// We could also highlight the matching region in the text node with a color
+  /// so users know it was applied.
+  ///
+  /// Not sure what else we could do.
+  ///
+  /// Never matches elements. Only matches [TextNode]s. Return false for now.
   @override
-  SelectorMatch match(ElementView element, Template template) {
-    // TODO check against actual text contents so we know which :contains
-    // directives were used (for when we want to advise removal of unused
-    // directives).
-    //
-    // We could also highlight the matching region in the text node with a color
-    // so users know it was applied.
-    //
-    // Not sure what else we could do.
-    //
-    // Never matches elements. Only matches [TextNode]s. Return false for now.
-    return SelectorMatch.NoMatch;
-  }
+  SelectorMatch match(ElementView element, Template template) =>
+      SelectorMatch.NoMatch;
 
   @override
   String toString() => ":contains($regex)";
 
+  @override
   List<HtmlTagForSelector> refineTagSuggestions(
-      List<HtmlTagForSelector> context) {
-    return context;
-  }
+          List<HtmlTagForSelector> context) =>
+      context;
 
+  @override
   void recordElementNameSelectors(List<ElementNameSelector> recordingList) {
     // empty
   }
 }
 
-/**
- * The base class for all Angular selectors.
- */
+/// The base class for all Angular selectors.
 abstract class Selector {
   String originalString;
   int offset;
 
-  /**
-   * Check whether the given [element] matches this selector.
-   * If yes, then record resolved ranges into [template].
-   */
+  /// Check whether the given [element] matches this selector.
+  /// If yes, then record resolved ranges into [template].
   SelectorMatch match(ElementView element, Template template);
 
-  /**
-   * See [HtmlTagForSelector] for info on what this does.
-   */
+  /// See [HtmlTagForSelector] for info on what this does.
   List<HtmlTagForSelector> refineTagSuggestions(
       List<HtmlTagForSelector> context);
 
-  /**
-   * See [HtmlTagForSelector] for info on what this does. Selectors should NOT
-   * override this method, but rather [refineTagSuggestions].
-   */
+  /// See [HtmlTagForSelector] for info on what this does. Selectors should NOT
+  /// override this method, but rather [refineTagSuggestions].
   List<HtmlTagForSelector> suggestTags() {
     // create a seed tag: ORs will copy this, everything else modifies. Each
     // selector returns the newest set of tags to be transformed.
-    List<HtmlTagForSelector> tags = <HtmlTagForSelector>[
-      new HtmlTagForSelector()
-    ];
-    return refineTagSuggestions(tags).where((t) => t.isValid);
+    final tags = [new HtmlTagForSelector()];
+    return refineTagSuggestions(tags).where((t) => t.isValid).toList();
   }
 
   void recordElementNameSelectors(List<ElementNameSelector> recordingList);
@@ -434,26 +418,24 @@
       : super(message, source, offset);
 }
 
-/**
- * Where possible it is good to be able to suggest a fully completed html tag to
- * match a selector. This has a few challenges: the selector may match multiple
- * things, it may not include any tag name to go off of at all. It may lend
- * itself to infinite suggestions (such as matching a regex), and parts of its
- * selector may cancel other parts out leading to invalid suggestions (such as
- * [prop=this][prop=thistoo]), especially in the presence of heavy booleans.
- * 
- * This doesn't track :not, so it may still suggest invalid things, but in
- * general the goal of this class is that its an empty shell which tracks
- * conflicting information.
- *
- * Each selector takes in the current round of suggestions in
- * [refineTagSuggestions], and may return more suggestions than it got
- * originally (as in OR). At the end, all valid selectors can be checked for
- * validity.
- *
- * Selector.suggestTags() handles creating a seed HtmlTagForSelector and
- * stripping invalid suggestions at the end, potentially resulting in none.
- */
+/// Where possible it is good to be able to suggest a fully completed html tag to
+/// match a selector. This has a few challenges: the selector may match multiple
+/// things, it may not include any tag name to go off of at all. It may lend
+/// itself to infinite suggestions (such as matching a regex), and parts of its
+/// selector may cancel other parts out leading to invalid suggestions (such as
+/// [prop=this][prop=thistoo]), especially in the presence of heavy booleans.
+///
+/// This doesn't track :not, so it may still suggest invalid things, but in
+/// general the goal of this class is that its an empty shell which tracks
+/// conflicting information.
+///
+/// Each selector takes in the current round of suggestions in
+/// [refineTagSuggestions], and may return more suggestions than it got
+/// originally (as in OR). At the end, all valid selectors can be checked for
+/// validity.
+///
+/// Selector.suggestTags() handles creating a seed HtmlTagForSelector and
+/// stripping invalid suggestions at the end, potentially resulting in none.
 class HtmlTagForSelector {
   String _name;
   Map<String, String> _attributes = <String, String>{};
@@ -466,7 +448,8 @@
       ? true
       : _classes.length == 1 && _classes.first == _attributes["class"];
 
-  void set name(String name) {
+  String get name => _name;
+  set name(String name) {
     if (_name != null && _name != name) {
       _isValid = false;
     } else {
@@ -492,19 +475,17 @@
     _classes.add(classname);
   }
 
-  HtmlTagForSelector clone() {
-    HtmlTagForSelector copy = new HtmlTagForSelector();
-    copy.name = _name;
-    copy._attributes = <String, String>{}..addAll(_attributes);
-    copy._isValid = _isValid;
-    copy._classes = new HashSet<String>.from(_classes);
-    return copy;
-  }
+  HtmlTagForSelector clone() => new HtmlTagForSelector()
+    ..name = _name
+    .._attributes = (<String, String>{}..addAll(_attributes))
+    .._isValid = _isValid
+    .._classes = new HashSet<String>.from(_classes);
 
+  @override
   String toString() {
-    bool keepClassAttr = _classes.isEmpty;
+    final keepClassAttr = _classes.isEmpty;
 
-    List<String> attrStrs = <String>[];
+    final attrStrs = <String>[];
     _attributes.forEach((k, v) {
       // in the case of [class].myclass don't create multiple class attrs
       if (k != "class" || keepClassAttr) {
@@ -512,8 +493,8 @@
       }
     });
 
-    if (!_classes.isEmpty) {
-      String classesList = (<String>[]
+    if (_classes.isNotEmpty) {
+      final classesList = (<String>[]
             ..addAll(_classes)
             ..sort())
           .join(' ');
@@ -537,12 +518,12 @@
   final Source source;
   SelectorParser(this.source, this.fileOffset, this.str);
 
-  final RegExp _regExp = new RegExp(r'(\:not\()|' +
-      r'([-\w]+)|' +
-      r'(?:\.([-\w]+))|' +
-      r'(?:\[([-\w*]+)(?:=([^\]]*))?\])|' +
-      r'(\))|' +
-      r'(\s*,\s*)|' +
+  final RegExp _regExp = new RegExp(r'(\:not\()|'
+      r'([-\w]+)|'
+      r'(?:\.([-\w]+))|'
+      r'(?:\[([-\w*]+)(?:=([^\]]*))?\])|'
+      r'(\))|'
+      r'(\s*,\s*)|'
       r'(^\:contains\(\/(.+)\/\)$)'); // :contains doesn't mix with the rest
 
   static const Map<int, _SelectorRegexMatch> matchIndexToType =
@@ -566,14 +547,14 @@
     currentMatch = matches.current;
     // no content should be skipped
     {
-      String skipStr = str.substring(lastOffset, currentMatch.start);
+      final skipStr = str.substring(lastOffset, currentMatch.start);
       if (!isBlank(skipStr)) {
         _unexpected(skipStr, lastOffset + fileOffset);
       }
       lastOffset = currentMatch.end;
     }
 
-    for (int index in matchIndexToType.keys) {
+    for (final index in matchIndexToType.keys) {
       if (currentMatch[index] != null) {
         currentMatchType = matchIndexToType[index];
         currentMatchStr = currentMatch[index];
@@ -592,18 +573,18 @@
     }
     matches = _regExp.allMatches(str).iterator;
     advance();
-    Selector selector = parseNested();
+    final selector = parseNested();
     if (currentMatch != null) {
       _unexpected(
           currentMatchStr, fileOffset + (currentMatch?.start ?? lastOffset));
     }
-    selector.originalString = str;
-    selector.offset = fileOffset;
-    return selector;
+    return selector
+      ..originalString = str
+      ..offset = fileOffset;
   }
 
   Selector parseNested() {
-    List<Selector> selectors = <Selector>[];
+    final selectors = <Selector>[];
     while (currentMatch != null) {
       if (currentMatchType == _SelectorRegexMatch.NotEnd) {
         // don't advance, just know we're at the end of this And
@@ -613,23 +594,24 @@
       if (currentMatchType == _SelectorRegexMatch.NotStart) {
         selectors.add(parseNotSelector());
       } else if (currentMatchType == _SelectorRegexMatch.Tag) {
-        int nameOffset = fileOffset + currentMatch.start;
-        String name = currentMatchStr;
+        final nameOffset = fileOffset + currentMatch.start;
+        final name = currentMatchStr;
         selectors.add(new ElementNameSelector(
             new SelectorName(name, nameOffset, name.length, source)));
         advance();
       } else if (currentMatchType == _SelectorRegexMatch.Class) {
-        int nameOffset = fileOffset + currentMatch.start + 1;
-        String name = currentMatchStr;
+        final nameOffset = fileOffset + currentMatch.start + 1;
+        final name = currentMatchStr;
         selectors.add(new ClassSelector(
             new SelectorName(name, nameOffset, name.length, source)));
         advance();
       } else if (currentMatchType == _SelectorRegexMatch.Attribute) {
-        int nameIndex = currentMatch.start + '['.length;
-        String name = currentMatch[4];
-        int nameOffset = fileOffset + nameIndex;
-        bool isWildcard = false;
-        String value = currentMatch[5];
+        final nameIndex = currentMatch.start + '['.length;
+        final nameOffset = fileOffset + nameIndex;
+        final value = currentMatch[5];
+
+        var name = currentMatch[4];
+        var isWildcard = false;
         advance();
 
         if (name == '*' &&
@@ -645,12 +627,11 @@
         }
 
         selectors.add(new AttributeSelector(
-            new SelectorName(name, nameOffset, name.length, source),
-            value,
-            isWildcard));
+            new SelectorName(name, nameOffset, name.length, source), value,
+            isWildcard: isWildcard));
       } else if (currentMatchType == _SelectorRegexMatch.Comma) {
         advance();
-        Selector rhs = parseNested();
+        final rhs = parseNested();
         if (rhs is OrSelector) {
           // flatten "a, b, c, d" from (a, (b, (c, d))) into (a, b, c, d)
           return new OrSelector(
@@ -671,7 +652,7 @@
 
   NotSelector parseNotSelector() {
     advance();
-    Selector condition = parseNested();
+    final condition = parseNested();
     if (currentMatchType != _SelectorRegexMatch.NotEnd) {
       _unexpected(
           currentMatchStr, fileOffset + (currentMatch?.start ?? lastOffset));
@@ -693,9 +674,7 @@
   }
 }
 
-/**
- * A name that is a part of a [Selector].
- */
+/// A name that is a part of a [Selector].
 class SelectorName extends AngularElementImpl {
   SelectorName(String name, int nameOffset, int nameLength, Source source)
       : super(name, nameOffset, nameLength, source);
diff --git a/analyzer_plugin/lib/src/standard_components.dart b/analyzer_plugin/lib/src/standard_components.dart
index cfe1167..a0abb30 100644
--- a/analyzer_plugin/lib/src/standard_components.dart
+++ b/analyzer_plugin/lib/src/standard_components.dart
@@ -14,6 +14,14 @@
   StandardHtml(this.components, this.events, this.attributes);
 }
 
+class StandardAngular {
+  final ClassElement templateRef;
+  final ClassElement elementRef;
+  final ClassElement queryList;
+
+  StandardAngular({this.templateRef, this.elementRef, this.queryList});
+}
+
 class BuildStandardHtmlComponentsVisitor extends RecursiveAstVisitor {
   final Map<String, Component> components;
   final Map<String, OutputElement> events;
@@ -40,21 +48,21 @@
     classElement = node.element;
     super.visitClassDeclaration(node);
     if (classElement.name == 'HtmlElement') {
-      List<OutputElement> outputElements = _buildOutputs(false);
-      for (OutputElement outputElement in outputElements) {
+      final outputElements = _buildOutputs(false);
+      for (final outputElement in outputElements) {
         events[outputElement.name] = outputElement;
       }
-      List<InputElement> inputElements = _buildInputs(false);
-      for (InputElement inputElement in inputElements) {
+      final inputElements = _buildInputs(false);
+      for (final inputElement in inputElements) {
         attributes[inputElement.name] = inputElement;
       }
     } else {
-      String specialTagName = specialElementClasses[classElement.name];
+      final specialTagName = specialElementClasses[classElement.name];
       if (specialTagName != null) {
-        String tag = specialTagName;
+        final tag = specialTagName;
         // TODO any better offset we can do here?
-        int tagOffset = classElement.nameOffset + 'HTML'.length;
-        Component component = _buildComponent(tag, tagOffset);
+        final tagOffset = classElement.nameOffset + 'HTML'.length;
+        final component = _buildComponent(tag, tagOffset);
         components[tag] = component;
       }
     }
@@ -70,18 +78,19 @@
 
   @override
   void visitMethodInvocation(ast.MethodInvocation node) {
-    ast.Expression target = node.target;
-    ast.ArgumentList argumentList = node.argumentList;
+    // ignore: omit_local_variable_types
+    final ast.Expression target = node.target;
+    final argumentList = node.argumentList;
     if (target is ast.SimpleIdentifier &&
         target.name == 'document' &&
         node.methodName.name == 'createElement' &&
         argumentList != null &&
         argumentList.arguments.length == 1) {
-      ast.Expression argument = argumentList.arguments.single;
+      final argument = argumentList.arguments.single;
       if (argument is ast.SimpleStringLiteral) {
-        String tag = argument.value;
-        int tagOffset = argument.contentsOffset;
-        Component component = _buildComponent(tag, tagOffset);
+        final tag = argument.value;
+        final tagOffset = argument.contentsOffset;
+        final component = _buildComponent(tag, tagOffset);
         components[tag] = component;
       }
     } else if (node.methodName.name == 'JS' &&
@@ -92,20 +101,18 @@
       if (documentArgument is ast.SimpleIdentifier &&
           documentArgument.name == 'document' &&
           tagArgument is ast.SimpleStringLiteral) {
-        String tag = tagArgument.value;
-        int tagOffset = tagArgument.contentsOffset;
-        Component component = _buildComponent(tag, tagOffset);
+        final tag = tagArgument.value;
+        final tagOffset = tagArgument.contentsOffset;
+        final component = _buildComponent(tag, tagOffset);
         components[tag] = component;
       }
     }
   }
 
-  /**
-   * Return a new [Component] for the current [classElement].
-   */
+  /// Return a new [Component] for the current [classElement].
   Component _buildComponent(String tag, int tagOffset) {
-    List<InputElement> inputElements = _buildInputs(true);
-    List<OutputElement> outputElements = _buildOutputs(true);
+    final inputElements = _buildInputs(true);
+    final outputElements = _buildOutputs(true);
     return new Component(classElement,
         inputs: inputElements,
         outputs: outputElements,
@@ -114,66 +121,62 @@
         isHtml: true);
   }
 
-  List<InputElement> _buildInputs(bool skipHtmlElement) {
-    return _captureAspects(
-        (Map<String, InputElement> inputMap, PropertyAccessorElement accessor) {
-      String name = accessor.displayName;
-      if (!inputMap.containsKey(name)) {
-        if (accessor.isSetter) {
-          inputMap[name] = new InputElement(
-              name,
-              accessor.nameOffset,
-              accessor.nameLength,
-              accessor.source,
-              accessor,
-              new SourceRange(accessor.nameOffset, accessor.nameLength),
-              accessor.variable.type);
+  List<InputElement> _buildInputs(bool skipHtmlElement) =>
+      _captureAspects((inputMap, accessor) {
+        final name = accessor.displayName;
+        if (!inputMap.containsKey(name)) {
+          if (accessor.isSetter) {
+            inputMap[name] = new InputElement(
+                name,
+                accessor.nameOffset,
+                accessor.nameLength,
+                accessor.source,
+                accessor,
+                new SourceRange(accessor.nameOffset, accessor.nameLength),
+                accessor.variable.type);
+          }
         }
-      }
-    }, skipHtmlElement); // Either grabbing HtmlElement attrs or skipping them
-  }
+      }, skipHtmlElement); // Either grabbing HtmlElement attrs or skipping them
 
-  List<OutputElement> _buildOutputs(bool skipHtmlElement) {
-    return _captureAspects((Map<String, OutputElement> outputMap,
-        PropertyAccessorElement accessor) {
-      String domName = _getDomName(accessor);
-      if (domName == null) {
-        return;
-      }
+  List<OutputElement> _buildOutputs(bool skipHtmlElement) =>
+      _captureAspects((outputMap, accessor) {
+        final domName = _getDomName(accessor);
+        if (domName == null) {
+          return;
+        }
 
-      // Event domnames start with Element.on or Document.on
-      int offset = domName.indexOf(".") + ".on".length;
-      String name = domName.substring(offset);
+        // Event domnames start with Element.on or Document.on
+        final offset = domName.indexOf(".") + ".on".length;
+        final name = domName.substring(offset);
 
-      if (!outputMap.containsKey(name)) {
-        if (accessor.isGetter) {
-          var returnType =
-              accessor.type == null ? null : accessor.type.returnType;
-          DartType eventType = null;
-          if (returnType != null && returnType is InterfaceType) {
-            // TODO allow subtypes of ElementStream? This is a generated file
-            // so might not be necessary.
-            if (returnType.element.name == 'ElementStream') {
-              eventType = returnType.typeArguments[0]; // may be null
-              outputMap[name] = new OutputElement(
-                  name,
-                  accessor.nameOffset,
-                  accessor.nameLength,
-                  accessor.source,
-                  accessor,
-                  null,
-                  eventType);
+        if (!outputMap.containsKey(name)) {
+          if (accessor.isGetter) {
+            final returnType =
+                accessor.type == null ? null : accessor.type.returnType;
+            DartType eventType;
+            if (returnType != null && returnType is InterfaceType) {
+              // TODO allow subtypes of ElementStream? This is a generated file
+              // so might not be necessary.
+              if (returnType.element.name == 'ElementStream') {
+                eventType = returnType.typeArguments[0]; // may be null
+                outputMap[name] = new OutputElement(
+                    name,
+                    accessor.nameOffset,
+                    accessor.nameLength,
+                    accessor.source,
+                    accessor,
+                    null,
+                    eventType);
+              }
             }
           }
         }
-      }
-    }, skipHtmlElement); // Either grabbing HtmlElement events or skipping them
-  }
+      }, skipHtmlElement); // Either grabbing HtmlElement events or skipping them
 
   String _getDomName(Element element) {
-    for (ElementAnnotation annotation in element.metadata) {
+    for (final annotation in element.metadata) {
       // this has caching built in, so we can compute every time
-      var value = annotation.computeConstantValue();
+      final value = annotation.computeConstantValue();
       if (value != null && value.type is InterfaceType) {
         if (value.type.element.name == 'DomName') {
           return value.getField("name").toStringValue();
@@ -186,8 +189,8 @@
 
   List<T> _captureAspects<T>(
       CaptureAspectFn<T> addAspect, bool skipHtmlElement) {
-    Map<String, T> aspectMap = <String, T>{};
-    Set<InterfaceType> visitedTypes = new Set<InterfaceType>();
+    final aspectMap = <String, T>{};
+    final visitedTypes = new Set<InterfaceType>();
 
     void addAspects(InterfaceType type) {
       if (type != null && visitedTypes.add(type)) {
diff --git a/analyzer_plugin/lib/src/strings.dart b/analyzer_plugin/lib/src/strings.dart
index 5f97bd8..8158249 100644
--- a/analyzer_plugin/lib/src/strings.dart
+++ b/analyzer_plugin/lib/src/strings.dart
@@ -1,26 +1,19 @@
-library services.src.correction.strings;
-
 import 'dart:math';
 
-/**
- * "$"
- */
-const int CHAR_DOLLAR = 0x24;
+/// "$"
+const CHAR_DOLLAR = 0x24;
 
-/**
- * "."
- */
-const int CHAR_DOT = 0x2E;
+/// "."
+const CHAR_DOT = 0x2E;
 
-/**
- * "_"
- */
-const int CHAR_UNDERSCORE = 0x5F;
+/// "_"
+const CHAR_UNDERSCORE = 0x5F;
 
 String capitalize(String str) {
   if (isEmpty(str)) {
     return str;
   }
+  // ignore: prefer_interpolation_to_compose_strings
   return str.substring(0, 1).toUpperCase() + str.substring(1);
 }
 
@@ -28,6 +21,7 @@
   if (isEmpty(str)) {
     return str;
   }
+  // ignore: prefer_interpolation_to_compose_strings
   return str.substring(0, 1).toLowerCase() + str.substring(1);
 }
 
@@ -44,15 +38,14 @@
   return a.compareTo(b);
 }
 
-/**
- * Counts how many times [sub] appears in [str].
- */
+/// Counts how many times [sub] appears in [str].
 int countMatches(String str, String sub) {
   if (isEmpty(str) || isEmpty(sub)) {
     return 0;
   }
-  int count = 0;
-  int idx = 0;
+  var count = 0;
+  var idx = 0;
+  // ignore: prefer_contains
   while ((idx = str.indexOf(sub, idx)) != -1) {
     count++;
     idx += sub.length;
@@ -60,32 +53,32 @@
   return count;
 }
 
-/**
- * Returns the number of characters common to the end of [a] and the start
- * of [b].
- */
-int findCommonOverlap(String a, String b) {
-  int a_length = a.length;
-  int b_length = b.length;
+/// Returns the number of characters common to the end of [a] and the start
+/// of [b].
+int findCommonOverlap(String _a, String _b) {
+  var a = _a;
+  var b = _b;
+  final aLength = a.length;
+  final bLength = b.length;
   // all empty
-  if (a_length == 0 || b_length == 0) {
+  if (aLength == 0 || bLength == 0) {
     return 0;
   }
   // truncate
-  if (a_length > b_length) {
-    a = a.substring(a_length - b_length);
-  } else if (a_length < b_length) {
-    b = b.substring(0, a_length);
+  if (aLength > bLength) {
+    a = a.substring(aLength - bLength);
+  } else if (aLength < bLength) {
+    b = b.substring(0, aLength);
   }
-  int text_length = min(a_length, b_length);
+  final textLength = min(aLength, bLength);
   // the worst case
   if (a == b) {
-    return text_length;
+    return textLength;
   }
   // increase common length one by one
-  int length = 0;
-  while (length < text_length) {
-    if (a.codeUnitAt(text_length - 1 - length) != b.codeUnitAt(length)) {
+  var length = 0;
+  while (length < textLength) {
+    if (a.codeUnitAt(textLength - 1 - length) != b.codeUnitAt(length)) {
       break;
     }
     length++;
@@ -93,12 +86,10 @@
   return length;
 }
 
-/**
- * Return the number of characters common to the start of [a] and [b].
- */
+/// Return the number of characters common to the start of [a] and [b].
 int findCommonPrefix(String a, String b) {
-  int n = min(a.length, b.length);
-  for (int i = 0; i < n; i++) {
+  final n = min(a.length, b.length);
+  for (var i = 0; i < n; i++) {
     if (a.codeUnitAt(i) != b.codeUnitAt(i)) {
       return i;
     }
@@ -106,39 +97,35 @@
   return n;
 }
 
-/**
- * Return the number of characters common to the end of [a] and [b].
- */
+/// Return the number of characters common to the end of [a] and [b].
 int findCommonSuffix(String a, String b) {
-  int a_length = a.length;
-  int b_length = b.length;
-  int n = min(a_length, b_length);
-  for (int i = 1; i <= n; i++) {
-    if (a.codeUnitAt(a_length - i) != b.codeUnitAt(b_length - i)) {
+  final aLength = a.length;
+  final bLength = b.length;
+  final n = min(aLength, bLength);
+  for (var i = 1; i <= n; i++) {
+    if (a.codeUnitAt(aLength - i) != b.codeUnitAt(bLength - i)) {
       return i - 1;
     }
   }
   return n;
 }
 
-/**
- * Returns a list of words for the given camel case string.
-*
- * 'getCamelWords' => ['get', 'Camel', 'Words']
- * 'getHTMLText' => ['get', 'HTML', 'Text']
- */
+/// Returns a list of words for the given camel case string.
+///
+/// 'getCamelWords' => ['get', 'Camel', 'Words']
+/// 'getHTMLText' => ['get', 'HTML', 'Text']
 List<String> getCamelWords(String str) {
   if (str == null || str.isEmpty) {
     return <String>[];
   }
-  List<String> parts = <String>[];
-  bool wasLowerCase = false;
-  bool wasUpperCase = false;
-  int wordStart = 0;
-  for (int i = 0; i < str.length; i++) {
-    int c = str.codeUnitAt(i);
-    var newLowerCase = isLowerCase(c);
-    var newUpperCase = isUpperCase(c);
+  final parts = <String>[];
+  var wasLowerCase = false;
+  var wasUpperCase = false;
+  var wordStart = 0;
+  for (var i = 0; i < str.length; i++) {
+    final c = str.codeUnitAt(i);
+    final newLowerCase = isLowerCase(c);
+    final newUpperCase = isUpperCase(c);
     // myWord
     // | ^
     if (wasLowerCase && newUpperCase) {
@@ -161,9 +148,7 @@
   return parts;
 }
 
-/**
- * Checks if [str] is `null`, empty or is whitespace.
- */
+/// Checks if [str] is `null`, empty or is whitespace.
 bool isBlank(String str) {
   if (str == null) {
     return true;
@@ -174,35 +159,21 @@
   return str.codeUnits.every(isSpace);
 }
 
-bool isDigit(int c) {
-  return c >= 0x30 && c <= 0x39;
-}
+bool isDigit(int c) => c >= 0x30 && c <= 0x39;
 
-bool isEmpty(String str) {
-  return str == null || str.isEmpty;
-}
+bool isEmpty(String str) => str == null || str.isEmpty;
 
-bool isLetter(int c) {
-  return (c >= 0x41 && c <= 0x5A) || (c >= 0x61 && c <= 0x7A);
-}
+bool isLetter(int c) => (c >= 0x41 && c <= 0x5A) || (c >= 0x61 && c <= 0x7A);
 
-bool isLetterOrDigit(int c) {
-  return isLetter(c) || isDigit(c);
-}
+bool isLetterOrDigit(int c) => isLetter(c) || isDigit(c);
 
-bool isLowerCase(int c) {
-  return c >= 0x61 && c <= 0x7A;
-}
+bool isLowerCase(int c) => c >= 0x61 && c <= 0x7A;
 
 bool isSpace(int c) => c == 0x20 || c == 0x09;
 
-bool isUpperCase(int c) {
-  return c >= 0x41 && c <= 0x5A;
-}
+bool isUpperCase(int c) => c >= 0x41 && c <= 0x5A;
 
-bool isWhitespace(int c) {
-  return isSpace(c) || c == 0x0D || c == 0x0A;
-}
+bool isWhitespace(int c) => isSpace(c) || c == 0x0D || c == 0x0A;
 
 String remove(String str, String remove) {
   if (isEmpty(str) || isEmpty(remove)) {
@@ -232,17 +203,15 @@
 }
 
 String repeat(String s, int n) {
-  StringBuffer sb = new StringBuffer();
-  for (int i = 0; i < n; i++) {
+  final sb = new StringBuffer();
+  for (var i = 0; i < n; i++) {
     sb.write(s);
   }
   return sb.toString();
 }
 
-/**
- * Gets the substring after the last occurrence of a separator.
- * The separator is not returned.
- */
+/// Gets the substring after the last occurrence of a separator.
+/// The separator is not returned.
 String substringAfterLast(String str, String separator) {
   if (isEmpty(str)) {
     return str;
@@ -250,7 +219,7 @@
   if (isEmpty(separator)) {
     return '';
   }
-  int pos = str.lastIndexOf(separator);
+  final pos = str.lastIndexOf(separator);
   if (pos == -1) {
     return str;
   }
diff --git a/analyzer_plugin/lib/src/summary/format.dart b/analyzer_plugin/lib/src/summary/format.dart
index d1ff7ab..47ae931 100644
--- a/analyzer_plugin/lib/src/summary/format.dart
+++ b/analyzer_plugin/lib/src/summary/format.dart
@@ -5,10 +5,10 @@
 // This file has been automatically generated.  Please do not edit it manually.
 // To regenerate the file, use the script "pkg/analyzer/tool/generate_files".
 
-import 'package:analyzer/src/summary/flat_buffers.dart' as fb;
+import 'package:front_end/src/base/flat_buffers.dart' as fb;
 import 'idl.dart' as idl;
 import 'dart:convert' as convert;
-import 'package:analyzer/src/summary/api_signature.dart' as api_sig;
+import 'package:front_end/src/base/api_signature.dart' as api_sig;
 
 class PackageBundleBuilder extends Object
     with _PackageBundleMixin
@@ -750,6 +750,8 @@
   List<SummarizedBindableBuilder> _inputs;
   List<SummarizedBindableBuilder> _outputs;
   List<SummarizedDirectiveUseBuilder> _subdirectives;
+  List<SummarizedContentChildFieldBuilder> _contentChildFields;
+  List<SummarizedContentChildFieldBuilder> _contentChildrenFields;
 
   @override
   bool get isComponent => _isComponent ??= false;
@@ -865,6 +867,23 @@
     this._subdirectives = 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;
+  }
+
   SummarizedDirectiveBuilder(
       {bool isComponent,
       String selectorStr,
@@ -880,7 +899,9 @@
       List<SummarizedNgContentBuilder> ngContents,
       List<SummarizedBindableBuilder> inputs,
       List<SummarizedBindableBuilder> outputs,
-      List<SummarizedDirectiveUseBuilder> subdirectives})
+      List<SummarizedDirectiveUseBuilder> subdirectives,
+      List<SummarizedContentChildFieldBuilder> contentChildFields,
+      List<SummarizedContentChildFieldBuilder> contentChildrenFields})
       : _isComponent = isComponent,
         _selectorStr = selectorStr,
         _selectorOffset = selectorOffset,
@@ -895,7 +916,9 @@
         _ngContents = ngContents,
         _inputs = inputs,
         _outputs = outputs,
-        _subdirectives = subdirectives;
+        _subdirectives = subdirectives,
+        _contentChildFields = contentChildFields,
+        _contentChildrenFields = contentChildrenFields;
 
   /**
    * Flush [informative] data recursively.
@@ -905,6 +928,8 @@
     _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());
   }
 
   /**
@@ -954,6 +979,22 @@
         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) {
@@ -966,6 +1007,8 @@
     fb.Offset offset_inputs;
     fb.Offset offset_outputs;
     fb.Offset offset_subdirectives;
+    fb.Offset offset_contentChildFields;
+    fb.Offset offset_contentChildrenFields;
     if (_selectorStr != null) {
       offset_selectorStr = fbBuilder.writeString(_selectorStr);
     }
@@ -997,6 +1040,14 @@
       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());
+    }
     fbBuilder.startTable();
     if (_isComponent == true) {
       fbBuilder.addBool(0, true);
@@ -1043,6 +1094,12 @@
     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);
+    }
     return fbBuilder.endTable();
   }
 }
@@ -1079,6 +1136,8 @@
   List<idl.SummarizedBindable> _inputs;
   List<idl.SummarizedBindable> _outputs;
   List<idl.SummarizedDirectiveUse> _subdirectives;
+  List<idl.SummarizedContentChildField> _contentChildFields;
+  List<idl.SummarizedContentChildField> _contentChildrenFields;
 
   @override
   bool get isComponent {
@@ -1181,6 +1240,26 @@
         .vTableGet(_bc, _bcOffset, 14, 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;
+  }
 }
 
 abstract class _SummarizedDirectiveMixin implements idl.SummarizedDirective {
@@ -1211,6 +1290,12 @@
     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();
     return _result;
   }
 
@@ -1231,6 +1316,8 @@
         "inputs": inputs,
         "outputs": outputs,
         "subdirectives": subdirectives,
+        "contentChildFields": contentChildFields,
+        "contentChildrenFields": contentChildrenFields,
       };
 
   @override
@@ -2006,3 +2093,185 @@
   @override
   String toString() => convert.JSON.encode(toJson());
 }
+
+class SummarizedContentChildFieldBuilder extends Object
+    with _SummarizedContentChildFieldMixin
+    implements idl.SummarizedContentChildField {
+  String _fieldName;
+  int _nameOffset;
+  int _nameLength;
+  int _typeOffset;
+  int _typeLength;
+
+  @override
+  String get fieldName => _fieldName ??= '';
+
+  void set fieldName(String value) {
+    this._fieldName = value;
+  }
+
+  @override
+  int get nameOffset => _nameOffset ??= 0;
+
+  void set nameOffset(int value) {
+    assert(value == null || value >= 0);
+    this._nameOffset = value;
+  }
+
+  @override
+  int get nameLength => _nameLength ??= 0;
+
+  void set nameLength(int value) {
+    assert(value == null || value >= 0);
+    this._nameLength = value;
+  }
+
+  @override
+  int get typeOffset => _typeOffset ??= 0;
+
+  void set typeOffset(int value) {
+    assert(value == null || value >= 0);
+    this._typeOffset = value;
+  }
+
+  @override
+  int get typeLength => _typeLength ??= 0;
+
+  void set typeLength(int value) {
+    assert(value == null || value >= 0);
+    this._typeLength = value;
+  }
+
+  SummarizedContentChildFieldBuilder(
+      {String fieldName,
+      int nameOffset,
+      int nameLength,
+      int typeOffset,
+      int typeLength})
+      : _fieldName = fieldName,
+        _nameOffset = nameOffset,
+        _nameLength = nameLength,
+        _typeOffset = typeOffset,
+        _typeLength = typeLength;
+
+  /**
+   * Flush [informative] data recursively.
+   */
+  void flushInformative() {}
+
+  /**
+   * Accumulate non-[informative] data into [signature].
+   */
+  void collectApiSignature(api_sig.ApiSignature signature) {
+    signature.addString(this._fieldName ?? '');
+    signature.addInt(this._nameOffset ?? 0);
+    signature.addInt(this._nameLength ?? 0);
+    signature.addInt(this._typeOffset ?? 0);
+    signature.addInt(this._typeLength ?? 0);
+  }
+
+  fb.Offset finish(fb.Builder fbBuilder) {
+    fb.Offset offset_fieldName;
+    if (_fieldName != null) {
+      offset_fieldName = fbBuilder.writeString(_fieldName);
+    }
+    fbBuilder.startTable();
+    if (offset_fieldName != null) {
+      fbBuilder.addOffset(0, offset_fieldName);
+    }
+    if (_nameOffset != null && _nameOffset != 0) {
+      fbBuilder.addUint32(1, _nameOffset);
+    }
+    if (_nameLength != null && _nameLength != 0) {
+      fbBuilder.addUint32(2, _nameLength);
+    }
+    if (_typeOffset != null && _typeOffset != 0) {
+      fbBuilder.addUint32(3, _typeOffset);
+    }
+    if (_typeLength != null && _typeLength != 0) {
+      fbBuilder.addUint32(4, _typeLength);
+    }
+    return fbBuilder.endTable();
+  }
+}
+
+class _SummarizedContentChildFieldReader
+    extends fb.TableReader<_SummarizedContentChildFieldImpl> {
+  const _SummarizedContentChildFieldReader();
+
+  @override
+  _SummarizedContentChildFieldImpl createObject(
+          fb.BufferContext bc, int offset) =>
+      new _SummarizedContentChildFieldImpl(bc, offset);
+}
+
+class _SummarizedContentChildFieldImpl extends Object
+    with _SummarizedContentChildFieldMixin
+    implements idl.SummarizedContentChildField {
+  final fb.BufferContext _bc;
+  final int _bcOffset;
+
+  _SummarizedContentChildFieldImpl(this._bc, this._bcOffset);
+
+  String _fieldName;
+  int _nameOffset;
+  int _nameLength;
+  int _typeOffset;
+  int _typeLength;
+
+  @override
+  String get fieldName {
+    _fieldName ??= const fb.StringReader().vTableGet(_bc, _bcOffset, 0, '');
+    return _fieldName;
+  }
+
+  @override
+  int get nameOffset {
+    _nameOffset ??= const fb.Uint32Reader().vTableGet(_bc, _bcOffset, 1, 0);
+    return _nameOffset;
+  }
+
+  @override
+  int get nameLength {
+    _nameLength ??= const fb.Uint32Reader().vTableGet(_bc, _bcOffset, 2, 0);
+    return _nameLength;
+  }
+
+  @override
+  int get typeOffset {
+    _typeOffset ??= const fb.Uint32Reader().vTableGet(_bc, _bcOffset, 3, 0);
+    return _typeOffset;
+  }
+
+  @override
+  int get typeLength {
+    _typeLength ??= const fb.Uint32Reader().vTableGet(_bc, _bcOffset, 4, 0);
+    return _typeLength;
+  }
+}
+
+abstract class _SummarizedContentChildFieldMixin
+    implements idl.SummarizedContentChildField {
+  @override
+  Map<String, Object> toJson() {
+    Map<String, Object> _result = <String, Object>{};
+    if (fieldName != '') _result["fieldName"] = fieldName;
+    if (nameOffset != 0) _result["nameOffset"] = nameOffset;
+    if (nameLength != 0) _result["nameLength"] = nameLength;
+    if (typeOffset != 0) _result["typeOffset"] = typeOffset;
+    if (typeLength != 0) _result["typeLength"] = typeLength;
+    return _result;
+  }
+
+  @override
+  Map<String, Object> toMap() => {
+        "fieldName": fieldName,
+        "nameOffset": nameOffset,
+        "nameLength": nameLength,
+        "typeOffset": typeOffset,
+        "typeLength": typeLength,
+      };
+
+  @override
+  String toString() => convert.JSON.encode(toJson());
+}
diff --git a/analyzer_plugin/lib/src/summary/format.fbs b/analyzer_plugin/lib/src/summary/format.fbs
index b9f561b..d7785d5 100644
--- a/analyzer_plugin/lib/src/summary/format.fbs
+++ b/analyzer_plugin/lib/src/summary/format.fbs
@@ -66,6 +66,10 @@
   outputs:[SummarizedBindable] (id: 13);
 
   subdirectives:[SummarizedDirectiveUse] (id: 14);
+
+  contentChildFields:[SummarizedContentChildField] (id: 15);
+
+  contentChildrenFields:[SummarizedContentChildField] (id: 16);
 }
 
 table SummarizedAnalysisError {
@@ -116,6 +120,18 @@
   selectorOffset:uint (id: 3);
 }
 
+table SummarizedContentChildField {
+  fieldName:string (id: 0);
+
+  nameOffset:uint (id: 1);
+
+  nameLength:uint (id: 2);
+
+  typeOffset:uint (id: 3);
+
+  typeLength:uint (id: 4);
+}
+
 root_type PackageBundle;
 
 file_identifier "APdl";
diff --git a/analyzer_plugin/lib/src/summary/idl.dart b/analyzer_plugin/lib/src/summary/idl.dart
index 6e1146a..07550de 100644
--- a/analyzer_plugin/lib/src/summary/idl.dart
+++ b/analyzer_plugin/lib/src/summary/idl.dart
@@ -93,6 +93,10 @@
   List<SummarizedBindable> get outputs;
   @Id(14)
   List<SummarizedDirectiveUse> get subdirectives;
+  @Id(15)
+  List<SummarizedContentChildField> get contentChildFields;
+  @Id(16)
+  List<SummarizedContentChildField> get contentChildrenFields;
 }
 
 abstract class SummarizedAnalysisError extends base.SummaryClass {
@@ -147,3 +151,16 @@
   @Id(3)
   int get selectorOffset;
 }
+
+abstract class SummarizedContentChildField extends base.SummaryClass {
+  @Id(0)
+  String get fieldName;
+  @Id(1)
+  int get nameOffset;
+  @Id(2)
+  int get nameLength;
+  @Id(3)
+  int get typeOffset;
+  @Id(4)
+  int get typeLength;
+}
diff --git a/analyzer_plugin/lib/src/tasks.dart b/analyzer_plugin/lib/src/tasks.dart
index 5c6f278..dc6fd4c 100644
--- a/analyzer_plugin/lib/src/tasks.dart
+++ b/analyzer_plugin/lib/src/tasks.dart
@@ -16,10 +16,10 @@
 
   @override
   Object visitAdjacentStrings(ast.AdjacentStrings node) {
-    StringBuffer buffer = new StringBuffer();
-    int lastEndingOffset = null;
-    for (ast.StringLiteral string in node.strings) {
-      Object value = string.accept(this);
+    final buffer = new StringBuffer();
+    int lastEndingOffset;
+    for (final string in node.strings) {
+      final value = string.accept(this);
       if (identical(value, utils.ConstantEvaluator.NOT_A_CONSTANT)) {
         return value;
       }
@@ -36,19 +36,22 @@
   @override
   Object visitBinaryExpression(ast.BinaryExpression node) {
     if (node.operator.type == TokenType.PLUS) {
-      Object leftOperand = node.leftOperand.accept(this);
+      // ignore: omit_local_variable_types
+      final Object leftOperand = node.leftOperand.accept(this);
       if (identical(leftOperand, utils.ConstantEvaluator.NOT_A_CONSTANT)) {
         return leftOperand;
       }
-      Object rightOperand = node.rightOperand.accept(this);
+      // ignore: omit_local_variable_types
+      final Object rightOperand = node.rightOperand.accept(this);
       if (identical(rightOperand, utils.ConstantEvaluator.NOT_A_CONSTANT)) {
         return rightOperand;
       }
       // numeric or {@code null}
       if (leftOperand is String && rightOperand is String) {
-        int gap = node.rightOperand.offset -
+        final gap = node.rightOperand.offset -
             node.leftOperand.offset -
             node.leftOperand.length;
+        // ignore: prefer_interpolation_to_compose_strings
         return leftOperand + (' ' * gap) + rightOperand;
       }
     }
@@ -74,13 +77,15 @@
   Object visitParenthesizedExpression(ast.ParenthesizedExpression node) {
     offsetsAreValid = false;
     lastUnoffsettableNode = node;
-    int preGap = node.expression.offset - node.offset;
-    int postGap = node.offset +
+    final preGap = node.expression.offset - node.offset;
+    final postGap = node.offset +
         node.length -
         node.expression.offset -
         node.expression.length;
-    Object value = super.visitParenthesizedExpression(node);
+    // ignore: omit_local_variable_types
+    final Object value = super.visitParenthesizedExpression(node);
     if (value is String) {
+      // ignore: prefer_interpolation_to_compose_strings
       return ' ' * preGap + value + ' ' * postGap;
     }
 
@@ -89,8 +94,9 @@
 
   @override
   Object visitSimpleStringLiteral(ast.SimpleStringLiteral node) {
-    int gap = node.contentsOffset - node.offset;
+    final gap = node.contentsOffset - node.offset;
     lastUnoffsettableNode = node;
+    // ignore: prefer_interpolation_to_compose_strings
     return ' ' * gap + node.value + ' ';
   }
 
@@ -116,35 +122,28 @@
   }
 }
 
-/**
- * Helper for processing Angular annotations.
- */
+/// Helper for processing Angular annotations.
 class AnnotationProcessorMixin {
-  RecordingErrorListener errorListener = new RecordingErrorListener();
+  var errorListener = new RecordingErrorListener();
   ErrorReporter errorReporter;
 
-  /**
-   * The evaluator of constant values, such as annotation arguments.
-   */
+  /// The evaluator of constant values, such as annotation arguments.
   final utils.ConstantEvaluator _constantEvaluator =
       new utils.ConstantEvaluator();
 
-  /**
-   * Initialize the processor working in the given [target].
-   */
+  /// Initialize the processor working in the given [target].
   void initAnnotationProcessor(Source source) {
     assert(errorReporter == null);
     errorReporter = new ErrorReporter(errorListener, source);
   }
 
-  /**
-   * Returns the [String] value of the given [expression].
-   * If [expression] does not have a [String] value, reports an error
-   * and returns `null`.
-   */
+  /// Returns the [String] value of the given [expression].
+  /// If [expression] does not have a [String] value, reports an error
+  /// and returns `null`.
   String getExpressionString(ast.Expression expression) {
     if (expression != null) {
-      Object value = expression.accept(_constantEvaluator);
+      // ignore: omit_local_variable_types
+      final Object value = expression.accept(_constantEvaluator);
       if (value is String) {
         return value;
       }
@@ -154,15 +153,13 @@
     return null;
   }
 
-  /**
-   * Returns the [String] value of the given [expression].
-   * If [expression] does not have a [String] value, reports an error
-   * and returns `null`.
-   */
+  /// Returns the [String] value of the given [expression].
+  /// If [expression] does not have a [String] value, reports an error
+  /// and returns `null`.
   OffsettingConstantEvaluator calculateStringWithOffsets(
       ast.Expression expression) {
     if (expression != null) {
-      OffsettingConstantEvaluator evaluator = new OffsettingConstantEvaluator();
+      final evaluator = new OffsettingConstantEvaluator();
       evaluator.value = expression.accept(evaluator);
 
       if (evaluator.value is String) {
@@ -179,14 +176,12 @@
     return null;
   }
 
-  /**
-   * Returns the value of the argument with the given [name].
-   * Returns `null` if not found.
-   */
+  /// Returns the value of the argument with the given [name].
+  /// Returns `null` if not found.
   ast.Expression getNamedArgument(ast.Annotation node, String name) {
     if (node.arguments != null) {
-      List<ast.Expression> arguments = node.arguments.arguments;
-      for (ast.Expression argument in arguments) {
+      final arguments = node.arguments.arguments;
+      for (var argument in arguments) {
         if (argument is ast.NamedExpression &&
             argument.name != null &&
             argument.name.label != null &&
@@ -198,13 +193,11 @@
     return null;
   }
 
-  /**
-   * Returns `true` is the given [node] is resolved to a creation of an Angular
-   * annotation class with the given [name].
-   */
+  /// Returns `true` is the given [node] is resolved to a creation of an Angular
+  /// annotation class with the given [name].
   bool isAngularAnnotation(ast.Annotation node, String name) {
     if (node.element is ConstructorElement) {
-      ClassElement clazz = node.element.enclosingElement;
+      final clazz = node.element.enclosingElement;
       return clazz.library.source.uri.path
               .endsWith('angular2/src/core/metadata.dart') &&
           clazz.name == name;
diff --git a/analyzer_plugin/lib/src/view_extraction.dart b/analyzer_plugin/lib/src/view_extraction.dart
index c36c39a..69b655b 100644
--- a/analyzer_plugin/lib/src/view_extraction.dart
+++ b/analyzer_plugin/lib/src/view_extraction.dart
@@ -5,9 +5,9 @@
 import 'package:analyzer/src/generated/source.dart';
 import 'package:angular_analyzer_plugin/src/model.dart';
 import 'package:angular_analyzer_plugin/tasks.dart';
-import 'tasks.dart';
 import 'package:analyzer/error/error.dart';
-import 'package:angular_ast/angular_ast.dart' as NgAst;
+import 'package:angular_ast/angular_ast.dart' as ng_ast;
+import 'tasks.dart';
 
 class ViewExtractor extends AnnotationProcessorMixin {
   AnalysisContext context;
@@ -27,14 +27,14 @@
     //
     // Process all classes.
     //
-    List<View> views = <View>[];
-    for (ast.CompilationUnitMember unitMember in unit.declarations) {
+    final views = <View>[];
+    for (final unitMember in unit.declarations) {
       if (unitMember is ast.ClassDeclaration) {
-        ClassElement classElement = unitMember.element;
+        final classElement = unitMember.element;
         ast.Annotation viewAnnotation;
         ast.Annotation componentAnnotation;
 
-        for (ast.Annotation annotation in unitMember.metadata) {
+        for (final annotation in unitMember.metadata) {
           if (isAngularAnnotation(annotation, 'View')) {
             viewAnnotation = annotation;
           } else if (isAngularAnnotation(annotation, 'Component')) {
@@ -47,7 +47,7 @@
         }
 
         //@TODO when there's both a @View and @Component, handle edge cases
-        View view =
+        final view =
             _createView(classElement, viewAnnotation ?? componentAnnotation);
 
         if (view != null) {
@@ -59,23 +59,22 @@
     return views;
   }
 
-  /**
-   * Create a new [View] for the given [annotation], may return `null`
-   * if [annotation] or [classElement] don't provide enough information.
-   */
+  /// Create a new [View] for the given [annotation], may return `null`
+  /// if [annotation] or [classElement] don't provide enough information.
   View _createView(ClassElement classElement, ast.Annotation annotation) {
     // Template in a separate HTML file.
-    Source templateUriSource = null;
-    bool definesTemplate = false;
-    bool definesTemplateUrl = false;
-    SourceRange templateUrlRange = null;
+    Source templateUriSource;
+    var definesTemplate = false;
+    var definesTemplateUrl = false;
+    SourceRange templateUrlRange;
     {
-      ast.Expression templateUrlExpression =
+      // ignore: omit_local_variable_types
+      final ast.Expression templateUrlExpression =
           getNamedArgument(annotation, 'templateUrl');
       definesTemplateUrl = templateUrlExpression != null;
-      String templateUrl = getExpressionString(templateUrlExpression);
+      final templateUrl = getExpressionString(templateUrlExpression);
       if (templateUrl != null) {
-        SourceFactory sourceFactory = context.sourceFactory;
+        final sourceFactory = context.sourceFactory;
         templateUriSource = sourceFactory.resolveUri(source, templateUrl);
 
         if (templateUriSource == null || !templateUriSource.exists()) {
@@ -90,17 +89,22 @@
     }
     // Try to find inline "template".
     String templateText;
-    int templateOffset = 0;
+    var templateOffset = 0;
     {
-      ast.Expression expression = getNamedArgument(annotation, 'template');
+      // ignore: omit_local_variable_types
+      final ast.Expression expression =
+          getNamedArgument(annotation, 'template');
       if (expression != null) {
         templateOffset = expression.offset;
         definesTemplate = true;
-        OffsettingConstantEvaluator constantEvaluation =
+        // ignore: omit_local_variable_types
+        final OffsettingConstantEvaluator constantEvaluation =
             calculateStringWithOffsets(expression);
 
         // highly dynamically generated constant expressions can't be validated
-        if (constantEvaluation == null || !constantEvaluation.offsetsAreValid) {
+        if (constantEvaluation == null ||
+            !constantEvaluation.offsetsAreValid ||
+            constantEvaluation.value is! String) {
           templateText = '';
         } else {
           templateText = constantEvaluation.value;
@@ -123,7 +127,7 @@
     }
 
     // Find the corresponding Component.
-    Component component = _findComponentAnnotationOrReportError(classElement);
+    final component = _findComponentAnnotationOrReportError(classElement);
     if (component == null) {
       return null;
     }
@@ -140,7 +144,7 @@
   }
 
   Component _findComponentAnnotationOrReportError(ClassElement classElement) {
-    for (AbstractDirective directive in directivesDefinedInFile) {
+    for (final directive in directivesDefinedInFile) {
       if (directive is Component && directive.classElement == classElement) {
         return directive;
       }
@@ -153,30 +157,25 @@
   void findDirectives(
       ast.Annotation annotation, List<DirectiveReference> directiveReferences) {
     // Prepare directives and elementTags
-    ast.Expression listExpression = getNamedArgument(annotation, 'directives');
+    // ignore: omit_local_variable_types
+    final ast.Expression listExpression =
+        getNamedArgument(annotation, 'directives');
     if (listExpression is ast.ListLiteral) {
-      for (ast.Expression item in listExpression.elements) {
+      // ignore: omit_local_variable_types
+      for (final ast.Expression item in listExpression.elements) {
         if (item is ast.Identifier) {
-          var name = item.name;
+          final name = item.name;
           var prefix = "";
           if (item is ast.PrefixedIdentifier) {
             prefix = item.prefix.name;
           }
-          Element element = item.staticElement;
+          final element = item.staticElement;
           // LIST_OF_DIRECTIVES or TypeLiteral
           if (element is ClassElement ||
               element is PropertyAccessorElement &&
                   element.variable.constantValue != null) {
             directiveReferences.add(new DirectiveReference(
                 name, prefix, new SourceRange(item.offset, item.length)));
-            //DartObject value = element.variable.constantValue;
-            //bool success = _addDirectivesAndElementTagsForDartObject(
-            //    directiveReferences, value);
-            //if (!success) {
-            //  errorReporter.reportErrorForNode(
-            //      AngularWarningCode.TYPE_LITERAL_EXPECTED, item);
-            //  return null;
-            //}
             continue;
           }
         }
@@ -189,22 +188,24 @@
 }
 
 class TemplateParser {
+  //Todo(Max): remove errorMap after new ast implemented
   static const errorMap = const {
-    NgAst.NgParserWarningCode.UNTERMINATED_MUSTACHE:
+    ng_ast.NgParserWarningCode.UNTERMINATED_MUSTACHE:
         AngularWarningCode.UNTERMINATED_MUSTACHE,
-    NgAst.NgParserWarningCode.UNOPENED_MUSTACHE:
+    ng_ast.NgParserWarningCode.UNOPENED_MUSTACHE:
         AngularWarningCode.UNOPENED_MUSTACHE,
   };
 
-  List<NgAst.TemplateAst> rawAst;
-  final List<AnalysisError> parseErrors = <AnalysisError>[];
+  List<ng_ast.TemplateAst> rawAst;
+  final parseErrors = <AnalysisError>[];
 
   void parse(String content, Source source, {int offset = 0}) {
     if (offset != null) {
+      // ignore: prefer_interpolation_to_compose_strings, parameter_assignments
       content = ' ' * offset + content;
     }
-    var exceptionHandler = new NgAst.RecoveringExceptionHandler();
-    rawAst = NgAst.parse(
+    final exceptionHandler = new ng_ast.RecoveringExceptionHandler();
+    rawAst = ng_ast.parse(
       content,
       sourceUrl: source.toString(),
       desugar: false,
@@ -212,28 +213,28 @@
       exceptionHandler: exceptionHandler,
     );
 
-    for (NgAst.AngularParserException e in exceptionHandler.exceptions) {
-      if (e.errorCode is NgAst.NgParserWarningCode) {
-        this.parseErrors.add(new AnalysisError(
-              source,
-              e.offset,
-              e.length,
-              errorMap[e.errorCode] ?? e.errorCode,
-            ));
+    for (final e in exceptionHandler.exceptions) {
+      if (e.errorCode is ng_ast.NgParserWarningCode) {
+        parseErrors.add(new AnalysisError(
+          source,
+          e.offset,
+          e.length,
+          errorMap[e.errorCode] ?? e.errorCode,
+        ));
       }
     }
   }
 }
 
-setIgnoredErrors(Template template, List<NgAst.TemplateAst> asts) {
-  if (asts == null || asts.length == 0) {
+void setIgnoredErrors(Template template, List<ng_ast.TemplateAst> asts) {
+  if (asts == null || asts.isEmpty) {
     return;
   }
-  for (NgAst.TemplateAst ast in asts) {
-    if (ast is NgAst.TextAst && ast.value.trim().isEmpty) {
+  for (final ast in asts) {
+    if (ast is ng_ast.TextAst && ast.value.trim().isEmpty) {
       continue;
-    } else if (ast is NgAst.CommentAst) {
-      String text = ast.value.trim();
+    } else if (ast is ng_ast.CommentAst) {
+      var text = ast.value.trim();
       if (text.startsWith("@ngIgnoreErrors")) {
         text = text.substring("@ngIgnoreErrors".length);
         // Per spec: optional color
@@ -241,7 +242,7 @@
           text = text.substring(1);
         }
         // Per spec: optional commas
-        String delim = text.indexOf(',') == -1 ? ' ' : ',';
+        final delim = !text.contains(',') ? ' ' : ',';
         template.ignoredErrors.addAll(new HashSet.from(
             text.split(delim).map((c) => c.trim().toUpperCase())));
       }
diff --git a/analyzer_plugin/lib/starter.dart b/analyzer_plugin/lib/starter.dart
index dff6cfe..757a278 100644
--- a/analyzer_plugin/lib/starter.dart
+++ b/analyzer_plugin/lib/starter.dart
@@ -5,6 +5,14 @@
 import 'package:analyzer/src/generated/source.dart';
 import 'package:angular_analyzer_plugin/src/angular_driver.dart';
 import 'package:analyzer/src/context/builder.dart';
+import 'package:analysis_server/protocol/protocol.dart' show Request;
+import 'package:analysis_server/protocol/protocol_generated.dart'
+    show CompletionGetSuggestionsParams, CompletionGetSuggestionsResult;
+import 'package:analysis_server/src/services/completion/completion_core.dart';
+import 'package:analysis_server/src/services/completion/completion_performance.dart';
+import 'package:angular_analyzer_server_plugin/src/completion.dart';
+import 'package:analyzer/src/source/source_resource.dart';
+import 'package:analysis_server/src/domain_completion.dart';
 
 class Starter {
   final angularDrivers = <String, AngularDriver>{};
@@ -13,8 +21,10 @@
   void start(AnalysisServer server) {
     this.server = server;
     ContextBuilder.onCreateAnalysisDriver = onCreateAnalysisDriver;
-    server.onResultErrorSupplementor = sumErrors;
-    server.onNoAnalysisResult = sendHtmlResult;
+    server
+      ..onResultErrorSupplementor = sumErrors
+      ..onNoAnalysisResult = sendHtmlResult
+      ..onNoAnalysisCompletion = sendAngularCompletions;
   }
 
   void onCreateAnalysisDriver(
@@ -27,10 +37,10 @@
       driverPath,
       sourceFactory,
       analysisOptions) {
-    final AngularDriver driver = new AngularDriver(server, analysisDriver,
-        scheduler, byteStore, sourceFactory, contentOverlay);
+    final driver = new AngularDriver(server, analysisDriver, scheduler,
+        byteStore, sourceFactory, contentOverlay);
     angularDrivers[driverPath] = driver;
-    server.onFileAdded.listen((String path) {
+    server.onFileAdded.listen((path) {
       if (server.contextManager.getContextFolderFor(path).path == driverPath) {
         // only the owning driver "adds" the path
         driver.addFile(path);
@@ -39,10 +49,9 @@
         driver.fileChanged(path);
       }
     });
-    server.onFileChanged.listen((String path) {
-      // all drivers get change notification
-      driver.fileChanged(path);
-    });
+
+    // all drivers get change notification
+    server.onFileChanged.listen(driver.fileChanged);
   }
 
   Future sumErrors(String path, List<AnalysisError> errors) async {
@@ -69,4 +78,49 @@
 
     sendFn(null, null, null);
   }
+
+  // Handles .html completion. Directly sends the suggestions to the
+  // [completionHandler].
+  Future sendAngularCompletions(
+    Request request,
+    CompletionDomainHandler completionHandler,
+    CompletionGetSuggestionsParams params,
+    CompletionPerformance performance,
+    String completionId,
+  ) async {
+    final filePath = (request.toJson()['params'] as Map)['file'];
+    final source =
+        new FileSource(server.resourceProvider.getFile(filePath), filePath);
+
+    if (server.contextManager.isInAnalysisRoot(filePath)) {
+      for (final driverPath in angularDrivers.keys) {
+        if (server.contextManager.getContextFolderFor(filePath).path ==
+            driverPath) {
+          final driver = angularDrivers[driverPath];
+
+          final completionContributor =
+              new AngularCompletionContributor(driver);
+          final completionRequest = new CompletionRequestImpl(
+              null, // AnalysisResult - unneeded for AngularCompletion
+              server.resourceProvider,
+              source,
+              params.offset,
+              performance,
+              server.ideOptions);
+          completionHandler.setNewRequest(completionRequest);
+          server.sendResponse(new CompletionGetSuggestionsResult(completionId)
+              .toResponse(request.id));
+          final suggestions =
+              await completionContributor.computeSuggestions(completionRequest);
+          completionHandler
+            ..sendCompletionNotification(
+                completionId,
+                completionRequest.replacementOffset,
+                completionRequest.replacementLength,
+                suggestions)
+            ..ifMatchesRequestClear(completionRequest);
+        }
+      }
+    }
+  }
 }
diff --git a/analyzer_plugin/lib/tasks.dart b/analyzer_plugin/lib/tasks.dart
index 2011acd..0f1dadc 100644
--- a/analyzer_plugin/lib/tasks.dart
+++ b/analyzer_plugin/lib/tasks.dart
@@ -4,8 +4,8 @@
 import 'package:analyzer/error/error.dart';
 import 'package:angular_ast/angular_ast.dart';
 
-// used by angularWarningCodeByUniqueName to create a map for fast lookup
-const List<AngularWarningCode> _angularWarningCodeValues = const [
+/// used by angularWarningCodeByUniqueName to create a map for fast lookup
+const _angularWarningCodeValues = const <AngularWarningCode>[
   AngularWarningCode.ARGUMENT_SELECTOR_MISSING,
   AngularWarningCode.CANNOT_PARSE_SELECTOR,
   AngularWarningCode.REFERENCED_HTML_FILE_DOESNT_EXIST,
@@ -27,7 +27,7 @@
   AngularWarningCode.TWO_WAY_BINDING_OUTPUT_TYPE_ERROR,
   AngularWarningCode.INPUT_BINDING_TYPE_ERROR,
   AngularWarningCode.TRAILING_EXPRESSION,
-  AngularWarningCode.OUTPUT_MUST_BE_EVENTEMITTER,
+  AngularWarningCode.OUTPUT_MUST_BE_STREAM,
   AngularWarningCode.TWO_WAY_BINDING_NOT_ASSIGNABLE,
   AngularWarningCode.INPUT_ANNOTATION_PLACEMENT_INVALID,
   AngularWarningCode.OUTPUT_ANNOTATION_PLACEMENT_INVALID,
@@ -38,380 +38,332 @@
   AngularWarningCode.INVALID_CSS_PROPERTY_NAME,
   AngularWarningCode.INVALID_BINDING_NAME,
   AngularWarningCode.STRUCTURAL_DIRECTIVES_REQUIRE_TEMPLATE,
+  AngularWarningCode.CUSTOM_DIRECTIVE_MAY_REQUIRE_TEMPLATE,
+  AngularWarningCode.TEMPLATE_ATTR_NOT_USED,
   AngularWarningCode.NO_DIRECTIVE_EXPORTED_BY_SPECIFIED_NAME,
   AngularWarningCode.OFFSETS_CANNOT_BE_CREATED,
   AngularWarningCode.CONTENT_NOT_TRANSCLUDED,
   AngularWarningCode.OUTPUT_STATEMENT_REQUIRES_EXPRESSION_STATEMENT,
   AngularWarningCode.DISALLOWED_EXPRESSION,
   AngularWarningCode.ATTRIBUTE_PARAMETER_MUST_BE_STRING,
-  AngularWarningCode.STRING_STYLE_INPUT_BINDING_INVALID
+  AngularWarningCode.STRING_STYLE_INPUT_BINDING_INVALID,
+  AngularWarningCode.INVALID_TYPE_FOR_CHILD_QUERY,
+  AngularWarningCode.UNKNOWN_CHILD_QUERY_TYPE,
+  AngularWarningCode.CONTENT_OR_VIEW_CHILDREN_REQUIRES_QUERY_LIST,
+  AngularWarningCode.MATCHED_LET_BINDING_HAS_WRONG_TYPE
 ];
 
-/**
- * The lazy initialized map from [AngularWarningCode.uniqueName] to the
- * [ErrorCode] instance.
- */
+/// The lazy initialized map from [AngularWarningCode.uniqueName] to the
+/// [AngularWarningCode] instance.
 HashMap<String, ErrorCode> _uniqueNameToCodeMap;
 
-/**
- * Return the [AngularWarningCode] with the given [uniqueName], or `null` if not
- * found.
- */
+/// Return the [AngularWarningCode] with the given [uniqueName], or `null` if not
+/// found.
 ErrorCode angularWarningCodeByUniqueName(String uniqueName) {
   if (_uniqueNameToCodeMap == null) {
     _uniqueNameToCodeMap = new HashMap<String, AngularWarningCode>();
-    for (AngularWarningCode angularCode in _angularWarningCodeValues) {
+    for (final angularCode in _angularWarningCodeValues) {
       _uniqueNameToCodeMap[angularCode.uniqueName] = angularCode;
     }
-    for (NgParserWarningCode angularAstCode in angularAstWarningCodes) {
+    for (final angularAstCode in angularAstWarningCodes) {
       _uniqueNameToCodeMap[angularAstCode.uniqueName] = angularAstCode;
     }
   }
   return _uniqueNameToCodeMap[uniqueName];
 }
 
-/**
- * The error codes used for Angular warnings. The convention for this
- * class is for the name of the error code to indicate the problem that caused
- * the error to be generated and for the error message to explain what is wrong
- * and, when appropriate, how the problem can be corrected.
- */
+/// The error codes used for Angular warnings. The convention for this
+/// class is for the name of the error code to indicate the problem that caused
+/// the error to be generated and for the error message to explain what is wrong
+/// and, when appropriate, how the problem can be corrected.
 class AngularWarningCode extends ErrorCode {
-  /**
-   * An error code indicating that the annotation does not define the
-   * required "selector" argument.
-   */
-  static const AngularWarningCode ARGUMENT_SELECTOR_MISSING =
-      const AngularWarningCode(
-          'ARGUMENT_SELECTOR_MISSING', 'Argument "selector" missing');
+  /// An error code indicating that the annotation does not define the
+  /// required "selector" argument.
+  static const ARGUMENT_SELECTOR_MISSING = const AngularWarningCode(
+      'ARGUMENT_SELECTOR_MISSING', 'Argument "selector" missing');
 
-  /**
-   * An error code indicating that the provided selector cannot be parsed.
-   */
-  static const AngularWarningCode CANNOT_PARSE_SELECTOR =
-      const AngularWarningCode(
-          'CANNOT_PARSE_SELECTOR', 'Cannot parse the given selector ({0})');
+  /// An error code indicating that the provided selector cannot be parsed.
+  static const CANNOT_PARSE_SELECTOR = const AngularWarningCode(
+      'CANNOT_PARSE_SELECTOR', 'Cannot parse the given selector ({0})');
 
-  /**
-   * An error code indicating that a template points to a missing html file
-   */
-  static const AngularWarningCode REFERENCED_HTML_FILE_DOESNT_EXIST =
-      const AngularWarningCode('REFERENCED_HTML_FILE_DOESNT_EXIST',
-          'The referenced HTML file doesn\'t exist');
+  /// An error code indicating that a template points to a missing html file
+  static const REFERENCED_HTML_FILE_DOESNT_EXIST = const AngularWarningCode(
+      'REFERENCED_HTML_FILE_DOESNT_EXIST',
+      'The referenced HTML file doesn\'t exist');
 
-  /**
-   * An error code indicating that the component has @View annotation,
-   * but not @Component annotation.
-   */
-  static const AngularWarningCode COMPONENT_ANNOTATION_MISSING =
-      const AngularWarningCode('COMPONENT_ANNOTATION_MISSING',
-          'Every @View requires exactly one @Component annotation');
+  /// An error code indicating that the component has @View annotation,
+  /// but not @Component annotation.
+  static const COMPONENT_ANNOTATION_MISSING = const AngularWarningCode(
+      'COMPONENT_ANNOTATION_MISSING',
+      'Every @View requires exactly one @Component annotation');
 
-  /**
-   * An error code indicating that a @View or @Component has both a
-   * template and a templateUrl defined at once (illegal)
-   */
-  static const AngularWarningCode TEMPLATE_URL_AND_TEMPLATE_DEFINED =
-      const AngularWarningCode('TEMPLATE_URL_AND_TEMPLATE_DEFINED',
-          'Cannot define both template and templateUrl. Remove one');
+  /// An error code indicating that a @View or @Component has both a
+  /// template and a templateUrl defined at once (illegal)
+  static const TEMPLATE_URL_AND_TEMPLATE_DEFINED = const AngularWarningCode(
+      'TEMPLATE_URL_AND_TEMPLATE_DEFINED',
+      'Cannot define both template and templateUrl. Remove one');
 
-  /**
-   * An error code indicating that a @View or @Component does not have
-   * a template or a templateUrl
-   */
-  static const AngularWarningCode NO_TEMPLATE_URL_OR_TEMPLATE_DEFINED =
-      const AngularWarningCode('NO_TEMPLATE_URL_OR_TEMPLATE_DEFINED',
-          'Either a template or templateUrl is required');
+  /// An error code indicating that a @View or @Component does not have
+  /// a template or a templateUrl
+  static const NO_TEMPLATE_URL_OR_TEMPLATE_DEFINED = const AngularWarningCode(
+      'NO_TEMPLATE_URL_OR_TEMPLATE_DEFINED',
+      'Either a template or templateUrl is required');
 
-  /**
-   * An error code indicating that an identifier was expected, but not found.
-   */
-  static const AngularWarningCode EXPECTED_IDENTIFIER =
+  /// An error code indicating that an identifier was expected, but not found.
+  static const EXPECTED_IDENTIFIER =
       const AngularWarningCode('EXPECTED_IDENTIFIER', 'Expected identifier');
 
-  /**
-   * An error code indicating that an hash was unexpected in template.
-   */
-  static const AngularWarningCode UNEXPECTED_HASH_IN_TEMPLATE =
-      const AngularWarningCode(
-          'UNEXPECTED_HASH_IN_TEMPLATE', "Did you mean 'let' instead?");
+  /// An error code indicating that an hash was unexpected in template.
+  static const UNEXPECTED_HASH_IN_TEMPLATE = const AngularWarningCode(
+      'UNEXPECTED_HASH_IN_TEMPLATE', "Did you mean 'let' instead?");
 
-  /**
-   * An error code indicating that the value of an expression is not a string.
-   */
-  static const AngularWarningCode STRING_VALUE_EXPECTED =
-      const AngularWarningCode(
-          'STRING_VALUE_EXPECTED', 'A string value expected');
+  /// An error code indicating that the value of an expression is not a string.
+  static const STRING_VALUE_EXPECTED = const AngularWarningCode(
+      'STRING_VALUE_EXPECTED', 'A string value expected');
 
-  /**
-   * An error code indicating that the value of an expression is not a string.
-   */
-  static const AngularWarningCode TYPE_LITERAL_EXPECTED =
-      const AngularWarningCode(
-          'TYPE_LITERAL_EXPECTED', 'A type literal expected');
+  /// An error code indicating that the value of an expression is not a string.
+  static const TYPE_LITERAL_EXPECTED = const AngularWarningCode(
+      'TYPE_LITERAL_EXPECTED', 'A type literal expected');
 
-  /**
-   * An error code indicating that the value of an expression is not a string.
-   */
-  static const AngularWarningCode TYPE_IS_NOT_A_DIRECTIVE =
-      const AngularWarningCode(
-          'TYPE_IS_NOT_A_DIRECTIVE',
-          'The type "{0}" is included in the directives list, but is not a' +
-              ' directive');
+  /// An error code indicating that the value of an expression is not a string.
+  static const TYPE_IS_NOT_A_DIRECTIVE = const AngularWarningCode(
+      'TYPE_IS_NOT_A_DIRECTIVE',
+      'The type "{0}" is included in the directives list, but is not a'
+      ' directive');
 
-  /**
-   * An error code indicating that the tag was not resolved.
-   */
-  static const AngularWarningCode UNRESOLVED_TAG =
+  /// An error code indicating that the tag was not resolved.
+  static const UNRESOLVED_TAG =
       const AngularWarningCode('UNRESOLVED_TAG', 'Unresolved tag "{0}"');
 
-  /**
-   * An error code indicating that the embedded expression is not terminated.
-   */
-  static const AngularWarningCode UNTERMINATED_MUSTACHE =
-      const AngularWarningCode(
-          'UNTERMINATED_MUSTACHE', 'Unterminated mustache');
+  /// An error code indicating that the embedded expression is not terminated.
+  static const UNTERMINATED_MUSTACHE = const AngularWarningCode(
+      'UNTERMINATED_MUSTACHE', 'Unterminated mustache');
 
-  /**
-   * An error code indicating that a mustache ending was found unopened
-   */
-  static const AngularWarningCode UNOPENED_MUSTACHE = const AngularWarningCode(
+  /// An error code indicating that a mustache ending was found unopened
+  static const UNOPENED_MUSTACHE = const AngularWarningCode(
       'UNOPENED_MUSTACHE', 'Mustache terminator with no opening');
 
-  /**
-   * An error code indicating that a nonexist input was bound
-   */
-  static const AngularWarningCode NONEXIST_INPUT_BOUND =
-      const AngularWarningCode(
-          'NONEXIST_INPUT_BOUND',
-          'The bound input {0} does not exist on any directives or ' +
-              'on the element');
+  /// An error code indicating that a nonexist input was bound
+  static const NONEXIST_INPUT_BOUND = const AngularWarningCode(
+      'NONEXIST_INPUT_BOUND',
+      'The bound input {0} does not exist on any directives or on the element');
 
-  /**
-   * An error code indicating that a nonexist output was bound
-   */
-  static const AngularWarningCode NONEXIST_OUTPUT_BOUND =
-      const AngularWarningCode(
-          'NONEXIST_OUTPUT_BOUND',
-          'The bound output {0} does not exist on any directives or ' +
-              'on the element');
+  /// An error code indicating that a nonexist output was bound
+  static const NONEXIST_OUTPUT_BOUND = const AngularWarningCode(
+      'NONEXIST_OUTPUT_BOUND',
+      'The bound output {0} does not exist on any directives or on the'
+      ' element');
 
-  /**
-   * An error code indicating that a nonexist output was bound
-   */
-  static const AngularWarningCode EMPTY_BINDING = const AngularWarningCode(
+  /// An error code indicating that a nonexist output was bound
+  static const EMPTY_BINDING = const AngularWarningCode(
       'EMPTY_BINDING', 'The binding {0} does not have a value specified');
 
-  /**
-   * An error code indicating that a nonexist output was bound, perhaps
-   * because an input was two way bound. The nonexist bound output is
-   * an implementation detail, so give its own error.
-   */
-  static const AngularWarningCode NONEXIST_TWO_WAY_OUTPUT_BOUND =
-      const AngularWarningCode('NONEXIST_TWO_WAY_OUTPUT_BOUND',
-          'The two-way binding {0} requires a bindable output of name {1}');
+  /// An error code indicating that a nonexist output was bound, perhaps
+  /// because an input was two way bound. The nonexist bound output is
+  /// an implementation detail, so give its own error.
+  static const NONEXIST_TWO_WAY_OUTPUT_BOUND = const AngularWarningCode(
+      'NONEXIST_TWO_WAY_OUTPUT_BOUND',
+      'The two-way binding {0} requires a bindable output of name {1}');
 
-  /**
-   * An error code indicating that the output event in a two-way binding
-   * doesn't match the input
-   */
-  static const AngularWarningCode TWO_WAY_BINDING_OUTPUT_TYPE_ERROR =
-      const AngularWarningCode(
-          'TWO_WAY_BINDING_OUTPUT_TYPE_ERROR',
-          'Output event in two-way binding (of type {0}) ' +
-              'is not assignable to component input (of type {1})');
+  /// An error code indicating that the output event in a two-way binding
+  /// doesn't match the input
+  static const TWO_WAY_BINDING_OUTPUT_TYPE_ERROR = const AngularWarningCode(
+      'TWO_WAY_BINDING_OUTPUT_TYPE_ERROR',
+      'Output event in two-way binding (of type {0}) is not assignable to'
+      ' component input (of type {1})');
 
-  /**
-   * An error code indicating that an input was bound with a incorrectly
-   * typed expression
-   */
-  static const AngularWarningCode INPUT_BINDING_TYPE_ERROR =
-      const AngularWarningCode(
-          'INPUT_BINDING_TYPE_ERROR',
-          'Attribute value expression (of type {0}) ' +
-              'is not assignable to component input (of type {1})');
+  /// An error code indicating that an input was bound with a incorrectly
+  /// typed expression
+  static const INPUT_BINDING_TYPE_ERROR = const AngularWarningCode(
+      'INPUT_BINDING_TYPE_ERROR',
+      'Attribute value expression (of type {0}) is not assignable to component'
+      ' input (of type {1})');
 
-  /**
-   * An error code indicating that an expression did not correctly
-   * end with an EOF token.
-   */
-  static const AngularWarningCode TRAILING_EXPRESSION =
-      const AngularWarningCode(
-          'TRAILING_EXPRESSION', 'Expressions must end with an EOF');
+  /// An error code indicating that an expression did not correctly
+  /// end with an EOF token.
+  static const TRAILING_EXPRESSION = const AngularWarningCode(
+      'TRAILING_EXPRESSION', 'Expressions must end with an EOF');
 
-  /**
-   * An error code indicating that an @Output is not an EventEmitter
-   */
-  static const AngularWarningCode OUTPUT_MUST_BE_EVENTEMITTER =
-      const AngularWarningCode('OUTPUT_MUST_BE_EVENTEMMITTER',
-          'Output (of name {0}) must return an EventEmitter');
+  /// An error code indicating that an @Output is not an EventEmitter
+  static const OUTPUT_MUST_BE_STREAM = const AngularWarningCode(
+      'OUTPUT_MUST_BE_STREAM', 'Output (of name {0}) must return a Stream');
 
-  /**
-   * An error code indicating that a two-way binding expression was not
-   * a assignable (and therefore could only be one-way bound...)
-   */
-  static const AngularWarningCode TWO_WAY_BINDING_NOT_ASSIGNABLE =
-      const AngularWarningCode('TWO_WAY_BINDING_NOT_ASSIGNABLE',
-          'Only assignable expressions can be two-way bound');
+  /// An error code indicating that a two-way binding expression was not
+  /// a assignable (and therefore could only be one-way bound...)
+  static const TWO_WAY_BINDING_NOT_ASSIGNABLE = const AngularWarningCode(
+      'TWO_WAY_BINDING_NOT_ASSIGNABLE',
+      'Only assignable expressions can be two-way bound');
 
-  /**
-   * An error code indicating that an @Input annottaion was used in the wrong
-   * place
-   */
-  static const AngularWarningCode INPUT_ANNOTATION_PLACEMENT_INVALID =
-      const AngularWarningCode('INPUT_ANNOTATION_PLACEMENT_INVALID',
-          'The @Input() annotation can only be put on properties and setters');
+  /// An error code indicating that an @Input annottaion was used in the wrong
+  /// place
+  static const INPUT_ANNOTATION_PLACEMENT_INVALID = const AngularWarningCode(
+      'INPUT_ANNOTATION_PLACEMENT_INVALID',
+      'The @Input() annotation can only be put on properties and setters');
 
-  /**
-   * An error code indicating that an @Output annottaion was used in the wrong
-   * place
-   */
-  static const AngularWarningCode OUTPUT_ANNOTATION_PLACEMENT_INVALID =
-      const AngularWarningCode('OUTPUT_ANNOTATION_PLACEMENT_INVALID',
-          'The @Output() annotation can only be put on properties and getters');
+  /// An error code indicating that an @Output annottaion was used in the wrong
+  /// place
+  static const OUTPUT_ANNOTATION_PLACEMENT_INVALID = const AngularWarningCode(
+      'OUTPUT_ANNOTATION_PLACEMENT_INVALID',
+      'The @Output() annotation can only be put on properties and getters');
 
-  /**
-   * An error code indicating that a html classname was bound via
-   * [class.classname]="x" where classname is not a css identifier
-   * https://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
-   */
-  static const AngularWarningCode INVALID_HTML_CLASSNAME =
-      const AngularWarningCode('INVALID_HTML_CLASSNAME',
-          'The html classname {0} is not a valid classname');
+  /// An error code indicating that a html classname was bound via
+  /// [class.classname]="x" where classname is not a css identifier
+  /// https://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
+  static const INVALID_HTML_CLASSNAME = const AngularWarningCode(
+      'INVALID_HTML_CLASSNAME',
+      'The html classname {0} is not a valid classname');
 
-  /**
-   * An error code indicating that a html classname was bound via
-   * [class.classname]="x" where x was not a boolean
-   */
-  static const AngularWarningCode CLASS_BINDING_NOT_BOOLEAN =
-      const AngularWarningCode('CLASS_BINDING_NOT_BOOLEAN',
-          'Binding to a classname requires a boolean');
+  /// An error code indicating that a html classname was bound via
+  /// [class.classname]="x" where x was not a boolean
+  static const CLASS_BINDING_NOT_BOOLEAN = const AngularWarningCode(
+      'CLASS_BINDING_NOT_BOOLEAN', 'Binding to a classname requires a boolean');
 
-  /**
-   * An error code indicating that a css property with a unit was bound via
-   * [style.property.unit]="x" where x was not a number
-   */
-  static const AngularWarningCode CSS_UNIT_BINDING_NOT_NUMBER =
-      const AngularWarningCode('CSS_UNIT_BINDING_NOT_NUMBER',
-          'Binding to a css property with a unit requires a number');
+  /// An error code indicating that a css property with a unit was bound via
+  /// [style.property.unit]="x" where x was not a number
+  static const CSS_UNIT_BINDING_NOT_NUMBER = const AngularWarningCode(
+      'CSS_UNIT_BINDING_NOT_NUMBER',
+      'Binding to a css property with a unit requires a number');
 
-  /**
-   * An error code indicating that a css property with a unit was bound via
-   * [style.property.unit]="x" where unit was not an identifier
-   * https://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
-   */
-  static const AngularWarningCode INVALID_CSS_UNIT_NAME =
-      const AngularWarningCode('INVALID_CSS_UNIT_NAME',
-          'The css unit {0} is not a valid css identifier');
+  /// An error code indicating that a css property with a unit was bound via
+  /// [style.property.unit]="x" where unit was not an identifier
+  /// https://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
+  static const INVALID_CSS_UNIT_NAME = const AngularWarningCode(
+      'INVALID_CSS_UNIT_NAME',
+      'The css unit {0} is not a valid css identifier');
 
-  /**
-   * An error code indicating that a css property bound via
-   * [style.property]="x" or [style.property.unit]="x" where property was not an
-   * identifier
-   * https://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
-   */
-  static const AngularWarningCode INVALID_CSS_PROPERTY_NAME =
-      const AngularWarningCode('INVALID_CSS_PROPERTY_NAME',
-          'The css property {0} is not a valid css identifier');
+  /// An error code indicating that a css property bound via
+  /// [style.property]="x" or [style.property.unit]="x" where property was not an
+  /// identifier
+  /// https://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
+  static const INVALID_CSS_PROPERTY_NAME = const AngularWarningCode(
+      'INVALID_CSS_PROPERTY_NAME',
+      'The css property {0} is not a valid css identifier');
 
-  /**
-   * An error code indicating that a binding was not a * dart identifier, or
-   * [class.classname], or [attr.attrname], or [style.property], or
-   * [style.property.unit].
-   */
-  static const AngularWarningCode INVALID_BINDING_NAME =
-      const AngularWarningCode(
-          'INVALID_BINDING_NAME',
-          'The binding {} is not a valid dart identifer, attribute, style, ' +
-              'or class binding');
+  /// An error code indicating that a binding was not a * dart identifier, or
+  /// [class.classname], or [attr.attrname], or [style.property], or
+  /// [style.property.unit].
+  static const INVALID_BINDING_NAME = const AngularWarningCode(
+      'INVALID_BINDING_NAME',
+      'The binding {} is not a valid dart identifer, attribute, style, or class'
+      ' binding');
 
-  /**
-   * An error code indicating that ngIf or ngFor were used without a template
-   */
-  static const AngularWarningCode STRUCTURAL_DIRECTIVES_REQUIRE_TEMPLATE =
+  /// An error code indicating that ngIf or ngFor were used without a template
+  static const STRUCTURAL_DIRECTIVES_REQUIRE_TEMPLATE =
       const AngularWarningCode(
           'STRUCTURAL_DIRECTIVES_REQUIRE_TEMPLATE',
-          'Structural directive {0} requires a template. Did you mean ' +
-              '*{0}="..." or template="{0} ..." or <template {0} ...>?');
+          'Structural directive {0} requires a template. Did you mean'
+          ' *{0}="..." or template="{0} ..." or <template {0} ...>?');
 
-  /**
-   * An error code indicating in #y="x", x was not an exported name
-   */
-  static const AngularWarningCode NO_DIRECTIVE_EXPORTED_BY_SPECIFIED_NAME =
+  /// An error code indicating in #y="x", x was not an exported name
+  static const NO_DIRECTIVE_EXPORTED_BY_SPECIFIED_NAME =
       const AngularWarningCode('NO_DIRECTIVE_EXPORTED_BY_SPECIFIED_NAME',
           'No directives matching this element are exported by the name {0}');
 
-  /**
-   * An error code indicating that an output-bound statement
-   * must be an [ExpressionStatement].
-   */
-  static const AngularWarningCode
-      OUTPUT_STATEMENT_REQUIRES_EXPRESSION_STATEMENT = const AngularWarningCode(
-          'OUTPUT_STATEMENT_REQUIRES_EXPRESSION_STATEMENT',
+  /// An error code indicating that a custom component appears to require a star.
+  static const AngularWarningCode CUSTOM_DIRECTIVE_MAY_REQUIRE_TEMPLATE =
+      const AngularWarningCode(
+          'CUSTOM_DIRECTIVE_MAY_REQUIRE_TEMPLATE',
+          'The directive {0} accepts a TemplateRef in its constructor, so it'
+          ' may require a *-style-attr to work correctly.');
+
+  /// An error code indicating that a custom component appears to require a star.
+  static const AngularWarningCode TEMPLATE_ATTR_NOT_USED =
+      const AngularWarningCode(
+          'TEMPLATE_ATTR_NOT_USED',
+          'This template attr does not match any directives that use the'
+          ' resulting hidden template. Check that all directives are being'
+          ' imported and used correctly.');
+
+  /// An error code indicating that an output-bound statement
+  /// must be an [ExpressionStatement].
+  static const OUTPUT_STATEMENT_REQUIRES_EXPRESSION_STATEMENT =
+      const AngularWarningCode('OUTPUT_STATEMENT_REQUIRES_EXPRESSION_STATEMENT',
           "Syntax Error: unexpected {0}");
 
-  /**
-   * An error code indicating that a mustache or other expression binding was an
-   * unsupported type such as an 'as' expression or a constructor
-   */
-  static const AngularWarningCode DISALLOWED_EXPRESSION =
+  /// An error code indicating that a mustache or other expression binding was an
+  /// unsupported type such as an 'as' expression or a constructor
+  static const DISALLOWED_EXPRESSION = const AngularWarningCode(
+      'DISALLOWED_EXPRESSION', "{0} not allowed in angular templates");
+
+  /// An error code indicating that an output-bound statement
+  /// must be an [ExpressionStatement].
+  static const OFFSETS_CANNOT_BE_CREATED = const AngularWarningCode(
+      'OFFSETS_CANNOT_BE_CREATED',
+      'Errors cannot be tracked for the constant expression because it is too'
+      ' complex for errors to be mapped to locations in the file');
+
+  /// An error code indicating that dom inside a component won't be transcluded
+  static const CONTENT_NOT_TRANSCLUDED = const AngularWarningCode(
+      'CONTENT_NOT_TRANSCLUDED',
+      'The content does not match any transclusion selectors of the surrounding'
+      ' component');
+
+  /// An error code indicating that an <ng-content> tag had content, which is not
+  /// allowed.
+  static const NG_CONTENT_MUST_BE_EMPTY = const AngularWarningCode(
+      'NG_CONTENT_MUST_BE_EMPTY',
+      'Nothing is allowed inside an <ng-content> tag, as it will be replaced');
+
+  /// An error code indicating that a constructor parameter was marked with
+  /// @Attribute, but the argument wasn't of type string.
+  static const ATTRIBUTE_PARAMETER_MUST_BE_STRING = const AngularWarningCode(
+      'ATTRIBUTE_PARAMETER_MUST_BE_STRING',
+      'Parameters marked with @Attribute must be of type String');
+
+  /// An error code indicating that an input binding was used in string form, ie,
+  /// `x="y"` rather than `[x]="y"`, where input x is not a string input.
+  static const STRING_STYLE_INPUT_BINDING_INVALID = const AngularWarningCode(
+      'STRING_STYLE_INPUT_BINDING_INVALID',
+      'Input {0} is not a string input, but is not bound with [bracket] syntax.'
+      ' This binds the String attribute value directly, resulting  in a type '
+      'error.');
+
+  /// An error code indicating that a @ContentChild or @ContentChildren field
+  /// either mismatched types in the definition, or where it was used (ie
+  /// `@ContentChild(TemplateRef) ElementRef foo`, or `@ContentChild('foo')
+  /// TemplateRef foo` with `<div #foo>`).
+  static const INVALID_TYPE_FOR_CHILD_QUERY = const AngularWarningCode(
+      'INVALID_TYPE_FOR_CHILD_QUERY',
+      'The field {0} marked with @{1} referencing type {2} expects a member'
+      ' referencing type {2}, but got a {3}');
+
+  /// An error code indicating that a @ContentChild or @ContentChildren field
+  /// didn't have an expected value
+  static const UNKNOWN_CHILD_QUERY_TYPE = const AngularWarningCode(
+      'UNKNOWN_CHILD_QUERY_TYPE',
+      'The field {0} marked with @{1} must reference a directive, a string'
+      ' let-binding name, TemplateRef, or ElementRef');
+
+  /// An error code indicating that @ContentChildren or @ViewChildren was used
+  /// but the property wasn't a `QueryList`.
+  static const CONTENT_OR_VIEW_CHILDREN_REQUIRES_QUERY_LIST =
       const AngularWarningCode(
-          'DISALLOWED_EXPRESSION', "{0} not allowed in angular templates");
+          'CONTENT_OR_VIEW_CHILDREN_REQUIRES_QUERY_LIST',
+          'The field {0} marked with @{1} expects a member of type QueryList,'
+          ' but got {2}');
 
-  /**
-   * An error code indicating that an output-bound statement
-   * must be an [ExpressionStatement].
-   */
-  static const AngularWarningCode OFFSETS_CANNOT_BE_CREATED =
+  /// An error code indicating that @ContentChild or @ViewChild with a string
+  /// let-binding query was matched in a way that's not assignable to the
+  /// annotated property.
+  static const MATCHED_LET_BINDING_HAS_WRONG_TYPE = const AngularWarningCode(
+      'MATCHED_LET_BINDING_HAS_WRONG_TYPE',
+      'Marking this with #{0} here expects the element to be of type {1}, (but'
+      ' is of type {2}) because an enclosing element marks {0} as a content'
+      ' child field of type {1}.');
+
+  /// An error code indicating that @ContentChild or @ViewChild was matched
+  /// multiple times.
+  static const SINGULAR_CHILD_QUERY_MATCHED_MULTIPLE_TIMES =
       const AngularWarningCode(
-          'OFFSETS_CANNOT_BE_CREATED',
-          "Errors cannot be tracked for the constant expression because it is" +
-              " too complex for errors to be mapped to locations in the file");
+          'SINGULAR_CHILD_QUERY_MATCHED_MULTIPLE_TIMES',
+          'A containing {0} expects a single child matching {1}, but this is'
+          ' not the first match. Use (Content or View)Children to allow'
+          ' multiple matches.');
 
-  /**
-   * An error code indicating that dom inside a component won't be transcluded
-   */
-  static const AngularWarningCode CONTENT_NOT_TRANSCLUDED =
-      const AngularWarningCode(
-          'CONTENT_NOT_TRANSCLUDED',
-          "The content does not match any transclusion selectors of the" +
-              " surrounding component");
-
-  /**
-   * An error code indicating that an <ng-content> tag had content, which is not
-   * allowed.
-   */
-  static const AngularWarningCode NG_CONTENT_MUST_BE_EMPTY =
-      const AngularWarningCode(
-          'NG_CONTENT_MUST_BE_EMPTY',
-          "Nothing is allowed inside an <ng-content> tag, as it will be" +
-              " replaced");
-
-  /**
-   * An error code indicating that a constructor parameter was marked with
-   * @Attribute, but the argument wasn't of type string.
-   */
-  static const AngularWarningCode ATTRIBUTE_PARAMETER_MUST_BE_STRING =
-      const AngularWarningCode('ATTRIBUTE_PARAMETER_MUST_BE_STRING',
-          "Parameters marked with @Attribute must be of type String");
-
-  /**
-   * An error code indicating that an input binding was used in string form, ie,
-   * `x="y"` rather than `[x]="y"`, where input x is not a string input.
-   */
-  static const AngularWarningCode STRING_STYLE_INPUT_BINDING_INVALID =
-      const AngularWarningCode(
-          'STRING_STYLE_INPUT_BINDING_INVALID',
-          "Input {0} is not a string input, but is not bound with [bracket] "
-          "syntax. This binds the String attribute value directly, resulting "
-          "in a type error.");
-
-  /**
-   * Initialize a newly created error code to have the given [name].
-   * The message associated with the error will be created from the given
-   * [message] template. The correction associated with the error will be
-   * created from the given [correction] template.
-   */
+  /// Initialize a newly created error code to have the given [name].
+  /// The message associated with the error will be created from the given
+  /// [message] template. The correction associated with the error will be
+  /// created from the given [correction] template.
   const AngularWarningCode(String name, String message, [String correction])
       : super(name, message, correction);
 
diff --git a/analyzer_plugin/pubspec.yaml b/analyzer_plugin/pubspec.yaml
index a51e13a..f20f85a 100644
--- a/analyzer_plugin/pubspec.yaml
+++ b/analyzer_plugin/pubspec.yaml
@@ -4,16 +4,20 @@
 environment:
   sdk: '>=1.21.0-dev.1.0'
 dependencies:
-  analyzer: '^0.30.0-alpha.0'
+  analyzer: '^0.30.0'
   plugin: '^0.2.0'
   html: '^0.12.2'
   tuple: '^1.0.1'
+  analysis_server: any
 dev_dependencies:
-  test_reflective_loader: '^0.0.3'
+  test_reflective_loader: '^0.1.0'
   typed_mock: '^0.0.4'
   unittest: '^0.11.0'
+  test: '^0.12.20'
 dependency_overrides:
   analyzer:
-     path: ../../sdk/pkg/analyzer
+     path: ../deps/sdk/pkg/analyzer
   front_end:
-     path: ../../sdk/pkg/front_end
+     path: ../deps/sdk/pkg/front_end
+  analysis_server:
+    path: ../deps/sdk/pkg/analysis_server
diff --git a/analyzer_plugin/test/abstract_angular.dart b/analyzer_plugin/test/abstract_angular.dart
index 7d4560c..4112f3c 100644
--- a/analyzer_plugin/test/abstract_angular.dart
+++ b/analyzer_plugin/test/abstract_angular.dart
@@ -1,6 +1,3 @@
-library angular2.src.analysis.analyzer_plugin.src.angular_base;
-
-import 'package:analyzer/file_system/file_system.dart' as fs;
 import 'package:analyzer/context/context_root.dart';
 import 'package:analyzer/source/package_map_resolver.dart';
 import 'package:analyzer/file_system/memory_file_system.dart';
@@ -16,13 +13,12 @@
 import 'package:tuple/tuple.dart';
 import 'package:unittest/unittest.dart';
 
-import 'mock_sdk.dart';
-
 import 'package:analysis_server/src/analysis_server.dart';
 import 'package:analysis_server/src/plugin/notification_manager.dart';
-import 'package:analyzer/src/dart/analysis/byte_store.dart';
+import 'package:front_end/src/incremental/byte_store.dart';
+import 'package:front_end/src/base/performace_logger.dart';
 import 'package:analyzer/src/dart/analysis/driver.dart'
-    show AnalysisDriver, AnalysisDriverScheduler, PerformanceLog;
+    show AnalysisDriver, AnalysisDriverScheduler;
 import 'package:analyzer/src/dart/analysis/file_state.dart';
 import 'package:analyzer/src/generated/engine.dart';
 
@@ -30,16 +26,18 @@
 import 'package:analyzer/src/dart/analysis/driver.dart';
 import 'package:analyzer/src/generated/source_io.dart';
 
+import 'mock_sdk.dart';
+
 void assertComponentReference(
     ResolvedRange resolvedRange, Component component) {
-  ElementNameSelector selector = component.selector;
-  AngularElement element = resolvedRange.element;
+  final ElementNameSelector selector = component.selector;
+  final element = resolvedRange.element;
   expect(element, selector.nameElement);
   expect(resolvedRange.range.length, selector.nameElement.name.length);
 }
 
 PropertyAccessorElement assertGetter(ResolvedRange resolvedRange) {
-  PropertyAccessorElement element =
+  final PropertyAccessorElement element =
       (resolvedRange.element as DartElement).element;
   expect(element.isGetter, isTrue);
   return element;
@@ -47,34 +45,32 @@
 
 void assertPropertyReference(
     ResolvedRange resolvedRange, AbstractDirective directive, String name) {
-  var element = resolvedRange.element;
-  for (InputElement input in directive.inputs) {
+  final element = resolvedRange.element;
+  for (final input in directive.inputs) {
     if (input.name == name) {
       expect(element, same(input));
       return;
     }
   }
-  fail('Expected input "$name", but ${element} found.');
+  fail('Expected input "$name", but $element found.');
 }
 
 Component getComponentByClassName(
-    List<AbstractDirective> directives, String className) {
-  return getDirectiveByClassName(directives, className);
-}
+        List<AbstractDirective> directives, String className) =>
+    getDirectiveByClassName(directives, className);
 
 AbstractDirective getDirectiveByClassName(
-    List<AbstractDirective> directives, String className) {
-  return directives.firstWhere(
-      (directive) => directive.classElement.name == className, orElse: () {
-    fail('DirectiveMetadata with the class "$className" was not found.');
-    return null;
-  });
-}
+        List<AbstractDirective> directives, String className) =>
+    directives.firstWhere(
+        (directive) => directive.classElement.name == className, orElse: () {
+      fail('DirectiveMetadata with the class "$className" was not found.');
+      return null;
+    });
 
 ResolvedRange getResolvedRangeAtString(
     String code, List<ResolvedRange> ranges, String str,
     [ResolvedRangeCondition condition]) {
-  int offset = code.indexOf(str);
+  final offset = code.indexOf(str);
   return ranges.firstWhere((range) {
     if (range.range.offset == offset) {
       return condition == null || condition(range);
@@ -86,15 +82,13 @@
   });
 }
 
-View getViewByClassName(List<View> views, String className) {
-  return views.firstWhere((view) => view.classElement.name == className,
-      orElse: () {
-    fail('View with the class "$className" was not found.');
-    return null;
-  });
-}
+View getViewByClassName(List<View> views, String className) =>
+    views.firstWhere((view) => view.classElement.name == className, orElse: () {
+      fail('View with the class "$className" was not found.');
+      return null;
+    });
 
-typedef ResolvedRangeCondition(ResolvedRange range);
+typedef bool ResolvedRangeCondition(ResolvedRange range);
 
 class AbstractAngularTest {
   MemoryResourceProvider resourceProvider;
@@ -106,7 +100,7 @@
   GatheringErrorListener errorListener;
 
   Source newSource(String path, [String content = '']) {
-    fs.File file = resourceProvider.newFile(path, content);
+    final file = resourceProvider.newFile(path, content);
     final source = file.createSource();
     angularDriver.addFile(path);
     dartDriver.addFile(path);
@@ -114,24 +108,25 @@
   }
 
   void setUp() {
-    PerformanceLog logger = new PerformanceLog(new StringBuffer());
-    var byteStore = new MemoryByteStore();
+    final logger = new PerformanceLog(new StringBuffer());
+    final byteStore = new MemoryByteStore();
 
-    AnalysisDriverScheduler scheduler = new AnalysisDriverScheduler(logger);
-    scheduler.start();
+    final scheduler = new AnalysisDriverScheduler(logger)..start();
     resourceProvider = new MemoryResourceProvider();
 
     sdk = new MockSdk(resourceProvider: resourceProvider);
-    final packageMap = new Map<String, List<Folder>>();
-    PackageMapUriResolver packageResolver =
+    final packageMap = <String, List<Folder>>{
+      "angular2": [resourceProvider.getFolder("/angular2")]
+    };
+    final packageResolver =
         new PackageMapUriResolver(resourceProvider, packageMap);
-    SourceFactory sf = new SourceFactory([
+    final sf = new SourceFactory([
       new DartUriResolver(sdk),
       packageResolver,
       new ResourceUriResolver(resourceProvider)
     ]);
-    var testPath = resourceProvider.convertPath('/test');
-    var contextRoot = new ContextRoot(testPath, []);
+    final testPath = resourceProvider.convertPath('/test');
+    final contextRoot = new ContextRoot(testPath, []);
 
     dartDriver = new AnalysisDriver(
         scheduler,
@@ -246,6 +241,29 @@
   final String attributeName;
   const Attribute(this.attributeName);
 }
+
+class ContentChild extends Query {
+  const ContentChild(dynamic /* Type | String */ selector,
+              {dynamic read: null}) : super(selector);
+}
+
+class ContentChildren extends Query {
+  const ContentChildren(dynamic /* Type | String */ selector,
+              {dynamic read: null}) : super(selector);
+}
+
+class Query extends DependencyMetadata {
+  final dynamic /* Type | String */ selector;
+  const DependencyMetadata(this.selector) : super();
+}
+
+class DependencyMetadata {
+  const DependencyMetadata();
+}
+
+class TemplateRef {}
+class ElementRef {}
+class QueryList<T> implements Iterable<T> {}
 ''');
     newSource(
         '/angular2/src/core/async.dart',
@@ -289,6 +307,7 @@
 
 @Directive(selector: "[ngIf]", inputs: const ["ngIf"])
 class NgIf {
+  NgIf(TemplateRef tpl);
   set ngIf(newCondition) {}
 }
 ''');
@@ -301,41 +320,39 @@
     selector: "[ngFor][ngForOf]",
     inputs: const ["ngForOf", "ngForTemplate"])
 class NgFor {
+  NgFor(TemplateRef tpl);
   set ngForOf(dynamic value) {}
 }
 ''');
   }
 
-  /**
-   * Assert that the [errCode] is reported for [code], highlighting the [snippet].
-   */
+  /// Assert that the [errCode] is reported for [code], highlighting the [snippet].
   void assertErrorInCodeAtPosition(
       ErrorCode errCode, String code, String snippet) {
-    int snippetIndex = code.indexOf(snippet);
+    final snippetIndex = code.indexOf(snippet);
     expect(snippetIndex, greaterThan(-1),
-        reason: 'Error in test: snippet ${snippet} not part of code ${code}');
+        reason: 'Error in test: snippet $snippet not part of code $code');
     errorListener.assertErrorsWithCodes(<ErrorCode>[errCode]);
-    AnalysisError error = errorListener.errors.single;
+    final error = errorListener.errors.single;
     expect(error.offset, snippetIndex);
     expect(errorListener.errors.single.length, snippet.length);
   }
 
-  /** For [expectedErrors], it is a List of Tuple4 (1 per error):
-   *    code segment where offset begins,
-   *    length of the error highlight,
-   *    errorCode,
-   *    and optional error args - pass empty list if not needed.
-   */
+  /// For [expectedErrors], it is a List of Tuple4 (1 per error):
+  ///   code segment where offset begins,
+  ///   length of error highlight,
+  ///   errorCode,
+  ///   and optional error args - pass empty list if not needed.
   void assertMultipleErrorsExplicit(
     Source source,
     String code,
     List<Tuple4<String, int, ErrorCode, List<Object>>> expectedErrors,
   ) {
-    var realErrors = errorListener.errors;
+    final realErrors = errorListener.errors;
     for (Tuple4 expectedError in expectedErrors) {
-      var offset = code.indexOf(expectedError.item1);
+      final offset = code.indexOf(expectedError.item1);
       assert(offset != -1);
-      var currentExpectedError = new AnalysisError(
+      final currentExpectedError = new AnalysisError(
         source,
         offset,
         expectedError.item2,
@@ -346,7 +363,7 @@
         realErrors.contains(currentExpectedError),
         true,
         reason: 'Expected error code ${expectedError.item3} never occurs at '
-            'location ${offset} of length ${expectedError.item2}.',
+            'location $offset of length ${expectedError.item2}.',
       );
       expect(realErrors.length, expectedErrors.length,
           reason: 'Expected error counts do not  match.');
@@ -354,46 +371,38 @@
   }
 }
 
-/**
- * Instances of the class [GatheringErrorListener] implement an error listener
- * that collects all of the errors passed to it for later examination.
- */
+/// Instances of the class [GatheringErrorListener] implement an error listener
+/// that collects all of the errors passed to it for later examination.
 class GatheringErrorListener implements AnalysisErrorListener {
-  /**
-   * A list containing the errors that were collected.
-   */
-  List<AnalysisError> errors = new List<AnalysisError>();
+  /// A list containing the errors that were collected.
+  final errors = <AnalysisError>[];
 
-  /**
-   * Add all of the given errors to this listener.
-   */
+  /// Add all of the given errors to this listener.
   void addAll(List<AnalysisError> errors) {
-    for (AnalysisError error in errors) {
+    for (final error in errors) {
       onError(error);
     }
   }
 
-  /**
-   * Assert that the number of errors that have been gathered matches the number
-   * of errors that are given and that they have the expected error codes. The
-   * order in which the errors were gathered is ignored.
-   */
+  /// Assert that the number of errors that have been gathered matches the number
+  /// of errors that are given and that they have the expected error codes. The
+  /// order in which the errors were gathered is ignored.
   void assertErrorsWithCodes(
       [List<ErrorCode> expectedErrorCodes = const <ErrorCode>[]]) {
-    StringBuffer buffer = new StringBuffer();
+    final buffer = new StringBuffer();
     //
     // Verify that the expected error codes have a non-empty message.
     //
-    for (ErrorCode errorCode in expectedErrorCodes) {
+    for (final errorCode in expectedErrorCodes) {
       expect(errorCode.message.isEmpty, isFalse,
           reason: "Empty error code message");
     }
     //
     // Compute the expected number of each type of error.
     //
-    Map<ErrorCode, int> expectedCounts = <ErrorCode, int>{};
-    for (ErrorCode code in expectedErrorCodes) {
-      int count = expectedCounts[code];
+    final expectedCounts = <ErrorCode, int>{};
+    for (final code in expectedErrorCodes) {
+      var count = expectedCounts[code];
       if (count == null) {
         count = 1;
       } else {
@@ -404,13 +413,12 @@
     //
     // Compute the actual number of each type of error.
     //
-    Map<ErrorCode, List<AnalysisError>> errorsByCode =
-        <ErrorCode, List<AnalysisError>>{};
-    for (AnalysisError error in errors) {
-      ErrorCode code = error.errorCode;
-      List<AnalysisError> list = errorsByCode[code];
+    final errorsByCode = <ErrorCode, List<AnalysisError>>{};
+    for (final error in errors) {
+      final code = error.errorCode;
+      var list = errorsByCode[code];
       if (list == null) {
-        list = new List<AnalysisError>();
+        list = <AnalysisError>[];
         errorsByCode[code] = list;
       }
       list.add(error);
@@ -418,9 +426,9 @@
     //
     // Compare the expected and actual number of each type of error.
     //
-    expectedCounts.forEach((ErrorCode code, int expectedCount) {
+    expectedCounts.forEach((code, expectedCount) {
       int actualCount;
-      List<AnalysisError> list = errorsByCode.remove(code);
+      final list = errorsByCode.remove(code);
       if (list == null) {
         actualCount = 0;
       } else {
@@ -432,31 +440,33 @@
         } else {
           buffer.write("; ");
         }
-        buffer.write(expectedCount);
-        buffer.write(" errors of type ");
-        buffer.write(code.uniqueName);
-        buffer.write(", found ");
-        buffer.write(actualCount);
+        buffer
+          ..write(expectedCount)
+          ..write(" errors of type ")
+          ..write(code.uniqueName)
+          ..write(", found ")
+          ..write(actualCount);
       }
     });
     //
     // Check that there are no more errors in the actual-errors map,
     // otherwise record message.
     //
-    errorsByCode.forEach((ErrorCode code, List<AnalysisError> actualErrors) {
-      int actualCount = actualErrors.length;
-      if (buffer.length == 0) {
+    errorsByCode.forEach((code, actualErrors) {
+      final actualCount = actualErrors.length;
+      if (buffer.isEmpty) {
         buffer.write("Expected ");
       } else {
         buffer.write("; ");
       }
-      buffer.write("0 errors of type ");
-      buffer.write(code.uniqueName);
-      buffer.write(", found ");
-      buffer.write(actualCount);
-      buffer.write(" (");
-      for (int i = 0; i < actualErrors.length; i++) {
-        AnalysisError error = actualErrors[i];
+      buffer
+        ..write("0 errors of type ")
+        ..write(code.uniqueName)
+        ..write(", found ")
+        ..write(actualCount)
+        ..write(" (");
+      for (var i = 0; i < actualErrors.length; i++) {
+        final error = actualErrors[i];
         if (i > 0) {
           buffer.write(", ");
         }
@@ -469,9 +479,7 @@
     }
   }
 
-  /**
-   * Assert that no errors have been gathered.
-   */
+  /// Assert that no errors have been gathered.
   void assertNoErrors() {
     assertErrorsWithCodes();
   }
@@ -483,6 +491,7 @@
 }
 
 class MockAnalysisServer extends TypedMock implements AnalysisServer {
+  @override
   NotificationManager notificationManager = new MockNotificationManager();
 }
 
diff --git a/analyzer_plugin/test/angular_driver_test.dart b/analyzer_plugin/test/angular_driver_test.dart
index 912c4e8..099699d 100644
--- a/analyzer_plugin/test/angular_driver_test.dart
+++ b/analyzer_plugin/test/angular_driver_test.dart
@@ -1,8 +1,5 @@
-library angular2.src.analysis.analyzer_plugin.src.tasks_test;
-
 import 'dart:async';
 
-import 'package:angular_analyzer_plugin/src/standard_components.dart';
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/error/error.dart';
 import 'package:analyzer/error/listener.dart';
@@ -21,7 +18,7 @@
 
 import 'abstract_angular.dart';
 
-main() {
+void main() {
   defineReflectiveSuite(() {
     defineReflectiveTests(AngularParseHtmlTest);
     defineReflectiveTests(BuildStandardHtmlComponentsTest);
@@ -35,8 +32,9 @@
 
 @reflectiveTest
 class AngularParseHtmlTest extends AbstractAngularTest {
-  test_perform() {
-    String code = r'''
+  // ignore: non_constant_identifier_names
+  void test_perform() {
+    final code = r'''
 <!DOCTYPE html>
 <html>
   <head>
@@ -48,34 +46,31 @@
 </html>
     ''';
     final source = newSource('/test.html', code);
-    final tplParser = new TemplateParser();
-
-    tplParser.parse(code, source);
+    final tplParser = new TemplateParser()..parse(code, source);
     expect(tplParser.parseErrors, isEmpty);
     // HTML_DOCUMENT
     {
-      var asts = tplParser.rawAst;
+      final asts = tplParser.rawAst;
       expect(asts, isNotNull);
       // verify that attributes are not lower-cased
-      ElementAst element = asts[1].childNodes[3].childNodes[1];
+      final element = asts[1].childNodes[3].childNodes[1] as ElementAst;
       expect(element.attributes.length, 1);
       expect(element.attributes[0].name, 'myAttr');
       expect(element.attributes[0].value, 'my value');
     }
   }
 
-  test_perform_noDocType() {
-    String code = r'''
+  // ignore: non_constant_identifier_names
+  void test_perform_noDocType() {
+    final code = r'''
 <div>AAA</div>
 <span>BBB</span>
 ''';
     final source = newSource('/test.html', code);
-    final tplParser = new TemplateParser();
-
-    tplParser.parse(code, source);
+    final tplParser = new TemplateParser()..parse(code, source);
     // validate Document
     {
-      List<StandaloneTemplateAst> asts = tplParser.rawAst;
+      final asts = tplParser.rawAst;
       expect(asts, isNotNull);
       expect(asts.length, 4);
       expect((asts[0] as ElementAst).name, 'div');
@@ -85,18 +80,18 @@
     expect(tplParser.parseErrors, isEmpty);
   }
 
-  test_perform_noDocType_with_dangling_unclosed_tag() {
-    String code = r'''
+  // ignore: non_constant_identifier_names
+  // ignore: non_constant_identifier_names
+  void test_perform_noDocType_with_dangling_unclosed_tag() {
+    final code = r'''
 <div>AAA</div>
 <span>BBB</span>
 <di''';
     final source = newSource('/test.html', code);
-    final tplParser = new TemplateParser();
-
-    tplParser.parse(code, source);
+    final tplParser = new TemplateParser()..parse(code, source);
     // quick validate Document
     {
-      List<StandaloneTemplateAst> asts = tplParser.rawAst;
+      final asts = tplParser.rawAst;
       expect(asts, isNotNull);
       expect(asts.length, 5);
       expect((asts[0] as ElementAst).name, 'div');
@@ -108,21 +103,23 @@
 
 @reflectiveTest
 class BuildStandardHtmlComponentsTest extends AbstractAngularTest {
+  // ignore: non_constant_identifier_names
+  // ignore: non_constant_identifier_names
   Future test_perform() async {
-    StandardHtml stdhtml = await angularDriver.getStandardHtml();
+    final stdhtml = await angularDriver.getStandardHtml();
     // validate
-    Map<String, Component> map = stdhtml.components;
+    final map = stdhtml.components;
     expect(map, isNotNull);
     // a
     {
-      Component component = map['a'];
+      final component = map['a'];
       expect(component, isNotNull);
       expect(component.classElement.displayName, 'AnchorElement');
       expect(component.selector.toString(), 'a');
-      List<InputElement> inputs = component.inputs;
-      List<OutputElement> outputElements = component.outputs;
+      final inputs = component.inputs;
+      final outputElements = component.outputs;
       {
-        InputElement input = inputs.singleWhere((i) => i.name == 'href');
+        final input = inputs.singleWhere((i) => i.name == 'href');
         expect(input, isNotNull);
         expect(input.setter, isNotNull);
         expect(input.setterType.toString(), equals("String"));
@@ -132,14 +129,14 @@
     }
     // button
     {
-      Component component = map['button'];
+      final component = map['button'];
       expect(component, isNotNull);
       expect(component.classElement.displayName, 'ButtonElement');
       expect(component.selector.toString(), 'button');
-      List<InputElement> inputs = component.inputs;
-      List<OutputElement> outputElements = component.outputs;
+      final inputs = component.inputs;
+      final outputElements = component.outputs;
       {
-        InputElement input = inputs.singleWhere((i) => i.name == 'autofocus');
+        final input = inputs.singleWhere((i) => i.name == 'autofocus');
         expect(input, isNotNull);
         expect(input.setter, isNotNull);
         expect(input.setterType.toString(), equals("bool"));
@@ -148,23 +145,23 @@
     }
     // input
     {
-      Component component = map['input'];
+      final component = map['input'];
       expect(component, isNotNull);
       expect(component.classElement.displayName, 'InputElement');
       expect(component.selector.toString(), 'input');
-      List<OutputElement> outputElements = component.outputs;
+      final outputElements = component.outputs;
       expect(outputElements, hasLength(0));
     }
     // body is one of the few elements with special events
     {
-      Component component = map['body'];
+      final component = map['body'];
       expect(component, isNotNull);
       expect(component.classElement.displayName, 'BodyElement');
       expect(component.selector.toString(), 'body');
-      List<OutputElement> outputElements = component.outputs;
+      final outputElements = component.outputs;
       expect(outputElements, hasLength(1));
       {
-        OutputElement output = outputElements[0];
+        final output = outputElements[0];
         expect(output.name, equals("unload"));
         expect(output.getter, isNotNull);
         expect(output.eventType, isNotNull);
@@ -178,59 +175,61 @@
     expect(map['option'], isNotNull);
   }
 
-  test_buildStandardHtmlEvents() async {
-    StandardHtml stdhtml = await angularDriver.getStandardHtml();
-    Map<String, OutputElement> outputElements = stdhtml.events;
+  // ignore: non_constant_identifier_names
+  Future test_buildStandardHtmlEvents() async {
+    final stdhtml = await angularDriver.getStandardHtml();
+    final outputElements = stdhtml.events;
     {
       // This one is important because it proves we're using @DomAttribute
       // to generate the output name and not the method in the sdk.
-      OutputElement outputElement = outputElements['keyup'];
+      final outputElement = outputElements['keyup'];
       expect(outputElement, isNotNull);
       expect(outputElement.getter, isNotNull);
       expect(outputElement.eventType, isNotNull);
     }
     {
-      OutputElement outputElement = outputElements['cut'];
+      final outputElement = outputElements['cut'];
       expect(outputElement, isNotNull);
       expect(outputElement.getter, isNotNull);
       expect(outputElement.eventType, isNotNull);
     }
     {
-      OutputElement outputElement = outputElements['click'];
+      final outputElement = outputElements['click'];
       expect(outputElement, isNotNull);
       expect(outputElement.getter, isNotNull);
       expect(outputElement.eventType, isNotNull);
       expect(outputElement.eventType.toString(), equals('MouseEvent'));
     }
     {
-      OutputElement outputElement = outputElements['change'];
+      final outputElement = outputElements['change'];
       expect(outputElement, isNotNull);
       expect(outputElement.getter, isNotNull);
       expect(outputElement.eventType, isNotNull);
     }
     {
       // used to happen from "id" which got truncated by 'on'.length
-      OutputElement outputElement = outputElements[''];
+      final outputElement = outputElements[''];
       expect(outputElement, isNull);
     }
     {
       // used to happen from "hidden" which got truncated by 'on'.length
-      OutputElement outputElement = outputElements['dden'];
+      final outputElement = outputElements['dden'];
       expect(outputElement, isNull);
     }
   }
 
-  test_buildStandardHtmlAttributes() async {
-    StandardHtml stdhtml = await angularDriver.getStandardHtml();
-    Map<String, InputElement> inputElements = stdhtml.attributes;
+  // ignore: non_constant_identifier_names
+  Future test_buildStandardHtmlAttributes() async {
+    final stdhtml = await angularDriver.getStandardHtml();
+    final inputElements = stdhtml.attributes;
     {
-      InputElement input = inputElements['tabIndex'];
+      final input = inputElements['tabIndex'];
       expect(input, isNotNull);
       expect(input.setter, isNotNull);
       expect(input.setterType.toString(), equals("int"));
     }
     {
-      InputElement input = inputElements['hidden'];
+      final input = inputElements['hidden'];
       expect(input, isNotNull);
       expect(input.setter, isNotNull);
       expect(input.setterType.toString(), equals("bool"));
@@ -243,7 +242,7 @@
   List<AbstractDirective> directives;
   List<AnalysisError> errors;
 
-  Future getDirectives(Source source) async {
+  Future getDirectives(final source) async {
     final dartResult = await dartDriver.getResult(source.fullName);
     fillErrorListener(dartResult.errors);
     final result = await angularDriver.getDirectives(source.fullName);
@@ -252,11 +251,12 @@
     fillErrorListener(errors);
   }
 
+  // ignore: non_constant_identifier_names
   Future test_Component() async {
-    var source = newSource(
+    final source = newSource(
         '/test.dart',
         r'''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'comp-a', template:'')
 class ComponentA {
@@ -269,42 +269,43 @@
     await getDirectives(source);
     expect(directives, hasLength(2));
     {
-      Component component = directives[0];
-      expect(component, new isInstanceOf<Component>());
+      final component = directives[0];
+      expect(component, const isInstanceOf<Component>());
       {
-        Selector selector = component.selector;
-        expect(selector, new isInstanceOf<ElementNameSelector>());
+        final selector = component.selector;
+        expect(selector, const isInstanceOf<ElementNameSelector>());
         expect(selector.toString(), 'comp-a');
       }
       {
         expect(component.elementTags, hasLength(1));
-        Selector selector = component.elementTags[0];
-        expect(selector, new isInstanceOf<ElementNameSelector>());
+        final selector = component.elementTags[0];
+        expect(selector, const isInstanceOf<ElementNameSelector>());
         expect(selector.toString(), 'comp-a');
       }
     }
     {
-      Component component = directives[1];
-      expect(component, new isInstanceOf<Component>());
+      final component = directives[1];
+      expect(component, const isInstanceOf<Component>());
       {
-        Selector selector = component.selector;
-        expect(selector, new isInstanceOf<ElementNameSelector>());
+        final selector = component.selector;
+        expect(selector, const isInstanceOf<ElementNameSelector>());
         expect(selector.toString(), 'comp-b');
       }
       {
         expect(component.elementTags, hasLength(1));
-        Selector selector = component.elementTags[0];
-        expect(selector, new isInstanceOf<ElementNameSelector>());
+        final selector = component.elementTags[0];
+        expect(selector, const isInstanceOf<ElementNameSelector>());
         expect(selector.toString(), 'comp-b');
       }
     }
   }
 
+  // ignore: non_constant_identifier_names
   Future test_Directive() async {
-    var source = newSource(
+    final source = newSource(
         '/test.dart',
         r'''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 
 @Directive(selector: 'dir-a')
 class DirectiveA {
@@ -317,42 +318,43 @@
     await getDirectives(source);
     expect(directives, hasLength(2));
     {
-      AbstractDirective directive = directives[0];
-      expect(directive, new isInstanceOf<Directive>());
+      final directive = directives[0];
+      expect(directive, const isInstanceOf<Directive>());
       {
-        Selector selector = directive.selector;
-        expect(selector, new isInstanceOf<ElementNameSelector>());
+        final selector = directive.selector;
+        expect(selector, const isInstanceOf<ElementNameSelector>());
         expect(selector.toString(), 'dir-a');
       }
       {
         expect(directive.elementTags, hasLength(1));
-        Selector selector = directive.elementTags[0];
-        expect(selector, new isInstanceOf<ElementNameSelector>());
+        final selector = directive.elementTags[0];
+        expect(selector, const isInstanceOf<ElementNameSelector>());
         expect(selector.toString(), 'dir-a');
       }
     }
     {
-      AbstractDirective directive = directives[1];
-      expect(directive, new isInstanceOf<Directive>());
+      final directive = directives[1];
+      expect(directive, const isInstanceOf<Directive>());
       {
-        Selector selector = directive.selector;
-        expect(selector, new isInstanceOf<ElementNameSelector>());
+        final selector = directive.selector;
+        expect(selector, const isInstanceOf<ElementNameSelector>());
         expect(selector.toString(), 'dir-b');
       }
       {
         expect(directive.elementTags, hasLength(1));
-        Selector selector = directive.elementTags[0];
-        expect(selector, new isInstanceOf<ElementNameSelector>());
+        final selector = directive.elementTags[0];
+        expect(selector, const isInstanceOf<ElementNameSelector>());
         expect(selector.toString(), 'dir-b');
       }
     }
   }
 
+  // ignore: non_constant_identifier_names
   Future test_Directive_elementTags_OrSelector() async {
-    var source = newSource(
+    final source = newSource(
         '/test.dart',
         r'''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 
 @Directive(selector: 'dir-a1, dir-a2, dir-a3')
 class DirectiveA {
@@ -365,51 +367,52 @@
     await getDirectives(source);
     expect(directives, hasLength(2));
     {
-      Directive directive = directives[0];
-      expect(directive, new isInstanceOf<Directive>());
+      final directive = directives[0];
+      expect(directive, const isInstanceOf<Directive>());
       {
-        Selector selector = directive.selector;
-        expect(selector, new isInstanceOf<OrSelector>());
+        final selector = directive.selector;
+        expect(selector, const isInstanceOf<OrSelector>());
         expect((selector as OrSelector).selectors, hasLength(3));
       }
       {
         expect(directive.elementTags, hasLength(3));
-        expect(
-            directive.elementTags[0], new isInstanceOf<ElementNameSelector>());
+        expect(directive.elementTags[0],
+            const isInstanceOf<ElementNameSelector>());
         expect(directive.elementTags[0].toString(), 'dir-a1');
-        expect(
-            directive.elementTags[1], new isInstanceOf<ElementNameSelector>());
+        expect(directive.elementTags[1],
+            const isInstanceOf<ElementNameSelector>());
         expect(directive.elementTags[1].toString(), 'dir-a2');
-        expect(
-            directive.elementTags[2], new isInstanceOf<ElementNameSelector>());
+        expect(directive.elementTags[2],
+            const isInstanceOf<ElementNameSelector>());
         expect(directive.elementTags[2].toString(), 'dir-a3');
       }
     }
     {
-      Directive directive = directives[1];
-      expect(directive, new isInstanceOf<Directive>());
+      final directive = directives[1];
+      expect(directive, const isInstanceOf<Directive>());
       {
-        Selector selector = directive.selector;
-        expect(selector, new isInstanceOf<OrSelector>());
+        final selector = directive.selector;
+        expect(selector, const isInstanceOf<OrSelector>());
         expect((selector as OrSelector).selectors, hasLength(2));
       }
       {
         expect(directive.elementTags, hasLength(2));
-        expect(
-            directive.elementTags[0], new isInstanceOf<ElementNameSelector>());
+        expect(directive.elementTags[0],
+            const isInstanceOf<ElementNameSelector>());
         expect(directive.elementTags[0].toString(), 'dir-b1');
-        expect(
-            directive.elementTags[1], new isInstanceOf<ElementNameSelector>());
+        expect(directive.elementTags[1],
+            const isInstanceOf<ElementNameSelector>());
         expect(directive.elementTags[1].toString(), 'dir-b2');
       }
     }
   }
 
+  // ignore: non_constant_identifier_names
   Future test_Directive_elementTags_AndSelector() async {
-    var source = newSource(
+    final source = newSource(
         '/test.dart',
         r'''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 
 @Directive(selector: 'dir-a.myClass[myAttr]')
 class DirectiveA {
@@ -422,42 +425,43 @@
     await getDirectives(source);
     expect(directives, hasLength(2));
     {
-      Directive directive = directives[0];
-      expect(directive, new isInstanceOf<Directive>());
+      final directive = directives[0];
+      expect(directive, const isInstanceOf<Directive>());
       {
-        Selector selector = directive.selector;
-        expect(selector, new isInstanceOf<AndSelector>());
+        final selector = directive.selector;
+        expect(selector, const isInstanceOf<AndSelector>());
         expect((selector as AndSelector).selectors, hasLength(3));
       }
       {
         expect(directive.elementTags, hasLength(1));
-        expect(
-            directive.elementTags[0], new isInstanceOf<ElementNameSelector>());
+        expect(directive.elementTags[0],
+            const isInstanceOf<ElementNameSelector>());
         expect(directive.elementTags[0].toString(), 'dir-a');
       }
     }
     {
-      Directive directive = directives[1];
-      expect(directive, new isInstanceOf<Directive>());
+      final directive = directives[1];
+      expect(directive, const isInstanceOf<Directive>());
       {
-        Selector selector = directive.selector;
-        expect(selector, new isInstanceOf<AndSelector>());
+        final selector = directive.selector;
+        expect(selector, const isInstanceOf<AndSelector>());
         expect((selector as AndSelector).selectors, hasLength(2));
       }
       {
         expect(directive.elementTags, hasLength(1));
-        expect(
-            directive.elementTags[0], new isInstanceOf<ElementNameSelector>());
+        expect(directive.elementTags[0],
+            const isInstanceOf<ElementNameSelector>());
         expect(directive.elementTags[0].toString(), 'dir-b');
       }
     }
   }
 
+  // ignore: non_constant_identifier_names
   Future test_Directive_elementTags_CompoundSelector() async {
-    var source = newSource(
+    final source = newSource(
         '/test.dart',
         r'''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 
 @Directive(selector: 'dir-a1.myClass[myAttr], dir-a2.otherClass')
 class DirectiveA {
@@ -470,46 +474,47 @@
     await getDirectives(source);
     expect(directives, hasLength(2));
     {
-      Directive directive = directives[0];
-      expect(directive, new isInstanceOf<Directive>());
+      final directive = directives[0];
+      expect(directive, const isInstanceOf<Directive>());
       {
-        Selector selector = directive.selector;
-        expect(selector, new isInstanceOf<OrSelector>());
+        final selector = directive.selector;
+        expect(selector, const isInstanceOf<OrSelector>());
         expect((selector as OrSelector).selectors, hasLength(2));
       }
       {
         expect(directive.elementTags, hasLength(2));
-        expect(
-            directive.elementTags[0], new isInstanceOf<ElementNameSelector>());
+        expect(directive.elementTags[0],
+            const isInstanceOf<ElementNameSelector>());
         expect(directive.elementTags[0].toString(), 'dir-a1');
-        expect(
-            directive.elementTags[1], new isInstanceOf<ElementNameSelector>());
+        expect(directive.elementTags[1],
+            const isInstanceOf<ElementNameSelector>());
         expect(directive.elementTags[1].toString(), 'dir-a2');
       }
     }
     {
-      Directive directive = directives[1];
-      expect(directive, new isInstanceOf<Directive>());
+      final directive = directives[1];
+      expect(directive, const isInstanceOf<Directive>());
       {
-        Selector selector = directive.selector;
-        expect(selector, new isInstanceOf<OrSelector>());
+        final selector = directive.selector;
+        expect(selector, const isInstanceOf<OrSelector>());
         expect((selector as OrSelector).selectors, hasLength(2));
       }
       {
         expect(directive.elementTags, hasLength(2));
-        expect(
-            directive.elementTags[0], new isInstanceOf<ElementNameSelector>());
+        expect(directive.elementTags[0],
+            const isInstanceOf<ElementNameSelector>());
         expect(directive.elementTags[0].toString(), 'dir-b1');
-        expect(
-            directive.elementTags[1], new isInstanceOf<ElementNameSelector>());
+        expect(directive.elementTags[1],
+            const isInstanceOf<ElementNameSelector>());
         expect(directive.elementTags[1].toString(), 'dir-b2');
       }
     }
   }
 
+  // ignore: non_constant_identifier_names
   Future test_exportAs_Component() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'aaa', exportAs: 'export-name', template:'')
 class ComponentA {
@@ -519,21 +524,21 @@
 class ComponentB {
 }
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getDirectives(source);
     expect(directives, hasLength(2));
     {
-      Component component = getComponentByClassName(directives, 'ComponentA');
+      final component = getComponentByClassName(directives, 'ComponentA');
       {
-        AngularElement exportAs = component.exportAs;
+        final exportAs = component.exportAs;
         expect(exportAs.name, 'export-name');
         expect(exportAs.nameOffset, code.indexOf('export-name'));
       }
     }
     {
-      Component component = getComponentByClassName(directives, 'ComponentB');
+      final component = getComponentByClassName(directives, 'ComponentB');
       {
-        AngularElement exportAs = component.exportAs;
+        final exportAs = component.exportAs;
         expect(exportAs, isNull);
       }
     }
@@ -541,9 +546,10 @@
     errorListener.assertNoErrors();
   }
 
+  // ignore: non_constant_identifier_names
   Future test_exportAs_Directive() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 
 @Directive(selector: '[aaa]', exportAs: 'export-name')
 class DirectiveA {
@@ -553,21 +559,21 @@
 class DirectiveB {
 }
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getDirectives(source);
     expect(directives, hasLength(2));
     {
-      Directive directive = getDirectiveByClassName(directives, 'DirectiveA');
+      final directive = getDirectiveByClassName(directives, 'DirectiveA');
       {
-        AngularElement exportAs = directive.exportAs;
+        final exportAs = directive.exportAs;
         expect(exportAs.name, 'export-name');
         expect(exportAs.nameOffset, code.indexOf('export-name'));
       }
     }
     {
-      Directive directive = getDirectiveByClassName(directives, 'DirectiveB');
+      final directive = getDirectiveByClassName(directives, 'DirectiveB');
       {
-        AngularElement exportAs = directive.exportAs;
+        final exportAs = directive.exportAs;
         expect(exportAs, isNull);
       }
     }
@@ -575,11 +581,12 @@
     errorListener.assertNoErrors();
   }
 
+  // ignore: non_constant_identifier_names
   Future test_exportAs_hasError_notStringValue() async {
-    var source = newSource(
+    final source = newSource(
         '/test.dart',
         r'''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'aaa', exportAs: 42, template:'')
 class ComponentA {
@@ -594,11 +601,12 @@
     ]);
   }
 
+  // ignore: non_constant_identifier_names
   Future test_exportAs_constantStringExpressionOk() async {
-    var source = newSource(
+    final source = newSource(
         '/test.dart',
         r'''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'aaa', exportAs: 'a' + 'b', template:'')
 class ComponentA {
@@ -610,11 +618,12 @@
     errorListener.assertNoErrors();
   }
 
+  // ignore: non_constant_identifier_names
   Future test_hasError_ArgumentSelectorMissing() async {
-    var source = newSource(
+    final source = newSource(
         '/test.dart',
         r'''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 
 @Component(template:'')
 class ComponentA {
@@ -626,25 +635,27 @@
         <ErrorCode>[AngularWarningCode.ARGUMENT_SELECTOR_MISSING]);
   }
 
+  // ignore: non_constant_identifier_names
   Future test_hasError_CannotParseSelector() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 @Component(selector: 'a+bad selector', template: '')
 class ComponentA {
 }
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getDirectives(source);
     // validate
     assertErrorInCodeAtPosition(
         AngularWarningCode.CANNOT_PARSE_SELECTOR, code, "+");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_hasError_selector_notStringValue() async {
-    var source = newSource(
+    final source = newSource(
         '/test.dart',
         r'''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 55, template: '')
 class ComponentA {
@@ -658,11 +669,12 @@
     ]);
   }
 
+  // ignore: non_constant_identifier_names
   Future test_selector_constantExpressionOk() async {
-    var source = newSource(
+    final source = newSource(
         '/test.dart',
         r'''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'a' + '[b]', template: '')
 class ComponentA {
@@ -673,19 +685,20 @@
     errorListener.assertNoErrors();
   }
 
+  // ignore: non_constant_identifier_names
   Future test_hasError_UndefinedSetter_fullSyntax() async {
-    var source = newSource(
+    final source = newSource(
         '/test.dart',
         r'''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'my-component', inputs: const ['noSetter: no-setter'], template: '')
 class ComponentA {
 }
 ''');
     await getDirectives(source);
-    Component component = directives.single;
-    List<InputElement> inputs = component.inputs;
+    final component = directives.single;
+    final inputs = component.inputs;
     // the bad input should NOT show up, it is not usable see github #183
     expect(inputs, hasLength(0));
     // validate
@@ -693,11 +706,12 @@
         <ErrorCode>[StaticTypeWarningCode.UNDEFINED_SETTER]);
   }
 
+  // ignore: non_constant_identifier_names
   Future test_hasError_UndefinedSetter_shortSyntax() async {
-    var source = newSource(
+    final source = newSource(
         '/test.dart',
         r'''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'my-component', inputs: const ['noSetter'], template: '')
 class ComponentA {
@@ -709,19 +723,20 @@
         <ErrorCode>[StaticTypeWarningCode.UNDEFINED_SETTER]);
   }
 
+  // ignore: non_constant_identifier_names
   Future test_hasError_UndefinedSetter_shortSyntax_noInputMade() async {
-    var source = newSource(
+    final source = newSource(
         '/test.dart',
         r'''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'my-component', inputs: const ['noSetter'], template: '')
 class ComponentA {
 }
 ''');
     await getDirectives(source);
-    Component component = directives.single;
-    List<InputElement> inputs = component.inputs;
+    final component = directives.single;
+    final inputs = component.inputs;
     // the bad input should NOT show up, it is not usable see github #183
     expect(inputs, hasLength(0));
     // validate
@@ -729,9 +744,10 @@
         <ErrorCode>[StaticTypeWarningCode.UNDEFINED_SETTER]);
   }
 
+  // ignore: non_constant_identifier_names
   Future test_inputs() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 
 @Component(
     selector: 'my-component',
@@ -748,13 +764,13 @@
   set someSetter(String x) { }
 }
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getDirectives(source);
-    Component component = directives.single;
-    List<InputElement> inputs = component.inputs;
+    final component = directives.single;
+    final inputs = component.inputs;
     expect(inputs, hasLength(5));
     {
-      InputElement input = inputs[0];
+      final input = inputs[0];
       expect(input.name, 'leadingText');
       expect(input.nameOffset, code.indexOf("leadingText',"));
       expect(input.setterRange.offset, input.nameOffset);
@@ -765,7 +781,7 @@
       expect(input.setterType.toString(), equals("String"));
     }
     {
-      InputElement input = inputs[1];
+      final input = inputs[1];
       expect(input.name, 'tailText');
       expect(input.nameOffset, code.indexOf("tailText']"));
       expect(input.setterRange.offset, code.indexOf("trailingText: "));
@@ -776,7 +792,7 @@
       expect(input.setterType.toString(), equals("int"));
     }
     {
-      InputElement input = inputs[2];
+      final input = inputs[2];
       expect(input.name, 'firstField');
       expect(input.nameOffset, code.indexOf('firstField'));
       expect(input.nameLength, 'firstField'.length);
@@ -788,7 +804,7 @@
       expect(input.setterType.toString(), equals("bool"));
     }
     {
-      InputElement input = inputs[3];
+      final input = inputs[3];
       expect(input.name, 'secondInput');
       expect(input.nameOffset, code.indexOf('secondInput'));
       expect(input.setterRange.offset, code.indexOf('secondField'));
@@ -799,7 +815,7 @@
       expect(input.setterType.toString(), equals("String"));
     }
     {
-      InputElement input = inputs[4];
+      final input = inputs[4];
       expect(input.name, 'someSetter');
       expect(input.nameOffset, code.indexOf('someSetter'));
       expect(input.setterRange.offset, input.nameOffset);
@@ -814,9 +830,10 @@
     errorListener.assertNoErrors();
   }
 
+  // ignore: non_constant_identifier_names
   Future test_inputs_deprecatedProperties() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 
 @Component(
     selector: 'my-component',
@@ -827,13 +844,13 @@
   String trailingText;
 }
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getDirectives(source);
-    Component component = directives.single;
-    List<InputElement> inputs = component.inputs;
+    final component = directives.single;
+    final inputs = component.inputs;
     expect(inputs, hasLength(2));
     {
-      InputElement input = inputs[0];
+      final input = inputs[0];
       expect(input.name, 'leadingText');
       expect(input.nameOffset, code.indexOf("leadingText',"));
       expect(input.setterRange.offset, input.nameOffset);
@@ -843,7 +860,7 @@
       expect(input.setter.displayName, 'leadingText');
     }
     {
-      InputElement input = inputs[1];
+      final input = inputs[1];
       expect(input.name, 'tailText');
       expect(input.nameOffset, code.indexOf("tailText']"));
       expect(input.setterRange.offset, code.indexOf("trailingText: "));
@@ -854,9 +871,10 @@
     }
   }
 
+  // ignore: non_constant_identifier_names
   Future test_outputs() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 
 @Component(
     selector: 'my-component',
@@ -873,13 +891,13 @@
   EventEmitter get someGetter => null;
 }
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getDirectives(source);
-    Component component = directives.single;
-    List<OutputElement> compOutputs = component.outputs;
+    final component = directives.single;
+    final compOutputs = component.outputs;
     expect(compOutputs, hasLength(5));
     {
-      OutputElement output = compOutputs[0];
+      final output = compOutputs[0];
       expect(output.name, 'outputOne');
       expect(output.nameOffset, code.indexOf("outputOne"));
       expect(output.getterRange.offset, output.nameOffset);
@@ -891,7 +909,7 @@
       expect(output.eventType.toString(), equals("MyComponent"));
     }
     {
-      OutputElement output = compOutputs[1];
+      final output = compOutputs[1];
       expect(output.name, 'outputTwo');
       expect(output.nameOffset, code.indexOf("outputTwo']"));
       expect(output.getterRange.offset, code.indexOf("secondOutput: "));
@@ -903,7 +921,7 @@
       expect(output.eventType.toString(), equals("String"));
     }
     {
-      OutputElement output = compOutputs[2];
+      final output = compOutputs[2];
       expect(output.name, 'outputThree');
       expect(output.nameOffset, code.indexOf('outputThree'));
       expect(output.nameLength, 'outputThree'.length);
@@ -916,7 +934,7 @@
       expect(output.eventType.toString(), equals("int"));
     }
     {
-      OutputElement output = compOutputs[3];
+      final output = compOutputs[3];
       expect(output.name, 'outputFour');
       expect(output.nameOffset, code.indexOf('outputFour'));
       expect(output.getterRange.offset, code.indexOf('fourthOutput'));
@@ -928,7 +946,7 @@
       expect(output.eventType.isDynamic, isTrue);
     }
     {
-      OutputElement output = compOutputs[4];
+      final output = compOutputs[4];
       expect(output.name, 'someGetter');
       expect(output.nameOffset, code.indexOf('someGetter'));
       expect(output.getterRange.offset, output.nameOffset);
@@ -944,9 +962,10 @@
     errorListener.assertNoErrors();
   }
 
+  // ignore: non_constant_identifier_names
   Future test_outputs_streamIsOk() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 import 'dart:async';
 
 @Component(
@@ -957,21 +976,22 @@
   Stream<int> myOutput;
 }
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getDirectives(source);
-    Component component = directives.single;
-    List<OutputElement> compOutputs = component.outputs;
+    final component = directives.single;
+    final compOutputs = component.outputs;
     expect(compOutputs, hasLength(1));
     {
-      OutputElement output = compOutputs[0];
+      final output = compOutputs[0];
       expect(output.eventType, isNotNull);
       expect(output.eventType.toString(), equals("int"));
     }
   }
 
+  // ignore: non_constant_identifier_names
   Future test_outputs_extendStreamIsOk() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 import 'dart:async';
 
 abstract class MyStream<T> implements Stream<T> { }
@@ -984,21 +1004,21 @@
   MyStream<int> myOutput;
 }
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getDirectives(source);
-    Component component = directives.single;
-    List<OutputElement> compOutputs = component.outputs;
+    final component = directives.single;
+    final compOutputs = component.outputs;
     expect(compOutputs, hasLength(1));
     {
-      OutputElement output = compOutputs[0];
+      final output = compOutputs[0];
       expect(output.eventType, isNotNull);
-      expect(output.eventType.toString(), equals("int"));
     }
   }
 
+  // ignore: non_constant_identifier_names
   Future test_outputs_extendStreamSpecializedIsOk() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 import 'dart:async';
 
 class MyStream extends Stream<int> { }
@@ -1011,21 +1031,22 @@
   MyStream myOutput;
 }
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getDirectives(source);
-    Component component = directives.single;
-    List<OutputElement> compOutputs = component.outputs;
+    final component = directives.single;
+    final compOutputs = component.outputs;
     expect(compOutputs, hasLength(1));
     {
-      OutputElement output = compOutputs[0];
+      final output = compOutputs[0];
       expect(output.eventType, isNotNull);
       expect(output.eventType.toString(), equals("int"));
     }
   }
 
+  // ignore: non_constant_identifier_names
   Future test_outputs_extendStreamUntypedIsOk() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 import 'dart:async';
 
 class MyStream extends Stream { }
@@ -1038,21 +1059,22 @@
   MyStream myOutput;
 }
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getDirectives(source);
-    Component component = directives.single;
-    List<OutputElement> compOutputs = component.outputs;
+    final component = directives.single;
+    final compOutputs = component.outputs;
     expect(compOutputs, hasLength(1));
     {
-      OutputElement output = compOutputs[0];
+      final output = compOutputs[0];
       expect(output.eventType, isNotNull);
       expect(output.eventType.toString(), equals("dynamic"));
     }
   }
 
+  // ignore: non_constant_identifier_names
   Future test_outputs_notEventEmitterTypeError() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 
 @Component(
     selector: 'my-component',
@@ -1062,15 +1084,16 @@
   int badOutput;
 }
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getDirectives(source);
     assertErrorInCodeAtPosition(
-        AngularWarningCode.OUTPUT_MUST_BE_EVENTEMITTER, code, "badOutput");
+        AngularWarningCode.OUTPUT_MUST_BE_STREAM, code, "badOutput");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_outputs_extendStreamNotStreamHasDynamicEventType() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 
 @Component(
     selector: 'my-component',
@@ -1080,22 +1103,23 @@
   int badOutput;
 }
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getDirectives(source);
     // validate
-    Component component = directives.single;
-    List<OutputElement> compOutputs = component.outputs;
+    final component = directives.single;
+    final compOutputs = component.outputs;
     expect(compOutputs, hasLength(1));
     {
-      OutputElement output = compOutputs[0];
+      final output = compOutputs[0];
       expect(output.eventType, isNotNull);
       expect(output.eventType.toString(), equals("dynamic"));
     }
   }
 
+  // ignore: non_constant_identifier_names
   Future test_parameterizedInputsOutputs() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 
 @Component(
     selector: 'my-component',
@@ -1112,59 +1136,59 @@
 }
 
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getDirectives(source);
     // validate
-    Component component = directives.single;
-    List<InputElement> compInputs = component.inputs;
+    final component = directives.single;
+    final compInputs = component.inputs;
     expect(compInputs, hasLength(4));
     {
-      InputElement input = compInputs[0];
+      final input = compInputs[0];
       expect(input.name, 'dynamicInput');
       expect(input.setterType, isNotNull);
       expect(input.setterType.toString(), equals("dynamic"));
     }
     {
-      InputElement input = compInputs[1];
+      final input = compInputs[1];
       expect(input.name, 'stringInput');
       expect(input.setterType, isNotNull);
       expect(input.setterType.toString(), equals("String"));
     }
     {
-      InputElement input = compInputs[2];
+      final input = compInputs[2];
       expect(input.name, 'stringInput2');
       expect(input.setterType, isNotNull);
       expect(input.setterType.toString(), equals("String"));
     }
     {
-      InputElement input = compInputs[3];
+      final input = compInputs[3];
       expect(input.name, 'listInput');
       expect(input.setterType, isNotNull);
       expect(input.setterType.toString(), equals("List<String>"));
     }
 
-    List<OutputElement> compOutputs = component.outputs;
+    final compOutputs = component.outputs;
     expect(compOutputs, hasLength(4));
     {
-      OutputElement output = compOutputs[0];
+      final output = compOutputs[0];
       expect(output.name, 'dynamicOutput');
       expect(output.eventType, isNotNull);
       expect(output.eventType.toString(), equals("dynamic"));
     }
     {
-      OutputElement output = compOutputs[1];
+      final output = compOutputs[1];
       expect(output.name, 'stringOutput');
       expect(output.eventType, isNotNull);
       expect(output.eventType.toString(), equals("String"));
     }
     {
-      OutputElement output = compOutputs[2];
+      final output = compOutputs[2];
       expect(output.name, 'stringOutput2');
       expect(output.eventType, isNotNull);
       expect(output.eventType.toString(), equals("String"));
     }
     {
-      OutputElement output = compOutputs[3];
+      final output = compOutputs[3];
       expect(output.name, 'listOutput');
       expect(output.eventType, isNotNull);
       expect(output.eventType.toString(), equals("List<String>"));
@@ -1174,9 +1198,10 @@
     errorListener.assertNoErrors();
   }
 
+  // ignore: non_constant_identifier_names
   Future test_parameterizedInheritedInputsOutputs() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 
 class Generic<T> {
   T input;
@@ -1191,31 +1216,32 @@
 class MyComponent extends Generic {
 }
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getDirectives(source);
-    Component component = directives.single;
-    List<InputElement> compInputs = component.inputs;
+    final component = directives.single;
+    final compInputs = component.inputs;
     expect(compInputs, hasLength(1));
     {
-      InputElement input = compInputs[0];
+      final input = compInputs[0];
       expect(input.name, 'input');
       expect(input.setterType, isNotNull);
       expect(input.setterType.toString(), equals("dynamic"));
     }
 
-    List<OutputElement> compOutputs = component.outputs;
+    final compOutputs = component.outputs;
     expect(compOutputs, hasLength(1));
     {
-      OutputElement output = compOutputs[0];
+      final output = compOutputs[0];
       expect(output.name, 'output');
       expect(output.eventType, isNotNull);
       expect(output.eventType.toString(), equals("dynamic"));
     }
   }
 
+  // ignore: non_constant_identifier_names
   Future test_parameterizedInheritedInputsOutputsSpecified() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 
 class Generic<T> {
   T input;
@@ -1230,38 +1256,39 @@
 class MyComponent extends Generic<String> {
 }
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getDirectives(source);
-    Component component = directives.single;
-    List<InputElement> compInputs = component.inputs;
+    final component = directives.single;
+    final compInputs = component.inputs;
     expect(compInputs, hasLength(1));
     {
-      InputElement input = compInputs[0];
+      final input = compInputs[0];
       expect(input.name, 'input');
       expect(input.setterType, isNotNull);
       expect(input.setterType.toString(), equals("String"));
     }
 
-    List<OutputElement> compOutputs = component.outputs;
+    final compOutputs = component.outputs;
     expect(compOutputs, hasLength(1));
     {
-      OutputElement output = compOutputs[0];
+      final output = compOutputs[0];
       expect(output.name, 'output');
       expect(output.eventType, isNotNull);
       expect(output.eventType.toString(), equals("String"));
     }
   }
 
+  // ignore: non_constant_identifier_names
   Future test_finalPropertyInputError() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'my-component', template: '<p></p>')
 class MyComponent {
   @Input() final int immutable = 1;
 }
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getDirectives(source);
     // validate
     assertErrorInCodeAtPosition(
@@ -1270,24 +1297,26 @@
         "@Input()");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_finalPropertyInputStringError() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'my-component', template: '<p></p>', inputs: const ['immutable'])
 class MyComponent {
   final int immutable = 1;
 }
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getDirectives(source);
     // validate. Can't easily assert position though because its all 'immutable'
     errorListener
         .assertErrorsWithCodes([StaticTypeWarningCode.UNDEFINED_SETTER]);
   }
 
+  // ignore: non_constant_identifier_names
   Future test_noDirectives() async {
-    var source = newSource(
+    final source = newSource(
         '/test.dart',
         r'''
 class A {}
@@ -1297,9 +1326,10 @@
     expect(directives, isEmpty);
   }
 
+  // ignore: non_constant_identifier_names
   Future test_inputOnGetterIsError() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'my-component', template: '')
 class MyComponent {
@@ -1307,7 +1337,7 @@
   String get someGetter => null;
 }
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getDirectives(source);
     assertErrorInCodeAtPosition(
         AngularWarningCode.INPUT_ANNOTATION_PLACEMENT_INVALID,
@@ -1315,9 +1345,10 @@
         "@Input()");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_outputOnSetterIsError() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'my-component', template: '')
 class MyComponent {
@@ -1325,13 +1356,149 @@
   set someSetter(x) { }
 }
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getDirectives(source);
     assertErrorInCodeAtPosition(
         AngularWarningCode.OUTPUT_ANNOTATION_PLACEMENT_INVALID,
         code,
         "@Output()");
   }
+
+  // ignore: non_constant_identifier_names
+  Future test_hasContentChildDirective() async {
+    final code = r'''
+import 'package:angular2/angular2.dart';
+
+@Component(selector: 'my-component', template: '')
+class ComponentA {
+  @ContentChild(ContentChildComp)
+  ContentChildComp contentChild;
+}
+
+@Component(selector: 'foo', template: '')
+class ContentChildComp {}
+''';
+    final source = newSource('/test.dart', code);
+    await getDirectives(source);
+    final component = directives.first;
+    final childFields = component.contentChildFields;
+    expect(childFields, hasLength(1));
+    final child = childFields.first;
+    expect(child.fieldName, equals("contentChild"));
+    expect(child.nameRange.offset, equals(code.indexOf("ContentChildComp)")));
+    expect(child.nameRange.length, equals("ContentChildComp".length));
+    expect(child.typeRange.offset, equals(code.indexOf("ContentChildComp ")));
+    expect(child.typeRange.length, equals("ContentChildComp".length));
+    // validate
+    errorListener.assertNoErrors();
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_hasContentChildrenDirective() async {
+    final code = r'''
+import 'package:angular2/angular2.dart';
+
+@Component(selector: 'my-component', template: '')
+class ComponentA {
+  @ContentChildren(ContentChildComp)
+  QueryList<ContentChildComp> contentChildren;
+}
+
+@Component(selector: 'foo', template: '')
+class ContentChildComp {}
+''';
+    final source = newSource('/test.dart', code);
+    await getDirectives(source);
+    final component = directives.first;
+    final childrenFields = component.contentChildrenFields;
+    expect(childrenFields, hasLength(1));
+    final children = childrenFields.first;
+    expect(children.fieldName, equals("contentChildren"));
+    expect(
+        children.nameRange.offset, equals(code.indexOf("ContentChildComp)")));
+    expect(children.nameRange.length, equals("ContentChildComp".length));
+    expect(children.typeRange.offset,
+        equals(code.indexOf("QueryList<ContentChildComp>")));
+    expect(children.typeRange.length,
+        equals("QueryList<ContentChildComp>".length));
+    // validate
+    errorListener.assertNoErrors();
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_hasContentChildChildrenNoRangeNotRecorded() async {
+    final code = r'''
+import 'package:angular2/angular2.dart';
+
+@Component(selector: 'my-component', template: '')
+class ComponentA {
+  @ContentChildren()
+  QueryList<ContentChildComp> contentChildren;
+  @ContentChild()
+  ContentChildComp contentChild;
+}
+
+@Component(selector: 'foo', template: '')
+class ContentChildComp {}
+''';
+    final source = newSource('/test.dart', code);
+    await getDirectives(source);
+    final component = directives.first;
+    final childrenFields = component.contentChildrenFields;
+    expect(childrenFields, hasLength(0));
+    final childFields = component.contentChildFields;
+    expect(childFields, hasLength(0));
+    // validate
+    errorListener.assertErrorsWithCodes([
+      CompileTimeErrorCode.NOT_ENOUGH_REQUIRED_ARGUMENTS,
+      CompileTimeErrorCode.NOT_ENOUGH_REQUIRED_ARGUMENTS
+    ]);
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_hasContentChildChildrenSetter() async {
+    final code = r'''
+import 'package:angular2/angular2.dart';
+
+@Component(selector: 'my-component', template: '')
+class ComponentA {
+  @ContentChild(ContentChildComp) // 1
+  void set contentChild(ContentChildComp contentChild) => null;
+  @ContentChildren(ContentChildComp) // 2
+  void set contentChildren(QueryList<ContentChildComp> contentChildren) => null;
+}
+
+@Component(selector: 'foo', template: '')
+class ContentChildComp {}
+''';
+    final source = newSource('/test.dart', code);
+    await getDirectives(source);
+    final component = directives.first;
+
+    final childFields = component.contentChildFields;
+    expect(childFields, hasLength(1));
+    final child = childFields.first;
+    expect(child.fieldName, equals("contentChild"));
+    expect(
+        child.nameRange.offset, equals(code.indexOf("ContentChildComp) // 1")));
+    expect(child.nameRange.length, equals("ContentChildComp".length));
+    expect(child.typeRange.offset, equals(code.indexOf("ContentChildComp ")));
+    expect(child.typeRange.length, equals("ContentChildComp".length));
+
+    final childrenFields = component.contentChildrenFields;
+    expect(childrenFields, hasLength(1));
+    final children = childrenFields.first;
+    expect(children.fieldName, equals("contentChildren"));
+    expect(children.nameRange.offset,
+        equals(code.indexOf("ContentChildComp) // 2")));
+    expect(children.nameRange.length, equals("ContentChildComp".length));
+    expect(children.typeRange.offset,
+        equals(code.indexOf("QueryList<ContentChildComp>")));
+    expect(children.typeRange.length,
+        equals("QueryList<ContentChildComp>".length));
+
+    errorListener.assertNoErrors();
+  }
 }
 
 @reflectiveTest
@@ -1340,14 +1507,16 @@
   List<View> views;
   List<AnalysisError> errors;
 
-  Future getViews(Source source) async {
+  Future getViews(final source) async {
     final dartResult = await dartDriver.getResult(source.fullName);
     fillErrorListener(dartResult.errors);
     final result = await angularDriver.getDirectives(source.fullName);
     directives = result.directives;
 
     final linker = new ChildDirectiveLinker(
-        angularDriver, new ErrorReporter(errorListener, source));
+        angularDriver,
+        await angularDriver.getStandardAngular(),
+        new ErrorReporter(errorListener, source));
     await linker.linkDirectives(directives, dartResult.unit.element.library);
     views = directives
         .map((d) => d is Component ? d.view : null)
@@ -1357,32 +1526,33 @@
     fillErrorListener(errors);
   }
 
+  // ignore: non_constant_identifier_names
   Future test_buildViewsDoesntGetDependentDirectives() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 import 'other_file.dart';
 
 @Component(selector: 'my-component', template: 'My template',
     directives: const [OtherComponent])
 class MyComponent {}
 ''';
-    String otherCode = r'''
-import '/angular2/angular2.dart';
+    final otherCode = r'''
+import 'package:angular2/angular2.dart';
 @Component(selector: 'other-component', template: 'My template',
     directives: const [NgFor])
 class OtherComponent {}
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     newSource('/other_file.dart', otherCode);
     await getViews(source);
     {
-      View view = getViewByClassName(views, 'MyComponent');
+      final view = getViewByClassName(views, 'MyComponent');
       {
         expect(view.directives, hasLength(1));
       }
 
       // shouldn't be run yet
-      for (AbstractDirective directive in view.directives) {
+      for (final directive in view.directives) {
         if (directive is Component) {
           expect(directive.view.directives, hasLength(0));
         }
@@ -1392,9 +1562,10 @@
     errorListener.assertNoErrors();
   }
 
+  // ignore: non_constant_identifier_names
   Future test_directives() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 
 @Directive(selector: '[aaa]')
 class DirectiveA {}
@@ -1411,13 +1582,13 @@
     directives: const [DIR_AB, DirectiveC])
 class MyComponent {}
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getViews(source);
     {
-      View view = getViewByClassName(views, 'MyComponent');
+      final view = getViewByClassName(views, 'MyComponent');
       {
         expect(view.directives, hasLength(3));
-        List<String> directiveClassNames = view.directives
+        final directiveClassNames = view.directives
             .map((directive) => directive.classElement.name)
             .toList();
         expect(directiveClassNames,
@@ -1428,9 +1599,10 @@
     errorListener.assertNoErrors();
   }
 
+  // ignore: non_constant_identifier_names
   Future test_prefixedDirectives() async {
-    String otherCode = r'''
-import '/angular2/angular2.dart';
+    final otherCode = r'''
+import 'package:angular2/angular2.dart';
 
 @Directive(selector: '[aaa]')
 class DirectiveA {}
@@ -1444,22 +1616,22 @@
 const DIR_AB = const [DirectiveA, DirectiveB];
 ''';
 
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 import 'other.dart' as other;
 
 @Component(selector: 'my-component', template: 'My template',
     directives: const [other.DIR_AB, other.DirectiveC])
 class MyComponent {}
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     newSource('/other.dart', otherCode);
     await getViews(source);
     {
-      View view = getViewByClassName(views, 'MyComponent');
+      final view = getViewByClassName(views, 'MyComponent');
       {
         expect(view.directives, hasLength(3));
-        List<String> directiveClassNames = view.directives
+        final directiveClassNames = view.directives
             .map((directive) => directive.classElement.name)
             .toList();
         expect(directiveClassNames,
@@ -1470,9 +1642,10 @@
     errorListener.assertNoErrors();
   }
 
+  // ignore: non_constant_identifier_names
   Future test_directives_hasError_notListVariable() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 
 const NOT_DIRECTIVE_LIST = 42;
 
@@ -1480,17 +1653,18 @@
    directives: const [NOT_DIRECTIVE_LIST])
 class MyComponent {}
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getViews(source);
     errorListener.assertErrorsWithCodes(
         <ErrorCode>[AngularWarningCode.TYPE_IS_NOT_A_DIRECTIVE]);
   }
 
+  // ignore: non_constant_identifier_names
   Future test_hasError_ComponentAnnotationMissing() async {
-    var source = newSource(
+    final source = newSource(
         '/test.dart',
         r'''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 
 @View(template: 'AAA')
 class ComponentA {
@@ -1501,11 +1675,12 @@
         <ErrorCode>[AngularWarningCode.COMPONENT_ANNOTATION_MISSING]);
   }
 
+  // ignore: non_constant_identifier_names
   Future test_hasError_StringValueExpected() async {
-    var source = newSource(
+    final source = newSource(
         '/test.dart',
         r'''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'aaa', template: 55)
 class ComponentA {
@@ -1518,11 +1693,12 @@
     ]);
   }
 
+  // ignore: non_constant_identifier_names
   Future test_constantExpressionTemplateOk() async {
-    var source = newSource(
+    final source = newSource(
         '/test.dart',
         r'''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'aaa', template: 'abc' + 'bcd')
 class ComponentA {
@@ -1532,11 +1708,12 @@
     errorListener.assertNoErrors();
   }
 
+  // ignore: non_constant_identifier_names
   Future test_constantExpressionTemplateComplexIsOnlyError() async {
-    var source = newSource(
+    final source = newSource(
         '/test.dart',
         r'''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 
 const String tooComplex = 'bcd';
 
@@ -1549,11 +1726,12 @@
         <ErrorCode>[AngularWarningCode.STRING_VALUE_EXPECTED]);
   }
 
+  // ignore: non_constant_identifier_names
   Future test_hasError_TypeLiteralExpected() async {
-    var source = newSource(
+    final source = newSource(
         '/test.dart',
         r'''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'aaa', template: 'AAA', directives: const [42])
 class ComponentA {
@@ -1564,11 +1742,12 @@
         <ErrorCode>[AngularWarningCode.TYPE_LITERAL_EXPECTED]);
   }
 
+  // ignore: non_constant_identifier_names
   Future test_hasError_TemplateAndTemplateUrlDefined() async {
-    var source = newSource(
+    final source = newSource(
         '/test.dart',
         r'''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'aaa', template: 'AAA', templateUrl: 'a.html')
 class ComponentA {
@@ -1580,11 +1759,12 @@
         <ErrorCode>[AngularWarningCode.TEMPLATE_URL_AND_TEMPLATE_DEFINED]);
   }
 
+  // ignore: non_constant_identifier_names
   Future test_hasError_NeitherTemplateNorTemplateUrlDefined() async {
-    var source = newSource(
+    final source = newSource(
         '/test.dart',
         r'''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'aaa')
 class ComponentA {
@@ -1595,14 +1775,15 @@
         <ErrorCode>[AngularWarningCode.NO_TEMPLATE_URL_OR_TEMPLATE_DEFINED]);
   }
 
+  // ignore: non_constant_identifier_names
   Future test_hasError_missingHtmlFile() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'my-component', templateUrl: 'missing-template.html')
 class MyComponent {}
 ''';
-    var dartSource = newSource('/test.dart', code);
+    final dartSource = newSource('/test.dart', code);
     await getViews(dartSource);
     assertErrorInCodeAtPosition(
         AngularWarningCode.REFERENCED_HTML_FILE_DOESNT_EXIST,
@@ -1610,60 +1791,63 @@
         "'missing-template.html'");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_templateExternal() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'my-component', templateUrl: 'my-template.html')
 class MyComponent {}
 ''';
-    var dartSource = newSource('/test.dart', code);
-    var htmlSource = newSource('/my-template.html', '');
+    final dartSource = newSource('/test.dart', code);
+    final htmlSource = newSource('/my-template.html', '');
     await getViews(dartSource);
     expect(views, hasLength(1));
     // MyComponent
-    View view = getViewByClassName(views, 'MyComponent');
+    final view = getViewByClassName(views, 'MyComponent');
     expect(view.component, getComponentByClassName(directives, 'MyComponent'));
     expect(view.templateText, isNull);
     expect(view.templateUriSource, isNotNull);
     expect(view.templateUriSource, htmlSource);
     expect(view.templateSource, htmlSource);
     {
-      String url = "'my-template.html'";
+      final url = "'my-template.html'";
       expect(view.templateUrlRange,
           new SourceRange(code.indexOf(url), url.length));
     }
   }
 
+  // ignore: non_constant_identifier_names
   Future test_templateExternalUsingViewAnnotation() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'my-component')
 @View(templateUrl: 'my-template.html')
 class MyComponent {}
 ''';
-    var dartSource = newSource('/test.dart', code);
-    var htmlSource = newSource('/my-template.html', '');
+    final dartSource = newSource('/test.dart', code);
+    final htmlSource = newSource('/my-template.html', '');
     await getViews(dartSource);
     expect(views, hasLength(1));
     // MyComponent
-    View view = getViewByClassName(views, 'MyComponent');
+    final view = getViewByClassName(views, 'MyComponent');
     expect(view.component, getComponentByClassName(directives, 'MyComponent'));
     expect(view.templateText, isNull);
     expect(view.templateUriSource, isNotNull);
     expect(view.templateUriSource, htmlSource);
     expect(view.templateSource, htmlSource);
     {
-      String url = "'my-template.html'";
+      final url = "'my-template.html'";
       expect(view.templateUrlRange,
           new SourceRange(code.indexOf(url), url.length));
     }
   }
 
+  // ignore: non_constant_identifier_names
   Future test_templateInline() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 
 @Directive(selector: 'my-directive')
 class MyDirective {}
@@ -1675,11 +1859,11 @@
     directives: const [MyDirective, OtherComponent])
 class MyComponent {}
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getViews(source);
     expect(views, hasLength(2));
     {
-      View view = getViewByClassName(views, 'MyComponent');
+      final view = getViewByClassName(views, 'MyComponent');
       expect(
           view.component, getComponentByClassName(directives, 'MyComponent'));
       expect(view.templateText, ' My template '); // spaces preserve offsets
@@ -1688,7 +1872,7 @@
       expect(view.templateSource, source);
       {
         expect(view.directives, hasLength(2));
-        List<String> directiveClassNames = view.directives
+        final directiveClassNames = view.directives
             .map((directive) => directive.classElement.name)
             .toList();
         expect(directiveClassNames,
@@ -1697,9 +1881,10 @@
     }
   }
 
+  // ignore: non_constant_identifier_names
   Future test_templateInlineUsingViewAnnotation() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 
 @Directive(selector: 'my-directive')
 class MyDirective {}
@@ -1712,11 +1897,11 @@
 @View(template: 'My template', directives: const [MyDirective, OtherComponent])
 class MyComponent {}
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getViews(source);
     expect(views, hasLength(2));
     {
-      View view = getViewByClassName(views, 'MyComponent');
+      final view = getViewByClassName(views, 'MyComponent');
       expect(
           view.component, getComponentByClassName(directives, 'MyComponent'));
       expect(view.templateText, ' My template '); // spaces preserve offsets
@@ -1725,7 +1910,7 @@
       expect(view.templateSource, source);
       {
         expect(view.directives, hasLength(2));
-        List<String> directiveClassNames = view.directives
+        final directiveClassNames = view.directives
             .map((directive) => directive.classElement.name)
             .toList();
         expect(directiveClassNames,
@@ -1733,6 +1918,774 @@
       }
     }
   }
+
+  // ignore: non_constant_identifier_names
+  Future test_hasContentChildComponent() async {
+    final code = r'''
+import 'package:angular2/angular2.dart';
+
+@Component(selector: 'my-component', template: '')
+class ComponentA {
+  @ContentChild(ContentChildComp)
+  ContentChildComp contentChild;
+}
+
+@Component(selector: 'foo', template: '')
+class ContentChildComp {}
+''';
+    final source = newSource('/test.dart', code);
+    await getViews(source);
+    final component = directives.first;
+    final childs = component.contentChilds;
+    expect(childs, hasLength(1));
+    expect(childs.first.query, const isInstanceOf<DirectiveQueriedChildType>());
+    final DirectiveQueriedChildType child = childs.first.query;
+
+    expect(child.directive, equals(directives[1]));
+    // validate
+    errorListener.assertNoErrors();
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_hasContentChildrenDirective() async {
+    final code = r'''
+import 'package:angular2/angular2.dart';
+
+@Component(selector: 'my-component', template: '')
+class ComponentA {
+  @ContentChildren(ContentChildComp)
+  QueryList<ContentChildComp> contentChildren;
+}
+
+@Component(selector: 'foo', template: '')
+class ContentChildComp {}
+''';
+    final source = newSource('/test.dart', code);
+    await getViews(source);
+    final component = directives.first;
+    final childrens = component.contentChildren;
+    expect(childrens, hasLength(1));
+    expect(
+        childrens.first.query, const isInstanceOf<DirectiveQueriedChildType>());
+    final DirectiveQueriedChildType children = childrens.first.query;
+
+    expect(children.directive, equals(directives[1]));
+    // validate
+    errorListener.assertNoErrors();
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_hasContentChildChildrenSetter() async {
+    final code = r'''
+import 'package:angular2/angular2.dart';
+
+@Component(selector: 'my-component', template: '')
+class ComponentA {
+  @ContentChild(ContentChildComp) // 1
+  void set contentChild(ContentChildComp contentChild) => null;
+  @ContentChildren(ContentChildComp) // 2
+  void set contentChildren(QueryList<ContentChildComp> contentChildren) => null;
+}
+
+@Component(selector: 'foo', template: '')
+class ContentChildComp {}
+''';
+    final source = newSource('/test.dart', code);
+    await getViews(source);
+    final component = directives.first;
+
+    final childrens = component.contentChildren;
+    expect(childrens, hasLength(1));
+    expect(
+        childrens.first.query, const isInstanceOf<DirectiveQueriedChildType>());
+    final DirectiveQueriedChildType children = childrens.first.query;
+
+    expect(children.directive, equals(directives[1]));
+
+    final childs = component.contentChilds;
+    expect(childs, hasLength(1));
+    expect(childs.first.query, const isInstanceOf<DirectiveQueriedChildType>());
+    final DirectiveQueriedChildType child = childs.first.query;
+
+    expect(child.directive, equals(directives[1]));
+    // validate
+    errorListener.assertNoErrors();
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_hasContentChildLetBound() async {
+    final code = r'''
+import 'package:angular2/angular2.dart';
+
+@Component(selector: 'my-component', template: '')
+class ComponentA {
+  @ContentChild('foo')
+  ContentChildComp contentChildDirective;
+  @ContentChild('fooTpl')
+  TemplateRef contentChildTpl;
+  @ContentChild('fooElem')
+  ElementRef contentChildElem;
+  @ContentChild('fooDynamic')
+  dynamic contentChildDynamic;
+}
+
+@Component(selector: 'foo', template: '')
+class ContentChildComp {}
+''';
+    final source = newSource('/test.dart', code);
+    await getViews(source);
+    final component = directives.first;
+    final childs = component.contentChilds;
+    expect(childs, hasLength(4));
+
+    final LetBoundQueriedChildType childDirective = childs
+        .singleWhere((c) => c.field.fieldName == "contentChildDirective")
+        .query;
+    expect(childDirective, const isInstanceOf<LetBoundQueriedChildType>());
+    expect(childDirective.letBoundName, equals("foo"));
+    expect(childDirective.containerType.toString(), equals("ContentChildComp"));
+
+    final LetBoundQueriedChildType childTemplate =
+        childs.singleWhere((c) => c.field.fieldName == "contentChildTpl").query;
+    expect(childTemplate, const isInstanceOf<LetBoundQueriedChildType>());
+    expect(childTemplate.letBoundName, equals("fooTpl"));
+    expect(childTemplate.containerType.toString(), equals("TemplateRef"));
+
+    final LetBoundQueriedChildType childElement = childs
+        .singleWhere((c) => c.field.fieldName == "contentChildElem")
+        .query;
+    expect(childElement, const isInstanceOf<LetBoundQueriedChildType>());
+    expect(childElement.letBoundName, equals("fooElem"));
+    expect(childElement.containerType.toString(), equals("ElementRef"));
+
+    final LetBoundQueriedChildType childDynamic = childs
+        .singleWhere((c) => c.field.fieldName == "contentChildDynamic")
+        .query;
+    expect(childDynamic, const isInstanceOf<LetBoundQueriedChildType>());
+    expect(childDynamic.letBoundName, equals("fooDynamic"));
+    expect(childDynamic.containerType.toString(), equals("dynamic"));
+
+    // validate
+    errorListener.assertNoErrors();
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_hasContentChildrenLetBound() async {
+    final code = r'''
+import 'package:angular2/angular2.dart';
+
+@Component(selector: 'my-component', template: '')
+class ComponentA {
+  @ContentChildren('foo')
+  QueryList<ContentChildComp> contentChildDirective;
+  @ContentChildren('fooTpl')
+  QueryList<TemplateRef> contentChildTpl;
+  @ContentChildren('fooElem')
+  QueryList<ElementRef> contentChildElem;
+  @ContentChildren('fooDynamic')
+  QueryList contentChildDynamic;
+}
+
+@Component(selector: 'foo', template: '')
+class ContentChildComp {}
+''';
+    final source = newSource('/test.dart', code);
+    await getViews(source);
+    final component = directives.first;
+    final childrens = component.contentChildren;
+    expect(childrens, hasLength(4));
+
+    final LetBoundQueriedChildType childrenDirective = childrens
+        .singleWhere((c) => c.field.fieldName == "contentChildDirective")
+        .query;
+    expect(childrenDirective, const isInstanceOf<LetBoundQueriedChildType>());
+    expect(childrenDirective.letBoundName, equals("foo"));
+    expect(
+        childrenDirective.containerType.toString(), equals("ContentChildComp"));
+
+    final LetBoundQueriedChildType childrenTemplate = childrens
+        .singleWhere((c) => c.field.fieldName == "contentChildTpl")
+        .query;
+    expect(childrenTemplate, const isInstanceOf<LetBoundQueriedChildType>());
+    expect(childrenTemplate.letBoundName, equals("fooTpl"));
+    expect(childrenTemplate.containerType.toString(), equals("TemplateRef"));
+
+    final LetBoundQueriedChildType childrenElement = childrens
+        .singleWhere((c) => c.field.fieldName == "contentChildElem")
+        .query;
+    expect(childrenElement, const isInstanceOf<LetBoundQueriedChildType>());
+    expect(childrenElement.letBoundName, equals("fooElem"));
+    expect(childrenElement.containerType.toString(), equals("ElementRef"));
+
+    final LetBoundQueriedChildType childrenDynamic = childrens
+        .singleWhere((c) => c.field.fieldName == "contentChildDynamic")
+        .query;
+    expect(childrenDynamic, const isInstanceOf<LetBoundQueriedChildType>());
+    expect(childrenDynamic.letBoundName, equals("fooDynamic"));
+    expect(childrenDynamic.containerType.toString(), equals("dynamic"));
+
+    // validate
+    errorListener.assertNoErrors();
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_hasContentChildElementRef() async {
+    final code = r'''
+import 'package:angular2/angular2.dart';
+
+@Component(selector: 'my-component', template: '')
+class ComponentA {
+  @ContentChild(ElementRef)
+  ElementRef contentChild;
+}
+
+@Component(selector: 'foo', template: '')
+class ContentChildComp {}
+''';
+    final source = newSource('/test.dart', code);
+    await getViews(source);
+    final component = directives.first;
+    final childs = component.contentChilds;
+    expect(childs, hasLength(1));
+    expect(
+        childs.first.query, const isInstanceOf<ElementRefQueriedChildType>());
+
+    // validate
+    errorListener.assertNoErrors();
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_hasContentChildrenElementRef() async {
+    final code = r'''
+import 'package:angular2/angular2.dart';
+
+@Component(selector: 'my-component', template: '')
+class ComponentA {
+  @ContentChildren(ElementRef)
+  QueryList<ElementRef> contentChildren;
+}
+''';
+    final source = newSource('/test.dart', code);
+    await getViews(source);
+    final component = directives.first;
+    final childrens = component.contentChildren;
+    expect(childrens, hasLength(1));
+    expect(childrens.first.query,
+        const isInstanceOf<ElementRefQueriedChildType>());
+
+    // validate
+    errorListener.assertNoErrors();
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_hasContentChildrenTemplateRef() async {
+    final code = r'''
+import 'package:angular2/angular2.dart';
+
+@Component(selector: 'my-component', template: '')
+class ComponentA {
+  @ContentChildren(TemplateRef)
+  QueryList<TemplateRef> contentChildren;
+}
+''';
+    final source = newSource('/test.dart', code);
+    await getViews(source);
+    final component = directives.first;
+    final childrens = component.contentChildren;
+    expect(childrens, hasLength(1));
+    expect(childrens.first.query,
+        const isInstanceOf<TemplateRefQueriedChildType>());
+
+    // validate
+    errorListener.assertNoErrors();
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_hasContentChildTemplateRef() async {
+    final code = r'''
+import 'package:angular2/angular2.dart';
+
+@Component(selector: 'my-component', template: '')
+class ComponentA {
+  @ContentChild(TemplateRef)
+  TemplateRef contentChild;
+}
+
+@Component(selector: 'foo', template: '')
+class ContentChildComp {}
+''';
+    final source = newSource('/test.dart', code);
+    await getViews(source);
+    final component = directives.first;
+    final childs = component.contentChilds;
+    expect(childs, hasLength(1));
+    expect(
+        childs.first.query, const isInstanceOf<TemplateRefQueriedChildType>());
+
+    // validate
+    errorListener.assertNoErrors();
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_hasContentChildDirective_notRecognizedType() async {
+    final code = r'''
+import 'package:angular2/angular2.dart';
+
+@Component(selector: 'my-component', template: '')
+class ComponentA {
+  @ContentChild(String)
+  ElementRef contentChild;
+}
+''';
+    final source = newSource('/test.dart', code);
+    await getViews(source);
+    final component = directives.first;
+    final childs = component.contentChilds;
+    expect(childs, hasLength(0));
+
+    // validate
+    assertErrorInCodeAtPosition(
+        AngularWarningCode.UNKNOWN_CHILD_QUERY_TYPE, code, 'String');
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_hasContentChildDirective_htmlNotAllowed() async {
+    final code = r'''
+import 'package:angular2/angular2.dart';
+import 'dart:html';
+
+@Component(selector: 'my-component', template: '')
+class ComponentA {
+  @ContentChild(AnchorElement)
+  AnchorElement contentChild;
+}
+''';
+    final source = newSource('/test.dart', code);
+    await getViews(source);
+    final component = directives.first;
+    final childs = component.contentChilds;
+    expect(childs, hasLength(0));
+
+    // validate
+    assertErrorInCodeAtPosition(
+        AngularWarningCode.UNKNOWN_CHILD_QUERY_TYPE, code, 'AnchorElement');
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_hasContentChildDirective_notTypeOrString() async {
+    final code = r'''
+import 'package:angular2/angular2.dart';
+
+@Component(selector: 'my-component', template: '')
+class ComponentA {
+  @ContentChild(const [])
+  ElementRef contentChild;
+}
+''';
+    final source = newSource('/test.dart', code);
+    await getViews(source);
+    final component = directives.first;
+    final childs = component.contentChilds;
+    expect(childs, hasLength(0));
+
+    // validate
+    assertErrorInCodeAtPosition(
+        AngularWarningCode.UNKNOWN_CHILD_QUERY_TYPE, code, 'const []');
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_hasContentChildDirective_notAssignable() async {
+    final code = r'''
+import 'package:angular2/angular2.dart';
+
+@Component(selector: 'my-component', template: '')
+class ComponentA {
+  @ContentChild(ContentChildComp)
+  String contentChild;
+}
+
+@Component(selector: 'foo', template: '')
+class ContentChildComp {}
+''';
+    final source = newSource('/test.dart', code);
+    await getViews(source);
+    final component = directives.first;
+    final childs = component.contentChilds;
+    expect(childs, hasLength(1));
+    expect(childs.first.query, const isInstanceOf<DirectiveQueriedChildType>());
+    final DirectiveQueriedChildType child = childs.first.query;
+    expect(child.directive, equals(directives[1]));
+
+    // validate
+    assertErrorInCodeAtPosition(
+        AngularWarningCode.INVALID_TYPE_FOR_CHILD_QUERY, code, 'String');
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_hasContentChildDirective_dynamicOk() async {
+    final code = r'''
+import 'package:angular2/angular2.dart';
+
+@Component(selector: 'my-component', template: '')
+class ComponentA {
+  @ContentChild(ContentChildComp)
+  dynamic contentChild;
+}
+
+@Component(selector: 'foo', template: '')
+class ContentChildComp {}
+''';
+    final source = newSource('/test.dart', code);
+    await getViews(source);
+    final component = directives.first;
+    final childs = component.contentChilds;
+    expect(childs, hasLength(1));
+    expect(childs.first.query, const isInstanceOf<DirectiveQueriedChildType>());
+    final DirectiveQueriedChildType child = childs.first.query;
+
+    expect(child.directive, equals(directives[1]));
+    // validate
+    errorListener.assertNoErrors();
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_hasContentChildDirective_subTypeNotAssignable() async {
+    final code = r'''
+import 'package:angular2/angular2.dart';
+
+@Component(selector: 'my-component', template: '')
+class ComponentA {
+  @ContentChild(ContentChildComp)
+  ContentChildCompSub contentChild;
+}
+
+@Component(selector: 'foo', template: '')
+class ContentChildComp {}
+
+class ContentChildCompSub extends ContentChildComp {}
+''';
+    final source = newSource('/test.dart', code);
+    await getViews(source);
+    final component = directives.first;
+    final childs = component.contentChilds;
+    expect(childs, hasLength(1));
+    expect(childs.first.query, const isInstanceOf<DirectiveQueriedChildType>());
+    final DirectiveQueriedChildType child = childs.first.query;
+    expect(child.directive, equals(directives[1]));
+
+    // validate
+    assertErrorInCodeAtPosition(AngularWarningCode.INVALID_TYPE_FOR_CHILD_QUERY,
+        code, 'ContentChildCompSub');
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_hasContentChildrenDirective_notQueryList() async {
+    final code = r'''
+import 'package:angular2/angular2.dart';
+
+@Component(selector: 'my-component', template: '')
+class ComponentA {
+  @ContentChildren(ContentChildComp)
+  String contentChild;
+}
+
+@Component(selector: 'foo', template: '')
+class ContentChildComp {}
+''';
+    final source = newSource('/test.dart', code);
+    await getViews(source);
+    final component = directives.first;
+    final childrens = component.contentChildren;
+    expect(childrens, hasLength(1));
+    expect(
+        childrens.first.query, const isInstanceOf<DirectiveQueriedChildType>());
+    final DirectiveQueriedChildType children = childrens.first.query;
+    expect(children.directive, equals(directives[1]));
+
+    // validate
+    assertErrorInCodeAtPosition(
+        AngularWarningCode.CONTENT_OR_VIEW_CHILDREN_REQUIRES_QUERY_LIST,
+        code,
+        'String');
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_hasContentChildrenDirective_dynamicOk() async {
+    final code = r'''
+import 'package:angular2/angular2.dart';
+
+@Component(selector: 'my-component', template: '')
+class ComponentA {
+  @ContentChildren(ContentChildComp)
+  dynamic contentChildren;
+}
+
+@Component(selector: 'foo', template: '')
+class ContentChildComp {}
+''';
+    final source = newSource('/test.dart', code);
+    await getViews(source);
+    final component = directives.first;
+    final childrens = component.contentChildren;
+    expect(childrens, hasLength(1));
+    expect(
+        childrens.first.query, const isInstanceOf<DirectiveQueriedChildType>());
+    final DirectiveQueriedChildType children = childrens.first.query;
+
+    expect(children.directive, equals(directives[1]));
+    // validate
+    errorListener.assertNoErrors();
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_hasContentChildrenDirective_notAssignable() async {
+    final code = r'''
+import 'package:angular2/angular2.dart';
+
+@Component(selector: 'my-component', template: '')
+class ComponentA {
+  @ContentChildren(ContentChildComp)
+  QueryList<String> contentChildren;
+}
+
+@Component(selector: 'foo', template: '')
+class ContentChildComp {}
+''';
+    final source = newSource('/test.dart', code);
+    await getViews(source);
+
+    // validate
+    assertErrorInCodeAtPosition(AngularWarningCode.INVALID_TYPE_FOR_CHILD_QUERY,
+        code, 'QueryList<String>');
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_hasContentChildrenDirective_dynamicListOk() async {
+    final code = r'''
+import 'package:angular2/angular2.dart';
+
+@Component(selector: 'my-component', template: '')
+class ComponentA {
+  @ContentChildren(ContentChildComp)
+  QueryList contentChildren;
+}
+
+@Component(selector: 'foo', template: '')
+class ContentChildComp {}
+''';
+    final source = newSource('/test.dart', code);
+    await getViews(source);
+    final component = directives.first;
+    final childrens = component.contentChildren;
+    expect(childrens, hasLength(1));
+    expect(
+        childrens.first.query, const isInstanceOf<DirectiveQueriedChildType>());
+    final DirectiveQueriedChildType children = childrens.first.query;
+
+    expect(children.directive, equals(directives[1]));
+    // validate
+    errorListener.assertNoErrors();
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_hasContentChildrenDirective_iterableOk() async {
+    final code = r'''
+import 'package:angular2/angular2.dart';
+
+@Component(selector: 'my-component', template: '')
+class ComponentA {
+  @ContentChildren(ContentChildComp)
+  Iterable<ContentChildComp> contentChildren;
+}
+
+@Component(selector: 'foo', template: '')
+class ContentChildComp {}
+''';
+    final source = newSource('/test.dart', code);
+    await getViews(source);
+    final component = directives.first;
+    final childrens = component.contentChildren;
+    expect(childrens, hasLength(1));
+    expect(
+        childrens.first.query, const isInstanceOf<DirectiveQueriedChildType>());
+    final DirectiveQueriedChildType children = childrens.first.query;
+
+    expect(children.directive, equals(directives[1]));
+    // validate
+    errorListener.assertNoErrors();
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_hasContentChildrenDirective_iterableNotAssignable() async {
+    final code = r'''
+import 'package:angular2/angular2.dart';
+
+@Component(selector: 'my-component', template: '')
+class ComponentA {
+  @ContentChildren(ContentChildComp)
+  Iterable<String> contentChildren;
+}
+
+@Component(selector: 'foo', template: '')
+class ContentChildComp {}
+''';
+    final source = newSource('/test.dart', code);
+    await getViews(source);
+
+    // validate
+    assertErrorInCodeAtPosition(AngularWarningCode.INVALID_TYPE_FOR_CHILD_QUERY,
+        code, 'Iterable<String>');
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_hasContentChildrenDirective_dynamicIterableOk() async {
+    final code = r'''
+import 'package:angular2/angular2.dart';
+
+@Component(selector: 'my-component', template: '')
+class ComponentA {
+  @ContentChildren(ContentChildComp)
+  Iterable contentChildren;
+}
+
+@Component(selector: 'foo', template: '')
+class ContentChildComp {}
+''';
+    final source = newSource('/test.dart', code);
+    await getViews(source);
+    final component = directives.first;
+    final childrens = component.contentChildren;
+    expect(childrens, hasLength(1));
+    expect(
+        childrens.first.query, const isInstanceOf<DirectiveQueriedChildType>());
+    final DirectiveQueriedChildType children = childrens.first.query;
+
+    expect(children.directive, equals(directives[1]));
+    // validate
+    errorListener.assertNoErrors();
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_hasContentChildrenDirective_subtypingQueryListNotOk() async {
+    final code = r'''
+import 'package:angular2/angular2.dart';
+
+@Component(selector: 'my-component', template: '')
+class ComponentA {
+  @ContentChildren(ContentChildComp)
+  // this is not allowed. Angular makes a QueryList, regardless of your subtype
+  CannotSubtypeQueryList contentChild;
+}
+
+@Component(selector: 'foo', template: '')
+class ContentChildComp {}
+
+abstract class CannotSubtypeQueryList extends QueryList {}
+''';
+    final source = newSource('/test.dart', code);
+    await getViews(source);
+    final component = directives.first;
+    final childrens = component.contentChildren;
+    expect(childrens, hasLength(1));
+    expect(
+        childrens.first.query, const isInstanceOf<DirectiveQueriedChildType>());
+    final DirectiveQueriedChildType children = childrens.first.query;
+    expect(children.directive, equals(directives[1]));
+
+    // validate
+    assertErrorInCodeAtPosition(
+        AngularWarningCode.CONTENT_OR_VIEW_CHILDREN_REQUIRES_QUERY_LIST,
+        code,
+        'CannotSubtypeQueryList');
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_hasContentChildTemplateRef_notAssignable() async {
+    final code = r'''
+import 'package:angular2/angular2.dart';
+
+@Component(selector: 'my-component', template: '')
+class ComponentA {
+  @ContentChild(TemplateRef)
+  String contentChild;
+}
+
+@Component(selector: 'foo', template: '')
+class ContentChildComp {}
+''';
+    final source = newSource('/test.dart', code);
+    await getViews(source);
+
+    // validate
+    assertErrorInCodeAtPosition(
+        AngularWarningCode.INVALID_TYPE_FOR_CHILD_QUERY, code, 'String');
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_hasContentChildrenTemplateRef_notAssignable() async {
+    final code = r'''
+import 'package:angular2/angular2.dart';
+
+@Component(selector: 'my-component', template: '')
+class ComponentA {
+  @ContentChildren(TemplateRef)
+  QueryList<String> contentChildren;
+}
+
+@Component(selector: 'foo', template: '')
+class ContentChildComp {}
+''';
+    final source = newSource('/test.dart', code);
+    await getViews(source);
+
+    // validate
+    assertErrorInCodeAtPosition(AngularWarningCode.INVALID_TYPE_FOR_CHILD_QUERY,
+        code, 'QueryList<String>');
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_hasContentChildElementRef_notAssignable() async {
+    final code = r'''
+import 'package:angular2/angular2.dart';
+
+@Component(selector: 'my-component', template: '')
+class ComponentA {
+  @ContentChild(ElementRef)
+  String contentChild;
+}
+
+@Component(selector: 'foo', template: '')
+class ContentChildComp {}
+''';
+    final source = newSource('/test.dart', code);
+    await getViews(source);
+
+    // validate
+    assertErrorInCodeAtPosition(
+        AngularWarningCode.INVALID_TYPE_FOR_CHILD_QUERY, code, 'String');
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_hasContentChildrenElementRef_notAssignable() async {
+    final code = r'''
+import 'package:angular2/angular2.dart';
+
+@Component(selector: 'my-component', template: '')
+class ComponentA {
+  @ContentChildren(ElementRef)
+  QueryList<String> contentChildren;
+}
+
+@Component(selector: 'foo', template: '')
+class ContentChildComp {}
+''';
+    final source = newSource('/test.dart', code);
+    await getViews(source);
+
+    // validate
+    assertErrorInCodeAtPosition(AngularWarningCode.INVALID_TYPE_FOR_CHILD_QUERY,
+        code, 'QueryList<String>');
+  }
 }
 
 @reflectiveTest
@@ -1741,7 +2694,7 @@
   List<Template> templates;
   List<AnalysisError> errors;
 
-  Future getDirectives(Source source) async {
+  Future getDirectives(final source) async {
     final dartResult = await dartDriver.getResult(source.fullName);
     fillErrorListener(dartResult.errors);
     final ngResult = await angularDriver.resolveDart(source.fullName);
@@ -1754,11 +2707,12 @@
         .toList();
   }
 
+  // ignore: non_constant_identifier_names
   Future test_hasError_DirectiveTypeLiteralExpected() async {
-    var source = newSource(
+    final source = newSource(
         '/test.dart',
         r'''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'aaa', template: 'AAA', directives: const [int])
 class ComponentA {
@@ -1769,9 +2723,10 @@
         <ErrorCode>[AngularWarningCode.TYPE_IS_NOT_A_DIRECTIVE]);
   }
 
+  // ignore: non_constant_identifier_names
   Future test_componentReference() async {
-    var code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'my-aaa', template: '<div>AAA</div>')
 class ComponentA {
@@ -1790,41 +2745,41 @@
 class ComponentC {
 }
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getDirectives(source);
-    Component componentA = getComponentByClassName(directives, 'ComponentA');
-    Component componentB = getComponentByClassName(directives, 'ComponentB');
+    final componentA = getComponentByClassName(directives, 'ComponentA');
+    final componentB = getComponentByClassName(directives, 'ComponentB');
     // validate
     expect(templates, hasLength(3));
     {
-      Template template = _getDartTemplateByClassName(templates, 'ComponentA');
+      final template = _getDartTemplateByClassName(templates, 'ComponentA');
       expect(template.ranges, isEmpty);
     }
     {
-      Template template = _getDartTemplateByClassName(templates, 'ComponentB');
+      final template = _getDartTemplateByClassName(templates, 'ComponentB');
       expect(template.ranges, isEmpty);
     }
     {
-      Template template = _getDartTemplateByClassName(templates, 'ComponentC');
-      List<ResolvedRange> ranges = template.ranges;
+      final template = _getDartTemplateByClassName(templates, 'ComponentC');
+      final ranges = template.ranges;
       expect(ranges, hasLength(4));
       {
-        ResolvedRange resolvedRange =
+        final resolvedRange =
             getResolvedRangeAtString(code, ranges, 'my-aaa></');
         assertComponentReference(resolvedRange, componentA);
       }
       {
-        ResolvedRange resolvedRange =
+        final resolvedRange =
             getResolvedRangeAtString(code, ranges, 'my-aaa>1');
         assertComponentReference(resolvedRange, componentA);
       }
       {
-        ResolvedRange resolvedRange =
+        final resolvedRange =
             getResolvedRangeAtString(code, ranges, 'my-bbb></');
         assertComponentReference(resolvedRange, componentB);
       }
       {
-        ResolvedRange resolvedRange =
+        final resolvedRange =
             getResolvedRangeAtString(code, ranges, 'my-bbb>2');
         assertComponentReference(resolvedRange, componentB);
       }
@@ -1833,9 +2788,10 @@
     errorListener.assertNoErrors();
   }
 
+  // ignore: non_constant_identifier_names
   Future test_hasError_expression_ArgumentTypeNotAssignable() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'text-panel',
     template: r"<div> {{text.length + text}} </div>")
@@ -1843,15 +2799,16 @@
   String text;
 }
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getDirectives(source);
     errorListener.assertErrorsWithCodes(
         [StaticWarningCode.ARGUMENT_TYPE_NOT_ASSIGNABLE]);
   }
 
+  // ignore: non_constant_identifier_names
   Future test_hasError_expression_UndefinedIdentifier() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'text-panel', inputs: const ['text'],
     template: r"<div>some text</div>")
@@ -1867,46 +2824,49 @@
 class UserPanel {
 }
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getDirectives(source);
     errorListener
         .assertErrorsWithCodes([StaticWarningCode.UNDEFINED_IDENTIFIER]);
   }
 
   Future
+      // ignore: non_constant_identifier_names
       test_hasError_expression_UndefinedIdentifier_OutsideFirstHtmlTag() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'my-component', template: '<h1></h1>{{noSuchName}}')
 class MyComponent {
 }
 ''';
 
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getDirectives(source);
     assertErrorInCodeAtPosition(
         StaticWarningCode.UNDEFINED_IDENTIFIER, code, 'noSuchName');
   }
 
+  // ignore: non_constant_identifier_names
   Future test_hasError_UnresolvedTag() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'my-aaa',
     template: "<unresolved-tag attr='value'></unresolved-tag>")
 class ComponentA {
 }
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getDirectives(source);
     assertErrorInCodeAtPosition(
         AngularWarningCode.UNRESOLVED_TAG, code, 'unresolved-tag');
   }
 
+  // ignore: non_constant_identifier_names
   Future test_suppressError_UnresolvedTag() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'my-aaa',
     template: """
@@ -1915,14 +2875,15 @@
 class ComponentA {
 }
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getDirectives(source);
     errorListener.assertNoErrors();
   }
 
+  // ignore: non_constant_identifier_names
   Future test_suppressError_NotCaseSensitive() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'my-aaa',
     template: """
@@ -1931,14 +2892,15 @@
 class ComponentA {
 }
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getDirectives(source);
     errorListener.assertNoErrors();
   }
 
+  // ignore: non_constant_identifier_names
   Future test_suppressError_UnresolvedTagAndInput() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'my-aaa',
     template: """
@@ -1948,21 +2910,22 @@
   Object value;
 }
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getDirectives(source);
     errorListener.assertNoErrors();
   }
 
+  // ignore: non_constant_identifier_names
   Future test_htmlParsing_hasError() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'text-panel',
     template: r"<div> <h2> Expected closing H2 </h3> </div>")
 class TextPanel {
 }
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getDirectives(source);
     // has errors
     errorListener.assertErrorsWithCodes([
@@ -1971,10 +2934,11 @@
     ]);
   }
 
+  // ignore: non_constant_identifier_names
   Future test_input_OK_event() async {
-    String code = r'''
+    final code = r'''
 import 'dart:html';
-import '/angular2/angular2.dart';
+    import 'package:angular2/angular2.dart';
 
 @Component(selector: 'UserPanel', template: r"""
 <div>
@@ -1985,35 +2949,34 @@
   gotClicked(MouseEvent event) {}
 }
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getDirectives(source);
     expect(templates, hasLength(1));
     {
-      Template template = _getDartTemplateByClassName(templates, 'TodoList');
-      List<ResolvedRange> ranges = template.ranges;
+      final template = _getDartTemplateByClassName(templates, 'TodoList');
+      final ranges = template.ranges;
       expect(ranges, hasLength(4));
       {
-        ResolvedRange resolvedRange =
+        final resolvedRange =
             getResolvedRangeAtString(code, ranges, r'gotClicked($');
         expect(resolvedRange.range.length, 'gotClicked'.length);
-        Element element = (resolvedRange.element as DartElement).element;
-        expect(element, new isInstanceOf<MethodElement>());
+        final element = (resolvedRange.element as DartElement).element;
+        expect(element, const isInstanceOf<MethodElement>());
         expect(element.name, 'gotClicked');
         expect(
             element.nameOffset, code.indexOf('gotClicked(MouseEvent event)'));
       }
       {
-        ResolvedRange resolvedRange =
+        final resolvedRange =
             getResolvedRangeAtString(code, ranges, r"$event)'>");
         expect(resolvedRange.range.length, r'$event'.length);
-        Element element = (resolvedRange.element as LocalVariable).dartVariable;
-        expect(element, new isInstanceOf<LocalVariableElement>());
+        final element = (resolvedRange.element as LocalVariable).dartVariable;
+        expect(element, const isInstanceOf<LocalVariableElement>());
         expect(element.name, r'$event');
         expect(element.nameOffset, -1);
       }
       {
-        ResolvedRange resolvedRange =
-            getResolvedRangeAtString(code, ranges, 'click');
+        final resolvedRange = getResolvedRangeAtString(code, ranges, 'click');
         expect(resolvedRange.range.length, 'click'.length);
       }
     }
@@ -2021,9 +2984,10 @@
     errorListener.assertNoErrors();
   }
 
+  // ignore: non_constant_identifier_names
   Future test_input_OK_reference_expression() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'text-panel', inputs: const ['text'],
     template: r"<div>some text</div>")
@@ -2044,36 +3008,33 @@
   String name; // 2
 }
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getDirectives(source);
-    Component textPanel = getComponentByClassName(directives, 'TextPanel');
+    final textPanel = getComponentByClassName(directives, 'TextPanel');
     // validate
     expect(templates, hasLength(2));
     {
-      Template template = _getDartTemplateByClassName(templates, 'UserPanel');
-      List<ResolvedRange> ranges = template.ranges;
+      final template = _getDartTemplateByClassName(templates, 'UserPanel');
+      final ranges = template.ranges;
       expect(ranges, hasLength(5));
       {
-        ResolvedRange resolvedRange =
-            getResolvedRangeAtString(code, ranges, 'text]=');
+        final resolvedRange = getResolvedRangeAtString(code, ranges, 'text]=');
         expect(resolvedRange.range.length, 'text'.length);
         assertPropertyReference(resolvedRange, textPanel, 'text');
       }
       {
-        ResolvedRange resolvedRange =
-            getResolvedRangeAtString(code, ranges, 'user.');
+        final resolvedRange = getResolvedRangeAtString(code, ranges, 'user.');
         expect(resolvedRange.range.length, 'user'.length);
-        Element element = (resolvedRange.element as DartElement).element;
-        expect(element, new isInstanceOf<PropertyAccessorElement>());
+        final element = (resolvedRange.element as DartElement).element;
+        expect(element, const isInstanceOf<PropertyAccessorElement>());
         expect(element.name, 'user');
         expect(element.nameOffset, code.indexOf('user; // 1'));
       }
       {
-        ResolvedRange resolvedRange =
-            getResolvedRangeAtString(code, ranges, "name'>");
+        final resolvedRange = getResolvedRangeAtString(code, ranges, "name'>");
         expect(resolvedRange.range.length, 'name'.length);
-        Element element = (resolvedRange.element as DartElement).element;
-        expect(element, new isInstanceOf<PropertyAccessorElement>());
+        final element = (resolvedRange.element as DartElement).element;
+        expect(element, const isInstanceOf<PropertyAccessorElement>());
         expect(element.name, 'name');
         expect(element.nameOffset, code.indexOf('name; // 2'));
       }
@@ -2082,9 +3043,10 @@
     errorListener.assertNoErrors();
   }
 
+  // ignore: non_constant_identifier_names
   Future test_input_OK_reference_text() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 
 @Component(
     selector: 'comp-a',
@@ -2103,23 +3065,23 @@
 class ComponentB {
 }
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getDirectives(source);
-    Component componentA = getComponentByClassName(directives, 'ComponentA');
+    final componentA = getComponentByClassName(directives, 'ComponentA');
     // validate
     expect(templates, hasLength(2));
     {
-      Template template = _getDartTemplateByClassName(templates, 'ComponentB');
-      List<ResolvedRange> ranges = template.ranges;
+      final template = _getDartTemplateByClassName(templates, 'ComponentB');
+      final ranges = template.ranges;
       expect(ranges, hasLength(4));
       {
-        ResolvedRange resolvedRange =
+        final resolvedRange =
             getResolvedRangeAtString(code, ranges, 'firstValue]=');
         expect(resolvedRange.range.length, 'firstValue'.length);
         assertPropertyReference(resolvedRange, componentA, 'firstValue');
       }
       {
-        ResolvedRange resolvedRange =
+        final resolvedRange =
             getResolvedRangeAtString(code, ranges, 'second]=');
         expect(resolvedRange.range.length, 'second'.length);
         assertPropertyReference(resolvedRange, componentA, 'second');
@@ -2129,79 +3091,84 @@
     errorListener.assertNoErrors();
   }
 
+  // ignore: non_constant_identifier_names
   Future test_noRootElement() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'text-panel',
     template: r'Often used without an element in tests.')
 class TextPanel {
 }
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getDirectives(source);
     expect(templates, hasLength(1));
     // has errors
     errorListener.assertNoErrors();
   }
 
+  // ignore: non_constant_identifier_names
   Future test_noTemplateContents() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'text-panel',
     template: '')
 class TextPanel {
 }
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getDirectives(source);
     expect(templates, hasLength(1));
     // has errors
     errorListener.assertNoErrors();
   }
 
+  // ignore: non_constant_identifier_names
   Future test_textExpression_hasError_UnterminatedMustache() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'text-panel', template: r"{{text")
 class TextPanel {
   String text = "text";
 }
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getDirectives(source);
     // has errors
     errorListener
         .assertErrorsWithCodes([AngularWarningCode.UNTERMINATED_MUSTACHE]);
   }
 
+  // ignore: non_constant_identifier_names
   Future test_textExpression_hasError_UnopenedMustache() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'text-panel', template: r"<div> text}} </div>")
 class TextPanel {
   String text;
 }
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getDirectives(source);
     // has errors
     errorListener.assertErrorsWithCodes([AngularWarningCode.UNOPENED_MUSTACHE]);
   }
 
+  // ignore: non_constant_identifier_names
   Future test_textExpression_hasError_DoubleOpenedMustache() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'text-panel', template: r"<div> {{text {{ error}} </div>")
 class TextPanel {
   String text;
 }
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getDirectives(source);
     errorListener.assertErrorsWithCodes([
       AngularWarningCode.UNTERMINATED_MUSTACHE,
@@ -2209,16 +3176,17 @@
     ]);
   }
 
+  // ignore: non_constant_identifier_names
   Future test_textExpression_hasError_MultipleUnclosedMustaches() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'text-panel', template: r"<div> {{open {{error {{text}} close}} close}} </div>")
 class TextPanel {
   String text, open, close;
 }
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getDirectives(source);
     errorListener.assertErrorsWithCodes([
       AngularWarningCode.UNTERMINATED_MUSTACHE,
@@ -2229,9 +3197,10 @@
     ]);
   }
 
+  // ignore: non_constant_identifier_names
   Future test_textExpression_OK() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'text-panel', inputs: const ['text'],
     template: r"<div> <h2> {{text}}  </h2> and {{text.length}} </div>")
@@ -2239,34 +3208,33 @@
   String text; // 1
 }
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getDirectives(source);
     expect(templates, hasLength(1));
     {
-      Template template = _getDartTemplateByClassName(templates, 'TextPanel');
-      List<ResolvedRange> ranges = template.ranges;
+      final template = _getDartTemplateByClassName(templates, 'TextPanel');
+      final ranges = template.ranges;
       expect(ranges, hasLength(5));
       {
-        ResolvedRange resolvedRange =
-            getResolvedRangeAtString(code, ranges, 'text}}');
+        final resolvedRange = getResolvedRangeAtString(code, ranges, 'text}}');
         expect(resolvedRange.range.length, 'text'.length);
-        PropertyAccessorElement element = assertGetter(resolvedRange);
+        final element = assertGetter(resolvedRange);
         expect(element.name, 'text');
         expect(element.nameOffset, code.indexOf('text; // 1'));
       }
       {
-        ResolvedRange resolvedRange =
+        final resolvedRange =
             getResolvedRangeAtString(code, ranges, 'text.length');
         expect(resolvedRange.range.length, 'text'.length);
-        PropertyAccessorElement element = assertGetter(resolvedRange);
+        final element = assertGetter(resolvedRange);
         expect(element.name, 'text');
         expect(element.nameOffset, code.indexOf('text; // 1'));
       }
       {
-        ResolvedRange resolvedRange =
+        final resolvedRange =
             getResolvedRangeAtString(code, ranges, 'length}}');
         expect(resolvedRange.range.length, 'length'.length);
-        PropertyAccessorElement element = assertGetter(resolvedRange);
+        final element = assertGetter(resolvedRange);
         expect(element.name, 'length');
         expect(element.enclosingElement.name, 'String');
       }
@@ -2275,58 +3243,60 @@
     errorListener.assertNoErrors();
   }
 
-  Future test_resolveGetChildDirectivesNgContentSelectors_in_template() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+  // ignore: non_constant_identifier_names
+  Future test_resolveGetChildDirectivesNgContentSelectors() async {
+    final code = r'''
+import 'package:angular2/angular2.dart';
 import 'child_file.dart';
 
 @Component(selector: 'my-component', template: 'My template',
     directives: const [ChildComponent])
 class MyComponent {}
 ''';
-    String childCode = r'''
-import '/angular2/angular2.dart';
+    final childCode = r'''
+import 'package:angular2/angular2.dart';
 @Component(selector: 'child-component',
     template: 'My template <ng-content></ng-content>',
     directives: const [])
 class ChildComponent {}
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     newSource('/child_file.dart', childCode);
     await getDirectives(source);
     expect(templates, hasLength(1));
     // no errors
     errorListener.assertNoErrors();
 
-    List<AbstractDirective> childDirectives = templates.first.view.directives;
+    final childDirectives = templates.first.view.directives;
     expect(childDirectives, hasLength(1));
 
-    List<View> childViews = childDirectives
+    final childViews = childDirectives
         .map((d) => d is Component ? d.view : null)
         .where((v) => v != null)
         .toList();
     expect(childViews, hasLength(1));
-    View childView = childViews.first;
+    final childView = childViews.first;
     expect(childView.component, isNotNull);
     expect(childView.component.ngContents, hasLength(1));
   }
 
+  // ignore: non_constant_identifier_names
   Future test_attributes() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'my-component', template: '')
 class MyComponent {
   MyComponent(@Attribute("my-attr") String foo);
 }
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getDirectives(source);
-    Component component = directives.single;
-    List<AngularElement> attributes = component.attributes;
+    final component = directives.single;
+    final attributes = component.attributes;
     expect(attributes, hasLength(1));
     {
-      AngularElement attribute = attributes[0];
+      final attribute = attributes[0];
       expect(attribute.name, 'my-attr');
       // TODO better offsets here. But its really not that critical
       expect(attribute.nameOffset, code.indexOf("foo"));
@@ -2335,22 +3305,23 @@
     errorListener.assertNoErrors();
   }
 
+  // ignore: non_constant_identifier_names
   Future test_attributeNotString() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'my-component', template: '')
 class MyComponent {
   MyComponent(@Attribute("my-attr") int foo);
 }
 ''';
-    var source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     await getDirectives(source);
-    Component component = directives.single;
-    List<AngularElement> attributes = component.attributes;
+    final component = directives.single;
+    final attributes = component.attributes;
     expect(attributes, hasLength(1));
     {
-      AngularElement attribute = attributes[0];
+      final attribute = attributes[0];
       expect(attribute.name, 'my-attr');
       // TODO better offsets here. But its really not that critical
       expect(attribute.nameOffset, code.indexOf("foo"));
@@ -2360,14 +3331,33 @@
         AngularWarningCode.ATTRIBUTE_PARAMETER_MUST_BE_STRING, code, 'foo');
   }
 
-  static Template _getDartTemplateByClassName(
-      List<Template> templates, String className) {
-    return templates.firstWhere(
-        (template) => template.view.classElement.name == className, orElse: () {
-      fail('Template with the class "$className" was not found.');
-      return null;
-    });
+  // ignore: non_constant_identifier_names
+  Future test_constantExpressionTemplateVarDoesntCrash() async {
+    final source = newSource(
+        '/test.dart',
+        r'''
+import 'package:angular2/angular2.dart';
+
+const String tplText = "we don't analyze this";
+
+@Component(selector: 'aaa', template: tplText)
+class ComponentA {
+}
+''');
+    await getDirectives(source);
+    expect(templates, hasLength(0));
+    errorListener.assertErrorsWithCodes(
+        <ErrorCode>[AngularWarningCode.STRING_VALUE_EXPECTED]);
   }
+
+  static Template _getDartTemplateByClassName(
+          List<Template> templates, String className) =>
+      templates.firstWhere(
+          (template) => template.view.classElement.name == className,
+          orElse: () {
+        fail('Template with the class "$className" was not found.');
+        return null;
+      });
 }
 
 @reflectiveTest
@@ -2385,9 +3375,10 @@
         .where((d) => d != null);
   }
 
+  // ignore: non_constant_identifier_names
   Future test_multipleViewsWithTemplate() async {
-    String dartCodeOne = r'''
-import '/angular2/angular2.dart';
+    final dartCodeOne = r'''
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'text-panelA', templateUrl: 'text_panel.html')
 class TextPanelA {
@@ -2395,7 +3386,7 @@
 }
 ''';
 
-    String dartCodeTwo = r'''
+    final dartCodeTwo = r'''
 import '/angular2/angular2.dart';
 
 @Component(selector: 'text-panelB', templateUrl: 'text_panel.html')
@@ -2403,21 +3394,21 @@
   String text; // B
 }
 ''';
-    String htmlCode = r"""
+    final htmlCode = r"""
 <div>
   {{text}}
 </div>
 """;
-    var dartSourceOne = newSource('/test1.dart', dartCodeOne);
-    var dartSourceTwo = newSource('/test2.dart', dartCodeTwo);
-    var htmlSource = newSource('/text_panel.html', htmlCode);
+    final dartSourceOne = newSource('/test1.dart', dartCodeOne);
+    final dartSourceTwo = newSource('/test2.dart', dartCodeTwo);
+    final htmlSource = newSource('/text_panel.html', htmlCode);
     await getDirectives(htmlSource, [dartSourceOne, dartSourceTwo]);
     expect(templates, hasLength(2));
     // validate templates
-    bool hasTextPanelA = false;
-    bool hasTextPanelB = false;
-    for (HtmlTemplate template in templates) {
-      String viewClassName = template.view.classElement.name;
+    var hasTextPanelA = false;
+    var hasTextPanelB = false;
+    for (final template in templates) {
+      final viewClassName = template.view.classElement.name;
       int textLocation;
       if (viewClassName == 'TextPanelA') {
         hasTextPanelA = true;
@@ -2429,9 +3420,9 @@
       }
       expect(template.ranges, hasLength(1));
       {
-        ResolvedRange resolvedRange =
+        final resolvedRange =
             getResolvedRangeAtString(htmlCode, template.ranges, 'text}}');
-        PropertyAccessorElement element = assertGetter(resolvedRange);
+        final element = assertGetter(resolvedRange);
         expect(element.name, 'text');
         expect(element.nameOffset, textLocation);
       }
@@ -2454,17 +3445,18 @@
         .where((v) => v != null);
   }
 
+  // ignore: non_constant_identifier_names
   Future test_suppressError_UnresolvedTagHtmlTemplate() async {
-    var dartSource = newSource(
+    final dartSource = newSource(
         '/test.dart',
         r'''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'my-aaa', templateUrl: 'test.html')
 class ComponentA {
 }
 ''');
-    var htmlSource = newSource(
+    final htmlSource = newSource(
         '/test.html',
         '''
 <!-- @ngIgnoreErrors: UNRESOLVED_TAG -->
@@ -2474,88 +3466,91 @@
     errorListener.assertNoErrors();
   }
 
+  // ignore: non_constant_identifier_names
   Future test_errorFromWeirdInclude_includesFromPath() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'my-aaa', templateUrl: "test.html")
 class ComponentA {
 }
 ''';
-    var dartSource = newSource('/weird.dart', code);
-    var htmlSource =
+    final dartSource = newSource('/weird.dart', code);
+    final htmlSource =
         newSource('/test.html', "<unresolved-tag></unresolved-tag>");
     await getDirectives(htmlSource, dartSource);
     final errors = errorListener.errors;
     expect(errors, hasLength(1));
-    expect(errors.first, new isInstanceOf<FromFilePrefixedError>());
+    expect(errors.first, const isInstanceOf<FromFilePrefixedError>());
     expect(errors.first.message,
         equals('Unresolved tag "unresolved-tag" (from /weird.dart)'));
   }
 
+  // ignore: non_constant_identifier_names
   Future test_hasViewWithTemplate() async {
-    String dartCode = r'''
-import '/angular2/angular2.dart';
+    final dartCode = r'''
+import 'package:angular2/angular2.dart';
 
 @Component(selector: 'text-panel', templateUrl: 'text_panel.html')
 class TextPanel {
   String text; // 1
 }
 ''';
-    String htmlCode = r"""
+    final htmlCode = r"""
 <div>
   {{text}}
 </div>
 """;
-    var dartSource = newSource('/test.dart', dartCode);
-    var htmlSource = newSource('/text_panel.html', htmlCode);
+    final dartSource = newSource('/test.dart', dartCode);
+    final htmlSource = newSource('/text_panel.html', htmlCode);
     // compute
     await getDirectives(htmlSource, dartSource);
     expect(views, hasLength(1));
     {
-      View view = getViewByClassName(views, 'TextPanel');
+      final view = getViewByClassName(views, 'TextPanel');
       expect(view.templateUriSource, isNotNull);
       // resolve this View
-      Template template = view.template;
+      final template = view.template;
       expect(template, isNotNull);
       expect(template.view, view);
       expect(template.ranges, hasLength(1));
       {
-        ResolvedRange resolvedRange =
+        final resolvedRange =
             getResolvedRangeAtString(htmlCode, template.ranges, 'text}}');
-        PropertyAccessorElement element = assertGetter(resolvedRange);
+        final element = assertGetter(resolvedRange);
         expect(element.name, 'text');
         expect(element.nameOffset, dartCode.indexOf('text; // 1'));
       }
     }
   }
 
+  // ignore: non_constant_identifier_names
   Future test_resolveGetChildDirectivesNgContentSelectors() async {
-    String code = r'''
-import '/angular2/angular2.dart';
+    final code = r'''
+import 'package:angular2/angular2.dart';
 import 'child_file.dart';
 
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(selector: 'my-component', templateUrl: 'test.html',
     directives: const [ChildComponent])
 class MyComponent {}
 ''';
-    String childCode = r'''
-import '/angular2/angular2.dart';
+    final childCode = r'''
+import 'package:angular2/angular2.dart';
 @Component(selector: 'child-component',
     template: 'My template <ng-content></ng-content>',
     directives: const [])
 class ChildComponent {}
 ''';
-    var dartSource = newSource('/test.dart', code);
+    final dartSource = newSource('/test.dart', code);
     newSource('/child_file.dart', childCode);
-    var htmlSource = newSource('/test.html', '');
+    final htmlSource = newSource('/test.html', '');
     await getDirectives(htmlSource, dartSource);
 
-    List<AbstractDirective> childDirectives = views.first.directives;
+    final childDirectives = views.first.directives;
     expect(childDirectives, hasLength(1));
 
-    View childView = (views.first.directives.first as Component).view;
+    final childView = (views.first.directives.first as Component).view;
     expect(childView.component, isNotNull);
     expect(childView.component.ngContents, hasLength(1));
   }
diff --git a/analyzer_plugin/test/element_assert.dart b/analyzer_plugin/test/element_assert.dart
index 4bd7f4f..5a7347f 100644
--- a/analyzer_plugin/test/element_assert.dart
+++ b/analyzer_plugin/test/element_assert.dart
@@ -74,34 +74,34 @@
       this._htmlSource, this.element, this._referenceOffset);
 
   AngularElementAssert get angular {
-    expect(element, new isInstanceOf<AngularElement>());
+    expect(element, const isInstanceOf<AngularElement>());
     return new AngularElementAssert(element, _dartSource);
   }
 
   DartElementAssert get dart {
-    expect(element, new isInstanceOf<DartElement>());
-    DartElement dartElement = element;
+    expect(element, const isInstanceOf<DartElement>());
+    final DartElement dartElement = element;
     return new DartElementAssert(dartElement.element, _dartSource, _dartCode);
   }
 
   AngularElementAssert get input {
-    expect(element, new isInstanceOf<InputElement>());
+    expect(element, const isInstanceOf<InputElement>());
     return new AngularElementAssert(element, _dartSource);
   }
 
   AngularElementAssert get output {
-    expect(element, new isInstanceOf<OutputElement>());
+    expect(element, const isInstanceOf<OutputElement>());
     return new AngularElementAssert(element, _dartSource);
   }
 
   LocalVariableAssert get local {
-    expect(element, new isInstanceOf<LocalVariable>());
+    expect(element, const isInstanceOf<LocalVariable>());
     return new LocalVariableAssert(
         element, _referenceOffset, _htmlSource, _htmlCode);
   }
 
   AngularElementAssert get selector {
-    expect(element, new isInstanceOf<SelectorName>());
+    expect(element, const isInstanceOf<SelectorName>());
     return new AngularElementAssert(element, _dartSource);
   }
 }
@@ -137,10 +137,8 @@
   _AbstractElementAssert([this._source, this._code]);
 
   void _at(int actualOffset, String search) {
-    if (_code == null) {
-      _code = _source.contents.data;
-    }
-    int offset = _code.indexOf(search);
+    _code ??= _source.contents.data;
+    final offset = _code.indexOf(search);
     expect(offset, isNonNegative, reason: "|$search| in |$_code|");
     expect(actualOffset, offset);
   }
diff --git a/analyzer_plugin/test/file_tracker_test.dart b/analyzer_plugin/test/file_tracker_test.dart
index 2ea28f6..a5e4868 100644
--- a/analyzer_plugin/test/file_tracker_test.dart
+++ b/analyzer_plugin/test/file_tracker_test.dart
@@ -1,10 +1,10 @@
 import 'package:angular_analyzer_plugin/src/file_tracker.dart';
-import 'package:analyzer/src/summary/api_signature.dart';
+import 'package:front_end/src/base/api_signature.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 import 'package:unittest/unittest.dart';
 import 'package:typed_mock/typed_mock.dart';
 
-main() {
+void main() {
   defineReflectiveSuite(() {
     defineReflectiveTests(FileTrackerTest);
   });
@@ -20,60 +20,72 @@
     _fileTracker = new FileTracker(_fileHasher);
   }
 
+  // ignore: non_constant_identifier_names
   void test_dartHasTemplate() {
     _fileTracker.setDartHtmlTemplates("foo.dart", ["foo.html"]);
     expect(_fileTracker.getHtmlPathsReferencedByDart("foo.dart"),
         equals(["foo.html"]));
   }
 
+  // ignore: non_constant_identifier_names
   void test_dartHasTemplates() {
     _fileTracker.setDartHtmlTemplates("foo.dart", ["foo.html", "foo_bar.html"]);
     expect(_fileTracker.getHtmlPathsReferencedByDart("foo.dart"),
         equals(["foo.html", "foo_bar.html"]));
   }
 
+  // ignore: non_constant_identifier_names
   void test_templateHasDart() {
     _fileTracker.setDartHtmlTemplates("foo.dart", ["foo.html"]);
     expect(_fileTracker.getDartPathsReferencingHtml("foo.html"),
         equals(["foo.dart"]));
   }
 
+  // ignore: non_constant_identifier_names
   void test_notReferencedDart() {
     expect(_fileTracker.getDartPathsReferencingHtml("foo.html"), equals([]));
   }
 
+  // ignore: non_constant_identifier_names
   void test_notReferencedHtml() {
     expect(_fileTracker.getDartPathsReferencingHtml("foo.dart"), equals([]));
   }
 
+  // ignore: non_constant_identifier_names
   void test_templatesHaveDart() {
-    _fileTracker.setDartHtmlTemplates("foo.dart", ["foo.html"]);
-    _fileTracker.setDartHtmlTemplates("foo_test.dart", ["foo.html"]);
+    _fileTracker
+      ..setDartHtmlTemplates("foo.dart", ["foo.html"])
+      ..setDartHtmlTemplates("foo_test.dart", ["foo.html"]);
     expect(_fileTracker.getDartPathsReferencingHtml("foo.html"),
         equals(["foo.dart", "foo_test.dart"]));
   }
 
+  // ignore: non_constant_identifier_names
   void test_templatesHaveDartRepeated() {
-    _fileTracker.setDartHtmlTemplates("foo.dart", ["foo.html"]);
-    _fileTracker.setDartHtmlTemplates("foo_test.dart", ["foo.html"]);
-    _fileTracker.setDartHtmlTemplates("foo.dart", ["foo.html"]);
+    _fileTracker
+      ..setDartHtmlTemplates("foo.dart", ["foo.html"])
+      ..setDartHtmlTemplates("foo_test.dart", ["foo.html"])
+      ..setDartHtmlTemplates("foo.dart", ["foo.html"]);
     expect(_fileTracker.getDartPathsReferencingHtml("foo.html"),
         equals(["foo.dart", "foo_test.dart"]));
   }
 
+  // ignore: non_constant_identifier_names
   void test_templatesHaveDartRemove() {
-    _fileTracker.setDartHtmlTemplates("foo_test.dart", ["foo.html"]);
-    _fileTracker.setDartHtmlTemplates("foo.dart", ["foo.html"]);
-    _fileTracker.setDartHtmlTemplates("foo_test.dart", []);
+    _fileTracker
+      ..setDartHtmlTemplates("foo_test.dart", ["foo.html"])
+      ..setDartHtmlTemplates("foo.dart", ["foo.html"])
+      ..setDartHtmlTemplates("foo_test.dart", []);
     expect(_fileTracker.getDartPathsReferencingHtml("foo.html"),
         equals(["foo.dart"]));
   }
 
+  // ignore: non_constant_identifier_names
   void test_templatesHaveDartComplex() {
-    _fileTracker.setDartHtmlTemplates("foo.dart", ["foo.html", "foo_b.html"]);
     _fileTracker
-        .setDartHtmlTemplates("foo_test.dart", ["foo.html", "foo_b.html"]);
-    _fileTracker.setDartHtmlTemplates("unrelated.dart", ["unrelated.html"]);
+      ..setDartHtmlTemplates("foo.dart", ["foo.html", "foo_b.html"])
+      ..setDartHtmlTemplates("foo_test.dart", ["foo.html", "foo_b.html"])
+      ..setDartHtmlTemplates("unrelated.dart", ["unrelated.html"]);
     expect(_fileTracker.getDartPathsReferencingHtml("foo.html"),
         equals(["foo.dart", "foo_test.dart"]));
     expect(_fileTracker.getDartPathsReferencingHtml("foo_b.html"),
@@ -100,8 +112,9 @@
     expect(_fileTracker.getDartPathsReferencingHtml("foo_test.html"),
         equals(["foo_test.dart"]));
 
-    _fileTracker.setDartHtmlTemplates("foo.dart", ["foo.html"]);
-    _fileTracker.setDartHtmlTemplates("foo_b.dart", ["foo_b.html"]);
+    _fileTracker
+      ..setDartHtmlTemplates("foo.dart", ["foo.html"])
+      ..setDartHtmlTemplates("foo_b.dart", ["foo_b.html"]);
     expect(_fileTracker.getDartPathsReferencingHtml("foo.html"),
         equals(["foo.dart", "foo_test.dart"]));
     expect(_fileTracker.getDartPathsReferencingHtml("foo_b.html"),
@@ -110,35 +123,43 @@
         equals(["foo_test.dart"]));
   }
 
+  // ignore: non_constant_identifier_names
   void test_htmlHasHtmlEmpty() {
     expect(_fileTracker.getHtmlPathsReferencingHtml("foo.html"), equals([]));
   }
 
+  // ignore: non_constant_identifier_names
   void test_htmlHasHtmlEmptyNoImportedDart() {
     _fileTracker.setDartHtmlTemplates("foo.dart", ["foo.html"]);
     expect(_fileTracker.getHtmlPathsReferencingHtml("foo.html"), equals([]));
   }
 
+  // ignore: non_constant_identifier_names
   void test_htmlHasHtmlEmptyNoHtml() {
-    _fileTracker.setDartHtmlTemplates("foo.dart", []);
-    _fileTracker.setDartImports("foo.dart", ["bar.dart"]);
-    _fileTracker.setDartHtmlTemplates("bar.dart", ["bar.html"]);
+    _fileTracker
+      ..setDartHtmlTemplates("foo.dart", [])
+      ..setDartImports("foo.dart", ["bar.dart"])
+      ..setDartHtmlTemplates("bar.dart", ["bar.html"]);
     expect(_fileTracker.getHtmlPathsReferencingHtml("bar.html"), equals([]));
   }
 
+  // ignore: non_constant_identifier_names
   void test_htmlHasHtml() {
-    _fileTracker.setDartHtmlTemplates("foo.dart", ["foo.html"]);
-    _fileTracker.setDartImports("foo.dart", ["bar.dart"]);
-    _fileTracker.setDartHtmlTemplates("bar.dart", ["bar.html"]);
+    _fileTracker
+      ..setDartHtmlTemplates("foo.dart", ["foo.html"])
+      ..setDartImports("foo.dart", ["bar.dart"])
+      ..setDartHtmlTemplates("bar.dart", ["bar.html"]);
     expect(_fileTracker.getHtmlPathsReferencingHtml("bar.html"),
         equals(["foo.html"]));
   }
 
+  // ignore: non_constant_identifier_names
   void test_htmlHasHtmlMultipleResults() {
-    _fileTracker.setDartHtmlTemplates("foo.dart", ["foo.html", "foo_b.html"]);
-    _fileTracker.setDartImports("foo.dart", ["bar.dart", "baz.dart"]);
-    _fileTracker.setDartHtmlTemplates("bar.dart", ["bar.html"]);
-    _fileTracker.setDartHtmlTemplates("baz.dart", ["baz.html", "baz_b.html"]);
+    _fileTracker
+      ..setDartHtmlTemplates("foo.dart", ["foo.html", "foo_b.html"])
+      ..setDartImports("foo.dart", ["bar.dart", "baz.dart"])
+      ..setDartHtmlTemplates("bar.dart", ["bar.html"])
+      ..setDartHtmlTemplates("baz.dart", ["baz.html", "baz_b.html"]);
     expect(_fileTracker.getHtmlPathsReferencingHtml("bar.html"),
         equals(["foo.html", "foo_b.html"]));
     expect(_fileTracker.getHtmlPathsReferencingHtml("baz.html"),
@@ -147,81 +168,99 @@
         equals(["foo.html", "foo_b.html"]));
   }
 
+  // ignore: non_constant_identifier_names
   void test_htmlHasHtmlButNotGrandchildren() {
-    _fileTracker.setDartHtmlTemplates("foo.dart", ["foo.html"]);
-    _fileTracker.setDartImports("foo.dart", ["child.dart"]);
-    _fileTracker.setDartHtmlTemplates("child.dart", ["child.html"]);
-    _fileTracker.setDartImports("child.dart", ["grandchild.dart"]);
-    _fileTracker.setDartHtmlTemplates("grandchild.dart", ["grandchild.html"]);
+    _fileTracker
+      ..setDartHtmlTemplates("foo.dart", ["foo.html"])
+      ..setDartImports("foo.dart", ["child.dart"])
+      ..setDartHtmlTemplates("child.dart", ["child.html"])
+      ..setDartImports("child.dart", ["grandchild.dart"])
+      ..setDartHtmlTemplates("grandchild.dart", ["grandchild.html"]);
     expect(_fileTracker.getHtmlPathsReferencingHtml("child.html"),
         equals(["foo.html"]));
     expect(_fileTracker.getHtmlPathsReferencingHtml("grandchild.html"),
         equals(["child.html"]));
   }
 
+  // ignore: non_constant_identifier_names
   void test_htmlHasDartEmpty() {
     expect(_fileTracker.getDartPathsAffectedByHtml("foo.html"), equals([]));
   }
 
+  // ignore: non_constant_identifier_names
   void test_htmlHasDartEmptyNoImportedDart() {
     _fileTracker.setDartHtmlTemplates("foo.dart", ["foo.html"]);
     expect(_fileTracker.getDartPathsAffectedByHtml("foo.html"), equals([]));
   }
 
+  // ignore: non_constant_identifier_names
   void test_htmlHasDartEmptyNotDartTemplate() {
-    _fileTracker.setDartImports("foo.dart", ["bar.dart"]);
-    _fileTracker.setDartHtmlTemplates("bar.dart", ["bar.html"]);
+    _fileTracker
+      ..setDartImports("foo.dart", ["bar.dart"])
+      ..setDartHtmlTemplates("bar.dart", ["bar.html"]);
     expect(_fileTracker.getDartPathsAffectedByHtml("bar.html"), equals([]));
   }
 
+  // ignore: non_constant_identifier_names
   void test_htmlHasDart() {
-    _fileTracker.setDartHasTemplate("foo.dart", true);
-    _fileTracker.setDartImports("foo.dart", ["bar.dart"]);
-    _fileTracker.setDartHtmlTemplates("bar.dart", ["bar.html"]);
+    _fileTracker
+      ..setDartHasTemplate("foo.dart", true)
+      ..setDartImports("foo.dart", ["bar.dart"])
+      ..setDartHtmlTemplates("bar.dart", ["bar.html"]);
     expect(_fileTracker.getDartPathsAffectedByHtml("bar.html"),
         equals(["foo.dart"]));
   }
 
+  // ignore: non_constant_identifier_names
   void test_htmlAffectingDartEmpty() {
     expect(_fileTracker.getHtmlPathsAffectingDart("foo.dart"), equals([]));
   }
 
+  // ignore: non_constant_identifier_names
   void test_htmlAffectingDartEmptyNoImportedDart() {
     _fileTracker.setDartHtmlTemplates("foo.dart", ["foo.html"]);
     expect(_fileTracker.getHtmlPathsAffectingDart("foo.dart"), equals([]));
   }
 
+  // ignore: non_constant_identifier_names
   void test_htmlAffectingDartEmptyNotDartTemplate() {
-    _fileTracker.setDartImports("foo.dart", ["bar.dart"]);
-    _fileTracker.setDartHtmlTemplates("bar.dart", ["bar.html"]);
+    _fileTracker
+      ..setDartImports("foo.dart", ["bar.dart"])
+      ..setDartHtmlTemplates("bar.dart", ["bar.html"]);
     expect(_fileTracker.getHtmlPathsAffectingDart("foo.dart"), equals([]));
   }
 
+  // ignore: non_constant_identifier_names
   void test_htmlAffectingDart() {
-    _fileTracker.setDartHasTemplate("foo.dart", true);
-    _fileTracker.setDartImports("foo.dart", ["bar.dart"]);
-    _fileTracker.setDartHtmlTemplates("bar.dart", ["bar.html"]);
+    _fileTracker
+      ..setDartHasTemplate("foo.dart", true)
+      ..setDartImports("foo.dart", ["bar.dart"])
+      ..setDartHtmlTemplates("bar.dart", ["bar.html"]);
     expect(_fileTracker.getHtmlPathsAffectingDart("foo.dart"),
         equals(["bar.html"]));
   }
 
+  // ignore: non_constant_identifier_names
   void test_htmlHasDartNotGrandchildren() {
-    _fileTracker.setDartHasTemplate("foo.dart", true);
-    _fileTracker.setDartImports("foo.dart", ["child.dart"]);
-    _fileTracker.setDartHtmlTemplates("child.dart", ["child.html"]);
-    _fileTracker.setDartImports("child.dart", ["grandchild.dart"]);
-    _fileTracker.setDartHtmlTemplates("grandchild.dart", ["grandchild.html"]);
+    _fileTracker
+      ..setDartHasTemplate("foo.dart", true)
+      ..setDartImports("foo.dart", ["child.dart"])
+      ..setDartHtmlTemplates("child.dart", ["child.html"])
+      ..setDartImports("child.dart", ["grandchild.dart"])
+      ..setDartHtmlTemplates("grandchild.dart", ["grandchild.html"]);
     expect(_fileTracker.getDartPathsAffectedByHtml("child.html"),
         equals(["foo.dart"]));
     expect(
         _fileTracker.getDartPathsAffectedByHtml("grandchild.html"), equals([]));
   }
 
+  // ignore: non_constant_identifier_names
   void test_htmlHasDartMultiple() {
-    _fileTracker.setDartHasTemplate("foo.dart", true);
-    _fileTracker.setDartImports("foo.dart", ["bar.dart", "baz.dart"]);
-    _fileTracker.setDartHtmlTemplates("bar.dart", ["bar.html", "bar_b.html"]);
-    _fileTracker.setDartHtmlTemplates("baz.dart", ["baz.html", "baz_b.html"]);
+    _fileTracker
+      ..setDartHasTemplate("foo.dart", true)
+      ..setDartImports("foo.dart", ["bar.dart", "baz.dart"])
+      ..setDartHtmlTemplates("bar.dart", ["bar.html", "bar_b.html"])
+      ..setDartHtmlTemplates("baz.dart", ["baz.html", "baz_b.html"]);
     expect(_fileTracker.getDartPathsAffectedByHtml("bar.html"),
         equals(["foo.dart"]));
     expect(_fileTracker.getDartPathsAffectedByHtml("bar_b.html"),
@@ -232,42 +271,40 @@
         equals(["foo.dart"]));
   }
 
+  // ignore: non_constant_identifier_names
   void test_htmlHasDartGetSignature() {
-    _fileTracker.setDartHasTemplate("foo.dart", true);
-    _fileTracker.setDartImports("foo.dart", ["bar.dart"]);
-    _fileTracker.setDartHtmlTemplates("bar.dart", ["bar.html"]);
+    _fileTracker
+      ..setDartHasTemplate("foo.dart", true)
+      ..setDartImports("foo.dart", ["bar.dart"])
+      ..setDartHtmlTemplates("bar.dart", ["bar.html"]);
 
-    ApiSignature fooDartElementSignature = new ApiSignature();
-    fooDartElementSignature.addInt(1);
-    ApiSignature barHtmlSignature = new ApiSignature();
-    barHtmlSignature.addInt(2);
+    final fooDartElementSignature = new ApiSignature()..addInt(1);
+    final barHtmlSignature = new ApiSignature()..addInt(2);
 
     when(_fileHasher.getContentHash("bar.html")).thenReturn(barHtmlSignature);
     when(_fileHasher.getUnitElementHash("foo.dart"))
         .thenReturn(fooDartElementSignature);
 
-    ApiSignature expectedSignature = new ApiSignature();
-    expectedSignature.addBytes(fooDartElementSignature.toByteList());
-    expectedSignature.addBytes(barHtmlSignature.toByteList());
+    final expectedSignature = new ApiSignature()
+      ..addBytes(fooDartElementSignature.toByteList())
+      ..addBytes(barHtmlSignature.toByteList());
 
     expect(_fileTracker.getDartSignature("foo.dart").toHex(),
         equals(expectedSignature.toHex()));
   }
 
+  // ignore: non_constant_identifier_names
   void test_htmlHasHtmlGetSignature() {
-    _fileTracker.setDartHtmlTemplates("foo.dart", ["foo.html"]);
-    _fileTracker.setDartHtmlTemplates("foo_test.dart", ["foo.html"]);
-    _fileTracker.setDartImports("foo.dart", ["bar.dart"]);
-    _fileTracker.setDartHtmlTemplates("bar.dart", ["bar.html"]);
+    _fileTracker
+      ..setDartHtmlTemplates("foo.dart", ["foo.html"])
+      ..setDartHtmlTemplates("foo_test.dart", ["foo.html"])
+      ..setDartImports("foo.dart", ["bar.dart"])
+      ..setDartHtmlTemplates("bar.dart", ["bar.html"]);
 
-    ApiSignature fooHtmlSignature = new ApiSignature();
-    fooHtmlSignature.addInt(1);
-    ApiSignature fooDartElementSignature = new ApiSignature();
-    fooDartElementSignature.addInt(2);
-    ApiSignature fooTestDartElementSignature = new ApiSignature();
-    fooDartElementSignature.addInt(3);
-    ApiSignature barHtmlSignature = new ApiSignature();
-    barHtmlSignature.addInt(4);
+    final fooHtmlSignature = new ApiSignature()..addInt(1);
+    final fooDartElementSignature = new ApiSignature()..addInt(2);
+    final fooTestDartElementSignature = new ApiSignature()..addInt(3);
+    final barHtmlSignature = new ApiSignature()..addInt(4);
 
     when(_fileHasher.getContentHash("foo.html")).thenReturn(fooHtmlSignature);
     when(_fileHasher.getContentHash("bar.html")).thenReturn(barHtmlSignature);
@@ -276,15 +313,31 @@
     when(_fileHasher.getUnitElementHash("foo_test.dart"))
         .thenReturn(fooTestDartElementSignature);
 
-    ApiSignature expectedSignature = new ApiSignature();
-    expectedSignature.addBytes(fooHtmlSignature.toByteList());
-    expectedSignature.addBytes(fooDartElementSignature.toByteList());
-    expectedSignature.addBytes(barHtmlSignature.toByteList());
-    expectedSignature.addBytes(fooTestDartElementSignature.toByteList());
+    final expectedSignature = new ApiSignature()
+      ..addBytes(fooHtmlSignature.toByteList())
+      ..addBytes(fooDartElementSignature.toByteList())
+      ..addBytes(barHtmlSignature.toByteList())
+      ..addBytes(fooTestDartElementSignature.toByteList());
 
     expect(_fileTracker.getHtmlSignature("foo.html").toHex(),
         equals(expectedSignature.toHex()));
   }
+
+  // ignore: non_constant_identifier_names
+  void test_minimallyRehashesHtml() {
+    final fooHtmlSignature = new ApiSignature()..addInt(1);
+    when(_fileHasher.getContentHash("foo.html")).thenReturn(fooHtmlSignature);
+
+    for (var i = 0; i < 3; ++i) {
+      _fileTracker.getHtmlContentHash("foo.html");
+      verify(_fileHasher.getContentHash("foo.html")).once();
+    }
+
+    for (var i = 0; i < 3; ++i) {
+      _fileTracker.rehashHtmlContents("foo.html");
+      verify(_fileHasher.getContentHash("foo.html")).times(2);
+    }
+  }
 }
 
 class _FileHasherMock extends TypedMock implements FileHasher {}
diff --git a/analyzer_plugin/test/fuzz_test.dart b/analyzer_plugin/test/fuzz_test.dart
index af1a9e8..7e659bd 100644
--- a/analyzer_plugin/test/fuzz_test.dart
+++ b/analyzer_plugin/test/fuzz_test.dart
@@ -1,20 +1,25 @@
-import 'dart:math';
 import 'dart:async';
+import 'dart:math';
 
 import 'package:angular_analyzer_plugin/src/model.dart';
 import 'package:front_end/src/scanner/token.dart';
-import 'package:unittest/unittest.dart';
-import 'package:test_reflective_loader/test_reflective_loader.dart';
+//import 'package:unittest/unittest.dart';
+import 'package:test/test.dart';
+//import 'package:test_reflective_loader/test_reflective_loader.dart';
 
 import 'abstract_angular.dart';
 
-main() {
-  defineReflectiveSuite(() {
-    defineReflectiveTests(FuzzTest);
-  });
+//main() {
+//  defineReflectiveSuite(() {
+//    defineReflectiveTests(FuzzTest);
+//  });
+//}
+
+void main() {
+  new FuzzTest().test_fuzz_continually();
 }
 
-@reflectiveTest
+//@reflectiveTest
 class FuzzTest extends AbstractAngularTest {
   // collected with
   // `find ../deps -name '*.dart' -exec cat {} \; | shuf -n 500 | sort`
@@ -355,7 +360,7 @@
 ''';
 
   static const String baseDart = r'''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 
 @Component(
   selector: 'my-aaa',
@@ -377,6 +382,9 @@
   EventEmitter<String> resetEvent;
   @Output() EventEmitter<int> incremented;
 
+  @ContentChild(CounterComponent)
+  CounterComponent recursedComponent;
+
   void reset() {}
   void increment() {}
 }
@@ -401,7 +409,9 @@
     [maxCount]='4'
     (reset)=''
     (click)='h1.hidden = !h1.hidden; counter.reset()'
-    (incremented)='items.add($event.toString())'></my-counter>
+    (incremented)='items.add($event.toString())'>
+    <my-counter></my-counter>
+  </my-counter>
 </div>
 ''';
 
@@ -410,8 +420,9 @@
 
   Random random = new Random();
 
+  // ignore: non_constant_identifier_names
   Future test_fuzz_continually() async {
-    List<FuzzModification> fuzzOptions = [
+    final fuzzOptions = <FuzzModification>[
       fuzz_removeChar,
       fuzz_truncate,
       fuzz_addChar,
@@ -427,7 +438,7 @@
 
     const iters = 1000000;
     for (var i = 0; i < iters; ++i) {
-      var transforms = random.nextInt(20) + 1;
+      final transforms = random.nextInt(20) + 1;
       print("Fuzz $i: $transforms transforms");
       dart = baseDart;
       html = baseHtml;
@@ -452,7 +463,7 @@
   }
 
   int randomPos(String s) {
-    if (s.length == 0) {
+    if (s.isEmpty) {
       return 0;
     }
     // range is between 1 and n, but a random pos is 0 to n
@@ -460,7 +471,7 @@
   }
 
   int randomIndex(List s) {
-    if (s.length == 0) {
+    if (s.isEmpty) {
       return null;
     } else if (s.length == 1) {
       return 0;
@@ -469,14 +480,16 @@
     return random.nextInt(s.length - 1);
   }
 
+  // ignore: non_constant_identifier_names
   String fuzz_removeChar(String input) {
-    int charpos = randomIndex(input.codeUnits);
+    final charpos = randomIndex(input.codeUnits);
     if (charpos == null) {
       return input;
     }
     return input.replaceRange(charpos, charpos + 1, '');
   }
 
+  // ignore: non_constant_identifier_names
   String fuzz_addChar(String input) {
     String newchar;
     if (input.isEmpty) {
@@ -484,109 +497,119 @@
     } else {
       newchar = input[randomIndex(input.codeUnits)];
     }
-    int charpos = randomPos(input);
+    final charpos = randomPos(input);
     return input.replaceRange(charpos, charpos, newchar);
   }
 
+  // ignore: non_constant_identifier_names
   String fuzz_truncate(String input) {
-    int charpos = randomPos(input);
+    final charpos = randomPos(input);
     if (charpos == 0) {
       return '';
     }
     return input.substring(0, charpos);
   }
 
+  // ignore: non_constant_identifier_names
   String fuzz_shuffleLines(String input) {
-    List<String> lines = input.split('\n');
-    lines.shuffle(random);
+    final lines = input.split('\n')..shuffle(random);
     return lines.join('\n');
   }
 
+  // ignore: non_constant_identifier_names
   String fuzz_dropLine(String input) {
-    List<String> lines = input.split('\n');
-    lines.removeAt(randomIndex(lines));
+    final lines = input.split('\n');
+    lines.removeAt(randomIndex(lines)); // ignore: cascade_invocations
     return lines.join('\n');
   }
 
+  // ignore: non_constant_identifier_names
   String fuzz_joinLine(String input) {
-    List<String> lines = input.split('\n');
+    final lines = input.split('\n');
     if (lines.length == 1) {
       return input;
     }
-    int which = randomIndex(lines);
-    String toPrepend = lines[which];
+    final which = randomIndex(lines);
+    final toPrepend = lines[which];
     lines.removeAt(which);
+    // ignore: prefer_interpolation_to_compose_strings
     lines[which] = toPrepend + lines[which];
     return lines.join('\n');
   }
 
+  // ignore: non_constant_identifier_names
   String fuzz_copyLine(String input) {
-    List<String> lines = input.split('\n');
+    final lines = input.split('\n');
     if (lines.length == 1) {
       return input;
     }
-    int which = randomIndex(lines);
-    String toPrepend = lines[which];
+    final which = randomIndex(lines);
+    final toPrepend = lines[which];
     lines.removeAt(which);
+    // ignore: prefer_interpolation_to_compose_strings
     lines[which] = toPrepend + lines[which];
     return lines.join('\n');
   }
 
+  // ignore: non_constant_identifier_names
   String fuzz_copyChunk(String input) {
     if (input.isEmpty) {
       return input;
     }
 
-    String chunk = fuzz_truncate(input.substring(randomIndex(input.codeUnits)));
-    int charpos = randomPos(input);
+    final chunk = fuzz_truncate(input.substring(randomIndex(input.codeUnits)));
+    final charpos = randomPos(input);
     return input.replaceRange(charpos, charpos, chunk);
   }
 
+  // ignore: non_constant_identifier_names
   String fuzz_addKeyword(String input) {
-    Keyword token = Keyword.values[randomIndex(Keyword.values)];
+    final token = Keyword.values[randomIndex(Keyword.values)];
     if (input.isEmpty) {
       return input;
     }
 
-    int charpos = randomPos(input);
-    return input.replaceRange(charpos, charpos, token.syntax);
+    final charpos = randomPos(input);
+    return input.replaceRange(charpos, charpos, token.lexeme);
   }
 
+  // ignore: non_constant_identifier_names
   String fuzz_addDartChunk(String input) {
-    String chunk = fuzz_truncate(dartSnippets);
+    var chunk = fuzz_truncate(dartSnippets);
     if (chunk.length > 80) {
       chunk = chunk.substring(0, random.nextInt(80));
-    } else if (chunk.length == 0) {
+    } else if (chunk.isEmpty) {
       return input;
     } else {
       chunk = chunk.substring(randomPos(chunk));
     }
-    int charpos = randomPos(input);
+    final charpos = randomPos(input);
     return input.replaceRange(charpos, charpos, chunk);
   }
 
+  // ignore: non_constant_identifier_names
   String fuzz_addHtmlChunk(String input) {
-    String chunk = fuzz_truncate(htmlSnippets);
+    var chunk = fuzz_truncate(htmlSnippets);
     if (chunk.length > 80) {
       chunk = chunk.substring(0, random.nextInt(80));
-    } else if (chunk.length == 0) {
+    } else if (chunk.isEmpty) {
       return input;
     } else {
       chunk = chunk.substring(randomPos(chunk));
     }
-    int charpos = randomPos(input);
+    final charpos = randomPos(input);
     return input.replaceRange(charpos, charpos, chunk);
   }
 
   Future checkNoCrash(String dart, String html) async {
     newSource('/test.dart', dart);
     newSource('/test.html', html);
-    String reason =
+    final reason =
         '<<==DART CODE==>>\n$dart\n<<==HTML CODE==>>\n$html\n<<==DONE==>>';
     try {
       final result = await angularDriver.resolveDart('/test.dart');
-      if (result.directives.length > 0) {
-        AbstractDirective directive = result.directives.first;
+      if (result.directives.isNotEmpty) {
+        final directive = result.directives.first;
         if (directive is Component &&
             directive.view?.templateUriSource?.fullName == '/test.html') {
           try {
@@ -601,25 +624,25 @@
     }
   }
 
-  /**
-   * More or less expect(), but without failing the test. Returns a [Future] so
-   * that you can chain things to do when this succeeds or fails.
-   */
+  /// More or less expect(), but without failing the test. Returns a [Future] so
+  /// that you can chain things to do when this succeeds or fails.
   Future check(Object actual, Matcher matcher, {String reason}) {
-    var matchState = {};
+    final matchState = {};
 
     print('failed');
-    var description = new StringDescription();
+    final description = new StringDescription();
     description.add('Expected: ').addDescriptionOf(matcher).add('\n');
     description.add('  Actual: ').addDescriptionOf(actual).add('\n');
 
-    var mismatchDescription = new StringDescription();
+    final mismatchDescription = new StringDescription();
     matcher.describeMismatch(actual, mismatchDescription, matchState, false);
 
     if (mismatchDescription.length > 0) {
-      description.add('   Which: ${mismatchDescription}\n');
+      description.add('   Which: $mismatchDescription\n');
     }
-    if (reason != null) description.add(reason).add('\n');
+    if (reason != null) {
+      description.add(reason).add('\n');
+    }
 
     print(description.toString());
     return new Future.error(description);
diff --git a/analyzer_plugin/test/mock_sdk.dart b/analyzer_plugin/test/mock_sdk.dart
index fe66748..1536484 100644
--- a/analyzer_plugin/test/mock_sdk.dart
+++ b/analyzer_plugin/test/mock_sdk.dart
@@ -1,5 +1,3 @@
-library test.src.mock_sdk;
-
 import 'package:analyzer/file_system/file_system.dart' as resource;
 import 'package:analyzer/file_system/memory_file_system.dart' as resource;
 import 'package:analyzer/src/context/cache.dart';
@@ -10,8 +8,8 @@
 import 'package:analyzer/src/summary/idl.dart' show PackageBundle;
 import 'package:analyzer/src/summary/summary_file_builder.dart';
 
-const String librariesContent = r'''
-const Map<String, LibraryInfo> libraries = const {
+const librariesContent = r'''
+const libraries = const <String, LibraryInfo>{
   "async": const LibraryInfo("async/async.dart"),
   "collection": const LibraryInfo("collection/collection.dart"),
   "convert": const LibraryInfo("convert/convert.dart"),
@@ -24,9 +22,9 @@
 };
 ''';
 
-const String sdkRoot = '/sdk';
+const sdkRoot = '/sdk';
 
-const _MockSdkLibrary _LIB_ASYNC = const _MockSdkLibrary(
+const _LIB_ASYNC = const _MockSdkLibrary(
     'dart:async',
     '$sdkRoot/lib/async/async.dart',
     '''
@@ -85,7 +83,7 @@
 '''
     });
 
-const _MockSdkLibrary _LIB_COLLECTION = const _MockSdkLibrary(
+const _LIB_COLLECTION = const _MockSdkLibrary(
     'dart:collection',
     '$sdkRoot/lib/collection/collection.dart',
     '''
@@ -94,7 +92,7 @@
 abstract class HashMap<K, V> implements Map<K, V> {}
 ''');
 
-const _MockSdkLibrary _LIB_CONVERT = const _MockSdkLibrary(
+const _LIB_CONVERT = const _MockSdkLibrary(
     'dart:convert',
     '$sdkRoot/lib/convert/convert.dart',
     '''
@@ -106,7 +104,7 @@
 class JsonDecoder extends Converter<String, Object> {}
 ''');
 
-const _MockSdkLibrary _LIB_CORE = const _MockSdkLibrary(
+const _LIB_CORE = const _MockSdkLibrary(
     'dart:core',
     '$sdkRoot/lib/core/core.dart',
     '''
@@ -306,7 +304,7 @@
 const Object override = const _Override();
 ''');
 
-const _MockSdkLibrary _LIB_FOREIGN_HELPER = const _MockSdkLibrary(
+const _LIB_FOREIGN_HELPER = const _MockSdkLibrary(
     'dart:_foreign_helper',
     '$sdkRoot/lib/_foreign_helper/_foreign_helper.dart',
     '''
@@ -317,7 +315,7 @@
 {}
 ''');
 
-const _MockSdkLibrary _LIB_HTML_DART2JS = const _MockSdkLibrary(
+const _LIB_HTML_DART2JS = const _MockSdkLibrary(
     'dart:html',
     '$sdkRoot/lib/html/dartium/html_dartium.dart',
     '''
@@ -325,7 +323,7 @@
 class HtmlElement {}
 ''');
 
-const _MockSdkLibrary _LIB_HTML_DARTIUM = const _MockSdkLibrary(
+const _LIB_HTML_DARTIUM = const _MockSdkLibrary(
     'dart:html',
     '$sdkRoot/lib/html/dartium/html_dartium.dart',
     '''
@@ -440,14 +438,14 @@
 }
 ''');
 
-const _MockSdkLibrary _LIB_INTERCEPTORS = const _MockSdkLibrary(
+const _LIB_INTERCEPTORS = const _MockSdkLibrary(
     'dart:_interceptors',
     '$sdkRoot/lib/_internal/js_runtime/lib/interceptors.dart',
     '''
 library dart._interceptors;
 ''');
 
-const _MockSdkLibrary _LIB_MATH = const _MockSdkLibrary(
+const _LIB_MATH = const _MockSdkLibrary(
     'dart:math',
     '$sdkRoot/lib/math/math.dart',
     '''
@@ -470,7 +468,7 @@
 }
 ''');
 
-const List<SdkLibrary> _LIBRARIES = const [
+const _LIBRARIES = const <SdkLibrary>[
   _LIB_CORE,
   _LIB_ASYNC,
   _LIB_COLLECTION,
@@ -504,17 +502,13 @@
 
   final Map<String, String> uriMap;
 
-  /**
-   * The [AnalysisContextImpl] which is used for all of the sources.
-   */
+  /// The [AnalysisContextImpl] which is used for all of the sources.
   AnalysisContextImpl _analysisContext;
 
   @override
   final List<SdkLibrary> sdkLibraries;
 
-  /**
-   * The cached linked bundle of the SDK.
-   */
+  /// The cached linked bundle of the SDK.
   PackageBundle _bundle;
 
   MockSdk(
@@ -526,7 +520,7 @@
         uriMap = dartAsync ? FULL_URI_MAP : NO_ASYNC_URI_MAP {
     for (_MockSdkLibrary library in sdkLibraries) {
       provider.newFile(provider.convertPath(library.path), library.content);
-      library.parts.forEach((String path, String content) {
+      library.parts.forEach((path, content) {
         provider.newFile(provider.convertPath(path), content);
       });
     }
@@ -535,11 +529,12 @@
             '$sdkRoot/lib/_internal/sdk_library_metadata/lib/libraries.dart'),
         librariesContent);
     if (generateSummaryFiles) {
-      List<int> bytes = _computeLinkedBundleBytes();
-      provider.newFileWithBytes(
-          provider.convertPath('/lib/_internal/spec.sum'), bytes);
-      provider.newFileWithBytes(
-          provider.convertPath('/lib/_internal/strong.sum'), bytes);
+      final bytes = _computeLinkedBundleBytes();
+      provider
+        ..newFileWithBytes(
+            provider.convertPath('/lib/_internal/spec.sum'), bytes)
+        ..newFileWithBytes(
+            provider.convertPath('/lib/_internal/strong.sum'), bytes);
     }
   }
 
@@ -547,7 +542,7 @@
   AnalysisContextImpl get context {
     if (_analysisContext == null) {
       _analysisContext = new _SdkAnalysisContext(this);
-      SourceFactory factory = new SourceFactory([new DartUriResolver(this)]);
+      final factory = new SourceFactory([new DartUriResolver(this)]);
       _analysisContext.sourceFactory = factory;
     }
     return _analysisContext;
@@ -558,33 +553,34 @@
 
   @override
   List<String> get uris =>
-      sdkLibraries.map((SdkLibrary library) => library.shortName).toList();
+      sdkLibraries.map((library) => library.shortName).toList();
 
   @override
   Source fromFileUri(Uri uri) {
-    String filePath = provider.pathContext.fromUri(uri);
+    final filePath = provider.pathContext.fromUri(uri);
     if (!filePath.startsWith(provider.convertPath('$sdkRoot/lib/'))) {
       return null;
     }
-    for (SdkLibrary library in sdkLibraries) {
-      String libraryPath = provider.convertPath(library.path);
+    for (final library in sdkLibraries) {
+      final libraryPath = provider.convertPath(library.path);
       if (filePath == libraryPath) {
         try {
-          resource.File file = provider.getResource(filePath);
-          Uri dartUri = Uri.parse(library.shortName);
+          final resource.File file = provider.getResource(filePath);
+          final dartUri = Uri.parse(library.shortName);
           return file.createSource(dartUri);
         } catch (exception) {
           return null;
         }
       }
-      String libraryRootPath = provider.pathContext.dirname(libraryPath) +
+      // ignore: prefer_interpolation_to_compose_strings
+      final libraryRootPath = provider.pathContext.dirname(libraryPath) +
           provider.pathContext.separator;
       if (filePath.startsWith(libraryRootPath)) {
-        String pathInLibrary = filePath.substring(libraryRootPath.length);
-        String uriStr = '${library.shortName}/$pathInLibrary';
+        final pathInLibrary = filePath.substring(libraryRootPath.length);
+        final uriStr = '${library.shortName}/$pathInLibrary';
         try {
-          resource.File file = provider.getResource(filePath);
-          Uri dartUri = Uri.parse(uriStr);
+          final resource.File file = provider.getResource(filePath);
+          final dartUri = Uri.parse(uriStr);
           return file.createSource(dartUri);
         } catch (exception) {
           return null;
@@ -597,7 +593,7 @@
   @override
   PackageBundle getLinkedBundle() {
     if (_bundle == null) {
-      resource.File summaryFile =
+      final summaryFile =
           provider.getFile(provider.convertPath('/lib/_internal/spec.sum'));
       List<int> bytes;
       if (summaryFile.exists) {
@@ -612,7 +608,7 @@
 
   @override
   SdkLibrary getSdkLibrary(String dartUri) {
-    for (SdkLibrary library in _LIBRARIES) {
+    for (final library in _LIBRARIES) {
       if (library.shortName == dartUri) {
         return library;
       }
@@ -622,10 +618,11 @@
 
   @override
   Source mapDartUri(String dartUri) {
-    String path = uriMap[dartUri];
+    final path = uriMap[dartUri];
     if (path != null) {
-      resource.File file = provider.getResource(provider.convertPath(path));
-      Uri uri = new Uri(scheme: 'dart', path: dartUri.substring(5));
+      final resource.File file =
+          provider.getResource(provider.convertPath(path));
+      final uri = new Uri(scheme: 'dart', path: dartUri.substring(5));
       return file.createSource(uri);
     }
     // If we reach here then we tried to use a dartUri that's not in the
@@ -633,27 +630,22 @@
     return null;
   }
 
-  /**
-   * This method is used to apply patches to [MockSdk].  It may be called only
-   * before analysis, i.e. before the analysis context was created.
-   */
+  /// This method is used to apply patches to [MockSdk].  It may be called only
+  /// before analysis, i.e. before the analysis context was created.
   void updateUriFile(String uri, String updateContent(String content)) {
     assert(_analysisContext == null);
-    String path = FULL_URI_MAP[uri];
+    var path = FULL_URI_MAP[uri];
     assert(path != null);
     path = provider.convertPath(path);
-    String content = provider.getFile(path).readAsStringSync();
-    String newContent = updateContent(content);
+    final content = provider.getFile(path).readAsStringSync();
+    final newContent = updateContent(content);
     provider.updateFile(path, newContent);
   }
 
-  /**
-   * Compute the bytes of the linked bundle associated with this SDK.
-   */
+  /// Compute the bytes of the linked bundle associated with this SDK.
   List<int> _computeLinkedBundleBytes() {
-    List<Source> librarySources = sdkLibraries
-        .map((SdkLibrary library) => mapDartUri(library.shortName))
-        .toList();
+    final librarySources =
+        sdkLibraries.map((library) => mapDartUri(library.shortName)).toList();
     return new SummaryBuilder(
             librarySources, context, context.analysisOptions.strongMode)
         .build();
@@ -661,7 +653,9 @@
 }
 
 class _MockSdkLibrary implements SdkLibrary {
+  @override
   final String shortName;
+  @override
   final String path;
   final String content;
   final Map<String, String> parts;
@@ -691,9 +685,7 @@
   bool get isVmLibrary => throw new UnimplementedError();
 }
 
-/**
- * An [AnalysisContextImpl] that only contains sources for a Dart SDK.
- */
+/// An [AnalysisContextImpl] that only contains sources for a Dart SDK.
 class _SdkAnalysisContext extends AnalysisContextImpl {
   final DartSdk sdk;
 
diff --git a/analyzer_plugin/test/offsetting_constant_value_visitor_test.dart b/analyzer_plugin/test/offsetting_constant_value_visitor_test.dart
index a143b0d..e093f41 100644
--- a/analyzer_plugin/test/offsetting_constant_value_visitor_test.dart
+++ b/analyzer_plugin/test/offsetting_constant_value_visitor_test.dart
@@ -8,7 +8,7 @@
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 import 'package:unittest/unittest.dart';
 
-main() {
+void main() {
   defineReflectiveSuite(() {
     defineReflectiveTests(OffsettingConstantValueVisitorTest);
   });
@@ -16,6 +16,7 @@
 
 @reflectiveTest
 class OffsettingConstantValueVisitorTest {
+  // ignore: non_constant_identifier_names
   void test_simpleString() {
     assertCaretOffsetIsPreserved("'my template^'");
     assertCaretOffsetIsPreserved("r'my template^'");
@@ -25,6 +26,7 @@
     assertCaretOffsetIsPreserved("r'''my template^'''");
   }
 
+  // ignore: non_constant_identifier_names
   void test_parenthesizedString() {
     assertCaretOffsetIsPreserved("('my template^')");
     assertCaretOffsetIsPreserved("( 'my template^')");
@@ -32,6 +34,7 @@
     assertCaretOffsetIsPreserved("(\n'my template^')");
   }
 
+  // ignore: non_constant_identifier_names
   void test_adjacentStrings() {
     assertCaretOffsetIsPreserved("'my template^' 'which continues'");
     assertCaretOffsetIsPreserved("'my template' 'which continues ^'");
@@ -43,6 +46,7 @@
         "'my template'\n\n       'which continues' ' and continues ^'");
   }
 
+  // ignore: non_constant_identifier_names
   void test_concatenatedStrings() {
     assertCaretOffsetIsPreserved("'my template^' + 'which continues'");
     assertCaretOffsetIsPreserved("'my template' + 'which continues ^'");
@@ -54,6 +58,7 @@
         "'my template' +\n\n       'which continues' + ' and continues ^'");
   }
 
+  // ignore: non_constant_identifier_names
   void test_concatenatedAfterParenthesis() {
     assertCaretOffsetIsPreserved("('my template^') + 'which continues'");
     assertCaretOffsetIsPreserved("('my template') + 'which continues^'");
@@ -61,54 +66,62 @@
     assertCaretOffsetIsPreserved("('my template'\n) + 'which continues^'");
   }
 
+  // ignore: non_constant_identifier_names
   void test_computedStringsLookRight() {
-    Expression expression =
+    final expression =
         _parseDartExpression("('my template'\n) + 'which continues^'");
-    Object value = expression.accept(new OffsettingConstantEvaluator());
+    final value = expression.accept(new OffsettingConstantEvaluator());
     expect(value, equals("  my template       which continues^ "));
   }
 
+  // ignore: non_constant_identifier_names
   void test_notStringComputation() {
-    Expression expression = _parseDartExpression("1 + 2");
-    Object value = expression.accept(new OffsettingConstantEvaluator());
+    final expression = _parseDartExpression("1 + 2");
+    final value = expression.accept(new OffsettingConstantEvaluator());
     expect(value, equals(3));
   }
 
+  // ignore: non_constant_identifier_names
   void test_error() {
-    Expression expression = _parseDartExpression("1 + 'hello'");
-    Object value = expression.accept(new OffsettingConstantEvaluator());
+    final expression = _parseDartExpression("1 + 'hello'");
+    final value = expression.accept(new OffsettingConstantEvaluator());
     expect(value, equals(utils.ConstantEvaluator.NOT_A_CONSTANT));
   }
 
+  // ignore: non_constant_identifier_names
   void test_notOffsettableInterp() {
     assertNotOffsettable(r"'hello $world'", at: 'world');
   }
 
+  // ignore: non_constant_identifier_names
   void test_notOffsettableInterpExpr() {
     assertNotOffsettable(r"'hello ${world}'", at: 'world');
   }
 
+  // ignore: non_constant_identifier_names
   void test_notOffsettableGetter() {
     assertNotOffsettable(r"'hello' + world ", at: 'world');
   }
 
+  // ignore: non_constant_identifier_names
   void test_notOffsettableMethod() {
     assertNotOffsettable(r"'hello' + method() ", at: 'method()');
   }
 
+  // ignore: non_constant_identifier_names
   void test_notOffsettablePrefixedIdent() {
     assertNotOffsettable(r"'hello' + prefixed.identifier ",
         at: 'prefixed.identifier');
   }
 
   void assertNotOffsettable(String code, {String at}) {
-    Expression expression = _parseDartExpression(code);
-    int pos = code.indexOf(at);
-    int length = at.length;
+    final expression = _parseDartExpression(code);
+    final pos = code.indexOf(at);
+    final length = at.length;
     expect(pos, greaterThan(-1),
         reason: "```$code```` doesn't contain ```$at```");
 
-    OffsettingConstantEvaluator evaluator = new OffsettingConstantEvaluator();
+    final evaluator = new OffsettingConstantEvaluator();
     expression.accept(evaluator);
     expect(evaluator.offsetsAreValid, isFalse);
     expect(evaluator.lastUnoffsettableNode, isNotNull);
@@ -119,13 +132,13 @@
   }
 
   void assertCaretOffsetIsPreserved(String code) {
-    int pos = code.indexOf('^');
+    final pos = code.indexOf('^');
     expect(pos, greaterThan(-1), reason: 'the code should contain a caret');
 
-    Expression expression = _parseDartExpression(code);
+    final expression = _parseDartExpression(code);
 
-    OffsettingConstantEvaluator evaluator = new OffsettingConstantEvaluator();
-    Object value = expression.accept(evaluator);
+    final evaluator = new OffsettingConstantEvaluator();
+    final value = expression.accept(evaluator);
 
     if (value is String) {
       expect(value.indexOf('^'), equals(pos),
@@ -136,14 +149,14 @@
   }
 
   Token _scanDartCode(String code) {
-    CharSequenceReader reader = new CharSequenceReader(code);
-    Scanner scanner = new Scanner(null, reader, null);
+    final reader = new CharSequenceReader(code);
+    final scanner = new Scanner(null, reader, null);
     return scanner.tokenize();
   }
 
   Expression _parseDartExpression(String code) {
-    Token token = _scanDartCode(code);
-    Parser parser = new Parser(null, null);
+    final token = _scanDartCode(code);
+    final parser = new Parser(null, null);
     return parser.parseExpression(token);
   }
 }
diff --git a/analyzer_plugin/test/resolver_test.dart b/analyzer_plugin/test/resolver_test.dart
index f8087aa..a9f49db 100644
--- a/analyzer_plugin/test/resolver_test.dart
+++ b/analyzer_plugin/test/resolver_test.dart
@@ -17,7 +17,7 @@
 import 'abstract_angular.dart';
 import 'element_assert.dart';
 
-main() {
+void main() {
   defineReflectiveSuite(() {
     defineReflectiveTests(TemplateResolverTest);
   });
@@ -25,8 +25,8 @@
 
 void assertPropertyElement(AngularElement element,
     {nameMatcher, sourceMatcher}) {
-  expect(element, new isInstanceOf<InputElement>());
-  InputElement inputElement = element;
+  expect(element, const isInstanceOf<InputElement>());
+  final inputElement = element;
   if (nameMatcher != null) {
     expect(inputElement.name, nameMatcher);
   }
@@ -47,6 +47,7 @@
   Template template;
   List<ResolvedRange> ranges;
 
+  // ignore: non_constant_identifier_names
   Future test_attribute_mixedCase() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
@@ -61,6 +62,7 @@
     expect(ranges, hasLength(0));
   }
 
+  // ignore: non_constant_identifier_names
   Future test_attributeInterpolation() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
@@ -79,6 +81,7 @@
     _assertElement('bbb}}').dart.getter.at('bbb; // 2');
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_eventBinding() async {
     _addDartSource(r'''
 import 'dart:html';
@@ -98,7 +101,7 @@
     _assertElement('handleClick').dart.method.at('handleClick(MouseEvent');
 
     errorListener.assertNoErrors();
-    ElementSearch search = new ElementSearch((e) => e.localName == "div");
+    final search = new ElementSearch((e) => e.localName == "div");
     template.ast.accept(search);
 
     expect(search.element, isNotNull);
@@ -106,6 +109,7 @@
     expect(search.element.boundStandardOutputs.first.boundOutput.name, 'click');
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_nativeEventBindingOnComponent() async {
     _addDartSource(r'''
 import 'dart:html';
@@ -128,6 +132,7 @@
     _assertElement('click').output.inCoreHtml;
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_eventBinding_on() async {
     _addDartSource(r'''
 import 'dart:html';
@@ -147,6 +152,7 @@
     _assertElement('handleClick()').dart.method.at('handleClick(MouseEvent');
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_inputBinding_valid() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel',
@@ -165,16 +171,17 @@
     await _resolveSingleTemplate(dartSource);
 
     errorListener.assertNoErrors();
-    ElementSearch search = new ElementSearch((e) => e.localName == "span");
+    final search = new ElementSearch((e) => e.localName == "span");
     template.ast.accept(search);
 
     expect(search.element, isNotNull);
     expect(search.element.boundDirectives, hasLength(1));
-    DirectiveBinding boundDirective = search.element.boundDirectives.first;
+    final boundDirective = search.element.boundDirectives.first;
     expect(boundDirective.inputBindings, hasLength(1));
     expect(boundDirective.inputBindings.first.boundInput.name, 'title');
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_nativeGlobalAttrBindingOnComponent() async {
     _addDartSource(r'''
 import 'dart:html';
@@ -197,6 +204,7 @@
     _assertElement('hidden').input.inCoreHtml;
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_inputBinding_asString() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel',
@@ -208,7 +216,7 @@
   @Input() String title;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <title-comp title='anything can go here' id="some id"></title-comp>
 """;
     _addHtmlSource(code);
@@ -218,6 +226,7 @@
     _assertElement('id=').input.inCoreHtml;
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_inputBinding_asString_fromDynamic() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel',
@@ -234,7 +243,8 @@
   bool get title => _title;
 }
 ''');
-    var code = r"""
+
+    final code = r"""
 <title-comp title='anything can go here'></title-comp>
 """;
     _addHtmlSource(code);
@@ -243,6 +253,7 @@
     _assertElement('title=').input.inFileName('/test_panel.dart').at('title(');
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_inputBinding_typeError() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel',
@@ -255,7 +266,7 @@
   @Input() int title;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <title-comp [title]='text'></title-comp>
 """;
     _addHtmlSource(code);
@@ -264,6 +275,7 @@
         AngularWarningCode.INPUT_BINDING_TYPE_ERROR, code, "text");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_inputBinding_asString_typeError() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel',
@@ -275,7 +287,8 @@
   @Input() int titleInput;
 }
 ''');
-    var code = r"""
+
+    final code = r"""
 <title-comp titleInput='string binding'></title-comp>
 """;
     _addHtmlSource(code);
@@ -286,23 +299,24 @@
         "titleInput");
   }
 
-// DISABLED for #280 until we better know how to validate this case
-//  Future test_expression_inputBinding_global_asString_typeError() async {
-//    _addDartSource(r'''
-//@Component(selector: 'test-panel',
-//    directives: const [], templateUrl: 'test_panel.html')
-//class TestPanel {
-//}
-//''');
-//    var code = r"""
-//<div hidden="string binding"></div>
-//""";
-//    _addHtmlSource(code);
-//    await _resolveSingleTemplate(dartSource);
-//    assertErrorInCodeAtPosition(
-//        AngularWarningCode.STRING_STYLE_INPUT_BINDING_INVALID, code, "hidden");
-//  }
+  // ignore: non_constant_identifier_names
+  Future test_expression_inputBinding_nativeHtml_asString_notTypeError() async {
+    _addDartSource(r'''
+@Component(selector: 'test-panel',
+    directives: const [], templateUrl: 'test_panel.html')
+class TestPanel {
+}
+''');
+    final code = r"""
+<div hidden="allowed because becomes addAttribute() rather than .hidden="></div>
+<img width="allowed because becomes addAttribute() rather than .width=" />
+""";
+    _addHtmlSource(code);
+    await _resolveSingleTemplate(dartSource);
+    errorListener.assertNoErrors();
+  }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_inputBinding_noValue() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel',
@@ -315,7 +329,7 @@
   @Input() int title;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <title-comp [title]></title-comp>
 """;
     _addHtmlSource(code);
@@ -324,6 +338,7 @@
         AngularWarningCode.EMPTY_BINDING, code, "[title]");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_inputBinding_empty() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel',
@@ -336,7 +351,7 @@
   @Input() int title;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <title-comp [title]=""></title-comp>
 """;
     _addHtmlSource(code);
@@ -345,6 +360,7 @@
         AngularWarningCode.EMPTY_BINDING, code, "[title]");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_inputBinding_boundToNothing() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
@@ -352,7 +368,7 @@
   String text; // 1
 }
 ''');
-    var code = r"""
+    final code = r"""
 <span [title]='text'></span>
 """;
     _addHtmlSource(code);
@@ -361,6 +377,7 @@
         AngularWarningCode.NONEXIST_INPUT_BOUND, code, "title");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_twoWayBinding_valid() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel',
@@ -379,18 +396,19 @@
 """);
     await _resolveSingleTemplate(dartSource);
     errorListener.assertNoErrors();
-    ElementSearch search = new ElementSearch((e) => e.localName == "span");
+    final search = new ElementSearch((e) => e.localName == "span");
     template.ast.accept(search);
 
     expect(search.element, isNotNull);
     expect(search.element.boundDirectives, hasLength(1));
-    DirectiveBinding boundDirective = search.element.boundDirectives.first;
+    final boundDirective = search.element.boundDirectives.first;
     expect(boundDirective.inputBindings, hasLength(1));
     expect(boundDirective.inputBindings.first.boundInput.name, 'title');
     expect(boundDirective.outputBindings, hasLength(1));
     expect(boundDirective.outputBindings.first.boundOutput.name, 'titleChange');
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_twoWayBinding_noAttr_emptyBinding() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel',
@@ -404,7 +422,7 @@
   @Output() EventEmitter<String> twoWayChange;
 }
 ''');
-    String code = r"""
+    final code = r"""
 <span titled [(twoWay)]></span>
 """;
     _addHtmlSource(code);
@@ -413,6 +431,7 @@
         AngularWarningCode.EMPTY_BINDING, code, "[(twoWay)]");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_twoWayBinding_inputTypeError() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel',
@@ -426,7 +445,7 @@
   @Output() EventEmitter<String> titleChange;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <title-comp [(title)]='text'></title-comp>
 """;
     _addHtmlSource(code);
@@ -435,6 +454,7 @@
         AngularWarningCode.INPUT_BINDING_TYPE_ERROR, code, "text");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_twoWayBinding_outputTypeError() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel',
@@ -448,7 +468,7 @@
   @Output() EventEmitter<int> titleChange;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <title-comp [(title)]='text'></title-comp>
 """;
     _addHtmlSource(code);
@@ -457,6 +477,7 @@
         AngularWarningCode.TWO_WAY_BINDING_OUTPUT_TYPE_ERROR, code, "text");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_outputBinding_noValue() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel',
@@ -469,7 +490,7 @@
   @Output() EventEmitter<int> title;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <title-comp (title)></title-comp>
 """;
     _addHtmlSource(code);
@@ -478,6 +499,7 @@
         AngularWarningCode.EMPTY_BINDING, code, "(title)");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_twoWayBinding_notAssignableError() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel',
@@ -491,7 +513,7 @@
   @Output() EventEmitter<String> titleChange;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <title-comp [(title)]="text.toUpperCase()"></title-comp>
 """;
     _addHtmlSource(code);
@@ -502,6 +524,7 @@
         "text.toUpperCase()");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_twoWayBinding_noInputToBind() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel',
@@ -514,7 +537,7 @@
   @Output() EventEmitter<String> noInputChange;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <title-comp [(noInput)]="text"></title-comp>
 """;
     _addHtmlSource(code);
@@ -523,6 +546,7 @@
         AngularWarningCode.NONEXIST_INPUT_BOUND, code, "noInput");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_twoWayBinding_noOutputToBind() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel',
@@ -535,7 +559,7 @@
   @Input() String inputOnly;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <title-comp [(inputOnly)]="text"></title-comp>
 """;
     _addHtmlSource(code);
@@ -544,6 +568,7 @@
         AngularWarningCode.NONEXIST_TWO_WAY_OUTPUT_BOUND, code, "inputOnly");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_inputBinding_bind() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
@@ -560,6 +585,7 @@
     _assertElement("text'>").dart.getter.at('text; // 1');
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_outputBinding_boundToNothing() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
@@ -567,7 +593,7 @@
   String text; // 1
 }
 ''');
-    var code = r"""
+    final code = r"""
 <span (title)='text'></span>
 """;
     _addHtmlSource(code);
@@ -576,6 +602,7 @@
         AngularWarningCode.NONEXIST_OUTPUT_BOUND, code, "title");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_outputBinding_typeError() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel',
@@ -588,7 +615,7 @@
   @Output() EventEmitter<int> output;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <title-comp (output)='takeString($event)'></title-comp>
 """;
     _addHtmlSource(code);
@@ -597,13 +624,14 @@
         StaticWarningCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, code, r"$event");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_inputBinding_noEvent() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
 class TestPanel {
 }
 ''');
-    var code = r"""
+    final code = r"""
 <h1 [hidden]="$event">
 </h1>
 """;
@@ -613,13 +641,14 @@
         StaticWarningCode.UNDEFINED_IDENTIFIER, code, r"$event");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_mustache_noEvent() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
 class TestPanel {
 }
 ''');
-    var code = r"""
+    final code = r"""
 <h1>{{$event}}</h1>
 """;
     _addHtmlSource(code);
@@ -628,13 +657,14 @@
         StaticWarningCode.UNDEFINED_IDENTIFIER, code, r"$event");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_mustache_closeOpen_githubBug198() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
 class TestPanel {
 }
 ''');
-    var code = r"""
+    final code = r"""
     }}{{''}}
 """;
     _addHtmlSource(code);
@@ -643,6 +673,7 @@
         AngularWarningCode.UNOPENED_MUSTACHE, code, '}}');
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_as_not_allowed() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
@@ -650,7 +681,7 @@
   String str;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <h1>{{str as String}}</h1>
 """;
     _addHtmlSource(code);
@@ -659,6 +690,7 @@
         AngularWarningCode.DISALLOWED_EXPRESSION, code, "str as String");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_nested_as_not_allowed() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
@@ -666,7 +698,7 @@
   String str;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <h1>{{(str.isEmpty as String).isEmpty}}</h1>
 """;
     _addHtmlSource(code);
@@ -675,6 +707,7 @@
         "str.isEmpty as String");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_typed_list_not_allowed() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
@@ -682,7 +715,7 @@
   String str;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <h1 [hidden]="<String>[].isEmpty"></h1>
 """;
     _addHtmlSource(code);
@@ -691,6 +724,7 @@
         AngularWarningCode.DISALLOWED_EXPRESSION, code, "<String>[]");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_setter_not_allowed() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
@@ -698,7 +732,7 @@
   String str;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <h1 [hidden]="str = 'hey'"></h1>
 """;
     _addHtmlSource(code);
@@ -707,6 +741,7 @@
         AngularWarningCode.DISALLOWED_EXPRESSION, code, "str = 'hey'");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_assignment_not_allowed() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
@@ -714,7 +749,7 @@
   String str;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <h1 #h1 [hidden]="h1 = 4"></h1>
 """;
     _addHtmlSource(code);
@@ -723,6 +758,7 @@
         AngularWarningCode.DISALLOWED_EXPRESSION, code, "h1 = 4");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_statements_assignment_not_allowed() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
@@ -730,7 +766,7 @@
   String str;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <h1 #h1 (click)="h1 = 4"></h1>
 """;
     _addHtmlSource(code);
@@ -739,6 +775,7 @@
         AngularWarningCode.DISALLOWED_EXPRESSION, code, "h1 = 4");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_invocation_of_erroneous_assignment_no_crash() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
@@ -747,7 +784,7 @@
   Function f;
 }
 ''');
-    var code = r"""
+    final code = r"""
 {{str = (f)()}}
 """;
     _addHtmlSource(code);
@@ -756,6 +793,7 @@
         AngularWarningCode.DISALLOWED_EXPRESSION, code, "str = (f)()");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_statements_setter_allowed() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
@@ -763,7 +801,7 @@
   String str;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <h1 #h1 (click)="str = 'hey'"></h1>
 """;
     _addHtmlSource(code);
@@ -771,6 +809,7 @@
     errorListener.assertNoErrors();
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_is_not_allowed() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
@@ -778,7 +817,7 @@
   String str;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <h1 [hidden]="str is int"></h1>
 """;
     _addHtmlSource(code);
@@ -787,6 +826,7 @@
         AngularWarningCode.DISALLOWED_EXPRESSION, code, "str is int");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_typed_map_not_allowed() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
@@ -794,7 +834,7 @@
   String str;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <h1 [hidden]="<String, String>{}.keys.isEmpty"></h1>
 """;
     _addHtmlSource(code);
@@ -803,6 +843,7 @@
         AngularWarningCode.DISALLOWED_EXPRESSION, code, "<String, String>{}");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_func_not_allowed() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
@@ -810,7 +851,7 @@
   String str;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <h1 [hidden]="(){}"></h1>
 """;
     _addHtmlSource(code);
@@ -819,6 +860,7 @@
         AngularWarningCode.DISALLOWED_EXPRESSION, code, "(){}");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_func2_not_allowed() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
@@ -826,7 +868,7 @@
   String str;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <h1 [hidden]="()=>x"></h1>
 """;
     _addHtmlSource(code);
@@ -835,6 +877,7 @@
         AngularWarningCode.DISALLOWED_EXPRESSION, code, "()=>x");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_symbol_not_allowed() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
@@ -842,7 +885,7 @@
   String str;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <h1 [hidden]="#symbol"></h1>
 """;
     _addHtmlSource(code);
@@ -851,6 +894,7 @@
         AngularWarningCode.DISALLOWED_EXPRESSION, code, "#symbol");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_symbol_invoked_noCrash() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
@@ -858,7 +902,7 @@
   String str;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <h1 [hidden]="#symbol()"></h1>
 """;
     _addHtmlSource(code);
@@ -867,6 +911,7 @@
         AngularWarningCode.DISALLOWED_EXPRESSION, code, "#symbol");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_await_not_allowed() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
@@ -874,7 +919,7 @@
   String str;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <h1 [hidden]="await str"></h1>
 """;
     _addHtmlSource(code);
@@ -886,6 +931,7 @@
     ]);
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_throw_not_allowed() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
@@ -893,7 +939,7 @@
   String str;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <h1 [hidden]="throw str"></h1>
 """;
     _addHtmlSource(code);
@@ -902,6 +948,7 @@
         AngularWarningCode.DISALLOWED_EXPRESSION, code, "throw str");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_cascade_not_allowed() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
@@ -909,7 +956,7 @@
   String str;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <h1 [hidden]="str..x"></h1>
 """;
     _addHtmlSource(code);
@@ -918,6 +965,7 @@
         AngularWarningCode.DISALLOWED_EXPRESSION, code, "str..x");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_new_not_allowed() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
@@ -925,7 +973,7 @@
   String str;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <h1 [hidden]="new String().isEmpty"></h1>
 """;
     _addHtmlSource(code);
@@ -934,6 +982,7 @@
         AngularWarningCode.DISALLOWED_EXPRESSION, code, "new String()");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_named_args_not_allowed() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
@@ -941,7 +990,7 @@
   bool callMe({String arg}) => true;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <h1 [hidden]="callMe(arg: 'bob')"></h1>
 """;
     _addHtmlSource(code);
@@ -950,6 +999,7 @@
         AngularWarningCode.DISALLOWED_EXPRESSION, code, "arg: 'bob'");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_rethrow_not_allowed() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
@@ -957,7 +1007,7 @@
   String str;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <h1 [hidden]="rethrow"></h1>
 """;
     _addHtmlSource(code);
@@ -966,6 +1016,7 @@
         AngularWarningCode.DISALLOWED_EXPRESSION, code, "rethrow");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_super_not_allowed() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
@@ -973,7 +1024,7 @@
   String str;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <h1 [hidden]="super.x"></h1>
 """;
     _addHtmlSource(code);
@@ -982,6 +1033,7 @@
         AngularWarningCode.DISALLOWED_EXPRESSION, code, "super");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_this_not_allowed() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
@@ -989,7 +1041,7 @@
   String str;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <h1 [hidden]="this"></h1>
 """;
     _addHtmlSource(code);
@@ -998,6 +1050,7 @@
         AngularWarningCode.DISALLOWED_EXPRESSION, code, "this");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_attrBinding_valid() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
@@ -1005,7 +1058,7 @@
   String text; // 1
 }
 ''');
-    var code = r"""
+    final code = r"""
 <span [attr.aria-title]='text'></span>
 """;
     _addHtmlSource(code);
@@ -1013,6 +1066,7 @@
     errorListener.assertNoErrors();
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_attrBinding_expressionTypeError() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
@@ -1020,7 +1074,7 @@
   int pixels;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <span [attr.aria]='pixels.length'></span>
 """;
     _addHtmlSource(code);
@@ -1029,6 +1083,7 @@
         StaticTypeWarningCode.UNDEFINED_GETTER, code, "length");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_classBinding_valid() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
@@ -1036,7 +1091,7 @@
   String text; // 1
 }
 ''');
-    var code = r"""
+    final code = r"""
 <span [class.my-class]='text == null'></span>
 """;
     _addHtmlSource(code);
@@ -1044,6 +1099,7 @@
     errorListener.assertNoErrors();
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_classBinding_invalidClassName() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
@@ -1051,7 +1107,7 @@
   String title;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <span [class.invalid.class]='title == null'></span>
 """;
     _addHtmlSource(code);
@@ -1060,6 +1116,7 @@
         AngularWarningCode.INVALID_HTML_CLASSNAME, code, "invalid.class");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_classBinding_typeError() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
@@ -1067,7 +1124,7 @@
   String notBoolean;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <span [class.aria]='notBoolean'></span>
 """;
     _addHtmlSource(code);
@@ -1076,6 +1133,7 @@
         AngularWarningCode.CLASS_BINDING_NOT_BOOLEAN, code, "notBoolean");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_styleBinding_noUnit_valid() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
@@ -1083,7 +1141,7 @@
   String text; // 1
 }
 ''');
-    var code = r"""
+    final code = r"""
 <span [style.background-color]='text'></span>
 """;
     _addHtmlSource(code);
@@ -1091,6 +1149,7 @@
     errorListener.assertNoErrors();
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_styleBinding_noUnit_invalidCssProperty() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
@@ -1098,7 +1157,7 @@
   String text; // 1
 }
 ''');
-    var code = r"""
+    final code = r"""
 <span [style.invalid*property]='text'></span>
 """;
     _addHtmlSource(code);
@@ -1108,9 +1167,11 @@
       new Tuple4(']', 1,
           NgParserWarningCode.EXPECTED_WHITESPACE_BEFORE_NEW_DECORATOR, []),
       new Tuple4('[', 14, NgParserWarningCode.SUFFIX_PROPERTY, []),
+      new Tuple4('*property', 9, AngularWarningCode.TEMPLATE_ATTR_NOT_USED, []),
     ]);
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_styleBinding_noUnit_expressionTypeError() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
@@ -1118,7 +1179,7 @@
   int noLength; // 1
 }
 ''');
-    var code = r"""
+    final code = r"""
 <span [style.background-color]='noLength.length'></span>
 """;
     _addHtmlSource(code);
@@ -1127,6 +1188,7 @@
         StaticTypeWarningCode.UNDEFINED_GETTER, code, "length");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_styleBinding_withUnit_invalidPropertyName() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
@@ -1134,7 +1196,7 @@
   int pixels; // 1
 }
 ''');
-    var code = r"""
+    final code = r"""
 <span [style.border&radius.px]='pixels'></span>
 """;
     _addHtmlSource(code);
@@ -1149,6 +1211,7 @@
     ]);
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_styleBinding_withUnit_invalidUnitName() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
@@ -1156,7 +1219,7 @@
   double pixels; // 1
 }
 ''');
-    var code = r"""
+    final code = r"""
 <span [style.border-radius.p|x]='pixels'></span>
 """;
     _addHtmlSource(code);
@@ -1186,6 +1249,7 @@
     errorListener.assertNoErrors();
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_styleBinding_withUnit_widthPercent() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
@@ -1193,7 +1257,7 @@
   int percentage; // 1
 }
 ''');
-    var code = r"""
+    final code = r"""
 <span [style.width.%]='percentage'></span>
 """;
     _addHtmlSource(code);
@@ -1201,6 +1265,7 @@
     errorListener.assertNoErrors();
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_styleBinding_withUnit_nonWidthOrHeightPercent() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
@@ -1208,7 +1273,7 @@
   int percentage; // 1
 }
 ''');
-    var code = r"""
+    final code = r"""
 <span [style.something.%]='percentage'></span>
 """;
     _addHtmlSource(code);
@@ -1217,6 +1282,7 @@
         AngularWarningCode.INVALID_CSS_UNIT_NAME, code, "%");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_styleBinding_withUnit_typeError() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
@@ -1224,7 +1290,7 @@
   String notNumber; // 1
 }
 ''');
-    var code = r"""
+    final code = r"""
 <span [style.border-radius.px]='notNumber'></span>
 """;
     _addHtmlSource(code);
@@ -1233,6 +1299,7 @@
         AngularWarningCode.CSS_UNIT_BINDING_NOT_NUMBER, code, "notNumber");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_detect_eof_post_semicolon_in_moustache() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
@@ -1241,7 +1308,7 @@
 }
 ''');
 
-    var code = r"""
+    final code = r"""
 <p>{{name; bad portion}}</p>
  """;
     _addHtmlSource(code);
@@ -1250,6 +1317,7 @@
         AngularWarningCode.TRAILING_EXPRESSION, code, "; bad portion");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_detect_eof_ellipsis_in_moustache() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
@@ -1257,7 +1325,7 @@
   String name = "TestPanel";
 }
 ''');
-    var code = r"""
+    final code = r"""
 <p>{{name...}}</p>
 """;
     _addHtmlSource(code);
@@ -1266,6 +1334,7 @@
         AngularWarningCode.TRAILING_EXPRESSION, code, "...");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_detect_eof_post_semicolon_in_property_binding() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
@@ -1275,7 +1344,7 @@
 }
 ''');
 
-    var code = r"""
+    final code = r"""
 <div [class.selected]="a == b; bad portion"></div>
  """;
     _addHtmlSource(code);
@@ -1284,6 +1353,7 @@
         AngularWarningCode.TRAILING_EXPRESSION, code, "; bad portion");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_detect_eof_ellipsis_in_property_binding() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
@@ -1292,7 +1362,7 @@
   int b = 1;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <div [class.selected]="a==b..."></div>
 """;
     _addHtmlSource(code);
@@ -1301,6 +1371,7 @@
         AngularWarningCode.TRAILING_EXPRESSION, code, "...");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_inputAndOutputBinding_genericDirective_ok() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel',
@@ -1317,7 +1388,7 @@
   @Input() T twoWay;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <generic-comp (output)='$event.length' [input]="string" [(twoWay)]="string"></generic-comp>
 """;
     _addHtmlSource(code);
@@ -1326,6 +1397,7 @@
   }
 
   Future
+      // ignore: non_constant_identifier_names
       test_expression_inputAndOutputBinding_genericDirectiveChild_ok() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel',
@@ -1345,7 +1417,7 @@
 class GenericComponent<T> extends Generic<T> {
 }
 ''');
-    var code = r"""
+    final code = r"""
 <generic-comp (output)='$event.length' [input]="string" [(twoWay)]="string"></generic-comp>
 """;
     _addHtmlSource(code);
@@ -1354,6 +1426,7 @@
   }
 
   Future
+      // ignore: non_constant_identifier_names
       test_expression_inputAndOutputBinding_extendGenericUnbounded_ok() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel',
@@ -1373,7 +1446,7 @@
 class GenericComponent<T> extends Generic {
 }
 ''');
-    var code = r"""
+    final code = r"""
 <generic-comp (output)='$event.length' [input]="string" [(twoWay)]="string"></generic-comp>
 """;
     _addHtmlSource(code);
@@ -1382,6 +1455,7 @@
   }
 
   Future
+      // ignore: non_constant_identifier_names
       test_expression_inputAndOutputBinding_genericDirective_chain_ok() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel',
@@ -1398,7 +1472,7 @@
   @Input() T twoWay;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <generic-comp (output)='$event.length' [input]="string" [(twoWay)]="string"></generic-comp>
 """;
     _addHtmlSource(code);
@@ -1407,6 +1481,7 @@
   }
 
   Future
+      // ignore: non_constant_identifier_names
       test_expression_inputAndOutputBinding_genericDirective_nested_ok() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel',
@@ -1423,7 +1498,7 @@
   @Input() List<T> twoWay;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <generic-comp (output)='$event[0].length' [input]="stringList" [(twoWay)]="stringList"></generic-comp>
 """;
     _addHtmlSource(code);
@@ -1432,6 +1507,7 @@
   }
 
   Future
+      // ignore: non_constant_identifier_names
       test_expression_inputBinding_genericDirective_lowerBoundTypeError() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel',
@@ -1444,7 +1520,7 @@
   @Input() T string;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <generic-comp [string]="notString"></generic-comp>
 """;
     _addHtmlSource(code);
@@ -1454,6 +1530,7 @@
   }
 
   Future
+      // ignore: non_constant_identifier_names
       test_expression_input_genericDirective_lowerBoundChainTypeError() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel',
@@ -1466,7 +1543,7 @@
   @Input() T string;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <generic-comp [string]="notString"></generic-comp>
 """;
     _addHtmlSource(code);
@@ -1476,6 +1553,7 @@
   }
 
   Future
+      // ignore: non_constant_identifier_names
       test_expression_input_genericDirective_lowerBoundNestedTypeError() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel',
@@ -1488,7 +1566,7 @@
   @Input() List<T> stringList;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <generic-comp [stringList]="notStringList"></generic-comp>
 """;
     _addHtmlSource(code);
@@ -1498,6 +1576,7 @@
   }
 
   Future
+      // ignore: non_constant_identifier_names
       test_expression_outputBinding_genericDirective_lowerBoundTypeError() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel',
@@ -1510,7 +1589,7 @@
   @Output() EventEmitter<T> string;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <generic-comp (string)="takeInt($event)"></generic-comp>
 """;
     _addHtmlSource(code);
@@ -1520,6 +1599,7 @@
   }
 
   Future
+      // ignore: non_constant_identifier_names
       test_expression_twoWayBinding_genericDirective_lowerBoundTypeError() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel',
@@ -1533,7 +1613,7 @@
   @Input() dynamic string;
 }
 ''');
-    var code = r"""
+    final code = r"""
 <generic-comp [(string)]="anInt"></generic-comp>
 """;
     _addHtmlSource(code);
@@ -1542,6 +1622,7 @@
         AngularWarningCode.TWO_WAY_BINDING_OUTPUT_TYPE_ERROR, code, "anInt");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_pipe_in_moustache() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
@@ -1549,7 +1630,7 @@
   String name = "TestPanel";
 }
 ''');
-    var code = r"""
+    final code = r"""
 <p>{{((1 | pipe1:(2+2):(5 | pipe2:1:2)) + (2 | pipe3:4:2))}}</p>
 """;
     _addHtmlSource(code);
@@ -1557,6 +1638,7 @@
     errorListener.assertNoErrors();
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_pipe_in_moustache_with_error() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel', templateUrl: 'test_panel.html')
@@ -1564,7 +1646,7 @@
   String name = "TestPanel";
 }
 ''');
-    var code = r"""
+    final code = r"""
 <p>{{((1 | pipe1:(2+2):(5 | pipe2:1:2)) + (error1 | pipe3:4:2))}}</p>
 """;
     _addHtmlSource(code);
@@ -1573,6 +1655,7 @@
         StaticWarningCode.UNDEFINED_IDENTIFIER, code, "error1");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_pipe_in_input_binding() async {
     _addDartSource(r'''
 @Component(
@@ -1594,6 +1677,7 @@
     errorListener.assertNoErrors();
   }
 
+  // ignore: non_constant_identifier_names
   Future test_expression_pipe_in_ngFor() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
@@ -1612,6 +1696,7 @@
   }
 
   Future
+      // ignore: non_constant_identifier_names
       test_statement_eventBinding_single_statement_without_semicolon() async {
     _addDartSource(r'''
 import 'dart:html';
@@ -1630,6 +1715,7 @@
     errorListener.assertNoErrors();
   }
 
+  // ignore: non_constant_identifier_names
   Future test_statement_eventBinding_single_statement_with_semicolon() async {
     _addDartSource(r'''
 import 'dart:html';
@@ -1649,6 +1735,7 @@
   }
 
   Future
+      // ignore: non_constant_identifier_names
       test_statement_eventBinding_return_statement_without_semicolon() async {
     _addDartSource(r'''
 import 'dart:html';
@@ -1659,7 +1746,7 @@
   }
 }
 ''');
-    String code = r"""<h2 (click)='return 5'></h2>""";
+    final code = r"""<h2 (click)='return 5'></h2>""";
     _addHtmlSource(code);
     await _resolveSingleTemplate(dartSource);
     assertErrorInCodeAtPosition(
@@ -1668,6 +1755,7 @@
         "return 5");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_statement_eventBinding_return_statement_with_semicolon() async {
     _addDartSource(r'''
 import 'dart:html';
@@ -1678,7 +1766,7 @@
   }
 }
 ''');
-    String code = r"""<h2 (click)='return 5;'></h2>""";
+    final code = r"""<h2 (click)='return 5;'></h2>""";
     _addHtmlSource(code);
     await _resolveSingleTemplate(dartSource);
     assertErrorInCodeAtPosition(
@@ -1687,6 +1775,7 @@
         "return 5");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_statement_eventBinding_if_statement_without_semicolon() async {
     _addDartSource(r'''
 import 'dart:html';
@@ -1697,7 +1786,7 @@
   }
 }
 ''');
-    String code = r"""<h2 (click)='if(true){}'></h2>""";
+    final code = r"""<h2 (click)='if(true){}'></h2>""";
     _addHtmlSource(code);
     await _resolveSingleTemplate(dartSource);
     assertErrorInCodeAtPosition(
@@ -1706,6 +1795,7 @@
         "if(true){}");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_statement_eventBinding_if_statement_with_semicolon() async {
     _addDartSource(r'''
 import 'dart:html';
@@ -1716,7 +1806,7 @@
   }
 }
 ''');
-    String code = r"""<h2 (click)='if(true){};'></h2>""";
+    final code = r"""<h2 (click)='if(true){};'></h2>""";
     _addHtmlSource(code);
     await _resolveSingleTemplate(dartSource);
     assertErrorInCodeAtPosition(
@@ -1725,6 +1815,7 @@
         "if(true){}");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_statement_eventBinding_double_statement() async {
     _addDartSource(r'''
 import 'dart:html';
@@ -1743,6 +1834,7 @@
     _assertElement('handleClick').dart.method.at('handleClick(MouseEvent');
   }
 
+  // ignore: non_constant_identifier_names
   Future test_statement_eventBinding_error_on_second_statement() async {
     _addDartSource(r'''
 import 'dart:html';
@@ -1753,7 +1845,7 @@
   }
 }
 ''');
-    String code = r"""
+    final code = r"""
 <div (click)='handleClick($event); unknownFunction()'></div>
 """;
     _addHtmlSource(code);
@@ -1762,6 +1854,7 @@
         StaticTypeWarningCode.UNDEFINED_METHOD, code, "unknownFunction");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_statement_eventBinding_error_on_assignment_statement() async {
     _addDartSource(r'''
 import 'dart:html';
@@ -1772,7 +1865,7 @@
   }
 }
 ''');
-    String code = r"""
+    final code = r"""
 <div (click)='handleClick($event); String s;'></div>
     """;
     _addHtmlSource(code);
@@ -1783,6 +1876,7 @@
         "String s");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_statement_eventBinding_typeError() async {
     _addDartSource(r'''
 import 'dart:html';
@@ -1793,7 +1887,7 @@
   }
 }
 ''');
-    String code = r"""
+    final code = r"""
 <div (click)='handleClick($event); 1 + "asdf";'></div>
     """;
     _addHtmlSource(code);
@@ -1802,6 +1896,7 @@
         StaticWarningCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, code, '"asdf"');
   }
 
+  // ignore: non_constant_identifier_names
   Future test_statement_eventBinding_all_semicolons() async {
     _addDartSource(r'''
 import 'dart:html';
@@ -1812,7 +1907,7 @@
   }
 }
 ''');
-    String code = r"""
+    final code = r"""
 <div (click)=';;;;;;;;;;;;;'></div>
     """;
     _addHtmlSource(code);
@@ -1820,6 +1915,7 @@
     errorListener.assertNoErrors();
   }
 
+  // ignore: non_constant_identifier_names
   Future test_statement_eventBinding_single_variable() async {
     _addDartSource(r'''
 import 'dart:html';
@@ -1831,7 +1927,7 @@
   String random_string = "";
 }
 ''');
-    String code = r"""
+    final code = r"""
 <div (click)='handleClick;'></div>
     """;
     _addHtmlSource(code);
@@ -1840,6 +1936,7 @@
   }
 
   Future
+      // ignore: non_constant_identifier_names
       test_statement_eventBinding_unexpected_closing_brackets_at_end() async {
     _addDartSource(r'''
 import 'dart:html';
@@ -1850,7 +1947,7 @@
   }
 }
 ''');
-    String code = r"""
+    final code = r"""
 <div (click)='handleClick($event);}}}}'></div>
     """;
     _addHtmlSource(code);
@@ -1859,6 +1956,7 @@
   }
 
   Future
+      // ignore: non_constant_identifier_names
       test_statement_eventBinding_unexpected_closing_brackets_at_start() async {
     _addDartSource(r'''
 import 'dart:html';
@@ -1869,7 +1967,7 @@
   }
 }
 ''');
-    String code = r"""
+    final code = r"""
 <div (click)='}}handleClick($event)'></div>
     """;
     _addHtmlSource(code);
@@ -1878,6 +1976,7 @@
   }
 
   Future
+      // ignore: non_constant_identifier_names
       test_statement_eventBinding_typechecking_after_unexpected_bracket() async {
     _addDartSource(r'''
 import 'dart:html';
@@ -1888,7 +1987,7 @@
   }
 }
 ''');
-    String code = r"""
+    final code = r"""
 <div (click)='}1.length'></div>
     """;
     _addHtmlSource(code);
@@ -1900,6 +1999,7 @@
     ]);
   }
 
+  // ignore: non_constant_identifier_names
   Future test_inheritedFields() async {
     _addDartSource(r'''
 class BaseComponent {
@@ -1924,6 +2024,7 @@
     errorListener.assertNoErrors();
   }
 
+  // ignore: non_constant_identifier_names
   Future test_inputReference() async {
     _addDartSource(r'''
 @Component(
@@ -1949,6 +2050,7 @@
     _assertElement("id=").input.inCoreHtml;
   }
 
+  // ignore: non_constant_identifier_names
   Future test_outputReference() async {
     _addDartSource(r'''
 @Component(selector: 'name-panel',
@@ -1968,18 +2070,18 @@
     await _resolveSingleTemplate(dartSource);
     _assertElement("bbb)=").output.at("bbb;");
     _assertElement("ccc=").output.at("ccc;");
-    ElementSearch search =
-        new ElementSearch((e) => e.localName == "name-panel");
+    final search = new ElementSearch((e) => e.localName == "name-panel");
     template.ast.accept(search);
 
     expect(search.element, isNotNull);
     expect(search.element.boundDirectives, hasLength(1));
-    DirectiveBinding boundDirective = search.element.boundDirectives.first;
+    final boundDirective = search.element.boundDirectives.first;
     expect(boundDirective.outputBindings, hasLength(2));
     expect(boundDirective.outputBindings[0].boundOutput.name, 'bbb');
     expect(boundDirective.outputBindings[1].boundOutput.name, 'ccc');
   }
 
+  // ignore: non_constant_identifier_names
   Future test_twoWayReference() async {
     _addDartSource(r'''
 @Component(
@@ -2002,6 +2104,7 @@
     _assertElement("value)]").input.at("value;");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_localVariable_camelCaseName() async {
     _addDartSource(r'''
 import 'dart:html';
@@ -2022,6 +2125,7 @@
     _assertElement("myTargetElement)").local.at("myTargetElement>");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_localVariable_exportAs() async {
     _addDartSource(r'''
 @Directive(selector: '[myDirective]', exportAs: 'exportedValue')
@@ -2046,6 +2150,7 @@
     _assertElement("aaa}}").dart.getter.at('aaa; // 1');
   }
 
+  // ignore: non_constant_identifier_names
   Future test_attributeReference() async {
     _addDartSource(r'''
 @Component(
@@ -2069,6 +2174,7 @@
         .at("namePanelAttr");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_erroroneousTemplate_starHash_noCrash() async {
     _addDartSource(r'''
 import 'dart:html';
@@ -2088,13 +2194,14 @@
     // no assertion. Just don't crash.
   }
 
+  // ignore: non_constant_identifier_names
   Future test_localVariable_exportAs_notFound() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
 @View(templateUrl: 'test_panel.html')
 class TestPanel {}
 ''');
-    var code = r"""
+    final code = r"""
 <div #value='noSuchExportedValue'>
   {{value.aaa}}
   assertErrorInCodeAtPosition fails when it sees multiple errors.
@@ -2109,6 +2216,7 @@
         "noSuchExportedValue");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_localVariable_scope_forwardReference() async {
     _addDartSource(r'''
 import 'dart:html';
@@ -2138,6 +2246,7 @@
     _assertElement("handle'>").local.at("handle></bbb>").type('ComponentB');
   }
 
+  // ignore: non_constant_identifier_names
   Future test_ngContent() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
@@ -2151,6 +2260,7 @@
     errorListener.assertNoErrors();
   }
 
+  // ignore: non_constant_identifier_names
   Future test_ngFor_iterableElementType() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
@@ -2175,6 +2285,7 @@
     _assertElement("length}}").dart.getter;
   }
 
+  // ignore: non_constant_identifier_names
   Future test_ngFor_operatorLocalVariable() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
@@ -2195,18 +2306,19 @@
     _assertElement("operator of").local.declaration.type('String');
     _assertElement("length}}").dart.getter;
     errorListener.assertNoErrors();
-    ElementSearch search = new ElementSearch((e) => e.localName == "li");
+    final search = new ElementSearch((e) => e.localName == "li");
     template.ast.accept(search);
 
     expect(search.element, isNotNull);
     expect(search.element.templateAttribute, isNotNull);
     expect(search.element.templateAttribute.boundDirectives, hasLength(1));
-    DirectiveBinding boundDirective =
+    final boundDirective =
         search.element.templateAttribute.boundDirectives.first;
     expect(boundDirective.inputBindings, hasLength(1));
     expect(boundDirective.inputBindings.first.boundInput.name, 'ngForOf');
   }
 
+  // ignore: non_constant_identifier_names
   Future test_ngFor_operatorLocalVariableVarKeyword() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
@@ -2229,6 +2341,7 @@
     errorListener.assertNoErrors();
   }
 
+  // ignore: non_constant_identifier_names
   Future test_ngFor_star() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
@@ -2271,6 +2384,7 @@
     _assertElement("l}}").local.at('l = last');
   }
 
+  // ignore: non_constant_identifier_names
   Future test_ngFor_noStarError() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
@@ -2279,7 +2393,7 @@
   List<String> items = [];
 }
 ''');
-    var code = r"""
+    final code = r"""
 <li ngFor='let item of items; let i = index'>
 </li>
 """;
@@ -2291,6 +2405,152 @@
         "ngFor");
   }
 
+  // ignore: non_constant_identifier_names
+  Future test_customDirective_noStarError() async {
+    _addDartSource(r'''
+@Component(selector: 'test-panel')
+@View(templateUrl: 'test_panel.html',
+    directives: const [CustomTemplateDirective])
+class TestPanel {
+}
+
+@Directive(selector: '[customTemplateDirective]')
+class CustomTemplateDirective {
+  CustomTemplateDirective(TemplateRef tpl);
+}
+''');
+    final code = r"""
+<div customTemplateDirective></div>
+""";
+    _addHtmlSource(code);
+    await _resolveSingleTemplate(dartSource);
+    assertErrorInCodeAtPosition(
+        AngularWarningCode.CUSTOM_DIRECTIVE_MAY_REQUIRE_TEMPLATE,
+        code,
+        "<div customTemplateDirective>");
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_customDirective_withStarOk() async {
+    _addDartSource(r'''
+@Component(selector: 'test-panel')
+@View(templateUrl: 'test_panel.html',
+    directives: const [CustomTemplateDirective])
+class TestPanel {
+}
+
+@Directive(selector: '[customTemplateDirective]')
+class CustomTemplateDirective {
+  CustomTemplateDirective(TemplateRef tpl);
+}
+''');
+    final code = r"""
+<div *customTemplateDirective></div>
+""";
+    _addHtmlSource(code);
+    await _resolveSingleTemplate(dartSource);
+    errorListener.assertNoErrors();
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_customDirective_asTemplateAttrOk() async {
+    _addDartSource(r'''
+@Component(selector: 'test-panel')
+@View(templateUrl: 'test_panel.html',
+    directives: const [CustomTemplateDirective])
+class TestPanel {
+}
+
+@Directive(selector: '[customTemplateDirective]')
+class CustomTemplateDirective {
+  CustomTemplateDirective(TemplateRef tpl);
+}
+''');
+    final code = r"""
+<div template="customTemplateDirective"></div>
+""";
+    _addHtmlSource(code);
+    await _resolveSingleTemplate(dartSource);
+    errorListener.assertNoErrors();
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_customDirective_starDoesntTakeTemplateError() async {
+    _addDartSource(r'''
+@Component(selector: 'test-panel')
+@View(templateUrl: 'test_panel.html', directives: const [NotTemplateDirective])
+class TestPanel {
+}
+
+@Directive(selector: '[notTemplateDirective]')
+class NotTemplateDirective {
+}
+''');
+    final code = r"""
+<div *notTemplateDirective></div>
+""";
+    _addHtmlSource(code);
+    await _resolveSingleTemplate(dartSource);
+    assertErrorInCodeAtPosition(AngularWarningCode.TEMPLATE_ATTR_NOT_USED, code,
+        "*notTemplateDirective");
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_starNoDirectives() async {
+    _addDartSource(r'''
+@Component(selector: 'test-panel')
+@View(templateUrl: 'test_panel.html', directives: const [])
+class TestPanel {
+}
+''');
+    final code = r"""
+<div *foo></div>
+""";
+    _addHtmlSource(code);
+    await _resolveSingleTemplate(dartSource);
+    assertErrorInCodeAtPosition(
+        AngularWarningCode.TEMPLATE_ATTR_NOT_USED, code, "*foo");
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_customDirective_templateDoesntTakeTemplateError() async {
+    _addDartSource(r'''
+@Component(selector: 'test-panel')
+@View(templateUrl: 'test_panel.html', directives: const [NotTemplateDirective])
+class TestPanel {
+}
+
+@Directive(selector: '[notTemplateDirective]')
+class NotTemplateDirective {
+}
+''');
+    final code = r"""
+<div template="notTemplateDirective"></div>
+""";
+    _addHtmlSource(code);
+    await _resolveSingleTemplate(dartSource);
+    assertErrorInCodeAtPosition(
+        AngularWarningCode.TEMPLATE_ATTR_NOT_USED, code, 'template');
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_templateNoDirectives() async {
+    _addDartSource(r'''
+@Component(selector: 'test-panel')
+@View(templateUrl: 'test_panel.html', directives: const [])
+class TestPanel {
+}
+''');
+    final code = r"""
+<div template="foo"></div>
+""";
+    _addHtmlSource(code);
+    await _resolveSingleTemplate(dartSource);
+    assertErrorInCodeAtPosition(
+        AngularWarningCode.TEMPLATE_ATTR_NOT_USED, code, 'template');
+  }
+
+  // ignore: non_constant_identifier_names
   Future test_ngFor_star_itemHiddenInElement() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
@@ -2308,6 +2568,7 @@
     _assertElement("item == null").local.at('item of items');
   }
 
+  // ignore: non_constant_identifier_names
   Future test_ngFor_templateAttribute() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
@@ -2340,6 +2601,7 @@
     _assertElement("length}}").dart.getter;
   }
 
+  // ignore: non_constant_identifier_names
   Future test_ngFor_templateAttribute2() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
@@ -2372,6 +2634,7 @@
     _assertElement("length}}").dart.getter;
   }
 
+  // ignore: non_constant_identifier_names
   Future test_ngFor_templateElement() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
@@ -2404,6 +2667,7 @@
     _assertElement("length}}").dart.getter;
   }
 
+  // ignore: non_constant_identifier_names
   Future test_ngFor_templateElementVar() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
@@ -2425,6 +2689,7 @@
     _assertElement("item.").local.at('item [');
   }
 
+  // ignore: non_constant_identifier_names
 //  Future test_ngFor_variousKinds_useLowerIdentifier() async {
 //    _addDartSource(r'''
 //@Component(selector: 'test-panel')
@@ -2449,6 +2714,7 @@
 //    errorListener.assertNoErrors();
 //  }
 
+  // ignore: non_constant_identifier_names
   Future test_ngFor_hash_instead_of_let() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
@@ -2457,7 +2723,7 @@
   List<String> items = [];
 }
 ''');
-    var code = r"""
+    final code = r"""
 <li *ngFor='#item of items; let i = index'>
 </li>
 """;
@@ -2467,6 +2733,7 @@
         AngularWarningCode.UNEXPECTED_HASH_IN_TEMPLATE, code, "#");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_ngForSugar_dartExpression() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
@@ -2488,6 +2755,7 @@
     errorListener.assertNoErrors();
   }
 
+  // ignore: non_constant_identifier_names
   Future test_ngIf_star() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
@@ -2507,6 +2775,7 @@
     _assertElement("length != 0").dart.getter;
   }
 
+  // ignore: non_constant_identifier_names
   Future test_ngIf_noStarError() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
@@ -2515,7 +2784,7 @@
   String text; // 1
 }
 ''');
-    var code = r"""
+    final code = r"""
 <span ngIf='text.length != 0'></span>
 """;
     _addHtmlSource(code);
@@ -2526,6 +2795,7 @@
         "ngIf");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_ngIf_templateAttribute() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
@@ -2545,6 +2815,7 @@
     _assertElement("length != 0").dart.getter;
   }
 
+  // ignore: non_constant_identifier_names
   Future test_ngIf_templateElement() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
@@ -2563,6 +2834,7 @@
     _assertElement("length != 0").dart.getter;
   }
 
+  // ignore: non_constant_identifier_names
   Future test_standardHtmlComponent() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
@@ -2588,6 +2860,7 @@
     expect(ranges, hasLength(8));
   }
 
+  // ignore: non_constant_identifier_names
   Future test_standardHtmlComponentUsingRef() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
@@ -2613,10 +2886,13 @@
     expect(ranges, hasLength(8));
   }
 
+  // ignore: non_constant_identifier_names
   Future test_template_attribute_withoutValue() async {
     _addDartSource(r'''
 @Directive(selector: '[deferred-content]')
-class DeferredContentDirective {}
+class DeferredContentDirective {
+  DeferredContentDirective(TemplateRef tpl);
+}
 
 @Component(selector: 'test-panel')
 @View(
@@ -2632,6 +2908,7 @@
     errorListener.assertNoErrors();
   }
 
+  // ignore: non_constant_identifier_names
   Future test_textInterpolation() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
@@ -2653,6 +2930,7 @@
   }
 
   // see https://github.com/dart-lang/html/issues/44
+  // ignore: non_constant_identifier_names
   Future test_catchPkgHtmlGithubBug44() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
@@ -2668,6 +2946,7 @@
     // no assertion...this throws in the github bug
   }
 
+  // ignore: non_constant_identifier_names
   Future test_angleBracketInMustacheNoCrash_githubBug204() async {
     _addDartSource(r'''
 import 'dart:html';
@@ -2678,7 +2957,7 @@
   }
 }
 ''');
-    String code = r"""
+    final code = r"""
 {{<}}
     """;
     _addHtmlSource(code);
@@ -2692,6 +2971,7 @@
     ]);
   }
 
+  // ignore: non_constant_identifier_names
   Future test_resolveTemplateWithNgContentTracksSelectors() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
@@ -2699,7 +2979,7 @@
 class TestPanel {
 }
 ''');
-    String code = r"""
+    final code = r"""
 <div>
   <ng-content select="foo"></ng-content>
 </div>
@@ -2709,6 +2989,7 @@
     expect(template.view.component.ngContents, hasLength(1));
   }
 
+  // ignore: non_constant_identifier_names
   Future test_resolveTemplateWithNgContent_noSelectorIsNull() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
@@ -2716,7 +2997,7 @@
 class TestPanel {
 }
 ''');
-    String code = r"""
+    final code = r"""
 <div>
   <ng-content></ng-content>
 </div>
@@ -2727,6 +3008,7 @@
     expect(template.view.component.ngContents.first.selector, isNull);
   }
 
+  // ignore: non_constant_identifier_names
   Future test_resolveTemplateWithNgContent_selectorParseError() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
@@ -2734,7 +3016,7 @@
 class TestPanel {
 }
 ''');
-    String code = r"""
+    final code = r"""
 <div>
   <ng-content select="foo+bar"></ng-content>
 </div>
@@ -2746,6 +3028,7 @@
         AngularWarningCode.CANNOT_PARSE_SELECTOR, code, "+");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_resolveTemplateWithNgContent_emptySelectorError() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
@@ -2753,7 +3036,7 @@
 class TestPanel {
 }
 ''');
-    String code = r"""
+    final code = r"""
 <div>
   <ng-content select=""></ng-content>
 </div>
@@ -2765,6 +3048,7 @@
         AngularWarningCode.CANNOT_PARSE_SELECTOR, code, "\"\"");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_resolveTemplateWithNgContent_noValueError() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
@@ -2772,7 +3056,7 @@
 class TestPanel {
 }
 ''');
-    String code = r"""
+    final code = r"""
 <div>
   <ng-content select></ng-content>
 </div>
@@ -2784,6 +3068,7 @@
         AngularWarningCode.CANNOT_PARSE_SELECTOR, code, "select");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_resolveTemplateWithNgContent_hasContentsError() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
@@ -2791,7 +3076,7 @@
 class TestPanel {
 }
 ''');
-    String code = r"""
+    final code = r"""
 <div>
   <ng-content>with content</ng-content>
 </div>
@@ -2806,6 +3091,7 @@
     ]);
   }
 
+  // ignore: non_constant_identifier_names
   Future test_resolveTemplate_provideContentWhereInvalid() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
@@ -2817,7 +3103,7 @@
 class NoTransclude {
 }
 ''');
-    String code = r"""
+    final code = r"""
 <no-transclude>doesn't belong</no-transclude>
     """;
     _addHtmlSource(code);
@@ -2826,6 +3112,7 @@
         AngularWarningCode.CONTENT_NOT_TRANSCLUDED, code, "doesn't belong");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_resolveTemplate_provideContentNgSelectAll() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
@@ -2837,7 +3124,7 @@
 class TranscludeAll {
 }
 ''');
-    String code = r"""
+    final code = r"""
 <transclude-all>belongs</transclude-all>
     """;
     _addHtmlSource(code);
@@ -2845,6 +3132,7 @@
     errorListener.assertNoErrors();
   }
 
+  // ignore: non_constant_identifier_names
   Future test_resolveTemplate_provideContentEmptyTextAlwaysOK() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
@@ -2856,7 +3144,7 @@
 class NoTransclude {
 }
 ''');
-    String code = r"""
+    final code = r"""
 <no-transclude>
 </no-transclude>
     """;
@@ -2865,6 +3153,7 @@
     errorListener.assertNoErrors();
   }
 
+  // ignore: non_constant_identifier_names
   Future test_resolvedTag_complexSelector() async {
     _addDartSource(r'''
 import 'dart:html';
@@ -2878,7 +3167,7 @@
 class MyTag {
 }
 ''');
-    String code = r"""
+    final code = r"""
 <my-tag my-prop></my-tag>
     """;
     _addHtmlSource(code);
@@ -2886,6 +3175,7 @@
     errorListener.assertNoErrors();
   }
 
+  // ignore: non_constant_identifier_names
   Future test_resolveTemplate_provideContentNgSelectAllWithSelectors() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
@@ -2897,7 +3187,7 @@
 class TranscludeAll {
 }
 ''');
-    String code = r"""
+    final code = r"""
 <transclude-all>belongs</transclude-all>
     """;
     _addHtmlSource(code);
@@ -2905,6 +3195,7 @@
     errorListener.assertNoErrors();
   }
 
+  // ignore: non_constant_identifier_names
   Future test_resolveTemplate_provideContentNotMatchingSelectors() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
@@ -2916,7 +3207,7 @@
 class TranscludeSome {
 }
 ''');
-    String code = r"""
+    final code = r"""
 <transclude-some><div></div></transclude-some>
     """;
     _addHtmlSource(code);
@@ -2925,6 +3216,7 @@
         AngularWarningCode.CONTENT_NOT_TRANSCLUDED, code, "<div></div>");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_resolveTemplate_provideTextInfosDontMatchSelectors() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
@@ -2936,7 +3228,7 @@
 class TranscludeSome {
 }
 ''');
-    String code = r"""
+    final code = r"""
 <transclude-some>doesn't belong</transclude-some>
     """;
     _addHtmlSource(code);
@@ -2945,6 +3237,7 @@
         AngularWarningCode.CONTENT_NOT_TRANSCLUDED, code, "doesn't belong");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_resolveTemplate_provideContentMatchingSelectors() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
@@ -2956,7 +3249,7 @@
 class TranscludeSome {
 }
 ''');
-    String code = r"""
+    final code = r"""
 <transclude-some><div transclude-me></div></transclude-some>
     """;
     _addHtmlSource(code);
@@ -2964,6 +3257,7 @@
     errorListener.assertNoErrors();
   }
 
+  // ignore: non_constant_identifier_names
   Future test_resolveTemplate_provideContentMatchingSelectorsKnowsTag() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
@@ -2975,7 +3269,7 @@
 class TranscludeSome {
 }
 ''');
-    String code = r"""
+    final code = r"""
 <transclude-some><transclude-me></transclude-me></transclude-some>
     """;
     _addHtmlSource(code);
@@ -2984,6 +3278,7 @@
   }
 
   Future
+      // ignore: non_constant_identifier_names
       test_resolveTemplate_provideContentMatchingSelectorsAndAllKnowsTag() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
@@ -2997,7 +3292,7 @@
 class TranscludeAllAndKnowsTag {
 }
 ''');
-    String code = r"""
+    final code = r"""
 <transclude-all-and-knows-tag>
   <transclude-me></transclude-me>
 </transclude-all-and-knows-tag>
@@ -3008,6 +3303,7 @@
   }
 
   Future
+      // ignore: non_constant_identifier_names
       test_resolveTemplate_noDashesAroundTranscludedContent_stillError() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
@@ -3020,7 +3316,7 @@
 class TranscludeAllAndKnowsTag {
 }
 ''');
-    String code = r"""
+    final code = r"""
 <nodashes>shouldn't be allowed</nodashes>
     """;
     _addHtmlSource(code);
@@ -3030,6 +3326,7 @@
   }
 
   Future
+      // ignore: non_constant_identifier_names
       test_resolveTemplate_noDashesAroundTranscludedContent_stillMatchesTag() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
@@ -3042,7 +3339,7 @@
 class TranscludeAllAndKnowsTag {
 }
 ''');
-    String code = r"""
+    final code = r"""
 <nodashes>
   <custom-tag></custom-tag>
 </nodashes>
@@ -3053,6 +3350,7 @@
   }
 
   Future
+      // ignore: non_constant_identifier_names
       test_resolveTemplate_provideContentMatchingSelectorsReportsUnknownTag() async {
     _addDartSource(r'''
 @Component(selector: 'test-panel')
@@ -3064,7 +3362,7 @@
 class TranscludeSome {
 }
 ''');
-    String code = r"""
+    final code = r"""
 <transclude-some><unknown-tag transclude-me></unknown-tag></transclude-some>
     """;
     _addHtmlSource(code);
@@ -3073,6 +3371,7 @@
         AngularWarningCode.UNRESOLVED_TAG, code, "unknown-tag");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_unResolvedTag_evenThoughMatchedComplexSelector() async {
     _addDartSource(r'''
 import 'dart:html';
@@ -3086,7 +3385,7 @@
 class MyTag {
 }
 ''');
-    String code = r"""
+    final code = r"""
 <my-tag my-prop></my-tag>
     """;
     _addHtmlSource(code);
@@ -3099,6 +3398,7 @@
         AngularWarningCode.UNRESOLVED_TAG, code, "my-tag");
   }
 
+  // ignore: non_constant_identifier_names
   Future test_resolvedTag_evenThoughAlsoMatchesNonTagMatch() async {
     _addDartSource(r'''
 import 'dart:html';
@@ -3112,7 +3412,7 @@
 class MyTag {
 }
 ''');
-    String code = r"""
+    final code = r"""
 <my-tag red-herring unrelated></my-tag>
     """;
     _addHtmlSource(code);
@@ -3124,50 +3424,945 @@
     errorListener.assertNoErrors();
   }
 
-  void _addDartSource(String code) {
+  Future
+      // ignore: non_constant_identifier_names
+      test_resolveTemplate_provideContentNotMatchingSelectorsButMatchesContentChildElementRef() async {
+    _addDartSource(r'''
+@Component(selector: 'test-panel')
+@View(templateUrl: 'test_panel.html', directives: const [TranscludeSome])
+class TestPanel {
+}
+@Component(selector: 'transclude-some')
+@View(template: '<ng-content select="transclude-me"></ng-content>')
+class TranscludeSome {
+  @ContentChild(ElementRef)
+  ElementRef foo;
+}
+''');
+    final code = r"""
+<transclude-some><div></div></transclude-some>
+    """;
+    _addHtmlSource(code);
+    await _resolveSingleTemplate(dartSource);
+    errorListener.assertNoErrors();
+  }
+
+  Future
+      // ignore: non_constant_identifier_names
+      test_resolveTemplate_provideContentNotMatchingSelectorsButMatchesContentChildTemplateRef() async {
+    _addDartSource(r'''
+@Component(selector: 'test-panel')
+@View(templateUrl: 'test_panel.html', directives: const [TranscludeNone])
+class TestPanel {
+}
+@Component(selector: 'transclude-none')
+@View(template: '<ng-content select="transclude-me"></ng-content>')
+class TranscludeNone {
+  @ContentChild(TemplateRef)
+  TemplateRef foo;
+}
+''');
+    final code = r"""
+<transclude-none><template></template></transclude-none>
+    """;
+    _addHtmlSource(code);
+    await _resolveSingleTemplate(dartSource);
+    errorListener.assertNoErrors();
+  }
+
+  Future
+      // ignore: non_constant_identifier_names
+      test_resolveTemplate_provideContentNoTransclusionsButMatchesContentChildTemplateRef() async {
+    _addDartSource(r'''
+@Component(selector: 'test-panel')
+@View(templateUrl: 'test_panel.html', directives: const [TranscludeNone])
+class TestPanel {
+}
+@Component(selector: 'transclude-none')
+@View(template: '')
+class TranscludeNone {
+  @ContentChild(TemplateRef)
+  TemplateRef foo;
+}
+''');
+    final code = r"""
+<transclude-none><template></template></transclude-none>
+    """;
+    _addHtmlSource(code);
+    await _resolveSingleTemplate(dartSource);
+    errorListener.assertNoErrors();
+  }
+
+  Future
+      // ignore: non_constant_identifier_names
+      test_resolveTemplate_provideContentNoTransclusionsButMatchesContentChildDirective() async {
+    _addDartSource(r'''
+@Component(selector: 'test-panel')
+@View(templateUrl: 'test_panel.html',
+    directives: const [TranscludeNone, ContentChildComponent])
+class TestPanel {
+}
+@Component(selector: 'transclude-none')
+@View(template: '')
+class TranscludeNone {
+  @ContentChild(ContentChildComponent)
+  ContentChildComponent foo;
+}
+@Component(selector: 'content-child-comp', template: '')
+class ContentChildComponent {
+}
+''');
+    final code = r"""
+<transclude-none><content-child-comp></content-child-comp></transclude-none>
+    """;
+    _addHtmlSource(code);
+    await _resolveSingleTemplate(dartSource);
+    errorListener.assertNoErrors();
+  }
+
+  Future
+      // ignore: non_constant_identifier_names
+      test_resolveTemplate_provideContentNoTransclusionsButMatchesContentChildLetBoundElementRef() async {
+    _addDartSource(r'''
+@Component(selector: 'test-panel')
+@View(templateUrl: 'test_panel.html', directives: const [TranscludeNone])
+class TestPanel {
+}
+@Component(selector: 'transclude-none')
+@View(template: '')
+class TranscludeNone {
+  @ContentChild('contentChild')
+  ElementRef foo;
+  @ContentChild('contentChild')
+  dynamic fooDynamicShouldBeOk;
+  @ContentChild('contentChild')
+  Object fooObjectShouldBeOk;
+}
+''');
+    final code = r"""
+<transclude-none><div #contentChild></div></transclude-none>
+    """;
+    _addHtmlSource(code);
+    await _resolveSingleTemplate(dartSource);
+    errorListener.assertNoErrors();
+  }
+
+  Future
+      // ignore: non_constant_identifier_names
+      test_resolveTemplate_provideContentNoTransclusionsButMatchesContentChildLetBoundTemplateRef() async {
+    _addDartSource(r'''
+@Component(selector: 'test-panel')
+@View(templateUrl: 'test_panel.html', directives: const [TranscludeNone])
+class TestPanel {
+}
+@Component(selector: 'transclude-none')
+@View(template: '')
+class TranscludeNone {
+  @ContentChild('contentChild')
+  TemplateRef foo;
+  @ContentChild('contentChild')
+  dynamic fooDynamicShouldBeOk;
+  @ContentChild('contentChild')
+  Object fooObjectShouldBeOk;
+}
+''');
+    final code = r"""
+<transclude-none><template #contentChild></template></transclude-none>
+    """;
+    _addHtmlSource(code);
+    await _resolveSingleTemplate(dartSource);
+    errorListener.assertNoErrors();
+  }
+
+  Future
+      // ignore: non_constant_identifier_names
+      test_resolveTemplate_provideContentNoTransclusionsButMatchesContentChildLetBoundDirective() async {
+    _addDartSource(r'''
+@Component(selector: 'test-panel')
+@View(templateUrl: 'test_panel.html',
+    directives: const [TranscludeNone, ContentChildDirective])
+class TestPanel {
+}
+@Component(selector: 'transclude-none')
+@View(template: '')
+class TranscludeNone {
+  @ContentChild('contentChild')
+  ContentChildDirective foo;
+  @ContentChild('contentChild')
+  dynamic fooDynamicShouldBeOk;
+  @ContentChild('contentChild')
+  Object fooObjectShouldBeOk;
+  @ContentChild('contentChild')
+  Superclass fooSuperclassShouldBeOk;
+}
+@Directive(selector: '[content-child]', exportAs: 'contentChild')
+class ContentChildDirective extends Superclass {
+}
+
+class Superclass {}
+''');
+    final code = r"""
+<transclude-none><div content-child #contentChild="contentChild"></div></transclude-none>
+    """;
+    _addHtmlSource(code);
+    await _resolveSingleTemplate(dartSource);
+    errorListener.assertNoErrors();
+  }
+
+  Future
+      // ignore: non_constant_identifier_names
+      test_resolveTemplate_provideContentNoTransclusionsButMatchesContentChildLetBoundComponent() async {
+    _addDartSource(r'''
+@Component(selector: 'test-panel')
+@View(templateUrl: 'test_panel.html',
+    directives: const [TranscludeNone, ContentChildComponent])
+class TestPanel {
+}
+@Component(selector: 'transclude-none')
+@View(template: '')
+class TranscludeNone {
+  @ContentChild('contentChild')
+  ContentChildComponent foo;
+  @ContentChild('contentChild')
+  dynamic fooDynamicShouldBeOk;
+  @ContentChild('contentChild')
+  Object fooObjectShouldBeOk;
+  @ContentChild('contentChild')
+  Superclass fooSuperclassShouldBeOk;
+}
+@Component(selector: 'content-child-comp', template: '')
+class ContentChildComponent extends Superclass {
+}
+
+class Superclass {}
+''');
+    final code = r"""
+<transclude-none><content-child-comp #contentChild></content-child-comp></transclude-none>
+    """;
+    _addHtmlSource(code);
+    await _resolveSingleTemplate(dartSource);
+    errorListener.assertNoErrors();
+  }
+
+  Future
+      // ignore: non_constant_identifier_names
+      test_resolveTemplate_provideContentNotMatchingSelectorsOrContentChildElementRef() async {
+    _addDartSource(r'''
+@Component(selector: 'test-panel')
+@View(templateUrl: 'test_panel.html', directives: const [TranscludeSome])
+class TestPanel {
+}
+@Component(selector: 'transclude-some')
+@View(template: '<ng-content select="transclude-me"></ng-content>')
+class TranscludeSome {
+  @ContentChild(ElementRef)
+  ElementRef foo;
+}
+''');
+    final code = r"""
+<transclude-some><template></template></transclude-some>
+    """;
+    _addHtmlSource(code);
+    await _resolveSingleTemplate(dartSource);
+    assertErrorInCodeAtPosition(AngularWarningCode.CONTENT_NOT_TRANSCLUDED,
+        code, "<template></template>");
+  }
+
+  Future
+      // ignore: non_constant_identifier_names
+      test_resolveTemplate_provideContentNotMatchingSelectorsOrContentChildTemplateRef() async {
+    _addDartSource(r'''
+@Component(selector: 'test-panel')
+@View(templateUrl: 'test_panel.html', directives: const [TranscludeNone])
+class TestPanel {
+}
+@Component(selector: 'transclude-some')
+@View(template: '<ng-content select="transclude-me"></ng-content>')
+class TranscludeNone {
+  @ContentChild(TemplateRef)
+  TemplateRef foo;
+}
+''');
+    final code = r"""
+<transclude-some><div></div></transclude-some>
+    """;
+    _addHtmlSource(code);
+    await _resolveSingleTemplate(dartSource);
+    assertErrorInCodeAtPosition(
+        AngularWarningCode.CONTENT_NOT_TRANSCLUDED, code, "<div></div>");
+  }
+
+  Future
+      // ignore: non_constant_identifier_names
+      test_resolveTemplate_provideContentNoTransclusionsNoChildElementRefMatch() async {
+    _addDartSource(r'''
+@Component(selector: 'test-panel')
+@View(templateUrl: 'test_panel.html', directives: const [TranscludeNone])
+class TestPanel {
+}
+@Component(selector: 'transclude-none')
+@View(template: '')
+class TranscludeNone {
+  @ContentChild(ElementRef)
+  ElementRef foo;
+}
+''');
+    final code = r"""
+<transclude-none><template></template></transclude-none>
+    """;
+    _addHtmlSource(code);
+    await _resolveSingleTemplate(dartSource);
+    assertErrorInCodeAtPosition(AngularWarningCode.CONTENT_NOT_TRANSCLUDED,
+        code, "<template></template>");
+  }
+
+  Future
+      // ignore: non_constant_identifier_names
+      test_resolveTemplate_provideContentNoTransclusionsNoContentChildTemplateRefMatch() async {
+    _addDartSource(r'''
+@Component(selector: 'test-panel')
+@View(templateUrl: 'test_panel.html', directives: const [TranscludeNone])
+class TestPanel {
+}
+@Component(selector: 'transclude-none')
+@View(template: '')
+class TranscludeNone {
+  @ContentChild(TemplateRef)
+  TemplateRef foo;
+}
+''');
+    final code = r"""
+<transclude-none><div></div></transclude-none>
+    """;
+    _addHtmlSource(code);
+    await _resolveSingleTemplate(dartSource);
+    assertErrorInCodeAtPosition(
+        AngularWarningCode.CONTENT_NOT_TRANSCLUDED, code, "<div></div>");
+  }
+
+  Future
+      // ignore: non_constant_identifier_names
+      test_resolveTemplate_provideContentNoTransclusionsNoContentChildDirectiveMatch() async {
+    _addDartSource(r'''
+@Component(selector: 'test-panel')
+@View(templateUrl: 'test_panel.html',
+    directives: const [TranscludeNone, ContentChildComponent])
+class TestPanel {
+}
+@Component(selector: 'transclude-none')
+@View(template: '')
+class TranscludeNone {
+  @ContentChild(ContentChildComponent)
+  ContentChildComponent foo;
+}
+@Component(selector: 'content-child-comp', template: '')
+class ContentChildComponent {
+}
+''');
+    final code = r"""
+<transclude-none><div></div></transclude-none>
+    """;
+    _addHtmlSource(code);
+    await _resolveSingleTemplate(dartSource);
+    assertErrorInCodeAtPosition(
+        AngularWarningCode.CONTENT_NOT_TRANSCLUDED, code, "<div></div>");
+  }
+
+  Future
+      // ignore: non_constant_identifier_names
+      test_resolveTemplate_provideContentMatchingHigherComponentsIsStillNotTranscludedError() async {
+    _addDartSource(r'''
+@Component(selector: 'test-panel')
+@View(templateUrl: 'test_panel.html', directives: const [TranscludeNone, TranscludeAllWithContentChild])
+class TestPanel {
+}
+@Component(selector: 'transclude-none')
+@View(template: '')
+class TranscludeNone {
+}
+@Component(selector: 'transclude-all-with-content-child')
+@View(template: '<ng-content></ng-content>')
+class TranscludeAllWithContentChild {
+  @ContentChild("contentChildOfHigherComponent")
+  ElementRef foo;
+}
+''');
+    final code = r"""
+<transclude-all-with-content-child>
+  <transclude-none>
+    <div #contentChildOfHigherComponent></div>
+  </transclude-none>
+</transclude-all-with-content-child>
+    """;
+    _addHtmlSource(code);
+    await _resolveSingleTemplate(dartSource);
+    assertErrorInCodeAtPosition(AngularWarningCode.CONTENT_NOT_TRANSCLUDED,
+        code, "<div #contentChildOfHigherComponent></div>");
+  }
+
+  Future
+      // ignore: non_constant_identifier_names
+      test_resolveTemplate_provideContentChildLetBound_templateNotElementRef() async {
+    _addDartSource(r'''
+@Component(selector: 'test-panel')
+@View(templateUrl: 'test_panel.html', directives: const [HasContentChild])
+class TestPanel {
+}
+@Component(selector: 'has-content-child')
+@View(template: '<ng-content></ng-content>')
+class HasContentChild {
+  @ContentChild('contentChild')
+  ElementRef foo;
+}
+''');
+    final code = r"""
+<has-content-child><template #contentChild></template></has-content-child>
+    """;
+    _addHtmlSource(code);
+    await _resolveSingleTemplate(dartSource);
+    assertErrorInCodeAtPosition(
+        AngularWarningCode.MATCHED_LET_BINDING_HAS_WRONG_TYPE,
+        code,
+        "<template #contentChild></template>");
+  }
+
+  Future
+      // ignore: non_constant_identifier_names
+      test_resolveTemplate_provideContentChildLetBound_componentNotElementRef() async {
+    _addDartSource(r'''
+@Component(selector: 'test-panel')
+@View(templateUrl: 'test_panel.html', directives: const [HasContentChild, SomeComponent])
+class TestPanel {
+}
+@Component(selector: 'has-content-child')
+@View(template: '<ng-content></ng-content>')
+class HasContentChild {
+  @ContentChild('contentChild')
+  ElementRef foo;
+}
+@Component(selector: 'some-component', template: '')
+class SomeComponent {
+}
+''');
+    final code = r"""
+<has-content-child><some-component #contentChild></some-component></has-content-child>
+    """;
+    _addHtmlSource(code);
+    await _resolveSingleTemplate(dartSource);
+    assertErrorInCodeAtPosition(
+        AngularWarningCode.MATCHED_LET_BINDING_HAS_WRONG_TYPE,
+        code,
+        "<some-component #contentChild></some-component>");
+  }
+
+  Future
+      // ignore: non_constant_identifier_names
+      test_resolveTemplate_provideContentChildLetBound_directiveNotElementRef() async {
+    _addDartSource(r'''
+@Component(selector: 'test-panel')
+@View(templateUrl: 'test_panel.html', directives: const [HasContentChild, SomeDirective])
+class TestPanel {
+}
+@Component(selector: 'has-content-child')
+@View(template: '<ng-content></ng-content>')
+class HasContentChild {
+  @ContentChild('contentChild')
+  ElementRef foo;
+}
+@Directive(selector: '[some-directive]', template: '', exportAs: "theDirective")
+class SomeDirective {
+}
+''');
+    final code = r"""
+<has-content-child><div some-directive #contentChild="theDirective"></div></has-content-child>
+    """;
+    _addHtmlSource(code);
+    await _resolveSingleTemplate(dartSource);
+    assertErrorInCodeAtPosition(
+        AngularWarningCode.MATCHED_LET_BINDING_HAS_WRONG_TYPE,
+        code,
+        "<div some-directive #contentChild=\"theDirective\"></div>");
+  }
+
+  Future
+      // ignore: non_constant_identifier_names
+      test_resolveTemplate_provideContentChildLetBound_elementNotTemplateRef() async {
+    _addDartSource(r'''
+@Component(selector: 'test-panel')
+@View(templateUrl: 'test_panel.html', directives: const [HasContentChild])
+class TestPanel {
+}
+@Component(selector: 'has-content-child')
+@View(template: '<ng-content></ng-content>')
+class HasContentChild {
+  @ContentChild('contentChild')
+  TemplateRef foo;
+}
+''');
+    final code = r"""
+<has-content-child><div #contentChild></div></has-content-child>
+    """;
+    _addHtmlSource(code);
+    await _resolveSingleTemplate(dartSource);
+    assertErrorInCodeAtPosition(
+        AngularWarningCode.MATCHED_LET_BINDING_HAS_WRONG_TYPE,
+        code,
+        "<div #contentChild></div>");
+  }
+
+  Future
+      // ignore: non_constant_identifier_names
+      test_resolveTemplate_provideContentChildLetBound_componentNotTemplateRef() async {
+    _addDartSource(r'''
+@Component(selector: 'test-panel')
+@View(templateUrl: 'test_panel.html', directives: const [HasContentChild, SomeComponent])
+class TestPanel {
+}
+@Component(selector: 'has-content-child')
+@View(template: '<ng-content></ng-content>')
+class HasContentChild {
+  @ContentChild('contentChild')
+  TemplateRef foo;
+}
+@Component(selector: 'some-component', template: '')
+class SomeComponent {
+}
+''');
+    final code = r"""
+<has-content-child><some-component #contentChild></some-component></has-content-child>
+    """;
+    _addHtmlSource(code);
+    await _resolveSingleTemplate(dartSource);
+    assertErrorInCodeAtPosition(
+        AngularWarningCode.MATCHED_LET_BINDING_HAS_WRONG_TYPE,
+        code,
+        "<some-component #contentChild></some-component>");
+  }
+
+  Future
+      // ignore: non_constant_identifier_names
+      test_resolveTemplate_provideContentChildLetBound_directiveNotTemplateRef() async {
+    _addDartSource(r'''
+@Component(selector: 'test-panel')
+@View(templateUrl: 'test_panel.html', directives: const [HasContentChild, SomeDirective])
+class TestPanel {
+}
+@Component(selector: 'has-content-child')
+@View(template: '<ng-content></ng-content>')
+class HasContentChild {
+  @ContentChild('contentChild')
+  TemplateRef foo;
+}
+@Directive(selector: '[some-directive]', template: '', exportAs: "theDirective")
+class SomeDirective {
+}
+''');
+    final code = r"""
+<has-content-child><div some-directive #contentChild></div></has-content-child>
+    """;
+    _addHtmlSource(code);
+    await _resolveSingleTemplate(dartSource);
+    assertErrorInCodeAtPosition(
+        AngularWarningCode.MATCHED_LET_BINDING_HAS_WRONG_TYPE,
+        code,
+        "<div some-directive #contentChild></div>");
+  }
+
+  Future
+      // ignore: non_constant_identifier_names
+      test_resolveTemplate_provideContentChildLetBound_elementNotComponent() async {
+    _addDartSource(r'''
+@Component(selector: 'test-panel')
+@View(templateUrl: 'test_panel.html', directives: const [HasContentChild])
+class TestPanel {
+}
+@Component(selector: 'has-content-child')
+@View(template: '<ng-content></ng-content>')
+class HasContentChild {
+  @ContentChild('contentChild')
+  SomeComponent foo;
+}
+@Component(selector: 'some-component', template: '')
+class SomeComponent {
+}
+''');
+    final code = r"""
+<has-content-child><div #contentChild></div></has-content-child>
+    """;
+    _addHtmlSource(code);
+    await _resolveSingleTemplate(dartSource);
+    assertErrorInCodeAtPosition(
+        AngularWarningCode.MATCHED_LET_BINDING_HAS_WRONG_TYPE,
+        code,
+        "<div #contentChild></div>");
+  }
+
+  Future
+      // ignore: non_constant_identifier_names
+      test_resolveTemplate_provideContentChildLetBound_templateNotComponent() async {
+    _addDartSource(r'''
+@Component(selector: 'test-panel')
+@View(templateUrl: 'test_panel.html', directives: const [HasContentChild])
+class TestPanel {
+}
+@Component(selector: 'has-content-child')
+@View(template: '<ng-content></ng-content>')
+class HasContentChild {
+  @ContentChild('contentChild')
+  SomeComponent foo;
+}
+@Component(selector: 'some-component', template: '')
+class SomeComponent {
+}
+''');
+    final code = r"""
+<has-content-child><template #contentChild></template></has-content-child>
+    """;
+    _addHtmlSource(code);
+    await _resolveSingleTemplate(dartSource);
+    assertErrorInCodeAtPosition(
+        AngularWarningCode.MATCHED_LET_BINDING_HAS_WRONG_TYPE,
+        code,
+        "<template #contentChild></template>");
+  }
+
+  Future
+      // ignore: non_constant_identifier_names
+      test_resolveTemplate_provideContentChildLetBound_wrongComponent() async {
+    _addDartSource(r'''
+@Component(selector: 'test-panel')
+@View(templateUrl: 'test_panel.html', directives: const [HasContentChild, SomeOtherComponent])
+class TestPanel {
+}
+@Component(selector: 'has-content-child')
+@View(template: '<ng-content></ng-content>')
+class HasContentChild {
+  @ContentChild('contentChild')
+  SomeComponent foo;
+}
+@Component(selector: 'some-component', template: '')
+class SomeComponent {
+}
+@Component(selector: 'some-other-component', template: '')
+class SomeOtherComponent {
+}
+''');
+    final code = r"""
+<has-content-child><some-other-component #contentChild></some-other-component></has-content-child>
+    """;
+    _addHtmlSource(code);
+    await _resolveSingleTemplate(dartSource);
+    assertErrorInCodeAtPosition(
+        AngularWarningCode.MATCHED_LET_BINDING_HAS_WRONG_TYPE,
+        code,
+        "<some-other-component #contentChild></some-other-component>");
+  }
+
+  Future
+      // ignore: non_constant_identifier_names
+      test_resolveTemplate_provideContentChildLetBound_elementNotDirective() async {
+    _addDartSource(r'''
+@Component(selector: 'test-panel')
+@View(templateUrl: 'test_panel.html', directives: const [HasContentChild])
+class TestPanel {
+}
+@Component(selector: 'has-content-child')
+@View(template: '<ng-content></ng-content>')
+class HasContentChild {
+  @ContentChild('contentChild')
+  SomeDirective foo;
+}
+@Directive(selector: '[some-directive]')
+class SomeDirective {
+}
+''');
+    final code = r"""
+<has-content-child><div #contentChild></div></has-content-child>
+    """;
+    _addHtmlSource(code);
+    await _resolveSingleTemplate(dartSource);
+    assertErrorInCodeAtPosition(
+        AngularWarningCode.MATCHED_LET_BINDING_HAS_WRONG_TYPE,
+        code,
+        "<div #contentChild></div>");
+  }
+
+  Future
+      // ignore: non_constant_identifier_names
+      test_resolveTemplate_provideContentChildLetBound_element_directiveNotExported() async {
+    _addDartSource(r'''
+@Component(selector: 'test-panel')
+@View(templateUrl: 'test_panel.html', directives: const [HasContentChild, SomeDirective])
+class TestPanel {
+}
+@Component(selector: 'has-content-child')
+@View(template: '<ng-content></ng-content>')
+class HasContentChild {
+  @ContentChild('contentChild')
+  SomeDirective foo;
+}
+@Directive(selector: '[some-directive]')
+class SomeDirective {
+}
+''');
+    final code = r"""
+<has-content-child><div some-directive #contentChild></div></has-content-child>
+    """;
+    _addHtmlSource(code);
+    await _resolveSingleTemplate(dartSource);
+    assertErrorInCodeAtPosition(
+        AngularWarningCode.MATCHED_LET_BINDING_HAS_WRONG_TYPE,
+        code,
+        "<div some-directive #contentChild></div>");
+  }
+
+  Future
+      // ignore: non_constant_identifier_names
+      test_resolveTemplate_provideContentChildLetBound_templateNotDirective() async {
+    _addDartSource(r'''
+@Component(selector: 'test-panel')
+@View(templateUrl: 'test_panel.html', directives: const [HasContentChild])
+class TestPanel {
+}
+@Component(selector: 'has-content-child')
+@View(template: '<ng-content></ng-content>')
+class HasContentChild {
+  @ContentChild('contentChild')
+  SomeDirective foo;
+}
+@Directive(selector: '[some-directive]')
+class SomeDirective {
+}
+''');
+    final code = r"""
+<has-content-child><template #contentChild></template></has-content-child>
+    """;
+    _addHtmlSource(code);
+    await _resolveSingleTemplate(dartSource);
+    assertErrorInCodeAtPosition(
+        AngularWarningCode.MATCHED_LET_BINDING_HAS_WRONG_TYPE,
+        code,
+        "<template #contentChild></template>");
+  }
+
+  Future
+      // ignore: non_constant_identifier_names
+      test_resolveTemplate_provideContentChildLetBound_wrongDirective() async {
+    _addDartSource(r'''
+@Component(selector: 'test-panel')
+@View(templateUrl: 'test_panel.html', directives: const [HasContentChild, SomeOtherDirective])
+class TestPanel {
+}
+@Component(selector: 'has-content-child')
+@View(template: '<ng-content></ng-content>')
+class HasContentChild {
+  @ContentChild('contentChild')
+  SomeDirective foo;
+}
+@Directive(selector: '[some-directive]', exportAs: 'right')
+class SomeDirective {
+}
+@Directive(selector: '[some-other-directive]', exportAs: 'wrong')
+class SomeOtherDirective {
+}
+''');
+    final code = r"""
+<has-content-child><div some-other-directive #contentChild="wrong"></div></has-content-child>
+    """;
+    _addHtmlSource(code);
+    await _resolveSingleTemplate(dartSource);
+    assertErrorInCodeAtPosition(
+        AngularWarningCode.MATCHED_LET_BINDING_HAS_WRONG_TYPE,
+        code,
+        "<div some-other-directive #contentChild=\"wrong\"></div>");
+  }
+
+  Future
+      // ignore: non_constant_identifier_names
+      test_resolveTemplate_provideContentChildLetBound_directiveNotElementRef_deeplyNested() async {
+    _addDartSource(r'''
+@Component(selector: 'test-panel')
+@View(templateUrl: 'test_panel.html', directives: const [HasContentChild, SomeDirective])
+class TestPanel {
+}
+@Component(selector: 'has-content-child')
+@View(template: '<ng-content></ng-content>')
+class HasContentChild {
+  @ContentChild('contentChild')
+  ElementRef foo;
+}
+@Directive(selector: '[some-directive]', template: '', exportAs: "theDirective")
+class SomeDirective {
+}
+''');
+    final code = r"""
+<has-content-child>
+  <div>
+    <span>
+      <div>
+        <div some-directive #contentChild="theDirective"></div>
+      </div>
+    </span>
+  </div>
+</has-content-child>
+    """;
+    _addHtmlSource(code);
+    await _resolveSingleTemplate(dartSource);
+    assertErrorInCodeAtPosition(
+        AngularWarningCode.MATCHED_LET_BINDING_HAS_WRONG_TYPE,
+        code,
+        "<div some-directive #contentChild=\"theDirective\"></div>");
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_resolveTemplate_provideDuplicateContentChildError() async {
+    _addDartSource(r'''
+@Component(selector: 'test-panel')
+@View(templateUrl: 'test_panel.html', directives: const [HasContentChildElementRef])
+class TestPanel {
+}
+@Component(selector: 'has-content-child-element-ref')
+@View(template: '')
+class HasContentChildElementRef {
+  @ContentChild(ElementRef)
+  ElementRef theElement;
+}
+''');
+    final code = r"""
+<has-content-child-element-ref>
+  <div first></div>
+  <div second></div>
+</has-content-child-element-ref>
+    """;
+    _addHtmlSource(code);
+    await _resolveSingleTemplate(dartSource);
+    assertErrorInCodeAtPosition(
+        AngularWarningCode.SINGULAR_CHILD_QUERY_MATCHED_MULTIPLE_TIMES,
+        code,
+        "<div second></div>");
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_resolveTemplate_provideDuplicateContentChildrenOk() async {
+    _addDartSource(r'''
+@Component(selector: 'test-panel')
+@View(templateUrl: 'test_panel.html', directives: const [HasContentChildrenElementRef])
+class TestPanel {
+}
+@Component(selector: 'has-content-children-element-ref')
+@View(template: '')
+class HasContentChildrenElementRef {
+  @ContentChildren(ElementRef)
+  QueryList<ElementRef> theElement;
+}
+''');
+    final code = r"""
+<has-content-children-element-ref>
+  <div first></div>
+  <div second></div>
+</has-content-children-element-ref>
+    """;
+    _addHtmlSource(code);
+    await _resolveSingleTemplate(dartSource);
+    errorListener.assertNoErrors();
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_resolveTemplate_provideDuplicateContentChildNestedOk() async {
+    _addDartSource(r'''
+@Component(selector: 'test-panel')
+@View(templateUrl: 'test_panel.html', directives: const [HasContentChildElementRef])
+class TestPanel {
+}
+@Component(selector: 'has-content-child-element-ref')
+@View(template: '')
+class HasContentChildElementRef {
+  @ContentChild(ElementRef)
+  ElementRef theElement;
+}
+''');
+    final code = r"""
+<has-content-child-element-ref>
+  <div first>
+    <div second></div>
+  </div>
+</has-content-child-element-ref>
+    """;
+    _addHtmlSource(code);
+    await _resolveSingleTemplate(dartSource);
+    errorListener.assertNoErrors();
+  }
+
+  Future
+      // ignore: non_constant_identifier_names
+      test_resolveTemplate_provideDuplicateContentChildSiblingsError() async {
+    _addDartSource(r'''
+@Component(selector: 'test-panel')
+@View(templateUrl: 'test_panel.html', directives: const [HasContentChildTemplateRef])
+class TestPanel {
+}
+@Component(selector: 'has-content-child-template-ref')
+@View(template: '<ng-content></ng-content>')
+class HasContentChildTemplateRef {
+  @ContentChild(TemplateRef)
+  TemplateRef theTemplate;
+}
+''');
+    final code = r"""
+<has-content-child-template-ref>
+  <div>
+    <template first></template>
+  </div>
+  <div>
+    <template second></template>
+  </div>
+</has-content-child-template-ref>
+    """;
+    _addHtmlSource(code);
+    await _resolveSingleTemplate(dartSource);
+    assertErrorInCodeAtPosition(
+        AngularWarningCode.SINGULAR_CHILD_QUERY_MATCHED_MULTIPLE_TIMES,
+        code,
+        "<template second></template>");
+  }
+
+  void _addDartSource(final code) {
     dartCode = '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 $code
 ''';
     dartSource = newSource('/test_panel.dart', dartCode);
   }
 
-  void _addHtmlSource(String code) {
+  void _addHtmlSource(final code) {
     htmlCode = code;
     htmlSource = newSource('/test_panel.html', htmlCode);
   }
 
   ElementAssert _assertElement(String atString,
       [ResolvedRangeCondition condition]) {
-    ResolvedRange resolvedRange = _findResolvedRange(atString, condition);
+    final resolvedRange = _findResolvedRange(atString, condition);
     return new ElementAssert(dartCode, dartSource, htmlCode, htmlSource,
         resolvedRange.element, resolvedRange.range.offset);
   }
 
-  ElementAssert _assertInputElement(String atString) {
-    return _assertElement(atString, _isInputElement);
-  }
+  ElementAssert _assertInputElement(String atString) =>
+      _assertElement(atString, _isInputElement);
 
-  ElementAssert _assertSelectorElement(String atString) {
-    return _assertElement(atString, _isSelectorName);
-  }
+  ElementAssert _assertSelectorElement(String atString) =>
+      _assertElement(atString, _isSelectorName);
 
-  /**
-   * Return the [ResolvedRange] that starts at the position of the give
-   * [search] and, if specified satisfies the given [condition].
-   */
+  /// Return the [ResolvedRange] that starts at the position of the give
+  /// [search] and, if specified satisfies the given [condition].
   ResolvedRange _findResolvedRange(String search,
-      [ResolvedRangeCondition condition]) {
-    return getResolvedRangeAtString(htmlCode, ranges, search, condition);
-  }
+          [ResolvedRangeCondition condition]) =>
+      getResolvedRangeAtString(htmlCode, ranges, search, condition);
 
-  /**
-   * Compute all the views declared in the given [dartSource], and resolve the
-   * external template of the last one.
-   */
+  /// Compute all the views declared in the given [dartSource], and resolve the
+  /// external template of the last one.
   Future _resolveSingleTemplate(Source dartSource) async {
     final result = await angularDriver.resolveDart(dartSource.fullName);
-    final finder = (AbstractDirective d) =>
+    bool finder(AbstractDirective d) =>
         d is Component && d.view.templateUriSource != null;
     fillErrorListener(result.errors);
     errorListener.assertNoErrors();
@@ -3181,13 +4376,11 @@
     ranges = template.ranges;
   }
 
-  static bool _isInputElement(ResolvedRange region) {
-    return region.element is InputElement;
-  }
+  static bool _isInputElement(ResolvedRange region) =>
+      region.element is InputElement;
 
-  static bool _isSelectorName(ResolvedRange region) {
-    return region.element is SelectorName;
-  }
+  static bool _isSelectorName(ResolvedRange region) =>
+      region.element is SelectorName;
 }
 
 class ElementSearch extends AngularAstVisitor {
diff --git a/analyzer_plugin/test/selector_test.dart b/analyzer_plugin/test/selector_test.dart
index f92b54b..8e855a7 100644
--- a/analyzer_plugin/test/selector_test.dart
+++ b/analyzer_plugin/test/selector_test.dart
@@ -7,7 +7,7 @@
 import 'package:typed_mock/typed_mock.dart';
 import 'package:unittest/unittest.dart';
 
-main() {
+void main() {
   defineReflectiveSuite(() {
     defineReflectiveTests(AndSelectorTest);
     defineReflectiveTests(AttributeSelectorTest);
@@ -24,12 +24,13 @@
 
 @reflectiveTest
 class AndSelectorTest extends _SelectorTest {
-  Selector selector1 = new _SelectorMock('aaa');
-  Selector selector2 = new _SelectorMock('bbb');
-  Selector selector3 = new _SelectorMock('ccc');
+  final selector1 = new _SelectorMock('aaa');
+  final selector2 = new _SelectorMock('bbb');
+  final selector3 = new _SelectorMock('ccc');
 
   AndSelector selector;
 
+  @override
   void setUp() {
     super.setUp();
     selector = new AndSelector(<Selector>[selector1, selector2, selector3]);
@@ -41,6 +42,7 @@
         .thenReturn(SelectorMatch.NonTagMatch);
   }
 
+  // ignore: non_constant_identifier_names
   void test_match() {
     expect(
         selector.match(element, template), equals(SelectorMatch.NonTagMatch));
@@ -49,6 +51,7 @@
     verify(selector1.match(anyObject, anyObject)).times(2);
   }
 
+  // ignore: non_constant_identifier_names
   void test_match_false1() {
     when(selector1.match(anyObject, anyObject))
         .thenReturn(SelectorMatch.NoMatch);
@@ -58,6 +61,7 @@
     verify(selector3.match(anyObject, anyObject)).times(0);
   }
 
+  // ignore: non_constant_identifier_names
   void test_match_false2() {
     when(selector2.match(anyObject, anyObject))
         .thenReturn(SelectorMatch.NoMatch);
@@ -67,6 +71,7 @@
     verify(selector3.match(anyObject, anyObject)).times(0);
   }
 
+  // ignore: non_constant_identifier_names
   void test_match_falseTagMatch() {
     when(selector1.match(anyObject, anyObject))
         .thenReturn(SelectorMatch.TagMatch);
@@ -78,6 +83,7 @@
     verify(selector3.match(anyObject, anyObject)).times(0);
   }
 
+  // ignore: non_constant_identifier_names
   void test_match_TagMatch1() {
     when(selector1.match(anyObject, anyObject))
         .thenReturn(SelectorMatch.TagMatch);
@@ -87,6 +93,7 @@
     verify(selector3.match(anyObject, anyObject)).times(2);
   }
 
+  // ignore: non_constant_identifier_names
   void test_match_TagMatch2() {
     when(selector2.match(anyObject, anyObject))
         .thenReturn(SelectorMatch.TagMatch);
@@ -96,6 +103,7 @@
     verify(selector3.match(anyObject, anyObject)).times(2);
   }
 
+  // ignore: non_constant_identifier_names
   void test_toString() {
     expect(selector.toString(), 'aaa && bbb && ccc');
   }
@@ -106,25 +114,28 @@
   final AngularElement nameElement =
       new AngularElementImpl('kind', 10, 5, null);
 
+  // ignore: non_constant_identifier_names
   void test_match_notName() {
-    AttributeSelector selector =
-        new AttributeSelector(nameElement, null, false);
+    final selector =
+        new AttributeSelector(nameElement, null, isWildcard: false);
     when(element.attributes).thenReturn({'not-kind': 'no-matter'});
     expect(selector.match(element, template), equals(SelectorMatch.NoMatch));
   }
 
+  // ignore: non_constant_identifier_names
   void test_match_notValue() {
-    AttributeSelector selector =
-        new AttributeSelector(nameElement, 'silly', false);
+    final selector =
+        new AttributeSelector(nameElement, 'silly', isWildcard: false);
     when(element.attributes).thenReturn({'kind': 'strange'});
     when(element.attributeNameSpans)
         .thenReturn({'kind': _newStringSpan(100, "kind")});
     expect(selector.match(element, template), equals(SelectorMatch.NoMatch));
   }
 
+  // ignore: non_constant_identifier_names
   void test_match_noValue() {
-    AttributeSelector selector =
-        new AttributeSelector(nameElement, null, false);
+    final selector =
+        new AttributeSelector(nameElement, null, isWildcard: false);
     when(element.attributes).thenReturn({'kind': 'no-matter'});
     when(element.attributeNameSpans)
         .thenReturn({'kind': _newStringSpan(100, "kind")});
@@ -134,8 +145,9 @@
     _assertRange(resolvedRanges[0], 100, 4, selector.nameElement);
   }
 
+  // ignore: non_constant_identifier_names
   void test_match_wildCard() {
-    AttributeSelector selector = new AttributeSelector(nameElement, null, true);
+    final selector = new AttributeSelector(nameElement, null, isWildcard: true);
     when(element.attributes).thenReturn({'kindatrue': 'no-matter'});
     when(element.attributeNameSpans)
         .thenReturn({'kindatrue': _newStringSpan(100, "kindatrue")});
@@ -145,8 +157,9 @@
     _assertRange(resolvedRanges[0], 100, 9, selector.nameElement);
   }
 
+  // ignore: non_constant_identifier_names
   void test_noMatch_wildCard() {
-    AttributeSelector selector = new AttributeSelector(nameElement, null, true);
+    final selector = new AttributeSelector(nameElement, null, isWildcard: true);
     when(element.attributes).thenReturn({'indatrue': 'no-matter'});
     when(element.attributeNameSpans)
         .thenReturn({'indatrue': _newStringSpan(100, "indatrue")});
@@ -154,42 +167,47 @@
     expect(selector.match(element, template), equals(SelectorMatch.NoMatch));
   }
 
+  // ignore: non_constant_identifier_names
   void test_toString_hasValue() {
-    AttributeSelector selector =
-        new AttributeSelector(nameElement, 'daffy', false);
+    final selector =
+        new AttributeSelector(nameElement, 'daffy', isWildcard: false);
     expect(selector.toString(), '[kind=daffy]');
   }
 
+  // ignore: non_constant_identifier_names
   void test_toString_noValue() {
-    AttributeSelector selector =
-        new AttributeSelector(nameElement, null, false);
+    final selector =
+        new AttributeSelector(nameElement, null, isWildcard: false);
     expect(selector.toString(), '[kind]');
   }
 }
 
 @reflectiveTest
 class ClassSelectorTest extends _SelectorTest {
-  final AngularElement nameElement =
-      new AngularElementImpl('nice', 10, 5, null);
+  final nameElement = new AngularElementImpl('nice', 10, 5, null);
   ClassSelector selector;
 
+  @override
   void setUp() {
     super.setUp();
     selector = new ClassSelector(nameElement);
   }
 
+  // ignore: non_constant_identifier_names
   void test_match_false_noClass() {
     when(element.attributes).thenReturn({'not-class': 'no-matter'});
     expect(selector.match(element, template), equals(SelectorMatch.NoMatch));
   }
 
+  // ignore: non_constant_identifier_names
   void test_match_false_noSuchClass() {
     when(element.attributes).thenReturn({'class': 'not-nice'});
     expect(selector.match(element, template), equals(SelectorMatch.NoMatch));
   }
 
+  // ignore: non_constant_identifier_names
   void test_match_true_first() {
-    String classValue = 'nice some other';
+    final classValue = 'nice some other';
     when(element.attributes).thenReturn({'class': classValue});
     when(element.attributeValueSpans)
         .thenReturn({'class': _newStringSpan(100, classValue)});
@@ -199,8 +217,9 @@
     _assertRange(resolvedRanges[0], 100, 4, selector.nameElement);
   }
 
+  // ignore: non_constant_identifier_names
   void test_match_true_last() {
-    String classValue = 'some other nice';
+    final classValue = 'some other nice';
     when(element.attributes).thenReturn({'class': classValue});
     when(element.attributeValueSpans)
         .thenReturn({'class': _newStringSpan(100, classValue)});
@@ -210,8 +229,9 @@
     _assertRange(resolvedRanges[0], 111, 4, selector.nameElement);
   }
 
+  // ignore: non_constant_identifier_names
   void test_match_true_middle() {
-    String classValue = 'some nice other';
+    final classValue = 'some nice other';
     when(element.attributes).thenReturn({'class': classValue});
     when(element.attributeValueSpans)
         .thenReturn({'class': _newStringSpan(100, classValue)});
@@ -221,6 +241,7 @@
     _assertRange(resolvedRanges[0], 105, 4, selector.nameElement);
   }
 
+  // ignore: non_constant_identifier_names
   void test_toString() {
     expect(selector.toString(), '.nice');
   }
@@ -230,12 +251,14 @@
 class ElementNameSelectorTest extends _SelectorTest {
   ElementNameSelector selector;
 
+  @override
   void setUp() {
     super.setUp();
     selector =
         new ElementNameSelector(new AngularElementImpl('panel', 10, 5, null));
   }
 
+  // ignore: non_constant_identifier_names
   void test_match() {
     when(element.localName).thenReturn('panel');
     when(element.openingNameSpan).thenReturn(_newStringSpan(100, 'panel'));
@@ -245,11 +268,13 @@
     _assertRange(resolvedRanges[1], 200, 5, selector.nameElement);
   }
 
+  // ignore: non_constant_identifier_names
   void test_match_not() {
     when(element.localName).thenReturn('not-panel');
     expect(selector.match(element, template), equals(SelectorMatch.NoMatch));
   }
 
+  // ignore: non_constant_identifier_names
   void test_toString() {
     expect(selector.toString(), 'panel');
   }
@@ -257,25 +282,29 @@
 
 @reflectiveTest
 class AttributeValueRegexSelectorTest extends _SelectorTest {
-  AttributeValueRegexSelector selector = new AttributeValueRegexSelector("abc");
+  final selector = new AttributeValueRegexSelector("abc");
 
+  // ignore: non_constant_identifier_names
   void test_noMatch() {
     when(element.attributes).thenReturn({'kind': 'bcd'});
     expect(selector.match(element, template), equals(SelectorMatch.NoMatch));
   }
 
+  // ignore: non_constant_identifier_names
   void test_noMatch_any() {
     when(element.attributes)
         .thenReturn({'kind': 'bcd', 'plop': 'cde', 'klark': 'efg'});
     expect(selector.match(element, template), equals(SelectorMatch.NoMatch));
   }
 
+  // ignore: non_constant_identifier_names
   void test_match() {
     when(element.attributes).thenReturn({'kind': '0abcd'});
     expect(
         selector.match(element, template), equals(SelectorMatch.NonTagMatch));
   }
 
+  // ignore: non_constant_identifier_names
   void test_match_justOne() {
     when(element.attributes)
         .thenReturn({'kind': 'bcd', 'plop': 'zabcz', 'klark': 'efg'});
@@ -286,15 +315,17 @@
 
 @reflectiveTest
 class NotSelectorTest extends _SelectorTest {
-  Selector condition = new _SelectorMock('aaa');
+  final condition = new _SelectorMock('aaa');
 
   NotSelector selector;
 
+  @override
   void setUp() {
     super.setUp();
     selector = new NotSelector(condition);
   }
 
+  // ignore: non_constant_identifier_names
   void test_notFalse() {
     when(condition.match(anyObject, anyObject))
         .thenReturn(SelectorMatch.NoMatch);
@@ -302,12 +333,14 @@
         selector.match(element, template), equals(SelectorMatch.NonTagMatch));
   }
 
+  // ignore: non_constant_identifier_names
   void test_notTagMatch() {
     when(condition.match(anyObject, anyObject))
         .thenReturn(SelectorMatch.TagMatch);
     expect(selector.match(element, template), equals(SelectorMatch.NoMatch));
   }
 
+  // ignore: non_constant_identifier_names
   void test_notNonTagMatch() {
     when(condition.match(anyObject, anyObject))
         .thenReturn(SelectorMatch.NonTagMatch);
@@ -317,12 +350,13 @@
 
 @reflectiveTest
 class OrSelectorTest extends _SelectorTest {
-  Selector selector1 = new _SelectorMock('aaa');
-  Selector selector2 = new _SelectorMock('bbb');
-  Selector selector3 = new _SelectorMock('ccc');
+  final selector1 = new _SelectorMock('aaa');
+  final selector2 = new _SelectorMock('bbb');
+  final selector3 = new _SelectorMock('ccc');
 
   OrSelector selector;
 
+  @override
   void setUp() {
     super.setUp();
     selector = new OrSelector(<Selector>[selector1, selector2, selector3]);
@@ -334,6 +368,7 @@
         .thenReturn(SelectorMatch.NoMatch);
   }
 
+  // ignore: non_constant_identifier_names
   void test_matchFirstIsTagMatch() {
     when(selector1.match(anyObject, anyObject))
         .thenReturn(SelectorMatch.TagMatch);
@@ -343,6 +378,7 @@
     verify(selector3.match(anyObject, anyObject)).times(0);
   }
 
+  // ignore: non_constant_identifier_names
   void test_matchFirstIsNonTagMatch() {
     when(selector1.match(anyObject, anyObject))
         .thenReturn(SelectorMatch.NonTagMatch);
@@ -353,6 +389,7 @@
     verify(selector3.match(anyObject, anyObject)).times(1);
   }
 
+  // ignore: non_constant_identifier_names
   void test_match2TagMatch() {
     when(selector2.match(anyObject, anyObject))
         .thenReturn(SelectorMatch.TagMatch);
@@ -362,6 +399,7 @@
     verify(selector3.match(anyObject, anyObject)).times(0);
   }
 
+  // ignore: non_constant_identifier_names
   void test_match2NonTagMatch() {
     when(selector2.match(anyObject, anyObject))
         .thenReturn(SelectorMatch.NonTagMatch);
@@ -372,6 +410,7 @@
     verify(selector3.match(anyObject, anyObject)).times(1);
   }
 
+  // ignore: non_constant_identifier_names
   void test_match2TagAndNonTagMatch() {
     when(selector1.match(anyObject, anyObject))
         .thenReturn(SelectorMatch.NonTagMatch);
@@ -383,6 +422,7 @@
     verify(selector3.match(anyObject, anyObject)).times(0);
   }
 
+  // ignore: non_constant_identifier_names
   void test_match_false() {
     expect(selector.match(element, template), equals(SelectorMatch.NoMatch));
     verify(selector1.match(anyObject, anyObject)).times(1);
@@ -390,6 +430,7 @@
     verify(selector3.match(anyObject, anyObject)).times(1);
   }
 
+  // ignore: non_constant_identifier_names
   void test_toString() {
     expect(selector.toString(), 'aaa || bbb || ccc');
   }
@@ -399,22 +440,23 @@
 class SelectorParserTest {
   final Source source = new _SourceMock();
 
+  // ignore: non_constant_identifier_names
   void test_and() {
-    AndSelector selector =
+    final AndSelector selector =
         new SelectorParser(source, 10, '[ng-for][ng-for-of]').parse();
-    expect(selector, new isInstanceOf<AndSelector>());
+    expect(selector, const isInstanceOf<AndSelector>());
     expect(selector.selectors, hasLength(2));
     {
-      AttributeSelector subSelector = selector.selectors[0];
-      AngularElement nameElement = subSelector.nameElement;
+      final AttributeSelector subSelector = selector.selectors[0];
+      final nameElement = subSelector.nameElement;
       expect(nameElement.source, source);
       expect(nameElement.name, 'ng-for');
       expect(nameElement.nameOffset, 11);
       expect(nameElement.nameLength, 'ng-for'.length);
     }
     {
-      AttributeSelector subSelector = selector.selectors[1];
-      AngularElement nameElement = subSelector.nameElement;
+      final AttributeSelector subSelector = selector.selectors[1];
+      final nameElement = subSelector.nameElement;
       expect(nameElement.source, source);
       expect(nameElement.name, 'ng-for-of');
       expect(nameElement.nameOffset, 19);
@@ -422,12 +464,13 @@
     }
   }
 
+  // ignore: non_constant_identifier_names
   void test_attribute_hasValue() {
-    AttributeSelector selector =
+    final AttributeSelector selector =
         new SelectorParser(source, 10, '[kind=pretty]').parse();
-    expect(selector, new isInstanceOf<AttributeSelector>());
+    expect(selector, const isInstanceOf<AttributeSelector>());
     {
-      AngularElement nameElement = selector.nameElement;
+      final nameElement = selector.nameElement;
       expect(nameElement.source, source);
       expect(nameElement.name, 'kind');
       expect(nameElement.nameOffset, 11);
@@ -436,12 +479,13 @@
     expect(selector.value, 'pretty');
   }
 
+  // ignore: non_constant_identifier_names
   void test_attribute_hasWildcard() {
-    AttributeSelector selector =
+    final AttributeSelector selector =
         new SelectorParser(source, 10, '[kind*=pretty]').parse();
-    expect(selector, new isInstanceOf<AttributeSelector>());
+    expect(selector, const isInstanceOf<AttributeSelector>());
     {
-      AngularElement nameElement = selector.nameElement;
+      final nameElement = selector.nameElement;
       expect(nameElement.source, source);
       expect(nameElement.name, 'kind');
       expect(nameElement.nameOffset, 11);
@@ -451,19 +495,21 @@
     expect(selector.isWildcard, true);
   }
 
+  // ignore: non_constant_identifier_names
   void test_attribute_textRegex() {
-    AttributeValueRegexSelector selector =
+    final AttributeValueRegexSelector selector =
         new SelectorParser(source, 10, '[*=/pretty/]').parse();
-    expect(selector, new isInstanceOf<AttributeValueRegexSelector>());
+    expect(selector, const isInstanceOf<AttributeValueRegexSelector>());
     expect(selector.regexpStr, 'pretty');
   }
 
+  // ignore: non_constant_identifier_names
   void test_attribute_noValue() {
-    AttributeSelector selector =
+    final AttributeSelector selector =
         new SelectorParser(source, 10, '[ng-for]').parse();
-    expect(selector, new isInstanceOf<AttributeSelector>());
+    expect(selector, const isInstanceOf<AttributeSelector>());
     {
-      AngularElement nameElement = selector.nameElement;
+      final nameElement = selector.nameElement;
       expect(nameElement.source, source);
       expect(nameElement.name, 'ng-for');
       expect(nameElement.nameOffset, 11);
@@ -472,6 +518,7 @@
     expect(selector.value, isNull);
   }
 
+  // ignore: non_constant_identifier_names
   void test_bad() {
     try {
       new SelectorParser(source, 0, '+name').parse();
@@ -481,42 +528,47 @@
     fail("was supposed to throw");
   }
 
+  // ignore: non_constant_identifier_names
   void test_class() {
-    ClassSelector selector = new SelectorParser(source, 10, '.nice').parse();
-    expect(selector, new isInstanceOf<ClassSelector>());
-    AngularElement nameElement = selector.nameElement;
+    final ClassSelector selector =
+        new SelectorParser(source, 10, '.nice').parse();
+    expect(selector, const isInstanceOf<ClassSelector>());
+    final nameElement = selector.nameElement;
     expect(nameElement.source, source);
     expect(nameElement.name, 'nice');
     expect(nameElement.nameOffset, 11);
     expect(nameElement.nameLength, 'nice'.length);
   }
 
+  // ignore: non_constant_identifier_names
   void test_elementName() {
-    ElementNameSelector selector =
+    final ElementNameSelector selector =
         new SelectorParser(source, 10, 'text-panel').parse();
-    expect(selector, new isInstanceOf<ElementNameSelector>());
-    AngularElement nameElement = selector.nameElement;
+    expect(selector, const isInstanceOf<ElementNameSelector>());
+    final nameElement = selector.nameElement;
     expect(nameElement.source, source);
     expect(nameElement.name, 'text-panel');
     expect(nameElement.nameOffset, 10);
     expect(nameElement.nameLength, 'text-panel'.length);
   }
 
+  // ignore: non_constant_identifier_names
   void test_or() {
-    OrSelector selector = new SelectorParser(source, 10, 'aaa,bbb').parse();
-    expect(selector, new isInstanceOf<OrSelector>());
+    final OrSelector selector =
+        new SelectorParser(source, 10, 'aaa,bbb').parse();
+    expect(selector, const isInstanceOf<OrSelector>());
     expect(selector.selectors, hasLength(2));
     {
-      ElementNameSelector subSelector = selector.selectors[0];
-      AngularElement nameElement = subSelector.nameElement;
+      final ElementNameSelector subSelector = selector.selectors[0];
+      final nameElement = subSelector.nameElement;
       expect(nameElement.source, source);
       expect(nameElement.name, 'aaa');
       expect(nameElement.nameOffset, 10);
       expect(nameElement.nameLength, 'aaa'.length);
     }
     {
-      ElementNameSelector subSelector = selector.selectors[1];
-      AngularElement nameElement = subSelector.nameElement;
+      final ElementNameSelector subSelector = selector.selectors[1];
+      final nameElement = subSelector.nameElement;
       expect(nameElement.source, source);
       expect(nameElement.name, 'bbb');
       expect(nameElement.nameOffset, 14);
@@ -524,12 +576,14 @@
     }
   }
 
+  // ignore: non_constant_identifier_names
   void test_not() {
-    NotSelector selector = new SelectorParser(source, 10, ':not(aaa)').parse();
-    expect(selector, new isInstanceOf<NotSelector>());
+    final NotSelector selector =
+        new SelectorParser(source, 10, ':not(aaa)').parse();
+    expect(selector, const isInstanceOf<NotSelector>());
     {
-      ElementNameSelector condition = selector.condition;
-      AngularElement nameElement = condition.nameElement;
+      final ElementNameSelector condition = selector.condition;
+      final nameElement = condition.nameElement;
       expect(nameElement.source, source);
       expect(nameElement.name, 'aaa');
       expect(nameElement.nameOffset, 15);
@@ -537,89 +591,91 @@
     }
   }
 
+  // ignore: non_constant_identifier_names
   void test_contains() {
-    ContainsSelector selector =
+    final ContainsSelector selector =
         new SelectorParser(source, 10, ':contains(/aaa/)').parse();
-    expect(selector, new isInstanceOf<ContainsSelector>());
+    expect(selector, const isInstanceOf<ContainsSelector>());
     expect(selector.regex, 'aaa');
   }
 
+  // ignore: non_constant_identifier_names
   void test_complex_ast() {
-    OrSelector selector = new SelectorParser(
+    final OrSelector selector = new SelectorParser(
             source, 10, 'aaa, bbb:not(ccc), :not(:not(ddd)[eee], fff[ggg])')
         .parse();
 
-    expect(selector, new isInstanceOf<OrSelector>());
+    expect(selector, const isInstanceOf<OrSelector>());
     expect(
         selector.toString(),
-        equals("aaa || bbb && :not(ccc) || " +
-            ":not(:not(ddd) && [eee] || fff && [ggg])"));
+        equals('aaa || bbb && :not(ccc) || '
+            ':not(:not(ddd) && [eee] || fff && [ggg])'));
     {
-      ElementNameSelector subSelector = selector.selectors[0];
-      expect(subSelector, new isInstanceOf<ElementNameSelector>());
+      final ElementNameSelector subSelector = selector.selectors[0];
+      expect(subSelector, const isInstanceOf<ElementNameSelector>());
       expect(subSelector.toString(), "aaa");
     }
     {
-      AndSelector subSelector = selector.selectors[1];
-      expect(subSelector, new isInstanceOf<AndSelector>());
+      final AndSelector subSelector = selector.selectors[1];
+      expect(subSelector, const isInstanceOf<AndSelector>());
       expect(subSelector.toString(), "bbb && :not(ccc)");
       {
-        ElementNameSelector subSelector2 = subSelector.selectors[0];
-        expect(subSelector2, new isInstanceOf<ElementNameSelector>());
+        final ElementNameSelector subSelector2 = subSelector.selectors[0];
+        expect(subSelector2, const isInstanceOf<ElementNameSelector>());
         expect(subSelector2.toString(), "bbb");
       }
       {
-        NotSelector subSelector2 = subSelector.selectors[1];
-        expect(subSelector2, new isInstanceOf<NotSelector>());
+        final NotSelector subSelector2 = subSelector.selectors[1];
+        expect(subSelector2, const isInstanceOf<NotSelector>());
         expect(subSelector2.toString(), ":not(ccc)");
         {
-          ElementNameSelector subSelector3 = subSelector2.condition;
-          expect(subSelector3, new isInstanceOf<ElementNameSelector>());
+          final ElementNameSelector subSelector3 = subSelector2.condition;
+          expect(subSelector3, const isInstanceOf<ElementNameSelector>());
           expect(subSelector3.toString(), "ccc");
         }
       }
     }
     {
-      NotSelector subSelector = selector.selectors[2];
-      expect(subSelector, new isInstanceOf<NotSelector>());
+      final NotSelector subSelector = selector.selectors[2];
+      expect(subSelector, const isInstanceOf<NotSelector>());
       expect(
           subSelector.toString(), ":not(:not(ddd) && [eee] || fff && [ggg])");
       {
-        OrSelector subSelector2 = subSelector.condition;
-        expect(subSelector2, new isInstanceOf<OrSelector>());
+        final OrSelector subSelector2 = subSelector.condition;
+        expect(subSelector2, const isInstanceOf<OrSelector>());
         expect(subSelector2.toString(), ":not(ddd) && [eee] || fff && [ggg]");
         {
-          AndSelector subSelector3 = subSelector2.selectors[0];
-          expect(subSelector3, new isInstanceOf<AndSelector>());
+          final AndSelector subSelector3 = subSelector2.selectors[0];
+          expect(subSelector3, const isInstanceOf<AndSelector>());
           expect(subSelector3.toString(), ":not(ddd) && [eee]");
           {
-            NotSelector subSelector4 = subSelector3.selectors[0];
-            expect(subSelector4, new isInstanceOf<NotSelector>());
+            final NotSelector subSelector4 = subSelector3.selectors[0];
+            expect(subSelector4, const isInstanceOf<NotSelector>());
             expect(subSelector4.toString(), ":not(ddd)");
             {
-              ElementNameSelector subSelector5 = subSelector4.condition;
-              expect(subSelector5, new isInstanceOf<ElementNameSelector>());
+              final ElementNameSelector subSelector5 = subSelector4.condition;
+              expect(subSelector5, const isInstanceOf<ElementNameSelector>());
               expect(subSelector5.toString(), "ddd");
             }
           }
           {
-            AttributeSelector subSelector4 = subSelector3.selectors[1];
-            expect(subSelector4, new isInstanceOf<AttributeSelector>());
+            final AttributeSelector subSelector4 = subSelector3.selectors[1];
+            expect(subSelector4, const isInstanceOf<AttributeSelector>());
             expect(subSelector4.toString(), "[eee]");
           }
         }
         {
-          AndSelector subSelector3 = subSelector2.selectors[1];
-          expect(subSelector3, new isInstanceOf<AndSelector>());
+          final AndSelector subSelector3 = subSelector2.selectors[1];
+          expect(subSelector3, const isInstanceOf<AndSelector>());
           expect(subSelector3.toString(), "fff && [ggg]");
           {
-            ElementNameSelector subSelector4 = subSelector3.selectors[0];
-            expect(subSelector4, new isInstanceOf<ElementNameSelector>());
+            final ElementNameSelector subSelector4 = subSelector3.selectors[0];
+            expect(subSelector4, const isInstanceOf<ElementNameSelector>());
             expect(subSelector4.toString(), "fff");
           }
           {
-            AttributeSelector subSelector4 = subSelector3.selectors[1];
-            expect(subSelector4, new isInstanceOf<AttributeSelector>());
+            final AttributeSelector subSelector4 = subSelector3.selectors[1];
+            expect(subSelector4, const isInstanceOf<AttributeSelector>());
             expect(subSelector4.toString(), "[ggg]");
           }
         }
@@ -630,41 +686,45 @@
 
 @reflectiveTest
 class SuggestTagsTest {
+  // ignore: non_constant_identifier_names
   void test_suggestNodeName() {
-    Selector selector =
+    final selector =
         new ElementNameSelector(new AngularElementImpl('panel', 10, 5, null));
 
-    List<HtmlTagForSelector> suggestions = selector.suggestTags();
+    final suggestions = selector.suggestTags();
     expect(suggestions.length, 1);
     expect(suggestions.first.isValid, isTrue);
     expect(suggestions.first.toString(), equals("<panel"));
   }
 
+  // ignore: non_constant_identifier_names
   void test_suggestTagsFiltersInvalidResults() {
-    Selector selector =
+    final selector =
         new ClassSelector(new AngularElementImpl('class', 10, 5, null));
     expect(_evenInvalidSuggestions(selector), hasLength(1));
     expect(_evenInvalidSuggestions(selector).first.isValid, isFalse);
     expect(selector.suggestTags(), hasLength(0));
   }
 
+  // ignore: non_constant_identifier_names
   void test_suggestClass() {
-    Selector selector =
+    final selector =
         new ClassSelector(new AngularElementImpl('myclass', 10, 5, null));
 
-    List<HtmlTagForSelector> suggestions = _evenInvalidSuggestions(selector);
+    final suggestions = _evenInvalidSuggestions(selector);
     expect(suggestions.length, 1);
     expect(suggestions.first.isValid, isFalse);
     expect(suggestions.first.toString(), equals('<null class="myclass"'));
   }
 
+  // ignore: non_constant_identifier_names
   void test_suggestClasses() {
-    Selector selector1 =
+    final selector1 =
         new ClassSelector(new AngularElementImpl('class1', 10, 5, null));
-    Selector selector2 =
+    final selector2 =
         new ClassSelector(new AngularElementImpl('class2', 10, 5, null));
 
-    List<HtmlTagForSelector> suggestions =
+    final suggestions =
         selector2.refineTagSuggestions(_evenInvalidSuggestions(selector1));
     expect(suggestions.length, 1);
     expect(suggestions.first.isValid, isFalse);
@@ -672,52 +732,61 @@
     expect(suggestions.first.toString(), equals('<null class="class1 class2"'));
   }
 
+  // ignore: non_constant_identifier_names
   void test_suggestPropertyNoValue() {
-    Selector selector = new AttributeSelector(
-        new AngularElementImpl('attr', 10, 5, null), null, false);
+    final selector = new AttributeSelector(
+        new AngularElementImpl('attr', 10, 5, null), null,
+        isWildcard: false);
 
-    List<HtmlTagForSelector> suggestions = _evenInvalidSuggestions(selector);
+    final suggestions = _evenInvalidSuggestions(selector);
     expect(suggestions.length, 1);
     expect(suggestions.first.isValid, isFalse);
     expect(suggestions.first.toString(), equals("<null attr"));
   }
 
+  // ignore: non_constant_identifier_names
   void test_suggestPropertyWithValue() {
-    Selector selector = new AttributeSelector(
-        new AngularElementImpl('attr', 10, 5, null), "blah", false);
+    final selector = new AttributeSelector(
+        new AngularElementImpl('attr', 10, 5, null), "blah",
+        isWildcard: false);
 
-    List<HtmlTagForSelector> suggestions = _evenInvalidSuggestions(selector);
+    final suggestions = _evenInvalidSuggestions(selector);
     expect(suggestions.length, 1);
     expect(suggestions.first.isValid, isFalse);
     expect(suggestions.first.toString(), equals('<null attr="blah"'));
   }
 
+  // ignore: non_constant_identifier_names
   void test_suggestWildcardProperty() {
-    Selector selector = new AttributeSelector(
-        new AngularElementImpl('attr', 10, 5, null), null, true);
+    final selector = new AttributeSelector(
+        new AngularElementImpl('attr', 10, 5, null), null,
+        isWildcard: true);
 
-    List<HtmlTagForSelector> suggestions = _evenInvalidSuggestions(selector);
+    final suggestions = _evenInvalidSuggestions(selector);
     expect(suggestions.length, 1);
     expect(suggestions.first.isValid, isFalse);
     // [attr*] tells us they at LEAST want attr
     expect(suggestions.first.toString(), equals('<null attr'));
   }
 
+  // ignore: non_constant_identifier_names
   void test_suggestWildcardPropertyValue() {
-    Selector selector = new AttributeSelector(
-        new AngularElementImpl('attr', 10, 5, null), "value", true);
+    final selector = new AttributeSelector(
+        new AngularElementImpl('attr', 10, 5, null), "value",
+        isWildcard: true);
 
-    List<HtmlTagForSelector> suggestions = _evenInvalidSuggestions(selector);
+    final suggestions = _evenInvalidSuggestions(selector);
     expect(suggestions.length, 1);
     expect(suggestions.first.isValid, isFalse);
     // [attr*=x] tells us they at LEAST want attr=x
     expect(suggestions.first.toString(), equals('<null attr="value"'));
   }
 
+  // ignore: non_constant_identifier_names
   void test_suggestContainsIsInvalid() {
-    Selector selector = new ContainsSelector("foo");
+    final selector = new ContainsSelector("foo");
 
-    List<HtmlTagForSelector> suggestions = _evenInvalidSuggestions(selector);
+    final suggestions = _evenInvalidSuggestions(selector);
     expect(suggestions.length, 1);
     expect(suggestions.first.isValid, isFalse);
     // we could assert that it can't be made valid by adding a name,
@@ -726,40 +795,44 @@
     // but :contains is so rare we can leave this).
   }
 
+  // ignore: non_constant_identifier_names
   void test_suggestRegexPropertyValueNoops() {
-    Selector selector = new AttributeValueRegexSelector("foo");
+    final selector = new AttributeValueRegexSelector("foo");
 
-    List<HtmlTagForSelector> suggestions = _evenInvalidSuggestions(selector);
+    final suggestions = _evenInvalidSuggestions(selector);
     expect(suggestions.length, 1);
     expect(suggestions.first.isValid, isFalse);
     expect(suggestions.first.toString(),
         equals(new HtmlTagForSelector().toString()));
   }
 
+  // ignore: non_constant_identifier_names
   void test_suggestAndMergesSuggestionConstraints() {
-    Selector nameSelector =
+    final nameSelector =
         new ElementNameSelector(new AngularElementImpl('panel', 10, 5, null));
-    Selector attrSelector = new AttributeSelector(
-        new AngularElementImpl('attr', 10, 5, null), "value", true);
-    Selector selector = new AndSelector([nameSelector, attrSelector]);
+    final attrSelector = new AttributeSelector(
+        new AngularElementImpl('attr', 10, 5, null), "value",
+        isWildcard: true);
+    final selector = new AndSelector([nameSelector, attrSelector]);
 
-    List<HtmlTagForSelector> suggestions = selector.suggestTags();
+    final suggestions = selector.suggestTags();
     expect(suggestions.length, 1);
     expect(suggestions.first.isValid, isTrue);
     expect(suggestions.first.toString(), equals('<panel attr="value"'));
   }
 
+  // ignore: non_constant_identifier_names
   void test_suggestOrMergesSuggestionConstraints() {
-    Selector nameSelector =
+    final nameSelector =
         new ElementNameSelector(new AngularElementImpl('panel', 10, 5, null));
-    Selector attrSelector = new AttributeSelector(
-        new AngularElementImpl('attr', 10, 5, null), "value", true);
-    Selector selector = new OrSelector([nameSelector, attrSelector]);
+    final attrSelector = new AttributeSelector(
+        new AngularElementImpl('attr', 10, 5, null), "value",
+        isWildcard: true);
+    final selector = new OrSelector([nameSelector, attrSelector]);
 
-    List<HtmlTagForSelector> suggestions = _evenInvalidSuggestions(selector);
+    final suggestions = _evenInvalidSuggestions(selector);
     expect(suggestions.length, 2);
-    Map<String, HtmlTagForSelector> suggestionsMap =
-        <String, HtmlTagForSelector>{};
+    final suggestionsMap = <String, HtmlTagForSelector>{};
     suggestions.forEach((s) => suggestionsMap[s.toString()] = s);
     expect(suggestionsMap["<panel"], isNotNull);
     expect(suggestionsMap["<panel"].isValid, isTrue);
@@ -767,47 +840,51 @@
     expect(suggestionsMap['<null attr="value"'].isValid, isFalse);
   }
 
+  // ignore: non_constant_identifier_names
   void test_suggestOrAnd() {
-    Selector nameSelector1 =
+    final nameSelector1 =
         new ElementNameSelector(new AngularElementImpl('name1', 10, 5, null));
-    Selector attrSelector1 = new AttributeSelector(
-        new AngularElementImpl('attr1', 10, 5, null), "value", true);
-    Selector andSelector1 = new AndSelector([nameSelector1, attrSelector1]);
-    Selector nameSelector2 =
+    final attrSelector1 = new AttributeSelector(
+        new AngularElementImpl('attr1', 10, 5, null), "value",
+        isWildcard: true);
+    final andSelector1 = new AndSelector([nameSelector1, attrSelector1]);
+    final nameSelector2 =
         new ElementNameSelector(new AngularElementImpl('name2', 10, 5, null));
-    Selector attrSelector2 = new AttributeSelector(
-        new AngularElementImpl('attr2', 10, 5, null), "value", true);
-    Selector andSelector2 = new AndSelector([nameSelector2, attrSelector2]);
-    Selector selector = new OrSelector([andSelector1, andSelector2]);
+    final attrSelector2 = new AttributeSelector(
+        new AngularElementImpl('attr2', 10, 5, null), "value",
+        isWildcard: true);
+    final andSelector2 = new AndSelector([nameSelector2, attrSelector2]);
+    final selector = new OrSelector([andSelector1, andSelector2]);
 
-    List<HtmlTagForSelector> suggestions = selector.suggestTags();
+    final suggestions = selector.suggestTags();
     expect(suggestions.length, 2);
-    Map<String, HtmlTagForSelector> suggestionsMap =
-        <String, HtmlTagForSelector>{};
+    final suggestionsMap = <String, HtmlTagForSelector>{};
     suggestions.forEach((s) => suggestionsMap[s.toString()] = s);
     expect(suggestionsMap['<name1 attr1="value"'], isNotNull);
     expect(suggestionsMap['<name2 attr2="value"'], isNotNull);
   }
 
+  // ignore: non_constant_identifier_names
   void test_suggestAndOr() {
-    Selector nameSelector1 =
+    final nameSelector1 =
         new ElementNameSelector(new AngularElementImpl('name1', 10, 5, null));
-    Selector nameSelector2 =
+    final nameSelector2 =
         new ElementNameSelector(new AngularElementImpl('name2', 10, 5, null));
-    Selector orSelector1 = new OrSelector([nameSelector1, nameSelector2]);
+    final orSelector1 = new OrSelector([nameSelector1, nameSelector2]);
 
-    Selector attrSelector1 = new AttributeSelector(
-        new AngularElementImpl('attr1', 10, 5, null), "value", true);
-    Selector attrSelector2 = new AttributeSelector(
-        new AngularElementImpl('attr2', 10, 5, null), "value", true);
-    Selector orSelector2 = new OrSelector([attrSelector1, attrSelector2]);
+    final attrSelector1 = new AttributeSelector(
+        new AngularElementImpl('attr1', 10, 5, null), "value",
+        isWildcard: true);
+    final attrSelector2 = new AttributeSelector(
+        new AngularElementImpl('attr2', 10, 5, null), "value",
+        isWildcard: true);
+    final orSelector2 = new OrSelector([attrSelector1, attrSelector2]);
 
-    Selector selector = new AndSelector([orSelector1, orSelector2]);
+    final selector = new AndSelector([orSelector1, orSelector2]);
 
-    List<HtmlTagForSelector> suggestions = selector.suggestTags();
+    final suggestions = selector.suggestTags();
     expect(suggestions.length, 4);
-    Map<String, HtmlTagForSelector> suggestionsMap =
-        <String, HtmlTagForSelector>{};
+    final suggestionsMap = <String, HtmlTagForSelector>{};
     suggestions.forEach((s) => suggestionsMap[s.toString()] = s);
 
     // basically (name1, name2)(attr1, attr2) though I'm not sure that's legal
@@ -817,25 +894,27 @@
     expect(suggestionsMap['<name2 attr2="value"'], isNotNull);
   }
 
+  // ignore: non_constant_identifier_names
   void test_suggestOrOr() {
-    Selector nameSelector1 =
+    final nameSelector1 =
         new ElementNameSelector(new AngularElementImpl('name1', 10, 5, null));
-    Selector nameSelector2 =
+    final nameSelector2 =
         new ElementNameSelector(new AngularElementImpl('name2', 10, 5, null));
-    Selector orSelector1 = new OrSelector([nameSelector1, nameSelector2]);
+    final orSelector1 = new OrSelector([nameSelector1, nameSelector2]);
 
-    Selector attrSelector1 = new AttributeSelector(
-        new AngularElementImpl('attr1', 10, 5, null), "value", true);
-    Selector attrSelector2 = new AttributeSelector(
-        new AngularElementImpl('attr2', 10, 5, null), "value", true);
-    Selector orSelector2 = new OrSelector([attrSelector1, attrSelector2]);
+    final attrSelector1 = new AttributeSelector(
+        new AngularElementImpl('attr1', 10, 5, null), "value",
+        isWildcard: true);
+    final attrSelector2 = new AttributeSelector(
+        new AngularElementImpl('attr2', 10, 5, null), "value",
+        isWildcard: true);
+    final orSelector2 = new OrSelector([attrSelector1, attrSelector2]);
 
-    Selector selector = new OrSelector([orSelector1, orSelector2]);
+    final selector = new OrSelector([orSelector1, orSelector2]);
 
-    List<HtmlTagForSelector> suggestions = _evenInvalidSuggestions(selector);
+    final suggestions = _evenInvalidSuggestions(selector);
     expect(suggestions.length, 4);
-    Map<String, HtmlTagForSelector> suggestionsMap =
-        <String, HtmlTagForSelector>{};
+    final suggestionsMap = <String, HtmlTagForSelector>{};
     suggestions.forEach((s) => suggestionsMap[s.toString()] = s);
 
     // basically (name1, name2),(attr1, attr2) though I'm not sure that's legal
@@ -845,214 +924,242 @@
     expect(suggestionsMap['<null attr2="value"'], isNotNull);
   }
 
-  /**
-   * [refineTagSuggestions] filters out invalid tags, but those are important
-   * for us to test sometimes. This will do the same thing, but keep invalid
-   * suggestions so we can inspect them.
-   */
+  /// [refineTagSuggestions] filters out invalid tags, but those are important
+  /// for us to test sometimes. This will do the same thing, but keep invalid
+  /// suggestions so we can inspect them.
   List<HtmlTagForSelector> _evenInvalidSuggestions(Selector selector) {
-    List<HtmlTagForSelector> tags = <HtmlTagForSelector>[
-      new HtmlTagForSelector()
-    ];
+    final tags = <HtmlTagForSelector>[new HtmlTagForSelector()];
     return selector.refineTagSuggestions(tags);
   }
 }
 
 @reflectiveTest
 class HtmlTagForSelectorTest {
+  // ignore: non_constant_identifier_names
   void test_noNameIsInvalid() {
-    HtmlTagForSelector tag = new HtmlTagForSelector();
+    final tag = new HtmlTagForSelector();
     expect(tag.isValid, isFalse);
   }
 
+  // ignore: non_constant_identifier_names
   void test_setName() {
-    HtmlTagForSelector tag = new HtmlTagForSelector();
-    tag.name = "myname";
+    final tag = new HtmlTagForSelector()..name = "myname";
     expect(tag.isValid, isTrue);
     expect(tag.toString(), equals("<myname"));
   }
 
+  // ignore: non_constant_identifier_names
   void test_setNameTwice() {
-    HtmlTagForSelector tag = new HtmlTagForSelector();
-    tag.name = "myname";
+    final tag = new HtmlTagForSelector()..name = "myname";
+    // ignore: cascade_invocations
     tag.name = "myname";
     expect(tag.isValid, isTrue);
     expect(tag.toString(), equals("<myname"));
   }
 
+  // ignore: non_constant_identifier_names
   void test_setNameConflicting() {
-    HtmlTagForSelector tag = new HtmlTagForSelector();
-    tag.name = "myname1";
+    final tag = new HtmlTagForSelector()..name = "myname1";
+    // ignore: cascade_invocations
     tag.name = "myname2";
     expect(tag.isValid, isFalse);
   }
 
+  // ignore: non_constant_identifier_names
   void test_setAttributeNoValue() {
-    HtmlTagForSelector tag = new HtmlTagForSelector();
-    tag.name = "tagname";
-    tag.setAttribute("attr");
+    final tag = new HtmlTagForSelector()
+      ..name = "tagname"
+      ..setAttribute("attr");
     expect(tag.isValid, isTrue);
     expect(tag.toString(), equals("<tagname attr"));
   }
 
+  // ignore: non_constant_identifier_names
   void test_setAttributeNoValueTwice() {
-    HtmlTagForSelector tag = new HtmlTagForSelector();
-    tag.name = "tagname";
-    tag.setAttribute("attr");
+    final tag = new HtmlTagForSelector()
+      ..name = "tagname"
+      ..setAttribute("attr");
+    // ignore: cascade_invocations
     tag.setAttribute("attr");
     expect(tag.isValid, isTrue);
     expect(tag.toString(), equals("<tagname attr"));
   }
 
+  // ignore: non_constant_identifier_names
   void test_setAttributeValue() {
-    HtmlTagForSelector tag = new HtmlTagForSelector();
-    tag.name = "tagname";
-    tag.setAttribute("attr", value: "value");
+    final tag = new HtmlTagForSelector()
+      ..name = "tagname"
+      ..setAttribute("attr", value: "value");
     expect(tag.isValid, isTrue);
     expect(tag.toString(), equals('<tagname attr="value"'));
   }
 
+  // ignore: non_constant_identifier_names
   void test_setAttributeValueTwice() {
-    HtmlTagForSelector tag = new HtmlTagForSelector();
-    tag.name = "tagname";
-    tag.setAttribute("attr", value: "value");
+    final tag = new HtmlTagForSelector()
+      ..name = "tagname"
+      ..setAttribute("attr", value: "value");
+    // ignore: cascade_invocations
     tag.setAttribute("attr", value: "value");
     expect(tag.isValid, isTrue);
     expect(tag.toString(), equals('<tagname attr="value"'));
   }
 
+  // ignore: non_constant_identifier_names
   void test_setAttributeValueAfterJustAttr() {
-    HtmlTagForSelector tag = new HtmlTagForSelector();
-    tag.name = "tagname";
-    tag.setAttribute("attr");
+    final tag = new HtmlTagForSelector()
+      ..name = "tagname"
+      ..setAttribute("attr");
+    // ignore: cascade_invocations
     tag.setAttribute("attr", value: "value");
     expect(tag.isValid, isTrue);
     expect(tag.toString(), equals('<tagname attr="value"'));
   }
 
+  // ignore: non_constant_identifier_names
   void test_setAttributeNoValueAfterValue() {
-    HtmlTagForSelector tag = new HtmlTagForSelector();
-    tag.name = "tagname";
-    tag.setAttribute("attr", value: "value");
+    final tag = new HtmlTagForSelector()
+      ..name = "tagname"
+      ..setAttribute("attr", value: "value");
+    // ignore: cascade_invocations
     tag.setAttribute("attr");
     expect(tag.isValid, isTrue);
     expect(tag.toString(), equals('<tagname attr="value"'));
   }
 
+  // ignore: non_constant_identifier_names
   void test_setAttributeConflictingValues() {
-    HtmlTagForSelector tag = new HtmlTagForSelector();
-    tag.name = "tagname";
-    tag.setAttribute("attr", value: "value1");
+    final tag = new HtmlTagForSelector()
+      ..name = "tagname"
+      ..setAttribute("attr", value: "value1");
+    // ignore: cascade_invocations
     tag.setAttribute("attr", value: "value2");
     expect(tag.isValid, isFalse);
   }
 
+  // ignore: non_constant_identifier_names
   void test_addClassOneClass() {
-    HtmlTagForSelector tag = new HtmlTagForSelector();
-    tag.name = "tagname";
-    tag.addClass("myclass");
+    final tag = new HtmlTagForSelector()
+      ..name = "tagname"
+      ..addClass("myclass");
     expect(tag.isValid, isTrue);
     expect(tag.toString(), equals('<tagname class="myclass"'));
   }
 
+  // ignore: non_constant_identifier_names
   void test_addClassTwoClasses() {
-    HtmlTagForSelector tag = new HtmlTagForSelector();
-    tag.name = "tagname";
-    tag.addClass("myclass");
+    final tag = new HtmlTagForSelector()
+      ..name = "tagname"
+      ..addClass("myclass");
+    // ignore: cascade_invocations
     tag.addClass("myotherclass");
     expect(tag.isValid, isTrue);
     expect(tag.toString(), equals('<tagname class="myclass myotherclass"'));
   }
 
+  // ignore: non_constant_identifier_names
   void test_addClassMultipleTimesOKDoesntRepeat() {
-    HtmlTagForSelector tag = new HtmlTagForSelector();
-    tag.name = "tagname";
+    final tag = new HtmlTagForSelector()
+      ..name = "tagname"
+      ..addClass("myclass");
+    // ignore: cascade_invocations
     tag.addClass("myclass");
-    tag.addClass("myclass");
+    // ignore: cascade_invocations
     tag.addClass("myclass");
     expect(tag.isValid, isTrue);
     expect(tag.toString(), equals('<tagname class="myclass"'));
   }
 
+  // ignore: non_constant_identifier_names
   void test_classesAndClassAttrBindingInvalid() {
-    HtmlTagForSelector tag = new HtmlTagForSelector();
-    tag.name = "tagname";
-    tag.addClass("myclass");
-    tag.setAttribute("class", value: "blah");
+    final tag = new HtmlTagForSelector()
+      ..name = "tagname"
+      ..addClass("myclass")
+      ..setAttribute("class", value: "blah");
     expect(tag.isValid, isFalse);
   }
 
+  // ignore: non_constant_identifier_names
   void test_classesAndEmptyClassAttrBindingValid() {
-    HtmlTagForSelector tag = new HtmlTagForSelector();
-    tag.name = "tagname";
-    tag.addClass("myclass");
-    tag.setAttribute("class");
+    final tag = new HtmlTagForSelector()
+      ..name = "tagname"
+      ..addClass("myclass")
+      ..setAttribute("class");
     expect(tag.isValid, isTrue);
     expect(tag.toString(), equals('<tagname class="myclass"'));
   }
 
+  // ignore: non_constant_identifier_names
   void test_classesAndMatchingClassAttrBindingValid() {
-    HtmlTagForSelector tag = new HtmlTagForSelector();
-    tag.name = "tagname";
-    tag.addClass("myclass");
-    tag.setAttribute("class", value: 'myclass');
+    final tag = new HtmlTagForSelector()
+      ..name = "tagname"
+      ..addClass("myclass")
+      ..setAttribute("class", value: 'myclass');
     expect(tag.isValid, isTrue);
     expect(tag.toString(), equals('<tagname class="myclass"'));
   }
 
+  // ignore: non_constant_identifier_names
   void test_cloneKeepsName() {
-    HtmlTagForSelector tag = new HtmlTagForSelector();
-    tag.name = "tagname";
+    var tag = new HtmlTagForSelector()..name = "tagname";
     tag = tag.clone();
     expect(tag.toString(), equals("<tagname"));
   }
 
+  // ignore: non_constant_identifier_names
   void test_cloneKeepsAttributes() {
-    HtmlTagForSelector tag = new HtmlTagForSelector();
-    tag.name = "tagname";
-    tag.setAttribute("attr1");
-    tag.setAttribute("attr2");
+    var tag = new HtmlTagForSelector()
+      ..name = "tagname"
+      ..setAttribute("attr1")
+      ..setAttribute("attr2");
     tag = tag.clone();
     expect(tag.toString(), equals("<tagname attr1 attr2"));
   }
 
+  // ignore: non_constant_identifier_names
   void test_cloneKeepsAttributeValues() {
-    HtmlTagForSelector tag = new HtmlTagForSelector();
-    tag.name = "tagname";
-    tag.setAttribute("attr1", value: 'value1');
-    tag.setAttribute("attr2", value: 'value2');
+    var tag = new HtmlTagForSelector()
+      ..name = "tagname"
+      ..setAttribute("attr1", value: 'value1')
+      ..setAttribute("attr2", value: 'value2');
     tag = tag.clone();
     expect(tag.toString(), equals('<tagname attr1="value1" attr2="value2"'));
   }
 
+  // ignore: non_constant_identifier_names
   void test_cloneKeepsClassnames() {
-    HtmlTagForSelector tag = new HtmlTagForSelector();
-    tag.name = "tagname";
-    tag.addClass("class1");
-    tag.addClass("class2");
+    var tag = new HtmlTagForSelector()
+      ..name = "tagname"
+      ..addClass("class1")
+      ..addClass("class2");
     tag = tag.clone();
     expect(tag.isValid, isTrue);
     expect(tag.toString(), equals('<tagname class="class1 class2"'));
   }
 
+  // ignore: non_constant_identifier_names
   void test_cloneKeepsValid() {
-    HtmlTagForSelector tag = new HtmlTagForSelector();
-    tag.name = "tagname";
+    var tag = new HtmlTagForSelector()..name = "tagname";
+
+    // ignore: cascade_invocations
     tag.name = "break this tag";
+
+    // ignore: cascade_invocations
     tag = tag.clone();
     expect(tag.isValid, isFalse);
   }
 
+  // ignore: non_constant_identifier_names
   void test_cloneWithoutNameCanBecomeValid() {
-    HtmlTagForSelector tag = new HtmlTagForSelector();
-    tag = tag.clone();
-    tag.name = "tagname";
+    var tag = new HtmlTagForSelector();
+    tag = tag.clone()..name = "tagname";
     expect(tag.isValid, isTrue);
   }
 
+  // ignore: non_constant_identifier_names
   void test_cloneIsAClone() {
-    HtmlTagForSelector tag = new HtmlTagForSelector();
-    HtmlTagForSelector clone = tag.clone();
+    final tag = new HtmlTagForSelector();
+    final clone = tag.clone();
     tag.name = "original";
     clone.name = "clone";
     expect(tag, isNot(equals(clone)));
@@ -1062,42 +1169,42 @@
     expect(clone.toString(), "<clone");
   }
 
+  // ignore: non_constant_identifier_names
   void test_cloneHasItsOwnProperties() {
-    HtmlTagForSelector tag = new HtmlTagForSelector();
-    tag.name = "tagname";
-    HtmlTagForSelector clone = tag.clone();
-    clone.setAttribute("attr");
+    final tag = new HtmlTagForSelector()..name = "tagname";
+    final clone = tag.clone()..setAttribute("attr");
     expect(tag.toString(), "<tagname");
     expect(clone.toString(), "<tagname attr");
   }
 
+  // ignore: non_constant_identifier_names
   void test_cloneHasItsOwnClasses() {
-    HtmlTagForSelector tag = new HtmlTagForSelector();
-    tag.name = "tagname";
-    HtmlTagForSelector clone = tag.clone();
-    clone.addClass("myclass");
+    final tag = new HtmlTagForSelector()..name = "tagname";
+    final clone = tag.clone()..addClass("myclass");
     expect(tag.toString(), "<tagname");
     expect(clone.toString(), '<tagname class="myclass"');
   }
 
+  // ignore: non_constant_identifier_names
   void test_toStringIsAlphabeticalProperties() {
-    HtmlTagForSelector tag = new HtmlTagForSelector();
-    tag.name = "tagname";
-    tag.setAttribute("apple");
-    tag.setAttribute("flick");
-    tag.setAttribute("ziggy");
-    tag.setAttribute("cow");
-    tag.addClass("classes");
+    final tag = new HtmlTagForSelector()
+      ..name = "tagname"
+      ..setAttribute("apple")
+      ..setAttribute("flick")
+      ..setAttribute("ziggy")
+      ..setAttribute("cow")
+      ..addClass("classes");
     expect(tag.toString(), '<tagname apple class="classes" cow flick ziggy');
   }
 
+  // ignore: non_constant_identifier_names
   void test_toStringIsAlphabeticalClasses() {
-    HtmlTagForSelector tag = new HtmlTagForSelector();
-    tag.name = "tagname";
-    tag.addClass("apple");
-    tag.addClass("flick");
-    tag.addClass("ziggy");
-    tag.addClass("cow");
+    final tag = new HtmlTagForSelector()
+      ..name = "tagname"
+      ..addClass("apple")
+      ..addClass("flick")
+      ..addClass("ziggy")
+      ..addClass("cow");
     expect(tag.toString(), '<tagname class="apple cow flick ziggy"');
   }
 }
@@ -1120,15 +1227,13 @@
   List<ResolvedRange> resolvedRanges = <ResolvedRange>[];
 
   void setUp() {
-    when(template.addRange(anyObject, anyObject))
-        .thenInvoke((SourceRange range, AngularElement element) {
-      resolvedRanges.add(new ResolvedRange(range, element));
-    });
+    when(template.addRange(anyObject, anyObject)).thenInvoke((range, element) =>
+        resolvedRanges.add(new ResolvedRange(range, element)));
   }
 
   void _assertRange(ResolvedRange resolvedRange, int offset, int length,
       AngularElement element) {
-    SourceRange range = resolvedRange.range;
+    final range = resolvedRange.range;
     expect(range.offset, offset);
     expect(range.length, length);
     expect(resolvedRange.element, element);
diff --git a/analyzer_plugin/test/test_all.dart b/analyzer_plugin/test/test_all.dart
index c935c51..917365d 100644
--- a/analyzer_plugin/test/test_all.dart
+++ b/analyzer_plugin/test/test_all.dart
@@ -2,16 +2,13 @@
 
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
-import 'resolver_test.dart' as resolver_test;
-import 'selector_test.dart' as selector_test;
 import 'angular_driver_test.dart' as angular_driver_test;
 import 'offsetting_constant_value_visitor_test.dart'
     as offsetting_constant_value_visitor_test;
+import 'resolver_test.dart' as resolver_test;
+import 'selector_test.dart' as selector_test;
 
-/**
- * Utility for manually running all tests.
- */
-main() {
+void main() {
   defineReflectiveSuite(() {
     resolver_test.main();
     selector_test.main();
diff --git a/new_plugin/lib/plugin.dart b/new_plugin/lib/plugin.dart
new file mode 100644
index 0000000..635da43
--- /dev/null
+++ b/new_plugin/lib/plugin.dart
@@ -0,0 +1,101 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/context/context_root.dart';
+import 'package:analyzer/error/listener.dart';
+import 'package:analyzer/file_system/file_system.dart';
+import 'package:analyzer/src/context/builder.dart';
+import 'package:analyzer/src/dart/analysis/driver.dart';
+import 'package:analyzer/src/generated/engine.dart' hide AnalysisResult;
+import 'package:analyzer/src/generated/sdk.dart';
+import 'package:analyzer_plugin/plugin/plugin.dart';
+import 'package:analyzer_plugin/protocol/protocol_constants.dart' as plugin;
+import 'package:analyzer_plugin/protocol/protocol_generated.dart' as plugin;
+import 'package:analyzer_plugin/utilities/analyzer_converter.dart';
+import 'package:angular_analysis_plugin/src/error_visitor.dart';
+import 'package:front_end/src/base/source.dart';
+
+class AngularAnalysisPlugin extends ServerPlugin {
+  AngularAnalysisPlugin(ResourceProvider provider) : super(provider);
+
+  @override
+  List<String> get fileGlobsToAnalyze => <String>['*.dart', '*.html'];
+
+  @override
+  String get name => 'Angular Analysis Plugin';
+
+  @override
+  String get version => '1.0.0';
+
+  @override
+  AnalysisDriverGeneric createAnalysisDriver(plugin.ContextRoot contextRoot) {
+    ContextRoot root = new ContextRoot(contextRoot.root, contextRoot.exclude);
+    DartSdkManager sdkManager =
+        new DartSdkManager('/Users/brianwilkerson/Dev/dart/dart-sdk', true);
+    ContextBuilder builder =
+        new ContextBuilder(resourceProvider, sdkManager, null);
+    builder.analysisDriverScheduler = analysisDriverScheduler;
+    return builder.buildDriver(root);
+  }
+
+  void sendNotificationForSubscription(
+      String fileName, plugin.AnalysisService service, AnalysisResult result) {
+    switch (service) {
+      case plugin.AnalysisService.FOLDING:
+        // TODO(brianwilkerson) Implement this.
+        break;
+      case plugin.AnalysisService.HIGHLIGHTS:
+        // TODO(brianwilkerson) Implement this.
+        break;
+      case plugin.AnalysisService.NAVIGATION:
+        // TODO(brianwilkerson) Implement this.
+        break;
+      case plugin.AnalysisService.OCCURRENCES:
+        // TODO(brianwilkerson) Implement this.
+        break;
+      case plugin.AnalysisService.OUTLINE:
+        // TODO(brianwilkerson) Implement this.
+        break;
+      default:
+        // Ignore unhandled service types.
+        break;
+    }
+  }
+
+  @override
+  void sendNotificationsForSubscriptions(
+      Map<String, List<plugin.AnalysisService>> subscriptions) {
+    subscriptions
+        .forEach((String filePath, List<plugin.AnalysisService> services) {
+      // TODO(brianwilkerson) Get the results for this file.
+      AnalysisResult result;
+      for (plugin.AnalysisService service in services) {
+        sendNotificationForSubscription(filePath, service, result);
+      }
+    });
+  }
+
+  void _processResults(AnalysisResult result) {
+    RecordingErrorListener listener = new RecordingErrorListener();
+    Source source = result.unit.element.source;
+    String filePath = source.fullName;
+    ErrorReporter reporter = new ErrorReporter(listener, source);
+    AngularErrorVisitor visitor = new AngularErrorVisitor(reporter);
+    result.unit.accept(visitor);
+    AnalyzerConverter converter = new AnalyzerConverter();
+    // TODO(brianwilkerson) Get the right analysis options.
+    List<plugin.AnalysisError> errors = converter.convertAnalysisErrors(
+        listener.errors,
+        lineInfo: result.lineInfo,
+        options: null);
+    channel.sendNotification(
+        new plugin.AnalysisErrorsParams(filePath, errors).toNotification());
+    // TODO(brianwilkerson) Generate notifications based on subscriptions.
+    List<plugin.AnalysisService> services =
+        subscriptionManager.servicesForFile(filePath);
+    for (plugin.AnalysisService service in services) {
+      sendNotificationForSubscription(filePath, service, result);
+    }
+  }
+}
diff --git a/new_plugin/lib/src/error_code.dart b/new_plugin/lib/src/error_code.dart
new file mode 100644
index 0000000..460067f
--- /dev/null
+++ b/new_plugin/lib/src/error_code.dart
@@ -0,0 +1,20 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:front_end/src/base/errors.dart';
+
+class AngularErrorCode extends ErrorCode {
+  static final AngularErrorCode INVALID_USE_OF_ANNOTATION =
+      new AngularErrorCode(
+          'INVALID_USE_OF_ANNOTATION', 'Annotation cannot be used on classes');
+
+  AngularErrorCode(String name, String message, [String correction])
+      : super(name, message, correction);
+
+  @override
+  ErrorSeverity get errorSeverity => ErrorSeverity.WARNING;
+
+  @override
+  ErrorType get type => ErrorType.HINT;
+}
diff --git a/new_plugin/lib/src/error_visitor.dart b/new_plugin/lib/src/error_visitor.dart
new file mode 100644
index 0000000..5242cbc
--- /dev/null
+++ b/new_plugin/lib/src/error_visitor.dart
@@ -0,0 +1,31 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:analyzer/error/listener.dart';
+import 'package:angular_analysis_plugin/src/error_code.dart';
+
+class AngularErrorVisitor extends RecursiveAstVisitor {
+  final ErrorReporter reporter;
+
+  AngularErrorVisitor(this.reporter);
+
+  @override
+  void visitClassDeclaration(ClassDeclaration node) {
+    if (_hasAnnotation(node)) {
+      reporter.reportErrorForNode(
+          AngularErrorCode.INVALID_USE_OF_ANNOTATION, node.name);
+    }
+  }
+
+  bool _hasAnnotation(AnnotatedNode node) {
+    for (Annotation annotation in node.metadata) {
+      if (annotation.name.name == 'annotation') {
+        return true;
+      }
+    }
+    return false;
+  }
+}
diff --git a/new_plugin/lib/starter.dart b/new_plugin/lib/starter.dart
new file mode 100644
index 0000000..6926d43
--- /dev/null
+++ b/new_plugin/lib/starter.dart
@@ -0,0 +1,15 @@
+// Copyright (c) 2017, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:isolate';
+
+import 'package:analyzer/file_system/physical_file_system.dart';
+import 'package:analyzer_plugin/starter.dart';
+import 'package:angular_analysis_plugin/plugin.dart';
+
+void start(List<String> args, SendPort sendPort) {
+  new ServerPluginStarter(
+          new AngularAnalysisPlugin(PhysicalResourceProvider.INSTANCE))
+      .start(sendPort);
+}
diff --git a/new_plugin/pubspec.yaml b/new_plugin/pubspec.yaml
new file mode 100644
index 0000000..15e674e
--- /dev/null
+++ b/new_plugin/pubspec.yaml
@@ -0,0 +1,29 @@
+name: angular_analysis_plugin
+version: 0.0.8
+description: Dart analysis plugin for Angular 2, using new plugin arch
+environment:
+  sdk: '>=1.24.0-dev.1.0'
+dependencies:
+  analyzer: '^0.30.0'
+  plugin: '^0.2.0'
+  html: '^0.12.2'
+  tuple: '^1.0.1'
+  analyzer_plugin: 0.0.8
+  angular_analyzer_plugin: 0.0.8
+  angular_analyzer_server_plugin: 0.0.7
+dependency_overrides:
+  angular_analyzer_plugin:
+    path: ../analyzer_plugin
+  angular_analyzer_server_plugin:
+    path: ../server_plugin
+  analyzer_plugin:
+    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'
+  unittest: '^0.11.0'
+  test: '^0.12.20'
diff --git a/playground/lib/counter_component.dart b/playground/lib/counter_component.dart
index c6c8804..27a622e 100644
--- a/playground/lib/counter_component.dart
+++ b/playground/lib/counter_component.dart
@@ -1,7 +1,8 @@
 import 'package:angular2/angular2.dart';
 import 'dart:html';
 
-@Component(selector: 'my-counter', template: r'{{count}} <button (click)="increment($event)">++</button>')
+@Component(selector: 'my-counter',
+    template: r'<button  (click)="increment($event)">++</button>')
 class CounterComponent {
 
   @Input() int count;
diff --git a/playground/lib/overview_component.dart b/playground/lib/overview_component.dart
index 420e8d9..a32630c 100644
--- a/playground/lib/overview_component.dart
+++ b/playground/lib/overview_component.dart
@@ -2,7 +2,12 @@
 import 'counter_component.dart';
 import 'bubbled_directive.dart';
 
-@Component(selector: 'blah', directives: const[CounterComponent, NgFor, NgIf, BubbledDirective], templateUrl: 'overview_component.html')
+import 'package:analyze_angular/analyze_angular.dart';
+
+@Component(selector: 'blah',
+    directives: const[CounterComponent, NgFor, NgIf, BubbledDirective],
+    templateUrl: 'overview_component.html'
+)
 class OverviewComponent {
   String header;
   List<String> items;
diff --git a/playground/lib/overview_component.html b/playground/lib/overview_component.html
index 4b11aab..9996b65 100644
--- a/playground/lib/overview_component.html
+++ b/playground/lib/overview_component.html
@@ -9,3 +9,4 @@
 
 <my-counter [count]='4' (incremented)='items.add($event.toString())'></my-counter>
 
+<my-counter></my-counter>
\ No newline at end of file
diff --git a/playground/pubspec.yaml b/playground/pubspec.yaml
index d174be6..49e424f 100644
--- a/playground/pubspec.yaml
+++ b/playground/pubspec.yaml
@@ -2,11 +2,15 @@
 description: QuickStart
 version: 0.0.1
 environment:
-  sdk: '>=1.19.0 <2.0.0'
+  sdk: '>=1.24.0-dev.1.0 <2.0.0'
 dependencies:
   angular2: 2.0.0-beta.22
   browser: ^0.10.0
   dart_to_js_script_rewriter: ^1.0.1
+  analyze_angular: 0.0.0
+dependency_overrides:
+  analyze_angular:
+    path: ../analyze_angular
 transformers:
 - angular2:
     platform_directives:
diff --git a/server_plugin/.analysis_options b/server_plugin/.analysis_options
deleted file mode 100644
index a10d4c5..0000000
--- a/server_plugin/.analysis_options
+++ /dev/null
@@ -1,2 +0,0 @@
-analyzer:
-  strong-mode: true
diff --git a/server_plugin/analysis_options.yaml b/server_plugin/analysis_options.yaml
new file mode 100644
index 0000000..e0636d8
--- /dev/null
+++ b/server_plugin/analysis_options.yaml
@@ -0,0 +1,77 @@
+analyzer:
+  strong-mode: true
+
+linter:
+  rules:
+    - prefer_final_locals
+    - always_declare_return_types
+    - always_put_control_body_on_new_line
+    - always_require_non_null_named_parameters
+    - annotate_overrides
+    - avoid_annotating_with_dynamic
+    - avoid_catching_errors
+    - avoid_classes_with_only_static_members
+    - avoid_empty_else
+    - avoid_init_to_null
+    - avoid_null_checks_in_equality_operators
+    - avoid_positional_boolean_parameters
+    - avoid_return_types_on_setters
+    - avoid_returning_this
+    - avoid_setters_without_getters
+    - avoid_slow_async_io
+    - avoid_types_on_closure_parameters
+    - await_only_futures
+    - camel_case_types
+    - cancel_subscriptions
+    - cascade_invocations
+    - close_sinks
+    - control_flow_in_finally
+    - directives_ordering
+    - empty_catches
+    - empty_constructor_bodies
+    - empty_statements
+    - iterable_contains_unrelated_type
+    - join_return_with_assignment
+    - library_names
+    - library_prefixes
+    - list_remove_unrelated_type
+    - no_adjacent_strings_in_list
+    - no_duplicate_case_values
+    - non_constant_identifier_names
+    - omit_local_variable_types
+    - only_throw_errors
+    - overridden_fields
+    - parameter_assignments
+    - prefer_adjacent_string_concatenation
+    - prefer_collection_literals
+    - prefer_conditional_assignment
+    - prefer_const_constructors
+    - prefer_constructors_over_static_methods
+    - prefer_contains
+    - prefer_expression_function_bodies
+    - prefer_final_fields
+    - prefer_final_locals
+    - prefer_function_declarations_over_variables
+    - prefer_interpolation_to_compose_strings
+    - prefer_is_empty
+    - prefer_is_not_empty
+    - recursive_getters
+    - slash_for_doc_comments
+    - super_goes_last
+    - test_types_in_equals
+    - throw_in_finally
+    - type_init_formals
+    - unawaited_futures
+    - unnecessary_brace_in_string_interps
+    - unnecessary_getters_setters
+    - unnecessary_lambdas
+    - unnecessary_null_aware_assignments
+    - unnecessary_null_in_if_null_operators
+    - unnecessary_overrides
+    - unnecessary_this
+    - unrelated_type_equality_checks
+    - use_rethrow_when_possible
+    - use_setters_to_change_properties
+    - use_string_buffers
+    - use_to_and_as_if_applicable
+    - valid_regexps
diff --git a/server_plugin/lib/plugin.dart b/server_plugin/lib/plugin.dart
index 1ce777b..5a1761b 100644
--- a/server_plugin/lib/plugin.dart
+++ b/server_plugin/lib/plugin.dart
@@ -1,20 +1,12 @@
-library angular2.src.analysis.server_plugin;
-
 //import 'package:analysis_server/plugin/analysis/navigation/navigation.dart';
 //import 'package:analysis_server/plugin/analysis/occurrences/occurrences.dart';
-import 'package:analysis_server/src/provisional/completion/completion.dart';
-import 'package:angular_analyzer_server_plugin/src/completion.dart';
 import 'package:plugin/plugin.dart';
 
-/**
- * Contribute a plugin for services such as completions, indexing and refactoring
- * of Angular 2 dart code.
- */
+/// Contribute a plugin for services such as completions, indexing and refactoring
+/// of Angular 2 dart code.
 class AngularServerPlugin implements Plugin {
-  /**
-   * The unique identifier for this plugin.
-   */
-  static const String UNIQUE_IDENTIFIER = 'angular2.analysis.server_plugin';
+  /// The unique identifier for this plugin.
+  static const UNIQUE_IDENTIFIER = 'angular2.analysis.server_plugin';
 
   @override
   String get uniqueIdentifier => UNIQUE_IDENTIFIER;
@@ -28,9 +20,9 @@
     //    new AngularNavigationContributor());
     //registerExtension(OCCURRENCES_CONTRIBUTOR_EXTENSION_POINT_ID,
     //    new AngularOccurrencesContributor());
-    registerExtension(COMPLETION_CONTRIBUTOR_EXTENSION_POINT_ID,
-        () => new AngularTemplateCompletionContributor());
-    registerExtension(COMPLETION_CONTRIBUTOR_EXTENSION_POINT_ID,
-        () => new AngularDartCompletionContributor());
+    //registerExtension(COMPLETION_CONTRIBUTOR_EXTENSION_POINT_ID,
+    //    () => new AngularTemplateCompletionContributor());
+    //registerExtension(COMPLETION_CONTRIBUTOR_EXTENSION_POINT_ID,
+    //    () => new AngularDartCompletionContributor());
   }
 }
diff --git a/server_plugin/lib/src/analysis.dart b/server_plugin/lib/src/analysis.dart
index 8f72470..0211bd7 100644
--- a/server_plugin/lib/src/analysis.dart
+++ b/server_plugin/lib/src/analysis.dart
@@ -1,8 +1,6 @@
-library angular2.src.analysis.server_plugin.analysis;
-
 import 'package:analysis_server/plugin/analysis/navigation/navigation_core.dart';
 import 'package:analysis_server/plugin/analysis/occurrences/occurrences_core.dart';
-import 'package:analysis_server/plugin/protocol/protocol.dart' as protocol;
+import 'package:analysis_server/protocol/protocol_generated.dart' as protocol;
 import 'package:analysis_server/plugin/protocol/protocol_dart.dart' as protocol;
 import 'package:analyzer/dart/element/element.dart' as engine;
 import 'package:analyzer/src/dart/element/member.dart';
@@ -55,13 +53,12 @@
 
   void addDirectiveRegions(NavigationCollector collector, LineInfo lineInfo,
       AbstractDirective directive) {
-    for (InputElement input in directive.inputs) {
-      engine.PropertyAccessorElement setter = input.setter;
+    for (final input in directive.inputs) {
+      final setter = input.setter;
       if (setter == null) {
         continue;
       }
-      LineInfo_Location offsetLineLocation =
-          lineInfo.getLocation(setter.nameOffset);
+      final offsetLineLocation = lineInfo.getLocation(setter.nameOffset);
       if (setter != null) {
         collector.addRegion(
             input.setterRange.offset,
@@ -79,10 +76,10 @@
 
   void addTemplateRegions(
       NavigationCollector collector, LineInfo lineInfo, Template template) {
-    for (ResolvedRange resolvedRange in template.ranges) {
-      int offset = resolvedRange.range.offset;
-      AngularElement element = resolvedRange.element;
-      LineInfo_Location offsetLineLocation = lineInfo.getLocation(offset);
+    for (final resolvedRange in template.ranges) {
+      final offset = resolvedRange.range.offset;
+      final element = resolvedRange.element;
+      final offsetLineLocation = lineInfo.getLocation(offset);
       collector.addRegion(
           offset,
           resolvedRange.range.length,
@@ -135,14 +132,13 @@
 
   void addDirectiveOccurrences(
       OccurrencesCollector collector, AbstractDirective directive) {
-    Map<engine.PropertyAccessorElement, List<int>> elementsOffsets =
-        <engine.PropertyAccessorElement, List<int>>{};
-    for (InputElement input in directive.inputs) {
-      engine.PropertyAccessorElement setter = input.setter;
+    final elementsOffsets = <engine.PropertyAccessorElement, List<int>>{};
+    for (final input in directive.inputs) {
+      final setter = input.setter;
       if (setter == null) {
         continue;
       }
-      List<int> offsets = elementsOffsets[setter];
+      var offsets = elementsOffsets[setter];
       if (offsets == null) {
         offsets = <int>[setter.nameOffset];
         elementsOffsets[setter] = offsets;
@@ -151,9 +147,9 @@
     }
     // convert map into Occurrences
     elementsOffsets.forEach((setter, offsets) {
-      protocol.Element protocolElement = _newProtocolElement_forEngine(setter);
-      int length = protocolElement.location.length;
-      protocol.Occurrences occurrences =
+      final protocolElement = _newProtocolElementForEngine(setter);
+      final length = protocolElement.location.length;
+      final occurrences =
           new protocol.Occurrences(protocolElement, offsets, length);
       collector.addOccurrences(occurrences);
     });
@@ -161,11 +157,10 @@
 
   void addTemplateOccurrences(
       OccurrencesCollector collector, Template template) {
-    Map<AngularElement, List<int>> elementsOffsets =
-        <AngularElement, List<int>>{};
-    for (ResolvedRange resolvedRange in template.ranges) {
-      AngularElement element = resolvedRange.element;
-      List<int> offsets = elementsOffsets[element];
+    final elementsOffsets = <AngularElement, List<int>>{};
+    for (final resolvedRange in template.ranges) {
+      final element = resolvedRange.element;
+      var offsets = elementsOffsets[element];
       if (offsets == null) {
         offsets = <int>[element.nameOffset];
         elementsOffsets[element] = offsets;
@@ -174,38 +169,39 @@
     }
     // convert map into Occurrences
     elementsOffsets.forEach((angularElement, offsets) {
-      int length = angularElement.nameLength;
-      protocol.Element protocolElement = _newProtocolElement(angularElement);
-      protocol.Occurrences occurrences =
+      final length = angularElement.nameLength;
+      final protocolElement = _newProtocolElement(angularElement);
+      final occurrences =
           new protocol.Occurrences(protocolElement, offsets, length);
       collector.addOccurrences(occurrences);
     });
   }
 
   engine.Element _canonicalizeElement(engine.Element element) {
-    if (element is engine.PropertyAccessorElement) {
-      element = (element as engine.PropertyAccessorElement).variable;
+    var canonical = element;
+    if (canonical is engine.PropertyAccessorElement) {
+      canonical = (canonical as engine.PropertyAccessorElement).variable;
     }
-    if (element is Member) {
-      element = (element as Member).baseElement;
+    if (canonical is Member) {
+      canonical = (canonical as Member).baseElement;
     }
-    return element;
+    return canonical;
   }
 
   protocol.Element _newProtocolElement(AngularElement angularElement) {
-    String name = angularElement.name;
-    int length = name.length;
+    final name = angularElement.name;
+    final length = name.length;
     if (angularElement is DartElement) {
-      engine.Element dartElement = angularElement.element;
-      return _newProtocolElement_forEngine(dartElement);
+      final dartElement = angularElement.element;
+      return _newProtocolElementForEngine(dartElement);
     }
     return new protocol.Element(protocol.ElementKind.UNKNOWN, name, 0,
         location: new protocol.Location(angularElement.source.fullName,
             angularElement.nameOffset, length, -1, -1));
   }
 
-  protocol.Element _newProtocolElement_forEngine(engine.Element dartElement) {
-    dartElement = _canonicalizeElement(dartElement);
-    return protocol.convertElement(dartElement);
+  protocol.Element _newProtocolElementForEngine(engine.Element dartElement) {
+    final cannonical = _canonicalizeElement(dartElement);
+    return protocol.convertElement(cannonical);
   }
 }
diff --git a/server_plugin/lib/src/completion.dart b/server_plugin/lib/src/completion.dart
index a1accfc..5c2d15a 100644
--- a/server_plugin/lib/src/completion.dart
+++ b/server_plugin/lib/src/completion.dart
@@ -1,7 +1,7 @@
 import 'dart:async';
 import 'dart:collection';
 
-import 'package:analysis_server/plugin/protocol/protocol.dart' as protocol
+import 'package:analysis_server/protocol/protocol_generated.dart' as protocol
     show Element, ElementKind;
 import 'package:analysis_server/src/provisional/completion/completion_core.dart';
 import 'package:analysis_server/src/provisional/completion/dart/completion_dart.dart';
@@ -14,10 +14,12 @@
 import 'package:analyzer/error/listener.dart';
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/dart/element/type.dart';
+import 'package:analyzer/src/generated/resolver.dart' show TypeProvider;
 import 'package:angular_analyzer_plugin/src/converter.dart';
 import 'package:angular_analyzer_plugin/src/model.dart';
 import 'package:angular_analyzer_plugin/src/selector.dart';
 import 'package:angular_analyzer_plugin/ast.dart';
+import 'package:angular_analyzer_plugin/src/angular_driver.dart';
 
 import 'package:analysis_server/src/protocol_server.dart'
     show CompletionSuggestion, CompletionSuggestionKind, Location;
@@ -27,15 +29,22 @@
 
 import 'embedded_dart_completion_request.dart';
 
-bool offsetContained(int offset, int start, int length) {
-  return start <= offset && start + length >= offset;
-}
+bool offsetContained(int offset, int start, int length) =>
+    start <= offset && start + length >= offset;
 
 AngularAstNode findTarget(int offset, AngularAstNode root) {
-  for (AngularAstNode child in root.children) {
+  for (final child in root.children) {
+    // `*ngIf="x"` creates, inside the template attr, a property binding named
+    // `ngIf`, which will confuse our autocompleter. Skip it.
+    if (root is TemplateAttribute &&
+        child is AttributeInfo &&
+        child.name == root.prefix) {
+      continue;
+    }
+
     if (child is ElementInfo) {
       if (child.isSynthetic) {
-        var target = findTarget(offset, child);
+        final target = findTarget(offset, child);
         if (!(target is ElementInfo && target.openingSpan == null)) {
           return target;
         }
@@ -55,21 +64,21 @@
 }
 
 class DartSnippetExtractor extends AngularAstVisitor {
-  AstNode dartSnippet = null;
+  AstNode dartSnippet;
   int offset;
 
   @override
-  visitDocumentInfo(DocumentInfo document) {}
+  void visitDocumentInfo(DocumentInfo document) {}
 
   // don't recurse, findTarget already did that
   @override
-  visitElementInfo(ElementInfo element) {}
+  void visitElementInfo(ElementInfo element) {}
 
   @override
-  visitTextAttr(TextAttribute attr) {}
+  void visitTextAttr(TextAttribute attr) {}
 
   @override
-  visitExpressionBoundAttr(ExpressionBoundAttribute attr) {
+  void visitExpressionBoundAttr(ExpressionBoundAttribute attr) {
     if (attr.expression != null &&
         offsetContained(
             offset, attr.expression.offset, attr.expression.length)) {
@@ -78,8 +87,8 @@
   }
 
   @override
-  visitStatementsBoundAttr(StatementsBoundAttribute attr) {
-    for (Statement statement in attr.statements) {
+  void visitStatementsBoundAttr(StatementsBoundAttribute attr) {
+    for (final statement in attr.statements) {
       if (offsetContained(offset, statement.offset, statement.length)) {
         dartSnippet = statement;
       }
@@ -87,7 +96,7 @@
   }
 
   @override
-  visitMustache(Mustache mustache) {
+  void visitMustache(Mustache mustache) {
     if (offsetContained(
         offset, mustache.exprBegin, mustache.exprEnd - mustache.exprBegin)) {
       dartSnippet = mustache.expression;
@@ -95,10 +104,15 @@
   }
 
   @override
-  visitTemplateAttr(TemplateAttribute attr) {
+  void visitTemplateAttr(TemplateAttribute attr) {
+    if (attr.value == null ||
+        !offsetContained(offset, attr.valueOffset, attr.value.length)) {
+      return;
+    }
+
     // if we visit this, we're in a template but after one of its attributes.
     AttributeInfo attributeToAppendTo;
-    for (AttributeInfo subAttribute in attr.virtualAttributes) {
+    for (final subAttribute in attr.virtualAttributes) {
       if (subAttribute.valueOffset == null && subAttribute.offset < offset) {
         attributeToAppendTo = subAttribute;
       }
@@ -107,11 +121,11 @@
     if (attributeToAppendTo != null &&
         attributeToAppendTo is TextAttribute &&
         !attributeToAppendTo.name.startsWith("let")) {
-      AnalysisErrorListener analysisErrorListener =
-          new IgnoringAnalysisErrorListener();
-      EmbeddedDartParser dartParser =
+      final analysisErrorListener = new IgnoringAnalysisErrorListener();
+      final dartParser =
           new EmbeddedDartParser(null, analysisErrorListener, null);
-      dartSnippet = dartParser.parseDartExpression(offset, '', false);
+      dartSnippet =
+          dartParser.parseDartExpression(offset, '', detectTrailing: false);
     }
   }
 }
@@ -122,28 +136,30 @@
 }
 
 class LocalVariablesExtractor extends AngularAstVisitor {
-  Map<String, LocalVariable> variables = null;
+  Map<String, LocalVariable> variables;
 
   // don't recurse, findTarget already did that
   @override
-  visitDocumentInfo(DocumentInfo document) {}
-  @override
-  visitElementInfo(ElementInfo element) {}
-  @override
-  visitTextAttr(TextAttribute attr) {}
+  void visitDocumentInfo(DocumentInfo document) {}
 
   @override
-  visitExpressionBoundAttr(ExpressionBoundAttribute attr) {
+  void visitElementInfo(ElementInfo element) {}
+
+  @override
+  void visitTextAttr(TextAttribute attr) {}
+
+  @override
+  void visitExpressionBoundAttr(ExpressionBoundAttribute attr) {
     variables = attr.localVariables;
   }
 
   @override
-  visitStatementsBoundAttr(StatementsBoundAttribute attr) {
+  void visitStatementsBoundAttr(StatementsBoundAttribute attr) {
     variables = attr.localVariables;
   }
 
   @override
-  visitMustache(Mustache mustache) {
+  void visitMustache(Mustache mustache) {
     variables = mustache.localVariables;
   }
 }
@@ -154,28 +170,33 @@
   ReplacementRangeCalculator(this.request);
 
   @override
-  visitDocumentInfo(DocumentInfo document) {}
+  void visitDocumentInfo(DocumentInfo document) {}
 
   // don't recurse, findTarget already did that
   @override
-  visitElementInfo(ElementInfo element) {
+  void visitElementInfo(ElementInfo element) {
     if (element.openingSpan == null) {
       return;
     }
-    int nameSpanEnd =
+    final nameSpanEnd =
         element.openingNameSpan.offset + element.openingNameSpan.length;
     if (offsetContained(request.offset, element.openingSpan.offset,
         nameSpanEnd - element.openingSpan.offset)) {
-      request.replacementOffset = element.openingSpan.offset;
-      request.replacementLength = element.localName.length + 1;
+      request
+        ..replacementOffset = element.openingSpan.offset
+        ..replacementLength = element.localName.length + 1;
     }
   }
 
   @override
-  visitTextAttr(TextAttribute attr) {}
+  void visitTextAttr(TextAttribute attr) {
+    request
+      ..replacementOffset = attr.offset
+      ..replacementLength = attr.length;
+  }
 
   @override
-  visitTextInfo(TextInfo textInfo) {
+  void visitTextInfo(TextInfo textInfo) {
     if (request.offset > textInfo.offset &&
         textInfo.text[request.offset - textInfo.offset - 1] == '<') {
       request.replacementOffset--;
@@ -184,58 +205,75 @@
   }
 
   @override
-  visitExpressionBoundAttr(ExpressionBoundAttribute attr) {
+  void visitExpressionBoundAttr(ExpressionBoundAttribute attr) {
     if (offsetContained(
         request.offset, attr.originalNameOffset, attr.originalName.length)) {
-      request.replacementOffset = attr.originalNameOffset;
-      request.replacementLength = attr.originalName.length;
+      request
+        ..replacementOffset = attr.originalNameOffset
+        ..replacementLength = attr.originalName.length;
     }
   }
 
   @override
-  visitStatementsBoundAttr(StatementsBoundAttribute attr) {
+  void visitStatementsBoundAttr(StatementsBoundAttribute attr) {
     if (offsetContained(
         request.offset, attr.originalNameOffset, attr.originalName.length)) {
-      request.replacementOffset = attr.originalNameOffset;
-      request.replacementLength = attr.originalName.length;
+      request
+        ..replacementOffset = attr.originalNameOffset
+        ..replacementLength = attr.originalName.length;
     }
   }
 
   @override
-  visitMustache(Mustache mustache) {}
-}
+  void visitMustache(Mustache mustache) {}
 
-class AngularDartCompletionContributor extends CompletionContributor {
-  /**
-   * Return a [Future] that completes with a list of suggestions
-   * for the given completion [request].
-   */
-  Future<List<CompletionSuggestion>> computeSuggestions(
-      CompletionRequest request) async {
-    if (!request.source.shortName.endsWith('.dart')) {
-      return [];
+  @override
+  void visitTemplateAttr(TemplateAttribute attr) {
+    if (offsetContained(
+        request.offset, attr.originalNameOffset, attr.originalName.length)) {
+      request
+        ..replacementOffset = attr.originalNameOffset
+        ..replacementLength = attr.originalName.length;
     }
-
-    //return new TemplateCompleter().computeSuggestions(
-    //    request, templates, standardHtmlEvents, standardHtmlAttributes);
-    return [];
   }
 }
 
-class AngularTemplateCompletionContributor extends CompletionContributor {
-  /**
-   * Return a [Future] that completes with a list of suggestions
-   * for the given completion [request]. This will
-   * throw [AbortCompletion] if the completion request has been aborted.
-   */
+/// Contributor to contribute angular entities.
+class AngularCompletionContributor extends CompletionContributor {
+  final AngularDriver driver;
+
+  /// Initialize a newly created handler to handle requests for the given
+  /// [server].
+  AngularCompletionContributor(this.driver);
+
+  /// Return a [Future] that completes with a list of suggestions
+  /// for the given completion [request].
+  @override
   Future<List<CompletionSuggestion>> computeSuggestions(
       CompletionRequest request) async {
-    if (request.source.shortName.endsWith('.html')) {
-      //return new TemplateCompleter().computeSuggestions(
-      //    request, templates, standardHtmlEvents, standardHtmlAttributes);
-    }
+    final suggestions = <CompletionSuggestion>[];
+    final filePath = request.source.toString();
 
-    return [];
+    await driver.getStandardHtml();
+    assert(driver.standardHtml != null);
+
+    final events = driver.standardHtml.events.values;
+    final attributes = driver.standardHtml.attributes.values;
+    final templates = await driver.getTemplatesForFile(filePath);
+
+    if (templates.isEmpty) {
+      return <CompletionSuggestion>[];
+    }
+    final templateCompleter = new TemplateCompleter();
+    for (final template in templates) {
+      suggestions.addAll(await templateCompleter.computeSuggestions(
+        request,
+        template,
+        events,
+        attributes,
+      ));
+    }
+    return suggestions;
   }
 }
 
@@ -243,145 +281,165 @@
   static const int RELEVANCE_TRANSCLUSION = DART_RELEVANCE_DEFAULT + 10;
 
   Future<List<CompletionSuggestion>> computeSuggestions(
-      CompletionRequest request,
-      List<Template> templates,
-      List<OutputElement> standardHtmlEvents,
-      List<InputElement> standardHtmlAttributes) async {
-    var suggestions = <CompletionSuggestion>[];
-    for (Template template in templates) {
-      var target = findTarget(request.offset, template.ast);
-      target.accept(new ReplacementRangeCalculator(request));
-      var extractor = new DartSnippetExtractor();
-      extractor.offset = request.offset;
-      target.accept(extractor);
+    CompletionRequest request,
+    Template template,
+    List<OutputElement> standardHtmlEvents,
+    List<InputElement> standardHtmlAttributes,
+  ) async {
+    final suggestions = <CompletionSuggestion>[];
+    final typeProvider = template.view.component.classElement.enclosingElement
+        .enclosingElement.context.typeProvider;
+    final target = findTarget(request.offset, template.ast)
+      ..accept(new ReplacementRangeCalculator(request));
+    final extractor = new DartSnippetExtractor()..offset = request.offset;
+    target.accept(extractor);
 
-      // If [CompletionRequest] is in
-      // [StatementsBoundAttribute],
-      // [ExpressionsBoundAttribute],
-      // [Mustache],
-      // [TemplateAttribute].
-      if (extractor.dartSnippet != null) {
-        var dartRequest = new EmbeddedDartCompletionRequest.from(
-            request, extractor.dartSnippet);
-        var range = new ReplacementRange.compute(
-            dartRequest.offset, dartRequest.target);
-        (request as CompletionRequestImpl)
-          ..replacementOffset = range.offset
-          ..replacementLength = range.length;
+    // If [CompletionRequest] is in
+    // [StatementsBoundAttribute],
+    // [ExpressionsBoundAttribute],
+    // [Mustache],
+    // [TemplateAttribute].
+    if (extractor.dartSnippet != null) {
+      final dartRequest = new EmbeddedDartCompletionRequest.from(
+          request, extractor.dartSnippet);
+      final range =
+          new ReplacementRange.compute(dartRequest.offset, dartRequest.target);
+      (request as CompletionRequestImpl)
+        ..replacementOffset = range.offset
+        ..replacementLength = range.length;
 
-        dartRequest.libraryElement = template.view.classElement.library;
-        var memberContributor = new TypeMemberContributor();
-        var inheritedContributor = new InheritedReferenceContributor();
+      dartRequest.libraryElement = template.view.classElement.library;
+      final memberContributor = new TypeMemberContributor();
+      final inheritedContributor = new InheritedReferenceContributor();
 
-        suggestions.addAll(
+      suggestions
+        ..addAll(
           inheritedContributor.computeSuggestionsForClass(
             template.view.classElement,
             dartRequest,
             skipChildClass: false,
           ),
-        );
-        suggestions
-            .addAll(await memberContributor.computeSuggestions(dartRequest));
+        )
+        ..addAll(await memberContributor.computeSuggestions(dartRequest));
 
-        if (dartRequest.opType.includeIdentifiers) {
-          var varExtractor = new LocalVariablesExtractor();
-          target.accept(varExtractor);
-          if (varExtractor.variables != null) {
-            addLocalVariables(
-              suggestions,
-              varExtractor.variables,
-              dartRequest.opType,
-            );
-          }
+      if (dartRequest.opType.includeIdentifiers) {
+        final varExtractor = new LocalVariablesExtractor();
+        target.accept(varExtractor);
+        if (varExtractor.variables != null) {
+          addLocalVariables(
+            suggestions,
+            varExtractor.variables,
+            dartRequest.opType,
+          );
         }
-      } else if (target is ElementInfo) {
-        if (target.closingSpan != null &&
-            offsetContained(request.offset, target.closingSpan.offset,
-                target.closingSpan.length)) {
-          if (request.offset ==
-              (target.closingSpan.offset + target.closingSpan.length)) {
-            // In closing tag, but could be directly after it; ex: '</div>^'.
-            suggestHtmlTags(template, suggestions);
-            if (target.parent != null || target.parent is! DocumentInfo) {
-              suggestTransclusions(target.parent, suggestions);
-            }
-          } else {
-            // Directly within closing tag; suggest nothing. Ex: '</div^>'
-            continue;
-          }
-        }
-        if (!offsetContained(request.offset, target.openingNameSpan.offset,
-            target.openingNameSpan.length)) {
-          // If request is not in [openingNameSpan], suggest decorators.
-          suggestInputs(target.boundDirectives, suggestions,
-              standardHtmlAttributes, target.boundStandardInputs);
-          suggestOutputs(target.boundDirectives, suggestions,
-              standardHtmlEvents, target.boundStandardOutputs);
-        } else {
-          // Otherwise, suggest HTML tags and transclusions.
+      }
+    } else if (target is ElementInfo) {
+      if (target.closingSpan != null &&
+          offsetContained(request.offset, target.closingSpan.offset,
+              target.closingSpan.length)) {
+        if (request.offset ==
+            (target.closingSpan.offset + target.closingSpan.length)) {
+          // In closing tag, but could be directly after it; ex: '</div>^'.
           suggestHtmlTags(template, suggestions);
           if (target.parent != null || target.parent is! DocumentInfo) {
             suggestTransclusions(target.parent, suggestions);
           }
         }
-      } else if (target is ExpressionBoundAttribute &&
-          target.bound == ExpressionBoundType.input &&
-          offsetContained(request.offset, target.originalNameOffset,
-              target.originalName.length)) {
-        suggestInputs(target.parent.boundDirectives, suggestions,
-            standardHtmlAttributes, target.parent.boundStandardInputs,
-            currentAttr: target);
-      } else if (target is StatementsBoundAttribute) {
-        suggestOutputs(target.parent.boundDirectives, suggestions,
-            standardHtmlEvents, target.parent.boundStandardOutputs,
-            currentAttr: target);
-      } else if (target is TemplateAttribute) {
-        suggestInputs(target.parent.boundDirectives, suggestions,
-            standardHtmlAttributes, target.parent.boundStandardInputs);
-        suggestOutputs(target.parent.boundDirectives, suggestions,
-            standardHtmlEvents, target.parent.boundStandardOutputs);
-      } else if (target is TextAttribute &&
-          target.nameOffset != null &&
-          offsetContained(
-              request.offset, target.nameOffset, target.name.length)) {
-        suggestInputs(target.parent.boundDirectives, suggestions,
-            standardHtmlAttributes, target.parent.boundStandardInputs);
-        suggestOutputs(target.parent.boundDirectives, suggestions,
-            standardHtmlEvents, target.parent.boundStandardOutputs);
-      } else if (target is TextInfo) {
+      } else if (!offsetContained(request.offset, target.openingNameSpan.offset,
+          target.openingNameSpan.length)) {
+        // If request is not in [openingNameSpan], suggest decorators.
+        suggestInputs(target.boundDirectives, suggestions,
+            standardHtmlAttributes, target.boundStandardInputs, typeProvider,
+            includePlainAttributes: true);
+        suggestOutputs(target.boundDirectives, suggestions, standardHtmlEvents,
+            target.boundStandardOutputs);
+        if (!target.isOrHasTemplateAttribute) {
+          suggestStarAttrs(template, suggestions);
+        }
+      } else {
+        // Otherwise, suggest HTML tags and transclusions.
         suggestHtmlTags(template, suggestions);
-        suggestTransclusions(target.parent, suggestions);
+        if (target.parent != null || target.parent is! DocumentInfo) {
+          suggestTransclusions(target.parent, suggestions);
+        }
       }
+    } else if (target is AttributeInfo && target.parent is TemplateAttribute) {
+      // `let foo`. Nothing to suggest.
+      if (target is TextAttribute && target.name.startsWith("let-")) {
+        return suggestions;
+      }
+
+      if (offsetContained(request.offset, target.originalNameOffset,
+          target.originalName.length)) {
+        suggestInputsInTemplate(target.parent, suggestions,
+            currentAttr: target);
+      } else {
+        suggestInputsInTemplate(target.parent, suggestions);
+      }
+    } else if (target is ExpressionBoundAttribute &&
+        target.bound == ExpressionBoundType.input &&
+        offsetContained(request.offset, target.originalNameOffset,
+            target.originalName.length)) {
+      suggestInputs(
+          target.parent.boundDirectives,
+          suggestions,
+          standardHtmlAttributes,
+          target.parent.boundStandardInputs,
+          typeProvider,
+          currentAttr: target);
+    } else if (target is StatementsBoundAttribute) {
+      suggestOutputs(target.parent.boundDirectives, suggestions,
+          standardHtmlEvents, target.parent.boundStandardOutputs,
+          currentAttr: target);
+    } else if (target is TemplateAttribute) {
+      if (offsetContained(request.offset, target.originalNameOffset,
+          target.originalName.length)) {
+        suggestStarAttrs(template, suggestions);
+      }
+      suggestInputsInTemplate(target, suggestions);
+    } else if (target is TextAttribute &&
+        target.nameOffset != null &&
+        offsetContained(
+            request.offset, target.nameOffset, target.name.length)) {
+      suggestInputs(
+          target.parent.boundDirectives,
+          suggestions,
+          standardHtmlAttributes,
+          target.parent.boundStandardInputs,
+          typeProvider,
+          includePlainAttributes: true);
+      suggestOutputs(target.parent.boundDirectives, suggestions,
+          standardHtmlEvents, target.parent.boundStandardOutputs);
+    } else if (target is TextInfo) {
+      suggestHtmlTags(template, suggestions);
+      suggestTransclusions(target.parent, suggestions);
     }
+
     return suggestions;
   }
 
-  suggestTransclusions(
+  void suggestTransclusions(
       ElementInfo container, List<CompletionSuggestion> suggestions) {
-    for (AbstractDirective directive in container.directives) {
+    for (final directive in container.directives) {
       if (directive is! Component) {
         continue;
       }
 
-      Component component = directive;
-      Template template = component?.view?.template;
-      if (template == null) {
+      final Component component = directive;
+      final view = component?.view;
+      if (view == null) {
         continue;
       }
 
-      for (NgContent ngContent in component.ngContents) {
+      for (final ngContent in component.ngContents) {
         if (ngContent.selector == null) {
           continue;
         }
 
-        List<HtmlTagForSelector> tags = ngContent.selector.suggestTags();
-        for (HtmlTagForSelector tag in tags) {
-          Location location = new Location(
-              template.view.templateSource.fullName,
-              ngContent.offset,
-              ngContent.length,
-              0,
-              0);
+        final tags = ngContent.selector.suggestTags();
+        for (final tag in tags) {
+          final location = new Location(view.templateSource.fullName,
+              ngContent.offset, ngContent.length, 0, 0);
           suggestions.add(_createHtmlTagSuggestion(
               tag.toString(),
               RELEVANCE_TRANSCLUSION,
@@ -392,12 +450,12 @@
     }
   }
 
-  suggestHtmlTags(Template template, List<CompletionSuggestion> suggestions) {
-    Map<String, List<AbstractDirective>> elementTagMap =
-        template.view.elementTagsInfo;
-    for (String elementTagName in elementTagMap.keys) {
-      CompletionSuggestion currentSuggestion = _createHtmlTagSuggestion(
-          '<' + elementTagName,
+  void suggestHtmlTags(
+      Template template, List<CompletionSuggestion> suggestions) {
+    final elementTagMap = template.view.elementTagsInfo;
+    for (final elementTagName in elementTagMap.keys) {
+      final currentSuggestion = _createHtmlTagSuggestion(
+          '<$elementTagName',
           DART_RELEVANCE_DEFAULT,
           _createHtmlTagElement(
               elementTagName,
@@ -409,52 +467,107 @@
     }
   }
 
-  suggestInputs(
-      List<DirectiveBinding> directives,
-      List<CompletionSuggestion> suggestions,
-      List<InputElement> standardHtmlAttributes,
-      List<InputBinding> boundStandardAttributes,
-      {ExpressionBoundAttribute currentAttr}) {
-    for (DirectiveBinding directive in directives) {
-      Set<InputElement> usedInputs = new HashSet.from(directive.inputBindings
+  void suggestInputs(
+    List<DirectiveBinding> directives,
+    List<CompletionSuggestion> suggestions,
+    List<InputElement> standardHtmlAttributes,
+    List<InputBinding> boundStandardAttributes,
+    TypeProvider typeProvider, {
+    ExpressionBoundAttribute currentAttr,
+    bool includePlainAttributes: false,
+  }) {
+    for (final directive in directives) {
+      final usedInputs = new HashSet.from(directive.inputBindings
           .where((b) => b.attribute != currentAttr)
-          .map((b) => b.boundInput));
+          .map((b) => b.boundInput)).toSet();
 
-      for (InputElement input in directive.boundDirective.inputs) {
+      for (final input in directive.boundDirective.inputs) {
         // don't recommend [name] [name] [name]
         if (usedInputs.contains(input)) {
           continue;
         }
+
+        if (includePlainAttributes && typeProvider != null) {
+          if (typeProvider.stringType.isAssignableTo(input.setterType)) {
+            final relevance = input.setterType.displayName == 'String'
+                ? DART_RELEVANCE_DEFAULT
+                : DART_RELEVANCE_DEFAULT - 1;
+            suggestions.add(_createPlainAttributeSuggestions(
+                input,
+                relevance,
+                _createPlainAttributeElement(
+                    input, protocol.ElementKind.SETTER)));
+          }
+        }
         suggestions.add(_createInputSuggestion(input, DART_RELEVANCE_DEFAULT,
             _createInputElement(input, protocol.ElementKind.SETTER)));
       }
     }
 
-    Set<InputElement> usedStdInputs = new HashSet.from(boundStandardAttributes
+    final usedStdInputs = new HashSet.from(boundStandardAttributes
         .where((b) => b.attribute != currentAttr)
-        .map((b) => b.boundInput));
+        .map((b) => b.boundInput)).toSet();
 
-    for (InputElement input in standardHtmlAttributes) {
+    for (final input in standardHtmlAttributes) {
       // TODO don't recommend [hidden] [hidden] [hidden]
       if (usedStdInputs.contains(input)) {
         continue;
       }
-      suggestions.add(_createInputSuggestion(input, DART_RELEVANCE_DEFAULT - 1,
+      if (includePlainAttributes && typeProvider != null) {
+        if (typeProvider.stringType.isAssignableTo(input.setterType)) {
+          final relevance = input.setterType.displayName == 'String'
+              ? DART_RELEVANCE_DEFAULT - 2
+              : DART_RELEVANCE_DEFAULT - 3;
+          suggestions.add(_createPlainAttributeSuggestions(
+              input,
+              relevance,
+              _createPlainAttributeElement(
+                  input, protocol.ElementKind.SETTER)));
+        }
+      }
+      suggestions.add(_createInputSuggestion(input, DART_RELEVANCE_DEFAULT - 2,
           _createInputElement(input, protocol.ElementKind.SETTER)));
     }
   }
 
-  suggestOutputs(
+  void suggestInputsInTemplate(
+      TemplateAttribute templateAttr, List<CompletionSuggestion> suggestions,
+      {AttributeInfo currentAttr}) {
+    final directives = templateAttr.boundDirectives;
+    for (final binding in directives) {
+      final usedInputs = new HashSet.from(binding.inputBindings
+          .where((b) => b.attribute != currentAttr)
+          .map((b) => b.boundInput)).toSet();
+
+      for (final input in binding.boundDirective.inputs) {
+        // don't recommend trackBy: x trackBy: x trackBy: x
+        if (usedInputs.contains(input)) {
+          continue;
+        }
+        // edge case. Don't think this comes up in standard.
+        if (!input.name.startsWith(templateAttr.prefix)) {
+          continue;
+        }
+        suggestions.add(_createInputInTemplateSuggestion(
+            templateAttr.prefix,
+            input,
+            DART_RELEVANCE_DEFAULT,
+            _createInputElement(input, protocol.ElementKind.SETTER)));
+      }
+    }
+  }
+
+  void suggestOutputs(
       List<DirectiveBinding> directives,
       List<CompletionSuggestion> suggestions,
       List<OutputElement> standardHtmlEvents,
       List<OutputBinding> boundStandardOutputs,
       {BoundAttributeInfo currentAttr}) {
-    for (DirectiveBinding directive in directives) {
-      Set<OutputElement> usedOutputs = new HashSet.from(directive.outputBindings
+    for (final directive in directives) {
+      final usedOutputs = new HashSet.from(directive.outputBindings
           .where((b) => b.attribute != currentAttr)
-          .map((b) => b.boundOutput));
-      for (OutputElement output in directive.boundDirective.outputs) {
+          .map((b) => b.boundOutput)).toSet();
+      for (final output in directive.boundDirective.outputs) {
         // don't recommend (close) (close) (close)
         if (usedOutputs.contains(output)) {
           continue;
@@ -464,11 +577,11 @@
       }
     }
 
-    Set<OutputElement> usedStdOutputs = new HashSet.from(boundStandardOutputs
+    final usedStdOutputs = new HashSet.from(boundStandardOutputs
         .where((b) => b.attribute != currentAttr)
-        .map((b) => b.boundOutput));
+        .map((b) => b.boundOutput)).toSet();
 
-    for (OutputElement output in standardHtmlEvents) {
+    for (final output in standardHtmlEvents) {
       // don't recommend (click) (click) (click)
       if (usedStdOutputs.contains(output)) {
         continue;
@@ -480,9 +593,42 @@
     }
   }
 
-  addLocalVariables(List<CompletionSuggestion> suggestions,
+  void suggestStarAttrs(
+      Template template, List<CompletionSuggestion> suggestions) {
+    template.view.directives.where((d) => d.looksLikeTemplate).forEach(
+        (directive) =>
+            suggestStarAttrsForSelector(directive.selector, suggestions));
+  }
+
+  void suggestStarAttrsForSelector(
+      Selector selector, List<CompletionSuggestion> suggestions) {
+    if (selector is OrSelector) {
+      for (final subselector in selector.selectors) {
+        suggestStarAttrsForSelector(subselector, suggestions);
+      }
+    } else if (selector is AndSelector) {
+      for (final subselector in selector.selectors) {
+        suggestStarAttrsForSelector(subselector, suggestions);
+      }
+    } else if (selector is AttributeSelector) {
+      if (selector.nameElement.name == "ngForOf") {
+        // `ngFor`'s selector includes `[ngForOf]`, but `*ngForOf=..` won't ever
+        // work, because it then becomes impossible to satisfy the other half,
+        // `[ngFor]`. Hardcode to filter this out, rather than using some kind
+        // of complex heuristic.
+        return;
+      }
+
+      suggestions.add(_createStarAttrSuggestion(
+          selector,
+          DART_RELEVANCE_DEFAULT,
+          _createStarAttrElement(selector, protocol.ElementKind.CLASS)));
+    }
+  }
+
+  void addLocalVariables(List<CompletionSuggestion> suggestions,
       Map<String, LocalVariable> localVars, OpType optype) {
-    for (LocalVariable eachVar in localVars.values) {
+    for (final eachVar in localVars.values) {
       suggestions.add(_addLocalVariableSuggestion(
           eachVar,
           eachVar.dartVariable.type,
@@ -495,6 +641,7 @@
   CompletionSuggestion _addLocalVariableSuggestion(LocalVariable variable,
       DartType typeName, protocol.ElementKind elemKind, OpType optype,
       {int relevance: DART_RELEVANCE_DEFAULT}) {
+    // ignore: parameter_assignments
     relevance = optype.returnValueSuggestionsFilter(
             variable.dartVariable.type, relevance) ??
         DART_RELEVANCE_DEFAULT;
@@ -504,7 +651,7 @@
 
   CompletionSuggestion _createLocalSuggestion(LocalVariable localVar,
       int defaultRelevance, DartType type, protocol.Element element) {
-    String completion = localVar.name;
+    final completion = localVar.name;
     return new CompletionSuggestion(CompletionSuggestionKind.INVOCATION,
         defaultRelevance, completion, completion.length, 0, false, false,
         returnType: type.toString(), element: element);
@@ -512,45 +659,44 @@
 
   protocol.Element _createLocalElement(
       LocalVariable localVar, protocol.ElementKind kind, DartType type) {
-    String name = localVar.name;
-    Location location = new Location(localVar.source.fullName,
-        localVar.nameOffset, localVar.nameLength, 0, 0);
-    int flags = protocol.Element.makeFlags();
+    final name = localVar.name;
+    final location = new Location(localVar.source.fullName, localVar.nameOffset,
+        localVar.nameLength, 0, 0);
+    final flags = protocol.Element.makeFlags();
     return new protocol.Element(kind, name, flags,
         location: location, returnType: type.toString());
   }
 
-  CompletionSuggestion _createHtmlTagSuggestion(
-      String elementTagName, int defaultRelevance, protocol.Element element) {
-    return new CompletionSuggestion(
-        CompletionSuggestionKind.INVOCATION,
-        defaultRelevance,
-        elementTagName,
-        elementTagName.length,
-        0,
-        false,
-        false,
-        element: element);
-  }
+  CompletionSuggestion _createHtmlTagSuggestion(String elementTagName,
+          int defaultRelevance, protocol.Element element) =>
+      new CompletionSuggestion(
+          CompletionSuggestionKind.INVOCATION,
+          defaultRelevance,
+          elementTagName,
+          elementTagName.length,
+          0,
+          false,
+          false,
+          element: element);
 
   protocol.Element _createHtmlTagElement(String elementTagName,
       AbstractDirective directive, protocol.ElementKind kind) {
-    ElementNameSelector selector = directive.elementTags.firstWhere(
+    final selector = directive.elementTags.firstWhere(
         (currSelector) => currSelector.toString() == elementTagName);
-    int offset = selector.nameElement.nameOffset;
-    int length = selector.nameElement.nameLength;
+    final offset = selector.nameElement.nameOffset;
+    final length = selector.nameElement.nameLength;
 
-    Location location =
+    final location =
         new Location(directive.source.fullName, offset, length, 0, 0);
-    int flags = protocol.Element
+    final flags = protocol.Element
         .makeFlags(isAbstract: false, isDeprecated: false, isPrivate: false);
-    return new protocol.Element(kind, '<' + elementTagName, flags,
+    return new protocol.Element(kind, '<$elementTagName', flags,
         location: location);
   }
 
   protocol.Element _createHtmlTagTransclusionElement(
       String elementTagName, protocol.ElementKind kind, Location location) {
-    int flags = protocol.Element
+    final flags = protocol.Element
         .makeFlags(isAbstract: false, isDeprecated: false, isPrivate: false);
     return new protocol.Element(kind, elementTagName, flags,
         location: location);
@@ -558,7 +704,21 @@
 
   CompletionSuggestion _createInputSuggestion(InputElement inputElement,
       int defaultRelevance, protocol.Element element) {
-    String completion = '[' + inputElement.name + ']';
+    final completion = '[${inputElement.name}]';
+    return new CompletionSuggestion(CompletionSuggestionKind.INVOCATION,
+        defaultRelevance, completion, completion.length, 0, false, false,
+        element: element);
+  }
+
+  CompletionSuggestion _createInputInTemplateSuggestion(
+      String prefix,
+      InputElement inputElement,
+      int defaultRelevance,
+      protocol.Element element) {
+    final capitalized = inputElement.name.substring(prefix.length);
+    final firstLetter = capitalized.substring(0, 1).toLowerCase();
+    final remaining = capitalized.substring(1);
+    final completion = '$firstLetter$remaining:';
     return new CompletionSuggestion(CompletionSuggestionKind.INVOCATION,
         defaultRelevance, completion, completion.length, 0, false, false,
         element: element);
@@ -566,17 +726,37 @@
 
   protocol.Element _createInputElement(
       InputElement inputElement, protocol.ElementKind kind) {
-    String name = '[' + inputElement.name + ']';
-    Location location = new Location(inputElement.source.fullName,
+    final name = '[${inputElement.name}]';
+    final location = new Location(inputElement.source.fullName,
         inputElement.nameOffset, inputElement.nameLength, 0, 0);
-    int flags = protocol.Element
+    final flags = protocol.Element
+        .makeFlags(isAbstract: false, isDeprecated: false, isPrivate: false);
+    return new protocol.Element(kind, name, flags, location: location);
+  }
+
+  CompletionSuggestion _createPlainAttributeSuggestions(
+      InputElement inputElement,
+      int defaultRelevance,
+      protocol.Element element) {
+    final completion = inputElement.name;
+    return new CompletionSuggestion(CompletionSuggestionKind.INVOCATION,
+        defaultRelevance, completion, completion.length, 0, false, false,
+        element: element);
+  }
+
+  protocol.Element _createPlainAttributeElement(
+      InputElement inputElement, protocol.ElementKind kind) {
+    final name = inputElement.name;
+    final location = new Location(inputElement.source.fullName,
+        inputElement.nameOffset, inputElement.nameLength, 0, 0);
+    final flags = protocol.Element
         .makeFlags(isAbstract: false, isDeprecated: false, isPrivate: false);
     return new protocol.Element(kind, name, flags, location: location);
   }
 
   CompletionSuggestion _createOutputSuggestion(OutputElement outputElement,
       int defaultRelevance, protocol.Element element) {
-    String completion = '(' + outputElement.name + ')';
+    final completion = '(${outputElement.name})';
     return new CompletionSuggestion(CompletionSuggestionKind.INVOCATION,
         defaultRelevance, completion, completion.length, 0, false, false,
         element: element, returnType: outputElement.eventType.toString());
@@ -584,11 +764,32 @@
 
   protocol.Element _createOutputElement(
       OutputElement outputElement, protocol.ElementKind kind) {
-    String name = '(' + outputElement.name + ')';
-    Location location = new Location(outputElement.source.fullName,
+    final name = '(${ outputElement.name})';
+    final location = new Location(outputElement.source.fullName,
         outputElement.nameOffset, outputElement.nameLength, 0, 0);
-    int flags = protocol.Element.makeFlags();
+    final flags = protocol.Element.makeFlags();
     return new protocol.Element(kind, name, flags,
         location: location, returnType: outputElement.eventType.toString());
   }
+
+  CompletionSuggestion _createStarAttrSuggestion(AttributeSelector selector,
+      int defaultRelevance, protocol.Element element) {
+    final completion = '*${selector.nameElement.name}';
+    return new CompletionSuggestion(CompletionSuggestionKind.IDENTIFIER,
+        defaultRelevance, completion, completion.length, 0, false, false,
+        element: element);
+  }
+
+  protocol.Element _createStarAttrElement(
+      AttributeSelector selector, protocol.ElementKind kind) {
+    final name = '*${selector.nameElement.name}';
+    final location = new Location(
+        selector.nameElement.source.fullName,
+        selector.nameElement.nameOffset,
+        selector.nameElement.name.length,
+        0,
+        0);
+    final flags = protocol.Element.makeFlags();
+    return new protocol.Element(kind, name, flags, location: location);
+  }
 }
diff --git a/server_plugin/lib/src/embedded_dart_completion_request.dart b/server_plugin/lib/src/embedded_dart_completion_request.dart
index 8c84713..9bf4a0a 100644
--- a/server_plugin/lib/src/embedded_dart_completion_request.dart
+++ b/server_plugin/lib/src/embedded_dart_completion_request.dart
@@ -1,12 +1,8 @@
-import 'dart:async';
-
 import 'package:analysis_server/src/provisional/completion/completion_core.dart';
 import 'package:analysis_server/src/provisional/completion/dart/completion_dart.dart';
 import 'package:analysis_server/src/services/completion/dart/optype.dart';
 import 'package:analysis_server/src/provisional/completion/dart/completion_target.dart';
-import 'package:analysis_server/src/services/search/search_engine.dart';
 import 'package:analysis_server/src/ide_options.dart';
-import 'package:analyzer/src/generated/engine.dart' show AnalysisContext;
 import 'package:analyzer/src/dart/analysis/driver.dart';
 import 'package:analyzer/src/generated/source.dart';
 import 'package:analyzer/dart/element/element.dart';
@@ -21,36 +17,17 @@
     request.checkAborted();
 
     Source libSource;
-    if (request.context != null) {
-      Source source = request.source;
-      libSource = source;
-    }
+    libSource = request.source;
 
-    var dartRequest = new EmbeddedDartCompletionRequest._(
-        request.result,
-        request.context,
-        request.resourceProvider,
-        request.searchEngine,
-        libSource,
-        request.source,
-        request.offset);
-
-    dartRequest._updateTargets(dart);
-    return dartRequest;
+    return new EmbeddedDartCompletionRequest._(request.result,
+        request.resourceProvider, libSource, request.source, request.offset)
+      .._updateTargets(dart);
   }
 
-  EmbeddedDartCompletionRequest._(
-      this.result,
-      this.context,
-      this.resourceProvider,
-      this.searchEngine,
-      this.librarySource,
-      this.source,
-      this.offset) {}
+  EmbeddedDartCompletionRequest._(this.result, this.resourceProvider,
+      this.librarySource, this.source, this.offset);
 
-  /**
-   * Update the completion [target] and [dotTarget] based on the given [dart] AST
-   */
+  /// Update the completion [target] and [dotTarget] based on the given [dart] AST
   void _updateTargets(AstNode dart) {
     dotTarget = null;
     target = new CompletionTarget.forOffset(null, offset, entryPoint: dart);
@@ -59,10 +36,11 @@
     // if the containing node IS the AST, it means the context decides what's
     // completable. In that case, that's in our court only.
     if (target.containingNode == dart) {
-      opType.includeReturnValueSuggestions = true;
-      opType.includeTypeNameSuggestions = true;
-      // expressions always have nonvoid returns
-      opType.includeVoidReturnSuggestions = !(dart is Expression);
+      opType
+        ..includeReturnValueSuggestions = true
+        ..includeTypeNameSuggestions = true
+        // expressions always have nonvoid returns
+        ..includeVoidReturnSuggestions = !(dart is Expression);
     }
 
     // NG Expressions (not statements) always must return something. We have to
@@ -71,7 +49,8 @@
       opType.includeVoidReturnSuggestions = false;
     }
 
-    AstNode node = target.containingNode;
+    // Below is copied from analysis_server.../completion_manager.dart.
+    final node = target.containingNode;
     if (node is MethodInvocation) {
       if (identical(node.methodName, target.entity)) {
         dotTarget = node.realTarget;
@@ -94,9 +73,6 @@
   }
 
   @override
-  AnalysisContext context;
-
-  @override
   int offset;
 
   @override
@@ -106,9 +82,6 @@
   AnalysisResult result;
 
   @override
-  SearchEngine searchEngine;
-
-  @override
   Source source;
 
   @override
@@ -123,34 +96,6 @@
   @override
   CompletionTarget target;
 
-  /**
-   * Do nothing here, our expressions are already resolved.
-   */
-  @override
-  Future resolveContainingExpression(AstNode node) async {}
-
-  /**
-   * Do nothing here, our statements are already resolved.
-   */
-  @override
-  Future resolveContainingStatement(AstNode node) async {}
-
-  /**
-   * We don't use completions which rely on this
-   */
-  @override
-  Future<List<ImportElement>> resolveImports() async {
-    return [];
-  }
-
-  /**
-   * We don't use completions which rely on this
-   */
-  @override
-  Future<List<CompilationUnitElement>> resolveUnits() async {
-    return [];
-  }
-
   @override
   LibraryElement coreLib;
 
@@ -158,36 +103,26 @@
   Expression dotTarget;
 
   @override
-  bool get includeIdentifiers {
-    return opType.includeIdentifiers;
-  }
+  bool get includeIdentifiers => opType.includeIdentifiers;
 
   @override
   IdeOptions get ideOptions => null;
 
-  /**
-   * We have to return non null or much code will view this as an isolated part
-   * file. We will use our template's libraryElement.
-   */
+  /// We have to return non null or much code will view this as an isolated part
+  /// file. We will use our template's libraryElement.
   @override
   LibraryElement libraryElement;
 
-  /**
-   * We have to return non null or much code will view this as an isolated part
-   * file. We will use our template's libraryElement.
-   */
+  /// We have to return non null or much code will view this as an isolated part
+  /// file. We will use our template's libraryElement.
   @override
   Source librarySource;
 
-  /**
-   * Answer the [DartType] for Object in dart:core
-   */
+  /// Answer the [DartType] for Object in dart:core
   @override
   DartType objectType;
 
-  /**
-   * Return the [SourceFactory] of the request.
-   */
+  /// Return the [SourceFactory] of the request.
   @override
   SourceFactory sourceFactory;
 }
diff --git a/server_plugin/pubspec.yaml b/server_plugin/pubspec.yaml
index bdfffcf..089b328 100644
--- a/server_plugin/pubspec.yaml
+++ b/server_plugin/pubspec.yaml
@@ -4,7 +4,7 @@
 environment:
   sdk: '>=1.21.0-dev.1.0'
 dependencies:
-  analyzer: '>=0.25.1 <0.27.0'
+  analyzer: '^0.30.0'
   angular_analyzer_plugin:
     path: ../analyzer_plugin
   analysis_server: any
diff --git a/server_plugin/test/analysis_test.dart b/server_plugin/test/analysis_test.dart
index ef16d77..7e6a123 100644
--- a/server_plugin/test/analysis_test.dart
+++ b/server_plugin/test/analysis_test.dart
@@ -2,7 +2,9 @@
 
 import 'package:analysis_server/plugin/analysis/navigation/navigation_core.dart';
 import 'package:analysis_server/plugin/analysis/occurrences/occurrences_core.dart';
-import 'package:analysis_server/plugin/protocol/protocol.dart' as protocol;
+import 'package:analysis_server/protocol/protocol_generated.dart' as protocol;
+import 'package:analysis_server/src/plugin/notification_manager.dart';
+import 'package:analysis_server/src/analysis_server.dart';
 import 'package:analyzer/file_system/file_system.dart';
 import 'package:analyzer/file_system/memory_file_system.dart';
 import 'package:analyzer/src/context/context.dart' show AnalysisContextImpl;
@@ -13,9 +15,18 @@
 import 'package:analyzer/src/generated/source.dart';
 import 'package:analyzer/src/task/driver.dart';
 import 'package:analyzer/src/task/manager.dart';
+import 'package:analyzer/source/package_map_resolver.dart';
 import 'package:analyzer/task/model.dart';
+import 'package:analyzer/context/context_root.dart';
+import 'package:analyzer/src/dart/analysis/driver.dart' as non_task
+    show AnalysisDriver, AnalysisDriverScheduler;
+import 'package:analyzer/src/dart/analysis/file_state.dart';
+import 'package:analyzer/src/generated/engine.dart';
+import 'package:front_end/src/incremental/byte_store.dart';
+import 'package:front_end/src/base/performace_logger.dart';
 import 'package:angular_analyzer_plugin/plugin.dart';
 import 'package:angular_analyzer_server_plugin/src/analysis.dart';
+import 'package:angular_analyzer_plugin/src/angular_driver.dart';
 import 'package:plugin/manager.dart';
 import 'package:plugin/plugin.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
@@ -24,7 +35,7 @@
 
 import 'mock_sdk.dart';
 
-main() {
+void main() {
   defineReflectiveSuite(() {
     // TODO get these working again in the latest SDK
     //defineReflectiveTests(AngularNavigationContributorTest);
@@ -35,6 +46,7 @@
 
 @reflectiveTest
 class EmptyTest {
+  // ignore: non_constant_identifier_names
   void test_soTheSuitePasses() {
     expect(null, isNull);
   }
@@ -50,16 +62,17 @@
   _RecordedNavigationRegion region;
   protocol.Location targetLocation;
 
+  @override
   void setUp() {
     super.setUp();
-    when(collector.addRegion(anyInt, anyInt, anyObject, anyObject)).thenInvoke(
-        (int offset, int length, protocol.ElementKind targetKind,
-            protocol.Location targetLocation) {
+    when(collector.addRegion(anyInt, anyInt, anyObject, anyObject))
+        .thenInvoke((offset, length, targetKind, targetLocation) {
       regions.add(new _RecordedNavigationRegion(
           offset, length, targetKind, targetLocation));
     });
   }
 
+  // ignore: non_constant_identifier_names
   void test_dart_templates() {
     addAngularSources();
     code = r'''
@@ -86,7 +99,7 @@
   String name; // 3
 }
 ''';
-    Source source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     //LibrarySpecificUnit target = new LibrarySpecificUnit(source, source);
     //computeResult(target, DART_TEMPLATES);
     // compute navigation regions
@@ -143,6 +156,7 @@
     }
   }
 
+  // ignore: non_constant_identifier_names
   void test_dart_view_templateUrl() {
     addAngularSources();
     code = r'''
@@ -152,7 +166,7 @@
 @View(templateUrl: 'text_panel.html')
 class TextPanel {}
 ''';
-    Source dartSource = newSource('/test.dart', code);
+    final dartSource = newSource('/test.dart', code);
     newSource('/text_panel.html', "");
     // compute views, so that we have the TEMPLATE_VIEWS result
     //{
@@ -174,9 +188,10 @@
     }
   }
 
+  // ignore: non_constant_identifier_names
   void test_html_templates() {
     addAngularSources();
-    String dartCode = r'''
+    final dartCode = r'''
 import '/angular2/src/core/metadata.dart';
 
 @Component(selector: 'text-panel')
@@ -185,13 +200,13 @@
   String text; // 1
 }
 ''';
-    String htmlCode = r"""
+    final htmlCode = r"""
 <div>
   {{text}}
 </div>
 """;
     newSource('/test.dart', dartCode);
-    Source htmlSource = newSource('/text_panel.html', htmlCode);
+    final htmlSource = newSource('/text_panel.html', htmlCode);
     // compute views, so that we have the TEMPLATE_VIEWS result
     //{
     //  LibrarySpecificUnit target =
@@ -213,21 +228,21 @@
   }
 
   void _findRegion(int offset, int length) {
-    for (_RecordedNavigationRegion region in regions) {
+    for (final region in regions) {
       if (region.offset == offset && region.length == length) {
         this.region = region;
-        this.targetLocation = region.targetLocation;
+        targetLocation = region.targetLocation;
         return;
       }
     }
-    String regionsString = regions.join('\n');
+    final regionsString = regions.join('\n');
     fail('Unable to find a region at ($offset, $length) in $regionsString');
   }
 
-  void _findRegionString(String str, String suffix, {String codeOverride}) {
-    String code = codeOverride != null ? codeOverride : this.code;
-    String search = str + suffix;
-    int offset = code.indexOf(search);
+  void _findRegionString(String str, String suffix, {final codeOverride}) {
+    final code = codeOverride != null ? codeOverride : this.code;
+    final search = '$str$suffix';
+    final offset = code.indexOf(search);
     expect(offset, isNonNegative, reason: 'Cannot find |$search| in |$code|');
     _findRegion(offset, str.length);
   }
@@ -242,11 +257,13 @@
 
   protocol.Occurrences occurrences;
 
+  @override
   void setUp() {
     super.setUp();
     when(collector.addOccurrences(anyObject)).thenInvoke(occurrencesList.add);
   }
 
+  // ignore: non_constant_identifier_names
   void test_dart_templates() {
     addAngularSources();
     code = r'''
@@ -272,7 +289,7 @@
   T value; // 3
 }
 ''';
-    Source source = newSource('/test.dart', code);
+    final source = newSource('/test.dart', code);
     //LibrarySpecificUnit target = new LibrarySpecificUnit(source, source);
     //computeResult(target, DART_TEMPLATES);
     // compute navigation regions
@@ -310,31 +327,33 @@
   }
 
   void _findOccurrences(int offset) {
-    for (protocol.Occurrences occurrences in occurrencesList) {
+    for (final occurrences in occurrencesList) {
       if (occurrences.offsets.contains(offset)) {
         this.occurrences = occurrences;
         return;
       }
     }
-    String listStr = occurrencesList.join('\n');
+    final listStr = occurrencesList.join('\n');
     fail('Unable to find occurrences at $offset in $listStr');
   }
 }
 
-/**
- * Instances of the class [GatheringErrorListener] implement an error listener
- * that collects all of the errors passed to it for later examination.
- */
+/// Instances of the class [GatheringErrorListener] implement an error listener
+/// that collects all of the errors passed to it for later examination.
 class GatheringErrorListener implements AnalysisErrorListener {
-  /**
-   * A list containing the errors that were collected.
-   */
-  List<AnalysisError> _errors = new List<AnalysisError>();
+  /// A list containing the errors that were collected.
+  final _errors = <AnalysisError>[];
 
   @override
   void onError(AnalysisError error) {
     _errors.add(error);
   }
+
+  void addAll(List<AnalysisError> errors) {
+    for (final error in errors) {
+      onError(error);
+    }
+  }
 }
 
 class NavigationCollectorMock extends TypedMock implements NavigationCollector {
@@ -367,7 +386,7 @@
   GatheringErrorListener errorListener = new GatheringErrorListener();
 
   Source newSource(String path, [String content = '']) {
-    File file = resourceProvider.newFile(path, content);
+    final file = resourceProvider.newFile(path, content);
     return file.createSource();
   }
 
@@ -377,11 +396,11 @@
       ..add(new AngularAnalyzerPlugin()));
     emptySource = newSource('/test.dart');
     // prepare AnalysisContext
-    context = new AnalysisContextImpl();
-    context.sourceFactory = new SourceFactory(<UriResolver>[
-      new DartUriResolver(sdk),
-      new ResourceUriResolver(resourceProvider)
-    ]);
+    context = new AnalysisContextImpl()
+      ..sourceFactory = new SourceFactory(<UriResolver>[
+        new DartUriResolver(sdk),
+        new ResourceUriResolver(resourceProvider)
+      ]);
     // configure AnalysisDriver
     analysisDriver = context.driver;
   }
@@ -394,6 +413,7 @@
 
 export 'src/core/async.dart';
 export 'src/core/metadata.dart';
+export 'src/core/linker/template_ref.dart';
 export 'src/core/ng_if.dart';
 export 'src/core/ng_for.dart';
 ''');
@@ -518,9 +538,11 @@
         r'''
 library angular2.ng_if;
 import 'metadata.dart';
+import 'linker/template_ref.dart';
 
 @Directive(selector: "[ngIf]", inputs: const ["ngIf"])
 class NgIf {
+  NgIf(TemplateRef tpl);
   set ngIf(newCondition) {}
 }
 ''');
@@ -529,13 +551,25 @@
         r'''
 library angular2.ng_for;
 import 'metadata.dart';
+import 'linker/template_ref.dart';
 
 @Directive(
     selector: "[ngFor][ngForOf]",
-    inputs: const ["ngForOf", "ngForTemplate"])
+    inputs: const ["ngForOf", "ngForTemplate", "ngForTrackBy"])
 class NgFor {
+  NgFor(TemplateRef tpl);
   set ngForOf(dynamic value) {}
+  set ngForTrackBy(TrackByFn value) {}
 }
+
+typedef dynamic TrackByFn(num index, dynamic item);
+''');
+    newSource(
+        '/angular2/src/core/linker/template_ref.dart',
+        r'''
+library angular2.template_ref;
+
+class TemplateRef {}
 ''');
   }
 
@@ -556,7 +590,242 @@
       this.offset, this.length, this.targetKind, this.targetLocation);
 
   @override
-  String toString() {
-    return '$offset $length $targetKind $targetLocation';
+  String toString() => '$offset $length $targetKind $targetLocation';
+}
+
+class AbstractAngularTest {
+  MemoryResourceProvider resourceProvider;
+
+  DartSdk sdk;
+  AngularDriver angularDriver;
+  non_task.AnalysisDriver dartDriver;
+
+  GatheringErrorListener errorListener;
+
+  void setUp() {
+    final logger = new PerformanceLog(new StringBuffer());
+    final byteStore = new MemoryByteStore();
+
+    final scheduler = new non_task.AnalysisDriverScheduler(logger)..start();
+    resourceProvider = new MemoryResourceProvider();
+
+    sdk = new MockSdk(resourceProvider: resourceProvider);
+    final packageMap = <String, List<Folder>>{
+      "angular2": [resourceProvider.getFolder("/angular2")]
+    };
+    final packageResolver =
+        new PackageMapUriResolver(resourceProvider, packageMap);
+    final sf = new SourceFactory([
+      new DartUriResolver(sdk),
+      packageResolver,
+      new ResourceUriResolver(resourceProvider),
+    ]);
+    final testPath = resourceProvider.convertPath('/test');
+    final contextRoot = new ContextRoot(testPath, []);
+
+    dartDriver = new non_task.AnalysisDriver(
+      scheduler,
+      logger,
+      resourceProvider,
+      byteStore,
+      new FileContentOverlay(),
+      contextRoot,
+      sf,
+      new AnalysisOptionsImpl(),
+    );
+
+    angularDriver = new AngularDriver(new MockAnalysisServer(), dartDriver,
+        scheduler, byteStore, sf, new FileContentOverlay());
+
+    errorListener = new GatheringErrorListener();
+    addAngularSources();
   }
+
+  Source newSource(String path, [String content = '']) {
+    final file = resourceProvider.newFile(path, content);
+    final source = file.createSource();
+    angularDriver.addFile(path);
+    dartDriver.addFile(path);
+    return source;
+  }
+
+  void fillErrorListener(List<AnalysisError> errors) {
+    errorListener.addAll(errors);
+  }
+
+  void addAngularSources() {
+    newSource(
+        '/angular2/angular2.dart',
+        r'''
+library angular2;
+
+export 'src/core/async.dart';
+export 'src/core/metadata.dart';
+export 'src/core/linker/template_ref.dart';
+export 'src/core/ng_if.dart';
+export 'src/core/ng_for.dart';
+''');
+    newSource(
+        '/angular2/src/core/metadata.dart',
+        r'''
+library angular2.src.core.metadata;
+
+import 'dart:async';
+
+abstract class Directive {
+  const Directive(
+      {String selector,
+      List<String> inputs,
+      List<String> outputs,
+      @Deprecated('Use `inputs` or `@Input` instead') List<String> properties,
+      @Deprecated('Use `outputs` or `@Output` instead') List<String> events,
+      Map<String, String> host,
+      @Deprecated('Use `providers` instead') List bindings,
+      List providers,
+      String exportAs,
+      String moduleId,
+      Map<String, dynamic> queries})
+      : super(
+            selector: selector,
+            inputs: inputs,
+            outputs: outputs,
+            properties: properties,
+            events: events,
+            host: host,
+            bindings: bindings,
+            providers: providers,
+            exportAs: exportAs,
+            moduleId: moduleId,
+            queries: queries);
+}
+
+class Component extends Directive {
+  const Component(
+      {String selector,
+      List<String> inputs,
+      List<String> outputs,
+      @Deprecated('Use `inputs` or `@Input` instead') List<String> properties,
+      @Deprecated('Use `outputs` or `@Output` instead') List<String> events,
+      Map<String, String> host,
+      @Deprecated('Use `providers` instead') List bindings,
+      List providers,
+      String exportAs,
+      String moduleId,
+      Map<String, dynamic> queries,
+      @Deprecated('Use `viewProviders` instead') List viewBindings,
+      List viewProviders,
+      ChangeDetectionStrategy changeDetection,
+      String templateUrl,
+      String template,
+      dynamic directives,
+      dynamic pipes,
+      ViewEncapsulation encapsulation,
+      List<String> styles,
+      List<String> styleUrls});
+}
+
+class View {
+  const View(
+      {String templateUrl,
+      String template,
+      dynamic directives,
+      dynamic pipes,
+      ViewEncapsulation encapsulation,
+      List<String> styles,
+      List<String> styleUrls});
+}
+
+class Input {
+  final String bindingPropertyName;
+  const InputMetadata([this.bindingPropertyName]);
+}
+
+class Output {
+  final String bindingPropertyName;
+  const OutputMetadata([this.bindingPropertyName]);
+}
+''');
+    newSource(
+        '/angular2/src/core/async.dart',
+        r'''
+library angular2.core.facade.async;
+import 'dart:async';
+
+class EventEmitter<T> extends Stream<T> {
+  StreamController<dynamic> _controller;
+
+  /**
+   * Creates an instance of [EventEmitter], which depending on [isAsync],
+   * delivers events synchronously or asynchronously.
+   */
+  EventEmitter([bool isAsync = true]) {
+    _controller = new StreamController.broadcast(sync: !isAsync);
+  }
+
+  StreamSubscription listen(void onData(dynamic line),
+      {void onError(Error error), void onDone(), bool cancelOnError}) {
+    return _controller.stream.listen(onData,
+        onError: onError, onDone: onDone, cancelOnError: cancelOnError);
+  }
+
+  void add(value) {
+    _controller.add(value);
+  }
+
+  void addError(error) {
+    _controller.addError(error);
+  }
+
+  void close() {
+    _controller.close();
+  }
+}
+''');
+    newSource(
+        '/angular2/src/core/ng_if.dart',
+        r'''
+library angular2.ng_if;
+import 'metadata.dart';
+import 'linker/template_ref.dart';
+
+@Directive(selector: "[ngIf]", inputs: const ["ngIf"])
+class NgIf {
+  NgIf(TemplateRef tpl);
+  set ngIf(newCondition) {}
+}
+''');
+    newSource(
+        '/angular2/src/core/ng_for.dart',
+        r'''
+library angular2.ng_for;
+import 'metadata.dart';
+import 'linker/template_ref.dart';
+
+@Directive(
+    selector: "[ngFor][ngForOf]",
+    inputs: const ["ngForOf", "ngForTemplate", "ngForTrackBy"])
+class NgFor {
+  NgFor(TemplateRef tpl);
+  set ngForOf(dynamic value) {}
+  set ngForTrackBy(TrackByFn value) {}
+}
+
+typedef dynamic TrackByFn(num index, dynamic item);
+''');
+    newSource(
+        '/angular2/src/core/linker/template_ref.dart',
+        r'''
+library angular2.template_ref;
+
+class TemplateRef {}
+''');
+  }
+}
+
+class MockAnalysisServer extends TypedMock implements AnalysisServer {
+  @override
+  final notificationManager = new MockNotificationManager();
+}
+
+class MockNotificationManager extends TypedMock implements NotificationManager {
 }
diff --git a/server_plugin/test/completion_contributor_test.dart b/server_plugin/test/completion_contributor_test.dart
index a2718a1..ef0772d 100644
--- a/server_plugin/test/completion_contributor_test.dart
+++ b/server_plugin/test/completion_contributor_test.dart
@@ -1,3 +1,5 @@
+import 'dart:async';
+
 import 'package:analysis_server/src/provisional/completion/completion_core.dart';
 import 'package:analysis_server/src/provisional/completion/dart/completion_dart.dart';
 import 'package:angular_analyzer_server_plugin/src/completion.dart';
@@ -6,28 +8,28 @@
 
 import 'completion_contributor_test_util.dart';
 
-main() {
+void main() {
   // TODO: get these working again on the latest SDK
   //defineReflectiveTests(DartCompletionContributorTest);
-  //defineReflectiveTests(HtmlCompletionContributorTest);
+  defineReflectiveTests(HtmlCompletionContributorTest);
 }
 
 @reflectiveTest
 class DartCompletionContributorTest extends AbstractCompletionContributorTest {
   @override
-  setUp() {
+  void setUp() {
     testFile = '/completionTest.dart';
     super.setUp();
   }
 
   @override
-  CompletionContributor createContributor() {
-    return new AngularDartCompletionContributor();
-  }
+  CompletionContributor createContributor() =>
+      new AngularCompletionContributor(angularDriver);
 
-  test_completeMemberInMustache() async {
+  // ignore: non_constant_identifier_names
+  Future test_completeMemberInMustache() async {
     addTestSource('''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(template: '{{^}}', selector: 'a')
 class MyComp {
   String text;
@@ -40,9 +42,10 @@
     assertSuggestGetter('text', 'String');
   }
 
-  test_completeMemberInInputBinding() async {
+  // ignore: non_constant_identifier_names
+  Future test_completeMemberInInputBinding() async {
     addTestSource('''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(template: '<h1 [hidden]="^"></h1>', selector: 'a')
 class MyComp {
   String text;
@@ -55,9 +58,10 @@
     assertSuggestGetter('text', 'String');
   }
 
-  test_completeMemberInClassBinding() async {
+  // ignore: non_constant_identifier_names
+  Future test_completeMemberInClassBinding() async {
     addTestSource('''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(template: '<h1 [class.my-class]="^"></h1>', selector: 'a')
 class MyComp {
   String text;
@@ -70,9 +74,10 @@
     assertSuggestGetter('text', 'String');
   }
 
-  test_completeMemberInInputOutput_at_incompleteTag_with_newTag() async {
+  // ignore: non_constant_identifier_names
+  Future test_completeMemberInInputOutput_at_incompleteTag_with_newTag() async {
     addTestSource('''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(template: '<child-tag ^<div></div>', selector: 'my-tag',
 directives: const [MyChildComponent])
 class MyComponent {}
@@ -89,9 +94,10 @@
     assertSuggestGetter("(myEvent)", "String");
   }
 
-  test_completeInputStarted_at_incompleteTag_with_newTag() async {
+  // ignore: non_constant_identifier_names
+  Future test_completeInputStarted_at_incompleteTag_with_newTag() async {
     addTestSource('''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(template: '<child-tag [^<div></div>', selector: 'my-tag',
 directives: const [MyChildComponent])
 class MyComponent {}
@@ -108,9 +114,76 @@
     assertNotSuggested("(myEvent)");
   }
 
-  test_completeOutputStarted_at_incompleteTag_with_newTag() async {
+  // ignore: non_constant_identifier_names
+  Future test_completeInputNotStarted_at_incompleteTag_with_newTag() async {
     addTestSource('''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
+@Component(template: '<child-tag ^<div></div>', selector: 'my-tag',
+directives: const [MyChildComponent])
+class MyComponent {}
+@Component(template: '', selector: 'child-tag')
+class MyChildComponent {
+  @Input() String stringInput;
+  @Output() EventEmitter<String> myEvent; 
+}
+    ''');
+    await computeSuggestions();
+    expect(replacementOffset, completionOffset);
+    expect(replacementLength, 0);
+    assertSuggestSetter('[stringInput]');
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_completeInput_as_plainAttribute() async {
+    addTestSource('''
+import 'package:angular2/angular2.dart';
+@Component(template: '<child-tag ^<div></div>', selector: 'my-tag',
+directives: const [MyChildComponent])
+class MyComponent {}
+@Component(template: '', selector: 'child-tag', 
+    inputs: const ['myDynamicInput'])
+class MyChildComponent {
+  @Input() String stringInput;
+  @Input() String intInput;
+  @Output() EventEmitter<String> myEvent;
+  
+  bool _myDynamicInput = false;
+  bool get myDynamicInput => _myDynamicInput;
+  void set myDynamicInput(value) {}
+}
+    ''');
+    await computeSuggestions();
+    expect(replacementOffset, completionOffset);
+    expect(replacementLength, 0);
+    assertSuggestSetter('stringInput');
+    assertNotSuggested('intInput');
+    assertSuggestSetter('myDynamicInput',
+        relevance: DART_RELEVANCE_DEFAULT - 1);
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_completeStandardInput_as_plainAttribute() async {
+    addTestSource('''
+import 'package:angular2/angular2.dart';
+@Component(template: '<child-tag ^<div></div>', selector: 'my-tag',
+directives: const [MyChildComponent])
+class MyComponent {}
+@Component(template: '', selector: 'child-tag')
+class MyChildComponent {
+}
+  }
+  ''');
+    await computeSuggestions();
+    expect(replacementOffset, completionOffset);
+    expect(replacementLength, 0);
+    assertSuggestSetter('[id]', relevance: DART_RELEVANCE_DEFAULT - 2);
+    assertSuggestSetter('id', relevance: DART_RELEVANCE_DEFAULT - 2);
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_completeOutputStarted_at_incompleteTag_with_newTag() async {
+    addTestSource('''
+import 'package:angular2/angular2.dart';
 @Component(template: '<child-tag (^<div></div>', selector: 'my-tag',
 directives: const [MyChildComponent])
 class MyComponent {}
@@ -127,9 +200,10 @@
     assertSuggestGetter("(myEvent)", "String");
   }
 
-  test_completeMemberInInputOutput_at_incompleteTag_with_EOF() async {
+  // ignore: non_constant_identifier_names
+  Future test_completeMemberInInputOutput_at_incompleteTag_with_EOF() async {
     addTestSource('''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(template: '<child-tag ^', selector: 'my-tag',
 directives: const [MyChildComponent])
 class MyComponent {}
@@ -146,9 +220,10 @@
     assertSuggestGetter("(myEvent)", "String");
   }
 
-  test_completeInputStarted_at_incompleteTag_with_EOF() async {
+  // ignore: non_constant_identifier_names
+  Future test_completeInputStarted_at_incompleteTag_with_EOF() async {
     addTestSource('''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(template: '<child-tag [^', selector: 'my-tag',
 directives: const [MyChildComponent])
 class MyComponent {}
@@ -165,9 +240,10 @@
     assertNotSuggested("(myEvent)");
   }
 
-  test_completeOutputStarted_at_incompleteTag_with_EOF() async {
+  // ignore: non_constant_identifier_names
+  Future test_completeOutputStarted_at_incompleteTag_with_EOF() async {
     addTestSource('''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(template: '<child-tag (^', selector: 'my-tag',
 directives: const [MyChildComponent])
 class MyComponent {}
@@ -184,9 +260,10 @@
     assertSuggestGetter("(myEvent)", "String");
   }
 
-  test_completeMemberInStyleBinding() async {
+  // ignore: non_constant_identifier_names
+  Future test_completeMemberInStyleBinding() async {
     addTestSource('''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(template: '<h1 [style.background-color]="^"></h1>', selector: 'a')
 class MyComp {
   String text;
@@ -199,9 +276,10 @@
     assertSuggestGetter('text', 'String');
   }
 
-  test_completeMemberInAttrBinding() async {
+  // ignore: non_constant_identifier_names
+  Future test_completeMemberInAttrBinding() async {
     addTestSource('''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(template: '<h1 [attr.on-click]="^"></h1>', selector: 'a')
 class MyComp {
   String text;
@@ -214,9 +292,10 @@
     assertSuggestGetter('text', 'String');
   }
 
-  test_completeMemberMustacheAttrBinding() async {
+  // ignore: non_constant_identifier_names
+  Future test_completeMemberMustacheAttrBinding() async {
     addTestSource('''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(template: '<h1 title="{{^}}"></h1>', selector: 'a')
 class MyComp {
   String text;
@@ -229,9 +308,10 @@
     assertSuggestGetter('text', 'String');
   }
 
-  test_completeMultipleMembers() async {
+  // ignore: non_constant_identifier_names
+  Future test_completeMultipleMembers() async {
     addTestSource('''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(template: '{{d^}}', selector: 'a')
 class MyComp {
   String text;
@@ -246,9 +326,10 @@
     assertSuggestGetter('description', 'String');
   }
 
-  test_completeInlineHtmlSelectorTag_at_beginning() async {
+  // ignore: non_constant_identifier_names
+  Future test_completeInlineHtmlSelectorTag_at_beginning() async {
     addTestSource('''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(template: '<^<div></div>', selector: 'my-parent', directives: const[MyChildComponent1, MyChildComponent2])
 class MyParentComponent{}
 @Component(template: '', selector: 'my-child1, my-child2')
@@ -265,9 +346,10 @@
     assertSuggestClassTypeAlias("<my-child3");
   }
 
-  test_completeInlineHtmlSelectorTag_at_beginning_with_partial() async {
+  // ignore: non_constant_identifier_names
+  Future test_completeInlineHtmlSelectorTag_at_beginning_with_partial() async {
     addTestSource('''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(template: '<my^<div></div>', selector: 'my-parent', directives: const[MyChildComponent1, MyChildComponent2])
 class MyParentComponent{}
 @Component(template: '', selector: 'my-child1, my-child2')
@@ -284,9 +366,10 @@
     assertSuggestClassTypeAlias("<my-child3");
   }
 
-  test_completeInlineHtmlSelectorTag_at_middle() async {
+  // ignore: non_constant_identifier_names
+  Future test_completeInlineHtmlSelectorTag_at_middle() async {
     addTestSource('''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(template: '<div><div><^</div></div>', selector: 'my-parent', directives: const[MyChildComponent1,MyChildComponent2])
 class MyParentComponent{}
 @Component(template: '', selector: 'my-child1, my-child2')
@@ -303,9 +386,10 @@
     assertSuggestClassTypeAlias("<my-child3");
   }
 
-  test_completeInlineHtmlSelectorTag_at_middle_of_text() async {
+  // ignore: non_constant_identifier_names
+  Future test_completeInlineHtmlSelectorTag_at_middle_of_text() async {
     addTestSource('''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(template: '<div><div> some text<^</div></div>', selector: 'my-parent', directives: const[MyChildComponent1,MyChildComponent2])
 class MyParentComponent{}
 @Component(template: '', selector: 'my-child1, my-child2')
@@ -322,9 +406,10 @@
     assertSuggestClassTypeAlias("<my-child3");
   }
 
-  test_completeInlineHtmlSelectorTag_at_middle_with_partial() async {
+  // ignore: non_constant_identifier_names
+  Future test_completeInlineHtmlSelectorTag_at_middle_with_partial() async {
     addTestSource('''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(template: '<div><div><my^</div></div>', selector: 'my-parent', directives: const[MyChildComponent1, MyChildComponent2])
 class MyParentComponent{}
 @Component(template: '', selector: 'my-child1, my-child2')
@@ -341,9 +426,10 @@
     assertSuggestClassTypeAlias("<my-child3");
   }
 
-  test_completeInlineHtmlSelectorTag_at_end() async {
+  // ignore: non_constant_identifier_names
+  Future test_completeInlineHtmlSelectorTag_at_end() async {
     addTestSource('''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(template: '<div><div></div></div><^', selector: 'my-parent', directives: const[MyChildComponent1,MyChildComponent2])
 class MyParentComponent{}
 @Component(template: '', selector: 'my-child1, my-child2')
@@ -360,9 +446,10 @@
     assertSuggestClassTypeAlias("<my-child3");
   }
 
-  test_completeInlineHtmlSelectorTag_at_end_with_partial() async {
+  // ignore: non_constant_identifier_names
+  Future test_completeInlineHtmlSelectorTag_at_end_with_partial() async {
     addTestSource('''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(template: '<div><div></div></div><m^', selector: 'my-parent', directives: const[MyChildComponent1,MyChildComponent2])
 class MyParentComponent{}
 @Component(template: '', selector: 'my-child1, my-child2')
@@ -379,9 +466,10 @@
     assertSuggestClassTypeAlias("<my-child3");
   }
 
-  test_completeInlineHtmlSelectorTag_on_empty_document() async {
+  // ignore: non_constant_identifier_names
+  Future test_completeInlineHtmlSelectorTag_on_empty_document() async {
     addTestSource('''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(template: '^', selector: 'my-parent', directives: const[MyChildComponent1,MyChildComponent2])
 class MyParentComponent{}
 @Component(template: '', selector: 'my-child1, my-child2')
@@ -398,9 +486,10 @@
     assertSuggestClassTypeAlias("<my-child3");
   }
 
-  test_completeInlineHtmlSelectorTag_at_end_after_close() async {
+  // ignore: non_constant_identifier_names
+  Future test_completeInlineHtmlSelectorTag_at_end_after_close() async {
     addTestSource('''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(template: '<div><div></div></div>^', selector: 'my-parent', directives: const[MyChildComponent1,MyChildComponent2])
 class MyParentComponent{}
 @Component(template: '', selector: 'my-child1, my-child2')
@@ -417,9 +506,10 @@
     assertSuggestClassTypeAlias("<my-child3");
   }
 
-  test_completeInlineHtmlSelectorTag_in_middle_of_unclosed_tag() async {
+  // ignore: non_constant_identifier_names
+  Future test_completeInlineHtmlSelectorTag_in_middle_of_unclosed_tag() async {
     addTestSource('''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(template: '<div>some text<^', selector: 'my-parent', directives: const[MyChildComponent1,MyChildComponent2])
 class MyParentComponent{}
 @Component(template: '', selector: 'my-child1, my-child2')
@@ -440,32 +530,31 @@
 @reflectiveTest
 class HtmlCompletionContributorTest extends AbstractCompletionContributorTest {
   @override
-  setUp() {
+  void setUp() {
     testFile = '/completionTest.html';
     super.setUp();
+    createContributor();
   }
 
   @override
-  CompletionContributor createContributor() {
-    return new AngularTemplateCompletionContributor();
-  }
+  CompletionContributor createContributor() =>
+      new AngularCompletionContributor(angularDriver);
 
-  test_completeMemberInMustache() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeMemberInMustache() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a')
 class MyComp {
   String text;
 }
     ''');
 
-    addTestSource('html file {{ ^ }} with mustache');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
+    addTestSource('html file {{^}} with mustache');
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset);
     expect(replacementLength, 0);
@@ -474,11 +563,12 @@
     assertSuggestGetter('hashCode', 'int');
   }
 
-  test_completeDotMemberInMustache() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeDotMemberInMustache() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a')
 class MyComp {
   String text;
@@ -486,21 +576,20 @@
     ''');
 
     addTestSource('html file {{text.^}} with mustache');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset);
     expect(replacementLength, 0);
     assertSuggestGetter('length', 'int');
   }
 
-  test_completeDotMemberAlreadyStartedInMustache() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeDotMemberAlreadyStartedInMustache() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a')
 class MyComp {
   String text;
@@ -508,21 +597,20 @@
     ''');
 
     addTestSource('html file {{text.le^}} with mustache');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset - 'le'.length);
     expect(replacementLength, 'le'.length);
     assertSuggestGetter('length', 'int');
   }
 
-  test_completeDotMemberInNgFor() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeDotMemberInNgFor() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a', directives: const [NgFor])
 class MyComp {
   String text;
@@ -530,21 +618,20 @@
     ''');
 
     addTestSource('<div *ngFor="let item of text.^"></div>');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset);
     expect(replacementLength, 0);
     assertSuggestGetter('length', 'int');
   }
 
-  test_completeMemberInNgFor() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeMemberInNgFor() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a', directives: const [NgFor])
 class MyComp {
   String text;
@@ -552,10 +639,8 @@
     ''');
 
     addTestSource('<div *ngFor="let item of ^"></div>');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset);
     expect(replacementLength, 0);
@@ -564,11 +649,12 @@
     assertSuggestGetter('hashCode', 'int');
   }
 
-  test_noCompleteMemberInNgForRightAfterLet() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_noCompleteMemberInNgForRightAfterLet() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a', directives: const [NgFor])
 class MyComp {
   String text;
@@ -576,21 +662,20 @@
     ''');
 
     addTestSource('<div *ngFor="let^ item of [text]"></div>');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
-    expect(replacementOffset, completionOffset);
-    expect(replacementLength, 0);
+    expect(replacementOffset, completionOffset - 'let'.length);
+    expect(replacementLength, 'let item'.length);
     assertNotSuggested('text');
   }
 
-  test_noCompleteMemberInNgForInLet() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_noCompleteMemberInNgForInLet() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a', directives: const [NgFor])
 class MyComp {
   String text;
@@ -598,21 +683,20 @@
     ''');
 
     addTestSource('<div *ngFor="l^et item of [text]"></div>');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
-    expect(replacementOffset, completionOffset);
-    expect(replacementLength, 0);
+    expect(replacementOffset, completionOffset - 1);
+    expect(replacementLength, 'let item'.length);
     assertNotSuggested('text');
   }
 
-  test_noCompleteMemberInNgForAfterLettedName() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_noCompleteMemberInNgForAfterLettedName() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a', directives: const [NgFor])
 class MyComp {
   String text;
@@ -620,21 +704,20 @@
     ''');
 
     addTestSource('<div *ngFor="let item^ of [text]"></div>');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
-    expect(replacementOffset, completionOffset);
-    expect(replacementLength, 0);
+    expect(replacementOffset, completionOffset - 'let item'.length);
+    expect(replacementLength, 'let item'.length);
     assertNotSuggested('text');
   }
 
-  test_noCompleteMemberInNgForInLettedName() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_noCompleteMemberInNgForInLettedName() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a', directives: const [NgFor])
 class MyComp {
   String text;
@@ -642,21 +725,20 @@
     ''');
 
     addTestSource('<div *ngFor="let i^tem of [text]"></div>');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
-    expect(replacementOffset, completionOffset);
-    expect(replacementLength, 0);
+    expect(replacementOffset, completionOffset - 'let i'.length);
+    expect(replacementLength, 'let item'.length);
     assertNotSuggested('text');
   }
 
-  test_noCompleteMemberInNgFor_forLettedName() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_noCompleteMemberInNgFor_forLettedName() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a', directives: const [NgFor])
 class MyComp {
   String text;
@@ -664,21 +746,20 @@
     ''');
 
     addTestSource('<div *ngFor="let ^"></div>');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset);
     expect(replacementLength, 0);
     assertNotSuggested('text');
   }
 
-  test_completeNgForItem() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeNgForItem() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a', directives: const [NgFor])
 class MyComp {
   List<String> items;
@@ -686,42 +767,40 @@
     ''');
 
     addTestSource('<div *ngFor="let item of items">{{^}}</div>');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset);
     expect(replacementLength, 0);
     assertSuggestLocalVar('item', 'String');
   }
 
-  test_completeHashVar() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeHashVar() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a')
 class MyComp {
 }
     ''');
 
     addTestSource('<button #buttonEl>button</button> {{^}}');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset);
     expect(replacementLength, 0);
     assertSuggestLocalVar('buttonEl', 'ButtonElement');
   }
 
-  test_completeNgVars_notAfterDot() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeNgVars_notAfterDot() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a')
 class MyComp {
   List<String> items;
@@ -730,10 +809,8 @@
 
     addTestSource(
         '<button #buttonEl>button</button><div *ngFor="item of items">{{hashCode.^}}</div>');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset);
     expect(replacementLength, 0);
@@ -741,11 +818,12 @@
     assertNotSuggested('item');
   }
 
-  test_findCompletionTarget_afterUnclosedDom() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_findCompletionTarget_afterUnclosedDom() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a')
 class MyComp {
   String text;
@@ -753,21 +831,20 @@
     ''');
 
     addTestSource('<input /> {{^}}');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset);
     expect(replacementLength, 0);
     assertSuggestGetter('text', 'String');
   }
 
-  test_completeStatements() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeStatements() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a')
 class MyComp {
   String text;
@@ -775,10 +852,8 @@
     ''');
 
     addTestSource('<button (click)="^"></button>');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset);
     expect(replacementLength, 0);
@@ -786,33 +861,33 @@
     assertSuggestField('text', 'String');
   }
 
-  test_completeUnclosedMustache() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeUnclosedMustache() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a')
 class MyComp {
   String text;
 }
     ''');
 
-    addTestSource('some text and {{^');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
+    addTestSource('some text and {{^   <div>some html</div>');
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset);
     expect(replacementLength, 0);
     assertSuggestGetter('text', 'String');
   }
 
-  test_completeEmptyExpressionDoesntIncludeVoid() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeEmptyExpressionDoesntIncludeVoid() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a')
 class MyComp {
   void dontCompleteMe() {}
@@ -820,21 +895,20 @@
     ''');
 
     addTestSource('{{^}}');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset);
     expect(replacementLength, 0);
     assertNotSuggested("dontCompleteMe");
   }
 
-  test_completeInMiddleOfExpressionDoesntIncludeVoid() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeInMiddleOfExpressionDoesntIncludeVoid() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a')
 class MyComp {
   bool takesArg(dynamic arg) {};
@@ -843,21 +917,20 @@
     ''');
 
     addTestSource('{{takesArg(^)}}');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset);
     expect(replacementLength, 0);
     assertNotSuggested("dontCompleteMe");
   }
 
-  test_completeInputOutput() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeInputOutput() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a',
     directives: const [OtherComp])
 class MyComp {
@@ -870,25 +943,24 @@
     ''');
 
     addTestSource('<my-tag ^></my-tag>');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset);
     expect(replacementLength, 0);
     assertSuggestSetter("[name]");
-    assertSuggestSetter("[hidden]", relevance: DART_RELEVANCE_DEFAULT - 1);
+    assertSuggestSetter("[hidden]", relevance: DART_RELEVANCE_DEFAULT - 2);
     assertSuggestGetter("(nameEvent)", "String");
     assertSuggestGetter("(click)", "MouseEvent",
         relevance: DART_RELEVANCE_DEFAULT - 1);
   }
 
-  test_completeInputOutput_at_incompleteTag_with_newTag() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeInputOutput_at_incompleteTag_with_newTag() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a',
     directives: const [OtherComp])
 class MyComp {
@@ -901,25 +973,24 @@
     ''');
 
     addTestSource('<my-tag ^<div></div>');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset);
     expect(replacementLength, 0);
     assertSuggestSetter("[name]");
-    assertSuggestSetter("[hidden]", relevance: DART_RELEVANCE_DEFAULT - 1);
+    assertSuggestSetter("[hidden]", relevance: DART_RELEVANCE_DEFAULT - 2);
     assertSuggestGetter("(nameEvent)", "String");
     assertSuggestGetter("(click)", "MouseEvent",
         relevance: DART_RELEVANCE_DEFAULT - 1);
   }
 
-  test_completeInputStarted_at_incompleteTag_with_newTag() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeInputStarted_at_incompleteTag_with_newTag() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a',
     directives: const [OtherComp])
 class MyComp {
@@ -932,24 +1003,23 @@
     ''');
 
     addTestSource('<my-tag [^<div></div>');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset - 1);
     expect(replacementLength, 1);
     assertSuggestSetter("[name]");
-    assertSuggestSetter("[hidden]", relevance: DART_RELEVANCE_DEFAULT - 1);
+    assertSuggestSetter("[hidden]", relevance: DART_RELEVANCE_DEFAULT - 2);
     assertNotSuggested("(nameEvent)");
     assertNotSuggested("(click)");
   }
 
-  test_completeOutputStarted_at_incompleteTag_with_newTag() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeOutputStarted_at_incompleteTag_with_newTag() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a',
     directives: const [OtherComp])
 class MyComp {
@@ -962,10 +1032,8 @@
     ''');
 
     addTestSource('<my-tag (^<div></div>');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset - 1);
     expect(replacementLength, 1);
@@ -976,11 +1044,12 @@
         relevance: DART_RELEVANCE_DEFAULT - 1);
   }
 
-  test_completeInputOutput_at_incompleteTag_with_EOF() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeInputOutput_at_incompleteTag_with_EOF() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a',
     directives: const [OtherComp])
 class MyComp {
@@ -993,25 +1062,24 @@
     ''');
 
     addTestSource('<my-tag ^');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset);
     expect(replacementLength, 0);
     assertSuggestSetter("[name]");
-    assertSuggestSetter("[hidden]", relevance: DART_RELEVANCE_DEFAULT - 1);
+    assertSuggestSetter("[hidden]", relevance: DART_RELEVANCE_DEFAULT - 2);
     assertSuggestGetter("(nameEvent)", "String");
     assertSuggestGetter("(click)", "MouseEvent",
         relevance: DART_RELEVANCE_DEFAULT - 1);
   }
 
-  test_completeInputStarted_at_incompleteTag_with_EOF() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeInputStarted_at_incompleteTag_with_EOF() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a',
     directives: const [OtherComp])
 class MyComp {
@@ -1024,24 +1092,23 @@
     ''');
 
     addTestSource('<my-tag [^');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset - 1);
     expect(replacementLength, 1);
     assertSuggestSetter("[name]");
-    assertSuggestSetter("[hidden]", relevance: DART_RELEVANCE_DEFAULT - 1);
+    assertSuggestSetter("[hidden]", relevance: DART_RELEVANCE_DEFAULT - 2);
     assertNotSuggested("(nameEvent)");
     assertNotSuggested("(click)");
   }
 
-  test_completeOutputStarted_at_incompleteTag_with_EOF() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeOutputStarted_at_incompleteTag_with_EOF() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a',
     directives: const [OtherComp])
 class MyComp {
@@ -1054,10 +1121,8 @@
     ''');
 
     addTestSource('<my-tag (^');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset - 1);
     expect(replacementLength, 1);
@@ -1068,11 +1133,12 @@
         relevance: DART_RELEVANCE_DEFAULT - 1);
   }
 
-  test_completeInputNotSuggestedTwice() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeInputNotSuggestedTwice() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a',
     directives: const [OtherComp])
 class MyComp {
@@ -1085,10 +1151,8 @@
     ''');
 
     addTestSource('<my-tag [name]="\'bob\'" ^></my-tag>');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset);
     expect(replacementLength, 0);
@@ -1098,11 +1162,12 @@
         relevance: DART_RELEVANCE_DEFAULT - 1);
   }
 
-  test_completeStandardInputNotSuggestedTwice() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeStandardInputNotSuggestedTwice() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a',
     directives: const [OtherComp])
 class MyComp {
@@ -1115,10 +1180,8 @@
     ''');
 
     addTestSource('<my-tag [hidden]="true" ^></my-tag>');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset);
     expect(replacementLength, 0);
@@ -1129,11 +1192,12 @@
         relevance: DART_RELEVANCE_DEFAULT - 1);
   }
 
-  test_completeInputSuggestsItself() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeInputSuggestsItself() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a',
     directives: const [OtherComp])
 class MyComp {
@@ -1146,21 +1210,20 @@
     ''');
 
     addTestSource('<my-tag [name^></my-tag>');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset - '[name'.length);
     expect(replacementLength, '[name'.length);
     assertSuggestSetter("[name]");
   }
 
-  test_completeStandardInputSuggestsItself() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeStandardInputSuggestsItself() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a',
     directives: const [OtherComp])
 class MyComp {
@@ -1173,21 +1236,20 @@
     ''');
 
     addTestSource('<my-tag [hidden^></my-tag>');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset - '[hidden'.length);
     expect(replacementLength, '[hidden'.length);
-    assertSuggestSetter("[hidden]", relevance: DART_RELEVANCE_DEFAULT - 1);
+    assertSuggestSetter("[hidden]", relevance: DART_RELEVANCE_DEFAULT - 2);
   }
 
-  test_completeOutputNotSuggestedTwice() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeOutputNotSuggestedTwice() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a',
     directives: const [OtherComp])
 class MyComp {
@@ -1200,25 +1262,24 @@
     ''');
 
     addTestSource('<my-tag (nameEvent)="" ^></my-tag>');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset);
     expect(replacementLength, 0);
     assertSuggestSetter("[name]");
-    assertSuggestSetter("[hidden]", relevance: DART_RELEVANCE_DEFAULT - 1);
+    assertSuggestSetter("[hidden]", relevance: DART_RELEVANCE_DEFAULT - 2);
     assertNotSuggested("(nameEvent)");
     assertSuggestGetter("(click)", "MouseEvent",
         relevance: DART_RELEVANCE_DEFAULT - 1);
   }
 
-  test_completeOutputSuggestsItself() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeOutputSuggestsItself() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a',
     directives: const [OtherComp])
 class MyComp {
@@ -1231,21 +1292,20 @@
     ''');
 
     addTestSource('<my-tag (nameEvent^></my-tag>');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset - '(nameEvent'.length);
     expect(replacementLength, '(nameEvent'.length);
     assertSuggestGetter("(nameEvent)", "String");
   }
 
-  test_completeStdOutputNotSuggestedTwice() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeStdOutputNotSuggestedTwice() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a',
     directives: const [OtherComp])
 class MyComp {
@@ -1258,24 +1318,23 @@
     ''');
 
     addTestSource('<my-tag (click)="" ^></my-tag>');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset);
     expect(replacementLength, 0);
     assertSuggestSetter("[name]");
-    assertSuggestSetter("[hidden]", relevance: DART_RELEVANCE_DEFAULT - 1);
+    assertSuggestSetter("[hidden]", relevance: DART_RELEVANCE_DEFAULT - 2);
     assertSuggestGetter("(nameEvent)", "String");
     assertNotSuggested("(click)");
   }
 
-  test_completeStdOutputSuggestsItself() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeStdOutputSuggestsItself() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a',
     directives: const [OtherComp])
 class MyComp {
@@ -1288,10 +1347,8 @@
     ''');
 
     addTestSource('<my-tag (click^></my-tag>');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset - '(click'.length);
     expect(replacementLength, '(click'.length);
@@ -1299,11 +1356,12 @@
         relevance: DART_RELEVANCE_DEFAULT - 1);
   }
 
-  test_completeInputOutputNotSuggestedAfterTwoWay() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeInputOutputNotSuggestedAfterTwoWay() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a',
     directives: const [OtherComp])
 class MyComp {
@@ -1317,10 +1375,8 @@
     ''');
 
     addTestSource('<my-tag [(name)]="name" ^></my-tag>');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset);
     expect(replacementLength, 0);
@@ -1328,11 +1384,12 @@
     assertNotSuggested("(nameEvent)");
   }
 
-  test_completeInputStarted() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeInputStarted() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a',
     directives: const [OtherComp])
 class MyComp {
@@ -1345,24 +1402,117 @@
     ''');
 
     addTestSource('<my-tag [^></my-tag>');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset - 1);
     expect(replacementLength, 1);
     assertSuggestSetter("[name]");
-    assertSuggestSetter("[hidden]", relevance: DART_RELEVANCE_DEFAULT - 1);
+    assertSuggestSetter("[hidden]", relevance: DART_RELEVANCE_DEFAULT - 2);
     assertNotSuggested("(nameEvent)");
     assertNotSuggested("(click)");
   }
 
-  test_completeOutputStarted() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeInputNotStarted() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
+@Component(templateUrl: 'completionTest.html', selector: 'a',
+    directives: const [OtherComp])
+class MyComp {
+}
+@Component(template: '', selector: 'my-tag')
+class OtherComp {
+  @Input() String name;
+  @Output() EventEmitter<String> nameEvent;
+}
+    ''');
+    addTestSource('<my-tag ^></my-tag>');
+
+    await resolveSingleTemplate(dartSource);
+    await computeSuggestions();
+    expect(replacementOffset, completionOffset);
+    expect(replacementLength, 0);
+    assertSuggestSetter('[name]');
+    assertSuggestSetter('[hidden]', relevance: DART_RELEVANCE_DEFAULT - 2);
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_completeInputAsPlainAttribute() async {
+    final dartSource = newSource(
+        '/completionTest.dart',
+        '''
+import 'package:angular2/angular2.dart';
+@Component(templateUrl: 'completionTest.html', selector: 'a',
+    directives: const [OtherComp])
+class MyComp {
+}
+@Component(template: '', selector: 'my-tag', inputs: const ['myDynamicInput'])
+class OtherComp {
+  @Input() String name;
+  @Input() int intInput;
+  
+  bool _myDynamicInput = false;
+  bool get myDynamicInput => _myDynamicInput;
+  void set myDynamicInput(value) {}
+}
+    ''');
+    addTestSource('<my-tag ^></my-tag>');
+
+    await resolveSingleTemplate(dartSource);
+    await computeSuggestions();
+    expect(replacementOffset, completionOffset);
+    expect(replacementLength, 0);
+    assertSuggestSetter('name');
+    assertNotSuggested('intInput');
+    assertSuggestSetter('id', relevance: DART_RELEVANCE_DEFAULT - 2);
+    assertSuggestSetter('[myDynamicInput]');
+    assertSuggestSetter('myDynamicInput',
+        relevance: DART_RELEVANCE_DEFAULT - 1);
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_completeInputAsPlainAttributeStarted() async {
+    final dartSource = newSource(
+        '/completionTest.dart',
+        '''
+import 'package:angular2/angular2.dart';
+@Component(templateUrl: 'completionTest.html', selector: 'a',
+    directives: const [OtherComp])
+class MyComp {
+}
+@Component(template: '', selector: 'my-tag', inputs: const ['myDynamicInput'])
+class OtherComp {
+  @Input() String name;
+  @Input() int intInput;
+  
+  bool _myDynamicInput = false;
+  bool get myDynamicInput => _myDynamicInput;
+  void set myDynamicInput(value) {}
+}
+    ''');
+    addTestSource('<my-tag myDyna^></my-tag>');
+
+    await resolveSingleTemplate(dartSource);
+    await computeSuggestions();
+    expect(replacementOffset, completionOffset - 6);
+    expect(replacementLength, 6);
+    assertSuggestSetter('name');
+    assertNotSuggested('intInput');
+    assertSuggestSetter('id', relevance: DART_RELEVANCE_DEFAULT - 2);
+    assertSuggestSetter('[myDynamicInput]');
+    assertSuggestSetter('myDynamicInput',
+        relevance: DART_RELEVANCE_DEFAULT - 1);
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_completeOutputStarted() async {
+    final dartSource = newSource(
+        '/completionTest.dart',
+        '''
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a',
     directives: const [OtherComp])
 class MyComp {
@@ -1375,10 +1525,8 @@
     ''');
 
     addTestSource('<my-tag (^></my-tag>');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset - 1);
     expect(replacementLength, 1);
@@ -1388,11 +1536,12 @@
     assertNotSuggested("[name]");
   }
 
-  test_completeInputReplacing() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeInputReplacing() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a',
     directives: const [OtherComp])
 class MyComp {
@@ -1405,24 +1554,23 @@
     ''');
 
     addTestSource('<my-tag [^input]="4"></my-tag>');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset - 1);
     expect(replacementLength, '[input]'.length);
     assertSuggestSetter("[name]");
-    assertSuggestSetter("[hidden]", relevance: DART_RELEVANCE_DEFAULT - 1);
+    assertSuggestSetter("[hidden]", relevance: DART_RELEVANCE_DEFAULT - 2);
     assertNotSuggested("(nameEvent)");
     assertNotSuggested("(click)");
   }
 
-  test_completeOutputReplacing() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeOutputReplacing() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a',
     directives: const [OtherComp])
 class MyComp {
@@ -1435,10 +1583,8 @@
     ''');
 
     addTestSource('<my-tag (^output)="4"></my-tag>');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset - 1);
     expect(replacementLength, '(output)'.length);
@@ -1448,11 +1594,12 @@
     assertNotSuggested("[name]");
   }
 
-  test_noCompleteInOutputInCloseTag() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_noCompleteInOutputInCloseTag() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a',
     directives: const [OtherComp])
 class MyComp {
@@ -1465,10 +1612,8 @@
     ''');
 
     addTestSource('<my-tag></my-tag ^>');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset);
     expect(replacementLength, 0);
@@ -1478,11 +1623,12 @@
     assertNotSuggested("(click)");
   }
 
-  test_noCompleteEmptyTagContents() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_noCompleteEmptyTagContents() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a',
     directives: const [OtherComp])
 class MyComp {
@@ -1495,10 +1641,8 @@
     ''');
 
     addTestSource('<my-tag>^</my-tag>');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset);
     expect(replacementLength, 0);
@@ -1508,11 +1652,12 @@
     assertNotSuggested("(click)");
   }
 
-  test_noCompleteInOutputsOnTagNameCompletion() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_noCompleteInOutputsOnTagNameCompletion() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a',
     directives: const [OtherComp])
 class MyComp {
@@ -1525,10 +1670,8 @@
     ''');
 
     addTestSource('<my-tag^></my-tag>');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, 0);
     expect(replacementLength, '<my-tag'.length);
@@ -1538,11 +1681,12 @@
     assertNotSuggested("(click)");
   }
 
-  test_completeHtmlSelectorTag_at_beginning() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeHtmlSelectorTag_at_beginning() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-      import '/angular2/angular2.dart';
+      import 'package:angular2/angular2.dart';
       @Component(templateUrl: 'completionTest.html', selector: 'a',
         directives: const [MyChildComponent1, MyChildComponent2])
         class MyComp{}
@@ -1552,10 +1696,8 @@
       class MyChildComponent2
       ''');
     addTestSource('<^<div></div>');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset - 1);
     expect(replacementLength, 1);
@@ -1564,11 +1706,12 @@
     assertSuggestClassTypeAlias("<my-child3");
   }
 
-  test_completeHtmlSelectorTag_at_beginning_with_partial() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeHtmlSelectorTag_at_beginning_with_partial() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-      import '/angular2/angular2.dart';
+      import 'package:angular2/angular2.dart';
       @Component(templateUrl: 'completionTest.html', selector: 'a',
         directives: const [MyChildComponent1, MyChildComponent2])
         class MyComp{}
@@ -1578,10 +1721,8 @@
       class MyChildComponent2{}
       ''');
     addTestSource('<my^<div></div>');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset - '<my'.length);
     expect(replacementLength, '<my'.length);
@@ -1590,11 +1731,12 @@
     assertSuggestClassTypeAlias("<my-child3");
   }
 
-  test_completeHtmlSelectorTag_at_middle() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeHtmlSelectorTag_at_middle() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-      import '/angular2/angular2.dart';
+      import 'package:angular2/angular2.dart';
       @Component(templateUrl: 'completionTest.html', selector: 'a',
         directives: const [MyChildComponent1, MyChildComponent2])
         class MyComp{}
@@ -1604,10 +1746,8 @@
       class MyChildComponent2{}
       ''');
     addTestSource('''<div><div><^</div></div>''');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset - 1);
     expect(replacementLength, 1);
@@ -1616,11 +1756,12 @@
     assertSuggestClassTypeAlias("<my-child3");
   }
 
-  test_completeHtmlSelectorTag_at_middle_of_text() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeHtmlSelectorTag_at_middle_of_text() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-      import '/angular2/angular2.dart';
+      import 'package:angular2/angular2.dart';
       @Component(templateUrl: 'completionTest.html', selector: 'a',
         directives: const [MyChildComponent1, MyChildComponent2])
         class MyComp{}
@@ -1630,10 +1771,8 @@
       class MyChildComponent2{}
       ''');
     addTestSource('''<div><div> some text<^</div></div>''');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset - 1);
     expect(replacementLength, 1);
@@ -1642,11 +1781,12 @@
     assertSuggestClassTypeAlias("<my-child3");
   }
 
-  test_completeHtmlSelectorTag_at_middle_with_partial() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeHtmlSelectorTag_at_middle_with_partial() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-      import '/angular2/angular2.dart';
+      import 'package:angular2/angular2.dart';
       @Component(templateUrl: 'completionTest.html', selector: 'a',
         directives: const [MyChildComponent1, MyChildComponent2])
         class MyComp{}
@@ -1656,10 +1796,8 @@
       class MyChildComponent2{}
       ''');
     addTestSource('''<div><div><my^</div></div>''');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset - '<my'.length);
     expect(replacementLength, '<my'.length);
@@ -1668,11 +1806,12 @@
     assertSuggestClassTypeAlias("<my-child3");
   }
 
-  test_completeHtmlSelectorTag_at_end() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeHtmlSelectorTag_at_end() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-      import '/angular2/angular2.dart';
+      import 'package:angular2/angular2.dart';
       @Component(templateUrl: 'completionTest.html', selector: 'a',
         directives: const [MyChildComponent1, MyChildComponent2])
         class MyComp{}
@@ -1682,10 +1821,8 @@
       class MyChildComponent2{}
       ''');
     addTestSource('''<div><div></div></div><^''');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset - 1);
     expect(replacementLength, 1);
@@ -1694,11 +1831,12 @@
     assertSuggestClassTypeAlias("<my-child3");
   }
 
-  test_completeHtmlSelectorTag_at_end_with_partial() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeHtmlSelectorTag_at_end_with_partial() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-      import '/angular2/angular2.dart';
+      import 'package:angular2/angular2.dart';
       @Component(templateUrl: 'completionTest.html', selector: 'a',
         directives: const [MyChildComponent1, MyChildComponent2])
         class MyComp{}
@@ -1709,10 +1847,8 @@
       ''');
     addTestSource('''<div><div></div></div>
     <my^''');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset - '<my'.length);
     expect(replacementLength, '<my'.length);
@@ -1721,11 +1857,12 @@
     assertSuggestClassTypeAlias("<my-child3");
   }
 
-  test_completeHtmlSelectorTag_on_empty_document() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeHtmlSelectorTag_on_empty_document() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-      import '/angular2/angular2.dart';
+      import 'package:angular2/angular2.dart';
       @Component(templateUrl: 'completionTest.html', selector: 'a',
         directives: const [MyChildComponent1, MyChildComponent2])
         class MyComp{}
@@ -1735,10 +1872,8 @@
       class MyChildComponent2{}
       ''');
     addTestSource('^');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset);
     expect(replacementLength, 0);
@@ -1747,11 +1882,12 @@
     assertSuggestClassTypeAlias("<my-child3");
   }
 
-  test_completeHtmlSelectorTag_at_end_after_close() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeHtmlSelectorTag_at_end_after_close() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-      import '/angular2/angular2.dart';
+      import 'package:angular2/angular2.dart';
       @Component(templateUrl: 'completionTest.html', selector: 'a',
         directives: const [MyChildComponent1, MyChildComponent2])
         class MyComp{}
@@ -1761,10 +1897,8 @@
       class MyChildComponent2{}
       ''');
     addTestSource('<div><div></div></div>^');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset);
     expect(replacementLength, 0);
@@ -1773,30 +1907,12 @@
     assertSuggestClassTypeAlias("<my-child3");
   }
 
-  test_completeTextAttribute_expect_no_suggestion_in_value() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeHtmlSelectorTag__in_middle_of_unclosed_tag() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-      import '/angular2/angular2.dart';
-      @Component(templateUrl: 'completionTest.html', selector: 'a',
-        directives: const [MyChildComponent1, MyChildComponent2])
-        class MyComp{}
-      ''');
-    addTestSource('<div blah="^"></div>');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
-
-    await computeSuggestions();
-    expect(replacementOffset, completionOffset);
-    expect(suggestions.length, 0);
-  }
-
-  test_completeHtmlSelectorTag__in_middle_of_unclosed_tag() async {
-    newSource(
-        '/completionTest.dart',
-        '''
-      import '/angular2/angular2.dart';
+      import 'package:angular2/angular2.dart';
       @Component(templateUrl: 'completionTest.html', selector: 'a',
         directives: const [MyChildComponent1, MyChildComponent2])
         class MyComp{}
@@ -1806,10 +1922,8 @@
       class MyChildComponent2{}
       ''');
     addTestSource('<div>some text<^');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset - 1);
     expect(replacementLength, 1);
@@ -1818,11 +1932,12 @@
     assertSuggestClassTypeAlias("<my-child3");
   }
 
-  test_completeTransclusionSuggestion() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeTransclusionSuggestion() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a',
     directives: const [ContainerComponent])
 class MyComp{}
@@ -1833,10 +1948,8 @@
 class ContainerComponent{}
       ''');
     addTestSource('<container>^</container>');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset);
     expect(replacementLength, 0);
@@ -1845,11 +1958,12 @@
     assertSuggestTransclusion("<tag3 class=\"withclass\"");
   }
 
-  test_completeTransclusionSuggestionInWhitespace() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeTransclusionSuggestionInWhitespace() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a',
     directives: const [ContainerComponent])
 class MyComp{}
@@ -1863,10 +1977,8 @@
 <container>
   ^
 </container>''');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset);
     expect(replacementLength, 0);
@@ -1875,11 +1987,12 @@
     assertSuggestTransclusion("<tag3 class=\"withclass\"");
   }
 
-  test_completeTransclusionSuggestionStarted() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeTransclusionSuggestionStarted() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a',
     directives: const [ContainerComponent])
 class MyComp{}
@@ -1893,10 +2006,8 @@
 <container>
   <^
 </container>''');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     //expect(replacementOffset, completionOffset - 1);
     //expect(replacementLength, 1);
@@ -1905,11 +2016,12 @@
     assertSuggestTransclusion("<tag3 class=\"withclass\"");
   }
 
-  test_completeTransclusionSuggestionStartedTagName() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeTransclusionSuggestionStartedTagName() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a',
     directives: const [ContainerComponent])
 class MyComp{}
@@ -1923,10 +2035,8 @@
 <container>
   <tag^
 </container>''');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     //expect(replacementOffset, completionOffset - 4);
     //expect(replacementLength, 4);
@@ -1935,11 +2045,12 @@
     assertSuggestTransclusion("<tag3 class=\"withclass\"");
   }
 
-  test_completeTransclusionSuggestionAfterTag() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeTransclusionSuggestionAfterTag() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a',
     directives: const [ContainerComponent])
 class MyComp{}
@@ -1954,10 +2065,8 @@
   <blah></blah>
   ^
 </container>''');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset);
     expect(replacementLength, 0);
@@ -1966,11 +2075,12 @@
     assertSuggestTransclusion("<tag3 class=\"withclass\"");
   }
 
-  test_completeTransclusionSuggestionBeforeTag() async {
-    newSource(
+  // ignore: non_constant_identifier_names
+  Future test_completeTransclusionSuggestionBeforeTag() async {
+    final dartSource = newSource(
         '/completionTest.dart',
         '''
-import '/angular2/angular2.dart';
+import 'package:angular2/angular2.dart';
 @Component(templateUrl: 'completionTest.html', selector: 'a',
     directives: const [ContainerComponent])
 class MyComp{}
@@ -1985,10 +2095,8 @@
   ^
   <blah></blah>
 </container>''');
-    //LibrarySpecificUnit target =
-    //    new LibrarySpecificUnit(dartSource, dartSource);
-    //computeResult(target, VIEWS_WITH_HTML_TEMPLATES2);
 
+    await resolveSingleTemplate(dartSource);
     await computeSuggestions();
     expect(replacementOffset, completionOffset);
     expect(replacementLength, 0);
@@ -1997,8 +2105,223 @@
     assertSuggestTransclusion("<tag3 class=\"withclass\"");
   }
 
-  assertSuggestTransclusion(String name) {
+  void assertSuggestTransclusion(String name) {
     assertSuggestClassTypeAlias(name,
         relevance: TemplateCompleter.RELEVANCE_TRANSCLUSION);
   }
+
+  // ignore: non_constant_identifier_names
+  Future test_completeInputInStarReplacing() async {
+    final dartSource = newSource(
+        '/completionTest.dart',
+        '''
+import 'package:angular2/angular2.dart';
+@Component(templateUrl: 'completionTest.html', selector: 'a', directives: const [NgFor])
+class MyComp {
+  List<String> items;
+}
+    ''');
+
+    addTestSource('<div *ngFor="let x of items; trackBy^: foo"></div>');
+
+    await resolveSingleTemplate(dartSource);
+    await computeSuggestions();
+    expect(replacementOffset, completionOffset - 'trackBy'.length);
+    expect(replacementLength, 'trackBy'.length);
+    assertSuggestTemplateInput("trackBy:", elementName: '[ngForTrackBy]');
+    assertNotSuggested("of");
+    assertNotSuggested("of:");
+    assertNotSuggested("trackBy"); // without the colon
+    assertNotSuggested("items");
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_completeInputInStarReplacingBeforeValue() async {
+    final dartSource = newSource(
+        '/completionTest.dart',
+        '''
+import 'package:angular2/angular2.dart';
+@Component(templateUrl: 'completionTest.html', selector: 'a', directives: const [NgFor])
+class MyComp {
+  List<String> items;
+}
+    ''');
+
+    addTestSource('<div *ngFor="let x of items; trackBy^"></div>');
+
+    await resolveSingleTemplate(dartSource);
+    await computeSuggestions();
+    expect(replacementOffset, completionOffset - 'trackBy'.length);
+    expect(replacementLength, 'trackBy'.length);
+    assertSuggestTemplateInput("trackBy:", elementName: '[ngForTrackBy]');
+    assertNotSuggested("of");
+    assertNotSuggested("of:");
+    assertNotSuggested("trackBy"); // without the colon
+    assertNotSuggested("items");
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_completeInputInStar() async {
+    final dartSource = newSource(
+        '/completionTest.dart',
+        '''
+import 'package:angular2/angular2.dart';
+@Component(templateUrl: 'completionTest.html', selector: 'a', directives: const [NgFor])
+class MyComp {
+  List<String> items;
+}
+    ''');
+
+    addTestSource('<div *ngFor="let x of items; ^"></div>');
+
+    await resolveSingleTemplate(dartSource);
+    await computeSuggestions();
+    expect(replacementOffset, completionOffset);
+    expect(replacementLength, 0);
+    assertSuggestTemplateInput("trackBy:", elementName: '[ngForTrackBy]');
+    assertNotSuggested("of");
+    assertNotSuggested("of:");
+    assertNotSuggested("trackBy"); // without the colon
+    assertNotSuggested("items");
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_completeInputInStarValueAlready() async {
+    final dartSource = newSource(
+        '/completionTest.dart',
+        '''
+import 'package:angular2/angular2.dart';
+@Component(templateUrl: 'completionTest.html', selector: 'a', directives: const [NgFor])
+class MyComp {
+  List<String> items;
+}
+    ''');
+
+    addTestSource('<div *ngFor="let x of items; ^ : foo"></div>');
+
+    await resolveSingleTemplate(dartSource);
+    await computeSuggestions();
+    expect(replacementOffset, completionOffset);
+    expect(replacementLength, 0);
+    assertSuggestTemplateInput("trackBy:", elementName: '[ngForTrackBy]');
+    assertNotSuggested("of");
+    assertNotSuggested("of:");
+    assertNotSuggested("trackBy"); // without the colon
+    assertNotSuggested("items");
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_completeNgForStarted() async {
+    final dartSource = newSource(
+        '/completionTest.dart',
+        '''
+import 'package:angular2/angular2.dart';
+@Component(templateUrl: 'completionTest.html', selector: 'a', directives: const [NgFor])
+class MyComp {
+  List<String> items;
+}
+    ''');
+
+    addTestSource('<div *ngFor^');
+
+    await resolveSingleTemplate(dartSource);
+    await computeSuggestions();
+    expect(replacementOffset, completionOffset - '*ngFor'.length);
+    expect(replacementLength, '*ngFor'.length);
+    assertSuggestStar("*ngFor");
+    assertNotSuggested("*ngForOf");
+    assertNotSuggested("[id]");
+    assertNotSuggested("id");
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_completeNgForStartedWithValue() async {
+    final dartSource = newSource(
+        '/completionTest.dart',
+        '''
+import 'package:angular2/angular2.dart';
+@Component(templateUrl: 'completionTest.html', selector: 'a', directives: const [NgFor])
+class MyComp {
+  List<String> items;
+}
+    ''');
+
+    addTestSource('<div *ngFor^="let x of items"></div>');
+
+    await resolveSingleTemplate(dartSource);
+    await computeSuggestions();
+    expect(replacementOffset, completionOffset - '*ngFor'.length);
+    expect(replacementLength, '*ngFor'.length);
+    assertSuggestStar("*ngFor");
+    assertNotSuggested("*ngForOf");
+    assertNotSuggested("[id]");
+    assertNotSuggested("id");
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_completeStarAttrsNotStarted() async {
+    final dartSource = newSource(
+        '/completionTest.dart',
+        '''
+import 'package:angular2/angular2.dart';
+@Component(templateUrl: 'completionTest.html', selector: 'a',
+    directives: const [NgFor, NgIf, CustomTemplateDirective, NotTemplateDirective])
+class MyComp {
+  List<String> items;
+}
+
+@Directive(selector: '[customTemplateDirective]')
+class CustomTemplateDirective {
+  CustomTemplateDirective(TemplateRef tpl);
+}
+
+@Directive(selector: '[notTemplateDirective]')
+class NotTemplateDirective {
+}
+    ''');
+
+    addTestSource('<div ^></div>');
+
+    await resolveSingleTemplate(dartSource);
+    await computeSuggestions();
+    expect(replacementOffset, completionOffset);
+    expect(replacementLength, 0);
+    assertSuggestStar("*ngFor");
+    assertSuggestStar("*ngIf");
+    assertSuggestStar("*customTemplateDirective");
+    assertNotSuggested("*notTemplateDirective");
+    assertNotSuggested("*ngForOf");
+  }
+
+  // ignore: non_constant_identifier_names
+  Future test_completeStarAttrsOnlyStar() async {
+    final dartSource = newSource(
+        '/completionTest.dart',
+        '''
+import 'package:angular2/angular2.dart';
+@Component(templateUrl: 'completionTest.html', selector: 'a',
+    directives: const [NgFor, NgIf, CustomTemplateDirective])
+class MyComp {
+  List<String> items;
+}
+
+@Directive(selector: '[customTemplateDirective]')
+class CustomTemplateDirective {
+  CustomTemplateDirective(TemplateRef tpl);
+}
+    ''');
+
+    addTestSource('<div *^></div>');
+
+    await resolveSingleTemplate(dartSource);
+    await computeSuggestions();
+    expect(replacementOffset, completionOffset - 1);
+    expect(replacementLength, 1);
+    assertSuggestStar("*ngFor");
+    assertSuggestStar("*ngIf");
+    assertSuggestStar("*customTemplateDirective");
+    assertNotSuggested("*ngForOf");
+    assertNotSuggested("[id]");
+    assertNotSuggested("id");
+  }
 }
diff --git a/server_plugin/test/completion_contributor_test_util.dart b/server_plugin/test/completion_contributor_test_util.dart
index f171350..9f0555c 100644
--- a/server_plugin/test/completion_contributor_test_util.dart
+++ b/server_plugin/test/completion_contributor_test_util.dart
@@ -6,86 +6,26 @@
 
 import 'dart:async';
 
-import 'package:analysis_server/plugin/protocol/protocol.dart' as protocol
-    show Element, ElementKind;
-import 'package:analysis_server/plugin/protocol/protocol.dart'
+import 'package:analysis_server/protocol/protocol_generated.dart' as protocol
+    show ElementKind;
+import 'package:analysis_server/protocol/protocol_generated.dart'
     hide Element, ElementKind;
-import 'package:analysis_server/plugin/protocol/protocol.dart';
 import 'package:analysis_server/src/provisional/completion/completion_core.dart';
 import 'package:analysis_server/src/provisional/completion/dart/completion_dart.dart';
 import 'package:analysis_server/src/services/completion/completion_core.dart';
 import 'package:analysis_server/src/services/completion/completion_performance.dart';
-import 'package:analysis_server/src/services/completion/dart/completion_manager.dart'
-    show DartCompletionRequestImpl;
-import 'package:analysis_server/src/services/index/index.dart';
-import 'package:analysis_server/src/services/search/search_engine_internal.dart';
 import 'package:analyzer/src/generated/source.dart';
-import 'package:analyzer/task/dart.dart';
+import 'package:angular_analyzer_plugin/src/model.dart';
 import 'package:unittest/unittest.dart';
 
 import 'analysis_test.dart';
 
 int suggestionComparator(CompletionSuggestion s1, CompletionSuggestion s2) {
-  String c1 = s1.completion.toLowerCase();
-  String c2 = s2.completion.toLowerCase();
+  final c1 = s1.completion.toLowerCase();
+  final c2 = s2.completion.toLowerCase();
   return c1.compareTo(c2);
 }
 
-abstract class AbstractDartCompletionContributorTest
-    extends BaseCompletionContributorTest {
-  DartCompletionContributor contributor;
-  DartCompletionRequest request;
-
-  @override
-  void setUp() {
-    super.setUp();
-    contributor = createContributor();
-  }
-
-  DartCompletionContributor createContributor();
-
-  Future computeSuggestions([int times = 200]) async {
-    context.analysisPriorityOrder = [testSource];
-    CompletionRequestImpl baseRequest = new CompletionRequestImpl(
-      null,
-      context,
-      null,
-      searchEngine,
-      testSource,
-      completionOffset,
-      new CompletionPerformance(),
-      null,
-    );
-
-    // Build the request
-    Completer<DartCompletionRequest> requestCompleter =
-        new Completer<DartCompletionRequest>();
-    DartCompletionRequestImpl
-        .from(baseRequest)
-        .then((DartCompletionRequest request) {
-      requestCompleter.complete(request);
-    });
-    request = await performAnalysis(times, requestCompleter);
-
-    replacementOffset = (request as CompletionRequestImpl).replacementOffset;
-    replacementLength = (request as CompletionRequestImpl).replacementLength;
-    Completer<List<CompletionSuggestion>> suggestionCompleter =
-        new Completer<List<CompletionSuggestion>>();
-
-    // Request completions
-    contributor
-        .computeSuggestions(request)
-        .then((List<CompletionSuggestion> computedSuggestions) {
-      suggestionCompleter.complete(computedSuggestions);
-    });
-
-    // Perform analysis until the suggestions have been computed
-    // or the max analysis cycles ([times]) has been reached
-    suggestions = await performAnalysis(times, suggestionCompleter);
-    expect(suggestions, isNotNull, reason: 'expected suggestions');
-  }
-}
-
 abstract class AbstractCompletionContributorTest
     extends BaseCompletionContributorTest {
   CompletionContributor contributor;
@@ -99,47 +39,38 @@
 
   CompletionContributor createContributor();
 
+  @override
   Future computeSuggestions([int times = 200]) async {
-    context.analysisPriorityOrder = [testSource];
-    CompletionRequestImpl request = new CompletionRequestImpl(
+    final request = new CompletionRequestImpl(
       null,
-      context,
       null,
-      searchEngine,
       testSource,
       completionOffset,
       new CompletionPerformance(),
       null,
     );
 
-    // Build the request
-    Completer<CompletionRequest> requestCompleter =
-        new Completer<CompletionRequest>();
-    requestCompleter.complete(request);
-    request = await performAnalysis<CompletionRequest>(times, requestCompleter);
-
-    Completer<List<CompletionSuggestion>> suggestionCompleter =
-        new Completer<List<CompletionSuggestion>>();
-
     // Request completions
-    contributor
-        .computeSuggestions(request)
-        .then((List<CompletionSuggestion> computedSuggestions) {
-      suggestionCompleter.complete(computedSuggestions);
-    });
-
-    // Perform analysis until the suggestions have been computed
-    // or the max analysis cycles ([times]) has been reached
-    suggestions = await performAnalysis(times, suggestionCompleter);
+    suggestions = await contributor.computeSuggestions(request);
     replacementOffset = request.replacementOffset;
     replacementLength = request.replacementLength;
     expect(suggestions, isNotNull, reason: 'expected suggestions');
   }
+
+  /// Compute all the views declared in the given [dartSource], and resolve the
+  /// external template of all the views.
+  Future resolveSingleTemplate(Source dartSource) async {
+    final result = await angularDriver.resolveDart(dartSource.fullName);
+    for (var d in result.directives) {
+      if (d is Component && d.view.templateUriSource != null) {
+        final htmlPath = d.view.templateUriSource.fullName;
+        await angularDriver.resolveHtml(htmlPath);
+      }
+    }
+  }
 }
 
-abstract class BaseCompletionContributorTest extends AbstractAngularTaskTest {
-  Index index;
-  SearchEngineImpl searchEngine;
+abstract class BaseCompletionContributorTest extends AbstractAngularTest {
   String testFile;
   Source testSource;
   int completionOffset;
@@ -147,34 +78,33 @@
   int replacementLength;
   List<CompletionSuggestion> suggestions;
 
-  /**
-   * If `true` and `null` is specified as the suggestion's expected returnType
-   * then the actual suggestion is expected to have a `dynamic` returnType.
-   * Newer tests return `false` so that they can distinguish between
-   * `dynamic` and `null`.
-   * Eventually all tests should be converted and this getter removed.
-   */
+  /// If `true` and `null` is specified as the suggestion's expected returnType
+  /// then the actual suggestion is expected to have a `dynamic` returnType.
+  /// Newer tests return `false` so that they can distinguish between
+  /// `dynamic` and `null`.
+  /// Eventually all tests should be converted and this getter removed.
   bool get isNullExpectedReturnTypeConsideredDynamic => true;
 
   void addTestSource(String content) {
     expect(completionOffset, isNull, reason: 'Call addTestUnit exactly once');
     completionOffset = content.indexOf('^');
     expect(completionOffset, isNot(equals(-1)), reason: 'missing ^');
-    int nextOffset = content.indexOf('^', completionOffset + 1);
+    final nextOffset = content.indexOf('^', completionOffset + 1);
     expect(nextOffset, equals(-1), reason: 'too many ^');
+    // ignore: parameter_assignments, prefer_interpolation_to_compose_strings
     content = content.substring(0, completionOffset) +
         content.substring(completionOffset + 1);
     testSource = newSource(testFile, content);
   }
 
-  void assertHasNoParameterInfo(CompletionSuggestion suggestion) {
+  void assertHasNoParameterInfo(final suggestion) {
     expect(suggestion.parameterNames, isNull);
     expect(suggestion.parameterTypes, isNull);
     expect(suggestion.requiredParameterCount, isNull);
     expect(suggestion.hasNamedParameters, isNull);
   }
 
-  void assertHasParameterInfo(CompletionSuggestion suggestion) {
+  void assertHasParameterInfo(final suggestion) {
     expect(suggestion.parameterNames, isNotNull);
     expect(suggestion.parameterTypes, isNotNull);
     expect(suggestion.parameterNames.length, suggestion.parameterTypes.length);
@@ -185,13 +115,12 @@
 
   void assertNoSuggestions({CompletionSuggestionKind kind: null}) {
     if (kind == null) {
-      if (suggestions.length > 0) {
+      if (suggestions.isNotEmpty) {
         failedCompletion('Expected no suggestions', suggestions);
       }
       return;
     }
-    CompletionSuggestion suggestion = suggestions.firstWhere(
-        (CompletionSuggestion cs) => cs.kind == kind,
+    final suggestion = suggestions.firstWhere((final cs) => cs.kind == kind,
         orElse: () => null);
     if (suggestion != null) {
       failedCompletion('did not expect completion: $completion\n  $suggestion');
@@ -199,8 +128,8 @@
   }
 
   void assertNotSuggested(String completion) {
-    CompletionSuggestion suggestion = suggestions.firstWhere(
-        (CompletionSuggestion cs) => cs.completion == completion,
+    final suggestion = suggestions.firstWhere(
+        (final cs) => cs.completion == completion,
         orElse: () => null);
     if (suggestion != null) {
       failedCompletion('did not expect completion: $completion\n  $suggestion');
@@ -216,9 +145,9 @@
       bool isPotential: false,
       String elemFile,
       int elemOffset,
-      String paramName,
-      String paramType}) {
-    CompletionSuggestion cs =
+      final paramName,
+      final paramType}) {
+    final cs =
         getSuggest(completion: completion, csKind: csKind, elemKind: elemKind);
     if (cs == null) {
       failedCompletion('expected $completion $csKind $elemKind', suggestions);
@@ -265,14 +194,14 @@
       String elemFile,
       String elemName,
       int elemOffset}) {
-    CompletionSuggestion cs = assertSuggest(name,
+    final cs = assertSuggest(name,
         csKind: kind,
         relevance: relevance,
         importUri: importUri,
         isDeprecated: isDeprecated,
         elemFile: elemFile,
         elemOffset: elemOffset);
-    protocol.Element element = cs.element;
+    final element = cs.element;
     expect(element, isNotNull);
     expect(element.kind, equals(protocol.ElementKind.CLASS));
     expect(element.name, equals(elemName ?? name));
@@ -285,9 +214,8 @@
   CompletionSuggestion assertSuggestClassTypeAlias(String name,
       {int relevance: DART_RELEVANCE_DEFAULT,
       CompletionSuggestionKind kind: CompletionSuggestionKind.INVOCATION}) {
-    CompletionSuggestion cs =
-        assertSuggest(name, csKind: kind, relevance: relevance);
-    protocol.Element element = cs.element;
+    final cs = assertSuggest(name, csKind: kind, relevance: relevance);
+    final element = cs.element;
     expect(element, isNotNull);
     expect(element.kind, equals(protocol.ElementKind.CLASS_TYPE_ALIAS));
     expect(element.name, equals(name));
@@ -301,20 +229,19 @@
       {int relevance: DART_RELEVANCE_DEFAULT,
       String importUri,
       int elemOffset}) {
-    CompletionSuggestion cs = assertSuggest(name,
+    final cs = assertSuggest(name,
         relevance: relevance, importUri: importUri, elemOffset: elemOffset);
-    protocol.Element element = cs.element;
+    final element = cs.element;
     expect(element, isNotNull);
     expect(element.kind, equals(protocol.ElementKind.CONSTRUCTOR));
-    int index = name.indexOf('.');
+    final index = name.indexOf('.');
     expect(element.name, index >= 0 ? name.substring(index + 1) : '');
     return cs;
   }
 
   CompletionSuggestion assertSuggestEnum(String completion,
       {bool isDeprecated: false}) {
-    CompletionSuggestion suggestion =
-        assertSuggest(completion, isDeprecated: isDeprecated);
+    final suggestion = assertSuggest(completion, isDeprecated: isDeprecated);
     expect(suggestion.isDeprecated, isDeprecated);
     expect(suggestion.element.kind, protocol.ElementKind.ENUM);
     return suggestion;
@@ -322,7 +249,7 @@
 
   CompletionSuggestion assertSuggestEnumConst(String completion,
       {int relevance: DART_RELEVANCE_DEFAULT, bool isDeprecated: false}) {
-    CompletionSuggestion suggestion = assertSuggest(completion,
+    final suggestion = assertSuggest(completion,
         relevance: relevance, isDeprecated: isDeprecated);
     expect(suggestion.completion, completion);
     expect(suggestion.isDeprecated, isDeprecated);
@@ -335,7 +262,7 @@
       String importUri,
       CompletionSuggestionKind kind: CompletionSuggestionKind.INVOCATION,
       bool isDeprecated: false}) {
-    CompletionSuggestion cs = assertSuggest(name,
+    final cs = assertSuggest(name,
         csKind: kind,
         relevance: relevance,
         importUri: importUri,
@@ -343,7 +270,7 @@
         isDeprecated: isDeprecated);
     // The returnType represents the type of a field
     expect(cs.returnType, type != null ? type : 'dynamic');
-    protocol.Element element = cs.element;
+    final element = cs.element;
     expect(element, isNotNull);
     expect(element.kind, equals(protocol.ElementKind.FIELD));
     expect(element.name, equals(name));
@@ -359,7 +286,7 @@
       bool isDeprecated: false,
       int relevance: DART_RELEVANCE_DEFAULT,
       String importUri}) {
-    CompletionSuggestion cs = assertSuggest(name,
+    final cs = assertSuggest(name,
         csKind: kind,
         relevance: relevance,
         importUri: importUri,
@@ -369,12 +296,12 @@
     } else if (isNullExpectedReturnTypeConsideredDynamic) {
       expect(cs.returnType, 'dynamic');
     }
-    protocol.Element element = cs.element;
+    final element = cs.element;
     expect(element, isNotNull);
     expect(element.kind, equals(protocol.ElementKind.FUNCTION));
     expect(element.name, equals(name));
     expect(element.isDeprecated, equals(isDeprecated));
-    String param = element.parameters;
+    final param = element.parameters;
     expect(param, isNotNull);
     expect(param[0], equals('('));
     expect(param[param.length - 1], equals(')'));
@@ -393,7 +320,7 @@
       int relevance: DART_RELEVANCE_DEFAULT,
       CompletionSuggestionKind kind: CompletionSuggestionKind.INVOCATION,
       String importUri}) {
-    CompletionSuggestion cs = assertSuggest(name,
+    final cs = assertSuggest(name,
         csKind: kind,
         relevance: relevance,
         importUri: importUri,
@@ -405,13 +332,13 @@
     } else {
       expect(cs.returnType, isNull);
     }
-    protocol.Element element = cs.element;
+    final element = cs.element;
     expect(element, isNotNull);
     expect(element.kind, equals(protocol.ElementKind.FUNCTION_TYPE_ALIAS));
     expect(element.name, equals(name));
     expect(element.isDeprecated, equals(isDeprecated));
     // TODO (danrubel) Determine why params are null
-    //    String param = element.parameters;
+    //    final param = element.parameters;
     //    expect(param, isNotNull);
     //    expect(param[0], equals('('));
     //    expect(param[param.length - 1], equals(')'));
@@ -427,14 +354,14 @@
       String importUri,
       CompletionSuggestionKind kind: CompletionSuggestionKind.INVOCATION,
       bool isDeprecated: false}) {
-    CompletionSuggestion cs = assertSuggest(name,
+    final cs = assertSuggest(name,
         csKind: kind,
         relevance: relevance,
         importUri: importUri,
         elemKind: protocol.ElementKind.GETTER,
         isDeprecated: isDeprecated);
     expect(cs.returnType, returnType != null ? returnType : 'dynamic');
-    protocol.Element element = cs.element;
+    final element = cs.element;
     expect(element, isNotNull);
     expect(element.kind, equals(protocol.ElementKind.GETTER));
     expect(element.name, equals(name));
@@ -451,18 +378,18 @@
       String importUri,
       CompletionSuggestionKind kind: CompletionSuggestionKind.INVOCATION,
       bool isDeprecated: false}) {
-    CompletionSuggestion cs = assertSuggest(name,
+    final cs = assertSuggest(name,
         csKind: kind,
         relevance: relevance,
         importUri: importUri,
         isDeprecated: isDeprecated);
     expect(cs.declaringType, equals(declaringType));
     expect(cs.returnType, returnType != null ? returnType : 'dynamic');
-    protocol.Element element = cs.element;
+    final element = cs.element;
     expect(element, isNotNull);
     expect(element.kind, equals(protocol.ElementKind.METHOD));
     expect(element.name, equals(name));
-    String param = element.parameters;
+    final param = element.parameters;
     expect(param, isNotNull);
     expect(param[0], equals('('));
     expect(param[param.length - 1], equals(')'));
@@ -476,7 +403,7 @@
       String importUri,
       CompletionSuggestionKind kind: CompletionSuggestionKind.IDENTIFIER,
       bool isDeprecated: false}) {
-    CompletionSuggestion cs = assertSuggest(name,
+    final cs = assertSuggest(name,
         csKind: kind,
         relevance: relevance,
         importUri: importUri,
@@ -491,12 +418,12 @@
       {int relevance: DART_RELEVANCE_DEFAULT,
       String importUri,
       CompletionSuggestionKind kind: CompletionSuggestionKind.INVOCATION}) {
-    CompletionSuggestion cs = assertSuggest(name,
+    final cs = assertSuggest(name,
         csKind: kind,
         relevance: relevance,
         importUri: importUri,
         elemKind: protocol.ElementKind.SETTER);
-    protocol.Element element = cs.element;
+    final element = cs.element;
     expect(element, isNotNull);
     expect(element.kind, equals(protocol.ElementKind.SETTER));
     expect(element.name, equals(name));
@@ -510,18 +437,53 @@
     return cs;
   }
 
+  CompletionSuggestion assertSuggestTemplateInput(String name,
+      {String elementName,
+      int relevance: DART_RELEVANCE_DEFAULT,
+      String importUri,
+      CompletionSuggestionKind kind: CompletionSuggestionKind.INVOCATION}) {
+    final cs = assertSuggest(name,
+        csKind: kind,
+        relevance: relevance,
+        importUri: importUri,
+        elemKind: protocol.ElementKind.SETTER);
+    final element = cs.element;
+    expect(element, isNotNull);
+    expect(element.kind, equals(protocol.ElementKind.SETTER));
+    expect(element.name, equals(elementName));
+    if (element.returnType != null) {
+      expect(element.returnType, 'dynamic');
+    }
+    assertHasNoParameterInfo(cs);
+    return cs;
+  }
+
+  CompletionSuggestion assertSuggestStar(String name,
+      {int relevance: DART_RELEVANCE_DEFAULT,
+      CompletionSuggestionKind kind: CompletionSuggestionKind.IDENTIFIER}) {
+    final cs = assertSuggest(name, csKind: kind, relevance: relevance);
+    final element = cs.element;
+    expect(element, isNotNull);
+    expect(element.kind, equals(protocol.ElementKind.CLASS));
+    expect(element.name, equals(name));
+    expect(element.parameters, isNull);
+    expect(element.returnType, isNull);
+    assertHasNoParameterInfo(cs);
+    return cs;
+  }
+
   CompletionSuggestion assertSuggestTopLevelVar(String name, String returnType,
       {int relevance: DART_RELEVANCE_DEFAULT,
       CompletionSuggestionKind kind: CompletionSuggestionKind.INVOCATION,
       String importUri}) {
-    CompletionSuggestion cs = assertSuggest(name,
+    final cs = assertSuggest(name,
         csKind: kind, relevance: relevance, importUri: importUri);
     if (returnType != null) {
       expect(cs.returnType, returnType);
     } else if (isNullExpectedReturnTypeConsideredDynamic) {
       expect(cs.returnType, 'dynamic');
     }
-    protocol.Element element = cs.element;
+    final element = cs.element;
     expect(element, isNotNull);
     expect(element.kind, equals(protocol.ElementKind.TOP_LEVEL_VARIABLE));
     expect(element.name, equals(name));
@@ -539,14 +501,14 @@
       {int relevance: DART_RELEVANCE_LOCAL_VARIABLE,
       CompletionSuggestionKind kind: CompletionSuggestionKind.INVOCATION,
       String importUri}) {
-    CompletionSuggestion cs = assertSuggest(name,
+    final cs = assertSuggest(name,
         csKind: kind, relevance: relevance, importUri: importUri);
     if (returnType != null) {
       expect(cs.returnType, returnType);
     } else if (isNullExpectedReturnTypeConsideredDynamic) {
       expect(cs.returnType, 'dynamic');
     }
-    protocol.Element element = cs.element;
+    final element = cs.element;
     expect(element, isNotNull);
     expect(element.kind, equals(protocol.ElementKind.LOCAL_VARIABLE));
     expect(element.name, equals(name));
@@ -560,37 +522,16 @@
     return cs;
   }
 
-  /**
-   * Return a [Future] that completes with the containing library information
-   * after it is accessible via [context.getLibrariesContaining].
-   */
-  Future computeLibrariesContaining([int times = 200]) {
-    List<Source> libraries = context.getLibrariesContaining(testSource);
-    if (libraries.isNotEmpty) {
-      return new Future.value(libraries);
-    }
-    if (times == 0) {
-      fail('failed to determine libraries containing $testSource');
-    }
-    context.performAnalysisTask();
-    // We use a delayed future to allow microtask events to finish. The
-    // Future.value or Future() constructors use scheduleMicrotask themselves and
-    // would therefore not wait for microtask callbacks that are scheduled after
-    // invoking this method.
-    return new Future.delayed(
-        Duration.ZERO, () => computeLibrariesContaining(times - 1));
-  }
-
   Future computeSuggestions([int times = 200]);
 
   void failedCompletion(String message,
       [Iterable<CompletionSuggestion> completions]) {
-    StringBuffer sb = new StringBuffer(message);
+    final sb = new StringBuffer(message);
     if (completions != null) {
       sb.write('\n  found');
       completions.toList()
         ..sort(suggestionComparator)
-        ..forEach((CompletionSuggestion suggestion) {
+        ..forEach((final suggestion) {
           sb.write('\n    ${suggestion.completion} -> $suggestion');
         });
     }
@@ -601,9 +542,9 @@
       {String completion: null,
       CompletionSuggestionKind csKind: null,
       protocol.ElementKind elemKind: null}) {
-    CompletionSuggestion cs;
+    var cs;
     if (suggestions != null) {
-      suggestions.forEach((CompletionSuggestion s) {
+      suggestions.forEach((s) {
         if (completion != null && completion != s.completion) {
           return;
         }
@@ -611,7 +552,7 @@
           return;
         }
         if (elemKind != null) {
-          protocol.Element element = s.element;
+          final element = s.element;
           if (element == null || elemKind != element.kind) {
             return;
           }
@@ -627,33 +568,9 @@
     return cs;
   }
 
-  Future<E> performAnalysis<E>(int times, Completer<E> completer) {
-    if (completer.isCompleted) {
-      return completer.future;
-    }
-    if (times == 0 || context == null) {
-      return new Future.value();
-    }
-    context.performAnalysisTask();
-    // We use a delayed future to allow microtask events to finish. The
-    // Future.value or Future() constructors use scheduleMicrotask themselves and
-    // would therefore not wait for microtask callbacks that are scheduled after
-    // invoking this method.
-    return new Future.delayed(
-        Duration.ZERO, () => performAnalysis(times - 1, completer));
-  }
-
-  void resolveSource(String path, String content) {
-    Source libSource = newSource(path, content);
-    var target = new LibrarySpecificUnit(libSource, libSource);
-    context.computeResult(target, RESOLVED_UNIT);
-  }
-
   @override
   void setUp() {
     super.setUp();
-    index = createMemoryIndex();
-    searchEngine = new SearchEngineImpl(index, null);
     addAngularSources();
   }
 }
diff --git a/server_plugin/test/mock_sdk.dart b/server_plugin/test/mock_sdk.dart
index 94eaa69..5cc5721 100644
--- a/server_plugin/test/mock_sdk.dart
+++ b/server_plugin/test/mock_sdk.dart
@@ -1,50 +1,165 @@
-library test.src.mock_sdk;
-
 import 'package:analyzer/file_system/file_system.dart' as resource;
 import 'package:analyzer/file_system/memory_file_system.dart' as resource;
 import 'package:analyzer/src/context/cache.dart';
 import 'package:analyzer/src/context/context.dart';
-import 'package:analyzer/src/generated/engine.dart'
-    show AnalysisEngine, ChangeSet;
+import 'package:analyzer/src/generated/engine.dart' show AnalysisEngine;
 import 'package:analyzer/src/generated/sdk.dart';
 import 'package:analyzer/src/generated/source.dart';
-import 'package:analyzer/src/summary/idl.dart';
+import 'package:analyzer/src/summary/idl.dart' show PackageBundle;
+import 'package:analyzer/src/summary/summary_file_builder.dart';
 
-class MockSdk implements DartSdk {
-  static const _MockSdkLibrary LIB_CORE = const _MockSdkLibrary(
-      'dart:core',
-      '/lib/core/core.dart',
-      '''
+const librariesContent = r'''
+const Map<String, LibraryInfo> libraries = const {
+  "async": const LibraryInfo("async/async.dart"),
+  "collection": const LibraryInfo("collection/collection.dart"),
+  "convert": const LibraryInfo("convert/convert.dart"),
+  "core": const LibraryInfo("core/core.dart"),
+  "html": const LibraryInfo(
+    "html/dartium/html_dartium.dart",
+    dart2jsPath: "html/dart2js/html_dart2js.dart"),
+  "math": const LibraryInfo("math/math.dart"),
+  "_foreign_helper": const LibraryInfo("_internal/js_runtime/lib/foreign_helper.dart"),
+};
+''';
+
+const sdkRoot = '/sdk';
+
+const _LIB_ASYNC = const _MockSdkLibrary(
+    'dart:async',
+    '$sdkRoot/lib/async/async.dart',
+    '''
+library dart.async;
+
+import 'dart:math';
+
+part 'stream.dart';
+
+class Future<T> {
+  factory Future(computation()) => null;
+  factory Future.delayed(Duration duration, [T computation()]) => null;
+  factory Future.value([value]) => null;
+
+  static Future<List/*<T>*/> wait/*<T>*/(
+      Iterable<Future/*<T>*/> futures) => null;
+  Future/*<R>*/ then/*<R>*/(FutureOr/*<R>*/ onValue(T value)) => null;
+
+  Future<T> whenComplete(action());
+}
+
+class FutureOr<T> {}
+
+abstract class Completer<T> {
+  factory Completer() => new _AsyncCompleter<T>();
+  factory Completer.sync() => new _SyncCompleter<T>();
+  Future<T> get future;
+  void complete([value]);
+  void completeError(Object error, [StackTrace stackTrace]);
+  bool get isCompleted;
+}
+''',
+    const <String, String>{
+      '$sdkRoot/lib/async/stream.dart': r'''
+part of dart.async;
+abstract class Stream<T> {
+  Future<T> get first;
+  StreamSubscription<T> listen(void onData(T event),
+                               { Function onError,
+                                 void onDone(),
+                                 bool cancelOnError});
+}
+
+abstract class StreamSubscription<T> {
+  Future cancel();
+  void onData(void handleData(T data));
+  void onError(Function handleError);
+  void onDone(void handleDone());
+  void pause([Future resumeSignal]);
+  void resume();
+  bool get isPaused;
+  Future<E> asFuture<E>([E futureValue]);
+}
+
+abstract class StreamTransformer<S, T> {}
+'''
+    });
+
+const _LIB_COLLECTION = const _MockSdkLibrary(
+    'dart:collection',
+    '$sdkRoot/lib/collection/collection.dart',
+    '''
+library dart.collection;
+
+abstract class HashMap<K, V> implements Map<K, V> {}
+''');
+
+const _LIB_CONVERT = const _MockSdkLibrary(
+    'dart:convert',
+    '$sdkRoot/lib/convert/convert.dart',
+    '''
+library dart.convert;
+
+import 'dart:async';
+
+abstract class Converter<S, T> implements StreamTransformer {}
+class JsonDecoder extends Converter<String, Object> {}
+''');
+
+const _LIB_CORE = const _MockSdkLibrary(
+    'dart:core',
+    '$sdkRoot/lib/core/core.dart',
+    '''
 library dart.core;
 
 import 'dart:async';
 
 class Object {
+  const Object() {}
   bool operator ==(other) => identical(this, other);
   String toString() => 'a string';
   int get hashCode => 0;
+  Type get runtimeType => null;
+  dynamic noSuchMethod(Invocation invocation) => null;
 }
 
 class Function {}
 class StackTrace {}
-class Symbol {}
+
+class Symbol {
+  const factory Symbol(String name) {
+    return null;
+  }
+}
+
 class Type {}
 
 abstract class Comparable<T> {
   int compareTo(T other);
 }
 
-abstract class String implements Comparable<String> {
+abstract class Pattern {}
+abstract class String implements Comparable<String>, Pattern {
   external factory String.fromCharCodes(Iterable<int> charCodes,
                                         [int start = 0, int end]);
+  String operator +(String other) => null;
   bool get isEmpty => false;
   bool get isNotEmpty => false;
   int get length => 0;
+  String substring(int len) => null;
+  String toLowerCase();
   String toUpperCase();
   List<int> get codeUnits;
 }
+abstract class RegExp implements Pattern {
+  external factory RegExp(String source);
+}
 
-class bool extends Object {}
+class bool extends Object {
+  external const factory bool.fromEnvironment(String name,
+                                              {bool defaultValue: false});
+}
+
+abstract class Invocation {}
+
 abstract class num implements Comparable<num> {
   bool operator <(num other);
   bool operator <=(num other);
@@ -54,20 +169,75 @@
   num operator -(num other);
   num operator *(num other);
   num operator /(num other);
+  int operator ^(int other);
+  int operator |(int other);
+  int operator <<(int other);
+  int operator >>(int other);
+  int operator ~/(num other);
+  num operator %(num other);
+  int operator ~();
+  num operator -();
   int toInt();
+  double toDouble();
   num abs();
   int round();
 }
 abstract class int extends num {
+  external const factory int.fromEnvironment(String name, {int defaultValue});
+
+  bool get isNegative;
   bool get isEven => false;
+
+  int operator &(int other);
+  int operator |(int other);
+  int operator ^(int other);
+  int operator ~();
+  int operator <<(int shiftAmount);
+  int operator >>(int shiftAmount);
+
   int operator -();
+
   external static int parse(String source,
                             { int radix,
                               int onError(String source) });
 }
-class double extends num {}
+
+abstract class double extends num {
+  static const double NAN = 0.0 / 0.0;
+  static const double INFINITY = 1.0 / 0.0;
+  static const double NEGATIVE_INFINITY = -INFINITY;
+  static const double MIN_POSITIVE = 5e-324;
+  static const double MAX_FINITE = 1.7976931348623157e+308;
+
+  double remainder(num other);
+  double operator +(num other);
+  double operator -(num other);
+  double operator *(num other);
+  double operator %(num other);
+  double operator /(num other);
+  int operator ~/(num other);
+  double operator -();
+  double abs();
+  double get sign;
+  int round();
+  int floor();
+  int ceil();
+  int truncate();
+  double roundToDouble();
+  double floorToDouble();
+  double ceilToDouble();
+  double truncateToDouble();
+  external static double parse(String source,
+                               [double onError(String source)]);
+}
+
 class DateTime extends Object {}
-class Null extends Object {}
+
+class Null extends Object {
+  factory Null._uninstantiable() {
+    throw new UnsupportedError('class Null cannot be instantiated');
+  }
+}
 
 class Deprecated extends Object {
   final String expires;
@@ -83,79 +253,211 @@
 abstract class Iterable<E> {
   Iterator<E> get iterator;
   bool get isEmpty;
+  E get first;
+
+  Iterable/*<R>*/ map/*<R>*/(/*=R*/ f(E e));
+
+  /*=R*/ fold/*<R>*/(/*=R*/ initialValue,
+      /*=R*/ combine(/*=R*/ previousValue, E element));
+
+  Iterable/*<T>*/ expand/*<T>*/(Iterable/*<T>*/ f(E element));
+
+  List<E> toList();
 }
 
-abstract class List<E> implements Iterable<E> {
-  void add(E value);
-  E operator [](int index);
-  void operator []=(int index, E value);
+class List<E> implements Iterable<E> {
+  List();
+  void add(E value) {}
+  void addAll(Iterable<E> iterable) {}
+  E operator [](int index) => null;
+  void operator []=(int index, E value) {}
   Iterator<E> get iterator => null;
-  void clear();
+  void clear() {}
+
+  bool get isEmpty => false;
+  E get first => null;
+  E get last => null;
+
+  Iterable/*<R>*/ map/*<R>*/(/*=R*/ f(E e)) => null;
+
+  /*=R*/ fold/*<R>*/(/*=R*/ initialValue,
+      /*=R*/ combine(/*=R*/ previousValue, E element)) => null;
+
 }
 
-abstract class Map<K, V> extends Object {
-  Iterable<K> get keys;
+class Map<K, V> extends Object {
+  V operator [](K key) => null;
+  void operator []=(K key, V value) {}
+  Iterable<K> get keys => null;
+  int get length;
+  Iterable<V> get values;
 }
 
 external bool identical(Object a, Object b);
 
 void print(Object object) {}
 
-class _Override {
-  const _Override();
-}
+class _Proxy { const _Proxy(); }
+const Object proxy = const _Proxy();
+
+class _Override { const _Override(); }
 const Object override = const _Override();
 ''');
 
-  static const _MockSdkLibrary LIB_ASYNC = const _MockSdkLibrary(
-      'dart:async',
-      '/lib/async/async.dart',
-      '''
-library dart.async;
+const _LIB_FOREIGN_HELPER = const _MockSdkLibrary(
+    'dart:_foreign_helper',
+    '$sdkRoot/lib/_foreign_helper/_foreign_helper.dart',
+    '''
+library dart._foreign_helper;
 
-import 'dart:math';
-
-class Future<T> {
-  factory Future.delayed(Duration duration, [T computation()]) => null;
-  factory Future.value([value]) => null;
-  static Future wait(List<Future> futures) => null;
-}
-
-class Stream<T> {}
-abstract class StreamTransformer<S, T> {}
+JS(String typeDescription, String codeTemplate,
+  [arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11])
+{}
 ''');
 
-  static const _MockSdkLibrary LIB_COLLECTION = const _MockSdkLibrary(
-      'dart:collection',
-      '/lib/collection/collection.dart',
-      '''
-library dart.collection;
-
-abstract class HashMap<K, V> implements Map<K, V> {}
+const _LIB_HTML_DART2JS = const _MockSdkLibrary(
+    'dart:html',
+    '$sdkRoot/lib/html/dartium/html_dartium.dart',
+    '''
+library dart.html;
+class HtmlElement {}
 ''');
 
-  static const _MockSdkLibrary LIB_CONVERT = const _MockSdkLibrary(
-      'dart:convert',
-      '/lib/convert/convert.dart',
-      '''
-library dart.convert;
-
+const _LIB_HTML_DARTIUM = const _MockSdkLibrary(
+    'dart:html',
+    '$sdkRoot/lib/html/dartium/html_dartium.dart',
+    '''
+library dart.html;
 import 'dart:async';
 
-abstract class Converter<S, T> implements StreamTransformer {}
-class JsonDecoder extends Converter<String, Object> {}
+class Event {}
+class MouseEvent extends Event {}
+
+class DomName {
+  final String name;
+  const DomName(this.name);
+}
+
+abstract class ElementStream<T extends Event> implements Stream<T> {}
+
+abstract class Element {
+  /// Stream of `cut` events handled by this [Element].
+  @DomName('Element.oncut')
+  ElementStream<Event> get onCut => null;
+  
+  @DomName('Element.id')
+  String get id => null;
+  
+  @DomName('Element.id')
+  set id(String value) => null;
+}
+
+class HtmlElement extends Element {
+  int tabIndex;
+  @DomName('Element.onchange')
+  ElementStream<Event> get onChange => null;
+  @DomName('Element.onclick')
+  ElementStream<MouseEvent> get onClick => null;
+  @DomName('Element.onkeyup')
+  ElementStream<Event> get onKeyUp => null;
+  
+  @DomName('HTMLElement.hidden')
+  bool get hidden => null;
+  @DomName('HTMLElement.hidden')
+  set hidden(bool value) => null;
+}
+
+dynamic JS(a, b, c, d) {}
+
+class AnchorElement extends HtmlElement {
+  factory AnchorElement({String href}) {
+    AnchorElement e = JS('returns:AnchorElement;creates:AnchorElement;new:true',
+        '#.createElement(#)', document, "a");
+    if (href != null) e.href = href;
+    return e;
+  }
+  String href;
+  String _privateField;
+}
+
+@DomName('HTMLBodyElement')
+class BodyElement extends HtmlElement {
+  factory BodyElement() => document.createElement("body");
+
+  @DomName('HTMLBodyElement.onunload')
+  ElementStream<Event> get onUnload => null;
+}
+
+class ButtonElement extends HtmlElement {
+  factory ButtonElement._() { throw new UnsupportedError("Not supported"); }
+  factory ButtonElement() => document.createElement("button");
+  bool autofocus;
+}
+
+class HeadingElement extends HtmlElement {
+  factory HeadingElement._() { throw new UnsupportedError("Not supported"); }
+  factory HeadingElement.h1() => document.createElement("h1");
+  factory HeadingElement.h2() => document.createElement("h2");
+  factory HeadingElement.h3() => document.createElement("h3");
+}
+
+class InputElement extends HtmlElement {
+  factory InputElement._() { throw new UnsupportedError("Not supported"); }
+  factory InputElement() => document.createElement("input");
+  String value;
+  String validationMessage;
+}
+
+class OptionElement extends HtmlElement {                                        
+  factory OptionElement({String data: '', String value : '', bool selected: false}) {
+  }                                                                              
+                                                                                 
+  @DomName('HTMLOptionElement.HTMLOptionElement')                                
+  @DocsEditable()                                                                
+  factory OptionElement._([String data, String value, bool defaultSelected, bool selected]) {
+  }    
+}
+
+class TableSectionElement extends HtmlElement {
+
+  @DomName('HTMLTableSectionElement.rows')
+  List<TableRowElement> get rows => null;
+
+  TableRowElement addRow() {
+  }
+
+  TableRowElement insertRow(int index) => null;
+
+  factory TableSectionElement._() { throw new UnsupportedError("Not supported"); }
+
+  @Deprecated("Internal Use Only")
+  external static Type get instanceRuntimeType;
+
+  @Deprecated("Internal Use Only")
+  TableSectionElement.internal_() : super.internal_();
+}
 ''');
 
-  static const _MockSdkLibrary LIB_MATH = const _MockSdkLibrary(
-      'dart:math',
-      '/lib/math/math.dart',
-      '''
+const _LIB_INTERCEPTORS = const _MockSdkLibrary(
+    'dart:_interceptors',
+    '$sdkRoot/lib/_internal/js_runtime/lib/interceptors.dart',
+    '''
+library dart._interceptors;
+''');
+
+const _LIB_MATH = const _MockSdkLibrary(
+    'dart:math',
+    '$sdkRoot/lib/math/math.dart',
+    '''
 library dart.math;
+
 const double E = 2.718281828459045;
 const double PI = 3.1415926535897932;
 const double LN10 =  2.302585092994046;
-num min(num a, num b) => 0;
-num max(num a, num b) => 0;
+
+T min<T extends num>(T a, T b) => null;
+T max<T extends num>(T a, T b) => null;
+
 external double cos(num x);
 external double sin(num x);
 external double sqrt(num x);
@@ -166,115 +468,119 @@
 }
 ''');
 
-  static const _MockSdkLibrary LIB_HTML = const _MockSdkLibrary(
-      'dart:html',
-      '/lib/html/dartium/html_dartium.dart',
-      '''
-library dart.html;
-import 'dart:async';
-class DomName {
-  final String name;
-  const DomName(this.name);
-}
+const _LIBRARIES = const <SdkLibrary>[
+  _LIB_CORE,
+  _LIB_ASYNC,
+  _LIB_COLLECTION,
+  _LIB_CONVERT,
+  _LIB_FOREIGN_HELPER,
+  _LIB_MATH,
+  _LIB_HTML_DART2JS,
+  _LIB_HTML_DARTIUM,
+  _LIB_INTERCEPTORS,
+];
 
-class Event {}
-class MouseEvent extends Event {}
-abstract class ElementStream<T extends Event> implements Stream<T> {}
+class MockSdk implements DartSdk {
+  static const Map<String, String> FULL_URI_MAP = const {
+    "dart:core": "$sdkRoot/lib/core/core.dart",
+    "dart:html": "$sdkRoot/lib/html/dartium/html_dartium.dart",
+    "dart:async": "$sdkRoot/lib/async/async.dart",
+    "dart:async/stream.dart": "$sdkRoot/lib/async/stream.dart",
+    "dart:collection": "$sdkRoot/lib/collection/collection.dart",
+    "dart:convert": "$sdkRoot/lib/convert/convert.dart",
+    "dart:_foreign_helper": "$sdkRoot/lib/_foreign_helper/_foreign_helper.dart",
+    "dart:_interceptors":
+        "$sdkRoot/lib/_internal/js_runtime/lib/interceptors.dart",
+    "dart:math": "$sdkRoot/lib/math/math.dart"
+  };
 
-class HtmlElement {
-  @DomName('Element.onclick')
-  ElementStream<MouseEvent> get onClick => null;
-  @DomName('Element.hidden')
-  bool hidden;
-}
+  static const Map<String, String> NO_ASYNC_URI_MAP = const {
+    "dart:core": "$sdkRoot/lib/core/core.dart",
+  };
 
-class ButtonElement extends HtmlElement {
-  factory ButtonElement._() { throw new UnsupportedError("Not supported"); }
-  factory ButtonElement() => document.createElement("button");
-  bool autofocus;
-}
-''');
+  final resource.MemoryResourceProvider provider;
 
-  static const List<SdkLibrary> LIBRARIES = const [
-    LIB_CORE,
-    LIB_ASYNC,
-    LIB_COLLECTION,
-    LIB_CONVERT,
-    LIB_MATH,
-    LIB_HTML,
-  ];
+  final Map<String, String> uriMap;
 
-  final resource.MemoryResourceProvider provider =
-      new resource.MemoryResourceProvider();
-
-  /**
-   * The [AnalysisContext] which is used for all of the sources.
-   */
+  /// The [AnalysisContextImpl] which is used for all of the sources.
   AnalysisContextImpl _analysisContext;
 
-  MockSdk() {
-    LIBRARIES.forEach((SdkLibrary library) {
-      provider.newFile(library.path, (library as _MockSdkLibrary).content);
-    });
+  @override
+  final List<SdkLibrary> sdkLibraries;
+
+  /// The cached linked bundle of the SDK.
+  PackageBundle _bundle;
+
+  MockSdk(
+      {bool generateSummaryFiles: false,
+      bool dartAsync: true,
+      resource.MemoryResourceProvider resourceProvider})
+      : provider = resourceProvider ?? new resource.MemoryResourceProvider(),
+        sdkLibraries = dartAsync ? _LIBRARIES : [_LIB_CORE],
+        uriMap = dartAsync ? FULL_URI_MAP : NO_ASYNC_URI_MAP {
+    for (_MockSdkLibrary library in sdkLibraries) {
+      provider.newFile(provider.convertPath(library.path), library.content);
+      library.parts.forEach((path, content) {
+        provider.newFile(provider.convertPath(path), content);
+      });
+    }
+    provider.newFile(
+        provider.convertPath(
+            '$sdkRoot/lib/_internal/sdk_library_metadata/lib/libraries.dart'),
+        librariesContent);
+    if (generateSummaryFiles) {
+      final bytes = _computeLinkedBundleBytes();
+      provider
+        ..newFileWithBytes(
+            provider.convertPath('/lib/_internal/spec.sum'), bytes)
+        ..newFileWithBytes(
+            provider.convertPath('/lib/_internal/strong.sum'), bytes);
+    }
   }
 
   @override
   AnalysisContextImpl get context {
     if (_analysisContext == null) {
       _analysisContext = new _SdkAnalysisContext(this);
-      SourceFactory factory = new SourceFactory([new DartUriResolver(this)]);
+      final factory = new SourceFactory([new DartUriResolver(this)]);
       _analysisContext.sourceFactory = factory;
-      ChangeSet changeSet = new ChangeSet();
-      for (String uri in uris) {
-        Source source = factory.forUri(uri);
-        changeSet.addedSource(source);
-      }
-      _analysisContext.applyChanges(changeSet);
     }
     return _analysisContext;
   }
 
   @override
-  List<SdkLibrary> get sdkLibraries => LIBRARIES;
+  String get sdkVersion => throw new UnimplementedError();
 
   @override
-  String get sdkVersion => throw unimplemented;
-
-  UnimplementedError get unimplemented => new UnimplementedError();
-
-  @override
-  List<String> get uris {
-    List<String> uris = <String>[];
-    for (SdkLibrary library in LIBRARIES) {
-      uris.add(library.shortName);
-    }
-    return uris;
-  }
+  List<String> get uris =>
+      sdkLibraries.map((library) => library.shortName).toList();
 
   @override
   Source fromFileUri(Uri uri) {
-    String filePath = uri.path;
-    String libPath = '/lib';
-    if (!filePath.startsWith("$libPath/")) {
+    final filePath = provider.pathContext.fromUri(uri);
+    if (!filePath.startsWith(provider.convertPath('$sdkRoot/lib/'))) {
       return null;
     }
-    for (SdkLibrary library in LIBRARIES) {
-      String libraryPath = library.path;
-      if (filePath.replaceAll('\\', '/') == libraryPath) {
+    for (final library in sdkLibraries) {
+      final libraryPath = provider.convertPath(library.path);
+      if (filePath == libraryPath) {
         try {
-          resource.File file = provider.getResource(uri.path);
-          Uri dartUri = Uri.parse(library.shortName);
+          final resource.File file = provider.getResource(filePath);
+          final dartUri = Uri.parse(library.shortName);
           return file.createSource(dartUri);
         } catch (exception) {
           return null;
         }
       }
-      if (filePath.startsWith("$libraryPath/")) {
-        String pathInLibrary = filePath.substring(libraryPath.length + 1);
-        String path = '${library.shortName}/${pathInLibrary}';
+      // ignore: prefer_interpolation_to_compose_strings
+      final libraryRootPath = provider.pathContext.dirname(libraryPath) +
+          provider.pathContext.separator;
+      if (filePath.startsWith(libraryRootPath)) {
+        final pathInLibrary = filePath.substring(libraryRootPath.length);
+        final uriStr = '${library.shortName}/$pathInLibrary';
         try {
-          resource.File file = provider.getResource(uri.path);
-          Uri dartUri = new Uri(scheme: 'dart', path: path);
+          final resource.File file = provider.getResource(filePath);
+          final dartUri = Uri.parse(uriStr);
           return file.createSource(dartUri);
         } catch (exception) {
           return null;
@@ -285,74 +591,101 @@
   }
 
   @override
+  PackageBundle getLinkedBundle() {
+    if (_bundle == null) {
+      final summaryFile =
+          provider.getFile(provider.convertPath('/lib/_internal/spec.sum'));
+      List<int> bytes;
+      if (summaryFile.exists) {
+        bytes = summaryFile.readAsBytesSync();
+      } else {
+        bytes = _computeLinkedBundleBytes();
+      }
+      _bundle = new PackageBundle.fromBuffer(bytes);
+    }
+    return _bundle;
+  }
+
+  @override
   SdkLibrary getSdkLibrary(String dartUri) {
-    // getSdkLibrary() is only used to determine whether a library is internal
-    // to the SDK.  The mock SDK doesn't have any internals, so it's safe to
-    // return null.
+    for (final library in _LIBRARIES) {
+      if (library.shortName == dartUri) {
+        return library;
+      }
+    }
     return null;
   }
 
   @override
   Source mapDartUri(String dartUri) {
-    const Map<String, String> uriToPath = const {
-      "dart:core": "/lib/core/core.dart",
-      "dart:html": "/lib/html/dartium/html_dartium.dart",
-      "dart:async": "/lib/async/async.dart",
-      "dart:collection": "/lib/collection/collection.dart",
-      "dart:convert": "/lib/convert/convert.dart",
-      "dart:math": "/lib/math/math.dart"
-    };
-
-    String path = uriToPath[dartUri];
+    final path = uriMap[dartUri];
     if (path != null) {
-      resource.File file = provider.getResource(path);
-      Uri uri = new Uri(scheme: 'dart', path: dartUri.substring(5));
+      final resource.File file =
+          provider.getResource(provider.convertPath(path));
+      final uri = new Uri(scheme: 'dart', path: dartUri.substring(5));
       return file.createSource(uri);
     }
-
     // If we reach here then we tried to use a dartUri that's not in the
     // table above.
     return null;
   }
 
-  @override
-  PackageBundle getLinkedBundle() => null;
+  /// This method is used to apply patches to [MockSdk].  It may be called only
+  /// before analysis, i.e. before the analysis context was created.
+  void updateUriFile(String uri, String updateContent(String content)) {
+    assert(_analysisContext == null);
+    var path = FULL_URI_MAP[uri];
+    assert(path != null);
+    path = provider.convertPath(path);
+    final content = provider.getFile(path).readAsStringSync();
+    final newContent = updateContent(content);
+    provider.updateFile(path, newContent);
+  }
+
+  /// Compute the bytes of the linked bundle associated with this SDK.
+  List<int> _computeLinkedBundleBytes() {
+    final librarySources =
+        sdkLibraries.map((library) => mapDartUri(library.shortName)).toList();
+    return new SummaryBuilder(
+            librarySources, context, context.analysisOptions.strongMode)
+        .build();
+  }
 }
 
 class _MockSdkLibrary implements SdkLibrary {
+  @override
   final String shortName;
+  @override
   final String path;
   final String content;
+  final Map<String, String> parts;
 
-  const _MockSdkLibrary(this.shortName, this.path, this.content);
+  const _MockSdkLibrary(this.shortName, this.path, this.content,
+      [this.parts = const <String, String>{}]);
 
   @override
-  String get category => throw unimplemented;
+  String get category => throw new UnimplementedError();
 
   @override
-  bool get isDart2JsLibrary => throw unimplemented;
+  bool get isDart2JsLibrary => throw new UnimplementedError();
 
   @override
-  bool get isDocumented => throw unimplemented;
+  bool get isDocumented => throw new UnimplementedError();
 
   @override
-  bool get isImplementation => throw unimplemented;
+  bool get isImplementation => throw new UnimplementedError();
 
   @override
-  bool get isInternal => throw unimplemented;
+  bool get isInternal => shortName.startsWith('dart:_');
 
   @override
-  bool get isShared => throw unimplemented;
+  bool get isShared => throw new UnimplementedError();
 
   @override
-  bool get isVmLibrary => throw unimplemented;
-
-  UnimplementedError get unimplemented => new UnimplementedError();
+  bool get isVmLibrary => throw new UnimplementedError();
 }
 
-/**
- * An [AnalysisContextImpl] that only contains sources for a Dart SDK.
- */
+/// An [AnalysisContextImpl] that only contains sources for a Dart SDK.
 class _SdkAnalysisContext extends AnalysisContextImpl {
   final DartSdk sdk;
 
diff --git a/server_plugin/test/test_all.dart b/server_plugin/test/test_all.dart
index ad9b4fe..bf2a629 100644
--- a/server_plugin/test/test_all.dart
+++ b/server_plugin/test/test_all.dart
@@ -1,14 +1,10 @@
-library angular2.src.analysis.analyzer_plugin.src;
-
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
 import 'analysis_test.dart' as analysis_test;
 import 'completion_contributor_test.dart' as completion_contributor_test;
 
-/**
- * Utility for manually running all tests.
- */
-main() {
+/// Utility for manually running all tests.
+void main() {
   defineReflectiveSuite(() {
     analysis_test.main();
     completion_contributor_test.main();