Disable implicit casts (#130)

This will make it easier to migrate to null safety which also disables
implicit casts except from dynamic. Also fix the implicit casts from
dynamic since this package has a lot of dynamic code that is harder to
read with implicit casts.

- Add argument types to some signatures that should have had them from
  the start.
- Add explicit casts.
- Extract a few new local variables to make some of the casts only need
  to happen once.
- Use `Iterable.whereType` in a loop instead of a type check and
  `continue` in the loop body.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 53d7970..615a09f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,5 @@
+## 0.15.0-dev
+
 ## 0.14.0+4
 
 - Fix a bug parsing bad HTML where a 'button' end tag needs to close other
diff --git a/analysis_options.yaml b/analysis_options.yaml
index b8dcf93..20eba0c 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -1,5 +1,7 @@
 include: package:pedantic/analysis_options.yaml
 analyzer:
+  strong-mode:
+    implicit-casts: false
   errors:
     # https://github.com/dart-lang/linter/issues/1649
     prefer_collection_literals: ignore
diff --git a/lib/dom.dart b/lib/dom.dart
index 835d8da..85e02c4 100644
--- a/lib/dom.dart
+++ b/lib/dom.dart
@@ -24,7 +24,7 @@
 
 // TODO(jmesserly): this needs to be replaced by an AttributeMap for attributes
 // that exposes namespace info.
-class AttributeName implements Comparable {
+class AttributeName implements Comparable<Object> {
   /// The namespace prefix, e.g. `xlink`.
   final String prefix;
 
@@ -55,14 +55,15 @@
   }
 
   @override
-  int compareTo(other) {
+  int compareTo(dynamic other) {
     // Not sure about this sort order
     if (other is! AttributeName) return 1;
-    var cmp = (prefix ?? '').compareTo((other.prefix ?? ''));
+    final otherAttributeName = other as AttributeName;
+    var cmp = (prefix ?? '').compareTo((otherAttributeName.prefix ?? ''));
     if (cmp != 0) return cmp;
-    cmp = name.compareTo(other.name);
+    cmp = name.compareTo(otherAttributeName.name);
     if (cmp != 0) return cmp;
-    return namespace.compareTo(other.namespace);
+    return namespace.compareTo(otherAttributeName.namespace);
   }
 
   @override
@@ -141,7 +142,10 @@
   ///
   /// Returns null if this node either does not have a parent or its parent is
   /// not an element.
-  Element get parent => parentNode is Element ? parentNode : null;
+  Element get parent {
+    final parentNode = this.parentNode;
+    return parentNode is Element ? parentNode : null;
+  }
 
   // TODO(jmesserly): should move to Element.
   /// A map holding name, value pairs for attributes of the node.
@@ -299,7 +303,7 @@
     }
   }
 
-  Node _clone(Node shallowClone, bool deep) {
+  T _clone<T extends Node>(T shallowClone, bool deep) {
     if (deep) {
       for (var child in nodes) {
         shallowClone.append(child.clone(true));
@@ -442,7 +446,7 @@
 
   void appendData(String data) {
     if (_data is! StringBuffer) _data = StringBuffer(_data);
-    StringBuffer sb = _data;
+    final sb = _data as StringBuffer;
     sb.write(data);
   }
 
@@ -1054,18 +1058,16 @@
   @override
   Iterable<Element> getRange(int start, int end) =>
       _filtered.getRange(start, end);
-  // TODO(sigmund): this should be typed Element, but we currently run into a
-  // bug where ListMixin<E>.indexOf() expects Object as the argument.
   @override
   int indexOf(Object element, [int start = 0]) =>
-      _filtered.indexOf(element, start);
+      // Cast forced by ListMixin https://github.com/dart-lang/sdk/issues/31311
+      _filtered.indexOf(element as Element, start);
 
-  // TODO(sigmund): this should be typed Element, but we currently run into a
-  // bug where ListMixin<E>.lastIndexOf() expects Object as the argument.
   @override
   int lastIndexOf(Object element, [int start]) {
     start ??= length - 1;
-    return _filtered.lastIndexOf(element, start);
+    // Cast forced by ListMixin https://github.com/dart-lang/sdk/issues/31311
+    return _filtered.lastIndexOf(element as Element, start);
   }
 
   @override
diff --git a/lib/dom_parsing.dart b/lib/dom_parsing.dart
index f16018c..3c8774b 100644
--- a/lib/dom_parsing.dart
+++ b/lib/dom_parsing.dart
@@ -10,17 +10,17 @@
   void visit(Node node) {
     switch (node.nodeType) {
       case Node.ELEMENT_NODE:
-        return visitElement(node);
+        return visitElement(node as Element);
       case Node.TEXT_NODE:
-        return visitText(node);
+        return visitText(node as Text);
       case Node.COMMENT_NODE:
-        return visitComment(node);
+        return visitComment(node as Comment);
       case Node.DOCUMENT_FRAGMENT_NODE:
-        return visitDocumentFragment(node);
+        return visitDocumentFragment(node as DocumentFragment);
       case Node.DOCUMENT_NODE:
-        return visitDocument(node);
+        return visitDocument(node as Document);
       case Node.DOCUMENT_TYPE_NODE:
-        return visitDocumentType(node);
+        return visitDocumentType(node as DocumentType);
       default:
         throw UnsupportedError('DOM node type ${node.nodeType}');
     }
diff --git a/lib/parser.dart b/lib/parser.dart
index 46702ae..2a6c431 100644
--- a/lib/parser.dart
+++ b/lib/parser.dart
@@ -318,7 +318,7 @@
 
         // Note: avoid "is" test here, see http://dartbug.com/4795
         if (type == TokenKind.parseError) {
-          ParseErrorToken error = newToken;
+          final error = newToken as ParseErrorToken;
           parseError(error.span, error.data, error.messageParams);
           newToken = null;
         } else {
@@ -329,22 +329,24 @@
 
           switch (type) {
             case TokenKind.characters:
-              newToken = localPhase.processCharacters(newToken);
+              newToken =
+                  localPhase.processCharacters(newToken as CharactersToken);
               break;
             case TokenKind.spaceCharacters:
-              newToken = localPhase.processSpaceCharacters(newToken);
+              newToken = localPhase
+                  .processSpaceCharacters(newToken as SpaceCharactersToken);
               break;
             case TokenKind.startTag:
-              newToken = localPhase.processStartTag(newToken);
+              newToken = localPhase.processStartTag(newToken as StartTagToken);
               break;
             case TokenKind.endTag:
-              newToken = localPhase.processEndTag(newToken);
+              newToken = localPhase.processEndTag(newToken as EndTagToken);
               break;
             case TokenKind.comment:
-              newToken = localPhase.processComment(newToken);
+              newToken = localPhase.processComment(newToken as CommentToken);
               break;
             case TokenKind.doctype:
-              newToken = localPhase.processDoctype(newToken);
+              newToken = localPhase.processDoctype(newToken as DoctypeToken);
               break;
           }
         }
@@ -571,7 +573,7 @@
   void parseRCDataRawtext(Token token, String contentType) {
     assert(contentType == 'RAWTEXT' || contentType == 'RCDATA');
 
-    tree.insertElement(token);
+    tree.insertElement(token as StartTagToken);
 
     if (contentType == 'RAWTEXT') {
       tokenizer.state = tokenizer.rawtextState;
@@ -665,7 +667,7 @@
 }
 
 class InitialPhase extends Phase {
-  InitialPhase(parser) : super(parser);
+  InitialPhase(HtmlParser parser) : super(parser);
 
   @override
   Token processSpaceCharacters(SpaceCharactersToken token) {
@@ -824,7 +826,7 @@
 }
 
 class BeforeHtmlPhase extends Phase {
-  BeforeHtmlPhase(parser) : super(parser);
+  BeforeHtmlPhase(HtmlParser parser) : super(parser);
 
   // helper methods
   void insertHtmlElement() {
@@ -885,7 +887,7 @@
 }
 
 class BeforeHeadPhase extends Phase {
-  BeforeHeadPhase(parser) : super(parser);
+  BeforeHeadPhase(HtmlParser parser) : super(parser);
 
   @override
   Token processStartTag(StartTagToken token) {
@@ -959,7 +961,7 @@
 }
 
 class InHeadPhase extends Phase {
-  InHeadPhase(parser) : super(parser);
+  InHeadPhase(HtmlParser parser) : super(parser);
 
   @override
   Token processStartTag(StartTagToken token) {
@@ -1106,7 +1108,7 @@
 // class InHeadNoScriptPhase extends Phase {
 
 class AfterHeadPhase extends Phase {
-  AfterHeadPhase(parser) : super(parser);
+  AfterHeadPhase(HtmlParser parser) : super(parser);
 
   @override
   Token processStartTag(StartTagToken token) {
@@ -1182,7 +1184,7 @@
   void startTagFromHead(StartTagToken token) {
     parser.parseError(token.span, 'unexpected-start-tag-out-of-my-head',
         {'name': token.name});
-    tree.openElements.add(tree.headPointer);
+    tree.openElements.add(tree.headPointer as Element);
     parser._inHeadPhase.processStartTag(token);
     for (var node in tree.openElements.reversed) {
       if (node.localName == 'head') {
@@ -1225,7 +1227,7 @@
 
   // http://www.whatwg.org/specs/web-apps/current-work///parsing-main-inbody
   // the really-really-really-very crazy mode
-  InBodyPhase(parser) : super(parser);
+  InBodyPhase(HtmlParser parser) : super(parser);
 
   @override
   Token processStartTag(StartTagToken token) {
@@ -1501,7 +1503,7 @@
   }
 
   // helper
-  void addFormattingElement(token) {
+  void addFormattingElement(StartTagToken token) {
     tree.insertElement(token);
     var element = tree.openElements.last;
 
@@ -1509,7 +1511,7 @@
     for (Node node in tree.activeFormattingElements.reversed) {
       if (node == Marker) {
         break;
-      } else if (isMatchingFormattingElement(node, element)) {
+      } else if (isMatchingFormattingElement(node as Element, element)) {
         matchingElements.add(node);
       }
     }
@@ -1816,7 +1818,7 @@
     if (tree.formPointer != null) {
       return;
     }
-    var formAttrs = <dynamic, String>{};
+    var formAttrs = LinkedHashMap<dynamic, String>();
     var dataAction = token.data['action'];
     if (dataAction != null) {
       formAttrs['action'] = dataAction;
@@ -2113,7 +2115,7 @@
       // Step 2
       // Start of the adoption agency algorithm proper
       var afeIndex = tree.openElements.indexOf(formattingElement);
-      Node furthestBlock;
+      Element furthestBlock;
       for (var element in slice(tree.openElements, afeIndex)) {
         if (specialElements.contains(getElementNameTuple(element))) {
           furthestBlock = element;
@@ -2270,7 +2272,7 @@
 }
 
 class TextPhase extends Phase {
-  TextPhase(parser) : super(parser);
+  TextPhase(HtmlParser parser) : super(parser);
 
   // "Tried to process start tag %s in RCDATA/RAWTEXT mode"%token.name
   @override
@@ -2321,7 +2323,7 @@
 
 class InTablePhase extends Phase {
   // http://www.whatwg.org/specs/web-apps/current-work///in-table
-  InTablePhase(parser) : super(parser);
+  InTablePhase(HtmlParser parser) : super(parser);
 
   @override
   Token processStartTag(StartTagToken token) {
@@ -2622,7 +2624,7 @@
 
 class InCaptionPhase extends Phase {
   // http://www.whatwg.org/specs/web-apps/current-work///in-caption
-  InCaptionPhase(parser) : super(parser);
+  InCaptionPhase(HtmlParser parser) : super(parser);
 
   @override
   Token processStartTag(StartTagToken token) {
@@ -2744,7 +2746,7 @@
 
 class InColumnGroupPhase extends Phase {
   // http://www.whatwg.org/specs/web-apps/current-work///in-column
-  InColumnGroupPhase(parser) : super(parser);
+  InColumnGroupPhase(HtmlParser parser) : super(parser);
 
   @override
   Token processStartTag(StartTagToken token) {
@@ -2832,7 +2834,7 @@
 
 class InTableBodyPhase extends Phase {
   // http://www.whatwg.org/specs/web-apps/current-work///in-table0
-  InTableBodyPhase(parser) : super(parser);
+  InTableBodyPhase(HtmlParser parser) : super(parser);
 
   @override
   Token processStartTag(StartTagToken token) {
@@ -2925,7 +2927,7 @@
     return token;
   }
 
-  Token startTagTableOther(token) => endTagTable(token);
+  Token startTagTableOther(TagToken token) => endTagTable(token);
 
   Token startTagOther(StartTagToken token) {
     return parser._inTablePhase.processStartTag(token);
@@ -2971,7 +2973,7 @@
 
 class InRowPhase extends Phase {
   // http://www.whatwg.org/specs/web-apps/current-work///in-row
-  InRowPhase(parser) : super(parser);
+  InRowPhase(HtmlParser parser) : super(parser);
 
   @override
   Token processStartTag(StartTagToken token) {
@@ -3117,7 +3119,7 @@
 
 class InCellPhase extends Phase {
   // http://www.whatwg.org/specs/web-apps/current-work///in-cell
-  InCellPhase(parser) : super(parser);
+  InCellPhase(HtmlParser parser) : super(parser);
 
   @override
   Token processStartTag(StartTagToken token) {
@@ -3241,7 +3243,7 @@
 }
 
 class InSelectPhase extends Phase {
-  InSelectPhase(parser) : super(parser);
+  InSelectPhase(HtmlParser parser) : super(parser);
 
   @override
   Token processStartTag(StartTagToken token) {
@@ -3397,7 +3399,7 @@
 }
 
 class InSelectInTablePhase extends Phase {
-  InSelectInTablePhase(parser) : super(parser);
+  InSelectInTablePhase(HtmlParser parser) : super(parser);
 
   @override
   Token processStartTag(StartTagToken token) {
@@ -3523,7 +3525,7 @@
     'var'
   ];
 
-  InForeignContentPhase(parser) : super(parser);
+  InForeignContentPhase(HtmlParser parser) : super(parser);
 
   void adjustSVGTagNames(token) {
     final replacements = const {
@@ -3628,7 +3630,7 @@
       if (asciiUpper2Lower(node.localName) == token.name) {
         //XXX this isn't in the spec but it seems necessary
         if (parser.phase == parser._inTableTextPhase) {
-          InTableTextPhase inTableText = parser.phase;
+          final inTableText = parser.phase as InTableTextPhase;
           inTableText.flushCharacters();
           parser.phase = inTableText.originalPhase;
         }
@@ -3653,7 +3655,7 @@
 }
 
 class AfterBodyPhase extends Phase {
-  AfterBodyPhase(parser) : super(parser);
+  AfterBodyPhase(HtmlParser parser) : super(parser);
 
   @override
   Token processStartTag(StartTagToken token) {
@@ -3725,7 +3727,7 @@
 
 class InFramesetPhase extends Phase {
   // http://www.whatwg.org/specs/web-apps/current-work///in-frameset
-  InFramesetPhase(parser) : super(parser);
+  InFramesetPhase(HtmlParser parser) : super(parser);
 
   @override
   Token processStartTag(StartTagToken token) {
@@ -3818,7 +3820,7 @@
 
 class AfterFramesetPhase extends Phase {
   // http://www.whatwg.org/specs/web-apps/current-work///after3
-  AfterFramesetPhase(parser) : super(parser);
+  AfterFramesetPhase(HtmlParser parser) : super(parser);
 
   @override
   Token processStartTag(StartTagToken token) {
@@ -3875,7 +3877,7 @@
 }
 
 class AfterAfterBodyPhase extends Phase {
-  AfterAfterBodyPhase(parser) : super(parser);
+  AfterAfterBodyPhase(HtmlParser parser) : super(parser);
 
   @override
   Token processStartTag(StartTagToken token) {
@@ -3926,7 +3928,7 @@
 }
 
 class AfterAfterFramesetPhase extends Phase {
-  AfterAfterFramesetPhase(parser) : super(parser);
+  AfterAfterFramesetPhase(HtmlParser parser) : super(parser);
 
   @override
   Token processStartTag(StartTagToken token) {
diff --git a/lib/src/query_selector.dart b/lib/src/query_selector.dart
index 0c660b7..041c5d8 100644
--- a/lib/src/query_selector.dart
+++ b/lib/src/query_selector.dart
@@ -7,7 +7,7 @@
 import 'package:html/dom.dart';
 import 'package:html/src/constants.dart' show isWhitespaceCC;
 
-bool matches(Node node, String selector) =>
+bool matches(Element node, String selector) =>
     SelectorEvaluator().matches(node, _parseSelectorList(selector));
 
 Element querySelector(Node node, String selector) =>
@@ -40,10 +40,9 @@
   }
 
   Element querySelector(Node root, SelectorGroup selector) {
-    for (var node in root.nodes) {
-      if (node is! Element) continue;
-      if (matches(node, selector)) return node;
-      var result = querySelector(node, selector);
+    for (var element in root.nodes.whereType<Element>()) {
+      if (matches(element, selector)) return element;
+      var result = querySelector(element, selector);
       if (result != null) return result;
     }
     return null;
@@ -51,10 +50,9 @@
 
   void querySelectorAll(
       Node root, SelectorGroup selector, List<Element> results) {
-    for (var node in root.nodes) {
-      if (node is! Element) continue;
-      if (matches(node, selector)) results.add(node);
-      querySelectorAll(node, selector, results);
+    for (var element in root.nodes.whereType<Element>()) {
+      if (matches(element, selector)) results.add(element);
+      querySelectorAll(element, selector, results);
     }
   }
 
@@ -71,13 +69,13 @@
     int combinator;
     for (var s in selector.simpleSelectorSequences.reversed) {
       if (combinator == null) {
-        result = s.simpleSelector.visit(this);
+        result = s.simpleSelector.visit(this) as bool;
       } else if (combinator == TokenKind.COMBINATOR_DESCENDANT) {
         // descendant combinator
         // http://dev.w3.org/csswg/selectors-4/#descendant-combinators
         do {
           _element = _element.parent;
-        } while (_element != null && !s.simpleSelector.visit(this));
+        } while (_element != null && !(s.simpleSelector.visit(this) as bool));
 
         if (_element == null) result = false;
       } else if (combinator == TokenKind.COMBINATOR_TILDE) {
@@ -85,7 +83,7 @@
         // http://dev.w3.org/csswg/selectors-4/#general-sibling-combinators
         do {
           _element = _element.previousElementSibling;
-        } while (_element != null && !s.simpleSelector.visit(this));
+        } while (_element != null && !(s.simpleSelector.visit(this) as bool));
 
         if (_element == null) result = false;
       }
@@ -216,10 +214,10 @@
         // TODO(jmesserly): support An+B syntax too.
         var exprs = selector.expression.expressions;
         if (exprs.length == 1 && exprs[0] is LiteralTerm) {
-          LiteralTerm literal = exprs[0];
+          final literal = exprs[0] as LiteralTerm;
           var parent = _element.parentNode;
           return parent != null &&
-              literal.value > 0 &&
+              (literal.value as num) > 0 &&
               parent.nodes.indexOf(_element) == literal.value;
         }
         break;
@@ -248,7 +246,7 @@
   @override
   bool visitNamespaceSelector(NamespaceSelector selector) {
     // Match element tag name
-    if (!selector.nameAsSimpleSelector.visit(this)) return false;
+    if (!(selector.nameAsSimpleSelector.visit(this) as bool)) return false;
 
     if (selector.isNamespaceWildcard) return true;
 
@@ -273,7 +271,7 @@
   // http://dev.w3.org/csswg/selectors-4/#negation
   @override
   bool visitNegationSelector(NegationSelector selector) =>
-      !selector.negationArg.visit(this);
+      !(selector.negationArg.visit(this) as bool);
 
   @override
   bool visitAttributeSelector(AttributeSelector selector) {
diff --git a/lib/src/token.dart b/lib/src/token.dart
index e92ec8e..24c531e 100644
--- a/lib/src/token.dart
+++ b/lib/src/token.dart
@@ -63,9 +63,7 @@
     return _string;
   }
 
-  StringToken(string)
-      : _string = string,
-        _buffer = string == null ? StringBuffer() : null;
+  StringToken(this._string) : _buffer = _string == null ? StringBuffer() : null;
 
   StringToken add(String data) {
     _buffer.write(data);
diff --git a/lib/src/tokenizer.dart b/lib/src/tokenizer.dart
index b26e8d9..26b7115 100644
--- a/lib/src/tokenizer.dart
+++ b/lib/src/tokenizer.dart
@@ -51,9 +51,7 @@
   Token currentToken;
 
   /// Holds a reference to the method to be invoked for the next parser state.
-  // TODO(jmesserly): the type should be "Predicate" but a dart2js checked mode
-  // bug prevents us from doing that. See http://dartbug.com/12465
-  Function state;
+  bool Function() state;
 
   final StringBuffer _buffer = StringBuffer();
 
@@ -79,9 +77,9 @@
     reset();
   }
 
-  TagToken get currentTagToken => currentToken;
-  DoctypeToken get currentDoctypeToken => currentToken;
-  StringToken get currentStringToken => currentToken;
+  TagToken get currentTagToken => currentToken as TagToken;
+  DoctypeToken get currentDoctypeToken => currentToken as DoctypeToken;
+  StringToken get currentStringToken => currentToken as StringToken;
 
   Token _current;
   @override
diff --git a/lib/src/treebuilder.dart b/lib/src/treebuilder.dart
index e096214..b60d4a7 100644
--- a/lib/src/treebuilder.dart
+++ b/lib/src/treebuilder.dart
@@ -13,7 +13,7 @@
 // The scope markers are inserted when entering object elements,
 // marquees, table cells, and table captions, and are used to prevent formatting
 // from "leaking" into tables, object elements, and marquees.
-const Node Marker = null;
+const Element Marker = null;
 
 // TODO(jmesserly): this should extend ListBase<Element>, but my simple attempt
 // didn't work.
@@ -230,7 +230,7 @@
     return null;
   }
 
-  void insertRoot(Token token) {
+  void insertRoot(StartTagToken token) {
     var element = createElement(token);
     openElements.add(element);
     document.nodes.add(element);
@@ -273,7 +273,7 @@
     return element;
   }
 
-  Element insertElementTable(token) {
+  Element insertElementTable(StartTagToken token) {
     /// Create an element and insert it into the tree
     var element = createElement(token);
     if (!tableInsertModeElements.contains(openElements.last.localName)) {
@@ -307,7 +307,7 @@
       // We should be in the InTable mode. This means we want to do
       // special magic element rearranging
       var nodePos = getTableMisnestedNodePosition();
-      _insertText(nodePos[0], data, span, nodePos[1]);
+      _insertText(nodePos[0], data, span, nodePos[1] as Element);
     }
   }
 
@@ -318,7 +318,7 @@
     var nodes = parent.nodes;
     if (refNode == null) {
       if (nodes.isNotEmpty && nodes.last is Text) {
-        Text last = nodes.last;
+        final last = nodes.last as Text;
         last.appendData(data);
 
         if (span != null) {
@@ -331,7 +331,7 @@
     } else {
       var index = nodes.indexOf(refNode);
       if (index > 0 && nodes[index - 1] is Text) {
-        Text last = nodes[index - 1];
+        final last = nodes[index - 1] as Text;
         last.appendData(data);
       } else {
         nodes.insert(index, Text(data)..sourceSpan = span);
@@ -345,7 +345,7 @@
     // The foster parent element is the one which comes before the most
     // recently opened table element
     // XXX - this is really inelegant
-    Node lastTable;
+    Element lastTable;
     Node fosterParent;
     Node insertBefore;
     for (var elm in openElements.reversed) {
diff --git a/lib/src/utils.dart b/lib/src/utils.dart
index 57124c8..3eadc2e 100644
--- a/lib/src/utils.dart
+++ b/lib/src/utils.dart
@@ -79,7 +79,7 @@
           result.write(padWithZeros(number, numberSize));
           break;
         case 'x':
-          var number = value.toRadixString(16);
+          var number = (value as int).toRadixString(16);
           result.write(padWithZeros(number, numberSize));
           break;
         default:
diff --git a/pubspec.yaml b/pubspec.yaml
index e7aa320..2576b05 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,8 +1,7 @@
 name: html
-version: 0.14.0+4
+version: 0.15.0-dev
 
 description: APIs for parsing and manipulating HTML content outside the browser.
-author: Dart Team <misc@dartlang.org>
 homepage: https://github.com/dart-lang/html
 
 environment:
diff --git a/test/parser_feature_test.dart b/test/parser_feature_test.dart
index 536f98f..3ed0031 100644
--- a/test/parser_feature_test.dart
+++ b/test/parser_feature_test.dart
@@ -13,7 +13,7 @@
   _testElementSpans();
   test('doctype is cloneable', () {
     var doc = parse('<!doctype HTML>');
-    DocumentType doctype = doc.nodes[0];
+    final doctype = doc.nodes[0] as DocumentType;
     expect(doctype.clone(false).toString(), '<!DOCTYPE html>');
   });
 
@@ -86,7 +86,7 @@
     var textContent = '\n  hello {{name}}';
     var html = '<body><div>$textContent</div>';
     var doc = parse(html, generateSpans: true);
-    Text text = doc.body.nodes[0].nodes[0];
+    final text = doc.body.nodes[0].nodes[0] as Text;
     expect(text, const TypeMatcher<Text>());
     expect(text.data, textContent);
     expect(text.sourceSpan.start.offset, html.indexOf(textContent));
@@ -186,35 +186,35 @@
     });
 
     test('escaping Text node in <script>', () {
-      Element e = parseFragment('<script>a && b</script>').firstChild;
+      final e = parseFragment('<script>a && b</script>').firstChild as Element;
       expect(e.outerHtml, '<script>a && b</script>');
     });
 
     test('escaping Text node in <span>', () {
-      Element e = parseFragment('<span>a && b</span>').firstChild;
+      final e = parseFragment('<span>a && b</span>').firstChild as Element;
       expect(e.outerHtml, '<span>a &amp;&amp; b</span>');
     });
 
     test('Escaping attributes', () {
-      Element e = parseFragment('<div class="a<b>">').firstChild;
+      var e = parseFragment('<div class="a<b>">').firstChild as Element;
       expect(e.outerHtml, '<div class="a<b>"></div>');
-      e = parseFragment('<div class=\'a"b\'>').firstChild;
+      e = parseFragment('<div class=\'a"b\'>').firstChild as Element;
       expect(e.outerHtml, '<div class="a&quot;b"></div>');
     });
 
     test('Escaping non-breaking space', () {
       var text = '<span>foO\u00A0bar</span>';
       expect(text.codeUnitAt(text.indexOf('O') + 1), 0xA0);
-      Element e = parseFragment(text).firstChild;
+      var e = parseFragment(text).firstChild as Element;
       expect(e.outerHtml, '<span>foO&nbsp;bar</span>');
     });
 
     test('Newline after <pre>', () {
-      Element e = parseFragment('<pre>\n\nsome text</span>').firstChild;
+      var e = parseFragment('<pre>\n\nsome text</span>').firstChild as Element;
       expect((e.firstChild as Text).data, '\nsome text');
       expect(e.outerHtml, '<pre>\n\nsome text</pre>');
 
-      e = parseFragment('<pre>\nsome text</span>').firstChild;
+      e = parseFragment('<pre>\nsome text</span>').firstChild as Element;
       expect((e.firstChild as Text).data, 'some text');
       expect(e.outerHtml, '<pre>some text</pre>');
     });
@@ -278,7 +278,7 @@
   test('Text.text', () {
     var doc = parseFragment('<div>foo<div>bar</div>baz</div>');
     var e = doc.firstChild;
-    Text text = e.firstChild;
+    final text = e.firstChild as Text;
     expect(text.data, 'foo');
     expect(text.text, 'foo');
 
diff --git a/test/parser_test.dart b/test/parser_test.dart
index 21c54d2..232a50f 100644
--- a/test/parser_test.dart
+++ b/test/parser_test.dart
@@ -79,12 +79,10 @@
     group(testName, () {
       for (var testData in tests) {
         var input = testData['data'];
-        var errors = testData['errors'];
+        final errorString = testData['errors'];
+        final errors = errorString?.split('\n');
         var innerHTML = testData['document-fragment'];
         var expected = testData['document'];
-        if (errors != null) {
-          errors = errors.split('\n');
-        }
 
         for (var treeCtor in treeTypes.values) {
           for (var namespaceHTMLElements in const [false, true]) {
diff --git a/test/selectors/level1_lib.dart b/test/selectors/level1_lib.dart
index f97dafc..721eca6 100644
--- a/test/selectors/level1_lib.dart
+++ b/test/selectors/level1_lib.dart
@@ -28,8 +28,7 @@
   anyNS.id = 'any-namespace';
   noNS.id = 'no-namespace';
 
-  var div;
-  div = [
+  var div = [
     doc.createElement('div'),
     doc.createElementNS('http://www.w3.org/1999/xhtml', 'div'),
     doc.createElementNS('', 'div'),
@@ -70,7 +69,7 @@
 /*
  * Check that the querySelector and querySelectorAll methods exist on the given Node
  */
-void interfaceCheck(type, obj) {
+void interfaceCheck(String type, obj) {
   runTest(() {
     var q = obj.querySelector is Function;
     assertTrue(q, type + ' supports querySelector.');
@@ -94,7 +93,7 @@
  * Verify that the NodeList returned by querySelectorAll is static and and that a new list is created after
  * each call. A static list should not be affected by subsequent changes to the DOM.
  */
-void verifyStaticList(type, root) {
+void verifyStaticList(String type, root) {
   var pre, post, preLength;
 
   runTest(() {
@@ -119,7 +118,7 @@
  * Verify handling of special values for the selector parameter, including stringification of
  * null and undefined, and the handling of the empty string.
  */
-void runSpecialSelectorTests(type, root) {
+void runSpecialSelectorTests(String type, root) {
   // Dart note: changed these tests because we don't have auto conversion to
   // String like JavaScript does.
   runTest(() {
@@ -170,7 +169,7 @@
     // 7
     var result = root.querySelectorAll('*');
     var i = 0;
-    traverse(root, (elem) {
+    traverse(root as Node, (elem) {
       if (!identical(elem, root)) {
         assertEquals(
             elem, result[i], 'The result in index $i should be in tree order.');
@@ -197,7 +196,7 @@
 void runValidSelectorTest(String type, root,
     List<Map<String, dynamic>> selectors, testType, docType) {
   var nodeType = '';
-  switch (root.nodeType) {
+  switch ((root as Node).nodeType) {
     case Node.DOCUMENT_NODE:
       nodeType = 'document';
       break;
@@ -213,20 +212,21 @@
 
   for (var i = 0; i < selectors.length; i++) {
     var s = selectors[i];
-    var n = s['name'];
+    var n = s['name'] as String;
     var skip = _getSkip(n);
-    var q = s['selector'];
-    var e = s['expect'];
+    var q = s['selector'] as String;
+    var e = s['expect'] as List;
 
     if ((s['exclude'] is! List ||
             (s['exclude'].indexOf(nodeType) == -1 &&
                 s['exclude'].indexOf(docType) == -1)) &&
         (s['testType'] & testType != 0)) {
       //console.log("Running tests " + nodeType + ": " + s["testType"] + "&" + testType + "=" + (s["testType"] & testType) + ": " + JSON.stringify(s))
-      var foundall, found;
+      List<Element> foundall;
+      Element found;
 
       runTest(() {
-        foundall = root.querySelectorAll(q);
+        foundall = root.querySelectorAll(q) as List<Element>;
         assertNotEquals(foundall, null, 'The method should not return null.');
         assertEquals(foundall.length, e.length,
             'The method should return the expected number of matches.');
@@ -242,7 +242,7 @@
       }, type + '.querySelectorAll: ' + n + ': ' + q, skip: skip);
 
       runTest(() {
-        found = root.querySelector(q);
+        found = root.querySelector(q) as Element;
 
         if (e.isNotEmpty) {
           assertNotEquals(found, null, 'The method should return a match.');
@@ -269,8 +269,8 @@
 void runInvalidSelectorTest(String type, root, List selectors) {
   for (var i = 0; i < selectors.length; i++) {
     var s = selectors[i];
-    var n = s['name'];
-    var q = s['selector'];
+    var n = s['name'] as String;
+    var q = s['selector'] as String;
 
     // Dart note: FormatException seems a reasonable mapping of SyntaxError
     runTest(() {
@@ -298,7 +298,7 @@
   }
 }
 
-void runTest(Function body, String name, {String skip}) =>
+void runTest(dynamic Function() body, String name, {String skip}) =>
     unittest.test(name, body, skip: skip);
 
 void assertTrue(bool value, String reason) =>
diff --git a/test/support.dart b/test/support.dart
index afcf952..0199535 100644
--- a/test/support.dart
+++ b/test/support.dart
@@ -29,7 +29,7 @@
 
 // TODO(jmesserly): make this class simpler. We could probably split on
 // "\n#" instead of newline and remove a lot of code.
-class TestData extends IterableBase<Map> {
+class TestData extends IterableBase<Map<String, String>> {
   final String _text;
   final String newTestHeading;
 
@@ -40,12 +40,12 @@
   // Note: in Python this was a generator, but since we can't do that in Dart,
   // it's easier to convert it into an upfront computation.
   @override
-  Iterator<Map> get iterator => _getData().iterator;
+  Iterator<Map<String, String>> get iterator => _getData().iterator;
 
-  List<Map> _getData() {
+  List<Map<String, String>> _getData() {
     var data = <String, String>{};
     String key;
-    var result = <Map>[];
+    var result = <Map<String, String>>[];
     var lines = _text.split('\n');
     // Remove trailing newline to match Python
     if (lines.last == '') {
@@ -79,7 +79,7 @@
     return line.startsWith('#') ? line.substring(1).trim() : null;
   }
 
-  static Map normaliseOutput(Map data) {
+  static Map<String, String> normaliseOutput(Map<String, String> data) {
     // Remove trailing newlines
     data.forEach((key, value) {
       if (value.endsWith('\n')) {
@@ -91,7 +91,7 @@
 }
 
 /// Serialize the [document] into the html5 test data format.
-String testSerializer(document) {
+String testSerializer(Node document) {
   return (TestSerializer()..visit(document)).toString();
 }
 
@@ -143,7 +143,7 @@
   @override
   void visitDocument(node) => _visitDocumentOrFragment(node);
 
-  void _visitDocumentOrFragment(node) {
+  void _visitDocumentOrFragment(Node node) {
     indent += 1;
     for (var child in node.nodes) {
       visit(child);
@@ -161,12 +161,16 @@
     _str.write(node);
     if (node.attributes.isNotEmpty) {
       indent += 2;
-      var keys = List.from(node.attributes.keys);
-      keys.sort((x, y) => x.compareTo(y));
+      var keys = node.attributes.keys.toList();
+      keys.sort((x, y) {
+        if (x is String) return x.compareTo(y as String);
+        if (x is AttributeName) return x.compareTo(y as AttributeName);
+        throw StateError('Cannot sort');
+      });
       for (var key in keys) {
         var v = node.attributes[key];
         if (key is AttributeName) {
-          AttributeName attr = key;
+          final attr = key as AttributeName;
           key = '${attr.prefix} ${attr.name}';
         }
         _newline();
diff --git a/test/tokenizer_test.dart b/test/tokenizer_test.dart
index 62d5f8a..41ae4d5 100644
--- a/test/tokenizer_test.dart
+++ b/test/tokenizer_test.dart
@@ -35,7 +35,8 @@
     // Note: we can't get a closure of the state method. However, we can
     // create a new closure to invoke it via mirrors.
     var mtok = reflect(tokenizer);
-    tokenizer.state = () => mtok.invoke(Symbol(_state), const []).reflectee;
+    tokenizer.state =
+        () => mtok.invoke(Symbol(_state), const []).reflectee as bool;
 
     if (_lastStartTag != null) {
       tokenizer.currentToken = StartTagToken(_lastStartTag);
@@ -45,25 +46,25 @@
       var token = tokenizer.current;
       switch (token.kind) {
         case TokenKind.characters:
-          processCharacters(token);
+          processCharacters(token as CharactersToken);
           break;
         case TokenKind.spaceCharacters:
-          processSpaceCharacters(token);
+          processSpaceCharacters(token as SpaceCharactersToken);
           break;
         case TokenKind.startTag:
-          processStartTag(token);
+          processStartTag(token as StartTagToken);
           break;
         case TokenKind.endTag:
-          processEndTag(token);
+          processEndTag(token as EndTagToken);
           break;
         case TokenKind.comment:
-          processComment(token);
+          processComment(token as CommentToken);
           break;
         case TokenKind.doctype:
-          processDoctype(token);
+          processDoctype(token as DoctypeToken);
           break;
         case TokenKind.parseError:
-          processParseError(token);
+          processParseError(token as ParseErrorToken);
           break;
       }
     }
@@ -186,20 +187,22 @@
   }
 }
 
-void runTokenizerTest(Map testInfo) {
+void runTokenizerTest(Map<String, dynamic> testInfo) {
   // XXX - move this out into the setup function
   // concatenate all consecutive character tokens into a single token
   if (testInfo.containsKey('doubleEscaped')) {
     testInfo = unescape(testInfo);
   }
 
-  var expected = concatenateCharacterTokens(testInfo['output']);
+  var expected = concatenateCharacterTokens(testInfo['output'] as List);
   if (!testInfo.containsKey('lastStartTag')) {
     testInfo['lastStartTag'] = null;
   }
-  var parser = TokenizerTestParser(testInfo['initialState'],
-      testInfo['lastStartTag'], testInfo['generateSpans'] ?? false);
-  var tokens = parser.parse(testInfo['input']);
+  var parser = TokenizerTestParser(
+      testInfo['initialState'] as String,
+      testInfo['lastStartTag'] as String,
+      testInfo['generateSpans'] as bool /*?*/ ?? false);
+  var tokens = parser.parse(testInfo['input'] as String);
   tokens = concatenateCharacterTokens(tokens);
   var received = normalizeTokens(tokens);
   var errorMsg = [
@@ -212,12 +215,12 @@
     '\nreceived:',
     tokens
   ].map((s) => '$s').join('\n');
-  var ignoreErrorOrder = testInfo['ignoreErrorOrder'] ?? false;
+  var ignoreErrorOrder = testInfo['ignoreErrorOrder'] as bool /*?*/ ?? false;
 
   expectTokensMatch(expected, received, ignoreErrorOrder, true, errorMsg);
 }
 
-Map unescape(Map testInfo) {
+Map<String, dynamic> unescape(Map<String, dynamic> testInfo) {
   // TODO(sigmundch,jmesserly): we currently use jsonDecode to unescape the
   // unicode characters in the string, we should use a decoding that works with
   // any control characters.
@@ -229,7 +232,7 @@
       continue;
     } else {
       token[1] = decode(token[1]);
-      if (token.length > 2) {
+      if ((token as List).length > 2) {
         for (var pair in token[2]) {
           var key = pair[0];
           var value = pair[1];
@@ -260,17 +263,17 @@
     var text = File(path).readAsStringSync();
     var tests = jsonDecode(text);
     var testName = pathos.basenameWithoutExtension(path);
-    var testList = tests['tests'];
+    var testList = tests['tests'] as List;
     if (testList == null) continue;
 
     group(testName, () {
       for (var index = 0; index < testList.length; index++) {
-        final testInfo = testList[index];
+        final testInfo = testList[index] as Map<String, dynamic>;
 
         testInfo.putIfAbsent('initialStates', () => ['Data state']);
         for (var initialState in testInfo['initialStates']) {
           test(testInfo['description'], () {
-            testInfo['initialState'] = camelCase(initialState);
+            testInfo['initialState'] = camelCase(initialState as String);
             runTokenizerTest(testInfo);
           });
         }