Temp staging of content child support
diff --git a/analyzer_plugin/lib/src/directive_extraction.dart b/analyzer_plugin/lib/src/directive_extraction.dart
index 9ba4a5c..16f8a83 100644
--- a/analyzer_plugin/lib/src/directive_extraction.dart
+++ b/analyzer_plugin/lib/src/directive_extraction.dart
@@ -83,6 +83,9 @@
         _parseMemberInputsAndOutputs(
             classDeclaration, inputElements, outputElements);
       }
+      final contentChilds = <ContentChildField>[];
+      final contentChildrens = <ContentChildField>[];
+      _parseContentChilds(classDeclaration, contentChilds, contentChildrens);
       List<ElementNameSelector> elementTags = <ElementNameSelector>[];
       selector.recordElementNameSelectors(elementTags);
       if (isComponent) {
@@ -91,7 +94,9 @@
             inputs: inputElements,
             outputs: outputElements,
             selector: selector,
-            elementTags: elementTags);
+            elementTags: elementTags,
+            contentChildFields: contentChilds,
+            contentChildrenFields: contentChildrens);
       }
       if (isDirective) {
         return new Directive(_currentClassElement,
@@ -99,7 +104,9 @@
             inputs: inputElements,
             outputs: outputElements,
             selector: selector,
-            elementTags: elementTags);
+            elementTags: elementTags,
+            contentChildFields: contentChilds,
+            contentChildrenFields: contentChildrens);
       }
     }
     return null;
@@ -371,6 +378,48 @@
     }
   }
 
+  /**
+   * 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.
+   */
+  _parseContentChilds(
+      ast.ClassDeclaration node,
+      List<ContentChildField> contentChilds,
+      List<ContentChildField> contentChildrens) {
+    for (ast.ClassMember member in node.members) {
+      for (ast.Annotation 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.length == 0) {
+          // no need to report an error, dart does that already
+          continue;
+        }
+
+        final offset = annotationArgs[0].offset;
+        final length = annotationArgs[0].length;
+
+        String name;
+        if (member is ast.FieldDeclaration) {
+          name = member.fields.variables[0].name.toString();
+        } else if (member is ast.MethodDeclaration) {
+          name = member.name.toString();
+        }
+        targetList
+            .add(new ContentChildField(name, new SourceRange(offset, length)));
+      }
+    }
+  }
+
   Selector _parseSelector(ast.Annotation node) {
     // Find the "selector" argument.
     ast.Expression expression = getNamedArgument(node, 'selector');
diff --git a/analyzer_plugin/lib/src/directive_linking.dart b/analyzer_plugin/lib/src/directive_linking.dart
index 95b6def..67453a9 100644
--- a/analyzer_plugin/lib/src/directive_linking.dart
+++ b/analyzer_plugin/lib/src/directive_linking.dart
@@ -177,6 +177,14 @@
           }
         }
       }
+
+      for (final childField in directive.contentChildFields) {
+        final member =
+            directive.classElement.lookUpSetter(childField.fieldName, library);
+        final type = member.variable.type;
+        final annotation = member.metadata.singleWhere(
+            (annotation) => annotation.element.name == "ContentChild");
+      }
     }
   }
 
diff --git a/analyzer_plugin/lib/src/model.dart b/analyzer_plugin/lib/src/model.dart
index 64f4b6c..b3d1ec0 100644
--- a/analyzer_plugin/lib/src/model.dart
+++ b/analyzer_plugin/lib/src/model.dart
@@ -27,12 +27,25 @@
   final Selector selector;
   final List<ElementNameSelector> elementTags;
 
+  /**
+   * 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 List<AbstractQueriedChildType> contentChilds = [];
+  final List<AbstractQueriedChildType> contentChildren = [];
+
   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.
@@ -112,6 +125,29 @@
   String toString() => name;
 }
 
+abstract class AbstractQueriedChildType {}
+
+class TemplateRefQueriedChildType extends AbstractQueriedChildType {}
+
+class ElementRefQueriedChildType extends AbstractQueriedChildType {}
+
+class LetBoundQueriedChildType extends AbstractQueriedChildType {
+  final String letBoundName;
+  LetBoundQueriedChildType(this.letBoundName);
+}
+
+class DirectiveQueriedChildType extends AbstractQueriedChildType {
+  final AbstractDirective directive;
+  DirectiveQueriedChildType(this.directive);
+}
+
+class ContentChildField {
+  final String fieldName;
+  final SourceRange range;
+
+  ContentChildField(this.fieldName, this.range);
+}
+
 /**
  * The model of an Angular component.
  */
@@ -129,14 +165,18 @@
       List<OutputElement> outputs,
       Selector selector,
       List<ElementNameSelector> elementTags,
-      List<NgContent> ngContents})
+      List<NgContent> ngContents,
+      List<ContentChildField> contentChildFields,
+      List<ContentChildField> contentChildrenFields})
       : ngContents = ngContents ?? [],
         super(classElement,
             exportAs: exportAs,
             inputs: inputs,
             outputs: outputs,
             selector: selector,
-            elementTags: elementTags);
+            elementTags: elementTags,
+            contentChildFields: contentChildFields,
+            contentChildrenFields: contentChildrenFields);
 }
 
 /**
@@ -160,13 +200,17 @@
       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);
 }
 
 /**
diff --git a/analyzer_plugin/test/abstract_angular.dart b/analyzer_plugin/test/abstract_angular.dart
index d4d505e..6f55a71 100644
--- a/analyzer_plugin/test/abstract_angular.dart
+++ b/analyzer_plugin/test/abstract_angular.dart
@@ -236,6 +236,21 @@
   final String bindingPropertyName;
   const Output([this.bindingPropertyName]);
 }
+
+class ContentChild {
+    const ContentChild(dynamic /* Type | String */ _selector,
+              {dynamic read: null})
+          : super(_selector, descendants: true, first: true, read: read);
+}
+
+class ContentChildren {
+    const ContentChildren(dynamic /* Type | String */ _selector,
+              {dynamic read: null})
+          : super(_selector, descendants: true, first: true, read: read);
+}
+
+class QueryList<T> extends Iterable<T> {}
+
 ''');
     newSource(
         '/angular2/src/core/async.dart',
diff --git a/analyzer_plugin/test/angular_driver_test.dart b/analyzer_plugin/test/angular_driver_test.dart
index afe4f73..9a5c08f 100644
--- a/analyzer_plugin/test/angular_driver_test.dart
+++ b/analyzer_plugin/test/angular_driver_test.dart
@@ -1340,6 +1340,124 @@
         code,
         "@Output()");
   }
+
+  Future test_hasViewChildDirective() async {
+    final code = r'''
+import '/angular2/angular2.dart';
+
+@Component(selector: 'my-component', template: '')
+class ComponentA {
+  @ContentChild(ContentChildComp)
+  ContentChildComp contentChild;
+}
+
+@Component(selector: 'foo', template: '')
+class ContentChildComp {}
+''';
+    Source source = newSource('/test.dart', code);
+    await getDirectives(source);
+    Component component = directives.first;
+    final childFields = component.contentChildFields;
+    expect(childFields, hasLength(1));
+    final child = childFields.first;
+    expect(child.fieldName, equals("contentChild"));
+    expect(child.range.offset, equals(code.indexOf("ContentChildComp)")));
+    expect(child.range.length, equals("ContentChildComp".length));
+    // validate
+    errorListener.assertNoErrors();
+  }
+
+  Future test_hasViewChildrenDirective() async {
+    final code = r'''
+import '/angular2/angular2.dart';
+
+@Component(selector: 'my-component', template: '')
+class ComponentA {
+  @ContentChildren(ContentChildComp)
+  QueryList<ContentChildComp> contentChildren;
+}
+
+@Component(selector: 'foo', template: '')
+class ContentChildComp {}
+''';
+    Source source = newSource('/test.dart', code);
+    await getDirectives(source);
+    Component component = directives.first;
+    final childrenFields = component.contentChildrenFields;
+    expect(childrenFields, hasLength(1));
+    final children = childrenFields.first;
+    expect(children.fieldName, equals("contentChildren"));
+    expect(children.range.offset, equals(code.indexOf("ContentChildComp)")));
+    expect(children.range.length, equals("ContentChildComp".length));
+    // validate
+    errorListener.assertNoErrors();
+  }
+
+  Future test_hasViewChildChildrenNoRangeNotRecorded() async {
+    final code = r'''
+import '/angular2/angular2.dart';
+
+@Component(selector: 'my-component', template: '')
+class ComponentA {
+  @ContentChildren()
+  QueryList<ContentChildComp> contentChildren;
+  @ContentChild()
+  ContentChildComp contentChild;
+}
+
+@Component(selector: 'foo', template: '')
+class ContentChildComp {}
+''';
+    Source source = newSource('/test.dart', code);
+    await getDirectives(source);
+    Component 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
+    ]);
+  }
+
+  Future test_hasViewChildChildrenSetter() async {
+    final code = r'''
+import '/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 {}
+''';
+    Source source = newSource('/test.dart', code);
+    await getDirectives(source);
+    Component component = directives.first;
+
+    final childFields = component.contentChildFields;
+    expect(childFields, hasLength(1));
+    final child = childFields.first;
+    expect(child.fieldName, equals("contentChild"));
+    expect(child.range.offset, equals(code.indexOf("ContentChildComp) // 1")));
+    expect(child.range.length, equals("ContentChildComp".length));
+
+    final childrenFields = component.contentChildrenFields;
+    expect(childrenFields, hasLength(1));
+    final children = childrenFields.first;
+    expect(children.fieldName, equals("contentChildren"));
+    expect(
+        children.range.offset, equals(code.indexOf("ContentChildComp) // 2")));
+    expect(children.range.length, equals("ContentChildComp".length));
+
+    errorListener.assertNoErrors();
+  }
 }
 
 @reflectiveTest
@@ -1741,6 +1859,124 @@
       }
     }
   }
+
+  Future test_hasViewChildDirective() async {
+    final code = r'''
+import '/angular2/angular2.dart';
+
+@Component(selector: 'my-component', template: '')
+class ComponentA {
+  @ContentChild(ContentChildComp)
+  ContentChildComp contentChild;
+}
+
+@Component(selector: 'foo', template: '')
+class ContentChildComp {}
+''';
+    Source source = newSource('/test.dart', code);
+    await getViews(source);
+    Component component = directives.first;
+    final childs = component.contentChilds;
+    expect(childs, hasLength(1));
+    expect(childs.first, new isInstanceOf<DirectiveQueriedChildType>());
+    final DirectiveQueriedChildType child = childs.first;
+
+    expect(child.directive, equals(directives[1]));
+    // validate
+    errorListener.assertNoErrors();
+  }
+
+  Future test_hasViewChildrenDirective() async {
+    final code = r'''
+import '/angular2/angular2.dart';
+
+@Component(selector: 'my-component', template: '')
+class ComponentA {
+  @ContentChildren(ContentChildComp)
+  QueryList<ContentChildComp> contentChildren;
+}
+
+@Component(selector: 'foo', template: '')
+class ContentChildComp {}
+''';
+    Source source = newSource('/test.dart', code);
+    await getViews(source);
+    Component component = directives.first;
+    final childrenFields = component.contentChildrenFields;
+    expect(childrenFields, hasLength(1));
+    final children = childrenFields.first;
+    expect(children.fieldName, equals("contentChildren"));
+    expect(children.range.offset, equals(code.indexOf("ContentChildComp)")));
+    expect(children.range.length, equals("ContentChildComp".length));
+    // validate
+    errorListener.assertNoErrors();
+  }
+
+  Future test_hasViewChildChildrenNoRangeNotRecorded() async {
+    final code = r'''
+import '/angular2/angular2.dart';
+
+@Component(selector: 'my-component', template: '')
+class ComponentA {
+  @ContentChildren()
+  QueryList<ContentChildComp> contentChildren;
+  @ContentChild()
+  ContentChildComp contentChild;
+}
+
+@Component(selector: 'foo', template: '')
+class ContentChildComp {}
+''';
+    Source source = newSource('/test.dart', code);
+    await getViews(source);
+    Component 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
+    ]);
+  }
+
+  Future test_hasViewChildChildrenSetter() async {
+    final code = r'''
+import '/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 {}
+''';
+    Source source = newSource('/test.dart', code);
+    await getViews(source);
+    Component component = directives.first;
+
+    final childFields = component.contentChildFields;
+    expect(childFields, hasLength(1));
+    final child = childFields.first;
+    expect(child.fieldName, equals("contentChild"));
+    expect(child.range.offset, equals(code.indexOf("ContentChildComp) // 1")));
+    expect(child.range.length, equals("ContentChildComp".length));
+
+    final childrenFields = component.contentChildrenFields;
+    expect(childrenFields, hasLength(1));
+    final children = childrenFields.first;
+    expect(children.fieldName, equals("contentChildren"));
+    expect(
+        children.range.offset, equals(code.indexOf("ContentChildComp) // 2")));
+    expect(children.range.length, equals("ContentChildComp".length));
+
+    errorListener.assertNoErrors();
+  }
 }
 
 @reflectiveTest
@@ -2315,6 +2551,24 @@
     expect(childView.component.ngContents, hasLength(1));
   }
 
+  Future test_constantExpressionTemplateVarDoesntCrash() async {
+    Source source = newSource(
+        '/test.dart',
+        r'''
+import '/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) {
     return templates.firstWhere(