Merge branch 'master' into null_safety-migration
diff --git a/.travis.yml b/.travis.yml
index 497fe68..9865545 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,26 +2,17 @@
 
 dart:
   - dev
-  - 2.8.1
-
-dart_task:
-  - test: -p vm
-  - test: -p chrome
 
 matrix:
   include:
-    - dart: dev
-      dart_task: dartfmt
-    - dart: dev
-      dart_task:
+    - script: pub run test -p vm,chrome
+    - dart_task: dartfmt
+    - dart_task:
         dartanalyzer: --fatal-warnings --fatal-infos .
-    - dart: 2.8.1
-      dart_task:
-        dartanalyzer: --fatal-warnings .
 
 # Only building master means that we don't run two builds for each pull request.
 branches:
-  only: [master]
+  only: [master, null_safety]
 
 cache:
  directories:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e294c41..2269d1f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,4 @@
-## 0.15.0-dev
+## 0.15.0-nullsafety-dev
 
 - Drop `HtmlParser.lastPhase` and `HtmlParser.beforeRcDataPhase`. These fields
   never had a value other than `null`.
diff --git a/lib/dom.dart b/lib/dom.dart
index 1950ffe..2f2496d 100644
--- a/lib/dom.dart
+++ b/lib/dom.dart
@@ -26,7 +26,7 @@
 // that exposes namespace info.
 class AttributeName implements Comparable<Object> {
   /// The namespace prefix, e.g. `xlink`.
-  final String prefix;
+  final String? prefix;
 
   /// The attribute name, e.g. `title`.
   final String name;
@@ -58,7 +58,7 @@
   int compareTo(Object other) {
     // Not sure about this sort order
     if (other is! AttributeName) return 1;
-    final otherAttributeName = other as AttributeName;
+    final otherAttributeName = other;
     var cmp = (prefix ?? '').compareTo((otherAttributeName.prefix ?? ''));
     if (cmp != 0) return cmp;
     cmp = name.compareTo(otherAttributeName.name);
@@ -85,7 +85,8 @@
   /// are implemented. For example, nth-child does not implement An+B syntax
   /// and *-of-type is not implemented. If a selector is not implemented this
   /// method will throw [UnimplementedError].
-  Element querySelector(String selector) => query.querySelector(this, selector);
+  Element? querySelector(String selector) =>
+      query.querySelector(this, selector);
 
   /// Returns all descendant nodes matching the given selectors, using a
   /// preorder traversal.
@@ -102,7 +103,7 @@
 // http://dom.spec.whatwg.org/#interface-nonelementparentnode
 abstract class _NonElementParentNode implements _ParentNode {
   // TODO(jmesserly): could be faster, should throw on invalid id.
-  Element getElementById(String id) => querySelector('#$id');
+  Element? getElementById(String id) => querySelector('#$id');
 }
 
 // This doesn't exist as an interface in the spec, but it's useful to merge
@@ -136,13 +137,13 @@
   static const int TEXT_NODE = 3;
 
   /// The parent of the current node (or null for the document node).
-  Node parentNode;
+  Node? parentNode;
 
   /// The parent element of this node.
   ///
   /// Returns null if this node either does not have a parent or its parent is
   /// not an element.
-  Element get parent {
+  Element? get parent {
     final parentNode = this.parentNode;
     return parentNode is Element ? parentNode : null;
   }
@@ -156,28 +157,26 @@
 
   /// A list of child nodes of the current node. This must
   /// include all elements but not necessarily other node types.
-  final NodeList nodes = NodeList._();
+  late final nodes = NodeList._(this);
 
-  List<Element> _elements;
+  List<Element>? _elements;
 
   // TODO(jmesserly): consider using an Expando for this, and put it in
   // dom_parsing. Need to check the performance affect.
   /// The source span of this node, if it was created by the [HtmlParser].
-  FileSpan sourceSpan;
+  FileSpan? sourceSpan;
 
   /// The attribute spans if requested. Otherwise null.
-  LinkedHashMap<Object, FileSpan> _attributeSpans;
-  LinkedHashMap<Object, FileSpan> _attributeValueSpans;
+  LinkedHashMap<Object, FileSpan>? _attributeSpans;
+  LinkedHashMap<Object, FileSpan>? _attributeValueSpans;
 
-  Node._() {
-    nodes._parent = this;
-  }
+  Node._();
 
   /// If [sourceSpan] is available, this contains the spans of each attribute.
   /// The span of an attribute is the entire attribute, including the name and
   /// quotes (if any). For example, the span of "attr" in `<a attr="value">`
   /// would be the text `attr="value"`.
-  LinkedHashMap<Object, FileSpan> get attributeSpans {
+  LinkedHashMap<Object, FileSpan>? get attributeSpans {
     _ensureAttributeSpans();
     return _attributeSpans;
   }
@@ -186,7 +185,7 @@
   /// value. Unlike [attributeSpans], this span will include only the value.
   /// For example, the value span of "attr" in `<a attr="value">` would be the
   /// text `value`.
-  LinkedHashMap<Object, FileSpan> get attributeValueSpans {
+  LinkedHashMap<Object, FileSpan>? get attributeValueSpans {
     _ensureAttributeSpans();
     return _attributeValueSpans;
   }
@@ -215,12 +214,12 @@
   }
 
   // Implemented per: http://dom.spec.whatwg.org/#dom-node-textcontent
-  String get text => null;
-  set text(String value) {}
+  String? get text => null;
+  set text(String? value) {}
 
   void append(Node node) => nodes.add(node);
 
-  Node get firstChild => nodes.isNotEmpty ? nodes[0] : null;
+  Node? get firstChild => nodes.isNotEmpty ? nodes[0] : null;
 
   void _addOuterHtml(StringBuffer str);
 
@@ -232,18 +231,12 @@
 
   Node remove() {
     // TODO(jmesserly): is parent == null an error?
-    if (parentNode != null) {
-      parentNode.nodes.remove(this);
-    }
+    parentNode?.nodes.remove(this);
     return this;
   }
 
   /// Insert [node] as a child of the current node, before [refNode] in the
-  /// list of child nodes.
-  ///
-  /// [refNode] must be a hild of the current node or null. If [refNode] is null
-  /// [node] will be added to the end of the list.
-  void insertBefore(Node node, Node refNode) {
+  void insertBefore(Node node, Node? refNode) {
     if (refNode == null) {
       nodes.add(node);
     } else {
@@ -256,7 +249,7 @@
     if (parentNode == null) {
       throw UnsupportedError('Node must have a parent to replace it.');
     }
-    parentNode.nodes[parentNode.nodes.indexOf(this)] = otherNode;
+    parentNode!.nodes[parentNode!.nodes.indexOf(this)] = otherNode;
     return this;
   }
 
@@ -280,12 +273,13 @@
   void _ensureAttributeSpans() {
     if (_attributeSpans != null) return;
 
-    _attributeSpans = LinkedHashMap<Object, FileSpan>();
-    _attributeValueSpans = LinkedHashMap<Object, FileSpan>();
+    final attributeSpans = _attributeSpans = LinkedHashMap<Object, FileSpan>();
+    final attributeValueSpans =
+        _attributeValueSpans = LinkedHashMap<Object, FileSpan>();
 
     if (sourceSpan == null) return;
 
-    final tokenizer = HtmlTokenizer(sourceSpan.text,
+    final tokenizer = HtmlTokenizer(sourceSpan!.text,
         generateSpans: true, attributeSpans: true);
 
     tokenizer.moveNext();
@@ -293,13 +287,14 @@
 
     if (token.attributeSpans == null) return; // no attributes
 
-    for (var attr in token.attributeSpans) {
-      final offset = sourceSpan.start.offset;
-      _attributeSpans[attr.name] =
-          sourceSpan.file.span(offset + attr.start, offset + attr.end);
+    for (var attr in token.attributeSpans!) {
+      final offset = sourceSpan!.start.offset;
+      final name = attr.name!;
+      attributeSpans[name] =
+          sourceSpan!.file.span(offset + attr.start, offset + attr.end);
       if (attr.startValue != null) {
-        _attributeValueSpans[attr.name] = sourceSpan.file
-            .span(offset + attr.startValue, offset + attr.endValue);
+        attributeValueSpans[name] = sourceSpan!.file
+            .span(offset + attr.startValue!, offset + attr.endValue);
       }
     }
   }
@@ -323,9 +318,9 @@
   int get nodeType => Node.DOCUMENT_NODE;
 
   // TODO(jmesserly): optmize this if needed
-  Element get documentElement => querySelector('html');
-  Element get head => documentElement.querySelector('head');
-  Element get body => documentElement.querySelector('body');
+  Element get documentElement => querySelector('html')!;
+  Element get head => documentElement.querySelector('head')!;
+  Element get body => documentElement.querySelector('body')!;
 
   /// Returns a fragment of HTML or XML that represents the element and its
   /// contents.
@@ -348,7 +343,7 @@
 
   // TODO(jmesserly): this is only a partial implementation of:
   // http://dom.spec.whatwg.org/#dom-document-createelementns
-  Element createElementNS(String namespaceUri, String tag) {
+  Element createElementNS(String? namespaceUri, String? tag) {
     if (namespaceUri == '') namespaceUri = null;
     return Element._(tag, namespaceUri);
   }
@@ -381,15 +376,15 @@
   void _addOuterHtml(StringBuffer str) => _addInnerHtml(str);
 
   @override
-  String get text => _getText(this);
+  String? get text => _getText(this);
   @override
-  set text(String value) => _setText(this, value);
+  set text(String? value) => _setText(this, value);
 }
 
 class DocumentType extends Node {
-  final String name;
-  final String publicId;
-  final String systemId;
+  final String? name;
+  final String? publicId;
+  final String? systemId;
 
   DocumentType(this.name, this.publicId, this.systemId) : super._();
 
@@ -424,7 +419,7 @@
   /// It will flatten back to a String on read.
   Object _data;
 
-  Text(String data)
+  Text(String? data)
       : _data = data ?? '',
         super._();
 
@@ -433,7 +428,7 @@
 
   String get data => _data = _data.toString();
   set data(String value) {
-    _data = value ?? '';
+    _data = identical(value, null) ? '' : value;
   }
 
   @override
@@ -454,24 +449,24 @@
   @override
   String get text => data;
   @override
-  set text(String value) {
+  set text(covariant String value) {
     data = value;
   }
 }
 
 // TODO(jmesserly): Elements should have a pointer back to their document
 class Element extends Node with _ParentNode, _ElementAndDocument {
-  final String namespaceUri;
+  final String? namespaceUri;
 
   /// The [local name](http://dom.spec.whatwg.org/#concept-element-local-name)
   /// of this element.
-  final String localName;
+  final String? localName;
 
   // TODO(jmesserly): consider using an Expando for this, and put it in
   // dom_parsing. Need to check the performance affect.
   /// The source span of the end tag this element, if it was created by the
   /// [HtmlParser]. May be `null` if does not have an implicit end tag.
-  FileSpan endSourceSpan;
+  FileSpan? endSourceSpan;
 
   Element._(this.localName, [this.namespaceUri]) : super._();
 
@@ -508,12 +503,12 @@
     // 3) Verify that the html does not contain both <head> and <body> tags.
     // 4) Detach the created element from its dummy parent.
     var parentTag = 'div';
-    String tag;
+    String? tag;
     final match = _startTagRegexp.firstMatch(html);
     if (match != null) {
-      tag = match.group(1).toLowerCase();
+      tag = match.group(1)!.toLowerCase();
       if (_customParentTagMap.containsKey(tag)) {
-        parentTag = _customParentTagMap[tag];
+        parentTag = _customParentTagMap[tag]!;
       }
     }
 
@@ -536,9 +531,9 @@
   int get nodeType => Node.ELEMENT_NODE;
 
   // TODO(jmesserly): we can make this faster
-  Element get previousElementSibling {
+  Element? get previousElementSibling {
     if (parentNode == null) return null;
-    final siblings = parentNode.nodes;
+    final siblings = parentNode!.nodes;
     for (var i = siblings.indexOf(this) - 1; i >= 0; i--) {
       final s = siblings[i];
       if (s is Element) return s;
@@ -546,7 +541,8 @@
     return null;
   }
 
-  Element get nextElementSibling {
+  Element? get nextElementSibling {
+    final parentNode = this.parentNode;
     if (parentNode == null) return null;
     final siblings = parentNode.nodes;
     for (var i = siblings.indexOf(this) + 1; i < siblings.length; i++) {
@@ -565,7 +561,7 @@
   @override
   String get text => _getText(this);
   @override
-  set text(String value) => _setText(this, value);
+  set text(String? value) => _setText(this, value);
 
   /// Returns a fragment of HTML or XML that represents the element and its
   /// contents.
@@ -581,7 +577,7 @@
     nodes.clear();
     // TODO(jmesserly): should be able to get the same effect by adding the
     // fragment directly.
-    nodes.addAll(parseFragment(value, container: localName).nodes);
+    nodes.addAll(parseFragment(value, container: localName!).nodes);
   }
 
   @override
@@ -626,7 +622,7 @@
     if (!isVoidElement(localName)) str.write('</$localName>');
   }
 
-  static String _getSerializationPrefix(String uri) {
+  static String _getSerializationPrefix(String? uri) {
     if (uri == null ||
         uri == Namespaces.html ||
         uri == Namespaces.mathml ||
@@ -678,7 +674,7 @@
 }
 
 class Comment extends Node {
-  String data;
+  String? data;
 
   Comment(this.data) : super._();
 
@@ -697,9 +693,9 @@
   Comment clone(bool deep) => Comment(data);
 
   @override
-  String get text => data;
+  String? get text => data;
   @override
-  set text(String value) {
+  set text(String? value) {
     data = value;
   }
 }
@@ -708,11 +704,9 @@
 // (The requirement to remove the node from the old node list makes it tricky.)
 // TODO(jmesserly): is there any way to share code with the _NodeListImpl?
 class NodeList extends ListProxy<Node> {
-  // Note: this is conceptually final, but because of circular reference
-  // between Node and NodeList we initialize it after construction.
-  Node _parent;
+  final Node _parent;
 
-  NodeList._();
+  NodeList._(this._parent);
 
   Node _setParent(Node node) {
     // Note: we need to remove the node from its previous parent node, if any,
@@ -918,7 +912,7 @@
   }
 
   @override
-  bool contains(Object element) {
+  bool contains(Object? element) {
     return element is Element && _childNodes.contains(element);
   }
 
@@ -926,7 +920,7 @@
   Iterable<Element> get reversed => _filtered.reversed;
 
   @override
-  void sort([int Function(Element, Element) compare]) {
+  void sort([int Function(Element, Element)? compare]) {
     throw UnsupportedError('TODO(jacobr): should we impl?');
   }
 
@@ -937,7 +931,7 @@
   }
 
   @override
-  void fillRange(int start, int end, [Element fillValue]) {
+  void fillRange(int start, int end, [Element? fillValue]) {
     throw UnimplementedError();
   }
 
@@ -960,11 +954,7 @@
 
   @override
   Element removeLast() {
-    final result = last;
-    if (result != null) {
-      result.remove();
-    }
-    return result;
+    return last..remove();
   }
 
   @override
@@ -992,7 +982,7 @@
   }
 
   @override
-  bool remove(Object element) {
+  bool remove(Object? element) {
     if (element is! Element) return false;
     for (var i = 0; i < length; i++) {
       final indexElement = this[i];
@@ -1025,18 +1015,19 @@
   @override
   Set<Element> toSet() => Set<Element>.from(this);
   @override
-  Element firstWhere(bool Function(Element) test, {Element Function() orElse}) {
+  Element firstWhere(bool Function(Element) test,
+      {Element Function()? orElse}) {
     return _filtered.firstWhere(test, orElse: orElse);
   }
 
   @override
-  Element lastWhere(bool Function(Element) test, {Element Function() orElse}) {
+  Element lastWhere(bool Function(Element) test, {Element Function()? orElse}) {
     return _filtered.lastWhere(test, orElse: orElse);
   }
 
   @override
   Element singleWhere(bool Function(Element) test,
-      {Element Function() orElse}) {
+      {Element Function()? orElse}) {
     if (orElse != null) throw UnimplementedError('orElse');
     return _filtered.singleWhere(test);
   }
@@ -1055,17 +1046,17 @@
   @override
   Iterator<Element> get iterator => _filtered.iterator;
   @override
-  List<Element> sublist(int start, [int end]) => _filtered.sublist(start, end);
+  List<Element> sublist(int start, [int? end]) => _filtered.sublist(start, end);
   @override
   Iterable<Element> getRange(int start, int end) =>
       _filtered.getRange(start, end);
   @override
-  int indexOf(Object element, [int start = 0]) =>
+  int indexOf(Object? element, [int start = 0]) =>
       // Cast forced by ListMixin https://github.com/dart-lang/sdk/issues/31311
       _filtered.indexOf(element as Element, start);
 
   @override
-  int lastIndexOf(Object element, [int start]) {
+  int lastIndexOf(Object? element, [int? start]) {
     start ??= length - 1;
     // Cast forced by ListMixin https://github.com/dart-lang/sdk/issues/31311
     return _filtered.lastIndexOf(element as Element, start);
@@ -1085,7 +1076,7 @@
 // For Element and DocumentFragment
 String _getText(Node node) => (_ConcatTextVisitor()..visit(node)).toString();
 
-void _setText(Node node, String value) {
+void _setText(Node node, String? value) {
   node.nodes.clear();
   node.append(Text(value));
 }
diff --git a/lib/dom_parsing.dart b/lib/dom_parsing.dart
index f108b59..142222e 100644
--- a/lib/dom_parsing.dart
+++ b/lib/dom_parsing.dart
@@ -111,7 +111,7 @@
 
   @override
   void visitComment(Comment node) {
-    final data = htmlSerializeEscape(node.data);
+    final data = htmlSerializeEscape(node.data!);
     _str.write('<code class="markup comment">&lt;!--$data--></code>');
   }
 }
@@ -136,10 +136,10 @@
 String htmlSerializeEscape(String text, {bool attributeMode = false}) {
   // TODO(jmesserly): is it faster to build up a list of codepoints?
   // StringBuffer seems cleaner assuming Dart can unbox 1-char strings.
-  StringBuffer result;
+  StringBuffer? result;
   for (var i = 0; i < text.length; i++) {
     final ch = text[i];
-    String replace;
+    String? replace;
     switch (ch) {
       case '&':
         replace = '&amp;';
@@ -172,7 +172,7 @@
 /// This method is useful to a pretty printer, because void elements must not
 /// have an end tag.
 /// See also: <http://dev.w3.org/html5/markup/syntax.html#void-elements>.
-bool isVoidElement(String tagName) {
+bool isVoidElement(String? tagName) {
   switch (tagName) {
     case 'area':
     case 'base':
diff --git a/lib/parser.dart b/lib/parser.dart
index 5dce969..ca193f0 100644
--- a/lib/parser.dart
+++ b/lib/parser.dart
@@ -37,7 +37,7 @@
 /// can additionally pass [sourceUrl] to indicate where the [input] was
 /// extracted from.
 Document parse(input,
-    {String encoding, bool generateSpans = false, String sourceUrl}) {
+    {String? encoding, bool generateSpans = false, String? sourceUrl}) {
   final p = HtmlParser(input,
       encoding: encoding, generateSpans: generateSpans, sourceUrl: sourceUrl);
   return p.parse();
@@ -57,9 +57,9 @@
 /// from.
 DocumentFragment parseFragment(input,
     {String container = 'div',
-    String encoding,
+    String? encoding,
     bool generateSpans = false,
-    String sourceUrl}) {
+    String? sourceUrl}) {
   final p = HtmlParser(input,
       encoding: encoding, generateSpans: generateSpans, sourceUrl: sourceUrl);
   return p.parseFragment(container);
@@ -80,8 +80,6 @@
 
   final List<ParseError> errors = <ParseError>[];
 
-  String container;
-
   bool firstStartTag = false;
 
   // TODO(jmesserly): use enum?
@@ -89,38 +87,43 @@
   String compatMode = 'no quirks';
 
   /// innerHTML container when parsing document fragment.
-  String innerHTML;
+  String? innerHTML;
 
-  Phase phase;
+  late Phase phase = _initialPhase;
 
-  Phase originalPhase;
+  Phase? originalPhase;
 
-  bool framesetOK;
+  var framesetOK = true;
 
   // These fields hold the different phase singletons. At any given time one
   // of them will be active.
-  InitialPhase _initialPhase;
-  BeforeHtmlPhase _beforeHtmlPhase;
-  BeforeHeadPhase _beforeHeadPhase;
-  InHeadPhase _inHeadPhase;
-  AfterHeadPhase _afterHeadPhase;
-  InBodyPhase _inBodyPhase;
-  TextPhase _textPhase;
-  InTablePhase _inTablePhase;
-  InTableTextPhase _inTableTextPhase;
-  InCaptionPhase _inCaptionPhase;
-  InColumnGroupPhase _inColumnGroupPhase;
-  InTableBodyPhase _inTableBodyPhase;
-  InRowPhase _inRowPhase;
-  InCellPhase _inCellPhase;
-  InSelectPhase _inSelectPhase;
-  InSelectInTablePhase _inSelectInTablePhase;
-  InForeignContentPhase _inForeignContentPhase;
-  AfterBodyPhase _afterBodyPhase;
-  InFramesetPhase _inFramesetPhase;
-  AfterFramesetPhase _afterFramesetPhase;
-  AfterAfterBodyPhase _afterAfterBodyPhase;
-  AfterAfterFramesetPhase _afterAfterFramesetPhase;
+  late final _initialPhase = InitialPhase(this);
+  late final _beforeHtmlPhase = BeforeHtmlPhase(this);
+  late final _beforeHeadPhase = BeforeHeadPhase(this);
+  late final _inHeadPhase = InHeadPhase(this);
+  // TODO: html5lib did not implement the no script parsing mode
+  // More information here:
+  // http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#scripting-flag
+  // http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#parsing-main-inheadnoscript
+  // late final _inHeadNoscript = InHeadNoScriptPhase(this);
+  late final _afterHeadPhase = AfterHeadPhase(this);
+  late final _inBodyPhase = InBodyPhase(this);
+  late final _textPhase = TextPhase(this);
+  late final _inTablePhase = InTablePhase(this);
+  late final _inTableTextPhase = InTableTextPhase(this);
+  late final _inCaptionPhase = InCaptionPhase(this);
+  late final _inColumnGroupPhase = InColumnGroupPhase(this);
+  late final _inTableBodyPhase = InTableBodyPhase(this);
+  late final _inRowPhase = InRowPhase(this);
+  late final _inCellPhase = InCellPhase(this);
+  late final _inSelectPhase = InSelectPhase(this);
+  late final _inSelectInTablePhase = InSelectInTablePhase(this);
+  late final _inForeignContentPhase = InForeignContentPhase(this);
+  late final _afterBodyPhase = AfterBodyPhase(this);
+  late final _inFramesetPhase = InFramesetPhase(this);
+  late final _afterFramesetPhase = AfterFramesetPhase(this);
+  late final _afterAfterBodyPhase = AfterAfterBodyPhase(this);
+  late final _afterAfterFramesetPhase = AfterAfterFramesetPhase(this);
 
   /// Create an HtmlParser and configure the [tree] builder and [strict] mode.
   /// The [input] can be a [String], [List<int>] of bytes or an [HtmlTokenizer].
@@ -138,14 +141,14 @@
   /// that standard way to parse HTML is to lowercase, which is what the browser
   /// DOM will do if you request `Element.outerHTML`, for example.
   HtmlParser(input,
-      {String encoding,
+      {String? encoding,
       bool parseMeta = true,
       bool lowercaseElementName = true,
       bool lowercaseAttrName = true,
       this.strict = false,
       this.generateSpans = false,
-      String sourceUrl,
-      TreeBuilder tree})
+      String? sourceUrl,
+      TreeBuilder? tree})
       : tree = tree ?? TreeBuilder(true),
         tokenizer = (input is HtmlTokenizer
             ? input
@@ -157,33 +160,6 @@
                 generateSpans: generateSpans,
                 sourceUrl: sourceUrl)) {
     tokenizer.parser = this;
-    _initialPhase = InitialPhase(this);
-    _beforeHtmlPhase = BeforeHtmlPhase(this);
-    _beforeHeadPhase = BeforeHeadPhase(this);
-    _inHeadPhase = InHeadPhase(this);
-    // TODO(jmesserly): html5lib did not implement the no script parsing mode
-    // More information here:
-    // http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#scripting-flag
-    // http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#parsing-main-inheadnoscript
-    // "inHeadNoscript": new InHeadNoScriptPhase(this);
-    _afterHeadPhase = AfterHeadPhase(this);
-    _inBodyPhase = InBodyPhase(this);
-    _textPhase = TextPhase(this);
-    _inTablePhase = InTablePhase(this);
-    _inTableTextPhase = InTableTextPhase(this);
-    _inCaptionPhase = InCaptionPhase(this);
-    _inColumnGroupPhase = InColumnGroupPhase(this);
-    _inTableBodyPhase = InTableBodyPhase(this);
-    _inRowPhase = InRowPhase(this);
-    _inCellPhase = InCellPhase(this);
-    _inSelectPhase = InSelectPhase(this);
-    _inSelectInTablePhase = InSelectInTablePhase(this);
-    _inForeignContentPhase = InForeignContentPhase(this);
-    _afterBodyPhase = AfterBodyPhase(this);
-    _inFramesetPhase = InFramesetPhase(this);
-    _afterFramesetPhase = AfterFramesetPhase(this);
-    _afterAfterBodyPhase = AfterAfterBodyPhase(this);
-    _afterAfterFramesetPhase = AfterAfterFramesetPhase(this);
   }
 
   bool get innerHTMLMode => innerHTML != null;
@@ -200,7 +176,7 @@
   /// Pass a [container] to change the type of the containing element.
   /// After parsing, [errors] will be populated with parse errors, if any.
   DocumentFragment parseFragment([String container = 'div']) {
-    if (container == null) throw ArgumentError('container');
+    ArgumentError.checkNotNull(container, 'container');
     innerHTML = container.toLowerCase();
     _parse();
     return tree.getFragment();
@@ -276,7 +252,7 @@
     if (isMathMLTextIntegrationPoint(node)) {
       if (type == TokenKind.startTag &&
           (token as StartTagToken).name != 'mglyph' &&
-          (token as StartTagToken).name != 'malignmark') {
+          token.name != 'malignmark') {
         return false;
       }
       if (type == TokenKind.characters || type == TokenKind.spaceCharacters) {
@@ -304,7 +280,7 @@
   void mainLoop() {
     while (tokenizer.moveNext()) {
       final token = tokenizer.current;
-      var newToken = token;
+      Token? newToken = token;
       int type;
       while (newToken != null) {
         type = newToken.kind;
@@ -367,14 +343,12 @@
 
   /// The last span available. Used for EOF errors if we don't have something
   /// better.
-  SourceSpan get _lastSpan {
-    if (tokenizer.stream.fileInfo == null) return null;
-    final pos = tokenizer.stream.position;
-    return tokenizer.stream.fileInfo.location(pos).pointSpan();
-  }
+  SourceSpan? get _lastSpan => tokenizer.stream.fileInfo
+      ?.location(tokenizer.stream.position)
+      .pointSpan();
 
-  void parseError(SourceSpan span, String errorcode,
-      [Map datavars = const {}]) {
+  void parseError(SourceSpan? span, String errorcode,
+      [Map? datavars = const {}]) {
     if (!generateSpans && span == null) {
       span = _lastSpan;
     }
@@ -457,9 +431,9 @@
       'zoomandpan': 'zoomAndPan'
     };
     for (var originalName in token.data.keys.toList()) {
-      final svgName = replacements[originalName];
+      final svgName = replacements[originalName as String];
       if (svgName != null) {
-        token.data[svgName] = token.data.remove(originalName);
+        token.data[svgName] = token.data.remove(originalName)!;
       }
     }
   }
@@ -483,9 +457,9 @@
     };
 
     for (var originalName in token.data.keys.toList()) {
-      final foreignName = replacements[originalName];
+      final foreignName = replacements[originalName as String];
       if (foreignName != null) {
-        token.data[foreignName] = token.data.remove(originalName);
+        token.data[foreignName] = token.data.remove(originalName)!;
       }
     }
   }
@@ -602,33 +576,33 @@
     throw UnimplementedError();
   }
 
-  Token processComment(CommentToken token) {
+  Token? processComment(CommentToken token) {
     // For most phases the following is correct. Where it's not it will be
     // overridden.
     tree.insertComment(token, tree.openElements.last);
     return null;
   }
 
-  Token processDoctype(DoctypeToken token) {
+  Token? processDoctype(DoctypeToken token) {
     parser.parseError(token.span, 'unexpected-doctype');
     return null;
   }
 
-  Token processCharacters(CharactersToken token) {
+  Token? processCharacters(CharactersToken token) {
     tree.insertText(token.data, token.span);
     return null;
   }
 
-  Token processSpaceCharacters(SpaceCharactersToken token) {
+  Token? processSpaceCharacters(SpaceCharactersToken token) {
     tree.insertText(token.data, token.span);
     return null;
   }
 
-  Token processStartTag(StartTagToken token) {
+  Token? processStartTag(StartTagToken token) {
     throw UnimplementedError();
   }
 
-  Token startTagHtml(StartTagToken token) {
+  Token? startTagHtml(StartTagToken token) {
     if (parser.firstStartTag == false && token.name == 'html') {
       parser.parseError(token.span, 'non-html-root');
     }
@@ -642,7 +616,7 @@
     return null;
   }
 
-  Token processEndTag(EndTagToken token) {
+  Token? processEndTag(EndTagToken token) {
     throw UnimplementedError();
   }
 
@@ -653,9 +627,7 @@
     while (node.localName != name) {
       node = tree.openElements.removeLast();
     }
-    if (node != null) {
-      node.endSourceSpan = token.span;
-    }
+    node.endSourceSpan = token.span;
   }
 }
 
@@ -663,18 +635,18 @@
   InitialPhase(HtmlParser parser) : super(parser);
 
   @override
-  Token processSpaceCharacters(SpaceCharactersToken token) {
+  Token? processSpaceCharacters(SpaceCharactersToken token) {
     return null;
   }
 
   @override
-  Token processComment(CommentToken token) {
+  Token? processComment(CommentToken token) {
     tree.insertComment(token, tree.document);
     return null;
   }
 
   @override
-  Token processDoctype(DoctypeToken token) {
+  Token? processDoctype(DoctypeToken token) {
     final name = token.name;
     var publicId = token.publicId?.toAsciiLowerCase();
     final systemId = token.systemId;
@@ -832,13 +804,13 @@
   }
 
   @override
-  Token processComment(CommentToken token) {
+  Token? processComment(CommentToken token) {
     tree.insertComment(token, tree.document);
     return null;
   }
 
   @override
-  Token processSpaceCharacters(SpaceCharactersToken token) {
+  Token? processSpaceCharacters(SpaceCharactersToken token) {
     return null;
   }
 
@@ -859,7 +831,7 @@
   }
 
   @override
-  Token processEndTag(EndTagToken token) {
+  Token? processEndTag(EndTagToken token) {
     switch (token.name) {
       case 'head':
       case 'body':
@@ -879,7 +851,7 @@
   BeforeHeadPhase(HtmlParser parser) : super(parser);
 
   @override
-  Token processStartTag(StartTagToken token) {
+  Token? processStartTag(StartTagToken token) {
     switch (token.name) {
       case 'html':
         return startTagHtml(token);
@@ -892,7 +864,7 @@
   }
 
   @override
-  Token processEndTag(EndTagToken token) {
+  Token? processEndTag(EndTagToken token) {
     switch (token.name) {
       case 'head':
       case 'body':
@@ -912,7 +884,7 @@
   }
 
   @override
-  Token processSpaceCharacters(SpaceCharactersToken token) {
+  Token? processSpaceCharacters(SpaceCharactersToken token) {
     return null;
   }
 
@@ -923,7 +895,7 @@
   }
 
   @override
-  Token startTagHtml(StartTagToken token) {
+  Token? startTagHtml(StartTagToken token) {
     return parser._inBodyPhase.processStartTag(token);
   }
 
@@ -953,7 +925,7 @@
   InHeadPhase(HtmlParser parser) : super(parser);
 
   @override
-  Token processStartTag(StartTagToken token) {
+  Token? processStartTag(StartTagToken token) {
     switch (token.name) {
       case 'html':
         return startTagHtml(token);
@@ -987,7 +959,7 @@
   }
 
   @override
-  Token processEndTag(EndTagToken token) {
+  Token? processEndTag(EndTagToken token) {
     switch (token.name) {
       case 'head':
         endTagHead(token);
@@ -1016,7 +988,7 @@
   }
 
   @override
-  Token startTagHtml(StartTagToken token) {
+  Token? startTagHtml(StartTagToken token) {
     return parser._inBodyPhase.processStartTag(token);
   }
 
@@ -1100,7 +1072,7 @@
   AfterHeadPhase(HtmlParser parser) : super(parser);
 
   @override
-  Token processStartTag(StartTagToken token) {
+  Token? processStartTag(StartTagToken token) {
     switch (token.name) {
       case 'html':
         return startTagHtml(token);
@@ -1130,7 +1102,7 @@
   }
 
   @override
-  Token processEndTag(EndTagToken token) {
+  Token? processEndTag(EndTagToken token) {
     switch (token.name) {
       case 'body':
       case 'html':
@@ -1155,7 +1127,7 @@
   }
 
   @override
-  Token startTagHtml(StartTagToken token) {
+  Token? startTagHtml(StartTagToken token) {
     return parser._inBodyPhase.processStartTag(token);
   }
 
@@ -1219,7 +1191,7 @@
   InBodyPhase(HtmlParser parser) : super(parser);
 
   @override
-  Token processStartTag(StartTagToken token) {
+  Token? processStartTag(StartTagToken token) {
     switch (token.name) {
       case 'html':
         return startTagHtml(token);
@@ -1392,7 +1364,7 @@
   }
 
   @override
-  Token processEndTag(EndTagToken token) {
+  Token? processEndTag(EndTagToken token) {
     switch (token.name) {
       case 'body':
         endTagBody(token);
@@ -1497,7 +1469,7 @@
     final element = tree.openElements.last;
 
     final matchingElements = [];
-    for (Node node in tree.activeFormattingElements.reversed) {
+    for (Node? node in tree.activeFormattingElements.reversed) {
       if (node == null) {
         break;
       } else if (isMatchingFormattingElement(node as Element, element)) {
@@ -1557,7 +1529,7 @@
   }
 
   @override
-  Token processCharacters(CharactersToken token) {
+  Token? processCharacters(CharactersToken token) {
     if (token.data == '\u0000') {
       //The tokenizer should always emit null on its own
       return null;
@@ -1571,7 +1543,7 @@
   }
 
   @override
-  Token processSpaceCharacters(SpaceCharactersToken token) {
+  Token? processSpaceCharacters(SpaceCharactersToken token) {
     if (dropNewline) {
       processSpaceCharactersDropNewline(token);
     } else {
@@ -1581,7 +1553,7 @@
     return null;
   }
 
-  Token startTagProcessInHead(StartTagToken token) {
+  Token? startTagProcessInHead(StartTagToken token) {
     return parser._inHeadPhase.processStartTag(token);
   }
 
@@ -1605,7 +1577,7 @@
       assert(parser.innerHTMLMode);
     } else if (parser.framesetOK) {
       if (tree.openElements[1].parentNode != null) {
-        tree.openElements[1].parentNode.nodes.remove(tree.openElements[1]);
+        tree.openElements[1].parentNode!.nodes.remove(tree.openElements[1]);
       }
       while (tree.openElements.last.localName != 'html') {
         tree.openElements.removeLast();
@@ -1651,7 +1623,7 @@
       'dt': ['dt', 'dd'],
       'dd': ['dt', 'dd']
     };
-    final stopNames = stopNamesMap[token.name];
+    final stopNames = stopNamesMap[token.name!]!;
     for (var node in tree.openElements.reversed) {
       if (stopNames.contains(node.localName)) {
         parser.phase.processEndTag(EndTagToken(node.localName));
@@ -1720,7 +1692,7 @@
     addFormattingElement(token);
   }
 
-  Token startTagButton(StartTagToken token) {
+  Token? startTagButton(StartTagToken token) {
     if (tree.elementInScope('button')) {
       parser.parseError(token.span, 'unexpected-start-tag-implies-end-tag',
           {'startName': 'button', 'endName': 'button'});
@@ -1922,7 +1894,7 @@
         token.span, 'unexpected-start-tag-ignored', {'name': token.name});
   }
 
-  Token startTagOther(StartTagToken token) {
+  Token? startTagOther(StartTagToken token) {
     tree.reconstructActiveFormattingElements();
     tree.insertElement(token);
     return null;
@@ -1978,7 +1950,7 @@
     parser.phase = parser._afterBodyPhase;
   }
 
-  Token endTagHtml(EndTagToken token) {
+  Token? endTagHtml(EndTagToken token) {
     //We repeat the test for the body end tag token being ignored here
     if (tree.elementInScope('body')) {
       endTagBody(EndTagToken('body'));
@@ -2021,7 +1993,7 @@
   }
 
   void endTagListItem(EndTagToken token) {
-    String variant;
+    String? variant;
     if (token.name == 'li') {
       variant = 'list';
     } else {
@@ -2056,9 +2028,7 @@
         while (!headingElements.contains(node.localName)) {
           node = tree.openElements.removeLast();
         }
-        if (node != null) {
-          node.endSourceSpan = token.span;
-        }
+        node.endSourceSpan = token.span;
         break;
       }
     }
@@ -2101,7 +2071,7 @@
       // Step 2
       // Start of the adoption agency algorithm proper
       final afeIndex = tree.openElements.indexOf(formattingElement);
-      Element furthestBlock;
+      Element? furthestBlock;
       for (var element in slice(tree.openElements, afeIndex)) {
         if (specialElements.contains(getElementNameTuple(element))) {
           furthestBlock = element;
@@ -2114,9 +2084,7 @@
         while (element != formattingElement) {
           element = tree.openElements.removeLast();
         }
-        if (element != null) {
-          element.endSourceSpan = token.span;
-        }
+        element.endSourceSpan = token.span;
         tree.activeFormattingElements.remove(element);
         return;
       }
@@ -2166,7 +2134,7 @@
         // Step 6.6
         // Remove lastNode from its parents, if any
         if (lastNode.parentNode != null) {
-          lastNode.parentNode.nodes.remove(lastNode);
+          lastNode.parentNode!.nodes.remove(lastNode);
         }
         node.nodes.add(lastNode);
         // Step 7.7
@@ -2179,13 +2147,13 @@
       // table, tbody, tfoot, thead, or tr we need to foster parent the
       // lastNode
       if (lastNode.parentNode != null) {
-        lastNode.parentNode.nodes.remove(lastNode);
+        lastNode.parentNode!.nodes.remove(lastNode);
       }
 
       if (const ['table', 'tbody', 'tfoot', 'thead', 'tr']
           .contains(commonAncestor.localName)) {
         final nodePos = tree.getTableMisnestedNodePosition();
-        nodePos[0].insertBefore(lastNode, nodePos[1]);
+        nodePos[0]!.insertBefore(lastNode, nodePos[1]);
       } else {
         commonAncestor.nodes.add(lastNode);
       }
@@ -2262,13 +2230,12 @@
 
   // "Tried to process start tag %s in RCDATA/RAWTEXT mode"%token.name
   @override
-  // ignore: missing_return
   Token processStartTag(StartTagToken token) {
-    assert(false);
+    throw StateError('Cannot process start stag in text phase');
   }
 
   @override
-  Token processEndTag(EndTagToken token) {
+  Token? processEndTag(EndTagToken token) {
     if (token.name == 'script') {
       endTagScript(token);
       return null;
@@ -2278,7 +2245,7 @@
   }
 
   @override
-  Token processCharacters(CharactersToken token) {
+  Token? processCharacters(CharactersToken token) {
     tree.insertText(token.data, token.span);
     return null;
   }
@@ -2289,21 +2256,21 @@
     parser.parseError(last.sourceSpan, 'expected-named-closing-tag-but-got-eof',
         {'name': last.localName});
     tree.openElements.removeLast();
-    parser.phase = parser.originalPhase;
+    parser.phase = parser.originalPhase!;
     return true;
   }
 
   void endTagScript(EndTagToken token) {
     final node = tree.openElements.removeLast();
     assert(node.localName == 'script');
-    parser.phase = parser.originalPhase;
+    parser.phase = parser.originalPhase!;
     //The rest of this method is all stuff that only happens if
     //document.write works
   }
 
   void endTagOther(EndTagToken token) {
     tree.openElements.removeLast();
-    parser.phase = parser.originalPhase;
+    parser.phase = parser.originalPhase!;
   }
 }
 
@@ -2312,7 +2279,7 @@
   InTablePhase(HtmlParser parser) : super(parser);
 
   @override
-  Token processStartTag(StartTagToken token) {
+  Token? processStartTag(StartTagToken token) {
     switch (token.name) {
       case 'html':
         return startTagHtml(token);
@@ -2351,7 +2318,7 @@
   }
 
   @override
-  Token processEndTag(EndTagToken token) {
+  Token? processEndTag(EndTagToken token) {
     switch (token.name) {
       case 'table':
         endTagTable(token);
@@ -2401,7 +2368,7 @@
   }
 
   @override
-  Token processSpaceCharacters(SpaceCharactersToken token) {
+  Token? processSpaceCharacters(SpaceCharactersToken token) {
     final originalPhase = parser.phase;
     parser.phase = parser._inTableTextPhase;
     parser._inTableTextPhase.originalPhase = originalPhase;
@@ -2410,7 +2377,7 @@
   }
 
   @override
-  Token processCharacters(CharactersToken token) {
+  Token? processCharacters(CharactersToken token) {
     final originalPhase = parser.phase;
     parser.phase = parser._inTableTextPhase;
     parser._inTableTextPhase.originalPhase = originalPhase;
@@ -2457,7 +2424,7 @@
     return token;
   }
 
-  Token startTagTable(StartTagToken token) {
+  Token? startTagTable(StartTagToken token) {
     parser.parseError(token.span, 'unexpected-start-tag-implies-end-tag',
         {'startName': 'table', 'endName': 'table'});
     parser.phase.processEndTag(EndTagToken('table'));
@@ -2467,7 +2434,7 @@
     return null;
   }
 
-  Token startTagStyleScript(StartTagToken token) {
+  Token? startTagStyleScript(StartTagToken token) {
     return parser._inHeadPhase.processStartTag(token);
   }
 
@@ -2536,7 +2503,7 @@
 }
 
 class InTableTextPhase extends Phase {
-  Phase originalPhase;
+  Phase? originalPhase;
   List<StringToken> characterTokens;
 
   InTableTextPhase(HtmlParser parser)
@@ -2548,10 +2515,10 @@
 
     // TODO(sigmund,jmesserly): remove '' (dartbug.com/8480)
     final data = characterTokens.map((t) => t.data).join('');
-    FileSpan span;
+    FileSpan? span;
 
     if (parser.generateSpans) {
-      span = characterTokens[0].span.expand(characterTokens.last.span);
+      span = characterTokens[0].span!.expand(characterTokens.last.span!);
     }
 
     if (!allWhitespace(data)) {
@@ -2565,19 +2532,19 @@
   @override
   Token processComment(CommentToken token) {
     flushCharacters();
-    parser.phase = originalPhase;
+    parser.phase = originalPhase!;
     return token;
   }
 
   @override
   bool processEOF() {
     flushCharacters();
-    parser.phase = originalPhase;
+    parser.phase = originalPhase!;
     return true;
   }
 
   @override
-  Token processCharacters(CharactersToken token) {
+  Token? processCharacters(CharactersToken token) {
     if (token.data == '\u0000') {
       return null;
     }
@@ -2586,7 +2553,7 @@
   }
 
   @override
-  Token processSpaceCharacters(SpaceCharactersToken token) {
+  Token? processSpaceCharacters(SpaceCharactersToken token) {
     //pretty sure we should never reach here
     characterTokens.add(token);
     // XXX assert(false);
@@ -2596,14 +2563,14 @@
   @override
   Token processStartTag(StartTagToken token) {
     flushCharacters();
-    parser.phase = originalPhase;
+    parser.phase = originalPhase!;
     return token;
   }
 
   @override
   Token processEndTag(EndTagToken token) {
     flushCharacters();
-    parser.phase = originalPhase;
+    parser.phase = originalPhase!;
     return token;
   }
 }
@@ -2613,7 +2580,7 @@
   InCaptionPhase(HtmlParser parser) : super(parser);
 
   @override
-  Token processStartTag(StartTagToken token) {
+  Token? processStartTag(StartTagToken token) {
     switch (token.name) {
       case 'html':
         return startTagHtml(token);
@@ -2633,7 +2600,7 @@
   }
 
   @override
-  Token processEndTag(EndTagToken token) {
+  Token? processEndTag(EndTagToken token) {
     switch (token.name) {
       case 'caption':
         endTagCaption(token);
@@ -2668,11 +2635,11 @@
   }
 
   @override
-  Token processCharacters(CharactersToken token) {
+  Token? processCharacters(CharactersToken token) {
     return parser._inBodyPhase.processCharacters(token);
   }
 
-  Token startTagTableElement(StartTagToken token) {
+  Token? startTagTableElement(StartTagToken token) {
     parser.parseError(token.span, 'undefined-error');
     //XXX Have to duplicate logic here to find out if the tag is ignored
     final ignoreEndTag = ignoreEndTagCaption();
@@ -2683,7 +2650,7 @@
     return null;
   }
 
-  Token startTagOther(StartTagToken token) {
+  Token? startTagOther(StartTagToken token) {
     return parser._inBodyPhase.processStartTag(token);
   }
 
@@ -2711,7 +2678,7 @@
     }
   }
 
-  Token endTagTable(EndTagToken token) {
+  Token? endTagTable(EndTagToken token) {
     parser.parseError(token.span, 'undefined-error');
     final ignoreEndTag = ignoreEndTagCaption();
     parser.phase.processEndTag(EndTagToken('caption'));
@@ -2725,7 +2692,7 @@
     parser.parseError(token.span, 'unexpected-end-tag', {'name': token.name});
   }
 
-  Token endTagOther(EndTagToken token) {
+  Token? endTagOther(EndTagToken token) {
     return parser._inBodyPhase.processEndTag(token);
   }
 }
@@ -2735,7 +2702,7 @@
   InColumnGroupPhase(HtmlParser parser) : super(parser);
 
   @override
-  Token processStartTag(StartTagToken token) {
+  Token? processStartTag(StartTagToken token) {
     switch (token.name) {
       case 'html':
         return startTagHtml(token);
@@ -2748,7 +2715,7 @@
   }
 
   @override
-  Token processEndTag(EndTagToken token) {
+  Token? processEndTag(EndTagToken token) {
     switch (token.name) {
       case 'colgroup':
         endTagColgroup(token);
@@ -2778,7 +2745,7 @@
   }
 
   @override
-  Token processCharacters(CharactersToken token) {
+  Token? processCharacters(CharactersToken token) {
     final ignoreEndTag = ignoreEndTagColgroup();
     endTagColgroup(EndTagToken('colgroup'));
     return ignoreEndTag ? null : token;
@@ -2789,7 +2756,7 @@
     tree.openElements.removeLast();
   }
 
-  Token startTagOther(StartTagToken token) {
+  Token? startTagOther(StartTagToken token) {
     final ignoreEndTag = ignoreEndTagColgroup();
     endTagColgroup(EndTagToken('colgroup'));
     return ignoreEndTag ? null : token;
@@ -2811,7 +2778,7 @@
     parser.parseError(token.span, 'no-end-tag', {'name': 'col'});
   }
 
-  Token endTagOther(EndTagToken token) {
+  Token? endTagOther(EndTagToken token) {
     final ignoreEndTag = ignoreEndTagColgroup();
     endTagColgroup(EndTagToken('colgroup'));
     return ignoreEndTag ? null : token;
@@ -2823,7 +2790,7 @@
   InTableBodyPhase(HtmlParser parser) : super(parser);
 
   @override
-  Token processStartTag(StartTagToken token) {
+  Token? processStartTag(StartTagToken token) {
     switch (token.name) {
       case 'html':
         return startTagHtml(token);
@@ -2846,7 +2813,7 @@
   }
 
   @override
-  Token processEndTag(EndTagToken token) {
+  Token? processEndTag(EndTagToken token) {
     switch (token.name) {
       case 'tbody':
       case 'tfoot':
@@ -2891,12 +2858,12 @@
   }
 
   @override
-  Token processSpaceCharacters(SpaceCharactersToken token) {
+  Token? processSpaceCharacters(SpaceCharactersToken token) {
     return parser._inTablePhase.processSpaceCharacters(token);
   }
 
   @override
-  Token processCharacters(CharactersToken token) {
+  Token? processCharacters(CharactersToken token) {
     return parser._inTablePhase.processCharacters(token);
   }
 
@@ -2913,9 +2880,9 @@
     return token;
   }
 
-  Token startTagTableOther(TagToken token) => endTagTable(token);
+  Token? startTagTableOther(TagToken token) => endTagTable(token);
 
-  Token startTagOther(StartTagToken token) {
+  Token? startTagOther(StartTagToken token) {
     return parser._inTablePhase.processStartTag(token);
   }
 
@@ -2931,7 +2898,7 @@
     }
   }
 
-  Token endTagTable(TagToken token) {
+  Token? endTagTable(TagToken token) {
     // XXX AT Any ideas on how to share this with endTagTable?
     if (tree.elementInScope('tbody', variant: 'table') ||
         tree.elementInScope('thead', variant: 'table') ||
@@ -2952,7 +2919,7 @@
         token.span, 'unexpected-end-tag-in-table-body', {'name': token.name});
   }
 
-  Token endTagOther(EndTagToken token) {
+  Token? endTagOther(EndTagToken token) {
     return parser._inTablePhase.processEndTag(token);
   }
 }
@@ -2962,7 +2929,7 @@
   InRowPhase(HtmlParser parser) : super(parser);
 
   @override
-  Token processStartTag(StartTagToken token) {
+  Token? processStartTag(StartTagToken token) {
     switch (token.name) {
       case 'html':
         return startTagHtml(token);
@@ -2984,7 +2951,7 @@
   }
 
   @override
-  Token processEndTag(EndTagToken token) {
+  Token? processEndTag(EndTagToken token) {
     switch (token.name) {
       case 'tr':
         endTagTr(token);
@@ -3035,12 +3002,12 @@
   }
 
   @override
-  Token processSpaceCharacters(SpaceCharactersToken token) {
+  Token? processSpaceCharacters(SpaceCharactersToken token) {
     return parser._inTablePhase.processSpaceCharacters(token);
   }
 
   @override
-  Token processCharacters(CharactersToken token) {
+  Token? processCharacters(CharactersToken token) {
     return parser._inTablePhase.processCharacters(token);
   }
 
@@ -3051,14 +3018,14 @@
     tree.activeFormattingElements.add(null);
   }
 
-  Token startTagTableOther(StartTagToken token) {
+  Token? startTagTableOther(StartTagToken token) {
     final ignoreEndTag = ignoreEndTagTr();
     endTagTr(EndTagToken('tr'));
     // XXX how are we sure it's always ignored in the innerHTML case?
     return ignoreEndTag ? null : token;
   }
 
-  Token startTagOther(StartTagToken token) {
+  Token? startTagOther(StartTagToken token) {
     return parser._inTablePhase.processStartTag(token);
   }
 
@@ -3075,7 +3042,7 @@
     }
   }
 
-  Token endTagTable(EndTagToken token) {
+  Token? endTagTable(EndTagToken token) {
     final ignoreEndTag = ignoreEndTagTr();
     endTagTr(EndTagToken('tr'));
     // Reprocess the current tag if the tr end tag was not ignored
@@ -3083,7 +3050,7 @@
     return ignoreEndTag ? null : token;
   }
 
-  Token endTagTableRowGroup(EndTagToken token) {
+  Token? endTagTableRowGroup(EndTagToken token) {
     if (tree.elementInScope(token.name, variant: 'table')) {
       endTagTr(EndTagToken('tr'));
       return token;
@@ -3098,7 +3065,7 @@
         token.span, 'unexpected-end-tag-in-table-row', {'name': token.name});
   }
 
-  Token endTagOther(EndTagToken token) {
+  Token? endTagOther(EndTagToken token) {
     return parser._inTablePhase.processEndTag(token);
   }
 }
@@ -3108,7 +3075,7 @@
   InCellPhase(HtmlParser parser) : super(parser);
 
   @override
-  Token processStartTag(StartTagToken token) {
+  Token? processStartTag(StartTagToken token) {
     switch (token.name) {
       case 'html':
         return startTagHtml(token);
@@ -3128,7 +3095,7 @@
   }
 
   @override
-  Token processEndTag(EndTagToken token) {
+  Token? processEndTag(EndTagToken token) {
     switch (token.name) {
       case 'td':
       case 'th':
@@ -3169,11 +3136,11 @@
   }
 
   @override
-  Token processCharacters(CharactersToken token) {
+  Token? processCharacters(CharactersToken token) {
     return parser._inBodyPhase.processCharacters(token);
   }
 
-  Token startTagTableOther(StartTagToken token) {
+  Token? startTagTableOther(StartTagToken token) {
     if (tree.elementInScope('td', variant: 'table') ||
         tree.elementInScope('th', variant: 'table')) {
       closeCell();
@@ -3186,7 +3153,7 @@
     }
   }
 
-  Token startTagOther(StartTagToken token) {
+  Token? startTagOther(StartTagToken token) {
     return parser._inBodyPhase.processStartTag(token);
   }
 
@@ -3212,7 +3179,7 @@
     parser.parseError(token.span, 'unexpected-end-tag', {'name': token.name});
   }
 
-  Token endTagImply(EndTagToken token) {
+  Token? endTagImply(EndTagToken token) {
     if (tree.elementInScope(token.name, variant: 'table')) {
       closeCell();
       return token;
@@ -3223,7 +3190,7 @@
     return null;
   }
 
-  Token endTagOther(EndTagToken token) {
+  Token? endTagOther(EndTagToken token) {
     return parser._inBodyPhase.processEndTag(token);
   }
 }
@@ -3232,7 +3199,7 @@
   InSelectPhase(HtmlParser parser) : super(parser);
 
   @override
-  Token processStartTag(StartTagToken token) {
+  Token? processStartTag(StartTagToken token) {
     switch (token.name) {
       case 'html':
         return startTagHtml(token);
@@ -3257,7 +3224,7 @@
   }
 
   @override
-  Token processEndTag(EndTagToken token) {
+  Token? processEndTag(EndTagToken token) {
     switch (token.name) {
       case 'option':
         endTagOption(token);
@@ -3287,7 +3254,7 @@
   }
 
   @override
-  Token processCharacters(CharactersToken token) {
+  Token? processCharacters(CharactersToken token) {
     if (token.data == '\u0000') {
       return null;
     }
@@ -3318,7 +3285,7 @@
     endTagSelect(EndTagToken('select'));
   }
 
-  Token startTagInput(StartTagToken token) {
+  Token? startTagInput(StartTagToken token) {
     parser.parseError(token.span, 'unexpected-input-in-select');
     if (tree.elementInScope('select', variant: 'select')) {
       endTagSelect(EndTagToken('select'));
@@ -3329,11 +3296,11 @@
     return null;
   }
 
-  Token startTagScript(StartTagToken token) {
+  Token? startTagScript(StartTagToken token) {
     return parser._inHeadPhase.processStartTag(token);
   }
 
-  Token startTagOther(StartTagToken token) {
+  Token? startTagOther(StartTagToken token) {
     parser.parseError(
         token.span, 'unexpected-start-tag-in-select', {'name': token.name});
     return null;
@@ -3388,7 +3355,7 @@
   InSelectInTablePhase(HtmlParser parser) : super(parser);
 
   @override
-  Token processStartTag(StartTagToken token) {
+  Token? processStartTag(StartTagToken token) {
     switch (token.name) {
       case 'caption':
       case 'table':
@@ -3405,7 +3372,7 @@
   }
 
   @override
-  Token processEndTag(EndTagToken token) {
+  Token? processEndTag(EndTagToken token) {
     switch (token.name) {
       case 'caption':
       case 'table':
@@ -3428,7 +3395,7 @@
   }
 
   @override
-  Token processCharacters(CharactersToken token) {
+  Token? processCharacters(CharactersToken token) {
     return parser._inSelectPhase.processCharacters(token);
   }
 
@@ -3441,11 +3408,11 @@
     return token;
   }
 
-  Token startTagOther(StartTagToken token) {
+  Token? startTagOther(StartTagToken token) {
     return parser._inSelectPhase.processStartTag(token);
   }
 
-  Token endTagTable(EndTagToken token) {
+  Token? endTagTable(EndTagToken token) {
     parser.parseError(
         token.span,
         'unexpected-table-element-end-tag-in-select-in-table',
@@ -3457,7 +3424,7 @@
     return null;
   }
 
-  Token endTagOther(EndTagToken token) {
+  Token? endTagOther(EndTagToken token) {
     return parser._inSelectPhase.processEndTag(token);
   }
 }
@@ -3560,7 +3527,7 @@
   }
 
   @override
-  Token processCharacters(CharactersToken token) {
+  Token? processCharacters(CharactersToken token) {
     if (token.data == '\u0000') {
       token.replaceData('\uFFFD');
     } else if (parser.framesetOK && !allWhitespace(token.data)) {
@@ -3570,7 +3537,7 @@
   }
 
   @override
-  Token processStartTag(StartTagToken token) {
+  Token? processStartTag(StartTagToken token) {
     final currentNode = tree.openElements.last;
     if (breakoutElements.contains(token.name) ||
         (token.name == 'font' &&
@@ -3604,21 +3571,21 @@
   }
 
   @override
-  Token processEndTag(EndTagToken token) {
+  Token? processEndTag(EndTagToken token) {
     var nodeIndex = tree.openElements.length - 1;
     var node = tree.openElements.last;
     if (node.localName?.toAsciiLowerCase() != token.name) {
       parser.parseError(token.span, 'unexpected-end-tag', {'name': token.name});
     }
 
-    Token newToken;
+    Token? newToken;
     while (true) {
       if (node.localName?.toAsciiLowerCase() == token.name) {
         //XXX this isn't in the spec but it seems necessary
         if (parser.phase == parser._inTableTextPhase) {
           final inTableText = parser.phase as InTableTextPhase;
           inTableText.flushCharacters();
-          parser.phase = inTableText.originalPhase;
+          parser.phase = inTableText.originalPhase!;
         }
         while (tree.openElements.removeLast() != node) {
           assert(tree.openElements.isNotEmpty);
@@ -3644,13 +3611,13 @@
   AfterBodyPhase(HtmlParser parser) : super(parser);
 
   @override
-  Token processStartTag(StartTagToken token) {
+  Token? processStartTag(StartTagToken token) {
     if (token.name == 'html') return startTagHtml(token);
     return startTagOther(token);
   }
 
   @override
-  Token processEndTag(EndTagToken token) {
+  Token? processEndTag(EndTagToken token) {
     if (token.name == 'html') {
       endTagHtml(token);
       return null;
@@ -3663,7 +3630,7 @@
   bool processEOF() => false;
 
   @override
-  Token processComment(CommentToken token) {
+  Token? processComment(CommentToken token) {
     // This is needed because data is to be appended to the <html> element
     // here and not to whatever is currently open.
     tree.insertComment(token, tree.openElements[0]);
@@ -3678,7 +3645,7 @@
   }
 
   @override
-  Token startTagHtml(StartTagToken token) {
+  Token? startTagHtml(StartTagToken token) {
     return parser._inBodyPhase.processStartTag(token);
   }
 
@@ -3716,7 +3683,7 @@
   InFramesetPhase(HtmlParser parser) : super(parser);
 
   @override
-  Token processStartTag(StartTagToken token) {
+  Token? processStartTag(StartTagToken token) {
     switch (token.name) {
       case 'html':
         return startTagHtml(token);
@@ -3734,7 +3701,7 @@
   }
 
   @override
-  Token processEndTag(EndTagToken token) {
+  Token? processEndTag(EndTagToken token) {
     switch (token.name) {
       case 'frameset':
         endTagFrameset(token);
@@ -3757,7 +3724,7 @@
   }
 
   @override
-  Token processCharacters(CharactersToken token) {
+  Token? processCharacters(CharactersToken token) {
     parser.parseError(token.span, 'unexpected-char-in-frameset');
     return null;
   }
@@ -3771,11 +3738,11 @@
     tree.openElements.removeLast();
   }
 
-  Token startTagNoframes(StartTagToken token) {
+  Token? startTagNoframes(StartTagToken token) {
     return parser._inBodyPhase.processStartTag(token);
   }
 
-  Token startTagOther(StartTagToken token) {
+  Token? startTagOther(StartTagToken token) {
     parser.parseError(
         token.span, 'unexpected-start-tag-in-frameset', {'name': token.name});
     return null;
@@ -3809,7 +3776,7 @@
   AfterFramesetPhase(HtmlParser parser) : super(parser);
 
   @override
-  Token processStartTag(StartTagToken token) {
+  Token? processStartTag(StartTagToken token) {
     switch (token.name) {
       case 'html':
         return startTagHtml(token);
@@ -3822,7 +3789,7 @@
   }
 
   @override
-  Token processEndTag(EndTagToken token) {
+  Token? processEndTag(EndTagToken token) {
     switch (token.name) {
       case 'html':
         endTagHtml(token);
@@ -3838,12 +3805,12 @@
   bool processEOF() => false;
 
   @override
-  Token processCharacters(CharactersToken token) {
+  Token? processCharacters(CharactersToken token) {
     parser.parseError(token.span, 'unexpected-char-after-frameset');
     return null;
   }
 
-  Token startTagNoframes(StartTagToken token) {
+  Token? startTagNoframes(StartTagToken token) {
     return parser._inHeadPhase.processStartTag(token);
   }
 
@@ -3866,7 +3833,7 @@
   AfterAfterBodyPhase(HtmlParser parser) : super(parser);
 
   @override
-  Token processStartTag(StartTagToken token) {
+  Token? processStartTag(StartTagToken token) {
     if (token.name == 'html') return startTagHtml(token);
     return startTagOther(token);
   }
@@ -3875,13 +3842,13 @@
   bool processEOF() => false;
 
   @override
-  Token processComment(CommentToken token) {
+  Token? processComment(CommentToken token) {
     tree.insertComment(token, tree.document);
     return null;
   }
 
   @override
-  Token processSpaceCharacters(SpaceCharactersToken token) {
+  Token? processSpaceCharacters(SpaceCharactersToken token) {
     return parser._inBodyPhase.processSpaceCharacters(token);
   }
 
@@ -3893,7 +3860,7 @@
   }
 
   @override
-  Token startTagHtml(StartTagToken token) {
+  Token? startTagHtml(StartTagToken token) {
     return parser._inBodyPhase.processStartTag(token);
   }
 
@@ -3917,7 +3884,7 @@
   AfterAfterFramesetPhase(HtmlParser parser) : super(parser);
 
   @override
-  Token processStartTag(StartTagToken token) {
+  Token? processStartTag(StartTagToken token) {
     switch (token.name) {
       case 'html':
         return startTagHtml(token);
@@ -3933,28 +3900,28 @@
   bool processEOF() => false;
 
   @override
-  Token processComment(CommentToken token) {
+  Token? processComment(CommentToken token) {
     tree.insertComment(token, tree.document);
     return null;
   }
 
   @override
-  Token processSpaceCharacters(SpaceCharactersToken token) {
+  Token? processSpaceCharacters(SpaceCharactersToken token) {
     return parser._inBodyPhase.processSpaceCharacters(token);
   }
 
   @override
-  Token processCharacters(CharactersToken token) {
+  Token? processCharacters(CharactersToken token) {
     parser.parseError(token.span, 'expected-eof-but-got-char');
     return null;
   }
 
   @override
-  Token startTagHtml(StartTagToken token) {
+  Token? startTagHtml(StartTagToken token) {
     return parser._inBodyPhase.processStartTag(token);
   }
 
-  Token startTagNoFrames(StartTagToken token) {
+  Token? startTagNoFrames(StartTagToken token) {
     return parser._inHeadPhase.processStartTag(token);
   }
 
@@ -3964,7 +3931,7 @@
   }
 
   @override
-  Token processEndTag(EndTagToken token) {
+  Token? processEndTag(EndTagToken token) {
     parser.parseError(
         token.span, 'expected-eof-but-got-end-tag', {'name': token.name});
     return null;
@@ -3975,31 +3942,31 @@
 class ParseError implements SourceSpanException {
   final String errorCode;
   @override
-  final SourceSpan span;
-  final Map data;
+  final SourceSpan? span;
+  final Map? data;
 
   ParseError(this.errorCode, this.span, this.data);
 
-  int get line => span.start.line;
+  int get line => span!.start.line;
 
-  int get column => span.start.column;
+  int get column => span!.start.column;
 
   /// Returns the human readable error message for this error.
   ///
   /// Use [SourceSpan.message] or the [toString] from the [span] field to get a
   /// message including span information
   @override
-  String get message => formatStr(errorMessages[errorCode], data);
+  String get message => formatStr(errorMessages[errorCode]!, data);
 
   @override
   String toString({color}) {
-    final res = span.message(message, color: color);
-    return span.sourceUrl == null ? 'ParserError on $res' : 'On $res';
+    final res = span!.message(message, color: color);
+    return span!.sourceUrl == null ? 'ParserError on $res' : 'On $res';
   }
 }
 
 /// Convenience function to get the pair of namespace and localName.
-Pair<String, String> getElementNameTuple(Element e) {
+Pair<String, String?> getElementNameTuple(Element e) {
   final ns = e.namespaceUri ?? Namespaces.html;
   return Pair(ns, e.localName);
 }
diff --git a/lib/src/constants.dart b/lib/src/constants.dart
index 0f82805..2c013d4 100644
--- a/lib/src/constants.dart
+++ b/lib/src/constants.dart
@@ -4,7 +4,7 @@
 // lookup than linear search "contains". In the Python code they were
 // frozensets.
 
-final String eof = null;
+final String? eof = null;
 
 class ReparseException implements Exception {
   final String message;
@@ -245,7 +245,7 @@
   static const xmlns = 'http://www.w3.org/2000/xmlns/';
   Namespaces._();
 
-  static String getPrefix(String url) {
+  static String? getPrefix(String? url) {
     switch (url) {
       case html:
         return 'html';
@@ -405,7 +405,7 @@
 const int NEWLINE = 10;
 const int RETURN = 13;
 
-bool isWhitespace(String char) {
+bool isWhitespace(String? char) {
   if (char == null) return false;
   return isWhitespaceCC(char.codeUnitAt(0));
 }
@@ -439,22 +439,22 @@
 const UPPER_A = 65;
 const UPPER_Z = 90;
 
-bool isLetterOrDigit(String char) => isLetter(char) || isDigit(char);
+bool isLetterOrDigit(String? char) => isLetter(char) || isDigit(char);
 
 // Note: this is intentially ASCII only
-bool isLetter(String char) {
+bool isLetter(String? char) {
   if (char == null) return false;
   final cc = char.codeUnitAt(0);
   return cc >= LOWER_A && cc <= LOWER_Z || cc >= UPPER_A && cc <= UPPER_Z;
 }
 
-bool isDigit(String char) {
+bool isDigit(String? char) {
   if (char == null) return false;
   final cc = char.codeUnitAt(0);
   return cc >= ZERO && cc < ZERO + 10;
 }
 
-bool isHexDigit(String char) {
+bool isHexDigit(String? char) {
   if (char == null) return false;
   switch (char.codeUnitAt(0)) {
     case 48:
diff --git a/lib/src/css_class_set.dart b/lib/src/css_class_set.dart
index e57c487..cebb565 100644
--- a/lib/src/css_class_set.dart
+++ b/lib/src/css_class_set.dart
@@ -41,7 +41,7 @@
   ///
   /// If [shouldAdd] is true, then we always add that [value] to the element. If
   /// [shouldAdd] is false then we always remove [value] from the element.
-  bool toggle(String value, [bool shouldAdd]);
+  bool toggle(String value, [bool? shouldAdd]);
 
   /// Returns [:true:] if classes cannot be added or removed from this
   /// [:CssClassSet:].
@@ -52,7 +52,7 @@
   /// This is the Dart equivalent of jQuery's
   /// [hasClass](http://api.jquery.com/hasClass/).
   @override
-  bool contains(Object value);
+  bool contains(Object? value);
 
   /// Add the class [value] to element.
   ///
@@ -72,7 +72,7 @@
   /// This is the Dart equivalent of jQuery's
   /// [removeClass](http://api.jquery.com/removeClass/).
   @override
-  bool remove(Object value);
+  bool remove(Object? value);
 
   /// Add all classes specified in [iterable] to element.
   ///
@@ -86,7 +86,7 @@
   /// This is the Dart equivalent of jQuery's
   /// [removeClass](http://api.jquery.com/removeClass/).
   @override
-  void removeAll(Iterable<Object> iterable);
+  void removeAll(Iterable<Object?> iterable);
 
   /// Toggles all classes specified in [iterable] on element.
   ///
@@ -96,7 +96,7 @@
   /// If [shouldAdd] is true, then we always add all the classes in [iterable]
   /// element. If [shouldAdd] is false then we always remove all the classes in
   /// [iterable] from the element.
-  void toggleAll(Iterable<String> iterable, [bool shouldAdd]);
+  void toggleAll(Iterable<String> iterable, [bool? shouldAdd]);
 }
 
 abstract class _CssClassSetImpl extends SetBase<String> implements CssClassSet {
@@ -111,7 +111,7 @@
   /// If [shouldAdd] is true, then we always add that [value] to the element. If
   /// [shouldAdd] is false then we always remove [value] from the element.
   @override
-  bool toggle(String value, [bool shouldAdd]) {
+  bool toggle(String value, [bool? shouldAdd]) {
     final s = readClasses();
     var result = false;
     shouldAdd ??= !s.contains(value);
@@ -142,11 +142,11 @@
   /// This is the Dart equivalent of jQuery's
   /// [hasClass](http://api.jquery.com/hasClass/).
   @override
-  bool contains(Object value) => readClasses().contains(value);
+  bool contains(Object? value) => readClasses().contains(value);
 
   /// Lookup from the Set interface. Not interesting for a String set.
   @override
-  String lookup(Object value) => contains(value) ? value as String : null;
+  String? lookup(Object? value) => contains(value) ? value as String? : null;
 
   @override
   Set<String> toSet() => readClasses().toSet();
@@ -168,7 +168,7 @@
   /// This is the Dart equivalent of jQuery's
   /// [removeClass](http://api.jquery.com/removeClass/).
   @override
-  bool remove(Object value) {
+  bool remove(Object? value) {
     if (value is! String) return false;
     final s = readClasses();
     final result = s.remove(value);
@@ -185,7 +185,7 @@
   /// element. If [shouldAdd] is false then we always remove all the classes in
   /// [iterable] from the element.
   @override
-  void toggleAll(Iterable<String> iterable, [bool shouldAdd]) {
+  void toggleAll(Iterable<String> iterable, [bool? shouldAdd]) {
     for (var e in iterable) {
       toggle(e, shouldAdd);
     }
diff --git a/lib/src/encoding_parser.dart b/lib/src/encoding_parser.dart
index 6fa96c6..e417351 100644
--- a/lib/src/encoding_parser.dart
+++ b/lib/src/encoding_parser.dart
@@ -56,7 +56,7 @@
   String get _currentByte => _bytes[_position];
 
   /// Skip past a list of characters. Defaults to skipping [isWhitespace].
-  String _skipChars([_CharPredicate skipChars]) {
+  String? _skipChars([_CharPredicate? skipChars]) {
     skipChars ??= isWhitespace;
     var p = _position; // use property for the error-checking
     while (p < _length) {
@@ -71,7 +71,7 @@
     return null;
   }
 
-  String _skipUntil(_CharPredicate untilChars) {
+  String? _skipUntil(_CharPredicate untilChars) {
     var p = _position;
     while (p < _length) {
       final c = _bytes[p];
@@ -112,7 +112,7 @@
     }
   }
 
-  String _slice(int start, [int end]) {
+  String _slice(int start, [int? end]) {
     end ??= _length;
     if (end < 0) end += _length;
     return _bytes.substring(start, end);
@@ -131,14 +131,14 @@
 /// Mini parser for detecting character encoding from meta elements.
 class EncodingParser {
   final EncodingBytes _data;
-  String _encoding;
+  String? _encoding;
 
   /// [bytes] - the data to work on for encoding detection.
   EncodingParser(List<int> bytes)
       // Note: this is intentionally interpreting bytes as codepoints.
       : _data = EncodingBytes(String.fromCharCodes(bytes).toLowerCase());
 
-  String getEncoding() {
+  String? getEncoding() {
     final methodDispatch = [
       _DispatchEntry('<!--', _handleComment),
       _DispatchEntry('<meta', _handleMeta),
@@ -239,7 +239,7 @@
 
   /// Return a name,value pair for the next attribute in the stream,
   /// if one is found, or null
-  List<String> _getAttribute() {
+  List<String>? _getAttribute() {
     // Step 1 (skip chars)
     var c = _data._skipChars((x) => x == '/' || isWhitespace(x));
     // Step 2
@@ -312,8 +312,6 @@
       c = _data._next();
       if (_isSpaceOrAngleBracket(c)) {
         return [attrName.join(), attrValue.join()];
-      } else if (c == null) {
-        return null;
       } else if (isLetter(c)) {
         attrValue.add(c.toLowerCase());
       } else {
@@ -328,7 +326,7 @@
 
   ContentAttrParser(this.data);
 
-  String parse() {
+  String? parse() {
     try {
       // Check if the attr name is charset
       // otherwise return
diff --git a/lib/src/html_input_stream.dart b/lib/src/html_input_stream.dart
index d08163a..60bf8f8 100644
--- a/lib/src/html_input_stream.dart
+++ b/lib/src/html_input_stream.dart
@@ -20,7 +20,7 @@
   static const String defaultEncoding = 'utf-8';
 
   /// The name of the character encoding.
-  String charEncodingName;
+  String? charEncodingName;
 
   /// True if we are certain about [charEncodingName], false for tenative.
   bool charEncodingCertain = true;
@@ -28,20 +28,20 @@
   final bool generateSpans;
 
   /// Location where the contents of the stream were found.
-  final String sourceUrl;
+  final String? sourceUrl;
 
-  List<int> _rawBytes;
+  List<int>? _rawBytes;
 
   /// Raw UTF-16 codes, used if a Dart String is passed in.
-  List<int> _rawChars;
+  List<int>? _rawChars;
 
-  Queue<String> errors;
+  var errors = Queue<String>();
 
-  SourceFile fileInfo;
+  SourceFile? fileInfo;
 
-  List<int> _chars;
+  var _chars = <int>[];
 
-  int _offset;
+  var _offset = 0;
 
   /// Initialise an HtmlInputStream.
   ///
@@ -58,7 +58,7 @@
   ///
   /// [parseMeta] - Look for a <meta> element containing encoding information
   HtmlInputStream(source,
-      [String encoding,
+      [String? encoding,
       bool parseMeta = true,
       this.generateSpans = false,
       this.sourceUrl])
@@ -88,18 +88,18 @@
     _offset = 0;
     _chars = <int>[];
 
-    _rawChars ??= _decodeBytes(charEncodingName, _rawBytes);
+    final rawChars = _rawChars ??= _decodeBytes(charEncodingName!, _rawBytes!);
 
     var skipNewline = false;
     var wasSurrogatePair = false;
-    for (var i = 0; i < _rawChars.length; i++) {
-      var c = _rawChars[i];
+    for (var i = 0; i < rawChars.length; i++) {
+      var c = rawChars[i];
       if (skipNewline) {
         skipNewline = false;
         if (c == NEWLINE) continue;
       }
 
-      final isSurrogatePair = _isSurrogatePair(_rawChars, i);
+      final isSurrogatePair = _isSurrogatePair(rawChars, i);
       if (!isSurrogatePair && !wasSurrogatePair) {
         if (_invalidUnicode(c)) {
           errors.add('invalid-codepoint');
@@ -146,12 +146,12 @@
     }
 
     // Substitute for equivalent encodings:
-    if (charEncodingName.toLowerCase() == 'iso-8859-1') {
+    if (charEncodingName!.toLowerCase() == 'iso-8859-1') {
       charEncodingName = 'windows-1252';
     }
   }
 
-  void changeEncoding(String newEncoding) {
+  void changeEncoding(String? newEncoding) {
     if (_rawBytes == null) {
       // We should never get here -- if encoding is certain we won't try to
       // change it.
@@ -179,17 +179,17 @@
   /// Attempts to detect at BOM at the start of the stream. If
   /// an encoding can be determined from the BOM return the name of the
   /// encoding otherwise return null.
-  String detectBOM() {
+  String? detectBOM() {
     // Try detecting the BOM using bytes from the string
-    if (_hasUtf8Bom(_rawBytes)) {
+    if (_hasUtf8Bom(_rawBytes!)) {
       return 'utf-8';
     }
     return null;
   }
 
   /// Report the encoding declared by the meta element.
-  String detectEncodingMeta() {
-    final parser = EncodingParser(slice(_rawBytes, 0, numBytesMeta));
+  String? detectEncodingMeta() {
+    final parser = EncodingParser(slice(_rawBytes!, 0, numBytesMeta));
     var encoding = parser.getEncoding();
 
     if (const ['utf-16', 'utf-16-be', 'utf-16-le'].contains(encoding)) {
@@ -205,14 +205,14 @@
 
   /// Read one character from the stream or queue if available. Return
   /// EOF when EOF is reached.
-  String char() {
+  String? char() {
     if (_offset >= _chars.length) return eof;
     return _isSurrogatePair(_chars, _offset)
         ? String.fromCharCodes([_chars[_offset++], _chars[_offset++]])
         : String.fromCharCode(_chars[_offset++]);
   }
 
-  String peekChar() {
+  String? peekChar() {
     if (_offset >= _chars.length) return eof;
     return _isSurrogatePair(_chars, _offset)
         ? String.fromCharCodes([_chars[_offset], _chars[_offset + 1]])
@@ -236,15 +236,15 @@
   /// including any character in 'characters' or EOF.
   String charsUntil(String characters, [bool opposite = false]) {
     final start = _offset;
-    String c;
-    while ((c = peekChar()) != null && characters.contains(c) == opposite) {
+    String? c;
+    while ((c = peekChar()) != null && characters.contains(c!) == opposite) {
       _offset += c.codeUnits.length;
     }
 
     return String.fromCharCodes(_chars.sublist(start, _offset));
   }
 
-  void unget(String ch) {
+  void unget(String? ch) {
     // Only one character is allowed to be ungotten at once - it must
     // be consumed again before any further call to unget
     if (ch != null) {
@@ -305,7 +305,7 @@
 
 /// Return the python codec name corresponding to an encoding or null if the
 /// string doesn't correspond to a valid encoding.
-String codecName(String encoding) {
+String? codecName(String? encoding) {
   final asciiPunctuation = RegExp(
       '[\u0009-\u000D\u0020-\u002F\u003A-\u0040\u005B-\u0060\u007B-\u007E]');
 
@@ -317,7 +317,7 @@
 /// Returns true if the [bytes] starts with a UTF-8 byte order mark.
 /// Since UTF-8 doesn't have byte order, it's somewhat of a misnomer, but it is
 /// used in HTML to detect the UTF-
-bool _hasUtf8Bom(List<int> bytes, [int offset = 0, int length]) {
+bool _hasUtf8Bom(List<int> bytes, [int offset = 0, int? length]) {
   final end = length != null ? offset + length : bytes.length;
   return (offset + 3) <= end &&
       bytes[offset] == 0xEF &&
diff --git a/lib/src/list_proxy.dart b/lib/src/list_proxy.dart
index ef271ab..61784c7 100644
--- a/lib/src/list_proxy.dart
+++ b/lib/src/list_proxy.dart
@@ -8,7 +8,7 @@
   final List<E> _list = <E>[];
 
   @override
-  bool remove(Object item) => _list.remove(item);
+  bool remove(Object? item) => _list.remove(item);
 
   @override
   int get length => _list.length;
diff --git a/lib/src/query_selector.dart b/lib/src/query_selector.dart
index 9d09857..8880cfe 100644
--- a/lib/src/query_selector.dart
+++ b/lib/src/query_selector.dart
@@ -10,7 +10,7 @@
 bool matches(Element node, String selector) =>
     SelectorEvaluator().matches(node, _parseSelectorList(selector));
 
-Element querySelector(Node node, String selector) =>
+Element? querySelector(Node node, String selector) =>
     SelectorEvaluator().querySelector(node, _parseSelectorList(selector));
 
 List<Element> querySelectorAll(Node node, String selector) {
@@ -32,14 +32,14 @@
 
 class SelectorEvaluator extends Visitor {
   /// The current HTML element to match against.
-  Element _element;
+  Element? _element;
 
   bool matches(Element element, SelectorGroup selector) {
     _element = element;
     return visitSelectorGroup(selector);
   }
 
-  Element querySelector(Node root, SelectorGroup selector) {
+  Element? querySelector(Node root, SelectorGroup selector) {
     for (var element in root.nodes.whereType<Element>()) {
       if (matches(element, selector)) return element;
       final result = querySelector(element, selector);
@@ -66,7 +66,7 @@
     var result = true;
 
     // Note: evaluate selectors right-to-left as it's more efficient.
-    int combinator;
+    int? combinator;
     for (var s in selector.simpleSelectorSequences.reversed) {
       if (combinator == null) {
         result = s.simpleSelector.visit(this) as bool;
@@ -74,7 +74,7 @@
         // descendant combinator
         // http://dev.w3.org/csswg/selectors-4/#descendant-combinators
         do {
-          _element = _element.parent;
+          _element = _element!.parent;
         } while (_element != null && !(s.simpleSelector.visit(this) as bool));
 
         if (_element == null) result = false;
@@ -82,7 +82,7 @@
         // Following-sibling combinator
         // http://dev.w3.org/csswg/selectors-4/#general-sibling-combinators
         do {
-          _element = _element.previousElementSibling;
+          _element = _element!.previousElementSibling;
         } while (_element != null && !(s.simpleSelector.visit(this) as bool));
 
         if (_element == null) result = false;
@@ -94,12 +94,12 @@
         case TokenKind.COMBINATOR_PLUS:
           // Next-sibling combinator
           // http://dev.w3.org/csswg/selectors-4/#adjacent-sibling-combinators
-          _element = _element.previousElementSibling;
+          _element = _element!.previousElementSibling;
           break;
         case TokenKind.COMBINATOR_GREATER:
           // Child combinator
           // http://dev.w3.org/csswg/selectors-4/#child-combinators
-          _element = _element.parent;
+          _element = _element!.parent;
           break;
         case TokenKind.COMBINATOR_DESCENDANT:
         case TokenKind.COMBINATOR_TILDE:
@@ -139,34 +139,34 @@
       case 'root':
         // TODO(jmesserly): fix when we have a .ownerDocument pointer
         // return _element == _element.ownerDocument.rootElement;
-        return _element.localName == 'html' && _element.parentNode == null;
+        return _element!.localName == 'html' && _element!.parentNode == null;
 
       // http://dev.w3.org/csswg/selectors-4/#the-empty-pseudo
       case 'empty':
-        return _element.nodes
+        return _element!.nodes
             .any((n) => !(n is Element || n is Text && n.text.isNotEmpty));
 
       // http://dev.w3.org/csswg/selectors-4/#the-blank-pseudo
       case 'blank':
-        return _element.nodes.any((n) => !(n is Element ||
+        return _element!.nodes.any((n) => !(n is Element ||
             n is Text && n.text.runes.any((r) => !isWhitespaceCC(r))));
 
       // http://dev.w3.org/csswg/selectors-4/#the-first-child-pseudo
       case 'first-child':
-        return _element.previousElementSibling == null;
+        return _element!.previousElementSibling == null;
 
       // http://dev.w3.org/csswg/selectors-4/#the-last-child-pseudo
       case 'last-child':
-        return _element.nextElementSibling == null;
+        return _element!.nextElementSibling == null;
 
       // http://dev.w3.org/csswg/selectors-4/#the-only-child-pseudo
       case 'only-child':
-        return _element.previousElementSibling == null &&
-            _element.nextElementSibling == null;
+        return _element!.previousElementSibling == null &&
+            _element!.nextElementSibling == null;
 
       // http://dev.w3.org/csswg/selectors-4/#link
       case 'link':
-        return _element.attributes['href'] != null;
+        return _element!.attributes['href'] != null;
 
       case 'visited':
         // Always return false since we aren't a browser. This is allowed per:
@@ -215,7 +215,7 @@
         final exprs = selector.expression.expressions;
         if (exprs.length == 1 && exprs[0] is LiteralTerm) {
           final literal = exprs[0] as LiteralTerm;
-          final parent = _element.parentNode;
+          final parent = _element!.parentNode;
           return parent != null &&
               (literal.value as num) > 0 &&
               parent.nodes.indexOf(_element) == literal.value;
@@ -234,7 +234,7 @@
     throw _unimplemented(selector);
   }
 
-  static String _getInheritedLanguage(Node node) {
+  static String? _getInheritedLanguage(Node? node) {
     while (node != null) {
       final lang = node.attributes['lang'];
       if (lang != null) return lang;
@@ -246,37 +246,37 @@
   @override
   bool visitNamespaceSelector(NamespaceSelector selector) {
     // Match element tag name
-    if (!(selector.nameAsSimpleSelector.visit(this) as bool)) return false;
+    if (!(selector.nameAsSimpleSelector!.visit(this) as bool)) return false;
 
     if (selector.isNamespaceWildcard) return true;
 
-    if (selector.namespace == '') return _element.namespaceUri == null;
+    if (selector.namespace == '') return _element!.namespaceUri == null;
 
     throw _unimplemented(selector);
   }
 
   @override
   bool visitElementSelector(ElementSelector selector) =>
-      selector.isWildcard || _element.localName == selector.name.toLowerCase();
+      selector.isWildcard || _element!.localName == selector.name.toLowerCase();
 
   @override
-  bool visitIdSelector(IdSelector selector) => _element.id == selector.name;
+  bool visitIdSelector(IdSelector selector) => _element!.id == selector.name;
 
   @override
   bool visitClassSelector(ClassSelector selector) =>
-      _element.classes.contains(selector.name);
+      _element!.classes.contains(selector.name);
 
   // TODO(jmesserly): negation should support any selectors in level 4,
   // not just simple selectors.
   // http://dev.w3.org/csswg/selectors-4/#negation
   @override
   bool visitNegationSelector(NegationSelector selector) =>
-      !(selector.negationArg.visit(this) as bool);
+      !(selector.negationArg!.visit(this) as bool);
 
   @override
   bool visitAttributeSelector(AttributeSelector selector) {
     // Match name first
-    final value = _element.attributes[selector.name.toLowerCase()];
+    final value = _element!.attributes[selector.name.toLowerCase()];
     if (value == null) return false;
 
     if (selector.operatorKind == TokenKind.NO_MATCH) return true;
diff --git a/lib/src/token.dart b/lib/src/token.dart
index 1869b4f..b2c3f40 100644
--- a/lib/src/token.dart
+++ b/lib/src/token.dart
@@ -6,13 +6,13 @@
 
 /// An html5 token.
 abstract class Token {
-  FileSpan span;
+  FileSpan? span;
 
   int get kind;
 }
 
 abstract class TagToken extends Token {
-  String name;
+  String? name;
 
   bool selfClosing;
 
@@ -25,26 +25,27 @@
   LinkedHashMap<Object, String> data;
 
   /// The attribute spans if requested. Otherwise null.
-  List<TagAttribute> attributeSpans;
+  List<TagAttribute>? attributeSpans;
 
   bool selfClosingAcknowledged;
 
   /// The namespace. This is filled in later during tree building.
-  String namespace;
+  String? namespace;
 
-  StartTagToken(String name,
-      {this.data,
+  StartTagToken(String? name,
+      {LinkedHashMap<Object, String>? data,
       bool selfClosing = false,
       this.selfClosingAcknowledged = false,
       this.namespace})
-      : super(name, selfClosing);
+      : data = data ?? LinkedHashMap(),
+        super(name, selfClosing);
 
   @override
   int get kind => TokenKind.startTag;
 }
 
 class EndTagToken extends TagToken {
-  EndTagToken(String name, {bool selfClosing = false})
+  EndTagToken(String? name, {bool selfClosing = false})
       : super(name, selfClosing);
 
   @override
@@ -52,28 +53,28 @@
 }
 
 abstract class StringToken extends Token {
-  StringBuffer _buffer;
+  StringBuffer? _buffer;
 
-  String _string;
+  String? _string;
   String get data {
     if (_string == null) {
       _string = _buffer.toString();
       _buffer = null;
     }
-    return _string;
+    return _string!;
   }
 
   StringToken(this._string) : _buffer = _string == null ? StringBuffer() : null;
 
   StringToken add(String data) {
-    _buffer.write(data);
+    _buffer!.write(data);
     return this;
   }
 }
 
 class ParseErrorToken extends StringToken {
   /// Extra information that goes along with the error message.
-  Map messageParams;
+  Map? messageParams;
 
   ParseErrorToken(String data, {this.messageParams}) : super(data);
 
@@ -82,7 +83,7 @@
 }
 
 class CharactersToken extends StringToken {
-  CharactersToken([String data]) : super(data);
+  CharactersToken([String? data]) : super(data);
 
   @override
   int get kind => TokenKind.characters;
@@ -96,23 +97,23 @@
 }
 
 class SpaceCharactersToken extends StringToken {
-  SpaceCharactersToken([String data]) : super(data);
+  SpaceCharactersToken([String? data]) : super(data);
 
   @override
   int get kind => TokenKind.spaceCharacters;
 }
 
 class CommentToken extends StringToken {
-  CommentToken([String data]) : super(data);
+  CommentToken([String? data]) : super(data);
 
   @override
   int get kind => TokenKind.comment;
 }
 
 class DoctypeToken extends Token {
-  String publicId;
-  String systemId;
-  String name = '';
+  String? publicId;
+  String? systemId;
+  String? name = '';
   bool correct;
 
   DoctypeToken({this.publicId, this.systemId, this.correct = false});
@@ -125,15 +126,15 @@
 /// They're also used by [StartTagToken.attributeSpans] if attribute spans are
 /// requested.
 class TagAttribute {
-  String name;
-  String value;
+  String? name;
+  late String value;
 
   // The spans of the attribute. This is not used unless we are computing an
   // attribute span on demand.
-  int start;
-  int end;
-  int startValue;
-  int endValue;
+  late int start;
+  late int end;
+  int? startValue;
+  late int endValue;
 
   TagAttribute();
 }
diff --git a/lib/src/tokenizer.dart b/lib/src/tokenizer.dart
index 2737a7e..4ee70fc 100644
--- a/lib/src/tokenizer.dart
+++ b/lib/src/tokenizer.dart
@@ -43,33 +43,33 @@
 
   /// This reference to the parser is used for correct CDATA handling.
   /// The [HtmlParser] will set this at construction time.
-  HtmlParser parser;
+  HtmlParser? parser;
 
-  final Queue<Token> tokenQueue;
+  final Queue<Token?> tokenQueue;
 
   /// Holds the token that is currently being processed.
-  Token currentToken;
+  Token? currentToken;
 
   /// Holds a reference to the method to be invoked for the next parser state.
-  bool Function() state;
+  late bool Function() state;
 
   final StringBuffer _buffer = StringBuffer();
 
-  int _lastOffset;
+  late int _lastOffset;
 
   // TODO(jmesserly): ideally this would be a LinkedHashMap and we wouldn't add
   // an item until it's ready. But the code doesn't have a clear notion of when
   // it's "done" with the attribute.
-  List<TagAttribute> _attributes;
-  Set<String> _attributeNames;
+  List<TagAttribute>? _attributes;
+  Set<String>? _attributeNames;
 
   HtmlTokenizer(doc,
-      {String encoding,
+      {String? encoding,
       bool parseMeta = true,
       this.lowercaseElementName = true,
       this.lowercaseAttrName = true,
       this.generateSpans = false,
-      String sourceUrl,
+      String? sourceUrl,
       this.attributeSpans = false})
       : stream =
             HtmlInputStream(doc, encoding, parseMeta, generateSpans, sourceUrl),
@@ -81,24 +81,24 @@
   DoctypeToken get currentDoctypeToken => currentToken as DoctypeToken;
   StringToken get currentStringToken => currentToken as StringToken;
 
-  Token _current;
+  Token? _current;
   @override
-  Token get current => _current;
+  Token get current => _current!;
 
   final StringBuffer _attributeName = StringBuffer();
   final StringBuffer _attributeValue = StringBuffer();
 
   void _markAttributeEnd(int offset) {
-    _attributes.last.value = '$_attributeValue';
-    if (attributeSpans) _attributes.last.end = stream.position + offset;
+    _attributes!.last.value = '$_attributeValue';
+    if (attributeSpans) _attributes!.last.end = stream.position + offset;
   }
 
   void _markAttributeValueStart(int offset) {
-    if (attributeSpans) _attributes.last.startValue = stream.position + offset;
+    if (attributeSpans) _attributes!.last.startValue = stream.position + offset;
   }
 
   void _markAttributeValueEnd(int offset) {
-    if (attributeSpans) _attributes.last.endValue = stream.position + offset;
+    if (attributeSpans) _attributes!.last.endValue = stream.position + offset;
     _markAttributeEnd(offset);
   }
 
@@ -111,7 +111,7 @@
     _attributeName.write(name);
     _attributeValue.clear();
     final attr = TagAttribute();
-    _attributes.add(attr);
+    _attributes!.add(attr);
     if (attributeSpans) attr.start = stream.position - name.length;
   }
 
@@ -155,7 +155,7 @@
   void _addToken(Token token) {
     if (generateSpans && token.span == null) {
       final offset = stream.position;
-      token.span = stream.fileInfo.span(_lastOffset, offset);
+      token.span = stream.fileInfo!.span(_lastOffset, offset);
       if (token is! ParseErrorToken) {
         _lastOffset = offset;
       }
@@ -255,9 +255,9 @@
     return char;
   }
 
-  void consumeEntity({String allowedChar, bool fromAttribute = false}) {
+  void consumeEntity({String? allowedChar, bool fromAttribute = false}) {
     // Initialise to the default output for when no entity is matched
-    var output = '&';
+    String? output = '&';
 
     final charStack = [stream.char()];
     if (isWhitespace(charStack[0]) ||
@@ -293,7 +293,7 @@
       //
       // Consume characters and compare to these to a substring of the
       // entity names in the list until the substring no longer matches.
-      var filteredEntityList = entitiesByFirstChar[charStack[0]] ?? const [];
+      var filteredEntityList = entitiesByFirstChar[charStack[0]!] ?? const [];
 
       while (charStack.last != eof) {
         final name = charStack.join();
@@ -308,7 +308,7 @@
 
       // At this point we have a string that starts with some characters
       // that may match an entity
-      String entityName;
+      String? entityName;
 
       // Try to find the longest entity the string will match to take care
       // of &noti for instance.
@@ -366,7 +366,7 @@
   /// the state to "data" because that's what's needed after a token has been
   /// emitted.
   void emitCurrentToken() {
-    final token = currentToken;
+    final token = currentToken!;
     // Add token to the queue to be yielded
     if (token is TagToken) {
       if (lowercaseElementName) {
@@ -384,8 +384,8 @@
         // Convert the list into a map where first key wins.
         token.data = LinkedHashMap<Object, String>();
         if (_attributes != null) {
-          for (var attr in _attributes) {
-            token.data.putIfAbsent(attr.name, () => attr.value);
+          for (var attr in _attributes!) {
+            token.data.putIfAbsent(attr.name!, () => attr.value);
           }
           if (attributeSpans) token.attributeSpans = _attributes;
         }
@@ -616,7 +616,7 @@
   bool _tokenIsAppropriate() {
     // TODO(jmesserly): this should use case insensitive compare instead.
     return currentToken is TagToken &&
-        currentTagToken.name.toLowerCase() == '$_buffer'.toLowerCase();
+        currentTagToken.name!.toLowerCase() == '$_buffer'.toLowerCase();
   }
 
   bool rcdataEndTagNameState() {
@@ -1006,7 +1006,7 @@
     final data = stream.char();
     if (isWhitespace(data)) {
       stream.charsUntil(spaceCharacters, true);
-    } else if (isLetter(data)) {
+    } else if (data != null && isLetter(data)) {
       _addAttribute(data);
       state = attributeNameState;
     } else if (data == '>') {
@@ -1016,7 +1016,7 @@
     } else if (data == eof) {
       _addToken(ParseErrorToken('expected-attribute-name-but-got-eof'));
       state = dataState;
-    } else if ("'\"=<".contains(data)) {
+    } else if ("'\"=<".contains(data!)) {
       _addToken(ParseErrorToken('invalid-character-in-attribute-name'));
       _addAttribute(data);
       state = attributeNameState;
@@ -1057,7 +1057,7 @@
     } else if (data == eof) {
       _addToken(ParseErrorToken('eof-in-attribute-name'));
       state = dataState;
-    } else if ("'\"<".contains(data)) {
+    } else if ("'\"<".contains(data!)) {
       _addToken(ParseErrorToken('invalid-character-in-attribute-name'));
       _attributeName.write(data);
       leavingThisState = false;
@@ -1076,12 +1076,12 @@
       if (lowercaseAttrName) {
         attrName = attrName.toAsciiLowerCase();
       }
-      _attributes.last.name = attrName;
+      _attributes!.last.name = attrName;
       _attributeNames ??= {};
-      if (_attributeNames.contains(attrName)) {
+      if (_attributeNames!.contains(attrName)) {
         _addToken(ParseErrorToken('duplicate-attribute'));
       }
-      _attributeNames.add(attrName);
+      _attributeNames!.add(attrName);
 
       // XXX Fix for above XXX
       if (emitToken) {
@@ -1099,7 +1099,7 @@
       state = beforeAttributeValueState;
     } else if (data == '>') {
       emitCurrentToken();
-    } else if (isLetter(data)) {
+    } else if (data != null && isLetter(data)) {
       _addAttribute(data);
       state = attributeNameState;
     } else if (data == '/') {
@@ -1111,7 +1111,7 @@
     } else if (data == eof) {
       _addToken(ParseErrorToken('expected-end-of-tag-but-got-eof'));
       state = dataState;
-    } else if ("'\"<".contains(data)) {
+    } else if ("'\"<".contains(data!)) {
       _addToken(ParseErrorToken('invalid-character-after-attribute-name'));
       _addAttribute(data);
       state = attributeNameState;
@@ -1148,7 +1148,7 @@
     } else if (data == eof) {
       _addToken(ParseErrorToken('expected-attribute-value-but-got-eof'));
       state = dataState;
-    } else if ('=<`'.contains(data)) {
+    } else if ('=<`'.contains(data!)) {
       _addToken(ParseErrorToken('equals-in-unquoted-attribute-value'));
       _markAttributeValueStart(-1);
       _attributeValue.write(data);
@@ -1219,7 +1219,7 @@
       _addToken(ParseErrorToken('eof-in-attribute-value-no-quotes'));
       _markAttributeValueEnd(-1);
       state = dataState;
-    } else if ('"\'=<`'.contains(data)) {
+    } else if ('"\'=<`'.contains(data!)) {
       _addToken(
           ParseErrorToken('unexpected-character-in-unquoted-attribute-value'));
       _attributeValue.write(data);
@@ -1299,7 +1299,7 @@
       for (var expected in const ['oO', 'cC', 'tT', 'yY', 'pP', 'eE']) {
         final char = stream.char();
         charStack.add(char);
-        if (char == eof || !expected.contains(char)) {
+        if (char == eof || !expected.contains(char!)) {
           matched = false;
           break;
         }
@@ -1311,9 +1311,9 @@
       }
     } else if (charStack.last == '[' &&
         parser != null &&
-        parser.tree.openElements.isNotEmpty &&
-        parser.tree.openElements.last.namespaceUri !=
-            parser.tree.defaultNamespace) {
+        parser!.tree.openElements.isNotEmpty &&
+        parser!.tree.openElements.last.namespaceUri !=
+            parser!.tree.defaultNamespace) {
       var matched = true;
       for (var expected in const ['C', 'D', 'A', 'T', 'A', '[']) {
         charStack.add(stream.char());
@@ -1346,14 +1346,14 @@
       currentStringToken.add('\uFFFD');
     } else if (data == '>') {
       _addToken(ParseErrorToken('incorrect-comment'));
-      _addToken(currentToken);
+      _addToken(currentToken!);
       state = dataState;
     } else if (data == eof) {
       _addToken(ParseErrorToken('eof-in-comment'));
-      _addToken(currentToken);
+      _addToken(currentToken!);
       state = dataState;
     } else {
-      currentStringToken.add(data);
+      currentStringToken.add(data!);
       state = commentState;
     }
     return true;
@@ -1368,14 +1368,14 @@
       currentStringToken.add('-\uFFFD');
     } else if (data == '>') {
       _addToken(ParseErrorToken('incorrect-comment'));
-      _addToken(currentToken);
+      _addToken(currentToken!);
       state = dataState;
     } else if (data == eof) {
       _addToken(ParseErrorToken('eof-in-comment'));
-      _addToken(currentToken);
+      _addToken(currentToken!);
       state = dataState;
     } else {
-      currentStringToken.add('-').add(data);
+      currentStringToken.add('-').add(data!);
       state = commentState;
     }
     return true;
@@ -1390,10 +1390,10 @@
       currentStringToken.add('\uFFFD');
     } else if (data == eof) {
       _addToken(ParseErrorToken('eof-in-comment'));
-      _addToken(currentToken);
+      _addToken(currentToken!);
       state = dataState;
     } else {
-      currentStringToken.add(data).add(stream.charsUntil('-\u0000'));
+      currentStringToken.add(data!).add(stream.charsUntil('-\u0000'));
     }
     return true;
   }
@@ -1408,10 +1408,10 @@
       state = commentState;
     } else if (data == eof) {
       _addToken(ParseErrorToken('eof-in-comment-end-dash'));
-      _addToken(currentToken);
+      _addToken(currentToken!);
       state = dataState;
     } else {
-      currentStringToken.add('-').add(data);
+      currentStringToken.add('-').add(data!);
       state = commentState;
     }
     return true;
@@ -1420,7 +1420,7 @@
   bool commentEndState() {
     final data = stream.char();
     if (data == '>') {
-      _addToken(currentToken);
+      _addToken(currentToken!);
       state = dataState;
     } else if (data == '\u0000') {
       _addToken(ParseErrorToken('invalid-codepoint'));
@@ -1433,15 +1433,15 @@
     } else if (data == '-') {
       _addToken(
           ParseErrorToken('unexpected-dash-after-double-dash-in-comment'));
-      currentStringToken.add(data);
+      currentStringToken.add(data!);
     } else if (data == eof) {
       _addToken(ParseErrorToken('eof-in-comment-double-dash'));
-      _addToken(currentToken);
+      _addToken(currentToken!);
       state = dataState;
     } else {
       // XXX
       _addToken(ParseErrorToken('unexpected-char-in-comment'));
-      currentStringToken.add('--').add(data);
+      currentStringToken.add('--').add(data!);
       state = commentState;
     }
     return true;
@@ -1450,7 +1450,7 @@
   bool commentEndBangState() {
     final data = stream.char();
     if (data == '>') {
-      _addToken(currentToken);
+      _addToken(currentToken!);
       state = dataState;
     } else if (data == '-') {
       currentStringToken.add('--!');
@@ -1461,10 +1461,10 @@
       state = commentState;
     } else if (data == eof) {
       _addToken(ParseErrorToken('eof-in-comment-end-bang-state'));
-      _addToken(currentToken);
+      _addToken(currentToken!);
       state = dataState;
     } else {
-      currentStringToken.add('--!').add(data);
+      currentStringToken.add('--!').add(data!);
       state = commentState;
     }
     return true;
@@ -1477,7 +1477,7 @@
     } else if (data == eof) {
       _addToken(ParseErrorToken('expected-doctype-name-but-got-eof'));
       currentDoctypeToken.correct = false;
-      _addToken(currentToken);
+      _addToken(currentToken!);
       state = dataState;
     } else {
       _addToken(ParseErrorToken('need-space-after-doctype'));
@@ -1494,7 +1494,7 @@
     } else if (data == '>') {
       _addToken(ParseErrorToken('expected-doctype-name-but-got-right-bracket'));
       currentDoctypeToken.correct = false;
-      _addToken(currentToken);
+      _addToken(currentToken!);
       state = dataState;
     } else if (data == '\u0000') {
       _addToken(ParseErrorToken('invalid-codepoint'));
@@ -1503,7 +1503,7 @@
     } else if (data == eof) {
       _addToken(ParseErrorToken('expected-doctype-name-but-got-eof'));
       currentDoctypeToken.correct = false;
-      _addToken(currentToken);
+      _addToken(currentToken!);
       state = dataState;
     } else {
       currentDoctypeToken.name = data;
@@ -1519,7 +1519,7 @@
       state = afterDoctypeNameState;
     } else if (data == '>') {
       currentDoctypeToken.name = currentDoctypeToken.name?.toAsciiLowerCase();
-      _addToken(currentToken);
+      _addToken(currentToken!);
       state = dataState;
     } else if (data == '\u0000') {
       _addToken(ParseErrorToken('invalid-codepoint'));
@@ -1529,7 +1529,7 @@
       _addToken(ParseErrorToken('eof-in-doctype-name'));
       currentDoctypeToken.correct = false;
       currentDoctypeToken.name = currentDoctypeToken.name?.toAsciiLowerCase();
-      _addToken(currentToken);
+      _addToken(currentToken!);
       state = dataState;
     } else {
       currentDoctypeToken.name = '${currentDoctypeToken.name}$data';
@@ -1542,13 +1542,13 @@
     if (isWhitespace(data)) {
       return true;
     } else if (data == '>') {
-      _addToken(currentToken);
+      _addToken(currentToken!);
       state = dataState;
     } else if (data == eof) {
       currentDoctypeToken.correct = false;
       stream.unget(data);
       _addToken(ParseErrorToken('eof-in-doctype'));
-      _addToken(currentToken);
+      _addToken(currentToken!);
       state = dataState;
     } else {
       if (data == 'p' || data == 'P') {
@@ -1556,7 +1556,7 @@
         var matched = true;
         for (var expected in const ['uU', 'bB', 'lL', 'iI', 'cC']) {
           data = stream.char();
-          if (data == eof || !expected.contains(data)) {
+          if (data == eof || !expected.contains(data!)) {
             matched = false;
             break;
           }
@@ -1569,7 +1569,7 @@
         var matched = true;
         for (var expected in const ['yY', 'sS', 'tT', 'eE', 'mM']) {
           data = stream.char();
-          if (data == eof || !expected.contains(data)) {
+          if (data == eof || !expected.contains(data!)) {
             matched = false;
             break;
           }
@@ -1604,7 +1604,7 @@
     } else if (data == eof) {
       _addToken(ParseErrorToken('eof-in-doctype'));
       currentDoctypeToken.correct = false;
-      _addToken(currentToken);
+      _addToken(currentToken!);
       state = dataState;
     } else {
       stream.unget(data);
@@ -1626,12 +1626,12 @@
     } else if (data == '>') {
       _addToken(ParseErrorToken('unexpected-end-of-doctype'));
       currentDoctypeToken.correct = false;
-      _addToken(currentToken);
+      _addToken(currentToken!);
       state = dataState;
     } else if (data == eof) {
       _addToken(ParseErrorToken('eof-in-doctype'));
       currentDoctypeToken.correct = false;
-      _addToken(currentToken);
+      _addToken(currentToken!);
       state = dataState;
     } else {
       _addToken(ParseErrorToken('unexpected-char-in-doctype'));
@@ -1651,12 +1651,12 @@
     } else if (data == '>') {
       _addToken(ParseErrorToken('unexpected-end-of-doctype'));
       currentDoctypeToken.correct = false;
-      _addToken(currentToken);
+      _addToken(currentToken!);
       state = dataState;
     } else if (data == eof) {
       _addToken(ParseErrorToken('eof-in-doctype'));
       currentDoctypeToken.correct = false;
-      _addToken(currentToken);
+      _addToken(currentToken!);
       state = dataState;
     } else {
       currentDoctypeToken.publicId = '${currentDoctypeToken.publicId}$data';
@@ -1674,12 +1674,12 @@
     } else if (data == '>') {
       _addToken(ParseErrorToken('unexpected-end-of-doctype'));
       currentDoctypeToken.correct = false;
-      _addToken(currentToken);
+      _addToken(currentToken!);
       state = dataState;
     } else if (data == eof) {
       _addToken(ParseErrorToken('eof-in-doctype'));
       currentDoctypeToken.correct = false;
-      _addToken(currentToken);
+      _addToken(currentToken!);
       state = dataState;
     } else {
       currentDoctypeToken.publicId = '${currentDoctypeToken.publicId}$data';
@@ -1692,7 +1692,7 @@
     if (isWhitespace(data)) {
       state = betweenDoctypePublicAndSystemIdentifiersState;
     } else if (data == '>') {
-      _addToken(currentToken);
+      _addToken(currentToken!);
       state = dataState;
     } else if (data == '"') {
       _addToken(ParseErrorToken('unexpected-char-in-doctype'));
@@ -1705,7 +1705,7 @@
     } else if (data == eof) {
       _addToken(ParseErrorToken('eof-in-doctype'));
       currentDoctypeToken.correct = false;
-      _addToken(currentToken);
+      _addToken(currentToken!);
       state = dataState;
     } else {
       _addToken(ParseErrorToken('unexpected-char-in-doctype'));
@@ -1720,7 +1720,7 @@
     if (isWhitespace(data)) {
       return true;
     } else if (data == '>') {
-      _addToken(currentToken);
+      _addToken(currentToken!);
       state = dataState;
     } else if (data == '"') {
       currentDoctypeToken.systemId = '';
@@ -1731,7 +1731,7 @@
     } else if (data == eof) {
       _addToken(ParseErrorToken('eof-in-doctype'));
       currentDoctypeToken.correct = false;
-      _addToken(currentToken);
+      _addToken(currentToken!);
       state = dataState;
     } else {
       _addToken(ParseErrorToken('unexpected-char-in-doctype'));
@@ -1752,7 +1752,7 @@
     } else if (data == eof) {
       _addToken(ParseErrorToken('eof-in-doctype'));
       currentDoctypeToken.correct = false;
-      _addToken(currentToken);
+      _addToken(currentToken!);
       state = dataState;
     } else {
       stream.unget(data);
@@ -1774,12 +1774,12 @@
     } else if (data == '>') {
       _addToken(ParseErrorToken('unexpected-char-in-doctype'));
       currentDoctypeToken.correct = false;
-      _addToken(currentToken);
+      _addToken(currentToken!);
       state = dataState;
     } else if (data == eof) {
       _addToken(ParseErrorToken('eof-in-doctype'));
       currentDoctypeToken.correct = false;
-      _addToken(currentToken);
+      _addToken(currentToken!);
       state = dataState;
     } else {
       _addToken(ParseErrorToken('unexpected-char-in-doctype'));
@@ -1799,12 +1799,12 @@
     } else if (data == '>') {
       _addToken(ParseErrorToken('unexpected-end-of-doctype'));
       currentDoctypeToken.correct = false;
-      _addToken(currentToken);
+      _addToken(currentToken!);
       state = dataState;
     } else if (data == eof) {
       _addToken(ParseErrorToken('eof-in-doctype'));
       currentDoctypeToken.correct = false;
-      _addToken(currentToken);
+      _addToken(currentToken!);
       state = dataState;
     } else {
       currentDoctypeToken.systemId = '${currentDoctypeToken.systemId}$data';
@@ -1822,12 +1822,12 @@
     } else if (data == '>') {
       _addToken(ParseErrorToken('unexpected-end-of-doctype'));
       currentDoctypeToken.correct = false;
-      _addToken(currentToken);
+      _addToken(currentToken!);
       state = dataState;
     } else if (data == eof) {
       _addToken(ParseErrorToken('eof-in-doctype'));
       currentDoctypeToken.correct = false;
-      _addToken(currentToken);
+      _addToken(currentToken!);
       state = dataState;
     } else {
       currentDoctypeToken.systemId = '${currentDoctypeToken.systemId}$data';
@@ -1840,12 +1840,12 @@
     if (isWhitespace(data)) {
       return true;
     } else if (data == '>') {
-      _addToken(currentToken);
+      _addToken(currentToken!);
       state = dataState;
     } else if (data == eof) {
       _addToken(ParseErrorToken('eof-in-doctype'));
       currentDoctypeToken.correct = false;
-      _addToken(currentToken);
+      _addToken(currentToken!);
       state = dataState;
     } else {
       _addToken(ParseErrorToken('unexpected-char-in-doctype'));
@@ -1857,12 +1857,12 @@
   bool bogusDoctypeState() {
     final data = stream.char();
     if (data == '>') {
-      _addToken(currentToken);
+      _addToken(currentToken!);
       state = dataState;
     } else if (data == eof) {
       // XXX EMIT
       stream.unget(data);
-      _addToken(currentToken);
+      _addToken(currentToken!);
       state = dataState;
     }
     return true;
@@ -1873,7 +1873,7 @@
     var matchedEnd = 0;
     while (true) {
       var ch = stream.char();
-      if (ch == eof) {
+      if (ch == null) {
         break;
       }
       // Deal with null here rather than in the parser
diff --git a/lib/src/treebuilder.dart b/lib/src/treebuilder.dart
index 3687464..04c3a19 100644
--- a/lib/src/treebuilder.dart
+++ b/lib/src/treebuilder.dart
@@ -16,7 +16,7 @@
 /// entering some elements.
 ///
 /// https://html.spec.whatwg.org/multipage/parsing.html#list-of-active-formatting-elements
-class ActiveFormattingElements extends ListProxy<Element /*?*/ > {
+class ActiveFormattingElements extends ListProxy<Element?> {
   /// Push an element into the active formatting elements.
   ///
   /// Prevents equivalent elements from appearing more than 3 times following
@@ -25,7 +25,7 @@
   // TODO - Earliest equivalent following a marker, as opposed to earliest
   // identical regardless of marker position, should be removed.
   @override
-  void add(Element node) {
+  void add(Element? node) {
     var equalCount = 0;
     if (node != null) {
       for (var element in reversed) {
@@ -71,21 +71,21 @@
 
 /// Basic treebuilder implementation.
 class TreeBuilder {
-  final String defaultNamespace;
+  final String? defaultNamespace;
 
-  Document document;
+  var document = Document();
 
   final List<Element> openElements = <Element>[];
 
   final activeFormattingElements = ActiveFormattingElements();
 
-  Node headPointer;
+  Node? headPointer;
 
-  Element formPointer;
+  Element? formPointer;
 
   /// Switch the function used to insert an element from the
   /// normal one to the misnested table one and back again
-  bool insertFromTable;
+  var insertFromTable = false;
 
   TreeBuilder(bool namespaceHTMLElements)
       : defaultNamespace = namespaceHTMLElements ? Namespaces.html : null {
@@ -105,7 +105,7 @@
     document = Document();
   }
 
-  bool elementInScope(target, {String variant}) {
+  bool elementInScope(target, {String? variant}) {
     //If we pass a node in we match that. if we pass a string
     //match any node with that name
     final exactNode = target is Node;
@@ -193,7 +193,7 @@
       entry = activeFormattingElements[i];
 
       // TODO(jmesserly): optimize this. No need to create a token.
-      final cloneToken = StartTagToken(entry.localName,
+      final cloneToken = StartTagToken(entry!.localName,
           namespace: entry.namespaceUri,
           data: LinkedHashMap.from(entry.attributes))
         ..span = entry.sourceSpan;
@@ -221,7 +221,7 @@
   /// Check if an element exists between the end of the active
   /// formatting elements and the last marker. If it does, return it, else
   /// return null.
-  Element elementInActiveFormattingElements(String name) {
+  Element? elementInActiveFormattingElements(String? name) {
     for (var item in activeFormattingElements.reversed) {
       // Check for Marker first because if it's a Marker it doesn't have a
       // name attribute.
@@ -246,7 +246,7 @@
     document.nodes.add(doctype);
   }
 
-  void insertComment(StringToken token, [Node parent]) {
+  void insertComment(StringToken token, [Node? parent]) {
     parent ??= openElements.last;
     parent.nodes.add(Comment(token.data)..sourceSpan = token.span);
   }
@@ -290,9 +290,9 @@
         // TODO(jmesserly): I don't think this is reachable. If insertFromTable
         // is true, there will be a <table> element open, and it always has a
         // parent pointer.
-        nodePos[0].nodes.add(element);
+        nodePos[0]!.nodes.add(element);
       } else {
-        nodePos[0].insertBefore(element, nodePos[1]);
+        nodePos[0]!.insertBefore(element, nodePos[1]);
       }
       openElements.add(element);
     }
@@ -300,7 +300,7 @@
   }
 
   /// Insert text data.
-  void insertText(String data, FileSpan span) {
+  void insertText(String data, FileSpan? span) {
     final parent = openElements.last;
 
     if (!insertFromTable ||
@@ -311,14 +311,14 @@
       // We should be in the InTable mode. This means we want to do
       // special magic element rearranging
       final nodePos = getTableMisnestedNodePosition();
-      _insertText(nodePos[0], data, span, nodePos[1] as Element);
+      _insertText(nodePos[0]!, data, span, nodePos[1] as Element?);
     }
   }
 
   /// Insert [data] as text in the current node, positioned before the
   /// start of node [refNode] or to the end of the node's text.
-  static void _insertText(Node parent, String data, FileSpan span,
-      [Element refNode]) {
+  static void _insertText(Node parent, String data, FileSpan? span,
+      [Element? refNode]) {
     final nodes = parent.nodes;
     if (refNode == null) {
       if (nodes.isNotEmpty && nodes.last is Text) {
@@ -327,7 +327,7 @@
 
         if (span != null) {
           last.sourceSpan =
-              span.file.span(last.sourceSpan.start.offset, span.end.offset);
+              span.file.span(last.sourceSpan!.start.offset, span.end.offset);
         }
       } else {
         nodes.add(Text(data)..sourceSpan = span);
@@ -345,13 +345,13 @@
 
   /// Get the foster parent element, and sibling to insert before
   /// (or null) when inserting a misnested table node
-  List<Node> getTableMisnestedNodePosition() {
+  List<Node?> getTableMisnestedNodePosition() {
     // The foster parent element is the one which comes before the most
     // recently opened table element
     // XXX - this is really inelegant
-    Element lastTable;
-    Node fosterParent;
-    Node insertBefore;
+    Element? lastTable;
+    Node? fosterParent;
+    Node? insertBefore;
     for (var elm in openElements.reversed) {
       if (elm.localName == 'table') {
         lastTable = elm;
@@ -373,7 +373,7 @@
     return [fosterParent, insertBefore];
   }
 
-  void generateImpliedEndTags([String exclude]) {
+  void generateImpliedEndTags([String? exclude]) {
     final name = openElements.last.localName;
     // XXX td, th and tr are not actually needed
     if (name != exclude &&
diff --git a/lib/src/utils.dart b/lib/src/utils.dart
index 559f6c3..001c706 100644
--- a/lib/src/utils.dart
+++ b/lib/src/utils.dart
@@ -10,14 +10,15 @@
   int get hashCode => 37 * first.hashCode + second.hashCode;
 
   @override
-  bool operator ==(other) => other.first == first && other.second == second;
+  bool operator ==(Object other) =>
+      other is Pair && other.first == first && other.second == second;
 }
 
 bool startsWithAny(String str, List<String> prefixes) =>
     prefixes.any(str.startsWith);
 
 // Like the python [:] operator.
-List<T> slice<T>(List<T> list, int start, [int end]) {
+List<T> slice<T>(List<T> list, int start, [int? end]) {
   end ??= list.length;
   if (end < 0) end += list.length;
 
@@ -50,7 +51,7 @@
 /// Format a string like Python's % string format operator. Right now this only
 /// supports a [data] dictionary used with %s or %08x. Those were the only
 /// things needed for [errorMessages].
-String formatStr(String format, Map data) {
+String formatStr(String format, Map? data) {
   if (data == null) return format;
   data.forEach((key, value) {
     final result = StringBuffer();
@@ -64,7 +65,7 @@
       while (isDigit(format[digits])) {
         digits++;
       }
-      int numberSize;
+      var numberSize = 0;
       if (digits > match) {
         numberSize = int.parse(format.substring(match, digits));
         match = digits;
diff --git a/pubspec.yaml b/pubspec.yaml
index a8fcab3..5dc06eb 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,17 +1,23 @@
 name: html
-version: 0.15.0-dev
+version: 0.15.0-nullsafety-dev
 
 description: APIs for parsing and manipulating HTML content outside the browser.
 homepage: https://github.com/dart-lang/html
 
+publish_to: none
+
 environment:
-  sdk: '>=2.8.0 <3.0.0'
+  sdk: ">=2.12.0-0 <3.0.0"
 
 dependencies:
-  csslib: '>=0.13.2 <0.17.0'
-  source_span: '>=1.0.0 <2.0.0'
+  csslib: ^0.17.0
+  source_span: ^1.8.0-nullsafety
 
 dev_dependencies:
-  path: ^1.6.2
-  pedantic: ^1.3.0
-  test: ^1.3.0
+  path: ^1.8.0-nullsafety
+  pedantic: ^1.10.0-nullsafety
+  test: ^1.16.0-nullsafety
+
+dependency_overrides:
+  csslib:
+    git: git://github.com/dart-lang/csslib.git
diff --git a/test/parser_feature_test.dart b/test/parser_feature_test.dart
index 0237172..7825a23 100644
--- a/test/parser_feature_test.dart
+++ b/test/parser_feature_test.dart
@@ -49,11 +49,11 @@
     expect(error.errorCode, 'unexpected-doctype');
 
     // Note: these values are 0-based, but the printed format is 1-based.
-    expect(error.span.start.line, 3);
-    expect(error.span.end.line, 3);
-    expect(error.span.start.column, 2);
-    expect(error.span.end.column, 17);
-    expect(error.span.text, '<!DOCTYPE html>');
+    expect(error.span!.start.line, 3);
+    expect(error.span!.end.line, 3);
+    expect(error.span!.start.column, 2);
+    expect(error.span!.end.column, 17);
+    expect(error.span!.text, '<!DOCTYPE html>');
 
     expect(error.toString(), '''
 On line 4, column 3 of ParseError: Unexpected DOCTYPE. Ignored.
@@ -77,9 +77,9 @@
     expect(parser.errors.length, 1);
     final error = parser.errors[0];
     expect(error.errorCode, 'unexpected-doctype');
-    expect(error.span.start.line, 3);
+    expect(error.span!.start.line, 3);
     // Note: error position is at the end, not the beginning
-    expect(error.span.start.column, 17);
+    expect(error.span!.start.column, 17);
   });
 
   test('text spans should have the correct length', () {
@@ -89,21 +89,21 @@
     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));
-    expect(text.sourceSpan.length, textContent.length);
+    expect(text.sourceSpan!.start.offset, html.indexOf(textContent));
+    expect(text.sourceSpan!.length, textContent.length);
   });
 
   test('attribute spans', () {
     final text = '<element name="x-foo" extends="x-bar" constructor="Foo">';
     final doc = parse(text, generateSpans: true);
-    final elem = doc.querySelector('element');
-    expect(elem.sourceSpan.start.offset, 0);
-    expect(elem.sourceSpan.end.offset, text.length);
-    expect(elem.sourceSpan.text, text);
+    final elem = doc.querySelector('element')!;
+    expect(elem.sourceSpan!.start.offset, 0);
+    expect(elem.sourceSpan!.end.offset, text.length);
+    expect(elem.sourceSpan!.text, text);
 
-    expect(elem.attributeSpans['quux'], null);
+    expect(elem.attributeSpans!['quux'], null);
 
-    final span = elem.attributeSpans['extends'];
+    final span = elem.attributeSpans!['extends']!;
     expect(span.start.offset, text.indexOf('extends'));
     expect(span.text, 'extends="x-bar"');
   });
@@ -111,11 +111,11 @@
   test('attribute value spans', () {
     final text = '<element name="x-foo" extends="x-bar" constructor="Foo">';
     final doc = parse(text, generateSpans: true);
-    final elem = doc.querySelector('element');
+    final elem = doc.querySelector('element')!;
 
-    expect(elem.attributeValueSpans['quux'], null);
+    expect(elem.attributeValueSpans!['quux'], null);
 
-    final span = elem.attributeValueSpans['extends'];
+    final span = elem.attributeValueSpans!['extends']!;
     expect(span.start.offset, text.indexOf('x-bar'));
     expect(span.text, 'x-bar');
   });
@@ -123,29 +123,29 @@
   test('attribute spans if no attributes', () {
     final text = '<element>';
     final doc = parse(text, generateSpans: true);
-    final elem = doc.querySelector('element');
+    final elem = doc.querySelector('element')!;
 
-    expect(elem.attributeSpans['quux'], null);
-    expect(elem.attributeValueSpans['quux'], null);
+    expect(elem.attributeSpans!['quux'], null);
+    expect(elem.attributeValueSpans!['quux'], null);
   });
 
   test('attribute spans if no attribute value', () {
     final text = '<foo template>';
     final doc = parse(text, generateSpans: true);
-    final elem = doc.querySelector('foo');
+    final elem = doc.querySelector('foo')!;
 
-    expect(
-        elem.attributeSpans['template'].start.offset, text.indexOf('template'));
-    expect(elem.attributeValueSpans.containsKey('template'), false);
+    expect(elem.attributeSpans!['template']!.start.offset,
+        text.indexOf('template'));
+    expect(elem.attributeValueSpans!.containsKey('template'), false);
   });
 
   test('attribute spans null if code parsed without spans', () {
     final text = '<element name="x-foo" extends="x-bar" constructor="Foo">';
     final doc = parse(text);
-    final elem = doc.querySelector('element');
+    final elem = doc.querySelector('element')!;
     expect(elem.sourceSpan, null);
-    expect(elem.attributeSpans['quux'], null);
-    expect(elem.attributeSpans['extends'], null);
+    expect(elem.attributeSpans!['quux'], null);
+    expect(elem.attributeSpans!['extends'], null);
   });
 
   test('void element innerHTML', () {
@@ -179,9 +179,9 @@
       // However, we preserve the input order via LinkedHashMap
       final body = parse('<foo d=1 a=2 c=3 b=4>').body;
       expect(body.innerHtml, '<foo d="1" a="2" c="3" b="4"></foo>');
-      expect(body.querySelector('foo').attributes.remove('a'), '2');
+      expect(body.querySelector('foo')!.attributes.remove('a'), '2');
       expect(body.innerHtml, '<foo d="1" c="3" b="4"></foo>');
-      body.querySelector('foo').attributes['a'] = '0';
+      body.querySelector('foo')!.attributes['a'] = '0';
       expect(body.innerHtml, '<foo d="1" c="3" b="4" a="0"></foo>');
     });
 
@@ -229,7 +229,7 @@
               xlink:href="http://example.com/logo.png"
               xlink:show="new"></desc>
       ''');
-      final n = doc.querySelector('desc');
+      final n = doc.querySelector('desc')!;
       final keys = n.attributes.keys.toList();
       expect(
           keys.first,
@@ -266,8 +266,8 @@
 
   test('Element.text', () {
     final doc = parseFragment('<div>foo<div>bar</div>baz</div>');
-    final e = doc.firstChild;
-    final text = e.firstChild;
+    final e = doc.firstChild!;
+    final text = e.firstChild!;
     expect((text as Text).data, 'foo');
     expect(e.text, 'foobarbaz');
 
@@ -280,7 +280,7 @@
 
   test('Text.text', () {
     final doc = parseFragment('<div>foo<div>bar</div>baz</div>');
-    final e = doc.firstChild;
+    final e = doc.firstChild!;
     final text = e.firstChild as Text;
     expect(text.data, 'foo');
     expect(text.text, 'foo');
@@ -293,14 +293,14 @@
 
   test('Comment.text', () {
     final doc = parseFragment('<div><!--foo-->bar</div>');
-    final e = doc.firstChild;
-    final c = e.firstChild;
+    final e = doc.firstChild!;
+    final c = e.firstChild!;
     expect((c as Comment).data, 'foo');
     expect(c.text, 'foo');
     expect(e.text, 'bar');
 
     c.text = 'qux';
-    expect((c as Comment).data, 'qux');
+    expect(c.data, 'qux');
     expect(c.text, 'qux');
     expect(e.text, 'bar');
   });
@@ -315,12 +315,12 @@
 </svg>''');
     final doc = p.parseFragment();
     expect(p.errors, isEmpty);
-    final svg = doc.querySelector('svg');
+    final svg = doc.querySelector('svg')!;
     expect(svg.children[0].children[0].localName, 'x-flow');
   });
 
   group('Encoding pre-parser', () {
-    String getEncoding(String s) => EncodingParser(s.codeUnits).getEncoding();
+    String? getEncoding(String s) => EncodingParser(s.codeUnits).getEncoding();
 
     test('gets encoding from meta charset', () {
       expect(getEncoding('<meta charset="utf-16">'), 'utf-16');
@@ -365,88 +365,88 @@
       final text = '<html><body>123</body></html>';
       final doc = parse(text, generateSpans: true);
       {
-        final elem = doc.querySelector('html');
-        assertSpan(elem.sourceSpan, 0, 6, '<html>');
-        assertSpan(elem.endSourceSpan, 22, 29, '</html>');
+        final elem = doc.querySelector('html')!;
+        assertSpan(elem.sourceSpan!, 0, 6, '<html>');
+        assertSpan(elem.endSourceSpan!, 22, 29, '</html>');
       }
       {
-        final elem = doc.querySelector('body');
-        assertSpan(elem.sourceSpan, 6, 12, '<body>');
-        assertSpan(elem.endSourceSpan, 15, 22, '</body>');
+        final elem = doc.querySelector('body')!;
+        assertSpan(elem.sourceSpan!, 6, 12, '<body>');
+        assertSpan(elem.endSourceSpan!, 15, 22, '</body>');
       }
     });
 
     test('normal', () {
       final text = '<div><element><span></span></element></div>';
       final doc = parse(text, generateSpans: true);
-      final elem = doc.querySelector('element');
-      assertSpan(elem.sourceSpan, 5, 14, '<element>');
-      assertSpan(elem.endSourceSpan, 27, 37, '</element>');
+      final elem = doc.querySelector('element')!;
+      assertSpan(elem.sourceSpan!, 5, 14, '<element>');
+      assertSpan(elem.endSourceSpan!, 27, 37, '</element>');
     });
 
     test('block', () {
       final text = '<div>123</div>';
       final doc = parse(text, generateSpans: true);
-      final elem = doc.querySelector('div');
-      assertSpan(elem.sourceSpan, 0, 5, '<div>');
-      assertSpan(elem.endSourceSpan, 8, 14, '</div>');
+      final elem = doc.querySelector('div')!;
+      assertSpan(elem.sourceSpan!, 0, 5, '<div>');
+      assertSpan(elem.endSourceSpan!, 8, 14, '</div>');
     });
 
     test('form', () {
       final text = '<form>123</form>';
       final doc = parse(text, generateSpans: true);
-      final elem = doc.querySelector('form');
-      assertSpan(elem.sourceSpan, 0, 6, '<form>');
-      assertSpan(elem.endSourceSpan, 9, 16, '</form>');
+      final elem = doc.querySelector('form')!;
+      assertSpan(elem.sourceSpan!, 0, 6, '<form>');
+      assertSpan(elem.endSourceSpan!, 9, 16, '</form>');
     });
 
     test('p explicit end', () {
       final text = '<p>123</p>';
       final doc = parse(text, generateSpans: true);
-      final elem = doc.querySelector('p');
-      assertSpan(elem.sourceSpan, 0, 3, '<p>');
-      assertSpan(elem.endSourceSpan, 6, 10, '</p>');
+      final elem = doc.querySelector('p')!;
+      assertSpan(elem.sourceSpan!, 0, 3, '<p>');
+      assertSpan(elem.endSourceSpan!, 6, 10, '</p>');
     });
 
     test('p implicit end', () {
       final text = '<div><p>123<p>456</div>';
       final doc = parse(text, generateSpans: true);
-      final elem = doc.querySelector('p');
-      assertSpan(elem.sourceSpan, 5, 8, '<p>');
+      final elem = doc.querySelector('p')!;
+      assertSpan(elem.sourceSpan!, 5, 8, '<p>');
       expect(elem.endSourceSpan, isNull);
     });
 
     test('li', () {
       final text = '<li>123</li>';
       final doc = parse(text, generateSpans: true);
-      final elem = doc.querySelector('li');
-      assertSpan(elem.sourceSpan, 0, 4, '<li>');
-      assertSpan(elem.endSourceSpan, 7, 12, '</li>');
+      final elem = doc.querySelector('li')!;
+      assertSpan(elem.sourceSpan!, 0, 4, '<li>');
+      assertSpan(elem.endSourceSpan!, 7, 12, '</li>');
     });
 
     test('heading', () {
       final text = '<h1>123</h1>';
       final doc = parse(text, generateSpans: true);
-      final elem = doc.querySelector('h1');
-      assertSpan(elem.sourceSpan, 0, 4, '<h1>');
-      assertSpan(elem.endSourceSpan, 7, 12, '</h1>');
+      final elem = doc.querySelector('h1')!;
+      assertSpan(elem.sourceSpan!, 0, 4, '<h1>');
+      assertSpan(elem.endSourceSpan!, 7, 12, '</h1>');
     });
 
     test('formatting', () {
       final text = '<b>123</b>';
       final doc = parse(text, generateSpans: true);
-      final elem = doc.querySelector('b');
-      assertSpan(elem.sourceSpan, 0, 3, '<b>');
-      assertSpan(elem.endSourceSpan, 6, 10, '</b>');
+      final elem = doc.querySelector('b')!;
+      assertSpan(elem.sourceSpan!, 0, 3, '<b>');
+      assertSpan(elem.endSourceSpan!, 6, 10, '</b>');
     });
 
     test('table tbody', () {
       final text = '<table><tbody>  </tbody></table>';
       final doc = parse(text, generateSpans: true);
       {
-        final elem = doc.querySelector('tbody');
-        assertSpan(elem.sourceSpan, 7, 14, '<tbody>');
-        assertSpan(elem.endSourceSpan, 16, 24, '</tbody>');
+        final elem = doc.querySelector('tbody')!;
+        assertSpan(elem.sourceSpan!, 7, 14, '<tbody>');
+        assertSpan(elem.endSourceSpan!, 16, 24, '</tbody>');
       }
     });
 
@@ -454,19 +454,19 @@
       final text = '<table><tr><td>123</td></tr></table>';
       final doc = parse(text, generateSpans: true);
       {
-        final elem = doc.querySelector('table');
-        assertSpan(elem.sourceSpan, 0, 7, '<table>');
-        assertSpan(elem.endSourceSpan, 28, 36, '</table>');
+        final elem = doc.querySelector('table')!;
+        assertSpan(elem.sourceSpan!, 0, 7, '<table>');
+        assertSpan(elem.endSourceSpan!, 28, 36, '</table>');
       }
       {
-        final elem = doc.querySelector('tr');
-        assertSpan(elem.sourceSpan, 7, 11, '<tr>');
-        assertSpan(elem.endSourceSpan, 23, 28, '</tr>');
+        final elem = doc.querySelector('tr')!;
+        assertSpan(elem.sourceSpan!, 7, 11, '<tr>');
+        assertSpan(elem.endSourceSpan!, 23, 28, '</tr>');
       }
       {
-        final elem = doc.querySelector('td');
-        assertSpan(elem.sourceSpan, 11, 15, '<td>');
-        assertSpan(elem.endSourceSpan, 18, 23, '</td>');
+        final elem = doc.querySelector('td')!;
+        assertSpan(elem.sourceSpan!, 11, 15, '<td>');
+        assertSpan(elem.endSourceSpan!, 18, 23, '</td>');
       }
     });
 
@@ -474,19 +474,19 @@
       final text = '<select><optgroup><option>123</option></optgroup></select>';
       final doc = parse(text, generateSpans: true);
       {
-        final elem = doc.querySelector('select');
-        assertSpan(elem.sourceSpan, 0, 8, '<select>');
-        assertSpan(elem.endSourceSpan, 49, 58, '</select>');
+        final elem = doc.querySelector('select')!;
+        assertSpan(elem.sourceSpan!, 0, 8, '<select>');
+        assertSpan(elem.endSourceSpan!, 49, 58, '</select>');
       }
       {
-        final elem = doc.querySelector('optgroup');
-        assertSpan(elem.sourceSpan, 8, 18, '<optgroup>');
-        assertSpan(elem.endSourceSpan, 38, 49, '</optgroup>');
+        final elem = doc.querySelector('optgroup')!;
+        assertSpan(elem.sourceSpan!, 8, 18, '<optgroup>');
+        assertSpan(elem.endSourceSpan!, 38, 49, '</optgroup>');
       }
       {
-        final elem = doc.querySelector('option');
-        assertSpan(elem.sourceSpan, 18, 26, '<option>');
-        assertSpan(elem.endSourceSpan, 29, 38, '</option>');
+        final elem = doc.querySelector('option')!;
+        assertSpan(elem.sourceSpan!, 18, 26, '<option>');
+        assertSpan(elem.endSourceSpan!, 29, 38, '</option>');
       }
     });
   });
diff --git a/test/parser_test.dart b/test/parser_test.dart
index 7bdbe23..06fb4c6 100644
--- a/test/parser_test.dart
+++ b/test/parser_test.dart
@@ -32,10 +32,10 @@
 
 void runParserTest(
     String groupName,
-    String innerHTML,
-    String input,
-    String expected,
-    List errors,
+    String? innerHTML,
+    String? input,
+    String? expected,
+    List? errors,
     TreeBuilderFactory treeCtor,
     bool namespaceHTMLElements) {
   // XXX - move this out into the setup function
@@ -53,7 +53,7 @@
   final output = testSerializer(document);
 
   if (namespaceHTMLElements) {
-    expected = namespaceHtml(expected);
+    expected = namespaceHtml(expected!);
   }
 
   expect(output, equals(expected),
@@ -61,7 +61,7 @@
           '\n\nInput:\n$input\n\nExpected:\n$expected\n\nReceived:\n$output');
 
   if (checkParseErrors) {
-    expect(parser.errors.length, equals(errors.length),
+    expect(parser.errors.length, equals(errors!.length),
         reason: '\n\nInput:\n$input\n\nExpected errors (${errors.length}):\n'
             "${errors.join('\n')}\n\n"
             'Actual errors (${parser.errors.length}):\n'
@@ -84,9 +84,9 @@
         final innerHTML = testData['document-fragment'];
         final expected = testData['document'];
 
-        for (var treeCtor in treeTypes.values) {
+        for (var treeCtor in treeTypes!.values) {
           for (var namespaceHTMLElements in const [false, true]) {
-            test(_nameFor(input), () {
+            test(_nameFor(input!), () {
               runParserTest(testName, innerHTML, input, expected, errors,
                   treeCtor, namespaceHTMLElements);
             });
diff --git a/test/selectors/level1_baseline_test.dart b/test/selectors/level1_baseline_test.dart
index 19d1123..cba4c21 100644
--- a/test/selectors/level1_baseline_test.dart
+++ b/test/selectors/level1_baseline_test.dart
@@ -65,7 +65,7 @@
   //doc = frame.contentDocument;                 // Document Node tests
   doc = await testContentDocument();
 
-  final element = doc.getElementById('root'); // In-document Element Node tests
+  final element = doc.getElementById('root')!; // In-document Element Node tests
 
   //Setup the namespace tests
   setupSpecialElements(element);
diff --git a/test/selectors/level1_lib.dart b/test/selectors/level1_lib.dart
index e22f9c4..65f1468 100644
--- a/test/selectors/level1_lib.dart
+++ b/test/selectors/level1_lib.dart
@@ -12,7 +12,7 @@
 import 'package:html/dom.dart';
 import 'package:test/test.dart' as unittest;
 
-Document doc;
+late Document doc;
 
 /*
  * Create and append special elements that cannot be created correctly with HTML markup alone.
@@ -96,7 +96,7 @@
 void verifyStaticList(String type, dynamic root) {
   List pre;
   List post;
-  int preLength;
+  late int preLength;
 
   runTest(() {
     pre = root.querySelectorAll('div') as List;
@@ -184,7 +184,7 @@
 /// Tests containing this string fail for an unknown reason
 final _failureName = 'matching custom data-* attribute with';
 
-String _getSkip(String name) {
+String? _getSkip(String name) {
   if (name.contains(_failureName)) {
     return 'Tests related to `$_failureName` fail for an unknown reason.';
   }
@@ -195,10 +195,10 @@
  * Execute queries with the specified valid selectors for both querySelector() and querySelectorAll()
  * Only run these tests when results are expected. Don't run for syntax error tests.
  */
-void runValidSelectorTest(String type, root,
+void runValidSelectorTest(String type, Node root,
     List<Map<String, dynamic>> selectors, testType, docType) {
   var nodeType = '';
-  switch ((root as Node).nodeType) {
+  switch (root.nodeType) {
     case Node.DOCUMENT_NODE:
       nodeType = 'document';
       break;
@@ -217,20 +217,20 @@
     final n = s['name'] as String;
     final skip = _getSkip(n);
     final q = s['selector'] as String;
-    final e = s['expect'] as List;
+    final 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))
-      List<Element> foundall;
-      Element found;
+      late List<Element> foundall;
+      Element? found;
 
       runTest(() {
-        foundall = root.querySelectorAll(q) as List<Element>;
+        foundall = (root as dynamic).querySelectorAll(q) as List<Element>;
         assertNotEquals(foundall, null, 'The method should not return null.');
-        assertEquals(foundall.length, e.length,
+        assertEquals(foundall.length, e!.length,
             'The method should return the expected number of matches.');
 
         for (var i = 0; i < e.length; i++) {
@@ -244,15 +244,15 @@
       }, '$type.querySelectorAll: $n:$q', skip: skip);
 
       runTest(() {
-        found = root.querySelector(q) as Element;
+        found = (root as dynamic).querySelector(q) as Element?;
 
-        if (e.isNotEmpty) {
+        if (e!.isNotEmpty) {
           assertNotEquals(found, null, 'The method should return a match.');
-          assertEquals(found.attributes['id'], e[0],
+          assertEquals(found!.attributes['id'], e[0],
               'The method should return the first match.');
           assertEquals(found, foundall[0],
               'The result should match the first item from querySelectorAll.');
-          assertFalse(found.attributes.containsKey('data-clone'),
+          assertFalse(found!.attributes.containsKey('data-clone'),
               'This should not be annotated as a cloned element.');
         } else {
           assertEquals(found, null, 'The method should not match anything.');
@@ -300,7 +300,7 @@
   }
 }
 
-void runTest(dynamic 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) =>
@@ -314,5 +314,5 @@
 void assertNotEquals(x, y, String reason) =>
     unittest.expect(x, unittest.isNot(y), reason: reason);
 
-void assertThrows(exception, void Function() body, [String reason]) =>
+void assertThrows(exception, void Function() body, [String? reason]) =>
     unittest.expect(body, unittest.throwsA(exception), reason: reason);
diff --git a/test/support.dart b/test/support.dart
index 60a248a..83894af 100644
--- a/test/support.dart
+++ b/test/support.dart
@@ -12,8 +12,8 @@
 
 typedef TreeBuilderFactory = TreeBuilder Function(bool namespaceHTMLElements);
 
-Map<String, TreeBuilderFactory> _treeTypes;
-Map<String, TreeBuilderFactory> get treeTypes {
+Map<String, TreeBuilderFactory>? _treeTypes;
+Map<String, TreeBuilderFactory>? get treeTypes {
   // TODO(jmesserly): add DOM here once it's implemented
   _treeTypes ??= {'simpletree': (useNs) => TreeBuilder(useNs)};
   return _treeTypes;
@@ -37,7 +37,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<String, String>> {
+class TestData extends IterableBase<Map<String?, String>> {
   final String _text;
   final String newTestHeading;
 
@@ -48,12 +48,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<String, String>> get iterator => _getData().iterator;
+  Iterator<Map<String?, String>> get iterator => _getData().iterator;
 
-  List<Map<String, String>> _getData() {
+  List<Map<String?, String>> _getData() {
     var data = <String, String>{};
-    String key;
-    final result = <Map<String, String>>[];
+    String? key;
+    final List<Map<String?, String>> result = <Map<String, String>>[];
     final lines = _text.split('\n');
     // Remove trailing newline to match Python
     if (lines.last == '') {
@@ -64,7 +64,7 @@
       if (heading != null) {
         if (data.isNotEmpty && heading == newTestHeading) {
           // Remove trailing newline
-          data[key] = data[key].substring(0, data[key].length - 1);
+          data[key!] = data[key]!.substring(0, data[key]!.length - 1);
           result.add(normaliseOutput(data));
           data = <String, String>{};
         }
@@ -83,7 +83,7 @@
 
   /// If the current heading is a test section heading return the heading,
   /// otherwise return null.
-  static String sectionHeading(String line) {
+  static String? sectionHeading(String line) {
     return line.startsWith('#') ? line.substring(1).trim() : null;
   }
 
@@ -173,7 +173,7 @@
       for (var key in keys) {
         final v = node.attributes[key];
         if (key is AttributeName) {
-          final attr = key as AttributeName;
+          final attr = key;
           key = '${attr.prefix} ${attr.name}';
         }
         _newline();
diff --git a/test/tokenizer_test.dart b/test/tokenizer_test.dart
index 0ae5cab..87ba6d0 100644
--- a/test/tokenizer_test.dart
+++ b/test/tokenizer_test.dart
@@ -14,18 +14,18 @@
 import 'support.dart';
 
 class TokenizerTestParser {
-  final String _state;
-  final String _lastStartTag;
+  final String? _state;
+  final String? _lastStartTag;
   final bool _generateSpans;
-  List outputTokens;
+  List? outputTokens;
 
-  TokenizerTestParser(String initialState,
-      [String lastStartTag, bool generateSpans = false])
+  TokenizerTestParser(String? initialState,
+      [String? lastStartTag, bool generateSpans = false])
       : _state = initialState,
         _lastStartTag = lastStartTag,
         _generateSpans = generateSpans;
 
-  List parse(String str) {
+  List? parse(String str) {
     // Note: we need to pass bytes to the tokenizer if we want it to handle BOM.
     final bytes = utf8.encode(str);
     final tokenizer =
@@ -36,7 +36,7 @@
     // create a new closure to invoke it via mirrors.
     final mtok = reflect(tokenizer);
     tokenizer.state =
-        () => mtok.invoke(Symbol(_state), const []).reflectee as bool;
+        () => mtok.invoke(Symbol(_state!), const []).reflectee as bool;
 
     if (_lastStartTag != null) {
       tokenizer.currentToken = StartTagToken(_lastStartTag);
@@ -108,10 +108,10 @@
   }
 
   void addOutputToken(Token token, List array) {
-    outputTokens.add([
+    outputTokens!.add([
       ...array,
-      if (token.span != null && _generateSpans) token.span.start.offset,
-      if (token.span != null && _generateSpans) token.span.end.offset,
+      if (token.span != null && _generateSpans) token.span!.start.offset,
+      if (token.span != null && _generateSpans) token.span!.end.offset,
     ]);
   }
 }
@@ -151,7 +151,7 @@
 /// positions of parse errors and non parse errors.
 void expectTokensMatch(
     List expectedTokens, List receivedTokens, bool ignoreErrorOrder,
-    [bool ignoreErrors = false, String message]) {
+    [bool ignoreErrors = false, String? message]) {
   // If the 'selfClosing' attribute is not included in the expected test tokens,
   // remove it from the received token.
   var removeSelfClosing = false;
@@ -201,10 +201,10 @@
     testInfo['lastStartTag'] = null;
   }
   final parser = TokenizerTestParser(
-      testInfo['initialState'] as String,
-      testInfo['lastStartTag'] as String,
-      testInfo['generateSpans'] as bool /*?*/ ?? false);
-  var tokens = parser.parse(testInfo['input'] as String);
+      testInfo['initialState'] as String?,
+      testInfo['lastStartTag'] as String?,
+      testInfo['generateSpans'] as bool? ?? false);
+  var tokens = parser.parse(testInfo['input'] as String)!;
   tokens = concatenateCharacterTokens(tokens);
   final received = normalizeTokens(tokens);
   final errorMsg = [
@@ -217,7 +217,7 @@
     '\nreceived:',
     tokens
   ].map((s) => '$s').join('\n');
-  final ignoreErrorOrder = testInfo['ignoreErrorOrder'] as bool /*?*/ ?? false;
+  final ignoreErrorOrder = testInfo['ignoreErrorOrder'] as bool? ?? false;
 
   expectTokensMatch(expected, received, ignoreErrorOrder, true, errorMsg);
 }
@@ -252,7 +252,7 @@
   final result = StringBuffer();
   for (var match in RegExp(r'\W+(\w)(\w+)').allMatches(s)) {
     if (result.length == 0) result.write(s.substring(0, match.start));
-    result.write(match.group(1).toUpperCase());
+    result.write(match.group(1)!.toUpperCase());
     result.write(match.group(2));
   }
   return result.toString();
@@ -265,7 +265,7 @@
     final text = File(path).readAsStringSync();
     final tests = jsonDecode(text);
     final testName = pathos.basenameWithoutExtension(path);
-    final testList = tests['tests'] as List;
+    final testList = tests['tests'] as List?;
     if (testList == null) continue;
 
     group(testName, () {