Fix newly enforced package:pedantic lints (#19)

- always_declare_return_types
- annotate_overrides
- prefer_conditional_assignment
- prefer_if_null_operators
- prefer_single_quotes
- use_function_type_syntax_for_parameters

Drop unused author field from pubspec.

Simplify dependency on `string_scanner` since older versions aren't
supported on the Dart 2 SDK and so wouldn't be picked up even with a
wide constraint.
diff --git a/example/example.dart b/example/example.dart
index 35548e4..a4f7879 100644
--- a/example/example.dart
+++ b/example/example.dart
@@ -1,6 +1,6 @@
 import 'package:boolean_selector/boolean_selector.dart';
 
 void main(List<String> args) {
-  var selector = BooleanSelector.parse("(x && y) || z");
+  var selector = BooleanSelector.parse('(x && y) || z');
   print(selector.evaluate((variable) => args.contains(variable)));
 }
diff --git a/lib/boolean_selector.dart b/lib/boolean_selector.dart
index adc36b3..e32eb40 100644
--- a/lib/boolean_selector.dart
+++ b/lib/boolean_selector.dart
@@ -54,5 +54,5 @@
   ///
   /// The [isDefined] function should return `true` for any variables that are
   /// considered valid, and `false` for any invalid or undefined variables.
-  void validate(bool isDefined(String variable));
+  void validate(bool Function(String variable) isDefined);
 }
diff --git a/lib/src/all.dart b/lib/src/all.dart
index f4b2c1f..d634b9f 100644
--- a/lib/src/all.dart
+++ b/lib/src/all.dart
@@ -8,17 +8,23 @@
 class All implements BooleanSelector {
   // TODO(nweiz): Stop explicitly providing a type argument when sdk#32412 is
   // fixed.
+  @override
   final variables = const <String>[];
 
   const All();
 
+  @override
   bool evaluate(semantics) => true;
 
+  @override
   BooleanSelector intersection(BooleanSelector other) => other;
 
+  @override
   BooleanSelector union(BooleanSelector other) => this;
 
-  void validate(bool isDefined(String variable)) {}
+  @override
+  void validate(bool Function(String variable) isDefined) {}
 
-  String toString() => "<all>";
+  @override
+  String toString() => '<all>';
 }
diff --git a/lib/src/ast.dart b/lib/src/ast.dart
index c532535..1408ffd 100644
--- a/lib/src/ast.dart
+++ b/lib/src/ast.dart
@@ -21,52 +21,65 @@
   Iterable<String> get variables;
 
   /// Calls the appropriate [Visitor] method on [this] and returns the result.
-  accept(Visitor visitor);
+  dynamic accept(Visitor visitor);
 }
 
 /// A single variable.
 class VariableNode implements Node {
+  @override
   final FileSpan span;
 
   /// The variable name.
   final String name;
 
+  @override
   Iterable<String> get variables => [name];
 
   VariableNode(this.name, [this.span]);
 
-  accept(Visitor visitor) => visitor.visitVariable(this);
+  @override
+  dynamic accept(Visitor visitor) => visitor.visitVariable(this);
 
+  @override
   String toString() => name;
 
+  @override
   bool operator ==(other) => other is VariableNode && name == other.name;
 
+  @override
   int get hashCode => name.hashCode;
 }
 
 /// A negation expression.
 class NotNode implements Node {
+  @override
   final FileSpan span;
 
   /// The expression being negated.
   final Node child;
 
+  @override
   Iterable<String> get variables => child.variables;
 
   NotNode(this.child, [this.span]);
 
-  accept(Visitor visitor) => visitor.visitNot(this);
+  @override
+  dynamic accept(Visitor visitor) => visitor.visitNot(this);
 
+  @override
   String toString() =>
-      child is VariableNode || child is NotNode ? "!$child" : "!($child)";
+      child is VariableNode || child is NotNode ? '!$child' : '!($child)';
 
+  @override
   bool operator ==(other) => other is NotNode && child == other.child;
 
+  @override
   int get hashCode => ~child.hashCode;
 }
 
 /// An or expression.
 class OrNode implements Node {
+  @override
   FileSpan get span => _expandSafe(left.span, right.span);
 
   /// The left-hand branch of the expression.
@@ -75,6 +88,7 @@
   /// The right-hand branch of the expression.
   final Node right;
 
+  @override
   Iterable<String> get variables sync* {
     yield* left.variables;
     yield* right.variables;
@@ -82,24 +96,29 @@
 
   OrNode(this.left, this.right);
 
-  accept(Visitor visitor) => visitor.visitOr(this);
+  @override
+  dynamic accept(Visitor visitor) => visitor.visitOr(this);
 
+  @override
   String toString() {
-    var string1 = left is AndNode || left is ConditionalNode ? "($left)" : left;
+    var string1 = left is AndNode || left is ConditionalNode ? '($left)' : left;
     var string2 =
-        right is AndNode || right is ConditionalNode ? "($right)" : right;
+        right is AndNode || right is ConditionalNode ? '($right)' : right;
 
-    return "$string1 || $string2";
+    return '$string1 || $string2';
   }
 
+  @override
   bool operator ==(other) =>
       other is OrNode && left == other.left && right == other.right;
 
+  @override
   int get hashCode => left.hashCode ^ right.hashCode;
 }
 
 /// An and expression.
 class AndNode implements Node {
+  @override
   FileSpan get span => _expandSafe(left.span, right.span);
 
   /// The left-hand branch of the expression.
@@ -108,6 +127,7 @@
   /// The right-hand branch of the expression.
   final Node right;
 
+  @override
   Iterable<String> get variables sync* {
     yield* left.variables;
     yield* right.variables;
@@ -115,24 +135,29 @@
 
   AndNode(this.left, this.right);
 
-  accept(Visitor visitor) => visitor.visitAnd(this);
+  @override
+  dynamic accept(Visitor visitor) => visitor.visitAnd(this);
 
+  @override
   String toString() {
-    var string1 = left is OrNode || left is ConditionalNode ? "($left)" : left;
+    var string1 = left is OrNode || left is ConditionalNode ? '($left)' : left;
     var string2 =
-        right is OrNode || right is ConditionalNode ? "($right)" : right;
+        right is OrNode || right is ConditionalNode ? '($right)' : right;
 
-    return "$string1 && $string2";
+    return '$string1 && $string2';
   }
 
+  @override
   bool operator ==(other) =>
       other is AndNode && left == other.left && right == other.right;
 
+  @override
   int get hashCode => left.hashCode ^ right.hashCode;
 }
 
 /// A ternary conditional expression.
 class ConditionalNode implements Node {
+  @override
   FileSpan get span => _expandSafe(condition.span, whenFalse.span);
 
   /// The condition expression to check.
@@ -144,6 +169,7 @@
   /// The branch to run if the condition is false.
   final Node whenFalse;
 
+  @override
   Iterable<String> get variables sync* {
     yield* condition.variables;
     yield* whenTrue.variables;
@@ -152,21 +178,25 @@
 
   ConditionalNode(this.condition, this.whenTrue, this.whenFalse);
 
-  accept(Visitor visitor) => visitor.visitConditional(this);
+  @override
+  dynamic accept(Visitor visitor) => visitor.visitConditional(this);
 
+  @override
   String toString() {
     var conditionString =
-        condition is ConditionalNode ? "($condition)" : condition;
-    var trueString = whenTrue is ConditionalNode ? "($whenTrue)" : whenTrue;
-    return "$conditionString ? $trueString : $whenFalse";
+        condition is ConditionalNode ? '($condition)' : condition;
+    var trueString = whenTrue is ConditionalNode ? '($whenTrue)' : whenTrue;
+    return '$conditionString ? $trueString : $whenFalse';
   }
 
+  @override
   bool operator ==(other) =>
       other is ConditionalNode &&
       condition == other.condition &&
       whenTrue == other.whenTrue &&
       whenFalse == other.whenFalse;
 
+  @override
   int get hashCode =>
       condition.hashCode ^ whenTrue.hashCode ^ whenFalse.hashCode;
 }
diff --git a/lib/src/evaluator.dart b/lib/src/evaluator.dart
index fa02f74..c15c0ca 100644
--- a/lib/src/evaluator.dart
+++ b/lib/src/evaluator.dart
@@ -18,16 +18,21 @@
             ? semantics.toSet().contains
             : semantics as _Semantics;
 
+  @override
   bool visitVariable(VariableNode node) => _semantics(node.name);
 
+  @override
   bool visitNot(NotNode node) => !node.child.accept(this);
 
+  @override
   bool visitOr(OrNode node) =>
       node.left.accept(this) || node.right.accept(this);
 
+  @override
   bool visitAnd(AndNode node) =>
       node.left.accept(this) && node.right.accept(this);
 
+  @override
   bool visitConditional(ConditionalNode node) => node.condition.accept(this)
       ? node.whenTrue.accept(this)
       : node.whenFalse.accept(this);
diff --git a/lib/src/impl.dart b/lib/src/impl.dart
index 49e0f5f..3cd87c8 100644
--- a/lib/src/impl.dart
+++ b/lib/src/impl.dart
@@ -28,10 +28,13 @@
 
   BooleanSelectorImpl._(this._selector);
 
+  @override
   Iterable<String> get variables => _selector.variables;
 
+  @override
   bool evaluate(semantics) => _selector.accept(Evaluator(semantics));
 
+  @override
   BooleanSelector intersection(BooleanSelector other) {
     if (other == BooleanSelector.all) return this;
     if (other == BooleanSelector.none) return other;
@@ -40,6 +43,7 @@
         : IntersectionSelector(this, other);
   }
 
+  @override
   BooleanSelector union(BooleanSelector other) {
     if (other == BooleanSelector.all) return other;
     if (other == BooleanSelector.none) return this;
@@ -48,14 +52,18 @@
         : UnionSelector(this, other);
   }
 
-  void validate(bool isDefined(String variable)) {
+  @override
+  void validate(bool Function(String variable) isDefined) {
     _selector.accept(Validator(isDefined));
   }
 
+  @override
   String toString() => _selector.toString();
 
+  @override
   bool operator ==(other) =>
       other is BooleanSelectorImpl && _selector == other._selector;
 
+  @override
   int get hashCode => _selector.hashCode;
 }
diff --git a/lib/src/intersection_selector.dart b/lib/src/intersection_selector.dart
index ef6bfdd..6c1d314 100644
--- a/lib/src/intersection_selector.dart
+++ b/lib/src/intersection_selector.dart
@@ -10,6 +10,7 @@
   final BooleanSelector _selector1;
   final BooleanSelector _selector2;
 
+  @override
   Iterable<String> get variables sync* {
     yield* _selector1.variables;
     yield* _selector2.variables;
@@ -17,25 +18,32 @@
 
   IntersectionSelector(this._selector1, this._selector2);
 
+  @override
   bool evaluate(semantics) =>
       _selector1.evaluate(semantics) && _selector2.evaluate(semantics);
 
+  @override
   BooleanSelector intersection(BooleanSelector other) =>
       IntersectionSelector(this, other);
 
+  @override
   BooleanSelector union(BooleanSelector other) => UnionSelector(this, other);
 
-  void validate(bool isDefined(String variable)) {
+  @override
+  void validate(bool Function(String variable) isDefined) {
     _selector1.validate(isDefined);
     _selector2.validate(isDefined);
   }
 
-  String toString() => "($_selector1) && ($_selector2)";
+  @override
+  String toString() => '($_selector1) && ($_selector2)';
 
+  @override
   bool operator ==(other) =>
       other is IntersectionSelector &&
       _selector1 == other._selector1 &&
       _selector2 == other._selector2;
 
+  @override
   int get hashCode => _selector1.hashCode ^ _selector2.hashCode;
 }
diff --git a/lib/src/none.dart b/lib/src/none.dart
index 637f185..1fc616f 100644
--- a/lib/src/none.dart
+++ b/lib/src/none.dart
@@ -8,17 +8,23 @@
 class None implements BooleanSelector {
   // TODO(nweiz): Stop explicitly providing a type argument when sdk#32412 is
   // fixed.
+  @override
   final variables = const <String>[];
 
   const None();
 
+  @override
   bool evaluate(semantics) => false;
 
+  @override
   BooleanSelector intersection(BooleanSelector other) => this;
 
+  @override
   BooleanSelector union(BooleanSelector other) => other;
 
-  void validate(bool isDefined(String variable)) {}
+  @override
+  void validate(bool Function(String) isDefined) {}
 
-  String toString() => "<none>";
+  @override
+  String toString() => '<none>';
 }
diff --git a/lib/src/parser.dart b/lib/src/parser.dart
index 91a2663..1a0a73b 100644
--- a/lib/src/parser.dart
+++ b/lib/src/parser.dart
@@ -28,7 +28,7 @@
 
     if (_scanner.peek().type != TokenType.endOfFile) {
       throw SourceSpanFormatException(
-          "Expected end of input.", _scanner.peek().span);
+          'Expected end of input.', _scanner.peek().span);
     }
 
     return selector;
@@ -97,7 +97,7 @@
         return VariableNode((token as IdentifierToken).name, token.span);
 
       default:
-        throw SourceSpanFormatException("Expected expression.", token.span);
+        throw SourceSpanFormatException('Expected expression.', token.span);
     }
   }
 }
diff --git a/lib/src/scanner.dart b/lib/src/scanner.dart
index 729c483..6325175 100644
--- a/lib/src/scanner.dart
+++ b/lib/src/scanner.dart
@@ -9,19 +9,19 @@
 /// A regular expression matching both whitespace and single-line comments.
 ///
 /// This will only match if consumes at least one character.
-final _whitespaceAndSingleLineComments = RegExp(r"([ \t\n]+|//[^\n]*(\n|$))+");
+final _whitespaceAndSingleLineComments = RegExp(r'([ \t\n]+|//[^\n]*(\n|$))+');
 
 /// A regular expression matching the body of a multi-line comment, after `/*`
 /// but before `*/` or a nested `/*`.
 ///
 /// This will only match if it consumes at least one character.
-final _multiLineCommentBody = RegExp(r"([^/*]|/[^*]|\*[^/])+");
+final _multiLineCommentBody = RegExp(r'([^/*]|/[^*]|\*[^/])+');
 
 /// A regular expression matching a hyphenated identifier.
 ///
 /// This is like a standard Dart identifier, except that it can also contain
 /// hyphens.
-final _hyphenatedIdentifier = RegExp(r"[a-zA-Z_-][a-zA-Z0-9_-]*");
+final _hyphenatedIdentifier = RegExp(r'[a-zA-Z_-][a-zA-Z0-9_-]*');
 
 /// A scanner that converts a boolean selector string into a stream of tokens.
 class Scanner {
@@ -40,17 +40,14 @@
   ///
   /// Throws a [StateError] if a [TokenType.endOfFile] token has already been
   /// consumed.
-  Token peek() {
-    if (_next == null) _next = _getNext();
-    return _next;
-  }
+  Token peek() => _next ??= _getNext();
 
   /// Consumes and returns the next token in the stream.
   ///
   /// Throws a [StateError] if a [TokenType.endOfFile] token has already been
   /// consumed.
   Token next() {
-    var token = _next == null ? _getNext() : _next;
+    var token = _next ?? _getNext();
     _endOfFileEmitted = token.type == TokenType.endOfFile;
     _next = null;
     return token;
@@ -69,7 +66,7 @@
 
   /// Scan and return the next token in the stream.
   Token _getNext() {
-    if (_endOfFileEmitted) throw StateError("No more tokens.");
+    if (_endOfFileEmitted) throw StateError('No more tokens.');
 
     _consumeWhitespace();
     if (_scanner.isDone) {
@@ -111,7 +108,7 @@
   /// This validates that the next two characters are `||`.
   Token _scanOr() {
     var start = _scanner.state;
-    _scanner.expect("||");
+    _scanner.expect('||');
     return Token(TokenType.or, _scanner.spanFrom(start));
   }
 
@@ -120,13 +117,13 @@
   /// This validates that the next two characters are `&&`.
   Token _scanAnd() {
     var start = _scanner.state;
-    _scanner.expect("&&");
+    _scanner.expect('&&');
     return Token(TokenType.and, _scanner.spanFrom(start));
   }
 
   /// Scans and returns an identifier token.
   Token _scanIdentifier() {
-    _scanner.expect(_hyphenatedIdentifier, name: "expression");
+    _scanner.expect(_hyphenatedIdentifier, name: 'expression');
     return IdentifierToken(_scanner.lastMatch[0], _scanner.lastSpan);
   }
 
@@ -143,12 +140,12 @@
   ///
   /// Returns whether or not a comment was consumed.
   bool _multiLineComment() {
-    if (!_scanner.scan("/*")) return false;
+    if (!_scanner.scan('/*')) return false;
 
     while (_scanner.scan(_multiLineCommentBody) || _multiLineComment()) {
       // Do nothing.
     }
-    _scanner.expect("*/");
+    _scanner.expect('*/');
 
     return true;
   }
diff --git a/lib/src/token.dart b/lib/src/token.dart
index 908d8a9..19e6864 100644
--- a/lib/src/token.dart
+++ b/lib/src/token.dart
@@ -21,7 +21,9 @@
 
 /// A token representing an identifier.
 class IdentifierToken implements Token {
+  @override
   final type = TokenType.identifier;
+  @override
   final FileSpan span;
 
   /// The name of the identifier.
@@ -29,42 +31,44 @@
 
   IdentifierToken(this.name, this.span);
 
+  @override
   String toString() => 'identifier "$name"';
 }
 
 /// An enumeration of types of tokens.
 class TokenType {
   /// A `(` character.
-  static const leftParen = TokenType._("left paren");
+  static const leftParen = TokenType._('left paren');
 
   /// A `)` character.
-  static const rightParen = TokenType._("right paren");
+  static const rightParen = TokenType._('right paren');
 
   /// A `||` sequence.
-  static const or = TokenType._("or");
+  static const or = TokenType._('or');
 
   /// A `&&` sequence.
-  static const and = TokenType._("and");
+  static const and = TokenType._('and');
 
   /// A `!` character.
-  static const not = TokenType._("not");
+  static const not = TokenType._('not');
 
   /// A `?` character.
-  static const questionMark = TokenType._("question mark");
+  static const questionMark = TokenType._('question mark');
 
   /// A `:` character.
-  static const colon = TokenType._("colon");
+  static const colon = TokenType._('colon');
 
   /// A named identifier.
-  static const identifier = TokenType._("identifier");
+  static const identifier = TokenType._('identifier');
 
   /// The end of the selector.
-  static const endOfFile = TokenType._("end of file");
+  static const endOfFile = TokenType._('end of file');
 
   /// The name of the token type.
   final String name;
 
   const TokenType._(this.name);
 
+  @override
   String toString() => name;
 }
diff --git a/lib/src/union_selector.dart b/lib/src/union_selector.dart
index a3a9b0a..c985be9 100644
--- a/lib/src/union_selector.dart
+++ b/lib/src/union_selector.dart
@@ -12,28 +12,36 @@
 
   UnionSelector(this._selector1, this._selector2);
 
+  @override
   List<String> get variables =>
       _selector1.variables.toList()..addAll(_selector2.variables);
 
+  @override
   bool evaluate(semantics) =>
       _selector1.evaluate(semantics) || _selector2.evaluate(semantics);
 
+  @override
   BooleanSelector intersection(BooleanSelector other) =>
       IntersectionSelector(this, other);
 
+  @override
   BooleanSelector union(BooleanSelector other) => UnionSelector(this, other);
 
-  void validate(bool isDefined(String variable)) {
+  @override
+  void validate(bool Function(String variable) isDefined) {
     _selector1.validate(isDefined);
     _selector2.validate(isDefined);
   }
 
-  String toString() => "($_selector1) && ($_selector2)";
+  @override
+  String toString() => '($_selector1) && ($_selector2)';
 
+  @override
   bool operator ==(other) =>
       other is UnionSelector &&
       _selector1 == other._selector1 &&
       _selector2 == other._selector2;
 
+  @override
   int get hashCode => _selector1.hashCode ^ _selector2.hashCode;
 }
diff --git a/lib/src/validator.dart b/lib/src/validator.dart
index deea1ef..a7f49f2 100644
--- a/lib/src/validator.dart
+++ b/lib/src/validator.dart
@@ -15,8 +15,9 @@
 
   Validator(this._isDefined);
 
+  @override
   void visitVariable(VariableNode node) {
     if (_isDefined(node.name)) return;
-    throw SourceSpanFormatException("Undefined variable.", node.span);
+    throw SourceSpanFormatException('Undefined variable.', node.span);
   }
 }
diff --git a/lib/src/visitor.dart b/lib/src/visitor.dart
index 4bc0436..d0efe72 100644
--- a/lib/src/visitor.dart
+++ b/lib/src/visitor.dart
@@ -20,22 +20,27 @@
 abstract class RecursiveVisitor implements Visitor {
   const RecursiveVisitor();
 
+  @override
   void visitVariable(VariableNode node) {}
 
+  @override
   void visitNot(NotNode node) {
     node.child.accept(this);
   }
 
+  @override
   void visitOr(OrNode node) {
     node.left.accept(this);
     node.right.accept(this);
   }
 
+  @override
   void visitAnd(AndNode node) {
     node.left.accept(this);
     node.right.accept(this);
   }
 
+  @override
   void visitConditional(ConditionalNode node) {
     node.condition.accept(this);
     node.whenTrue.accept(this);
diff --git a/pubspec.yaml b/pubspec.yaml
index 4b4a313..ba9a4b8 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,9 +1,8 @@
 name: boolean_selector
-version: 1.0.5
+version: 1.0.6-dev
 description: >-
   A flexible syntax for boolean expressions, based on a simplified version of
   Dart's expression syntax.
-author: Dart Team <misc@dartlang.org>
 homepage: https://github.com/dart-lang/boolean_selector
 
 environment:
@@ -11,7 +10,7 @@
 
 dependencies:
   source_span: ^1.0.0
-  string_scanner: '>=0.1.1 <2.0.0'
+  string_scanner: ^1.0.0
 
 dev_dependencies:
   pedantic: ^1.0.0
diff --git a/test/equality_test.dart b/test/equality_test.dart
index 4470cfd..b0d2500 100644
--- a/test/equality_test.dart
+++ b/test/equality_test.dart
@@ -7,42 +7,42 @@
 import 'package:boolean_selector/boolean_selector.dart';
 
 void main() {
-  test("variable", () {
-    _expectEqualsSelf("foo");
+  test('variable', () {
+    _expectEqualsSelf('foo');
   });
 
-  test("not", () {
-    _expectEqualsSelf("!foo");
+  test('not', () {
+    _expectEqualsSelf('!foo');
   });
 
-  test("or", () {
-    _expectEqualsSelf("foo || bar");
+  test('or', () {
+    _expectEqualsSelf('foo || bar');
   });
 
-  test("and", () {
-    _expectEqualsSelf("foo && bar");
+  test('and', () {
+    _expectEqualsSelf('foo && bar');
   });
 
-  test("conditional", () {
-    _expectEqualsSelf("foo ? bar : baz");
+  test('conditional', () {
+    _expectEqualsSelf('foo ? bar : baz');
   });
 
-  test("all", () {
+  test('all', () {
     expect(BooleanSelector.all, equals(BooleanSelector.all));
   });
 
-  test("none", () {
+  test('none', () {
     expect(BooleanSelector.none, equals(BooleanSelector.none));
   });
 
   test("redundant parens don't matter", () {
-    expect(BooleanSelector.parse("foo && (bar && baz)"),
-        equals(BooleanSelector.parse("foo && (bar && baz)")));
+    expect(BooleanSelector.parse('foo && (bar && baz)'),
+        equals(BooleanSelector.parse('foo && (bar && baz)')));
   });
 
-  test("meaningful parens do matter", () {
-    expect(BooleanSelector.parse("(foo && bar) || baz"),
-        equals(BooleanSelector.parse("foo && bar || baz")));
+  test('meaningful parens do matter', () {
+    expect(BooleanSelector.parse('(foo && bar) || baz'),
+        equals(BooleanSelector.parse('foo && bar || baz')));
   });
 }
 
diff --git a/test/evaluate_test.dart b/test/evaluate_test.dart
index d6961ec..9df5fdc 100644
--- a/test/evaluate_test.dart
+++ b/test/evaluate_test.dart
@@ -7,41 +7,41 @@
 import 'package:boolean_selector/boolean_selector.dart';
 
 void main() {
-  group("operator:", () {
-    test("conditional", () {
-      _expectEval("true ? true : false", true);
-      _expectEval("true ? false : true", false);
-      _expectEval("false ? true : false", false);
-      _expectEval("false ? false : true", true);
+  group('operator:', () {
+    test('conditional', () {
+      _expectEval('true ? true : false', true);
+      _expectEval('true ? false : true', false);
+      _expectEval('false ? true : false', false);
+      _expectEval('false ? false : true', true);
     });
 
-    test("or", () {
-      _expectEval("true || true", true);
-      _expectEval("true || false", true);
-      _expectEval("false || true", true);
-      _expectEval("false || false", false);
+    test('or', () {
+      _expectEval('true || true', true);
+      _expectEval('true || false', true);
+      _expectEval('false || true', true);
+      _expectEval('false || false', false);
     });
 
-    test("and", () {
-      _expectEval("true && true", true);
-      _expectEval("true && false", false);
-      _expectEval("false && true", false);
-      _expectEval("false && false", false);
+    test('and', () {
+      _expectEval('true && true', true);
+      _expectEval('true && false', false);
+      _expectEval('false && true', false);
+      _expectEval('false && false', false);
     });
 
-    test("not", () {
-      _expectEval("!true", false);
-      _expectEval("!false", true);
+    test('not', () {
+      _expectEval('!true', false);
+      _expectEval('!false', true);
     });
   });
 
-  test("with a semantics function", () {
-    _expectEval("foo", false,
-        semantics: (String variable) => variable.contains("a"));
-    _expectEval("bar", true,
-        semantics: (String variable) => variable.contains("a"));
-    _expectEval("baz", true,
-        semantics: (String variable) => variable.contains("a"));
+  test('with a semantics function', () {
+    _expectEval('foo', false,
+        semantics: (String variable) => variable.contains('a'));
+    _expectEval('bar', true,
+        semantics: (String variable) => variable.contains('a'));
+    _expectEval('baz', true,
+        semantics: (String variable) => variable.contains('a'));
   });
 }
 
@@ -58,5 +58,5 @@
 /// By default, "true" is true and all other variables are "false".
 bool _eval(String expression, {semantics}) {
   var selector = BooleanSelector.parse(expression);
-  return selector.evaluate(semantics ?? ["true"]);
+  return selector.evaluate(semantics ?? ['true']);
 }
diff --git a/test/parser_test.dart b/test/parser_test.dart
index 43af40f..5a1bd83 100644
--- a/test/parser_test.dart
+++ b/test/parser_test.dart
@@ -20,230 +20,230 @@
 final _isNotNode = TypeMatcher<NotNode>();
 
 void main() {
-  group("parses a conditional expression", () {
-    test("with identifiers", () {
-      var node = _parse("  a ? b : c   ");
-      expect(node.toString(), equals("a ? b : c"));
+  group('parses a conditional expression', () {
+    test('with identifiers', () {
+      var node = _parse('  a ? b : c   ');
+      expect(node.toString(), equals('a ? b : c'));
 
-      expect(node.span.text, 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));
     });
 
-    test("with nested ors", () {
+    test('with nested ors', () {
       // Should parse as "(a || b) ? (c || d) : (e || f)".
       // Should not parse as "a || (b ? (c || d) : (e || f))".
       // Should not parse as "((a || b) ? (c || d) : e) || f".
       // Should not parse as "a || (b ? (c || d) : e) || f".
-      _expectToString("a || b ? c || d : e || f", "a || b ? c || d : e || f");
+      _expectToString('a || b ? c || d : e || f', 'a || b ? c || d : e || f');
     });
 
-    test("with a conditional expression as branch 1", () {
+    test('with a conditional expression as branch 1', () {
       // Should parse as "a ? (b ? c : d) : e".
-      var node = _parse("a ? b ? c : d : e");
+      var node = _parse('a ? b ? c : d : e');
       expect(node, _isConditionalNode);
-      expect(node.condition, _isVar("a"));
-      expect(node.whenFalse, _isVar("e"));
+      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"));
+      expect(node.whenTrue.condition, _isVar('b'));
+      expect(node.whenTrue.whenTrue, _isVar('c'));
+      expect(node.whenTrue.whenFalse, _isVar('d'));
     });
 
-    test("with a conditional expression as branch 2", () {
+    test('with a conditional expression as branch 2', () {
       // Should parse as "a ? b : (c ? d : e)".
       // Should not parse as "(a ? b : c) ? d : e".
-      var node = _parse("a ? b : c ? d : e");
+      var node = _parse('a ? b : c ? d : e');
       expect(node, _isConditionalNode);
-      expect(node.condition, _isVar("a"));
-      expect(node.whenTrue, _isVar("b"));
+      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"));
+      expect(node.whenFalse.condition, _isVar('c'));
+      expect(node.whenFalse.whenTrue, _isVar('d'));
+      expect(node.whenFalse.whenFalse, _isVar('e'));
     });
 
-    group("which must have", () {
-      test("an expression after the ?", () {
-        expect(() => _parse("a ?"), throwsFormatException);
-        expect(() => _parse("a ? && b"), throwsFormatException);
+    group('which must have', () {
+      test('an expression after the ?', () {
+        expect(() => _parse('a ?'), throwsFormatException);
+        expect(() => _parse('a ? && b'), throwsFormatException);
       });
 
-      test("a :", () {
-        expect(() => _parse("a ? b"), throwsFormatException);
-        expect(() => _parse("a ? b && c"), throwsFormatException);
+      test('a :', () {
+        expect(() => _parse('a ? b'), throwsFormatException);
+        expect(() => _parse('a ? b && c'), throwsFormatException);
       });
 
-      test("an expression after the :", () {
-        expect(() => _parse("a ? b :"), throwsFormatException);
-        expect(() => _parse("a ? b : && c"), throwsFormatException);
+      test('an expression after the :', () {
+        expect(() => _parse('a ? b :'), throwsFormatException);
+        expect(() => _parse('a ? b : && c'), throwsFormatException);
       });
     });
   });
 
-  group("parses an or expression", () {
-    test("with identifiers", () {
-      var node = _parse("  a || b   ");
+  group('parses an or expression', () {
+    test('with identifiers', () {
+      var node = _parse('  a || b   ');
       expect(node, _isOrNode);
-      expect(node.left, _isVar("a"));
-      expect(node.right, _isVar("b"));
+      expect(node.left, _isVar('a'));
+      expect(node.right, _isVar('b'));
 
-      expect(node.span.text, equals("a || b"));
+      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", () {
+    test('with nested ands', () {
       // Should parse as "(a && b) || (c && d)".
       // Should not parse as "a && (b || c) && d".
-      var node = _parse("a && b || c && d");
+      var node = _parse('a && b || c && d');
       expect(node, _isOrNode);
 
       expect(node.left, _isAndNode);
-      expect(node.left.left, _isVar("a"));
-      expect(node.left.right, _isVar("b"));
+      expect(node.left.left, _isVar('a'));
+      expect(node.left.right, _isVar('b'));
 
       expect(node.right, _isAndNode);
-      expect(node.right.left, _isVar("c"));
-      expect(node.right.right, _isVar("d"));
+      expect(node.right.left, _isVar('c'));
+      expect(node.right.right, _isVar('d'));
     });
 
-    test("with trailing ors", () {
+    test('with trailing ors', () {
       // Should parse as "a || (b || (c || d))", although it doesn't affect the
       // semantics.
-      var node = _parse("a || b || c || d");
+      var node = _parse('a || b || c || d');
 
-      for (var variable in ["a", "b", "c"]) {
+      for (var variable in ['a', 'b', 'c']) {
         expect(node, _isOrNode);
         expect(node.left, _isVar(variable));
         node = node.right;
       }
-      expect(node, _isVar("d"));
+      expect(node, _isVar('d'));
     });
 
-    test("which must have an expression after the ||", () {
-      expect(() => _parse("a ||"), throwsFormatException);
-      expect(() => _parse("a || && b"), throwsFormatException);
+    test('which must have an expression after the ||', () {
+      expect(() => _parse('a ||'), throwsFormatException);
+      expect(() => _parse('a || && b'), throwsFormatException);
     });
   });
 
-  group("parses an and expression", () {
-    test("with identifiers", () {
-      var node = _parse("  a && b   ");
+  group('parses an and expression', () {
+    test('with identifiers', () {
+      var node = _parse('  a && b   ');
       expect(node, _isAndNode);
-      expect(node.left, _isVar("a"));
-      expect(node.right, _isVar("b"));
+      expect(node.left, _isVar('a'));
+      expect(node.right, _isVar('b'));
 
-      expect(node.span.text, equals("a && b"));
+      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", () {
+    test('with nested nots', () {
       // Should parse as "(!a) && (!b)", obviously.
       // Should not parse as "!(a && (!b))".
-      var node = _parse("!a && !b");
+      var node = _parse('!a && !b');
       expect(node, _isAndNode);
 
       expect(node.left, _isNotNode);
-      expect(node.left.child, _isVar("a"));
+      expect(node.left.child, _isVar('a'));
 
       expect(node.right, _isNotNode);
-      expect(node.right.child, _isVar("b"));
+      expect(node.right.child, _isVar('b'));
     });
 
-    test("with trailing ands", () {
+    test('with trailing ands', () {
       // Should parse as "a && (b && (c && d))", although it doesn't affect the
       // semantics since .
-      var node = _parse("a && b && c && d");
+      var node = _parse('a && b && c && d');
 
-      for (var variable in ["a", "b", "c"]) {
+      for (var variable in ['a', 'b', 'c']) {
         expect(node, _isAndNode);
         expect(node.left, _isVar(variable));
         node = node.right;
       }
-      expect(node, _isVar("d"));
+      expect(node, _isVar('d'));
     });
 
-    test("which must have an expression after the &&", () {
-      expect(() => _parse("a &&"), throwsFormatException);
-      expect(() => _parse("a && && b"), throwsFormatException);
+    test('which must have an expression after the &&', () {
+      expect(() => _parse('a &&'), throwsFormatException);
+      expect(() => _parse('a && && b'), throwsFormatException);
     });
   });
 
-  group("parses a not expression", () {
-    test("with an identifier", () {
-      var node = _parse("  ! a    ");
+  group('parses a not expression', () {
+    test('with an identifier', () {
+      var node = _parse('  ! a    ');
       expect(node, _isNotNode);
-      expect(node.child, _isVar("a"));
+      expect(node.child, _isVar('a'));
 
-      expect(node.span.text, equals("! a"));
+      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)");
+    test('with a parenthesized expression', () {
+      var node = _parse('!(a || b)');
       expect(node, _isNotNode);
 
       expect(node.child, _isOrNode);
-      expect(node.child.left, _isVar("a"));
-      expect(node.child.right, _isVar("b"));
+      expect(node.child.left, _isVar('a'));
+      expect(node.child.right, _isVar('b'));
     });
 
-    test("with a nested not", () {
-      var node = _parse("!!a");
+    test('with a nested not', () {
+      var node = _parse('!!a');
       expect(node, _isNotNode);
       expect(node.child, _isNotNode);
-      expect(node.child.child, _isVar("a"));
+      expect(node.child.child, _isVar('a'));
     });
 
-    test("which must have an expression after the !", () {
-      expect(() => _parse("!"), throwsFormatException);
-      expect(() => _parse("! && a"), throwsFormatException);
+    test('which must have an expression after the !', () {
+      expect(() => _parse('!'), throwsFormatException);
+      expect(() => _parse('! && a'), throwsFormatException);
     });
   });
 
-  group("parses a parenthesized expression", () {
-    test("with an identifier", () {
-      var node = _parse("(a)");
-      expect(node, _isVar("a"));
+  group('parses a parenthesized expression', () {
+    test('with an identifier', () {
+      var node = _parse('(a)');
+      expect(node, _isVar('a'));
     });
 
-    test("controls precedence", () {
+    test('controls precedence', () {
       // Without parentheses, this would parse as "(a || b) ? c : d".
-      var node = _parse("a || (b ? c : d)");
+      var node = _parse('a || (b ? c : d)');
 
       expect(node, _isOrNode);
-      expect(node.left, _isVar("a"));
+      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"));
+      expect(node.right.condition, _isVar('b'));
+      expect(node.right.whenTrue, _isVar('c'));
+      expect(node.right.whenFalse, _isVar('d'));
     });
 
-    group("which must have", () {
-      test("an expression within the ()", () {
-        expect(() => _parse("()"), throwsFormatException);
-        expect(() => _parse("( && a )"), throwsFormatException);
+    group('which must have', () {
+      test('an expression within the ()', () {
+        expect(() => _parse('()'), throwsFormatException);
+        expect(() => _parse('( && a )'), throwsFormatException);
       });
 
-      test("a matching )", () {
-        expect(() => _parse("( a"), throwsFormatException);
+      test('a matching )', () {
+        expect(() => _parse('( a'), throwsFormatException);
       });
     });
   });
 
-  group("disallows", () {
-    test("an empty selector", () {
-      expect(() => _parse(""), throwsFormatException);
+  group('disallows', () {
+    test('an empty selector', () {
+      expect(() => _parse(''), throwsFormatException);
     });
 
-    test("too many expressions", () {
-      expect(() => _parse("a b"), throwsFormatException);
+    test('too many expressions', () {
+      expect(() => _parse('a b'), throwsFormatException);
     });
   });
 }
@@ -258,7 +258,7 @@
     'is a variable named "$name"');
 
 void _expectToString(String selector, [String result]) {
-  if (result == null) result = selector;
+  result ??= selector;
   expect(_toString(selector), equals(result),
       reason: 'Expected toString of "$selector" to be "$result".');
 }
diff --git a/test/scanner_test.dart b/test/scanner_test.dart
index fafe31a..c684278 100644
--- a/test/scanner_test.dart
+++ b/test/scanner_test.dart
@@ -8,16 +8,16 @@
 import 'package:boolean_selector/src/token.dart';
 
 void main() {
-  group("peek()", () {
-    test("returns the next token without consuming it", () {
-      var scanner = Scanner("( )");
+  group('peek()', () {
+    test('returns the next token without consuming it', () {
+      var scanner = Scanner('( )');
       expect(scanner.peek().type, equals(TokenType.leftParen));
       expect(scanner.peek().type, equals(TokenType.leftParen));
       expect(scanner.peek().type, equals(TokenType.leftParen));
     });
 
-    test("returns an end-of-file token at the end of a file", () {
-      var scanner = Scanner("( )");
+    test('returns an end-of-file token at the end of a file', () {
+      var scanner = Scanner('( )');
       scanner.next();
       scanner.next();
 
@@ -27,8 +27,8 @@
       expect(token.span.end.offset, equals(3));
     });
 
-    test("throws a StateError if called after end-of-file was consumed", () {
-      var scanner = Scanner("( )");
+    test('throws a StateError if called after end-of-file was consumed', () {
+      var scanner = Scanner('( )');
       scanner.next();
       scanner.next();
       scanner.next();
@@ -36,16 +36,16 @@
     });
   });
 
-  group("next()", () {
-    test("consumes and returns the next token", () {
-      var scanner = Scanner("( )");
+  group('next()', () {
+    test('consumes and returns the next token', () {
+      var scanner = Scanner('( )');
       expect(scanner.next().type, equals(TokenType.leftParen));
       expect(scanner.peek().type, equals(TokenType.rightParen));
       expect(scanner.next().type, equals(TokenType.rightParen));
     });
 
-    test("returns an end-of-file token at the end of a file", () {
-      var scanner = Scanner("( )");
+    test('returns an end-of-file token at the end of a file', () {
+      var scanner = Scanner('( )');
       scanner.next();
       scanner.next();
 
@@ -55,8 +55,8 @@
       expect(token.span.end.offset, equals(3));
     });
 
-    test("throws a StateError if called after end-of-file was consumed", () {
-      var scanner = Scanner("( )");
+    test('throws a StateError if called after end-of-file was consumed', () {
+      var scanner = Scanner('( )');
       scanner.next();
       scanner.next();
       scanner.next();
@@ -64,21 +64,21 @@
     });
   });
 
-  group("scan()", () {
-    test("consumes a matching token and returns true", () {
-      var scanner = Scanner("( )");
+  group('scan()', () {
+    test('consumes a matching token and returns true', () {
+      var scanner = Scanner('( )');
       expect(scanner.scan(TokenType.leftParen), isTrue);
       expect(scanner.peek().type, equals(TokenType.rightParen));
     });
 
     test("doesn't consume a matching token and returns false", () {
-      var scanner = Scanner("( )");
+      var scanner = Scanner('( )');
       expect(scanner.scan(TokenType.questionMark), isFalse);
       expect(scanner.peek().type, equals(TokenType.leftParen));
     });
 
-    test("throws a StateError called after end-of-file was consumed", () {
-      var scanner = Scanner("( )");
+    test('throws a StateError called after end-of-file was consumed', () {
+      var scanner = Scanner('( )');
       scanner.next();
       scanner.next();
       scanner.next();
@@ -86,67 +86,67 @@
     });
   });
 
-  group("scans a simple token:", () {
-    test("left paren", () => _expectSimpleScan("(", TokenType.leftParen));
-    test("right paren", () => _expectSimpleScan(")", TokenType.rightParen));
-    test("or", () => _expectSimpleScan("||", TokenType.or));
-    test("and", () => _expectSimpleScan("&&", TokenType.and));
-    test("not", () => _expectSimpleScan("!", TokenType.not));
-    test("question mark", () => _expectSimpleScan("?", TokenType.questionMark));
-    test("colon", () => _expectSimpleScan(":", TokenType.colon));
+  group('scans a simple token:', () {
+    test('left paren', () => _expectSimpleScan('(', TokenType.leftParen));
+    test('right paren', () => _expectSimpleScan(')', TokenType.rightParen));
+    test('or', () => _expectSimpleScan('||', TokenType.or));
+    test('and', () => _expectSimpleScan('&&', TokenType.and));
+    test('not', () => _expectSimpleScan('!', TokenType.not));
+    test('question mark', () => _expectSimpleScan('?', TokenType.questionMark));
+    test('colon', () => _expectSimpleScan(':', TokenType.colon));
   });
 
-  group("scans an identifier that", () {
-    test("is simple", () {
-      var token = _scan("   foo  ");
-      expect(token.name, equals("foo"));
-      expect(token.span.text, equals("foo"));
+  group('scans an identifier that', () {
+    test('is simple', () {
+      var token = _scan('   foo  ');
+      expect(token.name, equals('foo'));
+      expect(token.span.text, equals('foo'));
       expect(token.span.start.offset, equals(3));
       expect(token.span.end.offset, equals(6));
     });
 
-    test("is a single character", () {
-      var token = _scan("f");
-      expect(token.name, equals("f"));
+    test('is a single character', () {
+      var token = _scan('f');
+      expect(token.name, equals('f'));
     });
 
-    test("has a leading underscore", () {
-      var token = _scan("_foo");
-      expect(token.name, equals("_foo"));
+    test('has a leading underscore', () {
+      var token = _scan('_foo');
+      expect(token.name, equals('_foo'));
     });
 
-    test("has a leading dash", () {
-      var token = _scan("-foo");
-      expect(token.name, equals("-foo"));
+    test('has a leading dash', () {
+      var token = _scan('-foo');
+      expect(token.name, equals('-foo'));
     });
 
-    test("contains an underscore", () {
-      var token = _scan("foo_bar");
-      expect(token.name, equals("foo_bar"));
+    test('contains an underscore', () {
+      var token = _scan('foo_bar');
+      expect(token.name, equals('foo_bar'));
     });
 
-    test("contains a dash", () {
-      var token = _scan("foo-bar");
-      expect(token.name, equals("foo-bar"));
+    test('contains a dash', () {
+      var token = _scan('foo-bar');
+      expect(token.name, equals('foo-bar'));
     });
 
-    test("is capitalized", () {
-      var token = _scan("FOO");
-      expect(token.name, equals("FOO"));
+    test('is capitalized', () {
+      var token = _scan('FOO');
+      expect(token.name, equals('FOO'));
     });
 
-    test("contains numbers", () {
-      var token = _scan("foo123");
-      expect(token.name, equals("foo123"));
+    test('contains numbers', () {
+      var token = _scan('foo123');
+      expect(token.name, equals('foo123'));
     });
   });
 
-  test("scans an empty selector", () {
-    expect(_scan("").type, equals(TokenType.endOfFile));
+  test('scans an empty selector', () {
+    expect(_scan('').type, equals(TokenType.endOfFile));
   });
 
-  test("scans multiple tokens", () {
-    var scanner = Scanner("(foo && bar)");
+  test('scans multiple tokens', () {
+    var scanner = Scanner('(foo && bar)');
 
     var token = scanner.next();
     expect(token.type, equals(TokenType.leftParen));
@@ -155,7 +155,7 @@
 
     token = scanner.next();
     expect(token.type, equals(TokenType.identifier));
-    expect((token as IdentifierToken).name, equals("foo"));
+    expect((token as IdentifierToken).name, equals('foo'));
     expect(token.span.start.offset, equals(1));
     expect(token.span.end.offset, equals(4));
 
@@ -166,7 +166,7 @@
 
     token = scanner.next();
     expect(token.type, equals(TokenType.identifier));
-    expect((token as IdentifierToken).name, equals("bar"));
+    expect((token as IdentifierToken).name, equals('bar'));
     expect(token.span.start.offset, equals(8));
     expect(token.span.end.offset, equals(11));
 
@@ -181,73 +181,73 @@
     expect(token.span.end.offset, equals(12));
   });
 
-  group("ignores", () {
-    test("a single-line comment", () {
-      var scanner = Scanner("( // &&\n// ||\n)");
+  group('ignores', () {
+    test('a single-line comment', () {
+      var scanner = Scanner('( // &&\n// ||\n)');
       expect(scanner.next().type, equals(TokenType.leftParen));
       expect(scanner.next().type, equals(TokenType.rightParen));
       expect(scanner.next().type, equals(TokenType.endOfFile));
     });
 
-    test("a single-line comment without a trailing newline", () {
-      var scanner = Scanner("( // &&");
+    test('a single-line comment without a trailing newline', () {
+      var scanner = Scanner('( // &&');
       expect(scanner.next().type, equals(TokenType.leftParen));
       expect(scanner.next().type, equals(TokenType.endOfFile));
     });
 
-    test("a multi-line comment", () {
-      var scanner = Scanner("( /* && * /\n|| */\n)");
+    test('a multi-line comment', () {
+      var scanner = Scanner('( /* && * /\n|| */\n)');
       expect(scanner.next().type, equals(TokenType.leftParen));
       expect(scanner.next().type, equals(TokenType.rightParen));
       expect(scanner.next().type, equals(TokenType.endOfFile));
     });
 
-    test("a multi-line nested comment", () {
-      var scanner = Scanner("(/* && /* ? /* || */ : */ ! */)");
+    test('a multi-line nested comment', () {
+      var scanner = Scanner('(/* && /* ? /* || */ : */ ! */)');
       expect(scanner.next().type, equals(TokenType.leftParen));
       expect(scanner.next().type, equals(TokenType.rightParen));
       expect(scanner.next().type, equals(TokenType.endOfFile));
     });
 
     test("Dart's notion of whitespace", () {
-      var scanner = Scanner("( \t \n)");
+      var scanner = Scanner('( \t \n)');
       expect(scanner.next().type, equals(TokenType.leftParen));
       expect(scanner.next().type, equals(TokenType.rightParen));
       expect(scanner.next().type, equals(TokenType.endOfFile));
     });
   });
 
-  group("disallows", () {
-    test("a single |", () {
-      expect(() => _scan("|"), throwsFormatException);
+  group('disallows', () {
+    test('a single |', () {
+      expect(() => _scan('|'), throwsFormatException);
     });
 
     test('"| |"', () {
-      expect(() => _scan("| |"), throwsFormatException);
+      expect(() => _scan('| |'), throwsFormatException);
     });
 
-    test("a single &", () {
-      expect(() => _scan("&"), throwsFormatException);
+    test('a single &', () {
+      expect(() => _scan('&'), throwsFormatException);
     });
 
     test('"& &"', () {
-      expect(() => _scan("& &"), throwsFormatException);
+      expect(() => _scan('& &'), throwsFormatException);
     });
 
-    test("an unknown operator", () {
-      expect(() => _scan("=="), throwsFormatException);
+    test('an unknown operator', () {
+      expect(() => _scan('=='), throwsFormatException);
     });
 
-    test("unicode", () {
-      expect(() => _scan("öh"), throwsFormatException);
+    test('unicode', () {
+      expect(() => _scan('öh'), throwsFormatException);
     });
 
-    test("an unclosed multi-line comment", () {
-      expect(() => _scan("/*"), throwsFormatException);
+    test('an unclosed multi-line comment', () {
+      expect(() => _scan('/*'), throwsFormatException);
     });
 
-    test("an unopened multi-line comment", () {
-      expect(() => _scan("*/"), throwsFormatException);
+    test('an unopened multi-line comment', () {
+      expect(() => _scan('*/'), throwsFormatException);
     });
   });
 }
@@ -256,7 +256,7 @@
 /// and that that token's span is exactly [selector].
 void _expectSimpleScan(String selector, TokenType type) {
   // Complicate the selector to test that the span covers it correctly.
-  var token = _scan("   $selector  ");
+  var token = _scan('   $selector  ');
   expect(token.type, equals(type));
   expect(token.span.text, equals(selector));
   expect(token.span.start.offset, equals(3));
diff --git a/test/to_string_test.dart b/test/to_string_test.dart
index 11dad2a..ec1b4de 100644
--- a/test/to_string_test.dart
+++ b/test/to_string_test.dart
@@ -7,77 +7,77 @@
 import 'package:boolean_selector/boolean_selector.dart';
 
 void main() {
-  group("toString() for", () {
-    test("a variable is its name", () {
-      _expectToString("foo");
-      _expectToString("a-b");
+  group('toString() for', () {
+    test('a variable is its name', () {
+      _expectToString('foo');
+      _expectToString('a-b');
     });
 
-    group("not", () {
-      test("doesn't parenthesize a variable", () => _expectToString("!a"));
-      test("doesn't parenthesize a nested not", () => _expectToString("!!a"));
-      test("parenthesizes an or", () => _expectToString("!(a || b)"));
-      test("parenthesizes an and", () => _expectToString("!(a && b)"));
-      test("parenthesizes a condition", () => _expectToString("!(a ? b : c)"));
+    group('not', () {
+      test("doesn't parenthesize a variable", () => _expectToString('!a'));
+      test("doesn't parenthesize a nested not", () => _expectToString('!!a'));
+      test('parenthesizes an or', () => _expectToString('!(a || b)'));
+      test('parenthesizes an and', () => _expectToString('!(a && b)'));
+      test('parenthesizes a condition', () => _expectToString('!(a ? b : c)'));
     });
 
-    group("or", () {
-      test("doesn't parenthesize variables", () => _expectToString("a || b"));
-      test("doesn't parenthesize nots", () => _expectToString("!a || !b"));
+    group('or', () {
+      test("doesn't parenthesize variables", () => _expectToString('a || b'));
+      test("doesn't parenthesize nots", () => _expectToString('!a || !b'));
 
       test("doesn't parenthesize ors", () {
-        _expectToString("a || b || c || d");
-        _expectToString("((a || b) || c) || d", "a || b || c || d");
+        _expectToString('a || b || c || d');
+        _expectToString('((a || b) || c) || d', 'a || b || c || d');
       });
 
-      test("parenthesizes ands",
-          () => _expectToString("a && b || c && d", "(a && b) || (c && d)"));
+      test('parenthesizes ands',
+          () => _expectToString('a && b || c && d', '(a && b) || (c && d)'));
 
-      test("parenthesizes conditions",
-          () => _expectToString("(a ? b : c) || (e ? f : g)"));
+      test('parenthesizes conditions',
+          () => _expectToString('(a ? b : c) || (e ? f : g)'));
     });
 
-    group("and", () {
-      test("doesn't parenthesize variables", () => _expectToString("a && b"));
-      test("doesn't parenthesize nots", () => _expectToString("!a && !b"));
+    group('and', () {
+      test("doesn't parenthesize variables", () => _expectToString('a && b'));
+      test("doesn't parenthesize nots", () => _expectToString('!a && !b'));
 
       test(
-          "parenthesizes ors",
+          'parenthesizes ors',
           () =>
-              _expectToString("(a || b) && (c || d)", "(a || b) && (c || d)"));
+              _expectToString('(a || b) && (c || d)', '(a || b) && (c || d)'));
 
       test("doesn't parenthesize ands", () {
-        _expectToString("a && b && c && d");
-        _expectToString("((a && b) && c) && d", "a && b && c && d");
+        _expectToString('a && b && c && d');
+        _expectToString('((a && b) && c) && d', 'a && b && c && d');
       });
 
-      test("parenthesizes conditions",
-          () => _expectToString("(a ? b : c) && (e ? f : g)"));
+      test('parenthesizes conditions',
+          () => _expectToString('(a ? b : c) && (e ? f : g)'));
     });
 
-    group("conditional", () {
+    group('conditional', () {
       test(
-          "doesn't parenthesize variables", () => _expectToString("a ? b : c"));
+          "doesn't parenthesize variables", () => _expectToString('a ? b : c'));
 
-      test("doesn't parenthesize nots", () => _expectToString("!a ? !b : !c"));
+      test("doesn't parenthesize nots", () => _expectToString('!a ? !b : !c'));
 
       test("doesn't parenthesize ors",
-          () => _expectToString("a || b ? c || d : e || f"));
+          () => _expectToString('a || b ? c || d : e || f'));
 
       test("doesn't parenthesize ands",
-          () => _expectToString("a && b ? c && d : e && f"));
+          () => _expectToString('a && b ? c && d : e && f'));
 
-      test("parenthesizes non-trailing conditions", () {
-        _expectToString("(a ? b : c) ? (e ? f : g) : h ? i : j");
-        _expectToString("(a ? b : c) ? (e ? f : g) : (h ? i : j)",
-            "(a ? b : c) ? (e ? f : g) : h ? i : j");
+      test('parenthesizes non-trailing conditions', () {
+        _expectToString('(a ? b : c) ? (e ? f : g) : h ? i : j');
+        _expectToString('(a ? b : c) ? (e ? f : g) : (h ? i : j)',
+            '(a ? b : c) ? (e ? f : g) : h ? i : j');
       });
     });
   });
 }
 
 void _expectToString(String selector, [String result]) {
-  if (result == null) result = selector;
+  result ??= selector;
   expect(_toString(selector), equals(result),
       reason: 'Expected toString of "$selector" to be "$result".');
 }
diff --git a/test/validate_test.dart b/test/validate_test.dart
index d61fd36..f040069 100644
--- a/test/validate_test.dart
+++ b/test/validate_test.dart
@@ -6,11 +6,11 @@
 
 import 'package:boolean_selector/boolean_selector.dart';
 
-var _selector = BooleanSelector.parse("foo && bar && baz");
+var _selector = BooleanSelector.parse('foo && bar && baz');
 
 void main() {
-  test("throws if any variables are undefined", () {
-    expect(() => _selector.validate((variable) => variable == "bar"),
+  test('throws if any variables are undefined', () {
+    expect(() => _selector.validate((variable) => variable == 'bar'),
         throwsFormatException);
   });
 
diff --git a/test/variables_test.dart b/test/variables_test.dart
index 1cb4740..e354907 100644
--- a/test/variables_test.dart
+++ b/test/variables_test.dart
@@ -7,38 +7,38 @@
 import 'package:boolean_selector/boolean_selector.dart';
 
 void main() {
-  test("a variable reports itself", () {
-    expect(BooleanSelector.parse("foo").variables, equals(["foo"]));
+  test('a variable reports itself', () {
+    expect(BooleanSelector.parse('foo').variables, equals(['foo']));
   });
 
-  test("a negation reports its contents", () {
-    expect(BooleanSelector.parse("!foo").variables, equals(["foo"]));
+  test('a negation reports its contents', () {
+    expect(BooleanSelector.parse('!foo').variables, equals(['foo']));
   });
 
-  test("a parenthesized expression reports its contents", () {
-    expect(BooleanSelector.parse("(foo)").variables, equals(["foo"]));
+  test('a parenthesized expression reports its contents', () {
+    expect(BooleanSelector.parse('(foo)').variables, equals(['foo']));
   });
 
-  test("an or reports its contents", () {
+  test('an or reports its contents', () {
     expect(
-        BooleanSelector.parse("foo || bar").variables, equals(["foo", "bar"]));
+        BooleanSelector.parse('foo || bar').variables, equals(['foo', 'bar']));
   });
 
-  test("an and reports its contents", () {
+  test('an and reports its contents', () {
     expect(
-        BooleanSelector.parse("foo && bar").variables, equals(["foo", "bar"]));
+        BooleanSelector.parse('foo && bar').variables, equals(['foo', 'bar']));
   });
 
-  test("a conditional reports its contents", () {
-    expect(BooleanSelector.parse("foo ? bar : baz").variables,
-        equals(["foo", "bar", "baz"]));
+  test('a conditional reports its contents', () {
+    expect(BooleanSelector.parse('foo ? bar : baz').variables,
+        equals(['foo', 'bar', 'baz']));
   });
 
-  test("BooleanSelector.all reports no variables", () {
+  test('BooleanSelector.all reports no variables', () {
     expect(BooleanSelector.all.variables, isEmpty);
   });
 
-  test("BooleanSelector.none reports no variables", () {
+  test('BooleanSelector.none reports no variables', () {
     expect(BooleanSelector.none.variables, isEmpty);
   });
 }