migrate pub client (except tests) to null-safety (#3178)

* migrate pub client (except tests) to null-safety
diff --git a/analysis_options.yaml b/analysis_options.yaml
index 9cefb58..f469a93 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -30,7 +30,6 @@
     - implementation_imports
     - iterable_contains_unrelated_type
     - list_remove_unrelated_type
-    - literal_only_boolean_expressions
     - missing_whitespace_between_adjacent_strings
     - no_adjacent_strings_in_list
     - no_runtimeType_toString
diff --git a/bin/pub.dart b/bin/pub.dart
index 926db85..f133a0f 100644
--- a/bin/pub.dart
+++ b/bin/pub.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'package:pub/src/command_runner.dart';
 import 'package:pub/src/io.dart';
 
diff --git a/lib/pub.dart b/lib/pub.dart
index 841e57d..b2fed29 100644
--- a/lib/pub.dart
+++ b/lib/pub.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'package:args/command_runner.dart';
 import 'src/command_runner.dart';
 import 'src/pub_embeddable_command.dart';
@@ -16,7 +14,7 @@
 ///
 /// If [analytics] is given, pub will use that analytics instance to send
 /// statistics about resolutions.
-Command<int> pubCommand({PubAnalytics analytics}) =>
+Command<int> pubCommand({PubAnalytics? analytics}) =>
     PubEmbeddableCommand(analytics);
 
 /// Support for the `pub` toplevel command.
diff --git a/lib/src/authentication/client.dart b/lib/src/authentication/client.dart
index 2ede545..1f37149 100644
--- a/lib/src/authentication/client.dart
+++ b/lib/src/authentication/client.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.11
-
 import 'dart:io';
 
 import 'package:collection/collection.dart';
@@ -31,7 +29,7 @@
   final http.BaseClient _inner;
 
   /// Authentication scheme that could be used for authenticating requests.
-  final Credential credential;
+  final Credential? credential;
 
   @override
   Future<http.StreamedResponse> send(http.BaseRequest request) async {
@@ -43,9 +41,9 @@
     // archive_url hosted on 3rd party server that should not receive
     // credentials of the first party.
     if (credential != null &&
-        credential.canAuthenticate(request.url.toString())) {
+        credential!.canAuthenticate(request.url.toString())) {
       request.headers[HttpHeaders.authorizationHeader] =
-          await credential.getAuthorizationHeaderValue();
+          await credential!.getAuthorizationHeaderValue();
     }
 
     try {
@@ -55,7 +53,7 @@
       }
       return response;
     } on PubHttpException catch (e) {
-      if (e.response?.statusCode == 403) {
+      if (e.response.statusCode == 403) {
         _throwAuthException(e.response);
       }
       rethrow;
@@ -68,16 +66,16 @@
   ///
   /// [RFC]: https://datatracker.ietf.org/doc/html/rfc7235#section-4.1
   void _throwAuthException(http.BaseResponse response) {
-    String serverMessage;
+    String? serverMessage;
     if (response.headers.containsKey(HttpHeaders.wwwAuthenticateHeader)) {
       try {
-        final header = response.headers[HttpHeaders.wwwAuthenticateHeader];
+        final header = response.headers[HttpHeaders.wwwAuthenticateHeader]!;
         final challenge = AuthenticationChallenge.parseHeader(header)
             .firstWhereOrNull((challenge) =>
                 challenge.scheme == 'bearer' &&
                 challenge.parameters['realm'] == 'pub' &&
                 challenge.parameters['message'] != null);
-        if (challenge?.parameters != null) {
+        if (challenge != null) {
           serverMessage = challenge.parameters['message'];
         }
       } on FormatException {
@@ -103,7 +101,7 @@
   const AuthenticationException(this.statusCode, this.serverMessage);
 
   final int statusCode;
-  final String serverMessage;
+  final String? serverMessage;
 
   @override
   String toString() {
@@ -131,7 +129,7 @@
   try {
     return await fn(client);
   } on AuthenticationException catch (error) {
-    String message;
+    var message = '';
 
     if (error.statusCode == 401) {
       if (systemCache.tokenStore.removeCredential(hostedUrl)) {
diff --git a/lib/src/authentication/credential.dart b/lib/src/authentication/credential.dart
index 534f595..e1dfc79 100644
--- a/lib/src/authentication/credential.dart
+++ b/lib/src/authentication/credential.dart
@@ -2,12 +2,8 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.11
-
 import 'dart:io';
 
-import 'package:meta/meta.dart';
-
 import '../exceptions.dart';
 import '../source/hosted.dart';
 
@@ -27,10 +23,10 @@
 class Credential {
   /// Internal constructor that's only used by [fromJson].
   Credential._internal({
-    @required this.url,
-    @required this.unknownFields,
-    @required this.token,
-    @required this.env,
+    required this.url,
+    required this.unknownFields,
+    required this.token,
+    required this.env,
   });
 
   /// Create credential that stores clear text token.
@@ -62,7 +58,7 @@
     /// doesn't contains [key].
     ///
     /// Throws [FormatException] if value type is not [String].
-    String _string(String key) {
+    String? _string(String key) {
       if (json.containsKey(key)) {
         if (json[key] is! String) {
           throw FormatException('Provided $key value should be string');
@@ -84,10 +80,10 @@
   final Uri url;
 
   /// Authentication token value
-  final String token;
+  final String? token;
 
   /// Environment variable name that stores token value
-  final String env;
+  final String? env;
 
   /// Unknown fields found in pub-tokens.json. The fields might be created by the
   /// future version of pub tool. We don't want to override them when using the
@@ -119,8 +115,9 @@
       );
     }
 
-    if (env != null) {
-      final value = Platform.environment[env];
+    final environment = env;
+    if (environment != null) {
+      final value = Platform.environment[environment];
       if (value == null) {
         throw DataException(
           'Saved credential for "$url" pub repository requires environment '
diff --git a/lib/src/authentication/token_store.dart b/lib/src/authentication/token_store.dart
index 8431088..3ef948d 100644
--- a/lib/src/authentication/token_store.dart
+++ b/lib/src/authentication/token_store.dart
@@ -2,12 +2,9 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.11
-
 import 'dart:convert';
 import 'dart:io';
 
-import 'package:meta/meta.dart';
 import 'package:path/path.dart' as path;
 
 import '../exceptions.dart';
@@ -20,7 +17,7 @@
   TokenStore(this.configDir);
 
   /// Cache directory.
-  final String configDir;
+  final String? configDir;
 
   /// List of saved authentication tokens.
   ///
@@ -93,8 +90,7 @@
     return result;
   }
 
-  @alwaysThrows
-  void missingConfigDir() {
+  Never missingConfigDir() {
     final variable = Platform.isWindows ? '%APPDATA%' : r'$HOME';
     throw DataException('No config dir found. Check that $variable is set');
   }
@@ -147,8 +143,8 @@
 
   /// Returns [Credential] for authenticating given [hostedUrl] or `null` if no
   /// matching credential is found.
-  Credential findCredential(Uri hostedUrl) {
-    Credential matchedCredential;
+  Credential? findCredential(Uri hostedUrl) {
+    Credential? matchedCredential;
     for (final credential in credentials) {
       if (credential.url == hostedUrl && credential.isValid()) {
         if (matchedCredential == null) {
@@ -180,12 +176,16 @@
     } else if (!fileExists(tokensFile)) {
       log.message('No credentials file found at "$tokensFile"');
     } else {
-      deleteEntry(_tokensFile);
+      deleteEntry(tokensFile);
       log.message('pub-tokens.json is deleted.');
     }
   }
 
   /// Full path to the "pub-tokens.json" file.
-  String get _tokensFile =>
-      configDir == null ? null : path.join(configDir, 'pub-tokens.json');
+  ///
+  /// `null` if no config directory could be found.
+  String? get _tokensFile {
+    var dir = configDir;
+    return dir == null ? null : path.join(dir, 'pub-tokens.json');
+  }
 }
diff --git a/lib/src/command.dart b/lib/src/command.dart
index 503d174..53b301a 100644
--- a/lib/src/command.dart
+++ b/lib/src/command.dart
@@ -2,13 +2,12 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:io';
 
 import 'package:args/args.dart';
 import 'package:args/command_runner.dart';
+import 'package:collection/collection.dart' show IterableExtension;
 import 'package:http/http.dart' as http;
 import 'package:meta/meta.dart';
 
@@ -46,15 +45,23 @@
 /// of subcommands. Only leaf commands are ever actually invoked. If a command
 /// has subcommands, then one of those must always be chosen.
 abstract class PubCommand extends Command<int> {
+  @override
+  ArgResults get argResults {
+    final a = super.argResults;
+    if (a == null) {
+      throw StateError(
+          'argResults cannot be used before Command.run is called.');
+    }
+    return a;
+  }
+
   String get directory => argResults['directory'] ?? _pubTopLevel.directory;
 
-  SystemCache get cache => _cache ??= SystemCache(isOffline: isOffline);
-
-  SystemCache _cache;
+  late final SystemCache cache = SystemCache(isOffline: isOffline);
 
   GlobalPackages get globals => _globals ??= GlobalPackages(cache);
 
-  GlobalPackages _globals;
+  GlobalPackages? _globals;
 
   TokenStore get tokenStore => cache.tokenStore;
 
@@ -62,12 +69,10 @@
   ///
   /// This will load the pubspec and fail with an error if the current directory
   /// is not a package.
-  Entrypoint get entrypoint => _entrypoint ??= Entrypoint(directory, cache);
-
-  Entrypoint _entrypoint;
+  late final Entrypoint entrypoint = Entrypoint(directory, cache);
 
   /// The URL for web documentation for this command.
-  String get docUrl => null;
+  String? get docUrl => null;
 
   /// Override this and return `false` to disallow trailing options from being
   /// parsed after a non-option argument is parsed.
@@ -76,11 +81,9 @@
   // Lazily initialize the parser because the superclass constructor requires
   // it but we want to initialize it based on [allowTrailingOptions].
   @override
-  ArgParser get argParser => _argParser ??= ArgParser(
+  late final ArgParser argParser = ArgParser(
       allowTrailingOptions: allowTrailingOptions, usageLineLength: lineLength);
 
-  ArgParser _argParser;
-
   /// Override this to use offline-only sources instead of hitting the network.
   ///
   /// This will only be called before the [SystemCache] is created. After that,
@@ -88,7 +91,7 @@
   bool get isOffline => false;
 
   @override
-  String get usageFooter {
+  String? get usageFooter {
     if (docUrl == null) return null;
     return 'See $docUrl for detailed documentation.';
   }
@@ -98,37 +101,41 @@
 
   /// The first command in the command chain.
   Command get _topCommand {
-    var command = this;
-    while (command.parent != null) {
+    Command current = this;
+    while (true) {
+      var parent = current.parent;
+      if (parent == null) return current;
+      current = parent;
+    }
+  }
+
+  PubEmbeddableCommand? get _pubEmbeddableCommand {
+    Command? command = this;
+    while (command is! PubEmbeddableCommand) {
+      if (command == null) {
+        return null;
+      }
       command = command.parent;
     }
+
     return command;
   }
 
-  PubEmbeddableCommand get _pubEmbeddableCommand {
-    var command = this;
-    while (command != null && command is! PubEmbeddableCommand) {
-      command = command.parent;
-    }
-    return command;
-  }
+  PubTopLevel get _pubTopLevel =>
+      _pubEmbeddableCommand ?? runner as PubCommandRunner;
 
-  PubTopLevel get _pubTopLevel {
-    return _pubEmbeddableCommand ?? (runner as PubCommandRunner);
-  }
-
-  PubAnalytics get analytics => _pubEmbeddableCommand?.analytics;
+  PubAnalytics? get analytics => _pubEmbeddableCommand?.analytics;
 
   @override
   String get invocation {
-    var command = this;
+    PubCommand? command = this;
     var names = [];
     do {
-      names.add(command.name);
-      command = command.parent;
+      names.add(command?.name);
+      command = command?.parent as PubCommand?;
     } while (command != null);
     return [
-      runner.executableName,
+      runner!.executableName,
       ...names.reversed,
       argumentsDescription,
     ].join(' ');
@@ -145,7 +152,7 @@
   /// when exiting successfully.
   ///
   /// This should only be modified by [overrideExitCode].
-  int _exitCodeOverride;
+  int? _exitCodeOverride;
 
   /// Override the exit code that would normally be used when exiting
   /// successfully. Intended to be used by subcommands like `run` that wishes
@@ -170,10 +177,10 @@
     log.fine('Pub ${sdk.version}');
 
     try {
-      await captureErrors(runProtected,
+      await captureErrors<void>(() async => runProtected(),
           captureStackChains: _pubTopLevel.captureStackChains);
       if (_exitCodeOverride != null) {
-        return _exitCodeOverride;
+        return _exitCodeOverride!;
       }
       return exit_codes.SUCCESS;
     } catch (error, chain) {
@@ -192,7 +199,7 @@
         log.error("""
 This is an unexpected error. Please run
 
-    dart pub --trace ${_topCommand.name} ${_topCommand.argResults.arguments.map(protectArgument).join(' ')}
+    dart pub --trace ${_topCommand.name} ${_topCommand.argResults!.arguments.map(protectArgument).join(' ')}
 
 and include the logs in an issue on https://github.com/dart-lang/pub/issues/new
 """);
@@ -246,7 +253,7 @@
     log.message(usage);
   }
 
-  static String _command;
+  static String? _command;
 
   /// Returns the nested name of the command that's currently being run.
   /// Examples:
@@ -260,10 +267,10 @@
   ///
   /// For top-level commands, if an alias is used, the primary command name is
   /// returned. For instance `install` becomes `get`.
-  static String get command => _command;
+  static late final String command = _command ?? '';
 
   static void computeCommand(ArgResults argResults) {
-    var list = <String>[];
+    var list = <String?>[];
     for (var command = argResults.command;
         command != null;
         command = command.command) {
@@ -271,9 +278,8 @@
 
       if (list.isEmpty) {
         // this is a top-level command
-        final rootCommand = pubCommandAliases.entries.singleWhere(
-            (element) => element.value.contains(command.name),
-            orElse: () => null);
+        final rootCommand = pubCommandAliases.entries.singleWhereOrNull(
+            (element) => element.value.contains(command!.name));
         if (rootCommand != null) {
           commandName = rootCommand.key;
         }
@@ -288,7 +294,7 @@
   bool get captureStackChains;
   log.Verbosity get verbosity;
   bool get trace;
-  String get directory;
+  String? get directory;
 
   /// The argResults from the level of parsing of the 'pub' command.
   ArgResults get argResults;
diff --git a/lib/src/command/add.dart b/lib/src/command/add.dart
index 0775325..b481fe4 100644
--- a/lib/src/command/add.dart
+++ b/lib/src/command/add.dart
@@ -2,8 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
+import 'package:collection/collection.dart' show IterableExtension;
 import 'package:path/path.dart' as p;
 import 'package:pub_semver/pub_semver.dart';
 import 'package:yaml/yaml.dart';
@@ -44,12 +43,12 @@
 
   bool get isDev => argResults['dev'];
   bool get isDryRun => argResults['dry-run'];
-  String get gitUrl => argResults['git-url'];
-  String get gitPath => argResults['git-path'];
-  String get gitRef => argResults['git-ref'];
-  String get hostUrl => argResults['hosted-url'];
-  String get path => argResults['path'];
-  String get sdk => argResults['sdk'];
+  String? get gitUrl => argResults['git-url'];
+  String? get gitPath => argResults['git-path'];
+  String? get gitRef => argResults['git-ref'];
+  String? get hostUrl => argResults['hosted-url'];
+  String? get path => argResults['path'];
+  String? get sdk => argResults['sdk'];
 
   bool get hasGitOptions => gitUrl != null || gitRef != null || gitPath != null;
   bool get hasHostOptions => hostUrl != null;
@@ -103,7 +102,7 @@
     final updatedPubSpec =
         await _addPackageToPubspec(entrypoint.root.pubspec, package);
 
-    SolveResult solveResult;
+    late SolveResult solveResult;
 
     try {
       /// Use [SolveType.UPGRADE] to solve for the highest version of [package]
@@ -127,10 +126,10 @@
         .firstWhere((packageId) => packageId.name == package.name);
 
     /// Assert that [resultPackage] is within the original user's expectations.
-    if (package.constraint != null &&
-        !package.constraint.allows(resultPackage.version)) {
-      if (updatedPubSpec.dependencyOverrides != null &&
-          updatedPubSpec.dependencyOverrides.isNotEmpty) {
+    var constraint = package.constraint;
+    if (!constraint.allows(resultPackage.version)) {
+      var dependencyOverrides = updatedPubSpec.dependencyOverrides;
+      if (dependencyOverrides.isNotEmpty) {
         dataError(
             '"${package.name}" resolved to "${resultPackage.version}" which '
             'does not satisfy constraint "${package.constraint}". This could be '
@@ -171,7 +170,7 @@
       );
 
       if (argResults['example'] && entrypoint.example != null) {
-        await entrypoint.example.acquireDependencies(
+        await entrypoint.example!.acquireDependencies(
           SolveType.GET,
           precompile: argResults['precompile'],
           onlyReportSuccessOrFailure: true,
@@ -290,7 +289,7 @@
       final conflictingFlag = _conflictingFlagSets
           .where((s) => !s.contains(flag))
           .expand((s) => s)
-          .firstWhere(argResults.wasParsed, orElse: () => null);
+          .firstWhereOrNull(argResults.wasParsed);
       if (conflictingFlag != null) {
         usageException(
             'Packages can only have one source, "pub add" flags "--$flag" and '
@@ -317,7 +316,7 @@
 
     /// We want to allow for [constraint] to take on a `null` value here to
     /// preserve the fact that the user did not specify a constraint.
-    VersionConstraint constraint;
+    VersionConstraint? constraint;
 
     try {
       constraint = splitPackage.length == 2
@@ -329,6 +328,7 @@
 
     /// Determine the relevant [packageRange] and [pubspecInformation] depending
     /// on the type of package.
+    var path = this.path;
     if (hasGitOptions) {
       dynamic git;
 
@@ -337,7 +337,7 @@
       }
       Uri parsed;
       try {
-        parsed = Uri.parse(gitUrl);
+        parsed = Uri.parse(gitUrl!);
       } on FormatException catch (e) {
         usageException('The --git-url must be a valid url: ${e.message}.');
       }
@@ -360,7 +360,7 @@
         git.removeWhere((key, value) => value == null);
       }
 
-      packageRange = cache.sources['git']
+      packageRange = cache.sources.git
           .parseRef(packageName, git, containingPath: entrypoint.pubspecPath)
           .withConstraint(constraint ?? VersionConstraint.any);
       pubspecInformation = {'git': git};
@@ -369,13 +369,13 @@
           ? PathSource.relativePathWithPosixSeparators(
               p.relative(path, from: entrypoint.root.dir))
           : path;
-      packageRange = cache.sources['path']
+      packageRange = cache.sources.path
           .parseRef(packageName, relativeToEntryPoint,
               containingPath: entrypoint.pubspecPath)
           .withConstraint(constraint ?? VersionConstraint.any);
       pubspecInformation = {'path': relativeToEntryPoint};
     } else if (sdk != null) {
-      packageRange = cache.sources['sdk']
+      packageRange = cache.sources.sdk
           .parseRef(packageName, sdk)
           .withConstraint(constraint ?? VersionConstraint.any);
       pubspecInformation = {'sdk': sdk};
@@ -436,7 +436,8 @@
 
     /// Handle situations where the user might not have the dependencies or
     /// dev_dependencies map.
-    if (yamlEditor.parseAt([dependencyKey], orElse: () => null)?.value ==
+    if (yamlEditor.parseAt([dependencyKey],
+            orElse: () => YamlScalar.wrap(null)).value ==
         null) {
       yamlEditor.update([dependencyKey],
           {package.name: pubspecInformation ?? '^${resultPackage.version}'});
@@ -450,8 +451,8 @@
     /// Remove the package from dev_dependencies if we are adding it to
     /// dependencies. Refer to [_addPackageToPubspec] for additional discussion.
     if (!isDevelopment) {
-      final devDependenciesNode =
-          yamlEditor.parseAt(['dev_dependencies'], orElse: () => null);
+      final devDependenciesNode = yamlEditor
+          .parseAt(['dev_dependencies'], orElse: () => YamlScalar.wrap(null));
 
       if (devDependenciesNode is YamlMap &&
           devDependenciesNode.containsKey(package.name)) {
diff --git a/lib/src/command/barback.dart b/lib/src/command/barback.dart
index f059827..d058015 100644
--- a/lib/src/command/barback.dart
+++ b/lib/src/command/barback.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import '../command.dart';
 import '../log.dart' as log;
 import '../utils.dart';
diff --git a/lib/src/command/build.dart b/lib/src/command/build.dart
index 6673722..8c199d6 100644
--- a/lib/src/command/build.dart
+++ b/lib/src/command/build.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'barback.dart';
 
 /// Handles the `build` pub command.
diff --git a/lib/src/command/cache.dart b/lib/src/command/cache.dart
index baa7403..12c4fbf 100644
--- a/lib/src/command/cache.dart
+++ b/lib/src/command/cache.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import '../command.dart';
 import 'cache_add.dart';
 import 'cache_clean.dart';
diff --git a/lib/src/command/cache_add.dart b/lib/src/command/cache_add.dart
index 8dc76b7..e3e64e4 100644
--- a/lib/src/command/cache_add.dart
+++ b/lib/src/command/cache_add.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:pub_semver/pub_semver.dart';
diff --git a/lib/src/command/cache_clean.dart b/lib/src/command/cache_clean.dart
index e5ae0df..42b0f1f 100644
--- a/lib/src/command/cache_clean.dart
+++ b/lib/src/command/cache_clean.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import '../command.dart';
 import '../command_runner.dart';
 import '../io.dart';
diff --git a/lib/src/command/cache_list.dart b/lib/src/command/cache_list.dart
index 9316172..f5ec62e 100644
--- a/lib/src/command/cache_list.dart
+++ b/lib/src/command/cache_list.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:convert';
 
 import '../command.dart';
diff --git a/lib/src/command/cache_repair.dart b/lib/src/command/cache_repair.dart
index 3658531..41bae2b 100644
--- a/lib/src/command/cache_repair.dart
+++ b/lib/src/command/cache_repair.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import '../command.dart';
diff --git a/lib/src/command/deps.dart b/lib/src/command/deps.dart
index 1742701..79463fc 100644
--- a/lib/src/command/deps.dart
+++ b/lib/src/command/deps.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:collection';
 import 'dart:convert';
 
@@ -37,7 +35,8 @@
       AnalysisContextManager();
 
   /// The [StringBuffer] used to accumulate the output.
-  StringBuffer _buffer;
+  // TODO(sigurdm): use a local variable for this.
+  final _buffer = StringBuffer();
 
   /// Whether to include dev dependencies.
   bool get _includeDev => argResults['dev'];
@@ -67,8 +66,7 @@
   Future<void> runProtected() async {
     // Explicitly Run this in the directorycase we don't access `entrypoint.packageGraph`.
     entrypoint.assertUpToDate();
-
-    _buffer = StringBuffer();
+    _buffer.clear();
 
     if (argResults['json']) {
       if (argResults.wasParsed('dev')) {
@@ -89,7 +87,7 @@
         final current = toVisit.removeLast();
         if (visited.contains(current)) continue;
         visited.add(current);
-        final currentPackage = entrypoint.packageGraph.packages[current];
+        final currentPackage = entrypoint.packageGraph.packages[current]!;
         final next = (current == entrypoint.root.name
                 ? entrypoint.root.immediateDependencies
                 : currentPackage.dependencies)
@@ -121,7 +119,7 @@
           ...entrypoint.root.immediateDependencies.keys
               .map((name) => entrypoint.packageGraph.packages[name])
         ])
-          ...package.executableNames.map((name) => package == entrypoint.root
+          ...package!.executableNames.map((name) => package == entrypoint.root
               ? ':$name'
               : (package.name == name ? name : '${package.name}:$name'))
       ];
@@ -331,7 +329,6 @@
     if (package != null) return package;
     dataError('The pubspec.yaml file has changed since the pubspec.lock file '
         'was generated, please run "$topLevelProgram pub get" again.');
-    return null;
   }
 
   /// Outputs all executables reachable from [entrypoint].
@@ -342,7 +339,7 @@
               ? entrypoint.root.immediateDependencies
               : entrypoint.root.dependencies)
           .keys
-          .map((name) => entrypoint.packageGraph.packages[name])
+          .map((name) => entrypoint.packageGraph.packages[name]!)
     ];
 
     for (var package in packages) {
diff --git a/lib/src/command/downgrade.dart b/lib/src/command/downgrade.dart
index 2e26bc4..5dd9d41 100644
--- a/lib/src/command/downgrade.dart
+++ b/lib/src/command/downgrade.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import '../command.dart';
@@ -60,8 +58,9 @@
       dryRun: dryRun,
       analytics: analytics,
     );
-    if (argResults['example'] && entrypoint.example != null) {
-      await entrypoint.example.acquireDependencies(
+    var example = entrypoint.example;
+    if (argResults['example'] && example != null) {
+      await example.acquireDependencies(
         SolveType.GET,
         unlock: argResults.rest,
         dryRun: dryRun,
diff --git a/lib/src/command/get.dart b/lib/src/command/get.dart
index 23efab3..9255e1e 100644
--- a/lib/src/command/get.dart
+++ b/lib/src/command/get.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import '../command.dart';
@@ -58,14 +56,13 @@
       analytics: analytics,
     );
 
-    if (argResults['example'] && entrypoint.example != null) {
-      await entrypoint.example.acquireDependencies(
-        SolveType.GET,
-        dryRun: argResults['dry-run'],
-        precompile: argResults['precompile'],
-        onlyReportSuccessOrFailure: true,
-        analytics: analytics,
-      );
+    var example = entrypoint.example;
+    if (argResults['example'] && example != null) {
+      await example.acquireDependencies(SolveType.GET,
+          dryRun: argResults['dry-run'],
+          precompile: argResults['precompile'],
+          onlyReportSuccessOrFailure: true,
+          analytics: analytics);
     }
   }
 }
diff --git a/lib/src/command/global.dart b/lib/src/command/global.dart
index b7f7b01..a7e8898 100644
--- a/lib/src/command/global.dart
+++ b/lib/src/command/global.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import '../command.dart';
 import 'global_activate.dart';
 import 'global_deactivate.dart';
diff --git a/lib/src/command/global_activate.dart b/lib/src/command/global_activate.dart
index abd0ee3..a21386a 100644
--- a/lib/src/command/global_activate.dart
+++ b/lib/src/command/global_activate.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:pub_semver/pub_semver.dart';
@@ -54,13 +52,13 @@
   @override
   Future<void> runProtected() async {
     // Default to `null`, which means all executables.
-    List<String> executables;
+    List<String>? executables;
     if (argResults.wasParsed('executable')) {
       if (argResults.wasParsed('no-executables')) {
         usageException('Cannot pass both --no-executables and --executable.');
       }
 
-      executables = argResults['executable'] as List<String>;
+      executables = argResults['executable'];
     } else if (argResults['no-executables']) {
       // An empty list means no executables.
       executables = [];
@@ -78,8 +76,8 @@
       features[feature] = FeatureDependency.unused;
     }
 
-    var overwrite = argResults['overwrite'];
-    Uri hostedUrl;
+    final overwrite = argResults['overwrite'] as bool;
+    Uri? hostedUrl;
     if (argResults.wasParsed('hosted-url')) {
       try {
         hostedUrl = validateAndNormalizeHostedUrl(argResults['hosted-url']);
@@ -90,7 +88,7 @@
 
     Iterable<String> args = argResults.rest;
 
-    dynamic readArg([String error]) {
+    String readArg([String error = '']) {
       if (args.isEmpty) usageException(error);
       var arg = args.first;
       args = args.skip(1);
diff --git a/lib/src/command/global_deactivate.dart b/lib/src/command/global_deactivate.dart
index e06bb5e..7e99e19 100644
--- a/lib/src/command/global_deactivate.dart
+++ b/lib/src/command/global_deactivate.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import '../command.dart';
 import '../log.dart' as log;
 import '../utils.dart';
diff --git a/lib/src/command/global_list.dart b/lib/src/command/global_list.dart
index cd72791..5e22042 100644
--- a/lib/src/command/global_list.dart
+++ b/lib/src/command/global_list.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import '../command.dart';
 
 /// Handles the `global list` pub command.
diff --git a/lib/src/command/global_run.dart b/lib/src/command/global_run.dart
index 28d5ca6..b8f7f1b 100644
--- a/lib/src/command/global_run.dart
+++ b/lib/src/command/global_run.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:path/path.dart' as p;
diff --git a/lib/src/command/lish.dart b/lib/src/command/lish.dart
index 0e7a725..325d482 100644
--- a/lib/src/command/lish.dart
+++ b/lib/src/command/lish.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:io';
 
@@ -36,15 +34,13 @@
   bool get takesArguments => false;
 
   /// The URL of the server to which to upload the package.
-  Uri get server {
-    if (_server != null) {
-      return _server;
-    }
+  late final Uri server = _createServer();
 
+  Uri _createServer() {
     // An explicit argument takes precedence.
     if (argResults.wasParsed('server')) {
       try {
-        return _server = validateAndNormalizeHostedUrl(argResults['server']);
+        return validateAndNormalizeHostedUrl(argResults['server']);
       } on FormatException catch (e) {
         usageException('Invalid server: $e');
       }
@@ -54,19 +50,16 @@
     final publishTo = entrypoint.root.pubspec.publishTo;
     if (publishTo != null) {
       try {
-        return _server = validateAndNormalizeHostedUrl(publishTo);
+        return validateAndNormalizeHostedUrl(publishTo);
       } on FormatException catch (e) {
         throw DataException('Invalid publish_to: $e');
       }
     }
 
     // Use the default server if nothing else is specified
-    return _server = cache.sources.hosted.defaultUrl;
+    return cache.sources.hosted.defaultUrl;
   }
 
-  /// Cache value for [server].
-  Uri _server;
-
   /// Whether the publish is just a preview.
   bool get dryRun => argResults['dry-run'];
 
@@ -92,9 +85,9 @@
 
   Future<void> _publishUsingClient(
     List<int> packageBytes,
-    http.BaseClient client,
+    http.Client client,
   ) async {
-    Uri cloudStorageUrl;
+    Uri? cloudStorageUrl;
 
     try {
       await log.progress('Uploading', () async {
@@ -107,7 +100,7 @@
         cloudStorageUrl = Uri.parse(url);
         // TODO(nweiz): Cloud Storage can provide an XML-formatted error. We
         // should report that error and exit.
-        var request = http.MultipartRequest('POST', cloudStorageUrl);
+        var request = http.MultipartRequest('POST', cloudStorageUrl!);
 
         var fields = _expectField(parameters, 'fields', response);
         if (fields is! Map) invalidServerResponse(response);
@@ -128,7 +121,7 @@
             await client.get(Uri.parse(location), headers: pubApiHeaders));
       });
     } on PubHttpException catch (error) {
-      var url = error.response.request.url;
+      var url = error.response.request!.url;
       if (url == cloudStorageUrl) {
         // TODO(nweiz): the response may have XML-formatted information about
         // the error. Try to parse that out once we have an easily-accessible
@@ -173,7 +166,7 @@
         });
       }
     } on PubHttpException catch (error) {
-      var url = error.response.request.url;
+      var url = error.response.request!.url;
       if (Uri.parse(url.origin) == Uri.parse(server.origin)) {
         handleJsonError(error.response);
       } else {
diff --git a/lib/src/command/list_package_dirs.dart b/lib/src/command/list_package_dirs.dart
index 96c0735..09d248e 100644
--- a/lib/src/command/list_package_dirs.dart
+++ b/lib/src/command/list_package_dirs.dart
@@ -2,15 +2,13 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as p;
 
 import '../command.dart';
 import '../command_runner.dart';
-import '../exit_codes.dart' as exit_codes;
 import '../io.dart';
 import '../log.dart' as log;
+import '../package_name.dart';
 import '../utils.dart';
 
 /// Handles the `list-package-dirs` pub command.
@@ -45,7 +43,8 @@
     var output = {};
 
     // Include the local paths to all locked packages.
-    var packages = mapMap(entrypoint.lockFile.packages, value: (name, package) {
+    var packages = mapMap(entrypoint.lockFile.packages,
+        value: (String name, PackageId package) {
       var source = entrypoint.cache.source(package.source);
       var packageDir = source.getDirectory(package);
       // Normalize paths and make them absolute for backwards compatibility
@@ -67,6 +66,5 @@
     ];
 
     log.json.message(output);
-    return exit_codes.SUCCESS;
   }
 }
diff --git a/lib/src/command/login.dart b/lib/src/command/login.dart
index 747bb55..8c31e69 100644
--- a/lib/src/command/login.dart
+++ b/lib/src/command/login.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:convert';
 
@@ -40,7 +38,7 @@
     }
   }
 
-  Future<_UserInfo> _retrieveUserInfo() async {
+  Future<_UserInfo?> _retrieveUserInfo() async {
     return await oauth2.withClient(cache, (client) async {
       final discovery = await httpClient.get(Uri.https(
           'accounts.google.com', '/.well-known/openid-configuration'));
@@ -62,5 +60,5 @@
   final String email;
   _UserInfo(this.name, this.email);
   @override
-  String toString() => ['<$email>', if (name != null) name].join(' ');
+  String toString() => ['<$email>', name].join(' ');
 }
diff --git a/lib/src/command/logout.dart b/lib/src/command/logout.dart
index 071e8c0..04ffe12 100644
--- a/lib/src/command/logout.dart
+++ b/lib/src/command/logout.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import '../command.dart';
diff --git a/lib/src/command/outdated.dart b/lib/src/command/outdated.dart
index e28b7ad..cedbadf 100644
--- a/lib/src/command/outdated.dart
+++ b/lib/src/command/outdated.dart
@@ -2,14 +2,13 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:convert';
 import 'dart:io';
 import 'dart:math';
 
-import 'package:meta/meta.dart';
+import 'package:collection/collection.dart'
+    show IterableExtension, IterableNullableExtension;
 import 'package:path/path.dart' as path;
 import 'package:pub_semver/pub_semver.dart';
 
@@ -120,7 +119,7 @@
       'outdated': _OutdatedMode(),
       'null-safety': _NullSafetyMode(cache, entrypoint,
           shouldShowSpinner: _shouldShowSpinner),
-    }[argResults['mode']];
+    }[argResults['mode']]!;
 
     final includeDevDependencies = argResults['dev-dependencies'];
     final includeDependencyOverrides = argResults['dependency-overrides'];
@@ -139,10 +138,10 @@
 
     final resolvablePubspec = await mode.resolvablePubspec(upgradablePubspec);
 
-    List<PackageId> upgradablePackages;
-    List<PackageId> resolvablePackages;
-    bool hasUpgradableResolution;
-    bool hasResolvableResolution;
+    late List<PackageId> upgradablePackages;
+    late List<PackageId> resolvablePackages;
+    late bool hasUpgradableResolution;
+    late bool hasResolvableResolution;
 
     await log.spinner('Resolving', () async {
       final upgradablePackagesResult =
@@ -170,16 +169,16 @@
 
     Future<_PackageDetails> analyzeDependency(PackageRef packageRef) async {
       final name = packageRef.name;
-      final current = (entrypoint.lockFile?.packages ?? {})[name];
+      final current = entrypoint.lockFile.packages[name];
 
-      final upgradable = upgradablePackages.firstWhere((id) => id.name == name,
-          orElse: () => null);
-      final resolvable = resolvablePackages.firstWhere((id) => id.name == name,
-          orElse: () => null);
+      final upgradable =
+          upgradablePackages.firstWhereOrNull((id) => id.name == name);
+      final resolvable =
+          resolvablePackages.firstWhereOrNull((id) => id.name == name);
 
       // Find the latest version, and if it's overridden.
       var latestIsOverridden = false;
-      PackageId latest;
+      PackageId? latest;
       // If not overridden in current resolution we can use this
       if (!entrypoint.root.pubspec.dependencyOverrides.containsKey(name)) {
         latest ??= await _getLatest(current);
@@ -291,21 +290,19 @@
     return argResults['mode'] != 'null-safety';
   }
 
-  bool _prereleases;
-
-  bool get prereleases => _prereleases ??= () {
-        // First check if 'prereleases' was passed as an argument.
-        // If that was not the case, check for use of the legacy spelling
-        // 'pre-releases'.
-        // Otherwise fall back to the default implied by the mode.
-        if (argResults.wasParsed('prereleases')) {
-          return argResults['prereleases'];
-        }
-        if (argResults.wasParsed('pre-releases')) {
-          return argResults['pre-releases'];
-        }
-        return argResults['mode'] == 'null-safety';
-      }();
+  late final bool prereleases = () {
+    // First check if 'prereleases' was passed as an argument.
+    // If that was not the case, check for use of the legacy spelling
+    // 'pre-releases'.
+    // Otherwise fall back to the default implied by the mode.
+    if (argResults.wasParsed('prereleases')) {
+      return argResults['prereleases'];
+    }
+    if (argResults.wasParsed('pre-releases')) {
+      return argResults['pre-releases'];
+    }
+    return argResults['mode'] == 'null-safety';
+  }();
 
   /// Get the latest version of [package].
   ///
@@ -316,7 +313,7 @@
   /// later stable version we return a prerelease version if it exists.
   ///
   /// Returns `null`, if unable to find the package.
-  Future<PackageId> _getLatest(PackageName package) async {
+  Future<PackageId?> _getLatest(PackageName? package) async {
     if (package == null) {
       return null;
     }
@@ -341,8 +338,8 @@
   /// Retrieves the pubspec of package [name] in [version] from [source].
   ///
   /// Returns `null`, if given `null` as a convinience.
-  Future<_VersionDetails> _describeVersion(
-    PackageId id,
+  Future<_VersionDetails?> _describeVersion(
+    PackageId? id,
     bool isOverridden,
   ) async {
     if (id == null) {
@@ -393,7 +390,7 @@
 
 /// Try to solve [pubspec] return [PackageId]s in the resolution or `null` if no
 /// resolution was found.
-Future<List<PackageId>> _tryResolve(Pubspec pubspec, SystemCache cache) async {
+Future<List<PackageId>?> _tryResolve(Pubspec pubspec, SystemCache cache) async {
   final solveResult = await tryResolveVersions(
     SolveType.UPGRADE,
     cache,
@@ -406,13 +403,13 @@
 Future<void> _outputJson(
   List<_PackageDetails> rows,
   Mode mode, {
-  @required bool showAll,
-  @required bool includeDevDependencies,
+  required bool showAll,
+  required bool includeDevDependencies,
 }) async {
   final markedRows =
       Map.fromIterables(rows, await mode.markVersionDetails(rows));
   if (!showAll) {
-    rows.removeWhere((row) => markedRows[row][0].asDesired);
+    rows.removeWhere((row) => markedRows[row]![0].asDesired);
   }
   if (!includeDevDependencies) {
     rows.removeWhere(
@@ -428,10 +425,10 @@
           ...(rows..sort((a, b) => a.name.compareTo(b.name)))
               .map((packageDetails) => {
                     'package': packageDetails.name,
-                    'current': markedRows[packageDetails][0]?.toJson(),
-                    'upgradable': markedRows[packageDetails][1]?.toJson(),
-                    'resolvable': markedRows[packageDetails][2]?.toJson(),
-                    'latest': markedRows[packageDetails][3]?.toJson(),
+                    'current': markedRows[packageDetails]![0].toJson(),
+                    'upgradable': markedRows[packageDetails]![1].toJson(),
+                    'resolvable': markedRows[packageDetails]![2].toJson(),
+                    'latest': markedRows[packageDetails]![3].toJson(),
                   })
         ]
       },
@@ -442,16 +439,16 @@
 Future<void> _outputHuman(
   List<_PackageDetails> rows,
   Mode mode, {
-  @required bool showAll,
-  @required bool useColors,
-  @required bool includeDevDependencies,
-  @required bool lockFileExists,
-  @required bool hasDirectDependencies,
-  @required bool hasDevDependencies,
-  @required bool showTransitiveDependencies,
-  @required bool hasUpgradableResolution,
-  @required bool hasResolvableResolution,
-  @required String directory,
+  required bool showAll,
+  required bool useColors,
+  required bool includeDevDependencies,
+  required bool lockFileExists,
+  required bool hasDirectDependencies,
+  required bool hasDevDependencies,
+  required bool showTransitiveDependencies,
+  required bool hasUpgradableResolution,
+  required bool hasResolvableResolution,
+  required String directory,
 }) async {
   final directoryDesc = directory == '.' ? '' : ' in $directory';
   log.message(mode.explanation(directoryDesc) + '\n');
@@ -460,11 +457,11 @@
 
   List<_FormattedString> formatted(_PackageDetails package) => [
         _FormattedString(package.name),
-        ...markedRows[package].map((m) => m.toHuman()),
+        ...markedRows[package]!.map((m) => m.toHuman()),
       ];
 
   if (!showAll) {
-    rows.removeWhere((row) => markedRows[row][0].asDesired);
+    rows.removeWhere((row) => markedRows[row]![0].asDesired);
   }
   if (rows.isEmpty) {
     log.message(mode.foundNoBadText);
@@ -532,7 +529,8 @@
     for (var j = 0; j < row.length; j++) {
       b.write(row[j].formatted(useColors: useColors));
       b.write(' ' *
-          ((columnWidths[j] + 2) - row[j].computeLength(useColors: useColors)));
+          ((columnWidths[j]! + 2) -
+              row[j].computeLength(useColors: useColors)));
     }
     log.message(b.toString());
   }
@@ -650,15 +648,15 @@
     final rows = <List<_MarkedVersionDetails>>[];
     for (final packageDetails in packages) {
       final cols = <_MarkedVersionDetails>[];
-      _VersionDetails previous;
+      _VersionDetails? previous;
       for (final versionDetails in [
         packageDetails.current,
         packageDetails.upgradable,
         packageDetails.resolvable,
         packageDetails.latest
       ]) {
-        String Function(String) color;
-        String prefix;
+        String Function(String)? color;
+        String? prefix;
         var asDesired = false;
         if (versionDetails != null) {
           final isLatest = versionDetails == packageDetails.latest;
@@ -686,8 +684,8 @@
   }
 
   @override
-  Future<Pubspec> resolvablePubspec(Pubspec pubspec) async {
-    return stripVersionUpperBounds(pubspec);
+  Future<Pubspec> resolvablePubspec(Pubspec? pubspec) async {
+    return stripVersionUpperBounds(pubspec!);
   }
 }
 
@@ -700,7 +698,7 @@
   final _notCompliantEmoji = emoji('✗', 'x');
 
   _NullSafetyMode(this.cache, this.entrypoint,
-      {@required this.shouldShowSpinner});
+      {required this.shouldShowSpinner});
 
   @override
   String explanation(String directoryDescription) => '''
@@ -740,14 +738,14 @@
           packageDetails.resolvable?._id,
           packageDetails.latest?._id,
         ]
-      }.where((id) => id != null);
+      }.whereNotNull();
 
       return Map.fromEntries(
         await Future.wait(
           ids.map(
             (id) async => MapEntry(
                 id,
-                (await id.source.bind(cache).describe(id))
+                (await id.source!.bind(cache).describe(id))
                     .languageVersion
                     .supportsNullSafety),
           ),
@@ -763,20 +761,20 @@
           packageDetails.latest
         ].map(
           (versionDetails) {
-            String Function(String) color;
-            String prefix;
-            bool nullSafetyJson;
+            String Function(String)? color;
+            String? prefix;
+            MapEntry<String, Object>? jsonExplanation;
             var asDesired = false;
             if (versionDetails != null) {
-              if (nullSafetyMap[versionDetails._id]) {
+              if (nullSafetyMap[versionDetails._id]!) {
                 color = log.green;
                 prefix = _compliantEmoji;
-                nullSafetyJson = true;
+                jsonExplanation = MapEntry('nullSafety', true);
                 asDesired = true;
               } else {
                 color = log.red;
                 prefix = _notCompliantEmoji;
-                nullSafetyJson = false;
+                jsonExplanation = MapEntry('nullSafety', false);
               }
             }
             return _MarkedVersionDetails(
@@ -784,7 +782,7 @@
               asDesired: asDesired,
               format: color,
               prefix: prefix,
-              jsonExplanation: MapEntry('nullSafety', nullSafetyJson),
+              jsonExplanation: jsonExplanation,
             );
           },
         ).toList()
@@ -839,10 +837,10 @@
 
 class _PackageDetails implements Comparable<_PackageDetails> {
   final String name;
-  final _VersionDetails current;
-  final _VersionDetails upgradable;
-  final _VersionDetails resolvable;
-  final _VersionDetails latest;
+  final _VersionDetails? current;
+  final _VersionDetails? upgradable;
+  final _VersionDetails? resolvable;
+  final _VersionDetails? latest;
   final _DependencyKind kind;
 
   _PackageDetails(this.name, this.current, this.upgradable, this.resolvable,
@@ -856,7 +854,7 @@
     return name.compareTo(other.name);
   }
 
-  Map<String, Object> toJson() {
+  Map<String, Object?> toJson() {
     return {
       'package': name,
       'current': current?.toJson(),
@@ -896,15 +894,16 @@
   devTransitive,
 }
 
-_FormattedString _format(String value, Function(String) format, {prefix = ''}) {
+_FormattedString _format(String value, String Function(String) format,
+    {prefix = ''}) {
   return _FormattedString(value, format: format, prefix: prefix);
 }
 
 class _MarkedVersionDetails {
-  final MapEntry<String, Object> _jsonExplanation;
-  final _VersionDetails _versionDetails;
-  final String Function(String) _format;
-  final String _prefix;
+  final MapEntry<String, Object>? _jsonExplanation;
+  final _VersionDetails? _versionDetails;
+  final String Function(String)? _format;
+  final String? _prefix;
 
   /// This should be true if the mode creating this consideres the version as
   /// "good".
@@ -915,7 +914,7 @@
 
   _MarkedVersionDetails(
     this._versionDetails, {
-    @required this.asDesired,
+    required this.asDesired,
     format,
     prefix = '',
     jsonExplanation,
@@ -929,12 +928,13 @@
         prefix: _prefix,
       );
 
-  Object toJson() {
+  Object? toJson() {
     if (_versionDetails == null) return null;
 
-    return _jsonExplanation == null
-        ? _versionDetails.toJson()
-        : (_versionDetails.toJson()..addEntries([_jsonExplanation]));
+    var jsonExplanation = _jsonExplanation;
+    return jsonExplanation == null
+        ? _versionDetails!.toJson()
+        : (_versionDetails!.toJson()..addEntries([jsonExplanation]));
   }
 }
 
@@ -947,15 +947,15 @@
   /// A prefix for marking this string if colors are not used.
   final String _prefix;
 
-  _FormattedString(this.value, {String Function(String) format, prefix})
+  _FormattedString(this.value, {String Function(String)? format, prefix})
       : _format = format ?? _noFormat,
         _prefix = prefix ?? '';
 
-  String formatted({@required bool useColors}) {
+  String formatted({required bool useColors}) {
     return useColors ? _format(_prefix + value) : _prefix + value;
   }
 
-  int computeLength({@required bool useColors}) {
+  int computeLength({required bool? useColors}) {
     return _prefix.length + value.length;
   }
 
diff --git a/lib/src/command/remove.dart b/lib/src/command/remove.dart
index 2072931..c7c674a 100644
--- a/lib/src/command/remove.dart
+++ b/lib/src/command/remove.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'package:yaml/yaml.dart';
 import 'package:yaml_edit/yaml_edit.dart';
 
@@ -85,8 +83,9 @@
         analytics: analytics,
       );
 
-      if (argResults['example'] && entrypoint.example != null) {
-        await entrypoint.example.acquireDependencies(
+      var example = entrypoint.example;
+      if (argResults['example'] && example != null) {
+        await example.acquireDependencies(
           SolveType.GET,
           precompile: argResults['precompile'],
           onlyReportSuccessOrFailure: true,
@@ -127,8 +126,8 @@
       /// There may be packages where the dependency is declared both in
       /// dependencies and dev_dependencies.
       for (final dependencyKey in ['dependencies', 'dev_dependencies']) {
-        final dependenciesNode =
-            yamlEditor.parseAt([dependencyKey], orElse: () => null);
+        final dependenciesNode = yamlEditor
+            .parseAt([dependencyKey], orElse: () => YamlScalar.wrap(null));
 
         if (dependenciesNode is YamlMap &&
             dependenciesNode.containsKey(package)) {
diff --git a/lib/src/command/run.dart b/lib/src/command/run.dart
index 4463c43..e223c5a 100644
--- a/lib/src/command/run.dart
+++ b/lib/src/command/run.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:path/path.dart' as p;
diff --git a/lib/src/command/serve.dart b/lib/src/command/serve.dart
index 76c0002..4c560e7 100644
--- a/lib/src/command/serve.dart
+++ b/lib/src/command/serve.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'barback.dart';
 
 /// Handles the `serve` pub command.
diff --git a/lib/src/command/token.dart b/lib/src/command/token.dart
index dafa88f..9886dcf 100644
--- a/lib/src/command/token.dart
+++ b/lib/src/command/token.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.11
-
 import '../command.dart';
 import 'token_add.dart';
 import 'token_list.dart';
diff --git a/lib/src/command/token_add.dart b/lib/src/command/token_add.dart
index 7450a8a..fddc0cc 100644
--- a/lib/src/command/token_add.dart
+++ b/lib/src/command/token_add.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.11
-
 import 'dart:async';
 import 'dart:io';
 
@@ -26,7 +24,7 @@
   @override
   String get argumentsDescription => '[hosted-url]';
 
-  String get envVar => argResults['env-var'];
+  String? get envVar => argResults['env-var'];
 
   TokenAddCommand() {
     argParser.addOption('env-var',
@@ -54,7 +52,7 @@
       if (envVar == null) {
         await _addTokenFromStdin(hostedUrl);
       } else {
-        await _addEnvVarToken(hostedUrl);
+        await _addEnvVarToken(hostedUrl, envVar!);
       }
     } on FormatException catch (e) {
       usageException('Invalid [hosted-url]: "$rawHostedUrl"\n'
@@ -81,7 +79,7 @@
     );
   }
 
-  Future<void> _addEnvVarToken(Uri hostedUrl) async {
+  Future<void> _addEnvVarToken(Uri hostedUrl, String envVar) async {
     if (envVar.isEmpty) {
       usageException('Cannot use the empty string as --env-var');
     }
diff --git a/lib/src/command/token_list.dart b/lib/src/command/token_list.dart
index 828f004..3cf73ee 100644
--- a/lib/src/command/token_list.dart
+++ b/lib/src/command/token_list.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.11
-
 import '../command.dart';
 import '../log.dart' as log;
 
diff --git a/lib/src/command/token_remove.dart b/lib/src/command/token_remove.dart
index 7c49a0d..1b08127 100644
--- a/lib/src/command/token_remove.dart
+++ b/lib/src/command/token_remove.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.11
-
 import '../command.dart';
 import '../exceptions.dart';
 import '../log.dart' as log;
diff --git a/lib/src/command/upgrade.dart b/lib/src/command/upgrade.dart
index b74cbe7..8e44abf 100644
--- a/lib/src/command/upgrade.dart
+++ b/lib/src/command/upgrade.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:io';
 
@@ -116,7 +114,7 @@
     if (argResults['example'] && entrypoint.example != null) {
       // Reload the entrypoint to ensure we pick up potential changes that has
       // been made.
-      final exampleEntrypoint = Entrypoint(directory, cache).example;
+      final exampleEntrypoint = Entrypoint(directory, cache).example!;
       await _runUpgrade(exampleEntrypoint, onlySummary: true);
     }
   }
@@ -130,7 +128,6 @@
       onlyReportSuccessOrFailure: onlySummary,
       analytics: analytics,
     );
-
     _showOfflineWarning();
   }
 
@@ -187,7 +184,7 @@
         Package.inMemory(resolvablePubspec),
       );
     }, condition: _shouldShowSpinner);
-    for (final resolvedPackage in solveResult?.packages ?? []) {
+    for (final resolvedPackage in solveResult.packages) {
       resolvedPackages[resolvedPackage.name] = resolvedPackage;
     }
 
@@ -199,9 +196,8 @@
       ...entrypoint.root.pubspec.devDependencies.values,
     ].where((dep) => dep.source is HostedSource);
     for (final dep in declaredHostedDependencies) {
-      final resolvedPackage = resolvedPackages[dep.name];
-      assert(resolvedPackage != null);
-      if (resolvedPackage == null || !toUpgrade.contains(dep.name)) {
+      final resolvedPackage = resolvedPackages[dep.name]!;
+      if (!toUpgrade.contains(dep.name)) {
         // If we're not to upgrade this package, or it wasn't in the
         // resolution somehow, then we ignore it.
         continue;
@@ -285,7 +281,7 @@
         Package.inMemory(nullsafetyPubspec),
       );
     }, condition: _shouldShowSpinner);
-    for (final resolvedPackage in solveResult?.packages ?? []) {
+    for (final resolvedPackage in solveResult.packages) {
       resolvedPackages[resolvedPackage.name] = resolvedPackage;
     }
 
@@ -297,9 +293,8 @@
       ...entrypoint.root.pubspec.devDependencies.values,
     ].where((dep) => dep.source is HostedSource);
     for (final dep in declaredHostedDependencies) {
-      final resolvedPackage = resolvedPackages[dep.name];
-      assert(resolvedPackage != null);
-      if (resolvedPackage == null || !toUpgrade.contains(dep.name)) {
+      final resolvedPackage = resolvedPackages[dep.name]!;
+      if (!toUpgrade.contains(dep.name)) {
         // If we're not to upgrade this package, or it wasn't in the
         // resolution somehow, then we ignore it.
         continue;
@@ -360,10 +355,9 @@
       ...entrypoint.root.pubspec.devDependencies.keys
     ];
     await Future.wait(directDeps.map((name) async {
-      final resolvedPackage = resolvedPackages[name];
-      assert(resolvedPackage != null);
+      final resolvedPackage = resolvedPackages[name]!;
 
-      final boundSource = resolvedPackage.source.bind(cache);
+      final boundSource = resolvedPackage.source!.bind(cache);
       final pubspec = await boundSource.describe(resolvedPackage);
       if (!pubspec.languageVersion.supportsNullSafety) {
         nonMigratedDirectDeps.add(name);
@@ -475,7 +469,7 @@
             return dep;
           }
 
-          final boundSource = dep.source.bind(cache);
+          final boundSource = dep.source!.bind(cache);
           final packages = await boundSource.getVersions(dep.toRef());
           packages.sort((a, b) => a.version.compareTo(b.version));
 
@@ -490,7 +484,9 @@
           }
 
           hasNoNullSafetyVersions.add(dep.name);
-          return null;
+          // This value is never used. We will throw an exception because
+          //`hasNonNullSafetyVersions` is not empty.
+          return dep.withConstraint(VersionConstraint.empty);
         }));
 
     final deps = _removeUpperConstraints(original.dependencies.values);
diff --git a/lib/src/command/uploader.dart b/lib/src/command/uploader.dart
index 2498eea..99a6a57 100644
--- a/lib/src/command/uploader.dart
+++ b/lib/src/command/uploader.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:io';
 
diff --git a/lib/src/command/version.dart b/lib/src/command/version.dart
index 51eeb8f..2ac96e7 100644
--- a/lib/src/command/version.dart
+++ b/lib/src/command/version.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import '../command.dart';
 import '../log.dart' as log;
 import '../sdk.dart';
diff --git a/lib/src/command_runner.dart b/lib/src/command_runner.dart
index 8a80aaf..4ed33f6 100644
--- a/lib/src/command_runner.dart
+++ b/lib/src/command_runner.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:io';
 
@@ -47,18 +45,18 @@
 
 class PubCommandRunner extends CommandRunner<int> implements PubTopLevel {
   @override
-  String get directory => _argResults['directory'];
+  String? get directory => argResults['directory'];
 
   @override
   bool get captureStackChains {
-    return _argResults['trace'] ||
-        _argResults['verbose'] ||
-        _argResults['verbosity'] == 'all';
+    return argResults['trace'] ||
+        argResults['verbose'] ||
+        argResults['verbosity'] == 'all';
   }
 
   @override
   Verbosity get verbosity {
-    switch (_argResults['verbosity']) {
+    switch (argResults['verbosity']) {
       case 'error':
         return log.Verbosity.ERROR;
       case 'warning':
@@ -73,19 +71,26 @@
         return log.Verbosity.ALL;
       default:
         // No specific verbosity given, so check for the shortcut.
-        if (_argResults['verbose']) return log.Verbosity.ALL;
+        if (argResults['verbose']) return log.Verbosity.ALL;
         return log.Verbosity.NORMAL;
     }
   }
 
   @override
-  bool get trace => _argResults['trace'];
+  bool get trace => argResults['trace'];
 
-  ArgResults _argResults;
+  ArgResults? _argResults;
 
   /// The top-level options parsed by the command runner.
   @override
-  ArgResults get argResults => _argResults;
+  ArgResults get argResults {
+    final a = _argResults;
+    if (a == null) {
+      throw StateError(
+          'argResults cannot be used before Command.run is called.');
+    }
+    return a;
+  }
 
   @override
   String get usageFooter =>
@@ -150,7 +155,7 @@
   Future<int> run(Iterable<String> args) async {
     try {
       _argResults = parse(args);
-      return await runCommand(_argResults) ?? exit_codes.SUCCESS;
+      return await runCommand(argResults) ?? exit_codes.SUCCESS;
     } on UsageException catch (error) {
       log.exception(error);
       return exit_codes.USAGE;
@@ -158,7 +163,7 @@
   }
 
   @override
-  Future<int> runCommand(ArgResults topLevelResults) async {
+  Future<int?> runCommand(ArgResults topLevelResults) async {
     _checkDepsSynced();
 
     if (topLevelResults['version']) {
diff --git a/lib/src/entrypoint.dart b/lib/src/entrypoint.dart
index 763e707..453ef56 100644
--- a/lib/src/entrypoint.dart
+++ b/lib/src/entrypoint.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:convert';
 import 'dart:io';
@@ -81,7 +79,7 @@
   final SystemCache cache;
 
   /// Whether this entrypoint exists within the package cache.
-  bool get isCached => root.dir != null && p.isWithin(cache.rootDir, root.dir);
+  bool get isCached => !root.isInMemory && p.isWithin(cache.rootDir, root.dir);
 
   /// Whether this is an entrypoint for a globally-activated package.
   final bool isGlobal;
@@ -89,45 +87,42 @@
   /// The lockfile for the entrypoint.
   ///
   /// If not provided to the entrypoint, it will be loaded lazily from disk.
-  LockFile get lockFile {
-    if (_lockFile != null) return _lockFile;
+  LockFile get lockFile => _lockFile ??= _loadLockFile();
 
+  LockFile _loadLockFile() {
     if (!fileExists(lockFilePath)) {
-      _lockFile = LockFile.empty();
+      return _lockFile = LockFile.empty();
     } else {
-      _lockFile = LockFile.load(lockFilePath, cache.sources);
+      return _lockFile = LockFile.load(lockFilePath, cache.sources);
     }
-
-    return _lockFile;
   }
 
-  LockFile _lockFile;
+  LockFile? _lockFile;
 
   /// The package graph for the application and all of its transitive
   /// dependencies.
   ///
   /// Throws a [DataError] if the `.dart_tool/package_config.json` file isn't
   /// up-to-date relative to the pubspec and the lockfile.
-  PackageGraph get packageGraph {
-    if (_packageGraph != null) return _packageGraph;
+  PackageGraph get packageGraph => _packageGraph ??= _createPackageGraph();
 
+  PackageGraph _createPackageGraph() {
     assertUpToDate();
     var packages = {
       for (var id in lockFile.packages.values) id.name: cache.load(id)
     };
     packages[root.name] = root;
 
-    _packageGraph = PackageGraph(this, lockFile, packages);
-    return _packageGraph;
+    return PackageGraph(this, lockFile, packages);
   }
 
-  PackageGraph _packageGraph;
+  PackageGraph? _packageGraph;
 
   /// Where the lock file and package configurations are to be found.
   ///
   /// Global packages (except those from path source)
   /// store these in the global cache.
-  String get _configRoot =>
+  String? get _configRoot =>
       isCached ? p.join(cache.rootDir, 'global_packages', root.name) : root.dir;
 
   /// The path to the entrypoint's ".packages" file.
@@ -135,17 +130,17 @@
   /// This file is being slowly deprecated in favor of
   /// `.dart_tool/package_config.json`. Pub will still create it, but will
   /// not require it or make use of it within pub.
-  String get packagesFile => p.normalize(p.join(_configRoot, '.packages'));
+  String get packagesFile => p.normalize(p.join(_configRoot!, '.packages'));
 
   /// The path to the entrypoint's ".dart_tool/package_config.json" file.
   String get packageConfigFile =>
-      p.normalize(p.join(_configRoot, '.dart_tool', 'package_config.json'));
+      p.normalize(p.join(_configRoot!, '.dart_tool', 'package_config.json'));
 
   /// The path to the entrypoint package's pubspec.
   String get pubspecPath => p.normalize(root.path('pubspec.yaml'));
 
   /// The path to the entrypoint package's lockfile.
-  String get lockFilePath => p.normalize(p.join(_configRoot, 'pubspec.lock'));
+  String get lockFilePath => p.normalize(p.join(_configRoot!, 'pubspec.lock'));
 
   /// The path to the entrypoint package's `.dart_tool/pub` cache directory.
   ///
@@ -183,9 +178,9 @@
         isGlobal = false;
 
   /// Creates an entrypoint given package and lockfile objects.
-  /// If a SolveResult is already created it can be passes as an optimization.
+  /// If a SolveResult is already created it can be passed as an optimization.
   Entrypoint.global(this.root, this._lockFile, this.cache,
-      {SolveResult solveResult})
+      {SolveResult? solveResult})
       : isGlobal = true {
     if (solveResult != null) {
       _packageGraph = PackageGraph.fromSolveResult(this, solveResult);
@@ -195,7 +190,7 @@
   /// Gets the [Entrypoint] package for the current working directory.
   ///
   /// This will be null if the example folder doesn't have a `pubspec.yaml`.
-  Entrypoint get example {
+  Entrypoint? get example {
     if (_example != null) return _example;
     if (!fileExists(root.path('example', 'pubspec.yaml'))) {
       return null;
@@ -203,7 +198,7 @@
     return _example = Entrypoint(root.path('example'), cache);
   }
 
-  Entrypoint _example;
+  Entrypoint? _example;
 
   /// Writes .packages and .dart_tool/package_config.json
   Future<void> writePackagesFiles() async {
@@ -245,13 +240,13 @@
   /// Updates [lockFile] and [packageRoot] accordingly.
   Future<void> acquireDependencies(
     SolveType type, {
-    Iterable<String> unlock,
+    Iterable<String>? unlock,
     bool dryRun = false,
     bool precompile = false,
-    @required PubAnalytics analytics,
+    required PubAnalytics? analytics,
     bool onlyReportSuccessOrFailure = false,
   }) async {
-    final suffix = root.dir == null || root.dir == '.' ? '' : ' in ${root.dir}';
+    final suffix = root.isInMemory || root.dir == '.' ? '' : ' in ${root.dir}';
     SolveResult result;
     try {
       result = await log.progress('Resolving dependencies$suffix', () async {
@@ -262,12 +257,12 @@
           cache,
           root,
           lockFile: lockFile,
-          unlock: unlock,
+          unlock: unlock ?? [],
         );
       });
     } catch (e) {
       if (onlyReportSuccessOrFailure && (e is ApplicationException)) {
-        final directoryOption = root.dir == null || root.dir == '.'
+        final directoryOption = root.isInMemory || root.dir == '.'
             ? ''
             : ' --directory ${root.dir}';
         throw ApplicationException(
@@ -278,7 +273,7 @@
     }
 
     // Log once about all overridden packages.
-    if (warnAboutPreReleaseSdkOverrides && result.pubspecs != null) {
+    if (warnAboutPreReleaseSdkOverrides) {
       var overriddenPackages = (result.pubspecs.values
               .where((pubspec) => pubspec.dartSdkWasOverridden)
               .map((pubspec) => pubspec.name)
@@ -321,7 +316,7 @@
 
       try {
         if (precompile) {
-          await precompileExecutables(changed: result.changedPackages);
+          await precompileExecutables();
         } else {
           _deleteExecutableSnapshots(changed: result.changedPackages);
         }
@@ -351,7 +346,7 @@
       }
     }
     final r = root.immediateDependencies.keys.expand((packageName) {
-      final package = packageGraph.packages[packageName];
+      final package = packageGraph.packages[packageName]!;
       return package.executablePaths
           .map((path) => Executable(packageName, path));
     }).toList();
@@ -359,7 +354,7 @@
   }
 
   /// Precompiles all [_builtExecutables].
-  Future<void> precompileExecutables({Iterable<String> changed}) async {
+  Future<void> precompileExecutables() async {
     migrateCache();
 
     final executables = _builtExecutables;
@@ -384,7 +379,7 @@
 
   /// Precompiles executable .dart file at [path] to a snapshot.
   Future<void> precompileExecutable(Executable executable) async {
-    return await log.progress('Building package executable', () async {
+    await log.progress('Building package executable', () async {
       ensureDir(p.dirname(pathOfExecutable(executable)));
       return waitAndPrintErrors([_precompileExecutable(executable)]);
     });
@@ -430,7 +425,7 @@
   /// The absolute path of [executable] resolved relative to [this].
   String resolveExecutable(Executable executable) {
     return p.join(
-      packageGraph.packages[executable.package].dir,
+      packageGraph.packages[executable.package]!.dir,
       executable.relativePath,
     );
   }
@@ -439,7 +434,7 @@
   ///
   /// If [changed] is passed, only dependencies whose contents might be changed
   /// if one of the given packages changes will have their executables deleted.
-  void _deleteExecutableSnapshots({Iterable<String> changed}) {
+  void _deleteExecutableSnapshots({Iterable<String>? changed}) {
     if (!dirExists(_snapshotPath)) return;
 
     // If we don't know what changed, we can't safely re-use any snapshots.
@@ -447,7 +442,8 @@
       deleteEntry(_snapshotPath);
       return;
     }
-    changed = changed.toSet();
+    var changedDeps = changed;
+    changedDeps = changedDeps.toSet();
 
     // If the existing executable was compiled with a different SDK, we need to
     // recompile regardless of what changed.
@@ -468,7 +464,7 @@
           packageGraph.isPackageMutable(package) ||
           packageGraph
               .transitiveDependencies(package)
-              .any((dep) => changed.contains(dep.name))) {
+              .any((dep) => changedDeps.contains(dep.name))) {
         deleteEntry(entry);
       }
     }
@@ -561,8 +557,8 @@
     }
 
     for (var match in _sdkConstraint.allMatches(lockFileText)) {
-      var identifier = match[1] == 'sdk' ? 'dart' : match[1].trim();
-      var sdk = sdks[identifier];
+      var identifier = match[1] == 'sdk' ? 'dart' : match[1]!.trim();
+      var sdk = sdks[identifier]!;
 
       // Don't complain if there's an SDK constraint for an unavailable SDK. For
       // example, the Flutter SDK being unavailable just means that we aren't
@@ -570,8 +566,8 @@
       // able to `pub run` non-Flutter tools even in a Flutter app.
       if (!sdk.isAvailable) continue;
 
-      var parsedConstraint = VersionConstraint.parse(match[2]);
-      if (!parsedConstraint.allows(sdk.version)) {
+      var parsedConstraint = VersionConstraint.parse(match[2]!);
+      if (!parsedConstraint.allows(sdk.version!)) {
         dataError('${sdk.name} ${sdk.version} is incompatible with your '
             "dependencies' SDK constraints. Please run \"$topLevelProgram pub get\" again.");
       }
@@ -715,7 +711,7 @@
 
     final packagePathsMapping = <String, String>{};
     for (final package in packages.keys) {
-      final packageUri = packages[package];
+      final packageUri = packages[package]!;
 
       // Pub only generates "file:" and relative URIs.
       if (packageUri.scheme != 'file' && packageUri.scheme.isNotEmpty) {
@@ -753,7 +749,7 @@
           '"pub" version, please run "$topLevelProgram pub get".');
     }
 
-    String packageConfigRaw;
+    late String packageConfigRaw;
     try {
       packageConfigRaw = readTextFile(packageConfigFile);
     } on FileException {
@@ -761,7 +757,7 @@
           'The "$packageConfigFile" file does not exist, please run "$topLevelProgram pub get".');
     }
 
-    PackageConfig cfg;
+    late PackageConfig cfg;
     try {
       cfg = PackageConfig.fromJson(json.decode(packageConfigRaw));
     } on FormatException {
@@ -850,7 +846,7 @@
     final windowsLineEndings = fileExists(lockFilePath) &&
         detectWindowsLineEndings(readTextFile(lockFilePath));
 
-    final serialized = _lockFile.serialize(root.dir);
+    final serialized = lockFile.serialize(root.dir);
     writeTextFile(lockFilePath,
         windowsLineEndings ? serialized.replaceAll('\n', '\r\n') : serialized);
   }
@@ -861,7 +857,7 @@
     // Cached packages don't have these.
     if (isCached) return;
 
-    var oldPath = p.join(_configRoot, '.pub');
+    var oldPath = p.join(_configRoot!, '.pub');
     if (!dirExists(oldPath)) return;
 
     var newPath = root.path('.dart_tool/pub');
@@ -878,8 +874,7 @@
   /// We require an SDK constraint lower-bound as of Dart 2.12.0
   void _checkSdkConstraintIsDefined(Pubspec pubspec) {
     final dartSdkConstraint = pubspec.sdkConstraints['dart'];
-    if (dartSdkConstraint is! VersionRange ||
-        (dartSdkConstraint is VersionRange && dartSdkConstraint.min == null)) {
+    if (dartSdkConstraint is! VersionRange || dartSdkConstraint.min == null) {
       // Suggest version range '>=2.10.0 <3.0.0', we avoid using:
       // [CompatibleWithVersionRange] because some pub versions don't support
       // caret syntax (e.g. '^2.10.0')
diff --git a/lib/src/executable.dart b/lib/src/executable.dart
index bdf1e0b..8c7be58 100644
--- a/lib/src/executable.dart
+++ b/lib/src/executable.dart
@@ -2,14 +2,11 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:io';
 import 'dart:isolate';
 
 import 'package:args/args.dart';
-import 'package:meta/meta.dart';
 import 'package:path/path.dart' as p;
 
 import 'entrypoint.dart';
@@ -50,11 +47,11 @@
 ///
 /// Returns the exit code of the spawned app.
 Future<int> runExecutable(
-    Entrypoint entrypoint, Executable executable, Iterable<String> args,
+    Entrypoint entrypoint, Executable executable, List<String> args,
     {bool enableAsserts = false,
-    Future<void> Function(Executable) recompile,
+    required Future<void> Function(Executable) recompile,
     List<String> vmArgs = const [],
-    @required bool alwaysUseSubprocess}) async {
+    required bool alwaysUseSubprocess}) async {
   final package = executable.package;
 
   // Make sure the package is an immediate dependency of the entrypoint or the
@@ -103,18 +100,7 @@
       await recompile(executable);
     }
     executablePath = snapshotPath;
-  } else {
-    if (executablePath == null) {
-      var message =
-          'Could not find ${log.bold(p.normalize(executable.relativePath))}';
-      if (entrypoint.isGlobal || package != entrypoint.root.name) {
-        message += ' in package ${log.bold(package)}';
-      }
-      log.error('$message.');
-      return exit_codes.NO_INPUT;
-    }
   }
-
   // We use an absolute path here not because the VM insists but because it's
   // helpful for the subprocess to be able to spawn Dart with
   // Platform.executableArguments and have that work regardless of the working
@@ -124,7 +110,7 @@
   try {
     return await _runDartProgram(
       executablePath,
-      args,
+      args.toList(),
       packageConfigAbsolute,
       enableAsserts: enableAsserts,
       vmArgs: vmArgs,
@@ -140,7 +126,7 @@
     await recompile(executable);
     return await _runDartProgram(
       executablePath,
-      args,
+      args.toList(),
       packageConfigAbsolute,
       enableAsserts: enableAsserts,
       vmArgs: vmArgs,
@@ -165,9 +151,9 @@
 /// a new process will always be started.
 Future<int> _runDartProgram(
     String path, List<String> args, String packageConfig,
-    {bool enableAsserts,
-    List<String> vmArgs,
-    @required bool alwaysUseSubprocess}) async {
+    {bool enableAsserts = false,
+    List<String> vmArgs = const <String>[],
+    required bool alwaysUseSubprocess}) async {
   path = p.absolute(path);
   packageConfig = p.absolute(packageConfig);
 
@@ -175,10 +161,8 @@
   // That provides better signal handling, and possibly faster startup.
   if ((!alwaysUseSubprocess) && vmArgs.isEmpty) {
     var argList = args.toList();
-    return await isolate.runUri(p.toUri(path), argList, null,
-        enableAsserts: enableAsserts,
-        automaticPackageResolution: packageConfig == null,
-        packageConfig: p.toUri(packageConfig));
+    return await isolate.runUri(p.toUri(path), argList, '',
+        enableAsserts: enableAsserts, packageConfig: p.toUri(packageConfig));
   } else {
     // By ignoring sigint, only the child process will get it when
     // they are sent to the current process group. That is what happens when
@@ -269,9 +253,9 @@
 Future<String> getExecutableForCommand(
   String descriptor, {
   bool allowSnapshot = true,
-  String root,
-  String pubCacheDir,
-  PubAnalytics analytics,
+  String? root,
+  String? pubCacheDir,
+  PubAnalytics? analytics,
 }) async {
   root ??= p.current;
   var asPath = descriptor;
diff --git a/lib/src/feature.dart b/lib/src/feature.dart
index ca98bc1..94134ab 100644
--- a/lib/src/feature.dart
+++ b/lib/src/feature.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'package:collection/collection.dart';
 import 'package:pub_semver/pub_semver.dart';
 
@@ -41,7 +39,7 @@
     void enableFeature(Feature feature) {
       if (!enabledFeatures.add(feature)) return;
       for (var require in feature.requires) {
-        enableFeature(features[require]);
+        enableFeature(features[require]!);
       }
     }
 
@@ -57,8 +55,8 @@
   }
 
   Feature(this.name, Iterable<PackageRange> dependencies,
-      {Iterable<String> requires,
-      Map<String, VersionConstraint> sdkConstraints,
+      {Iterable<String>? requires,
+      Map<String, VersionConstraint>? sdkConstraints,
       this.onByDefault = true})
       : dependencies = UnmodifiableListView(dependencies.toList()),
         requires = requires == null
diff --git a/lib/src/global_packages.dart b/lib/src/global_packages.dart
index 072065e..9c66bb8 100644
--- a/lib/src/global_packages.dart
+++ b/lib/src/global_packages.dart
@@ -2,12 +2,9 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:io';
 
-import 'package:meta/meta.dart';
 import 'package:path/path.dart' as p;
 import 'package:pub_semver/pub_semver.dart';
 
@@ -24,6 +21,7 @@
 import 'pub_embeddable_command.dart';
 import 'pubspec.dart';
 import 'sdk.dart';
+import 'sdk/dart.dart';
 import 'solver.dart';
 import 'solver/incompatibility_cause.dart';
 import 'source/cached.dart';
@@ -86,8 +84,9 @@
   /// If [overwriteBinStubs] is `true`, any binstubs that collide with
   /// existing binstubs in other packages will be overwritten by this one's.
   /// Otherwise, the previous ones will be preserved.
-  Future<void> activateGit(String repo, List<String> executables,
-      {Map<String, FeatureDependency> features, bool overwriteBinStubs}) async {
+  Future<void> activateGit(String repo, List<String>? executables,
+      {Map<String, FeatureDependency>? features,
+      required bool overwriteBinStubs}) async {
     var name = await cache.git.getPackageNameFromRepo(repo);
 
     // TODO(nweiz): Add some special handling for git repos that contain path
@@ -122,10 +121,10 @@
   /// [url] is an optional custom pub server URL. If not null, the package to be
   /// activated will be fetched from this URL instead of the default pub URL.
   Future<void> activateHosted(
-      String name, VersionConstraint constraint, List<String> executables,
-      {Map<String, FeatureDependency> features,
-      bool overwriteBinStubs,
-      Uri url}) async {
+      String name, VersionConstraint constraint, List<String>? executables,
+      {Map<String, FeatureDependency>? features,
+      required bool overwriteBinStubs,
+      Uri? url}) async {
     await _installInCache(
         cache.hosted.source
             .refFor(name, url: url)
@@ -144,8 +143,9 @@
   /// if [overwriteBinStubs] is `true`, any binstubs that collide with
   /// existing binstubs in other packages will be overwritten by this one's.
   /// Otherwise, the previous ones will be preserved.
-  Future<void> activatePath(String path, List<String> executables,
-      {bool overwriteBinStubs, @required PubAnalytics analytics}) async {
+  Future<void> activatePath(String path, List<String>? executables,
+      {required bool overwriteBinStubs,
+      required PubAnalytics? analytics}) async {
     var entrypoint = Entrypoint(path, cache);
 
     // Get the package's dependencies.
@@ -178,9 +178,9 @@
   }
 
   /// Installs the package [dep] and its dependencies into the system cache.
-  Future<void> _installInCache(PackageRange dep, List<String> executables,
-      {bool overwriteBinStubs}) async {
-    LockFile originalLockFile;
+  Future<void> _installInCache(PackageRange dep, List<String>? executables,
+      {required bool overwriteBinStubs}) async {
+    LockFile? originalLockFile;
     try {
       originalLockFile =
           LockFile.load(_getLockFilePath(dep.name), cache.sources);
@@ -241,12 +241,12 @@
     // We want the entrypoint to be rooted at 'dep' not the dummy-package.
     result.packages.removeWhere((id) => id.name == 'pub global activate');
 
-    var id = lockFile.packages[dep.name];
+    var id = lockFile.packages[dep.name]!;
     // Load the package graph from [result] so we don't need to re-parse all
     // the pubspecs.
     final entrypoint = Entrypoint.global(
       Package(
-        result.pubspecs[dep.name],
+        result.pubspecs[dep.name]!,
         (cache.source(dep.source) as CachedSource).getDirectoryInCache(id),
       ),
       lockFile,
@@ -260,7 +260,7 @@
 
     _updateBinStubs(
       entrypoint,
-      cache.load(entrypoint.lockFile.packages[dep.name]),
+      cache.load(entrypoint.lockFile.packages[dep.name]!),
       executables,
       overwriteBinStubs: overwriteBinStubs,
     );
@@ -296,8 +296,8 @@
   }
 
   /// Shows the user the currently active package with [name], if any.
-  void _describeActive(LockFile lockFile, String name) {
-    var id = lockFile.packages[name];
+  void _describeActive(LockFile lockFile, String? name) {
+    var id = lockFile.packages[name]!;
 
     var source = id.source;
     if (source is GitSource) {
@@ -324,7 +324,7 @@
     _deleteBinStubs(name);
 
     var lockFile = LockFile.load(_getLockFilePath(name), cache.sources);
-    var id = lockFile.packages[name];
+    var id = lockFile.packages[name]!;
     log.message('Deactivated package ${_formatPackage(id)}.');
 
     deleteEntry(dir);
@@ -337,7 +337,7 @@
   /// Returns an [Entrypoint] loaded with the active package if found.
   Future<Entrypoint> find(String name) async {
     var lockFilePath = _getLockFilePath(name);
-    LockFile lockFile;
+    late LockFile lockFile;
     try {
       lockFile = LockFile.load(lockFilePath, cache.sources);
     } on IOException {
@@ -362,7 +362,7 @@
     // Remove the package itself from the lockfile. We put it in there so we
     // could find and load the [Package] object, but normally an entrypoint
     // doesn't expect to be in its own lockfile.
-    var id = lockFile.packages[name];
+    var id = lockFile.packages[name]!;
     lockFile = lockFile.removePackage(name);
 
     var source = cache.source(id.source);
@@ -384,7 +384,7 @@
         dataError('${log.bold(name)} ${entrypoint.root.version} requires '
             'unknown SDK "$name".');
       } else if (sdkName == 'dart') {
-        if (constraint.allows(sdk.version)) return;
+        if (constraint.allows((sdk as DartSdk).version)) return;
         dataError("${log.bold(name)} ${entrypoint.root.version} doesn't "
             'support Dart ${sdk.version}.');
       } else {
@@ -400,7 +400,7 @@
         dataError('${log.bold(name)} as globally activated requires '
             'unknown SDK "$name".');
       } else if (sdkName == 'dart') {
-        if (constraint.allows(sdk.version)) return;
+        if (constraint.allows((sdk as DartSdk).version)) return;
         dataError("${log.bold(name)} as globally activated doesn't "
             'support Dart ${sdk.version}, try: $topLevelProgram pub global activate $name');
       } else {
@@ -422,11 +422,11 @@
   ///
   /// Returns the exit code from the executable.
   Future<int> runExecutable(
-      Entrypoint entrypoint, exec.Executable executable, Iterable<String> args,
+      Entrypoint entrypoint, exec.Executable executable, List<String> args,
       {bool enableAsserts = false,
-      Future<void> Function(exec.Executable) recompile,
+      required Future<void> Function(exec.Executable) recompile,
       List<String> vmArgs = const [],
-      @required bool alwaysUseSubprocess}) async {
+      required bool alwaysUseSubprocess}) async {
     return await exec.runExecutable(
       entrypoint,
       executable,
@@ -536,7 +536,7 @@
     var failures = <String>[];
     if (dirExists(_directory)) {
       for (var entry in listDir(_directory)) {
-        PackageId id;
+        PackageId? id;
         try {
           id = _loadPackageId(entry);
           log.message('Reactivating ${log.bold(id.name)} ${id.version}...');
@@ -632,8 +632,8 @@
   /// If [suggestIfNotOnPath] is `true` (the default), this will warn the user if
   /// the bin directory isn't on their path.
   void _updateBinStubs(
-      Entrypoint entrypoint, Package package, List<String> executables,
-      {bool overwriteBinStubs, bool suggestIfNotOnPath = true}) {
+      Entrypoint entrypoint, Package package, List<String>? executables,
+      {required bool overwriteBinStubs, bool suggestIfNotOnPath = true}) {
     // Remove any previously activated binstubs for this package, in case the
     // list of executables has changed.
     _deleteBinStubs(package.name);
@@ -651,7 +651,7 @@
     for (var executable in allExecutables) {
       if (executables != null && !executables.contains(executable)) continue;
 
-      var script = package.pubspec.executables[executable];
+      var script = package.pubspec.executables[executable]!;
 
       var previousPackage = _createBinStub(
         package,
@@ -735,19 +735,19 @@
   ///
   /// If a collision occurs, returns the name of the package that owns the
   /// existing binstub. Otherwise returns `null`.
-  String _createBinStub(
+  String? _createBinStub(
     Package package,
     String executable,
     String script, {
-    @required bool overwrite,
-    @required String snapshot,
+    required bool overwrite,
+    required String snapshot,
   }) {
     var binStubPath = p.join(_binStubDir, executable);
     if (Platform.isWindows) binStubPath += '.bat';
 
     // See if the binstub already exists. If so, it's for another package
     // since we already deleted all of this package's binstubs.
-    String previousPackage;
+    String? previousPackage;
     if (fileExists(binStubPath)) {
       var contents = readTextFile(binStubPath);
       previousPackage = _binStubProperty(contents, 'Package');
@@ -762,7 +762,7 @@
     // directly and skip pub global run entirely.
     String invocation;
     if (Platform.isWindows) {
-      if (snapshot != null && fileExists(snapshot)) {
+      if (fileExists(snapshot)) {
         // We expect absolute paths from the precompiler since relative ones
         // won't be relative to the right directory when the user runs this.
         assert(p.isAbsolute(snapshot));
@@ -797,7 +797,7 @@
 ''';
       writeTextFile(binStubPath, batch);
     } else {
-      if (snapshot != null && fileExists(snapshot)) {
+      if (fileExists(snapshot)) {
         // We expect absolute paths from the precompiler since relative ones
         // won't be relative to the right directory when the user runs this.
         assert(p.isAbsolute(snapshot));
@@ -897,7 +897,7 @@
       if (result.exitCode == 0) return;
 
       var binDir = _binStubDir;
-      if (binDir.startsWith(Platform.environment['HOME'])) {
+      if (binDir.startsWith(Platform.environment['HOME']!)) {
         binDir = p.join(
             r'$HOME', p.relative(binDir, from: Platform.environment['HOME']));
       }
@@ -914,7 +914,7 @@
 
   /// Returns the value of the property named [name] in the bin stub script
   /// [source].
-  String _binStubProperty(String source, String name) {
+  String? _binStubProperty(String source, String name) {
     var pattern = RegExp(RegExp.escape(name) + r': ([a-zA-Z0-9_-]+)');
     var match = pattern.firstMatch(source);
     return match == null ? null : match[1];
diff --git a/lib/src/http.dart b/lib/src/http.dart
index 541931f..fb6e9aa 100644
--- a/lib/src/http.dart
+++ b/lib/src/http.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 /// Helpers for dealing with HTTP.
 import 'dart:async';
 import 'dart:convert';
@@ -44,7 +42,7 @@
 
   http.Client _inner;
 
-  _PubHttpClient([http.Client inner]) : _inner = inner ?? http.Client();
+  _PubHttpClient([http.Client? inner]) : _inner = inner ?? http.Client();
 
   @override
   Future<http.StreamedResponse> send(http.BaseRequest request) async {
@@ -132,8 +130,8 @@
     // careful not to log OAuth2 private data, though.
 
     var responseLog = StringBuffer();
-    var request = response.request;
-    var stopwatch = _requestStopwatches.remove(request)..stop();
+    var request = response.request!;
+    var stopwatch = _requestStopwatches.remove(request)!..stop();
     responseLog.writeln('HTTP response ${response.statusCode} '
         '${response.reasonPhrase} for ${request.method} ${request.url}');
     responseLog.writeln('took ${stopwatch.elapsed}');
@@ -173,12 +171,12 @@
 
   @override
   Future<http.StreamedResponse> send(http.BaseRequest request) async {
-    http.StreamedResponse streamedResponse;
+    late http.StreamedResponse streamedResponse;
     try {
       streamedResponse = await _inner.send(request);
     } on SocketException catch (error, stackTraceOrNull) {
       // Work around issue 23008.
-      var stackTrace = stackTraceOrNull ?? Chain.current();
+      var stackTrace = stackTraceOrNull;
 
       if (error.osError == null) rethrow;
 
@@ -191,14 +189,14 @@
       // with a retry. Failing to retry intermittent issues is likely to cause
       // customers to wrap pub in a retry loop which will not improve the
       // end-user experience.
-      if (error.osError.errorCode == 8 ||
-          error.osError.errorCode == -2 ||
-          error.osError.errorCode == -5 ||
-          error.osError.errorCode == 11001 ||
-          error.osError.errorCode == 11004) {
+      if (error.osError!.errorCode == 8 ||
+          error.osError!.errorCode == -2 ||
+          error.osError!.errorCode == -5 ||
+          error.osError!.errorCode == 11001 ||
+          error.osError!.errorCode == 11004) {
         fail('Could not resolve URL "${request.url.origin}".', error,
             stackTrace);
-      } else if (error.osError.errorCode == -12276) {
+      } else if (error.osError!.errorCode == -12276) {
         fail(
             'Unable to validate SSL certificate for '
             '"${request.url.origin}".',
@@ -213,7 +211,7 @@
     // 401 responses should be handled by the OAuth2 client. It's very
     // unlikely that they'll be returned by non-OAuth2 requests. We also want
     // to pass along 400 responses from the token endpoint.
-    var tokenRequest = streamedResponse.request.url == oauth2.tokenEndpoint;
+    var tokenRequest = streamedResponse.request!.url == oauth2.tokenEndpoint;
     if (status < 400 || status == 401 || (status == 400 && tokenRequest)) {
       return streamedResponse;
     }
@@ -342,7 +340,7 @@
 }
 
 /// Throws an error describing an invalid response from the server.
-void invalidServerResponse(http.Response response) =>
+Never invalidServerResponse(http.Response response) =>
     fail(log.red('Invalid server response:\n${response.body}'));
 
 /// Exception thrown when an HTTP operation fails.
diff --git a/lib/src/lock_file.dart b/lib/src/lock_file.dart
index b2139fe..e2723c7 100644
--- a/lib/src/lock_file.dart
+++ b/lib/src/lock_file.dart
@@ -2,12 +2,9 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:convert';
 
 import 'package:collection/collection.dart' hide mapMap;
-import 'package:meta/meta.dart';
 import 'package:path/path.dart' as p;
 import 'package:pub_semver/pub_semver.dart';
 import 'package:source_span/source_span.dart';
@@ -52,10 +49,10 @@
   /// `dependency_overrides` sections, respectively. These are consumed by the
   /// analysis server to provide better auto-completion.
   LockFile(Iterable<PackageId> ids,
-      {Map<String, VersionConstraint> sdkConstraints,
-      Set<String> mainDependencies,
-      Set<String> devDependencies,
-      Set<String> overriddenDependencies})
+      {Map<String, VersionConstraint>? sdkConstraints,
+      Set<String>? mainDependencies,
+      Set<String>? devDependencies,
+      Set<String>? overriddenDependencies})
       : this._(
             Map.fromIterable(ids.where((id) => !id.isRoot),
                 key: (id) => id.name),
@@ -94,10 +91,10 @@
   /// [filePath] is the system-native path to the lockfile on disc. It may be
   /// `null`.
   static LockFile _parse(
-      String filePath, String contents, SourceRegistry sources) {
+      String? filePath, String contents, SourceRegistry sources) {
     if (contents.trim() == '') return LockFile.empty();
 
-    Uri sourceUrl;
+    Uri? sourceUrl;
     if (filePath != null) sourceUrl = p.toUri(filePath);
     var parsed = loadYamlNode(contents, sourceUrl: sourceUrl);
 
@@ -145,7 +142,7 @@
         var description = spec['description'];
 
         // Let the source parse the description.
-        var source = sources[sourceName];
+        var source = sources[sourceName]!;
         PackageId id;
         try {
           id = source.parseId(name, version, description,
@@ -173,8 +170,6 @@
 
   /// Asserts that [node] is a version constraint, and parses it.
   static VersionConstraint _parseVersionConstraint(YamlNode node) {
-    if (node == null) return null;
-
     _validate(node.value is String,
         'Invalid version constraint: must be a string.', node);
 
@@ -199,9 +194,9 @@
   }
 
   /// If [condition] is `false` throws a format error with [message] for [node].
-  static void _validate(bool condition, String message, YamlNode node) {
+  static void _validate(bool condition, String message, YamlNode? node) {
     if (condition) return;
-    throw SourceSpanFormatException(message, node.span);
+    throw SourceSpanFormatException(message, node!.span);
   }
 
   /// Returns a copy of this LockFile with a package named [name] removed.
@@ -222,8 +217,8 @@
   /// directory.
   String packagesFile(
     SystemCache cache, {
-    String entrypoint,
-    @required String relativeFrom,
+    String? entrypoint,
+    required String relativeFrom,
   }) {
     var header = '''
 This file is deprecated. Tools should instead consume 
@@ -233,9 +228,9 @@
 
 Generated by pub on ${DateTime.now()}.''';
 
-    var map =
-        Map<String, Uri>.fromIterable(ordered(packages.keys), value: (name) {
-      var id = packages[name];
+    var map = Map<String, Uri>.fromIterable(ordered<String>(packages.keys),
+        value: (name) {
+      var id = packages[name]!;
       var source = cache.source(id.source);
       return p.toUri(
           p.join(source.getDirectory(id, relativeFrom: relativeFrom), 'lib'));
@@ -259,13 +254,13 @@
   /// current package has no SDK constraint.
   Future<String> packageConfigFile(
     SystemCache cache, {
-    String entrypoint,
-    VersionConstraint entrypointSdkConstraint,
-    @required String relativeFrom,
+    String? entrypoint,
+    VersionConstraint? entrypointSdkConstraint,
+    required String relativeFrom,
   }) async {
     final entries = <PackageConfigEntry>[];
     for (final name in ordered(packages.keys)) {
-      final id = packages[name];
+      final id = packages[name]!;
       final source = cache.source(id.source);
       final rootPath = source.getDirectory(id, relativeFrom: relativeFrom);
       Uri rootUri;
@@ -317,11 +312,11 @@
     var packageMap = {};
     packages.forEach((name, package) {
       var description =
-          package.source.serializeDescription(packageDir, package.description);
+          package.source!.serializeDescription(packageDir, package.description);
 
       packageMap[name] = {
         'version': package.version.toString(),
-        'source': package.source.name,
+        'source': package.source!.name,
         'description': description,
         'dependency': _dependencyType(package.name)
       };
diff --git a/lib/src/null_safety_analysis.dart b/lib/src/null_safety_analysis.dart
index fb1c8ad..93c8993 100644
--- a/lib/src/null_safety_analysis.dart
+++ b/lib/src/null_safety_analysis.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:analyzer/dart/analysis/context_builder.dart';
@@ -74,16 +72,17 @@
   /// If [packageId] is a relative path dependency [containingPath] must be
   /// provided with an absolute path to resolve it against.
   Future<NullSafetyAnalysisResult> nullSafetyCompliance(PackageId packageId,
-      {String containingPath}) async {
+      {String? containingPath}) async {
     // A space in the name prevents clashes with other package names.
     final fakeRootName = '${packageId.name} importer';
     final fakeRoot = Package.inMemory(Pubspec(fakeRootName,
         fields: {
           'dependencies': {
             packageId.name: {
-              packageId.source.name: packageId.source is PathSource
+              packageId.source!.name: packageId.source is PathSource
                   ? (packageId.description['relative']
-                      ? path.join(containingPath, packageId.description['path'])
+                      ? path.join(
+                          containingPath!, packageId.description['path'])
                       : packageId.description['path'])
                   : packageId.description,
               'version': packageId.version.toString(),
@@ -93,7 +92,7 @@
         sources: _systemCache.sources));
 
     final rootPubspec =
-        await packageId.source.bind(_systemCache).describe(packageId);
+        await packageId.source!.bind(_systemCache).describe(packageId);
     final rootLanguageVersion = rootPubspec.languageVersion;
     if (!rootLanguageVersion.supportsNullSafety) {
       final span =
@@ -119,10 +118,10 @@
           'Could not resolve constraints: $e');
     }
     return nullSafetyComplianceOfPackages(
-        result.packages.where((id) => id.name != fakeRootName),
-        Package(rootPubspec,
-            packageId.source.bind(_systemCache).getDirectory(packageId)),
-        containingPath);
+      result.packages.where((id) => id.name != fakeRootName),
+      Package(rootPubspec,
+          packageId.source!.bind(_systemCache).getDirectory(packageId)),
+    );
   }
 
   /// Decides if all dependendencies (transitively) have a language version
@@ -139,14 +138,13 @@
   Future<NullSafetyAnalysisResult> nullSafetyComplianceOfPackages(
     Iterable<PackageId> packages,
     Package rootPackage,
-    String containingPath,
   ) async {
-    NullSafetyAnalysisResult firstBadPackage;
+    NullSafetyAnalysisResult? firstBadPackage;
     for (final dependencyId in packages) {
       final packageInternalAnalysis =
           await _packageInternallyGoodCache.putIfAbsent(dependencyId, () async {
         Pubspec pubspec;
-        BoundSource boundSource;
+        BoundSource? boundSource;
         String packageDir;
         if (dependencyId.source == null) {
           pubspec = rootPackage.pubspec;
@@ -225,7 +223,6 @@
         }
         return NullSafetyAnalysisResult(NullSafetyCompliance.compliant, null);
       });
-      assert(packageInternalAnalysis != null);
       if (packageInternalAnalysis.compliance ==
           NullSafetyCompliance.analysisFailed) {
         return packageInternalAnalysis;
@@ -251,12 +248,12 @@
   final NullSafetyCompliance compliance;
 
   /// `null` if compliance == [NullSafetyCompliance.compliant].
-  final String reason;
+  final String? reason;
 
   NullSafetyAnalysisResult(this.compliance, this.reason);
 }
 
-SourceSpan _tryGetSpanFromYamlMap(Object map, String key) {
+SourceSpan? _tryGetSpanFromYamlMap(Object? map, String key) {
   if (map is YamlMap) {
     return map.nodes[key]?.span;
   }
diff --git a/lib/src/oauth2.dart b/lib/src/oauth2.dart
index 20d32c1..7229bb1 100644
--- a/lib/src/oauth2.dart
+++ b/lib/src/oauth2.dart
@@ -2,11 +2,10 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:io';
 
+import 'package:collection/collection.dart' show IterableExtension;
 import 'package:oauth2/oauth2.dart';
 import 'package:path/path.dart' as path;
 import 'package:shelf/shelf.dart' as shelf;
@@ -64,19 +63,21 @@
 ///
 /// This should always be the same as the credentials file stored in the system
 /// cache.
-Credentials _credentials;
+Credentials? _credentials;
 
 /// Delete the cached credentials, if they exist.
 void _clearCredentials(SystemCache cache) {
   _credentials = null;
   var credentialsFile = _credentialsFile(cache);
-  if (entryExists(credentialsFile)) deleteEntry(credentialsFile);
+  if (credentialsFile != null && entryExists(credentialsFile)) {
+    deleteEntry(credentialsFile);
+  }
 }
 
 /// Try to delete the cached credentials.
 void logout(SystemCache cache) {
   var credentialsFile = _credentialsFile(cache);
-  if (entryExists(_credentialsFile(cache))) {
+  if (credentialsFile != null && entryExists(credentialsFile)) {
     log.message('Logging out of pub.dartlang.org.');
     log.message('Deleting $credentialsFile');
     _clearCredentials(cache);
@@ -151,14 +152,14 @@
 ///
 /// If the credentials can't be loaded for any reason, the returned [Future]
 /// completes to `null`.
-Credentials loadCredentials(SystemCache cache) {
+Credentials? loadCredentials(SystemCache cache) {
   log.fine('Loading OAuth2 credentials.');
 
   try {
     if (_credentials != null) return _credentials;
 
     var path = _credentialsFile(cache);
-    if (!fileExists(path)) return null;
+    if (path == null || !fileExists(path)) return null;
 
     var credentials = Credentials.fromJson(readTextFile(path));
     if (credentials.isExpired && !credentials.canRefresh) {
@@ -181,8 +182,10 @@
   log.fine('Saving OAuth2 credentials.');
   _credentials = credentials;
   var credentialsPath = _credentialsFile(cache);
-  ensureDir(path.dirname(credentialsPath));
-  writeTextFile(credentialsPath, credentials.toJson(), dontLogContents: true);
+  if (credentialsPath != null) {
+    ensureDir(path.dirname(credentialsPath));
+    writeTextFile(credentialsPath, credentials.toJson(), dontLogContents: true);
+  }
 }
 
 /// The path to the file in which the user's OAuth2 credentials are stored.
@@ -193,15 +196,16 @@
 /// To provide backwards compatibility we use the legacy file if only it exists.
 ///
 /// Returns `null` if there is no good place for the file.
-String _credentialsFile(SystemCache cache) {
+String? _credentialsFile(SystemCache cache) {
   final configDir = dartConfigDir;
 
   final newCredentialsFile =
       configDir == null ? null : path.join(configDir, 'pub-credentials.json');
-  return [
+  var file = [
     if (newCredentialsFile != null) newCredentialsFile,
     _legacyCredentialsFile(cache)
-  ].firstWhere(fileExists, orElse: () => newCredentialsFile);
+  ].firstWhereOrNull(fileExists);
+  return file ?? newCredentialsFile;
 }
 
 String _legacyCredentialsFile(SystemCache cache) {
@@ -230,7 +234,7 @@
     }
 
     log.message('Authorization received, processing...');
-    var queryString = request.url.query ?? '';
+    var queryString = request.url.query;
     // Closing the server here is safe, since it will wait until the response
     // is sent to actually shut down.
     server.close();
diff --git a/lib/src/package.dart b/lib/src/package.dart
index 1aeb9dc..0bc6f89 100644
--- a/lib/src/package.dart
+++ b/lib/src/package.dart
@@ -2,10 +2,9 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:io';
 
+import 'package:collection/collection.dart' show IterableExtension;
 import 'package:path/path.dart' as p;
 import 'package:pub_semver/pub_semver.dart';
 
@@ -36,19 +35,26 @@
     return a.version.compareTo(b.version);
   }
 
+  final String? _dir;
+
   /// The path to the directory containing the package.
-  final String dir;
+  ///
+  /// It is an error to access this on an in-memory package.
+  String get dir {
+    if (isInMemory) {
+      throw UnsupportedError(
+          'Package directory cannot be used for an in-memory package');
+    }
+
+    return _dir!;
+  }
 
   /// An in-memory package can be created for doing a resolution without having
   /// a package on disk. Paths should not be resolved for these.
-  bool get _isInMemory => dir == null;
+  bool get isInMemory => _dir == null;
 
   /// The name of the package.
-  String get name {
-    if (pubspec.name != null) return pubspec.name;
-    if (dir != null) return p.basename(dir);
-    return null;
-  }
+  String get name => pubspec.name;
 
   /// The package's version.
   Version get version => pubspec.version;
@@ -95,7 +101,7 @@
   /// If multiple READMEs are found, this uses the same conventions as
   /// pub.dartlang.org for choosing the primary one: the README with the fewest
   /// extensions that is lexically ordered first is chosen.
-  String get readmePath {
+  String? get readmePath {
     var readmes = listFiles(recursive: false)
         .map(p.basename)
         .where((entry) => entry.contains(_readmeRegexp));
@@ -112,61 +118,57 @@
 
   /// Returns the path to the CHANGELOG file at the root of the entrypoint, or
   /// null if no CHANGELOG file is found.
-  String get changelogPath {
-    return listFiles(recursive: false).firstWhere(
-        (entry) => p.basename(entry).contains(_changelogRegexp),
-        orElse: () => null);
+  String? get changelogPath {
+    return listFiles(recursive: false).firstWhereOrNull(
+        (entry) => p.basename(entry).contains(_changelogRegexp));
   }
 
   /// Returns whether or not this package is in a Git repo.
-  bool get inGitRepo {
-    if (_inGitRepoCache != null) return _inGitRepoCache;
+  late final bool inGitRepo = computeInGitRepoCache();
 
-    if (dir == null || !git.isInstalled) {
-      _inGitRepoCache = false;
+  bool computeInGitRepoCache() {
+    if (isInMemory || !git.isInstalled) {
+      return false;
     } else {
       // If the entire package directory is ignored, don't consider it part of a
       // git repo. `git check-ignore` will return a status code of 0 for
       // ignored, 1 for not ignored, and 128 for not a Git repo.
-      var result = runProcessSync(git.command, ['check-ignore', '--quiet', '.'],
+      var result = runProcessSync(
+          git.command!, ['check-ignore', '--quiet', '.'],
           workingDir: dir);
-      _inGitRepoCache = result.exitCode == 1;
+      return result.exitCode == 1;
     }
-
-    return _inGitRepoCache;
   }
 
-  bool _inGitRepoCache;
-
   /// Loads the package whose root directory is [packageDir].
   ///
   /// [name] is the expected name of that package (e.g. the name given in the
   /// dependency), or `null` if the package being loaded is the entrypoint
   /// package.
-  Package.load(String name, this.dir, SourceRegistry sources)
-      : pubspec = Pubspec.load(dir, sources, expectedName: name);
+  Package.load(String? name, String this._dir, SourceRegistry sources)
+      : pubspec = Pubspec.load(_dir, sources, expectedName: name);
 
   /// Constructs a package with the given pubspec.
   ///
   /// The package will have no directory associated with it.
-  Package.inMemory(this.pubspec) : dir = null;
+  Package.inMemory(this.pubspec) : _dir = null;
 
   /// Creates a package with [pubspec] located at [dir].
-  Package(this.pubspec, this.dir);
+  Package(this.pubspec, String this._dir);
 
   /// Given a relative path within this package, returns its absolute path.
   ///
   /// This is similar to `p.join(dir, part1, ...)`, except that subclasses may
   /// override it to report that certain paths exist elsewhere than within
   /// [dir].
-  String path(String part1,
-      [String part2,
-      String part3,
-      String part4,
-      String part5,
-      String part6,
-      String part7]) {
-    if (_isInMemory) {
+  String path(String? part1,
+      [String? part2,
+      String? part3,
+      String? part4,
+      String? part5,
+      String? part6,
+      String? part7]) {
+    if (isInMemory) {
       throw StateError("Package $name is in-memory and doesn't have paths "
           'on disk.');
     }
@@ -176,7 +178,7 @@
   /// Given an absolute path within this package (such as that returned by
   /// [path] or [listFiles]), returns it relative to the package root.
   String relative(String path) {
-    if (dir == null) {
+    if (isInMemory) {
       throw StateError("Package $name is in-memory and doesn't have paths "
           'on disk.');
     }
@@ -184,7 +186,7 @@
   }
 
   /// Returns the type of dependency from this package onto [name].
-  DependencyType dependencyType(String name) {
+  DependencyType dependencyType(String? name) {
     if (pubspec.fields['dependencies']?.containsKey(name) ?? false) {
       return DependencyType.direct;
     } else if (pubspec.fields['dev_dependencies']?.containsKey(name) ?? false) {
@@ -220,13 +222,15 @@
   ///
   /// Note that the returned paths won't always be beneath [dir]. To safely
   /// convert them to paths relative to the package root, use [relative].
-  List<String> listFiles({String beneath, bool recursive = true}) {
+  List<String> listFiles({String? beneath, bool recursive = true}) {
     // An in-memory package has no files.
-    if (dir == null) return [];
+    if (isInMemory) return [];
 
-    var root = git.repoRoot(dir) ?? dir;
+    var packageDir = dir;
+    var root = git.repoRoot(packageDir) ?? packageDir;
     beneath = p
-        .toUri(p.normalize(p.relative(p.join(dir, beneath ?? '.'), from: root)))
+        .toUri(p.normalize(
+            p.relative(p.join(packageDir, beneath ?? '.'), from: root)))
         .path;
     if (beneath == './') beneath = '.';
     String resolve(String path) {
diff --git a/lib/src/package_graph.dart b/lib/src/package_graph.dart
index ed1f30f..45351d7 100644
--- a/lib/src/package_graph.dart
+++ b/lib/src/package_graph.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'package:collection/collection.dart' hide mapMap;
 
 import 'entrypoint.dart';
@@ -32,7 +30,7 @@
   final Map<String, Package> packages;
 
   /// A map of transitive dependencies for each package.
-  Map<String, Set<Package>> _transitiveDependencies;
+  Map<String, Set<Package>>? _transitiveDependencies;
 
   PackageGraph(this.entrypoint, this.lockFile, this.packages);
 
@@ -47,7 +45,7 @@
         value: (id) {
           if (id.name == entrypoint.root.name) return entrypoint.root;
 
-          return Package(result.pubspecs[id.name],
+          return Package(result.pubspecs[id.name]!,
               entrypoint.cache.source(id.source).getDirectory(id));
         });
 
@@ -69,13 +67,12 @@
       _transitiveDependencies =
           mapMap<String, Set<String>, String, Set<Package>>(closure,
               value: (depender, names) {
-        var set = names.map((name) => packages[name]).toSet();
-        set.add(packages[depender]);
+        var set = names.map((name) => packages[name]!).toSet();
+        set.add(packages[depender]!);
         return set;
       });
     }
-
-    return _transitiveDependencies[package];
+    return _transitiveDependencies![package]!;
   }
 
   bool _isPackageCached(String package) {
@@ -86,7 +83,7 @@
     if (package == entrypoint.root.name) {
       return entrypoint.isCached;
     } else {
-      var id = lockFile.packages[package];
+      var id = lockFile.packages[package]!;
       return entrypoint.cache.source(id.source) is CachedSource;
     }
   }
diff --git a/lib/src/package_name.dart b/lib/src/package_name.dart
index 594bdfb..50f4bcc 100644
--- a/lib/src/package_name.dart
+++ b/lib/src/package_name.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'package:collection/collection.dart';
 import 'package:pub_semver/pub_semver.dart';
 
@@ -23,7 +21,7 @@
   /// The [Source] used to look up this package.
   ///
   /// If this is a root package, this will be `null`.
-  final Source source;
+  final Source? source;
 
   /// The metadata used by the package's [source] to identify and locate it.
   ///
@@ -51,25 +49,27 @@
   /// `this.toRef() == other.toRef()`.
   bool samePackage(PackageName other) {
     if (other.name != name) return false;
-    if (source == null) return other.source == null;
+    var thisSource = source;
+    if (thisSource == null) return other.source == null;
 
-    return other.source == source &&
-        source.descriptionsEqual(description, other.description);
+    return other.source == thisSource &&
+        thisSource.descriptionsEqual(description, other.description);
   }
 
   @override
   int get hashCode {
-    if (source == null) return name.hashCode;
+    var thisSource = source;
+    if (thisSource == null) return name.hashCode;
     return name.hashCode ^
-        source.hashCode ^
-        source.hashDescription(description);
+        thisSource.hashCode ^
+        thisSource.hashDescription(description);
   }
 
   /// Returns a string representation of this package name.
   ///
   /// If [detail] is passed, it controls exactly which details are included.
   @override
-  String toString([PackageDetail detail]);
+  String toString([PackageDetail? detail]);
 }
 
 /// A reference to a [Package], but not any particular version(s) of it.
@@ -80,14 +80,14 @@
   /// Since an ID's description is an implementation detail of its source, this
   /// should generally not be called outside of [Source] subclasses. A reference
   /// can be obtained from a user-supplied description using [Source.parseRef].
-  PackageRef(String name, Source source, description)
+  PackageRef(String name, Source? source, description)
       : super._(name, source, description);
 
   /// Creates a reference to the given root package.
   PackageRef.root(Package package) : super._(package.name, null, package.name);
 
   @override
-  String toString([PackageDetail detail]) {
+  String toString([PackageDetail? detail]) {
     detail ??= PackageDetail.defaults;
     if (isRoot) return name;
 
@@ -95,7 +95,7 @@
     if (detail.showSource ?? source is! HostedSource) {
       buffer.write(' from $source');
       if (detail.showDescription) {
-        buffer.write(' ${source.formatDescription(description)}');
+        buffer.write(' ${source!.formatDescription(description)}');
       }
     }
 
@@ -128,7 +128,7 @@
   ///
   /// Since an ID's description is an implementation detail of its source, this
   /// should generally not be called outside of [Source] subclasses.
-  PackageId(String name, Source source, this.version, description)
+  PackageId(String name, Source? source, this.version, description)
       : super._(name, source, description);
 
   /// Creates an ID for the given root package.
@@ -147,7 +147,7 @@
   PackageRange toRange() => withConstraint(version);
 
   @override
-  String toString([PackageDetail detail]) {
+  String toString([PackageDetail? detail]) {
     detail ??= PackageDetail.defaults;
 
     var buffer = StringBuffer(name);
@@ -156,7 +156,7 @@
     if (!isRoot && (detail.showSource ?? source is! HostedSource)) {
       buffer.write(' from $source');
       if (detail.showDescription) {
-        buffer.write(' ${source.formatDescription(description)}');
+        buffer.write(' ${source!.formatDescription(description)}');
       }
     }
 
@@ -177,8 +177,8 @@
   ///
   /// Since an ID's description is an implementation detail of its source, this
   /// should generally not be called outside of [Source] subclasses.
-  PackageRange(String name, Source source, this.constraint, description,
-      {Map<String, FeatureDependency> features})
+  PackageRange(String name, Source? source, this.constraint, description,
+      {Map<String, FeatureDependency>? features})
       : features = features == null
             ? const {}
             : UnmodifiableMapView(Map.from(features)),
@@ -218,7 +218,7 @@
   }
 
   @override
-  String toString([PackageDetail detail]) {
+  String toString([PackageDetail? detail]) {
     detail ??= PackageDetail.defaults;
 
     var buffer = StringBuffer(name);
@@ -229,7 +229,7 @@
     if (!isRoot && (detail.showSource ?? source is! HostedSource)) {
       buffer.write(' from $source');
       if (detail.showDescription) {
-        buffer.write(' ${source.formatDescription(description)}');
+        buffer.write(' ${source!.formatDescription(description)}');
       }
     }
 
@@ -244,7 +244,7 @@
   bool get _showVersionConstraint {
     if (isRoot) return false;
     if (!constraint.isAny) return true;
-    return source.hasMultipleVersions;
+    return source!.hasMultipleVersions;
   }
 
   /// Returns a new [PackageRange] with [features] merged with [this.features].
@@ -263,9 +263,10 @@
     var range = constraint as VersionRange;
     if (!range.includeMin) return this;
     if (range.includeMax) return this;
-    if (range.min == null) return this;
-    if (range.max == range.min.nextBreaking.firstPreRelease) {
-      return withConstraint(VersionConstraint.compatibleWith(range.min));
+    var min = range.min;
+    if (min == null) return this;
+    if (range.max == min.nextBreaking.firstPreRelease) {
+      return withConstraint(VersionConstraint.compatibleWith(min));
     } else {
       return this;
     }
@@ -324,13 +325,13 @@
   /// If this is `null`, the version is shown for all packages other than root
   /// [PackageId]s or [PackageRange]s with `git` or `path` sources and `any`
   /// constraints.
-  final bool showVersion;
+  final bool? showVersion;
 
   /// Whether to show the package source.
   ///
   /// If this is `null`, the source is shown for all non-hosted, non-root
   /// packages. It's always `true` if [showDescription] is `true`.
-  final bool showSource;
+  final bool? showSource;
 
   /// Whether to show the package description.
   ///
@@ -344,9 +345,9 @@
 
   const PackageDetail(
       {this.showVersion,
-      bool showSource,
-      bool showDescription,
-      bool showFeatures})
+      bool? showSource,
+      bool? showDescription,
+      bool? showFeatures})
       : showSource = showDescription == true ? true : showSource,
         showDescription = showDescription ?? false,
         showFeatures = showFeatures ?? true;
@@ -354,8 +355,8 @@
   /// Returns a [PackageDetail] with the maximum amount of detail between [this]
   /// and [other].
   PackageDetail max(PackageDetail other) => PackageDetail(
-      showVersion: showVersion || other.showVersion,
-      showSource: showSource || other.showSource,
+      showVersion: showVersion! || other.showVersion!,
+      showSource: showSource! || other.showSource!,
       showDescription: showDescription || other.showDescription,
       showFeatures: showFeatures || other.showFeatures);
 }
diff --git a/lib/src/pub_embeddable_command.dart b/lib/src/pub_embeddable_command.dart
index aebe06c..0403d17 100644
--- a/lib/src/pub_embeddable_command.dart
+++ b/lib/src/pub_embeddable_command.dart
@@ -2,7 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
 import 'package:meta/meta.dart';
 import 'package:usage/usage.dart';
 
@@ -34,7 +33,7 @@
   final String dependencyKindCustomDimensionName;
   final Analytics analytics;
   PubAnalytics(this.analytics,
-      {@required this.dependencyKindCustomDimensionName});
+      {required this.dependencyKindCustomDimensionName});
 }
 
 /// Exposes the `pub` commands as a command to be embedded in another command
@@ -48,10 +47,10 @@
   String get docUrl => 'https://dart.dev/tools/pub/cmd/pub-global';
 
   @override
-  final PubAnalytics analytics;
+  String get directory => argResults['directory'];
 
   @override
-  String get directory => argResults['directory'];
+  final PubAnalytics? analytics;
 
   PubEmbeddableCommand(this.analytics) : super() {
     argParser.addFlag('trace',
diff --git a/lib/src/pubspec.dart b/lib/src/pubspec.dart
index b825cb0..b700638 100644
--- a/lib/src/pubspec.dart
+++ b/lib/src/pubspec.dart
@@ -2,12 +2,9 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:io';
 
 import 'package:collection/collection.dart' hide mapMap;
-import 'package:meta/meta.dart';
 import 'package:path/path.dart' as path;
 import 'package:pub_semver/pub_semver.dart';
 import 'package:source_span/source_span.dart';
@@ -30,7 +27,7 @@
 ///
 /// This provides a sane default for packages that don't have an upper bound.
 final VersionRange _defaultUpperBoundSdkConstraint =
-    VersionConstraint.parse('<2.0.0');
+    VersionConstraint.parse('<2.0.0') as VersionRange;
 
 /// Whether or not to allow the pre-release SDK for packages that have an
 /// upper bound Dart SDK constraint of <2.0.0.
@@ -77,75 +74,64 @@
   ///
   /// This will be null if this was created using [new Pubspec] or [new
   /// Pubspec.empty].
-  final SourceRegistry _sources;
+  final SourceRegistry? _sources;
 
   /// The location from which the pubspec was loaded.
   ///
   /// This can be null if the pubspec was created in-memory or if its location
   /// is unknown.
-  Uri get _location => fields.span.sourceUrl;
+  Uri? get _location => fields.span.sourceUrl;
 
   /// The additional packages this package depends on.
-  Map<String, PackageRange> get dependencies {
-    if (_dependencies != null) return _dependencies;
-    _dependencies =
-        _parseDependencies('dependencies', fields.nodes['dependencies']);
-    return _dependencies;
-  }
+  Map<String, PackageRange> get dependencies => _dependencies ??=
+      _parseDependencies('dependencies', fields.nodes['dependencies']);
 
-  Map<String, PackageRange> _dependencies;
+  Map<String, PackageRange>? _dependencies;
 
   /// The packages this package depends on when it is the root package.
-  Map<String, PackageRange> get devDependencies {
-    if (_devDependencies != null) return _devDependencies;
-    _devDependencies = _parseDependencies(
-        'dev_dependencies', fields.nodes['dev_dependencies']);
-    return _devDependencies;
-  }
+  Map<String, PackageRange> get devDependencies => _devDependencies ??=
+      _parseDependencies('dev_dependencies', fields.nodes['dev_dependencies']);
 
-  Map<String, PackageRange> _devDependencies;
+  Map<String, PackageRange>? _devDependencies;
 
   /// The dependency constraints that this package overrides when it is the
   /// root package.
   ///
   /// Dependencies here will replace any dependency on a package with the same
   /// name anywhere in the dependency graph.
-  Map<String, PackageRange> get dependencyOverrides {
-    if (_dependencyOverrides != null) return _dependencyOverrides;
-    _dependencyOverrides = _parseDependencies(
-        'dependency_overrides', fields.nodes['dependency_overrides']);
-    return _dependencyOverrides;
-  }
+  Map<String, PackageRange> get dependencyOverrides =>
+      _dependencyOverrides ??= _parseDependencies(
+          'dependency_overrides', fields.nodes['dependency_overrides']);
 
-  Map<String, PackageRange> _dependencyOverrides;
+  Map<String, PackageRange>? _dependencyOverrides;
 
-  Map<String, Feature> get features {
-    if (_features != null) return _features;
-    var features = fields['features'];
+  late final Map<String, Feature> features = _computeFeatures();
+
+  Map<String, Feature> _computeFeatures() {
+    final features = fields['features'];
     if (features == null) {
-      _features = const {};
-      return _features;
+      return const {};
     }
 
-    if (features is! Map) {
-      _error('"features" field must be a map.', fields.nodes['features'].span);
+    if (features is! YamlMap) {
+      _error('"features" field must be a map.', fields.nodes['features']!.span);
     }
 
-    _features = mapMap(features.nodes,
-        key: (nameNode, _) => _validateFeatureName(nameNode),
-        value: (nameNode, specNode) {
+    return mapMap(features.nodes,
+        key: (dynamic nameNode, dynamic _) => _validateFeatureName(nameNode),
+        value: (dynamic nameNode, dynamic specNode) {
           if (specNode.value == null) {
             return Feature(nameNode.value, const []);
           }
 
-          if (specNode is! Map) {
+          if (specNode is! YamlMap) {
             _error('A feature specification must be a map.', specNode.span);
           }
 
           var onByDefault = specNode['default'] ?? true;
           if (onByDefault is! bool) {
             _error('Default must be true or false.',
-                specNode.nodes['default'].span);
+                specNode.nodes['default']!.span);
           }
 
           var requires = _parseStringList(specNode.nodes['requires'],
@@ -163,18 +149,15 @@
               sdkConstraints: sdkConstraints,
               onByDefault: onByDefault);
         });
-    return _features;
   }
 
-  Map<String, Feature> _features;
-
   /// A map from SDK identifiers to constraints on those SDK versions.
   Map<String, VersionConstraint> get sdkConstraints {
     _ensureEnvironment();
-    return _sdkConstraints;
+    return _sdkConstraints!;
   }
 
-  Map<String, VersionConstraint> _sdkConstraints;
+  Map<String, VersionConstraint>? _sdkConstraints;
 
   /// Whether or not to apply the [_defaultUpperBoundsSdkConstraint] to this
   /// pubspec.
@@ -191,10 +174,10 @@
   /// `sdkConstraints["dart"]`.
   VersionConstraint get originalDartSdkConstraint {
     _ensureEnvironment();
-    return _originalDartSdkConstraint ?? sdkConstraints['dart'];
+    return _originalDartSdkConstraint ?? sdkConstraints['dart']!;
   }
 
-  VersionConstraint _originalDartSdkConstraint;
+  VersionConstraint? _originalDartSdkConstraint;
 
   /// Ensures that the top-level "environment" field has been parsed and
   /// [_sdkConstraints] is set accordingly.
@@ -234,17 +217,19 @@
     if (!_allowPreReleaseSdk) return false;
     if (!sdk.version.isPreRelease) return false;
     if (sdkConstraint.includeMax) return false;
-    if (sdkConstraint.min != null &&
-        sdkConstraint.min.isPreRelease &&
-        equalsIgnoringPreRelease(sdkConstraint.min, sdk.version)) {
+    var minSdkConstraint = sdkConstraint.min;
+    if (minSdkConstraint != null &&
+        minSdkConstraint.isPreRelease &&
+        equalsIgnoringPreRelease(sdkConstraint.min!, sdk.version)) {
       return false;
     }
-    if (sdkConstraint.max == null) return false;
-    if (sdkConstraint.max.isPreRelease &&
-        !sdkConstraint.max.isFirstPreRelease) {
+    var maxSdkConstraint = sdkConstraint.max;
+    if (maxSdkConstraint == null) return false;
+    if (maxSdkConstraint.max.isPreRelease &&
+        !maxSdkConstraint.isFirstPreRelease) {
       return false;
     }
-    return equalsIgnoringPreRelease(sdkConstraint.max, sdk.version);
+    return equalsIgnoringPreRelease(maxSdkConstraint, sdk.version);
   }
 
   /// Parses the "environment" field in [parent] and returns a map from SDK
@@ -259,9 +244,9 @@
       };
     }
 
-    if (yaml is! Map) {
+    if (yaml is! YamlMap) {
       _error('"environment" field must be a map.',
-          parent.nodes['environment'].span);
+          parent.nodes['environment']!.span);
     }
 
     var constraints = {
@@ -287,10 +272,6 @@
     return constraints;
   }
 
-  /// Whether or not the pubspec has no contents.
-  bool get isEmpty =>
-      name == null && version == Version.none && dependencies.isEmpty;
-
   /// The language version implied by the sdk constraint.
   LanguageVersion get languageVersion =>
       LanguageVersion.fromSdkConstraint(originalDartSdkConstraint);
@@ -300,7 +281,7 @@
   /// If [expectedName] is passed and the pubspec doesn't have a matching name
   /// field, this will throw a [PubspecException].
   factory Pubspec.load(String packageDir, SourceRegistry sources,
-      {String expectedName}) {
+      {String? expectedName}) {
     var pubspecPath = path.join(packageDir, 'pubspec.yaml');
     var pubspecUri = path.toUri(pubspecPath);
     if (!fileExists(pubspecPath)) {
@@ -317,13 +298,13 @@
   }
 
   Pubspec(String name,
-      {Version version,
-      Iterable<PackageRange> dependencies,
-      Iterable<PackageRange> devDependencies,
-      Iterable<PackageRange> dependencyOverrides,
-      Map fields,
-      SourceRegistry sources,
-      Map<String, VersionConstraint> sdkConstraints})
+      {Version? version,
+      Iterable<PackageRange>? dependencies,
+      Iterable<PackageRange>? devDependencies,
+      Iterable<PackageRange>? dependencyOverrides,
+      Map? fields,
+      SourceRegistry? sources,
+      Map<String, VersionConstraint>? sdkConstraints})
       : _dependencies = dependencies == null
             ? null
             : Map.fromIterable(dependencies, key: (range) => range.name),
@@ -342,7 +323,6 @@
           name: name,
           version: version,
         );
-
   Pubspec.empty()
       : _sources = null,
         _dependencies = {},
@@ -362,7 +342,7 @@
   ///
   /// [location] is the location from which this pubspec was loaded.
   Pubspec.fromMap(Map fields, this._sources,
-      {String expectedName, Uri location})
+      {String? expectedName, Uri? location})
       : _includeDefaultSdkConstraint = true,
         super(fields is YamlMap
             ? fields
@@ -375,7 +355,7 @@
     throw PubspecException(
         '"name" field doesn\'t match expected name '
         '"$expectedName".',
-        this.fields.nodes['name'].span);
+        this.fields.nodes['name']!.span);
   }
 
   /// Parses the pubspec stored at [filePath] whose text is [contents].
@@ -383,7 +363,7 @@
   /// If the pubspec doesn't define a version for itself, it defaults to
   /// [Version.none].
   factory Pubspec.parse(String contents, SourceRegistry sources,
-      {String expectedName, Uri location}) {
+      {String? expectedName, Uri? location}) {
     YamlNode pubspecNode;
     try {
       pubspecNode = loadYamlNode(contents, sourceUrl: location);
@@ -432,7 +412,7 @@
 
   /// Parses the dependency field named [field], and returns the corresponding
   /// map of dependency names to dependencies.
-  Map<String, PackageRange> _parseDependencies(String field, YamlNode node) {
+  Map<String, PackageRange> _parseDependencies(String field, YamlNode? node) {
     var dependencies = <String, PackageRange>{};
 
     // Allow an empty dependencies key.
@@ -442,31 +422,30 @@
       _error('"$field" field must be a map.', node.span);
     }
 
-    var map = node as YamlMap;
-    var nonStringNode = map.nodes.keys
+    var nonStringNode = node.nodes.keys
         .firstWhere((e) => e.value is! String, orElse: () => null);
     if (nonStringNode != null) {
       _error('A dependency name must be a string.', nonStringNode.span);
     }
 
-    map.nodes.forEach((nameNode, specNode) {
+    node.nodes.forEach((nameNode, specNode) {
       var name = nameNode.value;
       var spec = specNode.value;
       if (fields['name'] != null && name == this.name) {
         _error('A package may not list itself as a dependency.', nameNode.span);
       }
 
-      YamlNode descriptionNode;
-      String sourceName;
+      YamlNode? descriptionNode;
+      String? sourceName;
 
       VersionConstraint versionConstraint = VersionRange();
       var features = const <String, FeatureDependency>{};
       if (spec == null) {
         descriptionNode = nameNode;
-        sourceName = _sources.defaultSource.name;
+        sourceName = _sources!.defaultSource.name;
       } else if (spec is String) {
         descriptionNode = nameNode;
-        sourceName = _sources.defaultSource.name;
+        sourceName = _sources!.defaultSource.name;
         versionConstraint = _parseVersionConstraint(specNode);
       } else if (spec is Map) {
         // Don't write to the immutable YAML map.
@@ -506,12 +485,13 @@
 
       // Let the source validate the description.
       var ref = _wrapFormatException('description', descriptionNode?.span, () {
-        String pubspecPath;
-        if (_location != null && _isFileUri(_location)) {
+        String? pubspecPath;
+        var location = _location;
+        if (location != null && _isFileUri(location)) {
           pubspecPath = path.fromUri(_location);
         }
 
-        return _sources[sourceName].parseRef(name, descriptionNode?.value,
+        return _sources![sourceName]!.parseRef(name, descriptionNode?.value,
             containingPath: pubspecPath);
       }, targetPackage: name);
 
@@ -529,13 +509,13 @@
   /// bound and it is compatible with [defaultUpperBoundConstraint].
   ///
   /// If [ignoreUpperBound] the max constraint is ignored.
-  VersionConstraint _parseVersionConstraint(YamlNode node,
-      {VersionConstraint defaultUpperBoundConstraint,
+  VersionConstraint _parseVersionConstraint(YamlNode? node,
+      {VersionConstraint? defaultUpperBoundConstraint,
       bool ignoreUpperBound = false}) {
     if (node?.value == null) {
       return defaultUpperBoundConstraint ?? VersionConstraint.any;
     }
-    if (node.value is! String) {
+    if (node!.value is! String) {
       _error('A version constraint must be a string.', node.span);
     }
 
@@ -558,13 +538,13 @@
 
   /// Parses [node] to a map from feature names to whether those features are
   /// enabled.
-  Map<String, FeatureDependency> _parseDependencyFeatures(YamlNode node) {
+  Map<String, FeatureDependency> _parseDependencyFeatures(YamlNode? node) {
     if (node?.value == null) return const {};
-    if (node is! YamlMap) _error('Features must be a map.', node.span);
+    if (node is! YamlMap) _error('Features must be a map.', node!.span);
 
-    return mapMap((node as YamlMap).nodes,
-        key: (nameNode, _) => _validateFeatureName(nameNode),
-        value: (_, valueNode) {
+    return mapMap(node.nodes,
+        key: (dynamic nameNode, dynamic _) => _validateFeatureName(nameNode),
+        value: (dynamic _, dynamic valueNode) {
           var value = valueNode.value;
           if (value is bool) {
             return value
@@ -595,8 +575,8 @@
   /// Verifies that [node] is a list of strings and returns it.
   ///
   /// If [validate] is passed, it's called for each string in [node].
-  List<String> _parseStringList(YamlNode node,
-      {void Function(String value, SourceSpan) validate}) {
+  List<String> _parseStringList(YamlNode? node,
+      {void Function(String value, SourceSpan)? validate}) {
     var list = _parseList(node);
     for (var element in list.nodes) {
       var value = element.value;
@@ -610,7 +590,7 @@
   }
 
   /// Verifies that [node] is a list and returns it.
-  YamlList _parseList(YamlNode node) {
+  YamlList _parseList(YamlNode? node) {
     if (node == null || node.value == null) return YamlList();
     if (node is YamlList) return node;
     _error('Must be a list.', node.span);
@@ -626,8 +606,8 @@
   /// If [targetPackage] is provided, the value is used to describe the
   /// dependency that caused the problem.
   T _wrapFormatException<T>(
-      String description, SourceSpan span, T Function() fn,
-      {String targetPackage}) {
+      String description, SourceSpan? span, T Function() fn,
+      {String? targetPackage}) {
     try {
       return fn();
     } on FormatException catch (e) {
@@ -641,8 +621,7 @@
   }
 
   /// Throws a [PubspecException] with the given message.
-  @alwaysThrows
-  void _error(String message, SourceSpan span) {
+  Never _error(String message, SourceSpan? span) {
     throw PubspecException(message, span);
   }
 }
diff --git a/lib/src/pubspec_parse.dart b/lib/src/pubspec_parse.dart
index dccc438..68f9ce7 100644
--- a/lib/src/pubspec_parse.dart
+++ b/lib/src/pubspec_parse.dart
@@ -37,9 +37,9 @@
         _version = version;
 
   /// The package's name.
-  String get name {
-    if (_name != null) return _name!;
+  String get name => _name ??= _lookupName();
 
+  String _lookupName() {
     final name = fields['name'];
     if (name == null) {
       throw PubspecException('Missing the required "name" field.', fields.span);
@@ -54,8 +54,7 @@
           fields.nodes['name']?.span);
     }
 
-    _name = name;
-    return _name!;
+    return name;
   }
 
   String? _name;
diff --git a/lib/src/pubspec_utils.dart b/lib/src/pubspec_utils.dart
index 42c3870..251c8c9 100644
--- a/lib/src/pubspec_utils.dart
+++ b/lib/src/pubspec_utils.dart
@@ -2,7 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
+import 'dart:async';
 
 import 'package:meta/meta.dart';
 import 'package:pub_semver/pub_semver.dart';
@@ -43,7 +43,7 @@
 Future<Pubspec> constrainedToAtLeastNullSafetyPubspec(
     Pubspec original, SystemCache cache) async {
   /// Get the first version of [package] opting in to null-safety.
-  Future<VersionRange> constrainToFirstWithNullSafety(
+  Future<VersionConstraint> constrainToFirstWithNullSafety(
       PackageRange packageRange) async {
     final ref = packageRange.toRef();
     final available = await cache.source(ref.source).getVersions(ref);
@@ -66,7 +66,7 @@
     Map<String, PackageRange> constrained,
   ) async {
     final result = await Future.wait(constrained.keys.map((name) async {
-      final packageRange = constrained[name];
+      final packageRange = constrained[name]!;
       var unconstrainedRange = packageRange;
 
       /// We only need to remove the upper bound if it is a hosted package.
@@ -107,7 +107,7 @@
 /// not specified or empty, then all packages will have their upper bounds
 /// removed.
 Pubspec stripVersionUpperBounds(Pubspec original,
-    {Iterable<String> stripOnly}) {
+    {Iterable<String>? stripOnly}) {
   ArgumentError.checkNotNull(original, 'original');
   stripOnly ??= [];
 
@@ -117,12 +117,12 @@
     final result = <PackageRange>[];
 
     for (final name in constrained.keys) {
-      final packageRange = constrained[name];
+      final packageRange = constrained[name]!;
       var unconstrainedRange = packageRange;
 
       /// We only need to remove the upper bound if it is a hosted package.
       if (packageRange.source is HostedSource &&
-          (stripOnly.isEmpty || stripOnly.contains(packageRange.name))) {
+          (stripOnly!.isEmpty || stripOnly.contains(packageRange.name))) {
         unconstrainedRange = PackageRange(
             packageRange.name,
             packageRange.source,
diff --git a/lib/src/solver.dart b/lib/src/solver.dart
index 18d3c23..74dbfa8 100644
--- a/lib/src/solver.dart
+++ b/lib/src/solver.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'lock_file.dart';
@@ -33,8 +31,8 @@
   SolveType type,
   SystemCache cache,
   Package root, {
-  LockFile lockFile,
-  Iterable<String> unlock,
+  LockFile? lockFile,
+  Iterable<String> unlock = const [],
 }) {
   lockFile ??= LockFile.empty();
   return VersionSolver(
@@ -42,7 +40,7 @@
     cache,
     root,
     lockFile,
-    unlock ?? [],
+    unlock,
   ).solve();
 }
 
@@ -60,12 +58,12 @@
 /// If [unlock] is empty [SolveType.get] interprets this as lock everything,
 /// while [SolveType.upgrade] and [SolveType.downgrade] interprets an empty
 /// [unlock] as unlock everything.
-Future<SolveResult> tryResolveVersions(
+Future<SolveResult?> tryResolveVersions(
   SolveType type,
   SystemCache cache,
   Package root, {
-  LockFile lockFile,
-  Iterable<String> unlock,
+  LockFile? lockFile,
+  Iterable<String>? unlock,
 }) async {
   try {
     return await resolveVersions(
@@ -73,7 +71,7 @@
       cache,
       root,
       lockFile: lockFile,
-      unlock: unlock,
+      unlock: unlock ?? [],
     );
   } on SolveFailure {
     return null;
diff --git a/lib/src/solver/assignment.dart b/lib/src/solver/assignment.dart
index 645eafb..d4c7bdf 100644
--- a/lib/src/solver/assignment.dart
+++ b/lib/src/solver/assignment.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import '../package_name.dart';
 import 'incompatibility.dart';
 import 'term.dart';
@@ -19,7 +17,7 @@
 
   /// The incompatibility that caused this assignment to be derived, or `null`
   /// if the assignment isn't a derivation.
-  final Incompatibility cause;
+  final Incompatibility? cause;
 
   /// Whether this assignment is a decision, as opposed to a derivation.
   bool get isDecision => cause == null;
diff --git a/lib/src/solver/failure.dart b/lib/src/solver/failure.dart
index 1a3897b..ba27a3b 100644
--- a/lib/src/solver/failure.dart
+++ b/lib/src/solver/failure.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'package:collection/collection.dart';
 
 import '../exceptions.dart';
@@ -30,7 +28,7 @@
   ///
   /// If multiple [PackageNotFoundException]s caused the error, it's undefined
   /// which one is returned.
-  PackageNotFoundException get packageNotFound {
+  PackageNotFoundException? get packageNotFound {
     for (var incompatibility in incompatibility.externalIncompatibilities) {
       var cause = incompatibility.cause;
       if (cause is PackageNotFoundCause) return cause.exception;
@@ -70,7 +68,7 @@
   /// incompatibility, and why its terms are incompatible. The number is
   /// optional and indicates the explicit number that should be associated with
   /// the line so it can be referred to later on.
-  final _lines = <Pair<String, int>>[];
+  final _lines = <Pair<String, int?>>[];
 
   // A map from incompatibilities to the line numbers that were written for
   // those incompatibilities.
@@ -82,16 +80,14 @@
 
   /// Populates [_derivations] for [incompatibility] and its transitive causes.
   void _countDerivations(Incompatibility incompatibility) {
-    if (_derivations.containsKey(incompatibility)) {
-      _derivations[incompatibility]++;
-    } else {
-      _derivations[incompatibility] = 1;
+    _derivations.update(incompatibility, (value) => value + 1, ifAbsent: () {
       var cause = incompatibility.cause;
       if (cause is ConflictCause) {
         _countDerivations(cause.conflict);
         _countDerivations(cause.other);
       }
-    }
+      return 1;
+    });
   }
 
   String write() {
@@ -105,8 +101,11 @@
 
     for (var incompatibility in _root.externalIncompatibilities) {
       var cause = incompatibility.cause;
-      if (cause is PackageNotFoundCause && cause.sdk != null) {
-        sdkCauses.add(cause.sdk);
+      if (cause is PackageNotFoundCause) {
+        var sdk = cause.sdk;
+        if (sdk != null) {
+          sdkCauses.add(sdk);
+        }
       } else if (cause is SdkCause) {
         sdkCauses.add(cause.sdk);
         sdkConstraintCauses.add(cause.sdk);
@@ -203,23 +202,24 @@
       {bool conclusion = false}) {
     // Add explicit numbers for incompatibilities that are written far away
     // from their successors or that are used for multiple derivations.
-    var numbered = conclusion || _derivations[incompatibility] > 1;
+    var numbered = conclusion || _derivations[incompatibility]! > 1;
     var conjunction = conclusion || incompatibility == _root ? 'So,' : 'And';
     var incompatibilityString =
         log.bold(incompatibility.toString(detailsForIncompatibility));
 
-    var cause = incompatibility.cause as ConflictCause;
-    var detailsForCause = _detailsForCause(cause);
-    if (cause.conflict.cause is ConflictCause &&
-        cause.other.cause is ConflictCause) {
-      var conflictLine = _lineNumbers[cause.conflict];
-      var otherLine = _lineNumbers[cause.other];
+    var conflictClause = incompatibility.cause as ConflictCause;
+    var detailsForCause = _detailsForCause(conflictClause);
+    var cause = conflictClause.conflict.cause;
+    var otherCause = conflictClause.other.cause;
+    if (cause is ConflictCause && otherCause is ConflictCause) {
+      var conflictLine = _lineNumbers[conflictClause.conflict];
+      var otherLine = _lineNumbers[conflictClause.other];
       if (conflictLine != null && otherLine != null) {
         _write(
             incompatibility,
             'Because ' +
-                cause.conflict.andToString(
-                    cause.other, detailsForCause, conflictLine, otherLine) +
+                conflictClause.conflict.andToString(conflictClause.other,
+                    detailsForCause, conflictLine, otherLine) +
                 ', $incompatibilityString.',
             numbered: numbered);
       } else if (conflictLine != null || otherLine != null) {
@@ -227,13 +227,13 @@
         Incompatibility withoutLine;
         int line;
         if (conflictLine != null) {
-          withLine = cause.conflict;
-          withoutLine = cause.other;
+          withLine = conflictClause.conflict;
+          withoutLine = conflictClause.other;
           line = conflictLine;
         } else {
-          withLine = cause.other;
-          withoutLine = cause.conflict;
-          line = otherLine;
+          withLine = conflictClause.other;
+          withoutLine = conflictClause.conflict;
+          line = otherLine!;
         }
 
         _visit(withoutLine, detailsForCause);
@@ -243,35 +243,38 @@
             '($line), $incompatibilityString.',
             numbered: numbered);
       } else {
-        var singleLineConflict = _isSingleLine(cause.conflict.cause);
-        var singleLineOther = _isSingleLine(cause.other.cause);
+        var singleLineConflict = _isSingleLine(cause);
+        var singleLineOther = _isSingleLine(otherCause);
         if (singleLineOther || singleLineConflict) {
-          var first = singleLineOther ? cause.conflict : cause.other;
-          var second = singleLineOther ? cause.other : cause.conflict;
+          var first =
+              singleLineOther ? conflictClause.conflict : conflictClause.other;
+          var second =
+              singleLineOther ? conflictClause.other : conflictClause.conflict;
           _visit(first, detailsForCause);
           _visit(second, detailsForCause);
           _write(incompatibility, 'Thus, $incompatibilityString.',
               numbered: numbered);
         } else {
-          _visit(cause.conflict, {}, conclusion: true);
+          _visit(conflictClause.conflict, {}, conclusion: true);
           _lines.add(Pair('', null));
 
-          _visit(cause.other, detailsForCause);
+          _visit(conflictClause.other, detailsForCause);
           _write(
               incompatibility,
               '$conjunction because '
-              '${cause.conflict.toString(detailsForCause)} '
-              '(${_lineNumbers[cause.conflict]}), '
+              '${conflictClause.conflict.toString(detailsForCause)} '
+              '(${_lineNumbers[conflictClause.conflict]}), '
               '$incompatibilityString.',
               numbered: numbered);
         }
       }
-    } else if (cause.conflict.cause is ConflictCause ||
-        cause.other.cause is ConflictCause) {
-      var derived =
-          cause.conflict.cause is ConflictCause ? cause.conflict : cause.other;
-      var ext =
-          cause.conflict.cause is ConflictCause ? cause.other : cause.conflict;
+    } else if (cause is ConflictCause || otherCause is ConflictCause) {
+      var derived = cause is ConflictCause
+          ? conflictClause.conflict
+          : conflictClause.other;
+      var ext = cause is ConflictCause
+          ? conflictClause.other
+          : conflictClause.conflict;
 
       var derivedLine = _lineNumbers[derived];
       if (derivedLine != null) {
@@ -313,7 +316,7 @@
       _write(
           incompatibility,
           'Because '
-          '${cause.conflict.andToString(cause.other, detailsForCause)}, '
+          '${conflictClause.conflict.andToString(conflictClause.other, detailsForCause)}, '
           '$incompatibilityString.',
           numbered: numbered);
     }
@@ -347,7 +350,7 @@
   bool _isCollapsible(Incompatibility incompatibility) {
     // If [incompatibility] is used for multiple derivations, it will need a
     // line number and so will need to be written explicitly.
-    if (_derivations[incompatibility] > 1) return false;
+    if (_derivations[incompatibility]! > 1) return false;
 
     var cause = incompatibility.cause as ConflictCause;
     // If [incompatibility] is derived from two derived incompatibilities,
diff --git a/lib/src/solver/incompatibility.dart b/lib/src/solver/incompatibility.dart
index 1dd6d96..9e39865 100644
--- a/lib/src/solver/incompatibility.dart
+++ b/lib/src/solver/incompatibility.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'package:pub_semver/pub_semver.dart';
 
 import '../package_name.dart';
@@ -67,14 +65,12 @@
       var byRef = byName.putIfAbsent(term.package.name, () => {});
       var ref = term.package.toRef();
       if (byRef.containsKey(ref)) {
-        byRef[ref] = byRef[ref].intersect(term);
-
         // If we have two terms that refer to the same package but have a null
         // intersection, they're mutually exclusive, making this incompatibility
         // irrelevant, since we already know that mutually exclusive version
         // ranges are incompatible. We should never derive an irrelevant
         // incompatibility.
-        assert(byRef[ref] != null);
+        byRef[ref] = byRef[ref]!.intersect(term)!;
       } else {
         byRef[ref] = term;
       }
@@ -100,7 +96,7 @@
   /// If [details] is passed, it controls the amount of detail that's written
   /// for packages with the given names.
   @override
-  String toString([Map<String, PackageDetail> details]) {
+  String toString([Map<String, PackageDetail>? details]) {
     if (cause == IncompatibilityCause.dependency) {
       assert(terms.length == 2);
 
@@ -223,7 +219,7 @@
   /// If [thisLine] and/or [otherLine] are passed, they indicate line numbers
   /// that should be associated with [this] and [other], respectively.
   String andToString(Incompatibility other,
-      [Map<String, PackageDetail> details, int thisLine, int otherLine]) {
+      [Map<String, PackageDetail>? details, int? thisLine, int? otherLine]) {
     var requiresBoth = _tryRequiresBoth(other, details, thisLine, otherLine);
     if (requiresBoth != null) return requiresBoth;
 
@@ -246,8 +242,8 @@
   /// and Y", this returns that expression.
   ///
   /// Otherwise, this returns `null`.
-  String _tryRequiresBoth(Incompatibility other,
-      [Map<String, PackageDetail> details, int thisLine, int otherLine]) {
+  String? _tryRequiresBoth(Incompatibility other,
+      [Map<String, PackageDetail>? details, int? thisLine, int? otherLine]) {
     if (terms.length == 1 || other.terms.length == 1) return null;
 
     var thisPositive = _singleTermWhere((term) => term.isPositive);
@@ -281,8 +277,8 @@
   /// Z", this returns that expression.
   ///
   /// Otherwise, this returns `null`.
-  String _tryRequiresThrough(Incompatibility other,
-      [Map<String, PackageDetail> details, int thisLine, int otherLine]) {
+  String? _tryRequiresThrough(Incompatibility other,
+      [Map<String, PackageDetail>? details, int? thisLine, int? otherLine]) {
     if (terms.length == 1 || other.terms.length == 1) return null;
 
     var thisNegative = _singleTermWhere((term) => !term.isPositive);
@@ -294,9 +290,9 @@
 
     Incompatibility prior;
     Term priorNegative;
-    int priorLine;
+    int? priorLine;
     Incompatibility latter;
-    int latterLine;
+    int? latterLine;
     if (thisNegative != null &&
         otherPositive != null &&
         thisNegative.package.name == otherPositive.package.name &&
@@ -358,14 +354,14 @@
   /// forbidden", this returns that expression.
   ///
   /// Otherwise, this returns `null`.
-  String _tryRequiresForbidden(Incompatibility other,
-      [Map<String, PackageDetail> details, int thisLine, int otherLine]) {
+  String? _tryRequiresForbidden(Incompatibility other,
+      [Map<String, PackageDetail>? details, int? thisLine, int? otherLine]) {
     if (terms.length != 1 && other.terms.length != 1) return null;
 
     Incompatibility prior;
     Incompatibility latter;
-    int priorLine;
-    int latterLine;
+    int? priorLine;
+    int? latterLine;
     if (terms.length == 1) {
       prior = other;
       latter = this;
@@ -439,8 +435,8 @@
   /// term.
   ///
   /// Otherwise, returns `null`.
-  Term _singleTermWhere(bool Function(Term) filter) {
-    Term found;
+  Term? _singleTermWhere(bool Function(Term) filter) {
+    Term? found;
     for (var term in terms) {
       if (!filter(term)) continue;
       if (found != null) return null;
@@ -450,7 +446,7 @@
   }
 
   /// Returns a terse representation of [term]'s package ref.
-  String _terseRef(Term term, Map<String, PackageDetail> details) =>
+  String _terseRef(Term term, Map<String, PackageDetail>? details) =>
       term.package
           .toRef()
           .toString(details == null ? null : details[term.package.name]);
@@ -459,12 +455,12 @@
   ///
   /// If [allowEvery] is `true`, this will return "every version of foo" instead
   /// of "foo any".
-  String _terse(Term term, Map<String, PackageDetail> details,
+  String _terse(Term? term, Map<String, PackageDetail>? details,
       {bool allowEvery = false}) {
-    if (allowEvery && term.constraint.isAny) {
+    if (allowEvery && term!.constraint.isAny) {
       return 'every version of ${_terseRef(term, details)}';
     } else {
-      return term.package
+      return term!.package
           .toString(details == null ? null : details[term.package.name]);
     }
   }
diff --git a/lib/src/solver/incompatibility_cause.dart b/lib/src/solver/incompatibility_cause.dart
index 45aa5ca..53dcfe2 100644
--- a/lib/src/solver/incompatibility_cause.dart
+++ b/lib/src/solver/incompatibility_cause.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'package:pub_semver/pub_semver.dart';
 
 import '../exceptions.dart';
@@ -59,7 +57,8 @@
 /// incompatible with the current SDK.
 class SdkCause implements IncompatibilityCause {
   /// The union of all the incompatible versions' constraints on the SDK.
-  final VersionConstraint constraint;
+  // TODO(zarah): Investigate if this can be non-nullable
+  final VersionConstraint? constraint;
 
   /// The SDK with which the package was incompatible.
   final Sdk sdk;
@@ -77,7 +76,7 @@
   /// that SDK.
   ///
   /// Otherwise `null`.
-  Sdk get sdk => exception.missingSdk;
+  Sdk? get sdk => exception.missingSdk;
 
   PackageNotFoundCause(this.exception);
 }
diff --git a/lib/src/solver/package_lister.dart b/lib/src/solver/package_lister.dart
index 136b0db..8e1abed 100644
--- a/lib/src/solver/package_lister.dart
+++ b/lib/src/solver/package_lister.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:async/async.dart';
@@ -34,7 +32,7 @@
   ///
   /// This is `null` if this package isn't locked or if the current version
   /// solve isn't a `pub get`.
-  final PackageId _locked;
+  final PackageId? _locked;
 
   // The version of this package that, if retracted, is still allowed in the
   // current version solve.
@@ -43,7 +41,7 @@
   // present in `pubspec.lock` or pinned in `dependency_overrides`.
   //
   // This is `null` if there is no retracted version that can be allowed.
-  final Version _allowedRetractedVersion;
+  final Version? _allowedRetractedVersion;
 
   /// The source from which [_ref] comes.
   final BoundSource _source;
@@ -78,25 +76,26 @@
 
   /// The versions of [_ref] that have been downloaded and cached, or `null` if
   /// they haven't been downloaded yet.
-  List<PackageId> get cachedVersions => _cachedVersions;
-  List<PackageId> _cachedVersions;
+  List<PackageId>? get cachedVersions => _cachedVersions;
+  List<PackageId>? _cachedVersions;
 
   /// All versions of the package, sorted by [Version.compareTo].
   Future<List<PackageId>> get _versions => _versionsMemo.runOnce(() async {
-        _cachedVersions = await withDependencyType(
+        var cachedVersions = (await withDependencyType(
             _dependencyType,
             () => _source.getVersions(_ref,
-                allowedRetractedVersion: _allowedRetractedVersion));
-        _cachedVersions.sort((id1, id2) => id1.version.compareTo(id2.version));
-        return _cachedVersions;
+                allowedRetractedVersion: _allowedRetractedVersion)))
+          ..sort((id1, id2) => id1.version.compareTo(id2.version));
+        _cachedVersions = cachedVersions;
+        return cachedVersions;
       });
   final _versionsMemo = AsyncMemoizer<List<PackageId>>();
 
   /// The most recent version of this package (or the oldest, if we're
   /// downgrading).
-  Future<PackageId> get latest =>
+  Future<PackageId?> get latest =>
       _latestMemo.runOnce(() => bestVersion(VersionConstraint.any));
-  final _latestMemo = AsyncMemoizer<PackageId>();
+  final _latestMemo = AsyncMemoizer<PackageId?>();
 
   /// Creates a package lister for the dependency identified by [ref].
   PackageLister(
@@ -125,7 +124,7 @@
 
   /// Returns the number of versions of this package that match [constraint].
   Future<int> countVersions(VersionConstraint constraint) async {
-    if (_locked != null && constraint.allows(_locked.version)) return 1;
+    if (_locked != null && constraint.allows(_locked!.version)) return 1;
     try {
       return (await _versions)
           .where((id) => constraint.allows(id.version))
@@ -144,32 +143,32 @@
   ///
   /// Throws a [PackageNotFoundException] if this lister's package doesn't
   /// exist.
-  Future<PackageId> bestVersion(VersionConstraint constraint) async {
-    if (_locked != null && constraint.allows(_locked.version)) return _locked;
+  Future<PackageId?> bestVersion(VersionConstraint? constraint) async {
+    if (_locked != null && constraint!.allows(_locked!.version)) return _locked;
 
     var versions = await _versions;
 
     // If [constraint] has a minimum (or a maximum in downgrade mode), we can
     // bail early once we're past it.
-    var isPastLimit = (Version _) => false;
+    var isPastLimit = (Version? _) => false;
     if (constraint is VersionRange) {
       if (_isDowngrade) {
         var max = constraint.max;
-        if (max != null) isPastLimit = (version) => version > max;
+        if (max != null) isPastLimit = (version) => version! > max;
       } else {
         var min = constraint.min;
-        if (min != null) isPastLimit = (version) => version < min;
+        if (min != null) isPastLimit = (version) => version! < min;
       }
     }
 
     // Return the most preferable version that matches [constraint]: the latest
     // non-prerelease version if one exists, or the latest prerelease version
     // otherwise.
-    PackageId bestPrerelease;
+    PackageId? bestPrerelease;
     for (var id in _isDowngrade ? versions : versions.reversed) {
-      if (isPastLimit != null && isPastLimit(id.version)) break;
+      if (isPastLimit(id.version)) break;
 
-      if (!constraint.allows(id.version)) continue;
+      if (!constraint!.allows(id.version)) continue;
       if (!id.version.isPreRelease) return id;
       bestPrerelease ??= id;
     }
@@ -210,7 +209,7 @@
 
     if (_cachedVersions == null &&
         _locked != null &&
-        id.version == _locked.version) {
+        id.version == _locked!.version) {
       if (_listedLockedVersion) return const [];
 
       var depender = id.toRange();
@@ -252,7 +251,8 @@
 
     var versions = await _versions;
     var index = lowerBound(versions, id,
-        compare: (id1, id2) => id1.version.compareTo(id2.version));
+        compare: (dynamic id1, dynamic id2) =>
+            id1.version.compareTo(id2.version));
     assert(index < versions.length);
     assert(versions[index].version == id.version);
 
@@ -289,7 +289,7 @@
           _alreadyListedDependencies[package] ?? VersionConstraint.empty);
 
       return _dependency(
-          _ref.withConstraint(constraint), dependencies[package]);
+          _ref.withConstraint(constraint), dependencies[package]!);
     }).toList();
   }
 
@@ -303,7 +303,7 @@
   /// version of [sdk], returns an [Incompatibility] indicating that.
   ///
   /// Otherwise, returns `null`.
-  Future<Incompatibility> _checkSdkConstraint(int index, Sdk sdk) async {
+  Future<Incompatibility?> _checkSdkConstraint(int index, Sdk sdk) async {
     var versions = await _versions;
 
     bool allowsSdk(Pubspec pubspec) => _matchesSdkConstraint(pubspec, sdk);
@@ -322,7 +322,7 @@
 
     var sdkConstraint = await foldAsync(
         slice(versions, bounds.first, bounds.last + 1), VersionConstraint.empty,
-        (previous, version) async {
+        (dynamic previous, dynamic version) async {
       var pubspec = await _describeSafe(version);
       return previous.union(
           pubspec.sdkConstraints[sdk.identifier] ?? VersionConstraint.any);
@@ -364,7 +364,7 @@
   /// If a package is absent from the return value, that indicates indicate that
   /// all versions above or below [index] (according to [upper]) have the same
   /// dependency.
-  Future<Map<String, Version>> _dependencyBounds(
+  Future<Map<String, Version?>> _dependencyBounds(
       Map<String, PackageRange> dependencies, int index,
       {bool upper = true}) async {
     var versions = await _versions;
@@ -427,7 +427,7 @@
     var constraint = pubspec.sdkConstraints[sdk.identifier];
     if (constraint == null) return true;
 
-    return sdk.isAvailable && constraint.allows(sdk.version);
+    return sdk.isAvailable && constraint.allows(sdk.version!);
   }
 }
 
@@ -447,7 +447,7 @@
 
   @override
   Future<List<PackageId>> getVersions(PackageRef ref,
-      {Duration maxAge, Version allowedRetractedVersion}) {
+      {Duration? maxAge, Version? allowedRetractedVersion}) {
     assert(ref.isRoot);
     return Future.value([PackageId.root(_package)]);
   }
@@ -463,11 +463,11 @@
   @override
   SystemCache get systemCache => throw _unsupported;
   @override
-  Future<List<PackageId>> doGetVersions(PackageRef ref, Duration maxAge) =>
+  Future<List<PackageId>> doGetVersions(PackageRef ref, Duration? maxAge) =>
       throw _unsupported;
   @override
   Future<Pubspec> doDescribe(PackageId id) => throw _unsupported;
   @override
-  String getDirectory(PackageId id, {String relativeFrom}) =>
+  String getDirectory(PackageId id, {String? relativeFrom}) =>
       throw _unsupported;
 }
diff --git a/lib/src/solver/partial_solution.dart b/lib/src/solver/partial_solution.dart
index 57c22bf..40d2611 100644
--- a/lib/src/solver/partial_solution.dart
+++ b/lib/src/solver/partial_solution.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import '../package_name.dart';
 import 'assignment.dart';
 import 'incompatibility.dart';
@@ -27,7 +25,7 @@
   /// negative [Assignment]s that refer to that package.
   ///
   /// This is derived from [_assignments].
-  final _positive = <String, Term>{};
+  final _positive = <String, Term?>{};
 
   /// The union of all negative [Assignment]s for each package.
   ///
@@ -43,8 +41,8 @@
   /// Returns all [PackageRange]s that have been assigned but are not yet
   /// satisfied.
   Iterable<PackageRange> get unsatisfied => _positive.values
-      .where((term) => !_decisions.containsKey(term.package.name))
-      .map((term) => term.package);
+      .where((term) => !_decisions.containsKey(term!.package.name))
+      .map((term) => term!.package);
 
   // The current decision level—that is, the length of [decisions].
   int get decisionLevel => _decisions.length;
@@ -70,7 +68,7 @@
   }
 
   /// Adds an assignment of [package] as a derivation.
-  void derive(PackageName package, bool isPositive, Incompatibility cause) {
+  void derive(PackageRange package, bool isPositive, Incompatibility cause) {
     _assign(Assignment.derivation(
         package, isPositive, cause, decisionLevel, _assignments.length));
   }
@@ -119,7 +117,7 @@
     var negativeByRef = _negative[name];
     var oldNegative = negativeByRef == null ? null : negativeByRef[ref];
     var term =
-        oldNegative == null ? assignment : assignment.intersect(oldNegative);
+        oldNegative == null ? assignment : assignment.intersect(oldNegative)!;
 
     if (term.isPositive) {
       _negative.remove(name);
@@ -134,7 +132,7 @@
   ///
   /// Throws a [StateError] if [term] isn't satisfied by [this].
   Assignment satisfier(Term term) {
-    Term assignedTerm;
+    Term? assignedTerm;
     for (var assignment in _assignments) {
       if (assignment.package.name != term.package.name) continue;
 
@@ -153,7 +151,7 @@
           : assignedTerm.intersect(assignment);
 
       // As soon as we have enough assignments to satisfy [term], return them.
-      if (assignedTerm.satisfies(term)) return assignment;
+      if (assignedTerm!.satisfies(term)) return assignment;
     }
 
     throw StateError('[BUG] $term is not satisfied.');
diff --git a/lib/src/solver/reformat_ranges.dart b/lib/src/solver/reformat_ranges.dart
index d40bd6f..5c763a6 100644
--- a/lib/src/solver/reformat_ranges.dart
+++ b/lib/src/solver/reformat_ranges.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'package:meta/meta.dart';
 import 'package:pub_semver/pub_semver.dart';
 
@@ -68,45 +66,46 @@
 
 /// Returns the new minimum version to use for [range], or `null` if it doesn't
 /// need to be reformatted.
-Version _reformatMin(List<PackageId> versions, VersionRange range) {
-  if (range.min == null) return null;
+Version? _reformatMin(List<PackageId> versions, VersionRange range) {
+  var min = range.min;
+  if (min == null) return null;
   if (!range.includeMin) return null;
-  if (!range.min.isFirstPreRelease) return null;
+  if (!min.isFirstPreRelease) return null;
 
-  var index = _lowerBound(versions, range.min);
+  var index = _lowerBound(versions, min);
   var next = index == versions.length ? null : versions[index].version;
 
   // If there's a real pre-release version of [range.min], use that as the min.
   // Otherwise, use the release version.
-  return next != null && equalsIgnoringPreRelease(range.min, next)
+  return next != null && equalsIgnoringPreRelease(min, next)
       ? next
-      : Version(range.min.major, range.min.minor, range.min.patch);
+      : Version(min.major, min.minor, min.patch);
 }
 
 /// Returns the new maximum version to use for [range] and whether that maximum
 /// is inclusive, or `null` if it doesn't need to be reformatted.
 @visibleForTesting
-Pair<Version, bool> reformatMax(List<PackageId> versions, VersionRange range) {
+Pair<Version, bool>? reformatMax(List<PackageId> versions, VersionRange range) {
   // This corresponds to the logic in the constructor of [VersionRange] with
   // `alwaysIncludeMaxPreRelease = false` for discovering when a max-bound
   // should not include prereleases.
 
-  if (range.max == null) return null;
+  var max = range.max;
+  var min = range.min;
+  if (max == null) return null;
   if (range.includeMax) return null;
-  if (range.max.isPreRelease) return null;
-  if (range.max.build.isNotEmpty) return null;
-  if (range.min != null &&
-      range.min.isPreRelease &&
-      equalsIgnoringPreRelease(range.min, range.max)) {
+  if (max.isPreRelease) return null;
+  if (max.build.isNotEmpty) return null;
+  if (min != null && min.isPreRelease && equalsIgnoringPreRelease(min, max)) {
     return null;
   }
 
-  var index = _lowerBound(versions, range.max);
+  var index = _lowerBound(versions, max);
   var previous = index == 0 ? null : versions[index - 1].version;
 
-  return previous != null && equalsIgnoringPreRelease(previous, range.max)
+  return previous != null && equalsIgnoringPreRelease(previous, max)
       ? Pair(previous, true)
-      : Pair(range.max.firstPreRelease, false);
+      : Pair(max.firstPreRelease, false);
 }
 
 /// Returns the first index in [ids] (which is sorted by version) whose version
diff --git a/lib/src/solver/report.dart b/lib/src/solver/report.dart
index c19d13b..04d6fa4 100644
--- a/lib/src/solver/report.dart
+++ b/lib/src/solver/report.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'package:path/path.dart' as path;
 import 'package:pub_semver/pub_semver.dart';
 
@@ -74,7 +72,7 @@
     }).length;
 
     var suffix = '';
-    if (_root.dir != null) {
+    if (!_root.isInMemory) {
       final dir = path.normalize(_root.dir);
       if (dir != '.') {
         suffix = ' in $dir';
@@ -168,7 +166,7 @@
   /// instruction to run `pub outdated` if outdated packages are detected.
   void reportOutdated() {
     final outdatedPackagesCount = _result.packages.where((id) {
-      final versions = _result.availableVersions[id.name];
+      final versions = _result.availableVersions[id.name]!;
       // A version is counted:
       // - if there is a newer version which is not a pre-release and current
       // version is also not a pre-release or,
@@ -199,7 +197,7 @@
       {bool alwaysShow = false, bool highlightOverride = true}) async {
     var newId = _dependencies[name];
     var oldId = _previousLockFile.packages[name];
-    var id = newId ?? oldId;
+    var id = newId ?? oldId!;
 
     var isOverridden = _root.dependencyOverrides.containsKey(id.name);
 
@@ -240,11 +238,11 @@
       // Unchanged.
       icon = '  ';
     }
-    String message;
+    String? message;
     // See if there are any newer versions of the package that we were
     // unable to upgrade to.
     if (newId != null && _type != SolveType.DOWNGRADE) {
-      var versions = _result.availableVersions[newId.name];
+      var versions = _result.availableVersions[newId.name]!;
 
       var newerStable = false;
       var newerUnstable = false;
@@ -301,7 +299,7 @@
     // If the package was upgraded, show what it was upgraded from.
     if (changed) {
       _output.write(' (was ');
-      _writeId(oldId);
+      _writeId(oldId!);
       _output.write(')');
     }
 
@@ -320,7 +318,7 @@
     _output.write(id.version);
 
     if (id.source != _sources.defaultSource) {
-      var description = id.source.formatDescription(id.description);
+      var description = id.source!.formatDescription(id.description);
       _output.write(' from ${id.source} $description');
     }
   }
diff --git a/lib/src/solver/result.dart b/lib/src/solver/result.dart
index c3c98d4..ee5cdc8 100644
--- a/lib/src/solver/result.dart
+++ b/lib/src/solver/result.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'package:collection/collection.dart';
 import 'package:pub_semver/pub_semver.dart';
 
@@ -84,8 +82,6 @@
   ///
   /// This includes packages that were added or removed.
   Set<String> get changedPackages {
-    if (packages == null) return null;
-
     var changed = packages
         .where((id) => _previousLockFile.packages[id.name] != id)
         .map((id) => id.name)
@@ -146,7 +142,7 @@
           DependencyType.dev: 'dev',
           DependencyType.direct: 'direct',
           DependencyType.none: 'transitive'
-        }[_root.dependencyType(package.name)];
+        }[_root.dependencyType(package.name)]!;
         analytics.analytics.sendEvent(
           'pub-get',
           package.name,
diff --git a/lib/src/solver/set_relation.dart b/lib/src/solver/set_relation.dart
index 09a1a80..2d88eb9 100644
--- a/lib/src/solver/set_relation.dart
+++ b/lib/src/solver/set_relation.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 /// An enum of possible relationships between two sets.
 class SetRelation {
   /// The second set contains all elements of the first, as well as possibly
diff --git a/lib/src/solver/term.dart b/lib/src/solver/term.dart
index f5f4662..266fabc 100644
--- a/lib/src/solver/term.dart
+++ b/lib/src/solver/term.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'package:pub_semver/pub_semver.dart';
 
 import '../package_name.dart';
@@ -60,7 +58,9 @@
         if (otherConstraint.allowsAll(constraint)) return SetRelation.subset;
 
         // foo ^2.0.0 is disjoint with foo ^1.0.0
-        if (!constraint.allowsAny(otherConstraint)) return SetRelation.disjoint;
+        if (!constraint.allowsAny(otherConstraint)) {
+          return SetRelation.disjoint;
+        }
 
         // foo >=1.5.0 <3.0.0 overlaps foo ^1.0.0
         return SetRelation.overlapping;
@@ -69,7 +69,9 @@
         if (!_compatiblePackage(other.package)) return SetRelation.overlapping;
 
         // not foo ^1.0.0 is disjoint with foo ^1.5.0
-        if (constraint.allowsAll(otherConstraint)) return SetRelation.disjoint;
+        if (constraint.allowsAll(otherConstraint)) {
+          return SetRelation.disjoint;
+        }
 
         // not foo ^1.5.0 overlaps foo ^1.0.0
         // not foo ^2.0.0 is a superset of foo ^1.5.0
@@ -110,7 +112,7 @@
   ///
   /// Throws an [ArgumentError] if [other] doesn't refer to a package with the
   /// same name as [package].
-  Term intersect(Term other) {
+  Term? intersect(Term other) {
     if (package.name != other.package.name) {
       throw ArgumentError.value(
           other, 'other', 'should refer to package ${package.name}');
@@ -148,7 +150,7 @@
   ///
   /// Throws an [ArgumentError] if [other] doesn't refer to a package with the
   /// same name as [package].
-  Term difference(Term other) => intersect(other.inverse); // A ∖ B → A ∩ not B
+  Term? difference(Term other) => intersect(other.inverse); // A ∖ B → A ∩ not B
 
   /// Returns whether [other] is compatible with [package].
   bool _compatiblePackage(PackageRange other) =>
@@ -157,7 +159,7 @@
   /// Returns a new [Term] with the same package as [this] and with
   /// [constraint], unless that would produce a term that allows no packages,
   /// in which case this returns `null`.
-  Term _nonEmptyTerm(VersionConstraint constraint, bool isPositive) =>
+  Term? _nonEmptyTerm(VersionConstraint constraint, bool isPositive) =>
       constraint.isEmpty
           ? null
           : Term(package.withConstraint(constraint), isPositive);
diff --git a/lib/src/solver/type.dart b/lib/src/solver/type.dart
index f0f37af..c899e31 100644
--- a/lib/src/solver/type.dart
+++ b/lib/src/solver/type.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 /// An enum for types of version resolution.
 class SolveType {
   /// As few changes to the lockfile as possible to be consistent with the
diff --git a/lib/src/solver/version_solver.dart b/lib/src/solver/version_solver.dart
index 4e08a6b..120244a 100644
--- a/lib/src/solver/version_solver.dart
+++ b/lib/src/solver/version_solver.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:math' as math;
 
@@ -94,7 +92,7 @@
 
     try {
       return await _systemCache.hosted.withPrefetching(() async {
-        var next = _root.name;
+        String? next = _root.name;
         while (next != null) {
           _propagate(next);
           next = await _choosePackageVersion();
@@ -124,7 +122,7 @@
       // general incompatibilities as time goes on. If we look at those first,
       // we can derive stronger assignments sooner and more eagerly find
       // conflicts.
-      for (var incompatibility in _incompatibilities[package].reversed) {
+      for (var incompatibility in _incompatibilities[package]!.reversed) {
         var result = _propagateIncompatibility(incompatibility);
         if (result == #conflict) {
           // If [incompatibility] is satisfied by [_solution], we use
@@ -160,7 +158,7 @@
     // The first entry in `incompatibility.terms` that's not yet satisfied by
     // [_solution], if one exists. If we find more than one, [_solution] is
     // inconclusive for [incompatibility] and we can't deduce anything.
-    Term unsatisfied;
+    Term? unsatisfied;
 
     for (var i = 0; i < incompatibility.terms.length; i++) {
       var term = incompatibility.terms[i];
@@ -209,17 +207,17 @@
     while (!incompatibility.isFailure) {
       // The term in `incompatibility.terms` that was most recently satisfied by
       // [_solution].
-      Term mostRecentTerm;
+      Term? mostRecentTerm;
 
       // The earliest assignment in [_solution] such that [incompatibility] is
       // satisfied by [_solution] up to and including this assignment.
-      Assignment mostRecentSatisfier;
+      Assignment? mostRecentSatisfier;
 
       // The difference between [mostRecentSatisfier] and [mostRecentTerm];
       // that is, the versions that are allowed by [mostRecentSatisfier] and not
       // by [mostRecentTerm]. This is `null` if [mostRecentSatisfier] totally
       // satisfies [mostRecentTerm].
-      Term difference;
+      Term? difference;
 
       // The decision level of the earliest assignment in [_solution] *before*
       // [mostRecentSatisfier] such that [incompatibility] is satisfied by
@@ -252,7 +250,7 @@
           // If [mostRecentSatisfier] doesn't satisfy [mostRecentTerm] on its
           // own, then the next-most-recent satisfier may be the one that
           // satisfies the remainder.
-          difference = mostRecentSatisfier.difference(mostRecentTerm);
+          difference = mostRecentSatisfier.difference(mostRecentTerm!);
           if (difference != null) {
             previousSatisfierLevel = math.max(previousSatisfierLevel,
                 _solution.satisfier(difference.inverse).decisionLevel);
@@ -265,7 +263,7 @@
       // than a derivation), then [incompatibility] is the root cause. We then
       // backjump to [previousSatisfierLevel], where [incompatibility] is
       // guaranteed to allow [_propagate] to produce more assignments.
-      if (previousSatisfierLevel < mostRecentSatisfier.decisionLevel ||
+      if (previousSatisfierLevel < mostRecentSatisfier!.decisionLevel ||
           mostRecentSatisfier.cause == null) {
         _solution.backtrack(previousSatisfierLevel);
         if (newIncompatibility) _addIncompatibility(incompatibility);
@@ -281,7 +279,7 @@
       var newTerms = <Term>[
         for (var term in incompatibility.terms)
           if (term != mostRecentTerm) term,
-        for (var term in mostRecentSatisfier.cause.terms)
+        for (var term in mostRecentSatisfier.cause!.terms)
           if (term.package != mostRecentSatisfier.package) term,
       ];
 
@@ -300,7 +298,7 @@
       if (difference != null) newTerms.add(difference.inverse);
 
       incompatibility = Incompatibility(
-          newTerms, ConflictCause(incompatibility, mostRecentSatisfier.cause));
+          newTerms, ConflictCause(incompatibility, mostRecentSatisfier.cause!));
       newIncompatibility = true;
 
       var partially = difference == null ? '' : ' partially';
@@ -319,7 +317,7 @@
   /// Returns the name of the package whose incompatibilities should be
   /// propagated by [_propagate], or `null` indicating that version solving is
   /// complete and a solution has been found.
-  Future<String> _choosePackageVersion() async {
+  Future<String?> _choosePackageVersion() async {
     var unsatisfied = _solution.unsatisfied.toList();
     if (unsatisfied.isEmpty) return null;
 
@@ -335,14 +333,14 @@
 
     /// Prefer packages with as few remaining versions as possible, so that if a
     /// conflict is necessary it's forced quickly.
-    var package = await minByAsync(unsatisfied, (package) async {
+    var package = await minByAsync(unsatisfied, (PackageRange package) async {
       return await _packageLister(package).countVersions(package.constraint);
     });
     if (package == null) {
       return null; // when unsatisfied.isEmpty
     }
 
-    PackageId version;
+    PackageId? version;
     try {
       version = await _packageLister(package).bestVersion(package.constraint);
     } on PackageNotFoundException catch (error) {
@@ -371,7 +369,7 @@
 
     var conflict = false;
     for (var incompatibility
-        in await _packageLister(package).incompatibilitiesFor(version)) {
+        in await _packageLister(package).incompatibilitiesFor(version!)) {
       _addIncompatibility(incompatibility);
 
       // If an incompatibility is already satisfied, then selecting [version]
@@ -494,7 +492,7 @@
   /// Gets the version of [package] currently locked in the lock file.
   ///
   /// Returns `null` if it isn't in the lockfile (or has been unlocked).
-  PackageId _getLocked(String package) {
+  PackageId? _getLocked(String? package) {
     if (_type == SolveType.GET) {
       if (_unlock.contains(package)) {
         return null;
@@ -507,7 +505,7 @@
     // can't be downgraded.
     if (_type == SolveType.DOWNGRADE) {
       var locked = _lockFile.packages[package];
-      if (locked != null && !locked.source.hasMultipleVersions) return locked;
+      if (locked != null && !locked.source!.hasMultipleVersions) return locked;
     }
 
     if (_unlock.isEmpty || _unlock.contains(package)) return null;
@@ -515,16 +513,17 @@
   }
 
   /// Gets the version of [package] which can be allowed during version solving
-  /// even if that version is marked as retracted.
+  /// even if that version is marked as retracted. Returns `null` if no such
+  /// version exists.
   ///
   /// We only allow resolving to a retracted version if it is already in the
   /// `pubspec.lock` or pinned in `dependency_overrides`.
-  Version _getAllowedRetracted(String package) {
+  Version? _getAllowedRetracted(String? package) {
     if (_dependencyOverrides.containsKey(package)) {
-      var range = _dependencyOverrides[package];
+      var range = _dependencyOverrides[package]!;
       if (range.constraint is Version) {
         // We have a pinned dependency.
-        return range.constraint;
+        return range.constraint as Version?;
       }
     }
     return _lockFile.packages[package]?.version;
@@ -533,7 +532,7 @@
   /// Logs [message] in the context of the current selected packages.
   ///
   /// If [message] is omitted, just logs a description of leaf-most selection.
-  void _log([String message]) {
+  void _log([String message = '']) {
     // Indent for the previous selections.
     log.solver(prefixLines(message, prefix: '  ' * _solution.decisionLevel));
   }
diff --git a/lib/src/source.dart b/lib/src/source.dart
index 4fe2322..53c694d 100644
--- a/lib/src/source.dart
+++ b/lib/src/source.dart
@@ -2,10 +2,9 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 
+import 'package:collection/collection.dart' show IterableNullableExtension;
 import 'package:pub_semver/pub_semver.dart';
 
 import 'exceptions.dart';
@@ -86,7 +85,7 @@
   /// the original user-provided description.
   ///
   /// Throws a [FormatException] if the description is not valid.
-  PackageRef parseRef(String name, description, {String containingPath});
+  PackageRef parseRef(String name, description, {String? containingPath});
 
   /// Parses a [PackageId] from a name and a serialized description.
   ///
@@ -99,7 +98,7 @@
   ///
   /// Throws a [FormatException] if the description is not valid.
   PackageId parseId(String name, Version version, description,
-      {String containingPath});
+      {String? containingPath});
 
   /// When a [LockFile] is serialized, it uses this method to get the
   /// [description] in the right format.
@@ -166,7 +165,7 @@
   /// selected even if it is marked as retracted. Otherwise, all the returned
   /// IDs correspond to non-retracted versions.
   Future<List<PackageId>> getVersions(PackageRef ref,
-      {Duration maxAge, Version allowedRetractedVersion}) async {
+      {Duration? maxAge, Version? allowedRetractedVersion}) async {
     if (ref.isRoot) {
       throw ArgumentError('Cannot get versions for the root package.');
     }
@@ -183,7 +182,7 @@
       }
       return null;
     })))
-        .where((element) => element != null)
+        .whereNotNull()
         .toList();
 
     return versions;
@@ -201,7 +200,7 @@
   ///
   /// This method is effectively protected: subclasses must implement it, but
   /// external code should not call this. Instead, call [getVersions].
-  Future<List<PackageId>> doGetVersions(PackageRef ref, Duration maxAge);
+  Future<List<PackageId>> doGetVersions(PackageRef ref, Duration? maxAge);
 
   /// A cache of pubspecs described by [describe].
   final _pubspecs = <PackageId, Pubspec>{};
@@ -256,7 +255,7 @@
   ///
   /// If id is a relative path id, the directory will be relative from
   /// [relativeFrom]. Returns an absolute path if [relativeFrom] is not passed.
-  String getDirectory(PackageId id, {String relativeFrom});
+  String getDirectory(PackageId id, {String? relativeFrom});
 
   /// Returns metadata about a given package.
   ///
@@ -265,7 +264,7 @@
   ///
   /// In the case of offline sources, [maxAge] is not used, since information is
   /// per definiton cached.
-  Future<PackageStatus> status(PackageId id, {Duration maxAge}) async =>
+  Future<PackageStatus> status(PackageId id, {Duration? maxAge}) async =>
       // Default implementation has no metadata.
       PackageStatus();
 
@@ -282,10 +281,11 @@
   /// `null` if not [isDiscontinued]. Otherwise contains the
   /// replacement string provided by the host or `null` if there is no
   /// replacement.
-  final String discontinuedReplacedBy;
+  final String? discontinuedReplacedBy;
   final bool isDiscontinued;
   final bool isRetracted;
-  PackageStatus({isDiscontinued, this.discontinuedReplacedBy, isRetracted})
-      : isDiscontinued = isDiscontinued ?? false,
-        isRetracted = isRetracted ?? false;
+  PackageStatus(
+      {this.isDiscontinued = false,
+      this.discontinuedReplacedBy,
+      this.isRetracted = false});
 }
diff --git a/lib/src/source/cached.dart b/lib/src/source/cached.dart
index c0d9ef1..0c8574d 100644
--- a/lib/src/source/cached.dart
+++ b/lib/src/source/cached.dart
@@ -2,11 +2,8 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 
-import 'package:meta/meta.dart';
 import 'package:path/path.dart' as path;
 
 import '../io.dart';
@@ -43,7 +40,7 @@
   }
 
   @override
-  String getDirectory(PackageId id, {String relativeFrom}) =>
+  String getDirectory(PackageId id, {String? relativeFrom}) =>
       getDirectoryInCache(id);
 
   String getDirectoryInCache(PackageId id);
@@ -82,5 +79,5 @@
   /// cache (but that might itself have failed).
   final bool success;
   final PackageId package;
-  RepairResult(this.package, {@required this.success});
+  RepairResult(this.package, {required this.success});
 }
diff --git a/lib/src/source/git.dart b/lib/src/source/git.dart
index 5f1def9..161c540 100644
--- a/lib/src/source/git.dart
+++ b/lib/src/source/git.dart
@@ -2,11 +2,10 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:io';
 
+import 'package:collection/collection.dart' show IterableNullableExtension;
 import 'package:path/path.dart' as p;
 import 'package:pool/pool.dart';
 import 'package:pub_semver/pub_semver.dart';
@@ -44,7 +43,7 @@
   }
 
   @override
-  PackageRef parseRef(String name, description, {String containingPath}) {
+  PackageRef parseRef(String name, description, {String? containingPath}) {
     dynamic url;
     dynamic ref;
     dynamic path;
@@ -73,7 +72,7 @@
 
   @override
   PackageId parseId(String name, Version version, description,
-      {String containingPath}) {
+      {String? containingPath}) {
     if (description is! Map) {
       throw FormatException("The description must be a map with a 'url' "
           'key.');
@@ -114,7 +113,7 @@
   }
 
   /// Throws a [FormatException] if [url] isn't a valid Git URL.
-  Map<String, Object> _validatedUrl(dynamic url, String containingDir) {
+  Map<String, Object> _validatedUrl(dynamic url, String? containingDir) {
     if (url is! String) {
       throw FormatException("The 'url' field of the description must be a "
           'string.');
@@ -248,7 +247,8 @@
   }
 
   @override
-  Future<List<PackageId>> doGetVersions(PackageRef ref, Duration maxAge) async {
+  Future<List<PackageId>> doGetVersions(
+      PackageRef ref, Duration? maxAge) async {
     return await _pool.withResource(() async {
       await _ensureRepoCache(ref);
       var path = _repoCachePath(ref);
@@ -301,7 +301,7 @@
 
     // Git doesn't recognize backslashes in paths, even on Windows.
     if (Platform.isWindows) pubspecPath = pubspecPath.replaceAll('\\', '/');
-    List<String> lines;
+    late List<String> lines;
     try {
       lines = await git
           .run(['show', '$revision:$pubspecPath'], workingDir: repoPath);
@@ -361,8 +361,8 @@
 
   /// Returns the path to the revision-specific cache of [id].
   @override
-  String getDirectoryInCache(PackageId id) =>
-      p.join(_revisionCachePath(id), id.description['path']);
+  String getDirectoryInCache(PackageId? id) =>
+      p.join(_revisionCachePath(id!), id.description['path']);
 
   @override
   List<Package> getCachedPackages() {
@@ -401,7 +401,7 @@
             }
           });
         })
-        .where((package) => package != null)
+        .whereNotNull()
         .toList();
 
     // Note that there may be multiple packages with the same name and version
@@ -516,7 +516,7 @@
         ['rev-parse', '--is-inside-git-dir'],
         workingDir: dirPath,
       );
-      if (result?.join('\n') != 'true') {
+      if (result.join('\n') != 'true') {
         isValid = false;
       }
     } on git.GitException {
diff --git a/lib/src/source/hosted.dart b/lib/src/source/hosted.dart
index 986dfa6..803bbc5 100644
--- a/lib/src/source/hosted.dart
+++ b/lib/src/source/hosted.dart
@@ -2,13 +2,12 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:convert';
 import 'dart:io' as io;
 
-import 'package:collection/collection.dart' show maxBy;
+import 'package:collection/collection.dart'
+    show maxBy, IterableNullableExtension;
 import 'package:http/http.dart' as http;
 import 'package:path/path.dart' as p;
 import 'package:pedantic/pedantic.dart';
@@ -122,14 +121,14 @@
     }
   }
 
-  Uri _defaultUrl;
+  Uri? _defaultUrl;
 
   /// Returns a reference to a hosted package named [name].
   ///
   /// If [url] is passed, it's the URL of the pub server from which the package
   /// should be downloaded. [url] most be normalized and validated using
   /// [validateAndNormalizeHostedUrl].
-  PackageRef refFor(String name, {Uri url}) =>
+  PackageRef refFor(String name, {Uri? url}) =>
       PackageRef(name, this, _descriptionFor(name, url));
 
   /// Returns an ID for a hosted package named [name] at [version].
@@ -137,12 +136,12 @@
   /// If [url] is passed, it's the URL of the pub server from which the package
   /// should be downloaded. [url] most be normalized and validated using
   /// [validateAndNormalizeHostedUrl].
-  PackageId idFor(String name, Version version, {Uri url}) =>
+  PackageId idFor(String name, Version version, {Uri? url}) =>
       PackageId(name, this, version, _descriptionFor(name, url));
 
   /// Returns the description for a hosted package named [name] with the
   /// given package server [url].
-  dynamic _descriptionFor(String name, [Uri url]) {
+  dynamic _descriptionFor(String name, [Uri? url]) {
     if (url == null) {
       return name;
     }
@@ -171,14 +170,14 @@
   /// given name from the default host, while a map with keys "name" and "url"
   /// refers to a package with the given name from the host at the given URL.
   @override
-  PackageRef parseRef(String name, description, {String containingPath}) {
+  PackageRef parseRef(String name, description, {String? containingPath}) {
     _parseDescription(description);
     return PackageRef(name, this, description);
   }
 
   @override
   PackageId parseId(String name, Version version, description,
-      {String containingPath}) {
+      {String? containingPath}) {
     _parseDescription(description);
     return PackageId(name, this, version, description);
   }
@@ -234,7 +233,8 @@
 
   @override
   final SystemCache systemCache;
-  RateLimitedScheduler<PackageRef, Map<PackageId, _VersionInfo>> _scheduler;
+  late RateLimitedScheduler<PackageRef, Map<PackageId, _VersionInfo>?>
+      _scheduler;
 
   BoundHostedSource(this.source, this.systemCache) {
     _scheduler = RateLimitedScheduler(
@@ -257,9 +257,9 @@
           var archiveUrl = map['archive_url'];
           if (archiveUrl is String) {
             final status = PackageStatus(
-                isDiscontinued: body['isDiscontinued'] as bool ?? false,
-                discontinuedReplacedBy: body['replacedBy'] as String,
-                isRetracted: map['retracted'] as bool ?? false);
+                isDiscontinued: body['isDiscontinued'] ?? false,
+                discontinuedReplacedBy: body['replacedBy'],
+                isRetracted: map['retracted'] ?? false);
             return MapEntry(
                 id, _VersionInfo(pubspec, Uri.parse(archiveUrl), status));
           }
@@ -271,15 +271,15 @@
     throw FormatException('versions must be a list');
   }
 
-  Future<Map<PackageId, _VersionInfo>> _fetchVersionsNoPrefetching(
+  Future<Map<PackageId, _VersionInfo>?> _fetchVersionsNoPrefetching(
       PackageRef ref) async {
     final serverUrl = _hostedUrl(ref.description);
     final url = _listVersionsUrl(ref.description);
     log.io('Get versions from $url.');
 
     String bodyText;
-    Map body;
-    Map<PackageId, _VersionInfo> result;
+    Map<String, dynamic>? body;
+    Map<PackageId, _VersionInfo>? result;
     try {
       // TODO(sigurdm): Implement cancellation of requests. This probably
       // requires resolution of: https://github.com/dart-lang/sdk/issues/22265.
@@ -289,38 +289,39 @@
         (client) => client.read(url, headers: pubApiHeaders),
       );
       body = jsonDecode(bodyText);
-      result = _versionInfoFromPackageListing(body, ref, url);
-    } catch (error, stackTrace) {
+      result = _versionInfoFromPackageListing(body!, ref, url);
+    } on Exception catch (error, stackTrace) {
       var parsed = source._parseDescription(ref.description);
       _throwFriendlyError(error, stackTrace, parsed.first, parsed.last);
     }
 
     // Cache the response on disk.
     // Don't cache overly big responses.
-    if (body.length < 100 * 1024) {
+    if (body!.length < 100 * 1024) {
       await _cacheVersionListingResponse(body, ref);
     }
     return result;
   }
 
-  Future<Map<PackageId, _VersionInfo>> _fetchVersions(PackageRef ref) async {
+  Future<Map<PackageId, _VersionInfo>?> _fetchVersions(PackageRef ref) async {
     final preschedule =
-        Zone.current[_prefetchingKey] as void Function(PackageRef);
+        Zone.current[_prefetchingKey] as void Function(PackageRef)?;
 
     /// Prefetch the dependencies of the latest version, we are likely to need
     /// them later.
-    void prescheduleDependenciesOfLatest(Map<PackageId, _VersionInfo> listing) {
+    void prescheduleDependenciesOfLatest(
+        Map<PackageId, _VersionInfo>? listing) {
       if (listing == null) return;
       final latestVersion =
-          maxBy(listing.keys.map((id) => id.version), (e) => e);
+          maxBy(listing.keys.map((id) => id.version), (e) => e)!;
       final latestVersionId =
           PackageId(ref.name, source, latestVersion, ref.description);
       final dependencies =
-          listing[latestVersionId]?.pubspec?.dependencies?.values ?? [];
+          listing[latestVersionId]?.pubspec.dependencies.values ?? [];
       unawaited(withDependencyType(DependencyType.none, () async {
         for (final packageRange in dependencies) {
           if (packageRange.source is HostedSource) {
-            preschedule(packageRange.toRef());
+            preschedule!(packageRange.toRef());
           }
         }
       }));
@@ -359,14 +360,14 @@
   ///
   /// If [maxAge] is not given, we will try to get the cached version no matter
   /// how old it is.
-  Future<Map<PackageId, _VersionInfo>> _cachedVersionListingResponse(
+  Future<Map<PackageId, _VersionInfo>?> _cachedVersionListingResponse(
       PackageRef ref,
-      {Duration maxAge}) async {
+      {Duration? maxAge}) async {
     if (_responseCache.containsKey(ref)) {
-      final cacheAge = DateTime.now().difference(_responseCache[ref].first);
+      final cacheAge = DateTime.now().difference(_responseCache[ref]!.first);
       if (maxAge == null || maxAge > cacheAge) {
         // The cached value is not too old.
-        return _responseCache[ref].last;
+        return _responseCache[ref]!.last;
       }
     }
     final cachePath = _versionListingCachePath(ref);
@@ -409,7 +410,8 @@
   }
 
   /// Saves the (decoded) response from package-listing of [ref].
-  Future<void> _cacheVersionListingResponse(Map body, PackageRef ref) async {
+  Future<void> _cacheVersionListingResponse(
+      Map<String, dynamic> body, PackageRef ref) async {
     final path = _versionListingCachePath(ref);
     try {
       ensureDir(p.dirname(path));
@@ -432,7 +434,7 @@
   }
 
   @override
-  Future<PackageStatus> status(PackageId id, {Duration maxAge}) async {
+  Future<PackageStatus> status(PackageId id, {Duration? maxAge}) async {
     final ref = id.toRef();
     // Did we already get info for this package?
     var versionListing = _scheduler.peek(ref);
@@ -450,7 +452,7 @@
           test: (error) => error is Exception,
         );
 
-    final listing = versionListing[id];
+    final listing = versionListing![id];
     // If we don't have the specific version we return the empty response, since
     // it is more or less harmless..
     //
@@ -477,7 +479,8 @@
   /// Downloads a list of all versions of a package that are available from the
   /// site.
   @override
-  Future<List<PackageId>> doGetVersions(PackageRef ref, Duration maxAge) async {
+  Future<List<PackageId>> doGetVersions(
+      PackageRef ref, Duration? maxAge) async {
     var versionListing = _scheduler.peek(ref);
     if (maxAge != null) {
       // Do we have a cached version response on disk?
@@ -485,7 +488,7 @@
           await _cachedVersionListingResponse(ref, maxAge: maxAge);
     }
     versionListing ??= await _scheduler.schedule(ref);
-    return versionListing.keys.toList();
+    return versionListing!.keys.toList();
   }
 
   /// Parses [description] into its server and package name components, then
@@ -509,7 +512,7 @@
   Future<Pubspec> describeUncached(PackageId id) async {
     final versions = await _scheduler.schedule(id.toRef());
     final url = _listVersionsUrl(id.description);
-    return versions[id]?.pubspec ??
+    return versions![id]?.pubspec ??
         (throw PackageNotFoundException('Could not find package $id at $url'));
   }
 
@@ -607,7 +610,7 @@
 
   /// Returns the best-guess package ID for [basename], which should be a
   /// subdirectory in a hosted cache.
-  PackageId _idForBasename(String basename, {Uri url}) {
+  PackageId _idForBasename(String basename, {Uri? url}) {
     var components = split1(basename, '-');
     var version = Version.none;
     if (components.length > 1) {
@@ -644,7 +647,7 @@
             return null;
           }
         })
-        .where((e) => e != null)
+        .whereNotNull()
         .toList();
   }
 
@@ -665,7 +668,7 @@
     // query-string as is the case with signed S3 URLs. And we wish to allow for
     // such URLs to be used.
     final versions = await _scheduler.schedule(id.toRef());
-    final versionInfo = versions[id];
+    final versionInfo = versions![id];
     final packageName = id.name;
     final version = id.version;
     if (versionInfo == null) {
@@ -676,9 +679,6 @@
     final server = parsedDescription.last;
 
     var url = versionInfo.archiveUrl;
-    // To support old servers that has no archive_url we fall back to the
-    // hard-coded path.
-    url ??= Uri.parse('$server/packages/$packageName/versions/$version.tar.gz');
     log.io('Get package from $url.');
     log.message('Downloading ${log.bold(id.name)} ${id.version}...');
 
@@ -778,7 +778,7 @@
     return replace(
       url,
       RegExp(r'[<>:"\\/|?*%]'),
-      (match) => '%${match[0].codeUnitAt(0)}',
+      (match) => '%${match[0]!.codeUnitAt(0)}',
     );
   }
 
@@ -835,7 +835,8 @@
 
   /// Gets the list of all versions of [ref] that are in the system cache.
   @override
-  Future<List<PackageId>> doGetVersions(PackageRef ref, Duration maxAge) async {
+  Future<List<PackageId>> doGetVersions(
+      PackageRef ref, Duration? maxAge) async {
     var parsed = source._parseDescription(ref.description);
     var server = parsed.last;
     log.io('Finding versions of ${ref.name} in '
@@ -877,14 +878,13 @@
   }
 
   @override
-  Future<PackageStatus> status(PackageId id, {Duration maxAge}) async {
+  Future<PackageStatus> status(PackageId id, {Duration? maxAge}) async {
     // Do we have a cached version response on disk?
     final versionListing = await _cachedVersionListingResponse(id.toRef());
 
     if (versionListing == null) {
       return PackageStatus();
     }
-    final listing = versionListing[id];
     // If we don't have the specific version we return the empty response.
     //
     // This should not happen. But in production we want to avoid a crash, since
@@ -892,7 +892,6 @@
     //
     // TODO(sigurdm): Consider representing the non-existence of the
     // package-version in the return value.
-    assert(listing != null);
     return versionListing[id]?.status ?? PackageStatus();
   }
 }
diff --git a/lib/src/source/path.dart b/lib/src/source/path.dart
index b1dc26b..545eae8 100644
--- a/lib/src/source/path.dart
+++ b/lib/src/source/path.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:path/path.dart' as p;
@@ -64,7 +62,7 @@
   /// original path but resolved relative to the containing path. The
   /// "relative" key will be `true` if the original path was relative.
   @override
-  PackageRef parseRef(String name, description, {String containingPath}) {
+  PackageRef parseRef(String name, description, {String? containingPath}) {
     if (description is! String) {
       throw FormatException('The description must be a path string.');
     }
@@ -90,7 +88,7 @@
 
   @override
   PackageId parseId(String name, Version version, description,
-      {String containingPath}) {
+      {String? containingPath}) {
     if (description is! Map) {
       throw FormatException('The description must be a map.');
     }
@@ -165,7 +163,8 @@
   BoundPathSource(this.source, this.systemCache);
 
   @override
-  Future<List<PackageId>> doGetVersions(PackageRef ref, Duration maxAge) async {
+  Future<List<PackageId>> doGetVersions(
+      PackageRef ref, Duration? maxAge) async {
     // There's only one package ID for a given path. We just need to find the
     // version.
     var pubspec = _loadPubspec(ref);
@@ -183,10 +182,10 @@
   }
 
   @override
-  String getDirectory(PackageId id, {String relativeFrom}) {
+  String getDirectory(PackageId id, {String? relativeFrom}) {
     return id.description['relative']
         ? p.relative(id.description['path'], from: relativeFrom)
-        : id.description['path'];
+        : id.description['path']!;
   }
 
   /// Ensures that [description] is a valid path description and returns a
diff --git a/lib/src/source/sdk.dart b/lib/src/source/sdk.dart
index aff6acc..3ff93fe 100644
--- a/lib/src/source/sdk.dart
+++ b/lib/src/source/sdk.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:pub_semver/pub_semver.dart';
@@ -35,7 +33,7 @@
 
   /// Parses an SDK dependency.
   @override
-  PackageRef parseRef(String name, description, {String containingPath}) {
+  PackageRef parseRef(String name, description, {String? containingPath}) {
     if (description is! String) {
       throw FormatException('The description must be an SDK name.');
     }
@@ -45,7 +43,7 @@
 
   @override
   PackageId parseId(String name, Version version, description,
-      {String containingPath}) {
+      {String? containingPath}) {
     if (description is! String) {
       throw FormatException('The description must be an SDK name.');
     }
@@ -72,7 +70,8 @@
   BoundSdkSource(this.source, this.systemCache);
 
   @override
-  Future<List<PackageId>> doGetVersions(PackageRef ref, Duration maxAge) async {
+  Future<List<PackageId>> doGetVersions(
+      PackageRef ref, Duration? maxAge) async {
     var pubspec = _loadPubspec(ref);
     var id = PackageId(ref.name, source, pubspec.version, ref.description);
     memoizePubspec(id, pubspec);
@@ -95,8 +94,8 @@
   /// Throws a [PackageNotFoundException] if [package]'s SDK is unavailable or
   /// doesn't contain the package.
   String _verifiedPackagePath(PackageName package) {
-    var identifier = package.description as String;
-    var sdk = sdks[identifier];
+    var identifier = package.description as String?;
+    var sdk = sdks[identifier!];
     if (sdk == null) {
       throw PackageNotFoundException('unknown SDK "$identifier"');
     } else if (!sdk.isAvailable) {
@@ -112,7 +111,7 @@
   }
 
   @override
-  String getDirectory(PackageId id, {String relativeFrom}) {
+  String getDirectory(PackageId id, {String? relativeFrom}) {
     try {
       return _verifiedPackagePath(id);
     } on PackageNotFoundException catch (error) {
diff --git a/lib/src/source/unknown.dart b/lib/src/source/unknown.dart
index d98d0e2..eaf94fd 100644
--- a/lib/src/source/unknown.dart
+++ b/lib/src/source/unknown.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:pub_semver/pub_semver.dart';
@@ -44,12 +42,12 @@
   int hashDescription(description) => description.hashCode;
 
   @override
-  PackageRef parseRef(String name, description, {String containingPath}) =>
+  PackageRef parseRef(String name, description, {String? containingPath}) =>
       PackageRef(name, this, description);
 
   @override
   PackageId parseId(String name, Version version, description,
-          {String containingPath}) =>
+          {String? containingPath}) =>
       PackageId(name, this, version, description);
 }
 
@@ -63,7 +61,7 @@
   _BoundUnknownSource(this.source, this.systemCache);
 
   @override
-  Future<List<PackageId>> doGetVersions(PackageRef ref, Duration maxAge) =>
+  Future<List<PackageId>> doGetVersions(PackageRef ref, Duration? maxAge) =>
       throw UnsupportedError(
           "Cannot get package versions from unknown source '${source.name}'.");
 
@@ -73,7 +71,7 @@
 
   /// Returns the directory where this package can be found locally.
   @override
-  String getDirectory(PackageId id, {String relativeFrom}) =>
+  String getDirectory(PackageId id, {String? relativeFrom}) =>
       throw UnsupportedError(
           "Cannot find a package from an unknown source '${source.name}'.");
 }
diff --git a/lib/src/source_registry.dart b/lib/src/source_registry.dart
index 9c946eb..7e8aa84 100644
--- a/lib/src/source_registry.dart
+++ b/lib/src/source_registry.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'source.dart';
 import 'source/git.dart';
 import 'source/hosted.dart';
@@ -27,7 +25,7 @@
   ///
   /// This defaults to [hosted].
   Source get defaultSource => _default;
-  Source _default;
+  late Source _default;
 
   /// The registered sources, in name order.
   List<Source> get all {
@@ -60,7 +58,7 @@
       throw StateError('Default source $name is not in the registry');
     }
 
-    _default = _sources[name];
+    _default = _sources[name]!;
   }
 
   /// Registers a new source.
@@ -80,7 +78,7 @@
   ///
   /// Returns an [UnknownSource] if no source with that name has been
   /// registered. If [name] is null, returns the default source.
-  Source operator [](String name) {
+  Source? operator [](String? name) {
     if (name == null) return _default;
     if (_sources.containsKey(name)) return _sources[name];
     return UnknownSource(name);
diff --git a/lib/src/system_cache.dart b/lib/src/system_cache.dart
index b8033ba..dc6eb4a 100644
--- a/lib/src/system_cache.dart
+++ b/lib/src/system_cache.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:io';
 
 import 'package:path/path.dart' as p;
@@ -36,19 +34,20 @@
 
   static String defaultDir = (() {
     if (Platform.environment.containsKey('PUB_CACHE')) {
-      return Platform.environment['PUB_CACHE'];
+      return Platform.environment['PUB_CACHE']!;
     } else if (Platform.isWindows) {
       // %LOCALAPPDATA% is preferred as the cache location over %APPDATA%, because the latter is synchronised between
       // devices when the user roams between them, whereas the former is not.
       // The default cache dir used to be in %APPDATA%, so to avoid breaking old installs,
       // we use the old dir in %APPDATA% if it exists. Else, we use the new default location
       // in %LOCALAPPDATA%.
-      var appData = Platform.environment['APPDATA'];
+      //  TODO(sigurdm): handle missing APPDATA.
+      var appData = Platform.environment['APPDATA']!;
       var appDataCacheDir = p.join(appData, 'Pub', 'Cache');
       if (dirExists(appDataCacheDir)) {
         return appDataCacheDir;
       }
-      var localAppData = Platform.environment['LOCALAPPDATA'];
+      var localAppData = Platform.environment['LOCALAPPDATA']!;
       return p.join(localAppData, 'Pub', 'Cache');
     } else {
       return '${Platform.environment['HOME']}/.pub-cache';
@@ -62,7 +61,7 @@
   final sources = SourceRegistry();
 
   /// The sources bound to this cache.
-  final _boundSources = <Source, BoundSource>{};
+  final _boundSources = <Source?, BoundSource>{};
 
   /// The built-in Git source bound to this cache.
   BoundGitSource get git => _boundSources[sources.git] as BoundGitSource;
@@ -87,7 +86,7 @@
   ///
   /// If [isOffline] is `true`, then the offline hosted source will be used.
   /// Defaults to `false`.
-  SystemCache({String rootDir, bool isOffline = false})
+  SystemCache({String? rootDir, bool isOffline = false})
       : rootDir = rootDir ?? SystemCache.defaultDir,
         tokenStore = TokenStore(dartConfigDir) {
     for (var source in sources.all) {
@@ -100,8 +99,8 @@
   }
 
   /// Returns the version of [source] bound to this cache.
-  BoundSource source(Source source) =>
-      _boundSources.putIfAbsent(source, () => source.bind(this));
+  BoundSource source(Source? source) =>
+      _boundSources.putIfAbsent(source, () => source!.bind(this));
 
   /// Loads the package identified by [id].
   ///
diff --git a/lib/src/utils.dart b/lib/src/utils.dart
index fc3984e..39439e9 100644
--- a/lib/src/utils.dart
+++ b/lib/src/utils.dart
@@ -9,7 +9,6 @@
 import 'dart:math' as math;
 
 import 'package:crypto/crypto.dart' as crypto;
-import 'package:meta/meta.dart';
 import 'package:pub_semver/pub_semver.dart';
 import 'package:stack_trace/stack_trace.dart';
 
@@ -501,8 +500,7 @@
 }
 
 /// Throw a [ApplicationException] with [message].
-@alwaysThrows
-void fail(String message, [Object? innerError, StackTrace? innerTrace]) {
+Never fail(String message, [Object? innerError, StackTrace? innerTrace]) {
   if (innerError != null) {
     throw WrappedException(message, innerError, innerTrace);
   } else {
@@ -514,7 +512,7 @@
 /// failed because of invalid input data.
 ///
 /// This will report the error and cause pub to exit with [exit_codes.DATA].
-void dataError(String message) => throw DataException(message);
+Never dataError(String message) => throw DataException(message);
 
 /// Returns a UUID in v4 format as a `String`.
 ///
diff --git a/lib/src/validator.dart b/lib/src/validator.dart
index c6a7b4a..29392fa 100644
--- a/lib/src/validator.dart
+++ b/lib/src/validator.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:meta/meta.dart';
@@ -123,8 +121,10 @@
   /// package, in bytes. This is used to validate that it's not too big to
   /// upload to the server.
   static Future<void> runAll(
-      Entrypoint entrypoint, Future<int> packageSize, Uri serverUrl,
-      {List<String> hints, List<String> warnings, List<String> errors}) {
+      Entrypoint entrypoint, Future<int> packageSize, Uri? serverUrl,
+      {required List<String> hints,
+      required List<String> warnings,
+      required List<String> errors}) {
     var validators = [
       GitignoreValidator(entrypoint),
       PubspecValidator(entrypoint),
@@ -149,9 +149,7 @@
       PubspecTypoValidator(entrypoint),
       LeakDetectionValidator(entrypoint),
     ];
-    if (packageSize != null) {
-      validators.add(SizeValidator(entrypoint, packageSize));
-    }
+    validators.add(SizeValidator(entrypoint, packageSize));
 
     return Future.wait(validators.map((validator) => validator.validate()))
         .then((_) {
diff --git a/lib/src/validator/changelog.dart b/lib/src/validator/changelog.dart
index 2f93903..694ace2 100644
--- a/lib/src/validator/changelog.dart
+++ b/lib/src/validator/changelog.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:convert';
 
@@ -43,9 +41,6 @@
         warnings.add('$changelog contains invalid UTF-8.\n'
             'This will cause it to be displayed incorrectly on '
             'the Pub site (https://pub.dev).');
-      }
-
-      if (contents == null) {
         // Failed to decode contents, so there's nothing else to check.
         return;
       }
diff --git a/lib/src/validator/compiled_dartdoc.dart b/lib/src/validator/compiled_dartdoc.dart
index 670862c..3f3376a 100644
--- a/lib/src/validator/compiled_dartdoc.dart
+++ b/lib/src/validator/compiled_dartdoc.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:path/path.dart' as path;
diff --git a/lib/src/validator/dependency.dart b/lib/src/validator/dependency.dart
index c9f221a..5d158d1 100644
--- a/lib/src/validator/dependency.dart
+++ b/lib/src/validator/dependency.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:pub_semver/pub_semver.dart';
@@ -223,8 +221,9 @@
       constraint = '^${(dep.constraint as VersionRange).min}';
     } else {
       constraint = '"${dep.constraint} '
-          '<${(dep.constraint as VersionRange).min.nextBreaking}"';
+          '<${(dep.constraint as VersionRange).min!.nextBreaking}"';
     }
+    // TODO: Handle the case where `dep.constraint.min` is null.
 
     warnings
         .add('Your dependency on "${dep.name}" should have an upper bound. For '
@@ -240,7 +239,7 @@
   void _warnAboutPrerelease(String dependencyName, VersionRange constraint) {
     final packageVersion = entrypoint.root.version;
     if (constraint.min != null &&
-        constraint.min.isPreRelease &&
+        constraint.min!.isPreRelease &&
         !packageVersion.isPreRelease) {
       warnings.add('Packages dependent on a pre-release of another package '
           'should themselves be published as a pre-release version. '
diff --git a/lib/src/validator/dependency_override.dart b/lib/src/validator/dependency_override.dart
index 8942be8..24e4e84 100644
--- a/lib/src/validator/dependency_override.dart
+++ b/lib/src/validator/dependency_override.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:collection/collection.dart';
diff --git a/lib/src/validator/deprecated_fields.dart b/lib/src/validator/deprecated_fields.dart
index 150d599..e1aee4f 100644
--- a/lib/src/validator/deprecated_fields.dart
+++ b/lib/src/validator/deprecated_fields.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import '../entrypoint.dart';
diff --git a/lib/src/validator/directory.dart b/lib/src/validator/directory.dart
index 9828f64..409e243 100644
--- a/lib/src/validator/directory.dart
+++ b/lib/src/validator/directory.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:path/path.dart' as path;
diff --git a/lib/src/validator/executable.dart b/lib/src/validator/executable.dart
index e9881d2..2163e8c 100644
--- a/lib/src/validator/executable.dart
+++ b/lib/src/validator/executable.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:path/path.dart' as p;
diff --git a/lib/src/validator/flutter_constraint.dart b/lib/src/validator/flutter_constraint.dart
index 74c2a2e..d689a22 100644
--- a/lib/src/validator/flutter_constraint.dart
+++ b/lib/src/validator/flutter_constraint.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:pub_semver/pub_semver.dart';
diff --git a/lib/src/validator/flutter_plugin_format.dart b/lib/src/validator/flutter_plugin_format.dart
index 87fd3df..6f00f06 100644
--- a/lib/src/validator/flutter_plugin_format.dart
+++ b/lib/src/validator/flutter_plugin_format.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:pub_semver/pub_semver.dart';
diff --git a/lib/src/validator/gitignore.dart b/lib/src/validator/gitignore.dart
index 0986fed..7770b57 100644
--- a/lib/src/validator/gitignore.dart
+++ b/lib/src/validator/gitignore.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:io';
 
diff --git a/lib/src/validator/language_version.dart b/lib/src/validator/language_version.dart
index 6d71b22..e52a92a 100644
--- a/lib/src/validator/language_version.dart
+++ b/lib/src/validator/language_version.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:analyzer/dart/ast/ast.dart';
diff --git a/lib/src/validator/leak_detection.dart b/lib/src/validator/leak_detection.dart
index 53bf31d..7a398d4 100644
--- a/lib/src/validator/leak_detection.dart
+++ b/lib/src/validator/leak_detection.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:convert';
 import 'dart:io';
@@ -75,7 +73,7 @@
       errors.addAll(leaks.take(2).map((leak) => leak.toString()));
 
       final files = leaks
-          .map((leak) => leak.span.sourceUrl.toFilePath(windows: false))
+          .map((leak) => leak.span.sourceUrl!.toFilePath(windows: false))
           .toSet()
           .toList(growable: false)
         ..sort();
@@ -155,8 +153,8 @@
   final List<String> testsWithNoLeaks;
 
   LeakPattern._({
-    @required this.kind,
-    @required String pattern,
+    required this.kind,
+    required String pattern,
     Iterable<Pattern> allowed = const <Pattern>[],
     Map<int, double> entropyThresholds = const <int, double>{},
     Iterable<String> testsWithLeaks = const <String>[],
@@ -177,17 +175,17 @@
   Iterable<LeakMatch> findPossibleLeaks(String file, String content) sync* {
     final source = SourceFile.fromString(content, url: file);
     for (final m in _pattern.allMatches(content)) {
-      if (_allowed.any((s) => m.group(0).contains(s))) {
+      if (_allowed.any((s) => m.group(0)!.contains(s))) {
         continue;
       }
       if (_entropyThresholds.entries
-          .any((entry) => _entropy(m.group(entry.key)) < entry.value)) {
+          .any((entry) => _entropy(m.group(entry.key)!) < entry.value)) {
         continue;
       }
 
       yield LeakMatch(
         this,
-        source.span(m.start, m.start + m.group(0).length),
+        source.span(m.start, m.start + m.group(0)!.length),
       );
     }
   }
@@ -554,7 +552,7 @@
       '$_pemRequireLineBreak$_pemWSP',
       '=(?:(?:[a-zA-Z0-9+/]$_pemWSP){4})',
       _pemEnd('PGP PRIVATE KEY BLOCK'),
-    ].join(''),
+    ].join(),
     testsWithLeaks: [
       '''
 -----BEGIN PGP PRIVATE KEY BLOCK-----
@@ -607,7 +605,7 @@
       _pemRequireLineBreak,
       // Allow arbitrary whitespace and escaped line breaks
       _pemWSP,
-    ].join('');
+    ].join();
 
 String _pemBase64Block() => [
       // Require base64 character in blocks of 4, allow arbirary whitespace
@@ -629,10 +627,10 @@
         // character, allow arbirary whitespace and escaped line breaks
         // in between.
         '(?:[a-zA-Z0-9+/]$_pemWSP){2}(?:=$_pemWSP){2}',
-      ].join(''),
+      ].join(),
       // End blocks
       '))?',
-    ].join('');
+    ].join();
 
 String _pemEnd(String label) => [
       // Require \n, \r, \\r, or \\n, backslash escaping is allowed if the key
@@ -641,10 +639,10 @@
       // Allow arbitrary whitespace and escaped line breaks
       _pemWSP,
       '-----END $label-----',
-    ].join('');
+    ].join();
 
 String _pemKeyFormat(String label) => [
       _pemBegin(label),
       _pemBase64Block(),
       _pemEnd(label),
-    ].join('');
+    ].join();
diff --git a/lib/src/validator/license.dart b/lib/src/validator/license.dart
index edca16d..1a2c68a 100644
--- a/lib/src/validator/license.dart
+++ b/lib/src/validator/license.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:path/path.dart' as path;
diff --git a/lib/src/validator/name.dart b/lib/src/validator/name.dart
index d484ed8..07d945c 100644
--- a/lib/src/validator/name.dart
+++ b/lib/src/validator/name.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:path/path.dart' as path;
@@ -74,7 +72,7 @@
       builder
         ..write(source.substring(lastMatchEnd, match.start + 1))
         ..write('_')
-        ..write(match.group(1).toLowerCase());
+        ..write(match.group(1)!.toLowerCase());
       lastMatchEnd = match.end;
     }
     builder.write(source.substring(lastMatchEnd));
diff --git a/lib/src/validator/null_safety_mixed_mode.dart b/lib/src/validator/null_safety_mixed_mode.dart
index 401043e..42f9f77 100644
--- a/lib/src/validator/null_safety_mixed_mode.dart
+++ b/lib/src/validator/null_safety_mixed_mode.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:path/path.dart' as p;
diff --git a/lib/src/validator/pubspec.dart b/lib/src/validator/pubspec.dart
index 20a7174..2a5e052 100644
--- a/lib/src/validator/pubspec.dart
+++ b/lib/src/validator/pubspec.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:path/path.dart' as p;
diff --git a/lib/src/validator/pubspec_field.dart b/lib/src/validator/pubspec_field.dart
index 482d696..8a5c23e 100644
--- a/lib/src/validator/pubspec_field.dart
+++ b/lib/src/validator/pubspec_field.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import '../entrypoint.dart';
diff --git a/lib/src/validator/pubspec_typo.dart b/lib/src/validator/pubspec_typo.dart
index a770b76..9b04292 100644
--- a/lib/src/validator/pubspec_typo.dart
+++ b/lib/src/validator/pubspec_typo.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import '../entrypoint.dart';
 import '../levenshtein.dart';
 import '../validator.dart';
@@ -16,8 +14,6 @@
   Future validate() async {
     final fields = entrypoint.root.pubspec.fields;
 
-    if (fields == null) return;
-
     /// Limit the number of typo warnings so as not to drown out the other
     /// warnings
     var warningCount = 0;
diff --git a/lib/src/validator/readme.dart b/lib/src/validator/readme.dart
index 14d6b5e..85de479 100644
--- a/lib/src/validator/readme.dart
+++ b/lib/src/validator/readme.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:convert';
 
diff --git a/lib/src/validator/relative_version_numbering.dart b/lib/src/validator/relative_version_numbering.dart
index 6609074..eb4246c 100644
--- a/lib/src/validator/relative_version_numbering.dart
+++ b/lib/src/validator/relative_version_numbering.dart
@@ -2,10 +2,10 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 
+import 'package:collection/collection.dart' show IterableExtension;
+
 import '../entrypoint.dart';
 import '../exceptions.dart';
 import '../null_safety_analysis.dart';
@@ -18,7 +18,7 @@
   static const String semverUrl =
       'https://dart.dev/tools/pub/versioning#semantic-versions';
 
-  final Uri _server;
+  final Uri? _server;
 
   RelativeVersionNumberingValidator(Entrypoint entrypoint, this._server)
       : super(entrypoint);
@@ -35,9 +35,8 @@
       existingVersions = [];
     }
     existingVersions.sort((a, b) => a.version.compareTo(b.version));
-    final previousVersion = existingVersions.lastWhere(
-        (id) => id.version < entrypoint.root.version,
-        orElse: () => null);
+    final previousVersion = existingVersions
+        .lastWhereOrNull((id) => id.version < entrypoint.root.version);
     if (previousVersion == null) return;
 
     final previousPubspec =
diff --git a/lib/src/validator/sdk_constraint.dart b/lib/src/validator/sdk_constraint.dart
index 68de6bf..063ad7e 100644
--- a/lib/src/validator/sdk_constraint.dart
+++ b/lib/src/validator/sdk_constraint.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:pub_semver/pub_semver.dart';
diff --git a/lib/src/validator/size.dart b/lib/src/validator/size.dart
index f873dfe..8f258a9 100644
--- a/lib/src/validator/size.dart
+++ b/lib/src/validator/size.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 import 'dart:math' as math;
 
diff --git a/lib/src/validator/strict_dependencies.dart b/lib/src/validator/strict_dependencies.dart
index 33e1210..881c55c 100644
--- a/lib/src/validator/strict_dependencies.dart
+++ b/lib/src/validator/strict_dependencies.dart
@@ -2,8 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-// @dart=2.10
-
 import 'dart:async';
 
 import 'package:analyzer/dart/ast/ast.dart';
@@ -47,9 +45,9 @@
       }
 
       for (var directive in directives) {
-        Uri url;
+        Uri? url;
         try {
-          url = Uri.parse(directive.uri.stringValue);
+          url = Uri.parse(directive.uri.stringValue!);
         } on FormatException catch (_) {
           // Ignore a format exception. [url] will be null, and we'll emit an
           // "Invalid URL" warning below.