use lints 2.0.0; prep for publishing (#39)

* use lints 2.0.0; prep for publishing

* fix ci; fix tests

* fix tests
diff --git a/.github/workflows/test-package.yml b/.github/workflows/test-package.yml
index 4ffa08c..a417de1 100644
--- a/.github/workflows/test-package.yml
+++ b/.github/workflows/test-package.yml
@@ -44,7 +44,7 @@
       matrix:
         # Add macos-latest and/or windows-latest if relevant for this package.
         os: [ubuntu-latest]
-        sdk: [2.12.0, dev]
+        sdk: [2.17.0, dev]
     steps:
       - uses: actions/checkout@v2
       - uses: dart-lang/setup-dart@v1.0
diff --git a/CHANGELOG.md b/CHANGELOG.md
index abfb484..ac86fb6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,7 @@
-## 2.1.1-dev
+## 2.1.1
+
+* Increase the SDK minimum to `2.17.0`.
+* Populate the pubspec `repository` field.
 
 ## 2.1.0
 
diff --git a/analysis_options.yaml b/analysis_options.yaml
index 9b763af..5575818 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -3,6 +3,7 @@
 linter:
   rules:
     - always_declare_return_types
+    - avoid_dynamic_calls
     - avoid_unused_constructor_parameters
     - cancel_subscriptions
     - directives_ordering
diff --git a/pubspec.yaml b/pubspec.yaml
index 2d4b86a..670f6aa 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,17 +1,17 @@
 name: boolean_selector
-version: 2.1.1-dev
+version: 2.1.1
 description: >-
   A flexible syntax for boolean expressions, based on a simplified version of
   Dart's expression syntax.
 repository: https://github.com/dart-lang/boolean_selector
 
 environment:
-  sdk: ">=2.12.0 <3.0.0"
+  sdk: '>=2.17.0 <3.0.0'
 
 dependencies:
   source_span: ^1.8.0
   string_scanner: ^1.1.0
 
 dev_dependencies:
-  lints: ^1.0.0
+  lints: ^2.0.0
   test: ^1.16.0
diff --git a/test/parser_test.dart b/test/parser_test.dart
index 0b9db48..5fbc0d2 100644
--- a/test/parser_test.dart
+++ b/test/parser_test.dart
@@ -24,9 +24,10 @@
       var node = _parse('  a ? b : c   ');
       expect(node.toString(), equals('a ? b : c'));
 
-      expect(node.span.text, equals('a ? b : c'));
-      expect(node.span.start.offset, equals(2));
-      expect(node.span.end.offset, equals(11));
+      expect(node.span, isNotNull);
+      expect(node.span!.text, equals('a ? b : c'));
+      expect(node.span!.start.offset, equals(2));
+      expect(node.span!.end.offset, equals(11));
     });
 
     test('with nested ors', () {
@@ -41,13 +42,16 @@
       // Should parse as "a ? (b ? c : d) : e".
       var node = _parse('a ? b ? c : d : e');
       expect(node, _isConditionalNode);
+      node as ConditionalNode; // promote node
+
       expect(node.condition, _isVar('a'));
       expect(node.whenFalse, _isVar('e'));
 
       expect(node.whenTrue, _isConditionalNode);
-      expect(node.whenTrue.condition, _isVar('b'));
-      expect(node.whenTrue.whenTrue, _isVar('c'));
-      expect(node.whenTrue.whenFalse, _isVar('d'));
+      var whenTrue = node.whenTrue as ConditionalNode;
+      expect(whenTrue.condition, _isVar('b'));
+      expect(whenTrue.whenTrue, _isVar('c'));
+      expect(whenTrue.whenFalse, _isVar('d'));
     });
 
     test('with a conditional expression as branch 2', () {
@@ -55,13 +59,16 @@
       // Should not parse as "(a ? b : c) ? d : e".
       var node = _parse('a ? b : c ? d : e');
       expect(node, _isConditionalNode);
+      node as ConditionalNode; //promote node
+
       expect(node.condition, _isVar('a'));
       expect(node.whenTrue, _isVar('b'));
 
       expect(node.whenFalse, _isConditionalNode);
-      expect(node.whenFalse.condition, _isVar('c'));
-      expect(node.whenFalse.whenTrue, _isVar('d'));
-      expect(node.whenFalse.whenFalse, _isVar('e'));
+      var whenFalse = node.whenFalse as ConditionalNode;
+      expect(whenFalse.condition, _isVar('c'));
+      expect(whenFalse.whenTrue, _isVar('d'));
+      expect(whenFalse.whenFalse, _isVar('e'));
     });
 
     group('which must have', () {
@@ -86,12 +93,15 @@
     test('with identifiers', () {
       var node = _parse('  a || b   ');
       expect(node, _isOrNode);
+      node as OrNode; //promote node
+
       expect(node.left, _isVar('a'));
       expect(node.right, _isVar('b'));
 
-      expect(node.span.text, equals('a || b'));
-      expect(node.span.start.offset, equals(2));
-      expect(node.span.end.offset, equals(8));
+      expect(node.span, isNotNull);
+      expect(node.span!.text, equals('a || b'));
+      expect(node.span!.start.offset, equals(2));
+      expect(node.span!.end.offset, equals(8));
     });
 
     test('with nested ands', () {
@@ -99,14 +109,17 @@
       // Should not parse as "a && (b || c) && d".
       var node = _parse('a && b || c && d');
       expect(node, _isOrNode);
+      node as OrNode; //promote node
 
       expect(node.left, _isAndNode);
-      expect(node.left.left, _isVar('a'));
-      expect(node.left.right, _isVar('b'));
+      var left = node.left as AndNode;
+      expect(left.left, _isVar('a'));
+      expect(left.right, _isVar('b'));
 
       expect(node.right, _isAndNode);
-      expect(node.right.left, _isVar('c'));
-      expect(node.right.right, _isVar('d'));
+      var right = node.right as AndNode;
+      expect(right.left, _isVar('c'));
+      expect(right.right, _isVar('d'));
     });
 
     test('with trailing ors', () {
@@ -116,6 +129,8 @@
 
       for (var variable in ['a', 'b', 'c']) {
         expect(node, _isOrNode);
+        node as OrNode; //promote node
+
         expect(node.left, _isVar(variable));
         node = node.right;
       }
@@ -132,12 +147,15 @@
     test('with identifiers', () {
       var node = _parse('  a && b   ');
       expect(node, _isAndNode);
+      node as AndNode; //promote node
+
       expect(node.left, _isVar('a'));
       expect(node.right, _isVar('b'));
 
-      expect(node.span.text, equals('a && b'));
-      expect(node.span.start.offset, equals(2));
-      expect(node.span.end.offset, equals(8));
+      expect(node.span, isNotNull);
+      expect(node.span!.text, equals('a && b'));
+      expect(node.span!.start.offset, equals(2));
+      expect(node.span!.end.offset, equals(8));
     });
 
     test('with nested nots', () {
@@ -145,12 +163,15 @@
       // Should not parse as "!(a && (!b))".
       var node = _parse('!a && !b');
       expect(node, _isAndNode);
+      node as AndNode; //promote node
 
       expect(node.left, _isNotNode);
-      expect(node.left.child, _isVar('a'));
+      var left = node.left as NotNode;
+      expect(left.child, _isVar('a'));
 
       expect(node.right, _isNotNode);
-      expect(node.right.child, _isVar('b'));
+      var right = node.right as NotNode;
+      expect(right.child, _isVar('b'));
     });
 
     test('with trailing ands', () {
@@ -160,6 +181,8 @@
 
       for (var variable in ['a', 'b', 'c']) {
         expect(node, _isAndNode);
+        node as AndNode; //promote node
+
         expect(node.left, _isVar(variable));
         node = node.right;
       }
@@ -176,27 +199,34 @@
     test('with an identifier', () {
       var node = _parse('  ! a    ');
       expect(node, _isNotNode);
+      node as NotNode; //promote node
       expect(node.child, _isVar('a'));
 
-      expect(node.span.text, equals('! a'));
-      expect(node.span.start.offset, equals(2));
-      expect(node.span.end.offset, equals(5));
+      expect(node.span, isNotNull);
+      expect(node.span!.text, equals('! a'));
+      expect(node.span!.start.offset, equals(2));
+      expect(node.span!.end.offset, equals(5));
     });
 
     test('with a parenthesized expression', () {
       var node = _parse('!(a || b)');
       expect(node, _isNotNode);
+      node as NotNode; //promote node
 
       expect(node.child, _isOrNode);
-      expect(node.child.left, _isVar('a'));
-      expect(node.child.right, _isVar('b'));
+      var child = node.child as OrNode;
+      expect(child.left, _isVar('a'));
+      expect(child.right, _isVar('b'));
     });
 
     test('with a nested not', () {
       var node = _parse('!!a');
       expect(node, _isNotNode);
+      node as NotNode; //promote node
+
       expect(node.child, _isNotNode);
-      expect(node.child.child, _isVar('a'));
+      var child = node.child as NotNode;
+      expect(child.child, _isVar('a'));
     });
 
     test('which must have an expression after the !', () {
@@ -216,12 +246,15 @@
       var node = _parse('a || (b ? c : d)');
 
       expect(node, _isOrNode);
+      node as OrNode; //promote node
+
       expect(node.left, _isVar('a'));
 
       expect(node.right, _isConditionalNode);
-      expect(node.right.condition, _isVar('b'));
-      expect(node.right.whenTrue, _isVar('c'));
-      expect(node.right.whenFalse, _isVar('d'));
+      var right = node.right as ConditionalNode;
+      expect(right.condition, _isVar('b'));
+      expect(right.whenTrue, _isVar('c'));
+      expect(right.whenFalse, _isVar('d'));
     });
 
     group('which must have', () {
@@ -248,7 +281,7 @@
 }
 
 /// Parses [selector] and returns its root node.
-dynamic _parse(String selector) => Parser(selector).parse();
+Node _parse(String selector) => Parser(selector).parse();
 
 /// A matcher that asserts that a value is a [VariableNode] with the given
 /// [name].
diff --git a/test/scanner_test.dart b/test/scanner_test.dart
index de9c416..30091d1 100644
--- a/test/scanner_test.dart
+++ b/test/scanner_test.dart
@@ -6,6 +6,9 @@
 import 'package:boolean_selector/src/token.dart';
 import 'package:test/test.dart';
 
+/// A matcher that asserts that a value is a [IdentifierToken].
+final _isIdentifierToken = TypeMatcher<IdentifierToken>();
+
 void main() {
   group('peek()', () {
     test('returns the next token without consuming it', () {
@@ -98,6 +101,9 @@
   group('scans an identifier that', () {
     test('is simple', () {
       var token = _scan('   foo  ');
+      expect(token, _isIdentifierToken);
+      token as IdentifierToken; // promote token
+
       expect(token.name, equals('foo'));
       expect(token.span.text, equals('foo'));
       expect(token.span.start.offset, equals(3));
@@ -106,37 +112,44 @@
 
     test('is a single character', () {
       var token = _scan('f');
-      expect(token.name, equals('f'));
+      expect(token, _isIdentifierToken);
+      expect((token as IdentifierToken).name, equals('f'));
     });
 
     test('has a leading underscore', () {
       var token = _scan('_foo');
-      expect(token.name, equals('_foo'));
+      expect(token, _isIdentifierToken);
+      expect((token as IdentifierToken).name, equals('_foo'));
     });
 
     test('has a leading dash', () {
       var token = _scan('-foo');
-      expect(token.name, equals('-foo'));
+      expect(token, _isIdentifierToken);
+      expect((token as IdentifierToken).name, equals('-foo'));
     });
 
     test('contains an underscore', () {
       var token = _scan('foo_bar');
-      expect(token.name, equals('foo_bar'));
+      expect(token, _isIdentifierToken);
+      expect((token as IdentifierToken).name, equals('foo_bar'));
     });
 
     test('contains a dash', () {
       var token = _scan('foo-bar');
-      expect(token.name, equals('foo-bar'));
+      expect(token, _isIdentifierToken);
+      expect((token as IdentifierToken).name, equals('foo-bar'));
     });
 
     test('is capitalized', () {
       var token = _scan('FOO');
-      expect(token.name, equals('FOO'));
+      expect(token, _isIdentifierToken);
+      expect((token as IdentifierToken).name, equals('FOO'));
     });
 
     test('contains numbers', () {
       var token = _scan('foo123');
-      expect(token.name, equals('foo123'));
+      expect(token, _isIdentifierToken);
+      expect((token as IdentifierToken).name, equals('foo123'));
     });
   });
 
@@ -263,4 +276,4 @@
 }
 
 /// Scans a single token from [selector].
-dynamic _scan(String selector) => Scanner(selector).next();
+Token _scan(String selector) => Scanner(selector).next();