migrate to stable, null-safety (dart-lang/pubspec_parse#65)

Co-authored-by: Jacob MacDonald <jakemac@google.com>
diff --git a/pkgs/pubspec_parse/.github/workflows/test-package.yml b/pkgs/pubspec_parse/.github/workflows/test-package.yml
index fa37a1f..d632645 100644
--- a/pkgs/pubspec_parse/.github/workflows/test-package.yml
+++ b/pkgs/pubspec_parse/.github/workflows/test-package.yml
@@ -45,9 +45,9 @@
     strategy:
       fail-fast: false
       matrix:
-        # Add macos-latest and/or windows-latest if relevant for this package.
         os: [ubuntu-latest]
-        sdk: [dev]
+        # change beta to 2.12.0 when released!
+        sdk: [beta, dev]
     steps:
       - uses: actions/checkout@v2
       - uses: dart-lang/setup-dart@v0.3
@@ -59,27 +59,3 @@
       - name: Run VM tests
         run: dart test --platform vm --run-skipped
         if: always() && steps.install.outcome == 'success'
-
-  # Run tests on a matrix consisting of two dimensions:
-  # 1. OS: ubuntu-latest, (macos-latest, windows-latest)
-  # 2. release: 2.7.0
-  test-legacy-sdk:
-    needs: analyze
-    runs-on: ${{ matrix.os }}
-    strategy:
-      fail-fast: false
-      matrix:
-        # Add macos-latest and/or windows-latest if relevant for this package.
-        os: [ubuntu-latest]
-        sdk: [2.7.0]
-    steps:
-      - uses: actions/checkout@v2
-      - uses: dart-lang/setup-dart@v0.3
-        with:
-          sdk: ${{ matrix.sdk }}
-      - id: install
-        name: Install dependencies
-        run: pub get
-      - name: Run VM tests
-        run: pub run test --platform vm --run-skipped
-        if: always() && steps.install.outcome == 'success'
diff --git a/pkgs/pubspec_parse/CHANGELOG.md b/pkgs/pubspec_parse/CHANGELOG.md
index a07e488..97c410f 100644
--- a/pkgs/pubspec_parse/CHANGELOG.md
+++ b/pkgs/pubspec_parse/CHANGELOG.md
@@ -1,3 +1,9 @@
+## 1.0.0
+
+- Migrate to null-safety.
+- Pubspec: `author` and `authors` are both now deprecated.
+  See https://dart.dev/tools/pub/pubspec#authorauthors
+
 ## 0.1.8
 
 - Allow the latest `package:pub_semver`.
diff --git a/pkgs/pubspec_parse/build.yaml b/pkgs/pubspec_parse/build.yaml
index aa3ea69..3e642b8 100644
--- a/pkgs/pubspec_parse/build.yaml
+++ b/pkgs/pubspec_parse/build.yaml
@@ -18,4 +18,6 @@
       source_gen|combining_builder:
         options:
           ignore_for_file:
+          - deprecated_member_use_from_same_package
+          - lines_longer_than_80_chars
           - prefer_expression_function_bodies
diff --git a/pkgs/pubspec_parse/lib/src/dependency.dart b/pkgs/pubspec_parse/lib/src/dependency.dart
index 2d4eda6..e4996c5 100644
--- a/pkgs/pubspec_parse/lib/src/dependency.dart
+++ b/pkgs/pubspec_parse/lib/src/dependency.dart
@@ -2,16 +2,17 @@
 // 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.
 
+import 'package:collection/collection.dart';
 import 'package:json_annotation/json_annotation.dart';
 import 'package:pub_semver/pub_semver.dart';
 import 'package:yaml/yaml.dart';
 
 part 'dependency.g.dart';
 
-Map<String, Dependency> parseDeps(Map source) =>
+Map<String, Dependency> parseDeps(Map? source) =>
     source?.map((k, v) {
       final key = k as String;
-      Dependency value;
+      Dependency? value;
       try {
         value = _fromJson(v);
       } on CheckedFromJsonException catch (e) {
@@ -19,7 +20,7 @@
           // This is likely a "synthetic" map created from a String value
           // Use `source` to throw this exception with an actual YamlMap and
           // extract the associated error information.
-          throw CheckedFromJsonException(source, key, e.className, e.message);
+          throw CheckedFromJsonException(source, key, e.className!, e.message);
         }
         rethrow;
       }
@@ -35,7 +36,7 @@
 const _sourceKeys = ['sdk', 'git', 'path', 'hosted'];
 
 /// Returns `null` if the data could not be parsed.
-Dependency _fromJson(dynamic data) {
+Dependency? _fromJson(Object? data) {
   if (data is String || data == null) {
     return _$HostedDependencyFromJson({'version': data});
   }
@@ -47,8 +48,8 @@
     if (data.isEmpty || (matchedKeys.isEmpty && data.containsKey('version'))) {
       return _$HostedDependencyFromJson(data);
     } else {
-      final firstUnrecognizedKey = matchedKeys
-          .firstWhere((k) => !_sourceKeys.contains(k), orElse: () => null);
+      final firstUnrecognizedKey =
+          matchedKeys.firstWhereOrNull((k) => !_sourceKeys.contains(k));
 
       return $checkedNew<Dependency>('Dependency', data, () {
         if (firstUnrecognizedKey != null) {
@@ -92,12 +93,13 @@
 
 @JsonSerializable()
 class SdkDependency extends Dependency {
-  @JsonKey(nullable: false, disallowNullValue: true, required: true)
   final String sdk;
   @JsonKey(fromJson: _constraintFromString)
   final VersionConstraint version;
 
-  SdkDependency(this.sdk, {this.version}) : super._();
+  SdkDependency(this.sdk, {VersionConstraint? version})
+      : version = version ?? VersionConstraint.any,
+        super._();
 
   @override
   String get _info => sdk;
@@ -105,14 +107,14 @@
 
 @JsonSerializable()
 class GitDependency extends Dependency {
-  @JsonKey(fromJson: parseGitUri, required: true, disallowNullValue: true)
+  @JsonKey(fromJson: parseGitUri)
   final Uri url;
-  final String ref;
-  final String path;
+  final String? ref;
+  final String? path;
 
-  GitDependency(this.url, this.ref, this.path) : super._();
+  GitDependency(this.url, {this.ref, this.path}) : super._();
 
-  factory GitDependency.fromData(Object data) {
+  factory GitDependency.fromData(Object? data) {
     if (data is String) {
       data = {'url': data};
     }
@@ -128,12 +130,14 @@
   String get _info => 'url@$url';
 }
 
-Uri parseGitUri(String value) =>
-    value == null ? null : _tryParseScpUri(value) ?? Uri.parse(value);
+Uri? parseGitUriOrNull(String? value) =>
+    value == null ? null : parseGitUri(value);
+
+Uri parseGitUri(String value) => _tryParseScpUri(value) ?? Uri.parse(value);
 
 /// Supports URIs like `[user@]host.xz:path/to/repo.git/`
 /// See https://git-scm.com/docs/git-clone#_git_urls_a_id_urls_a
-Uri _tryParseScpUri(String value) {
+Uri? _tryParseScpUri(String value) {
   final colonIndex = value.indexOf(':');
 
   if (colonIndex < 0) {
@@ -167,7 +171,7 @@
 
   PathDependency(this.path) : super._();
 
-  factory PathDependency.fromData(Object data) {
+  factory PathDependency.fromData(Object? data) {
     if (data is String) {
       return PathDependency(data);
     }
@@ -184,9 +188,9 @@
   final VersionConstraint version;
 
   @JsonKey(disallowNullValue: true)
-  final HostedDetails hosted;
+  final HostedDetails? hosted;
 
-  HostedDependency({VersionConstraint version, this.hosted})
+  HostedDependency({VersionConstraint? version, this.hosted})
       : version = version ?? VersionConstraint.any,
         super._();
 
@@ -196,15 +200,14 @@
 
 @JsonSerializable(disallowUnrecognizedKeys: true)
 class HostedDetails {
-  @JsonKey(required: true, disallowNullValue: true)
   final String name;
 
-  @JsonKey(fromJson: parseGitUri, disallowNullValue: true)
-  final Uri url;
+  @JsonKey(fromJson: parseGitUriOrNull, disallowNullValue: true)
+  final Uri? url;
 
   HostedDetails(this.name, this.url);
 
-  factory HostedDetails.fromJson(Object data) {
+  factory HostedDetails.fromJson(Object? data) {
     if (data is String) {
       data = {'name': data};
     }
@@ -217,5 +220,5 @@
   }
 }
 
-VersionConstraint _constraintFromString(String input) =>
-    input == null ? null : VersionConstraint.parse(input);
+VersionConstraint _constraintFromString(String? input) =>
+    input == null ? VersionConstraint.any : VersionConstraint.parse(input);
diff --git a/pkgs/pubspec_parse/lib/src/dependency.g.dart b/pkgs/pubspec_parse/lib/src/dependency.g.dart
index 37352b3..95b1779 100644
--- a/pkgs/pubspec_parse/lib/src/dependency.g.dart
+++ b/pkgs/pubspec_parse/lib/src/dependency.g.dart
@@ -1,6 +1,6 @@
 // GENERATED CODE - DO NOT MODIFY BY HAND
 
-// ignore_for_file: prefer_expression_function_bodies
+// ignore_for_file: deprecated_member_use_from_same_package, lines_longer_than_80_chars, prefer_expression_function_bodies
 
 part of 'dependency.dart';
 
@@ -10,12 +10,10 @@
 
 SdkDependency _$SdkDependencyFromJson(Map json) {
   return $checkedNew('SdkDependency', json, () {
-    $checkKeys(json,
-        requiredKeys: const ['sdk'], disallowNullValues: const ['sdk']);
     final val = SdkDependency(
       $checkedConvert(json, 'sdk', (v) => v as String),
       version: $checkedConvert(
-          json, 'version', (v) => _constraintFromString(v as String)),
+          json, 'version', (v) => _constraintFromString(v as String?)),
     );
     return val;
   });
@@ -23,12 +21,10 @@
 
 GitDependency _$GitDependencyFromJson(Map json) {
   return $checkedNew('GitDependency', json, () {
-    $checkKeys(json,
-        requiredKeys: const ['url'], disallowNullValues: const ['url']);
     final val = GitDependency(
       $checkedConvert(json, 'url', (v) => parseGitUri(v as String)),
-      $checkedConvert(json, 'ref', (v) => v as String),
-      $checkedConvert(json, 'path', (v) => v as String),
+      ref: $checkedConvert(json, 'ref', (v) => v as String?),
+      path: $checkedConvert(json, 'path', (v) => v as String?),
     );
     return val;
   });
@@ -41,9 +37,9 @@
         disallowNullValues: const ['hosted']);
     final val = HostedDependency(
       version: $checkedConvert(
-          json, 'version', (v) => _constraintFromString(v as String)),
-      hosted: $checkedConvert(
-          json, 'hosted', (v) => v == null ? null : HostedDetails.fromJson(v)),
+          json, 'version', (v) => _constraintFromString(v as String?)),
+      hosted: $checkedConvert(json, 'hosted',
+          (v) => v == null ? null : HostedDetails.fromJson(v as Object)),
     );
     return val;
   });
@@ -52,12 +48,10 @@
 HostedDetails _$HostedDetailsFromJson(Map json) {
   return $checkedNew('HostedDetails', json, () {
     $checkKeys(json,
-        allowedKeys: const ['name', 'url'],
-        requiredKeys: const ['name'],
-        disallowNullValues: const ['name', 'url']);
+        allowedKeys: const ['name', 'url'], disallowNullValues: const ['url']);
     final val = HostedDetails(
       $checkedConvert(json, 'name', (v) => v as String),
-      $checkedConvert(json, 'url', (v) => parseGitUri(v as String)),
+      $checkedConvert(json, 'url', (v) => parseGitUriOrNull(v as String?)),
     );
     return val;
   });
diff --git a/pkgs/pubspec_parse/lib/src/pubspec.dart b/pkgs/pubspec_parse/lib/src/pubspec.dart
index e3dbac1..b851486 100644
--- a/pkgs/pubspec_parse/lib/src/pubspec.dart
+++ b/pkgs/pubspec_parse/lib/src/pubspec.dart
@@ -17,53 +17,57 @@
   final String name;
 
   @JsonKey(fromJson: _versionFromString)
-  final Version version;
+  final Version? version;
 
-  final String description;
+  final String? description;
 
   /// This should be a URL pointing to the website for the package.
-  final String homepage;
+  final String? homepage;
 
   /// Specifies where to publish this package.
   ///
   /// Accepted values: `null`, `'none'` or an `http` or `https` URL.
   ///
   /// [More information](https://dart.dev/tools/pub/pubspec#publish_to).
-  final String publishTo;
+  final String? publishTo;
 
   /// Optional field to specify the source code repository of the package.
   /// Useful when a package has both a home page and a repository.
-  final Uri repository;
+  final Uri? repository;
 
   /// Optional field to a web page where developers can report new issues or
   /// view existing ones.
-  final Uri issueTracker;
+  final Uri? issueTracker;
 
   /// If there is exactly 1 value in [authors], returns it.
   ///
   /// If there are 0 or more than 1, returns `null`.
   @Deprecated(
-      'Here for completeness, but not recommended. Use `authors` instead.')
-  String get author {
+    'See https://dart.dev/tools/pub/pubspec#authorauthors',
+  )
+  String? get author {
     if (authors.length == 1) {
       return authors.single;
     }
     return null;
   }
 
+  @Deprecated(
+    'See https://dart.dev/tools/pub/pubspec#authorauthors',
+  )
   final List<String> authors;
-  final String documentation;
+  final String? documentation;
 
   @JsonKey(fromJson: _environmentMap)
-  final Map<String, VersionConstraint> environment;
+  final Map<String, VersionConstraint?>? environment;
 
-  @JsonKey(fromJson: parseDeps, nullable: false)
+  @JsonKey(fromJson: parseDeps)
   final Map<String, Dependency> dependencies;
 
-  @JsonKey(fromJson: parseDeps, nullable: false)
+  @JsonKey(fromJson: parseDeps)
   final Map<String, Dependency> devDependencies;
 
-  @JsonKey(fromJson: parseDeps, nullable: false)
+  @JsonKey(fromJson: parseDeps)
   final Map<String, Dependency> dependencyOverrides;
 
   /// Optional configuration specific to [Flutter](https://flutter.io/)
@@ -72,7 +76,7 @@
   /// May include
   /// [assets](https://flutter.io/docs/development/ui/assets-and-images)
   /// and other settings.
-  final Map<String, dynamic> flutter;
+  final Map<String, dynamic>? flutter;
 
   /// If [author] and [authors] are both provided, their values are combined
   /// with duplicates eliminated.
@@ -80,30 +84,38 @@
     this.name, {
     this.version,
     this.publishTo,
-    String author,
-    List<String> authors,
-    Map<String, VersionConstraint> environment,
+    @Deprecated(
+      'See https://dart.dev/tools/pub/pubspec#authorauthors',
+    )
+        String? author,
+    @Deprecated(
+      'See https://dart.dev/tools/pub/pubspec#authorauthors',
+    )
+        List<String>? authors,
+    Map<String, VersionConstraint?>? environment,
     this.homepage,
     this.repository,
     this.issueTracker,
     this.documentation,
     this.description,
-    Map<String, Dependency> dependencies,
-    Map<String, Dependency> devDependencies,
-    Map<String, Dependency> dependencyOverrides,
+    Map<String, Dependency>? dependencies,
+    Map<String, Dependency>? devDependencies,
+    Map<String, Dependency>? dependencyOverrides,
     this.flutter,
-  })  : authors = _normalizeAuthors(author, authors),
+  })  :
+        // ignore: deprecated_member_use_from_same_package
+        authors = _normalizeAuthors(author, authors),
         environment = environment ?? const {},
         dependencies = dependencies ?? const {},
         devDependencies = devDependencies ?? const {},
         dependencyOverrides = dependencyOverrides ?? const {} {
-    if (name == null || name.isEmpty) {
+    if (name.isEmpty) {
       throw ArgumentError.value(name, 'name', '"name" cannot be empty.');
     }
 
     if (publishTo != null && publishTo != 'none') {
       try {
-        final targetUri = Uri.parse(publishTo);
+        final targetUri = Uri.parse(publishTo!);
         if (!(targetUri.isScheme('http') || targetUri.isScheme('https'))) {
           throw const FormatException('Must be an http or https URL.');
         }
@@ -114,8 +126,6 @@
   }
 
   factory Pubspec.fromJson(Map json, {bool lenient = false}) {
-    lenient ??= false;
-
     if (lenient) {
       while (json.isNotEmpty) {
         // Attempting to remove top-level properties that cause parsing errors.
@@ -138,30 +148,26 @@
   ///
   /// When [lenient] is set, top-level property-parsing or type cast errors are
   /// ignored and `null` values are returned.
-  factory Pubspec.parse(String yaml, {sourceUrl, bool lenient = false}) {
-    lenient ??= false;
+  factory Pubspec.parse(String yaml, {Uri? sourceUrl, bool lenient = false}) =>
+      checkedYamlDecode(
+        yaml,
+        (map) => Pubspec.fromJson(map!, lenient: lenient),
+        sourceUrl: sourceUrl,
+      );
 
-    return checkedYamlDecode(
-        yaml, (map) => Pubspec.fromJson(map, lenient: lenient),
-        sourceUrl: sourceUrl);
-  }
-
-  static List<String> _normalizeAuthors(String author, List<String> authors) {
-    final value = <String>{};
-    if (author != null) {
-      value.add(author);
-    }
-    if (authors != null) {
-      value.addAll(authors);
-    }
+  static List<String> _normalizeAuthors(String? author, List<String>? authors) {
+    final value = <String>{
+      if (author != null) author,
+      ...?authors,
+    };
     return value.toList();
   }
 }
 
-Version _versionFromString(String input) =>
+Version? _versionFromString(String? input) =>
     input == null ? null : Version.parse(input);
 
-Map<String, VersionConstraint> _environmentMap(Map source) =>
+Map<String, VersionConstraint?>? _environmentMap(Map? source) =>
     source?.map((k, value) {
       final key = k as String;
       if (key == 'dart') {
@@ -176,7 +182,7 @@
         );
       }
 
-      VersionConstraint constraint;
+      VersionConstraint? constraint;
       if (value == null) {
         constraint = null;
       } else if (value is String) {
diff --git a/pkgs/pubspec_parse/lib/src/pubspec.g.dart b/pkgs/pubspec_parse/lib/src/pubspec.g.dart
index c29a221..72ed2a2 100644
--- a/pkgs/pubspec_parse/lib/src/pubspec.g.dart
+++ b/pkgs/pubspec_parse/lib/src/pubspec.g.dart
@@ -1,6 +1,6 @@
 // GENERATED CODE - DO NOT MODIFY BY HAND
 
-// ignore_for_file: prefer_expression_function_bodies
+// ignore_for_file: deprecated_member_use_from_same_package, lines_longer_than_80_chars, prefer_expression_function_bodies
 
 part of 'pubspec.dart';
 
@@ -13,30 +13,31 @@
     final val = Pubspec(
       $checkedConvert(json, 'name', (v) => v as String),
       version: $checkedConvert(
-          json, 'version', (v) => _versionFromString(v as String)),
-      publishTo: $checkedConvert(json, 'publish_to', (v) => v as String),
-      author: $checkedConvert(json, 'author', (v) => v as String),
+          json, 'version', (v) => _versionFromString(v as String?)),
+      publishTo: $checkedConvert(json, 'publish_to', (v) => v as String?),
+      author: $checkedConvert(json, 'author', (v) => v as String?),
       authors: $checkedConvert(json, 'authors',
-          (v) => (v as List)?.map((e) => e as String)?.toList()),
+          (v) => (v as List<dynamic>?)?.map((e) => e as String).toList()),
       environment: $checkedConvert(
-          json, 'environment', (v) => _environmentMap(v as Map)),
-      homepage: $checkedConvert(json, 'homepage', (v) => v as String),
+          json, 'environment', (v) => _environmentMap(v as Map?)),
+      homepage: $checkedConvert(json, 'homepage', (v) => v as String?),
       repository: $checkedConvert(
           json, 'repository', (v) => v == null ? null : Uri.parse(v as String)),
       issueTracker: $checkedConvert(json, 'issue_tracker',
           (v) => v == null ? null : Uri.parse(v as String)),
-      documentation: $checkedConvert(json, 'documentation', (v) => v as String),
-      description: $checkedConvert(json, 'description', (v) => v as String),
+      documentation:
+          $checkedConvert(json, 'documentation', (v) => v as String?),
+      description: $checkedConvert(json, 'description', (v) => v as String?),
       dependencies:
-          $checkedConvert(json, 'dependencies', (v) => parseDeps(v as Map)),
-      devDependencies:
-          $checkedConvert(json, 'dev_dependencies', (v) => parseDeps(v as Map)),
+          $checkedConvert(json, 'dependencies', (v) => parseDeps(v as Map?)),
+      devDependencies: $checkedConvert(
+          json, 'dev_dependencies', (v) => parseDeps(v as Map?)),
       dependencyOverrides: $checkedConvert(
-          json, 'dependency_overrides', (v) => parseDeps(v as Map)),
+          json, 'dependency_overrides', (v) => parseDeps(v as Map?)),
       flutter: $checkedConvert(
           json,
           'flutter',
-          (v) => (v as Map)?.map(
+          (v) => (v as Map?)?.map(
                 (k, e) => MapEntry(k as String, e),
               )),
     );
diff --git a/pkgs/pubspec_parse/pubspec.yaml b/pkgs/pubspec_parse/pubspec.yaml
index dee30ec..4179fea 100644
--- a/pkgs/pubspec_parse/pubspec.yaml
+++ b/pkgs/pubspec_parse/pubspec.yaml
@@ -2,28 +2,35 @@
 description: >-
   Simple package for parsing pubspec.yaml files with a type-safe API and rich
   error reporting.
-version: 0.1.8
+version: 1.0.0
 repository: https://github.com/dart-lang/pubspec_parse
 
 environment:
-  sdk: '>=2.7.0 <3.0.0'
+  sdk: '>=2.12.0-0 <3.0.0'
 
 dependencies:
-  checked_yaml: ^1.0.0
-  # Verified that no new features since 1.0.0 are used - be careful!
-  json_annotation: '>=1.0.0 <5.0.0'
-  pub_semver: '>=1.3.2 <3.0.0'
-  yaml: '>=2.1.12 <4.0.0'
+  checked_yaml: ^2.0.1
+  collection: ^1.15.0
+  json_annotation: ^4.0.0
+  pub_semver: ^2.0.0
+  yaml: ^3.0.0
 
 dev_dependencies:
   build_runner: ^1.0.0
   build_verify: ^1.0.0
-  json_serializable: ^3.0.0
+  json_serializable: ^4.0.2
   path: ^1.5.1
   pedantic: ^1.4.0
   # Needed because we are configuring `combining_builder`
   source_gen: ^0.9.5
   stack_trace: ^1.9.2
   test: ^1.0.0
-  test_descriptor: ^1.0.3
-  test_process: ^1.0.2
+  test_descriptor: ^2.0.0
+  test_process: ^2.0.0
+
+dependency_overrides:
+  # Need to update dependencies on these packages
+  build_config: 0.4.6
+  build_runner: any
+  checked_yaml: ^2.0.1
+  json_annotation: 4.0.0
diff --git a/pkgs/pubspec_parse/test/dependency_test.dart b/pkgs/pubspec_parse/test/dependency_test.dart
index 0afbf36..c6f2dc5 100644
--- a/pkgs/pubspec_parse/test/dependency_test.dart
+++ b/pkgs/pubspec_parse/test/dependency_test.dart
@@ -112,8 +112,8 @@
       'hosted': {'name': 'hosted_name', 'url': 'hosted_url'}
     });
     expect(dep.version.toString(), '^1.0.0');
-    expect(dep.hosted.name, 'hosted_name');
-    expect(dep.hosted.url.toString(), 'hosted_url');
+    expect(dep.hosted!.name, 'hosted_name');
+    expect(dep.hosted!.url.toString(), 'hosted_url');
     expect(dep.toString(), 'HostedDependency: ^1.0.0');
   });
 
@@ -149,16 +149,16 @@
     final dep = _dependency<HostedDependency>(
         {'version': '^1.0.0', 'hosted': 'hosted_name'});
     expect(dep.version.toString(), '^1.0.0');
-    expect(dep.hosted.name, 'hosted_name');
-    expect(dep.hosted.url, isNull);
+    expect(dep.hosted!.name, 'hosted_name');
+    expect(dep.hosted!.url, isNull);
     expect(dep.toString(), 'HostedDependency: ^1.0.0');
   });
 
   test('map w/ hosted as String', () {
     final dep = _dependency<HostedDependency>({'hosted': 'hosted_name'});
     expect(dep.version, VersionConstraint.any);
-    expect(dep.hosted.name, 'hosted_name');
-    expect(dep.hosted.url, isNull);
+    expect(dep.hosted!.name, 'hosted_name');
+    expect(dep.hosted!.url, isNull);
     expect(dep.toString(), 'HostedDependency: any');
   });
 
@@ -186,7 +186,7 @@
   test('without version', () {
     final dep = _dependency<SdkDependency>({'sdk': 'flutter'});
     expect(dep.sdk, 'flutter');
-    expect(dep.version, isNull);
+    expect(dep.version, VersionConstraint.any);
     expect(dep.toString(), 'SdkDependency: flutter');
   });
 
@@ -202,10 +202,12 @@
     _expectThrows(
       {'sdk': null},
       r'''
-line 5, column 4: These keys had `null` values, which is not allowed: [sdk]
+line 5, column 11: Unsupported value for "sdk". type 'Null' is not a subtype of type 'String' in type cast

-5 │    "sdk": null
-  │    ^^^^^
+5 │      "sdk": null
+  │ ┌───────────^
+6 │ │   }
+  │ └──^
   ╵''',
     );
   });
@@ -304,12 +306,15 @@
   });
 
   test('git - empty map', () {
-    _expectThrows({'git': {}}, r'''
-line 5, column 11: Required keys are missing: url.
+    _expectThrows(
+      {'git': {}},
+      r'''
+line 5, column 11: Missing key "url". type 'Null' is not a subtype of type 'String' in type cast

 5 │    "git": {}
   │           ^^
-  ╵''');
+  ╵''',
+    );
   });
 
   test('git - null url', () {
@@ -318,10 +323,12 @@
         'git': {'url': null}
       },
       r'''
-line 6, column 5: These keys had `null` values, which is not allowed: [url]
+line 6, column 12: Unsupported value for "url". type 'Null' is not a subtype of type 'String' in type cast

-6 │     "url": null
-  │     ^^^^^
+6 │       "url": null
+  │ ┌────────────^
+7 │ │    }
+  │ └───^
   ╵''',
     );
   });
@@ -402,7 +409,10 @@
   }, expectedError);
 }
 
-T _dependency<T extends Dependency>(Object content, {bool skipTryPub = false}) {
+T _dependency<T extends Dependency>(
+  Object? content, {
+  bool skipTryPub = false,
+}) {
   final value = parse({
     ...defaultPubspec,
     'dependencies': {'dep': content}
diff --git a/pkgs/pubspec_parse/test/ensure_build_test.dart b/pkgs/pubspec_parse/test/ensure_build_test.dart
index d64fa4f..a75d1bd 100644
--- a/pkgs/pubspec_parse/test/ensure_build_test.dart
+++ b/pkgs/pubspec_parse/test/ensure_build_test.dart
@@ -2,6 +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.9
+
 @TestOn('vm')
 @Tags(['presubmit-only'])
 import 'package:build_verify/build_verify.dart';
diff --git a/pkgs/pubspec_parse/test/parse_test.dart b/pkgs/pubspec_parse/test/parse_test.dart
index 179d0d8..52df370 100644
--- a/pkgs/pubspec_parse/test/parse_test.dart
+++ b/pkgs/pubspec_parse/test/parse_test.dart
@@ -2,9 +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.
 
-// TODO(kevmoo) Remove when github.com/dart-lang/sdk/commit/dac5a56422 lands
-// in a shipped SDK.
-// ignore_for_file: deprecated_member_use
+// ignore_for_file: deprecated_member_use_from_same_package
 library parse_test;
 
 import 'package:pub_semver/pub_semver.dart';
@@ -20,7 +18,6 @@
     expect(value.publishTo, isNull);
     expect(value.description, isNull);
     expect(value.homepage, isNull);
-    // ignore: deprecated_member_use_from_same_package
     expect(value.author, isNull);
     expect(value.authors, isEmpty);
     expect(
@@ -56,7 +53,6 @@
     expect(value.publishTo, 'none');
     expect(value.description, 'description');
     expect(value.homepage, 'homepage');
-    // ignore: deprecated_member_use_from_same_package
     expect(value.author, 'name@example.com');
     expect(value.authors, ['name@example.com']);
     expect(value.environment, hasLength(1));
@@ -86,7 +82,7 @@
   group('publish_to', () {
     for (var entry in {
       42: r'''
-line 3, column 16: Unsupported value for "publish_to". type 'int' is not a subtype of type 'String' in type cast
+line 3, column 16: Unsupported value for "publish_to". type 'int' is not a subtype of type 'String?' in type cast

 3 │  "publish_to": 42
   │                ^^
@@ -141,7 +137,6 @@
         ...defaultPubspec,
         'author': 'name@example.com',
       });
-      // ignore: deprecated_member_use_from_same_package
       expect(value.author, 'name@example.com');
       expect(value.authors, ['name@example.com']);
     });
@@ -151,7 +146,6 @@
         ...defaultPubspec,
         'authors': ['name@example.com']
       });
-      // ignore: deprecated_member_use_from_same_package
       expect(value.author, 'name@example.com');
       expect(value.authors, ['name@example.com']);
     });
@@ -161,7 +155,6 @@
         ...defaultPubspec,
         'authors': ['name@example.com', 'name2@example.com']
       });
-      // ignore: deprecated_member_use_from_same_package
       expect(value.author, isNull);
       expect(value.authors, ['name@example.com', 'name2@example.com']);
     });
@@ -172,7 +165,6 @@
         'author': 'name@example.com',
         'authors': ['name2@example.com']
       });
-      // ignore: deprecated_member_use_from_same_package
       expect(value.author, isNull);
       expect(value.authors, ['name@example.com', 'name2@example.com']);
     });
@@ -183,7 +175,6 @@
         'author': 'name@example.com',
         'authors': ['name@example.com', 'name@example.com']
       });
-      // ignore: deprecated_member_use_from_same_package
       expect(value.author, 'name@example.com');
       expect(value.authors, ['name@example.com']);
     });
@@ -230,12 +221,39 @@
     });
 
     test('missing name', () {
-      expectParseThrows({}, r'''
-line 1, column 1: "name" cannot be empty.
+      expectParseThrows(
+        {},
+        r'''
+line 1, column 1: Missing key "name". type 'Null' is not a subtype of type 'String' in type cast

 1 │ {}
   │ ^^
-  ╵''');
+  ╵''',
+      );
+    });
+
+    test('null name value', () {
+      expectParseThrows(
+        {'name': null},
+        r'''
+line 2, column 10: Unsupported value for "name". type 'Null' is not a subtype of type 'String' in type cast
+  ╷
+2 │  "name": null
+  │          ^^^^
+  ╵''',
+      );
+    });
+
+    test('empty name value', () {
+      expectParseThrows(
+        {'name': ''},
+        r'''
+line 2, column 10: Unsupported value for "name". "name" cannot be empty.
+  ╷
+2 │  "name": ""
+  │          ^^
+  ╵''',
+      );
     });
 
     test('"dart" is an invalid environment key', () {
@@ -359,7 +377,7 @@
       expectParseThrows(
         {},
         r'''
-line 1, column 1: "name" cannot be empty.
+line 1, column 1: Missing key "name". type 'Null' is not a subtype of type 'String' in type cast

 1 │ {}
   │ ^^
diff --git a/pkgs/pubspec_parse/test/test_utils.dart b/pkgs/pubspec_parse/test/test_utils.dart
index ac5abba..b66adba 100644
--- a/pkgs/pubspec_parse/test/test_utils.dart
+++ b/pkgs/pubspec_parse/test/test_utils.dart
@@ -18,7 +18,7 @@
   'environment': {'sdk': '>=2.7.0 <3.0.0'},
 };
 
-String _encodeJson(Object input) =>
+String _encodeJson(Object? input) =>
     const JsonEncoder.withIndent(' ').convert(input);
 
 Matcher _throwsParsedYamlException(String prettyValue) =>
@@ -31,10 +31,10 @@
 
 void _printDebugParsedYamlException(ParsedYamlException e) {
   var innerError = e.innerError;
-  StackTrace innerStack;
+  StackTrace? innerStack;
 
   if (innerError is CheckedFromJsonException) {
-    final cfje = innerError as CheckedFromJsonException;
+    final cfje = innerError;
 
     if (cfje.innerError != null) {
       innerError = cfje.innerError;
@@ -56,18 +56,14 @@
 }
 
 Pubspec parse(
-  Object content, {
+  Object? content, {
   bool quietOnError = false,
   bool skipTryPub = false,
   bool lenient = false,
 }) {
-  quietOnError ??= false;
-  skipTryPub ??= false;
-  lenient ??= false;
-
   final encoded = _encodeJson(content);
 
-  ProcResult pubResult;
+  ProcResult? pubResult;
   if (!skipTryPub) {
     pubResult = waitFor(tryPub(encoded));
     expect(pubResult, isNotNull);
@@ -78,7 +74,7 @@
 
     if (pubResult != null) {
       addTearDown(() {
-        expect(pubResult.cleanParse, isTrue,
+        expect(pubResult!.cleanParse, isTrue,
             reason:
                 'On success, parsing from the pub client should also succeed.');
       });
@@ -87,7 +83,7 @@
   } catch (e) {
     if (pubResult != null) {
       addTearDown(() {
-        expect(pubResult.cleanParse, isFalse,
+        expect(pubResult!.cleanParse, isFalse,
             reason:
                 'On failure, parsing from the pub client should also fail.');
       });
@@ -102,7 +98,7 @@
 }
 
 void expectParseThrows(
-  Object content,
+  Object? content,
   String expectedError, {
   bool skipTryPub = false,
   bool lenient = false,