enable many lints
diff --git a/analysis_options.yaml b/analysis_options.yaml
index a10d4c5..da17516 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -1,2 +1,51 @@
 analyzer:
   strong-mode: true
+  errors:
+    unused_import: error
+    unused_local_variable: error
+    dead_code: error
+    override_on_non_overriding_method: error
+linter:
+  rules:
+    # Errors
+    - avoid_empty_else
+    - await_only_futures
+    #- comment_references
+    - control_flow_in_finally
+    - empty_statements
+    - hash_and_equals
+    - iterable_contains_unrelated_type
+    - no_duplicate_case_values
+    - test_types_in_equals
+    - throw_in_finally
+    - unawaited_futures
+    - unnecessary_statements
+    - unrelated_type_equality_checks
+    - valid_regexps
+
+    # Style
+    #- annotate_overrides
+    #- avoid_function_literals_in_foreach_calls
+    - avoid_init_to_null
+    - avoid_return_types_on_setters
+    - avoid_returning_null
+    - avoid_unused_constructor_parameters
+    - camel_case_types
+    - directives_ordering
+    - empty_catches
+    - empty_constructor_bodies
+    - library_names
+    - library_prefixes
+    #- non_constant_identifier_names
+    #- prefer_conditional_assignment
+    - prefer_final_fields
+    - prefer_is_empty
+    - prefer_is_not_empty
+    #- prefer_typing_uninitialized_variables
+    - recursive_getters
+    #- slash_for_doc_comments
+    - type_init_formals
+    #- unnecessary_brace_in_string_interps
+    - unnecessary_getters_setters
+    - unnecessary_lambdas
+    - unnecessary_null_aware_assignments
diff --git a/lib/dom.dart b/lib/dom.dart
index 132aa55..fb0d458 100644
--- a/lib/dom.dart
+++ b/lib/dom.dart
@@ -257,7 +257,7 @@
 
   // TODO(jmesserly): should this be a property or remove?
   /// Return true if the node has children or text.
-  bool hasContent() => nodes.length > 0;
+  bool hasContent() => nodes.isNotEmpty;
 
   /// Move all the children of the current node to [newParent].
   /// This is needed so that trees that don't store text as nodes move the
@@ -406,7 +406,7 @@
   /// The text node's data, stored as either a String or StringBuffer.
   /// We support storing a StringBuffer here to support fast [appendData].
   /// It will flatten back to a String on read.
-  var _data;
+  dynamic _data;
 
   Text(String data)
       : _data = data != null ? data : '',
@@ -565,7 +565,7 @@
     str.write(_getSerializationPrefix(namespaceUri));
     str.write(localName);
 
-    if (attributes.length > 0) {
+    if (attributes.isNotEmpty) {
       attributes.forEach((key, v) {
         // Note: AttributeName.toString handles serialization of attribute
         // namespace, if needed.
@@ -579,7 +579,7 @@
 
     str.write('>');
 
-    if (nodes.length > 0) {
+    if (nodes.isNotEmpty) {
       if (localName == 'pre' ||
           localName == 'textarea' ||
           localName == 'listing') {
diff --git a/lib/dom_parsing.dart b/lib/dom_parsing.dart
index 56518c2..3435a81 100644
--- a/lib/dom_parsing.dart
+++ b/lib/dom_parsing.dart
@@ -85,14 +85,14 @@
   visitElement(Element node) {
     final tag = node.localName;
     _str.write('&lt;<code class="markup element-name">$tag</code>');
-    if (node.attributes.length > 0) {
+    if (node.attributes.isNotEmpty) {
       node.attributes.forEach((key, v) {
         v = htmlSerializeEscape(v, attributeMode: true);
         _str.write(' <code class="markup attribute-name">$key</code>'
             '=<code class="markup attribute-value">"$v"</code>');
       });
     }
-    if (node.nodes.length > 0) {
+    if (node.nodes.isNotEmpty) {
       _str.write(">");
       visitChildren(node);
     } else if (isVoidElement(tag)) {
diff --git a/lib/parser.dart b/lib/parser.dart
index e1c6099..75a5c62 100644
--- a/lib/parser.dart
+++ b/lib/parser.dart
@@ -276,7 +276,7 @@
   }
 
   bool inForeignContent(Token token, int type) {
-    if (tree.openElements.length == 0) return false;
+    if (tree.openElements.isEmpty) return false;
 
     var node = tree.openElements.last;
     if (node.namespaceUri == tree.defaultNamespace) return false;
@@ -1235,7 +1235,6 @@
       case "blockquote":
       case "center":
       case "details":
-      case "details":
       case "dir":
       case "div":
       case "dl":
@@ -1343,7 +1342,6 @@
         startTagIFrame(token);
         return null;
       case "noembed":
-      case "noframes":
       case "noscript":
         startTagRawtext(token);
         return null;
@@ -1538,7 +1536,7 @@
         data = data.substring(1);
       }
     }
-    if (data.length > 0) {
+    if (data.isNotEmpty) {
       tree.reconstructActiveFormattingElements();
       tree.insertText(data, token.span);
     }
@@ -2012,7 +2010,7 @@
   }
 
   void endTagListItem(EndTagToken token) {
-    var variant;
+    String variant;
     if (token.name == "li") {
       variant = "list";
     } else {
@@ -2232,7 +2230,9 @@
           parser.parseError(
               token.span, "unexpected-end-tag", {"name": token.name});
         }
-        while (tree.openElements.removeLast() != node);
+        while (tree.openElements.removeLast() != node) {
+          // noop
+        }
         node.endSourceSpan = token.span;
         break;
       } else {
@@ -2522,7 +2522,7 @@
         super(parser);
 
   void flushCharacters() {
-    if (characterTokens.length == 0) return;
+    if (characterTokens.isEmpty) return;
 
     // TODO(sigmund,jmesserly): remove '' (dartbug.com/8480)
     var data = characterTokens.map((t) => t.data).join('');
@@ -2534,7 +2534,7 @@
 
     if (!allWhitespace(data)) {
       parser._inTablePhase.insertText(new CharactersToken(data)..span = span);
-    } else if (data.length > 0) {
+    } else if (data.isNotEmpty) {
       tree.insertText(data, span);
     }
     characterTokens = <StringToken>[];
@@ -3551,7 +3551,7 @@
       parser.parseError(token.span, "unexpected-end-tag", {"name": token.name});
     }
 
-    var newToken;
+    Token newToken;
     while (true) {
       if (asciiUpper2Lower(node.localName) == token.name) {
         //XXX this isn't in the spec but it seems necessary
@@ -3561,7 +3561,7 @@
           parser.phase = inTableText.originalPhase;
         }
         while (tree.openElements.removeLast() != node) {
-          assert(tree.openElements.length > 0);
+          assert(tree.openElements.isNotEmpty);
         }
         newToken = null;
         break;
diff --git a/lib/src/encoding_parser.dart b/lib/src/encoding_parser.dart
index 21d6783..d97e4cc 100644
--- a/lib/src/encoding_parser.dart
+++ b/lib/src/encoding_parser.dart
@@ -254,7 +254,7 @@
     while (true) {
       if (c == null) {
         return null;
-      } else if (c == "=" && attrName.length > 0) {
+      } else if (c == "=" && attrName.isNotEmpty) {
         break;
       } else if (isWhitespace(c)) {
         // Step 6!
diff --git a/lib/src/tokenizer.dart b/lib/src/tokenizer.dart
index db9ab63..30092a2 100644
--- a/lib/src/tokenizer.dart
+++ b/lib/src/tokenizer.dart
@@ -125,16 +125,16 @@
   bool moveNext() {
     // Start processing. When EOF is reached state will return false;
     // instead of true and the loop will terminate.
-    while (stream.errors.length == 0 && tokenQueue.length == 0) {
+    while (stream.errors.isEmpty && tokenQueue.isEmpty) {
       if (!state()) {
         _current = null;
         return false;
       }
     }
-    if (stream.errors.length > 0) {
+    if (stream.errors.isNotEmpty) {
       _current = new ParseErrorToken(stream.errors.removeFirst());
     } else {
-      assert(tokenQueue.length > 0);
+      assert(tokenQueue.isNotEmpty);
       _current = tokenQueue.removeFirst();
     }
     return true;
@@ -302,7 +302,7 @@
         filteredEntityList =
             filteredEntityList.where((e) => e.startsWith(name)).toList();
 
-        if (filteredEntityList.length == 0) {
+        if (filteredEntityList.isEmpty) {
           break;
         }
         charStack.add(stream.char());
@@ -349,7 +349,7 @@
     if (fromAttribute) {
       _attributeValue.write(output);
     } else {
-      var token;
+      Token token;
       if (isWhitespace(output)) {
         token = new SpaceCharactersToken(output);
       } else {
@@ -1316,7 +1316,7 @@
       }
     } else if (charStack.last == "[" &&
         parser != null &&
-        parser.tree.openElements.length > 0 &&
+        parser.tree.openElements.isNotEmpty &&
         parser.tree.openElements.last.namespaceUri !=
             parser.tree.defaultNamespace) {
       var matched = true;
@@ -1335,7 +1335,7 @@
 
     _addToken(new ParseErrorToken("expected-dashes-or-doctype"));
 
-    while (charStack.length > 0) {
+    while (charStack.isNotEmpty) {
       stream.unget(charStack.removeLast());
     }
     state = bogusCommentState;
@@ -1904,7 +1904,7 @@
       }
     }
 
-    if (data.length > 0) {
+    if (data.isNotEmpty) {
       _addToken(new CharactersToken(data.join()));
     }
     state = dataState;
diff --git a/lib/src/treebuilder.dart b/lib/src/treebuilder.dart
index 69e1817..12cdbbb 100644
--- a/lib/src/treebuilder.dart
+++ b/lib/src/treebuilder.dart
@@ -18,7 +18,6 @@
 // TODO(jmesserly): this should extend ListBase<Element>, but my simple attempt
 // didn't work.
 class ActiveFormattingElements extends ListProxy<Element> {
-
   // Override the "add" method.
   // TODO(jmesserly): I'd rather not override this; can we do this in the
   // calling code instead?
@@ -45,7 +44,7 @@
 // TODO(jmesserly): this should exist in corelib...
 bool _mapEquals(Map a, Map b) {
   if (a.length != b.length) return false;
-  if (a.length == 0) return true;
+  if (a.isEmpty) return true;
 
   for (var keyA in a.keys) {
     var valB = b[keyA];
@@ -158,7 +157,7 @@
     // code. It should still do the same though.
 
     // Step 1: stop the algorithm when there's nothing to do.
-    if (activeFormattingElements.length == 0) {
+    if (activeFormattingElements.isEmpty) {
       return;
     }
 
@@ -209,7 +208,7 @@
 
   void clearActiveFormattingElements() {
     var entry = activeFormattingElements.removeLast();
-    while (activeFormattingElements.length > 0 && entry != Marker) {
+    while (activeFormattingElements.isNotEmpty && entry != Marker) {
       entry = activeFormattingElements.removeLast();
     }
   }
@@ -321,7 +320,7 @@
       [Element refNode]) {
     var nodes = parent.nodes;
     if (refNode == null) {
-      if (nodes.length > 0 && nodes.last is Text) {
+      if (nodes.isNotEmpty && nodes.last is Text) {
         Text last = nodes.last;
         last.appendData(data);
 
diff --git a/test/selectors/level1_lib.dart b/test/selectors/level1_lib.dart
index 0bf08f6..3c3df20 100644
--- a/test/selectors/level1_lib.dart
+++ b/test/selectors/level1_lib.dart
@@ -232,7 +232,7 @@
       test(() {
         found = root.querySelector(q);
 
-        if (e.length > 0) {
+        if (e.isNotEmpty) {
           assert_not_equals(found, null, "The method should return a match.");
           assert_equals(found.attributes["id"], e[0],
               "The method should return the first match.");
diff --git a/test/support.dart b/test/support.dart
index 2af8b7c..022da35 100644
--- a/test/support.dart
+++ b/test/support.dart
@@ -55,7 +55,7 @@
     for (var line in lines) {
       var heading = sectionHeading(line);
       if (heading != null) {
-        if (data.length > 0 && heading == newTestHeading) {
+        if (data.isNotEmpty && heading == newTestHeading) {
           // Remove trailing newline
           data[key] = data[key].substring(0, data[key].length - 1);
           result.add(normaliseOutput(data));
@@ -68,7 +68,7 @@
       }
     }
 
-    if (data.length > 0) {
+    if (data.isNotEmpty) {
       result.add(normaliseOutput(data));
     }
     return result;
@@ -150,7 +150,7 @@
   visitElement(Element node) {
     _newline();
     _str.write(node);
-    if (node.attributes.length > 0) {
+    if (node.attributes.isNotEmpty) {
       indent += 2;
       var keys = new List.from(node.attributes.keys);
       keys.sort((x, y) => x.compareTo(y));
diff --git a/test/tokenizer_test.dart b/test/tokenizer_test.dart
index a8543ff..c268f9f 100644
--- a/test/tokenizer_test.dart
+++ b/test/tokenizer_test.dart
@@ -14,10 +14,10 @@
 
 class TokenizerTestParser {
   String _state;
-  var _lastStartTag;
+  String _lastStartTag;
   List outputTokens;
 
-  TokenizerTestParser(String initialState, [lastStartTag])
+  TokenizerTestParser(String initialState, [String lastStartTag])
       : _state = initialState,
         _lastStartTag = lastStartTag;
 
@@ -105,7 +105,7 @@
   var outputTokens = [];
   for (var token in tokens) {
     if (token.indexOf("ParseError") == -1 && token[0] == "Character") {
-      if (outputTokens.length > 0 &&
+      if (outputTokens.isNotEmpty &&
           outputTokens.last.indexOf("ParseError") == -1 &&
           outputTokens.last[0] == "Character") {
         outputTokens.last[1] = '${outputTokens.last[1]}${token[1]}';