Match hosted shorthand syntax from Dart 2.15 (dart-lang/pubspec_parse#74)

* Match hosted shorthand syntax from Dart 2.15
diff --git a/pkgs/pubspec_parse/CHANGELOG.md b/pkgs/pubspec_parse/CHANGELOG.md
index 2601ee4..8f2d288 100644
--- a/pkgs/pubspec_parse/CHANGELOG.md
+++ b/pkgs/pubspec_parse/CHANGELOG.md
@@ -1,3 +1,12 @@
+## 1.2.0-dev
+
+- Update `HostedDetails` to reflect how `hosted` dependencies are parsed in
+  Dart 2.15:
+   - Add `HostedDetails.declaredName` as the (optional) `name` property in a 
+     `hosted` block.
+   - `HostedDetails.name` now falls back to the name of the dependency if no
+      name is declared in the block.
+
 ## 1.1.1-dev
 
 - Require Dart SDK >= 2.14.0
diff --git a/pkgs/pubspec_parse/lib/src/dependency.dart b/pkgs/pubspec_parse/lib/src/dependency.dart
index f8d3ecd..8b59f73 100644
--- a/pkgs/pubspec_parse/lib/src/dependency.dart
+++ b/pkgs/pubspec_parse/lib/src/dependency.dart
@@ -14,7 +14,7 @@
       final key = k as String;
       Dependency? value;
       try {
-        value = _fromJson(v);
+        value = _fromJson(v, k);
       } on CheckedFromJsonException catch (e) {
         if (e.map is! YamlMap) {
           // This is likely a "synthetic" map created from a String value
@@ -40,7 +40,7 @@
 const _sourceKeys = ['sdk', 'git', 'path', 'hosted'];
 
 /// Returns `null` if the data could not be parsed.
-Dependency? _fromJson(Object? data) {
+Dependency? _fromJson(Object? data, String name) {
   if (data is String || data == null) {
     return _$HostedDependencyFromJson({'version': data});
   }
@@ -82,7 +82,9 @@
           case 'sdk':
             return _$SdkDependencyFromJson(data);
           case 'hosted':
-            return _$HostedDependencyFromJson(data);
+            final hosted = _$HostedDependencyFromJson(data);
+            hosted.hosted?._nameOfPackage = name;
+            return hosted;
         }
         throw StateError('There is a bug in pubspec_parse.');
       });
@@ -211,16 +213,30 @@
 
 @JsonSerializable(disallowUnrecognizedKeys: true)
 class HostedDetails {
-  final String name;
+  /// The name of the target dependency as declared in a `hosted` block.
+  ///
+  /// This may be null if no explicit name is present, for instance because the
+  /// hosted dependency was declared as a string (`hosted: pub.example.org`).
+  @JsonKey(name: 'name')
+  final String? declaredName;
 
   @JsonKey(fromJson: parseGitUriOrNull, disallowNullValue: true)
   final Uri? url;
 
-  HostedDetails(this.name, this.url);
+  @JsonKey(ignore: true)
+  String? _nameOfPackage;
+
+  /// The name of this package on the package repository.
+  ///
+  /// If this hosted block has a [declaredName], that one will be used.
+  /// Otherwise, the name will be inferred from the surrounding package name.
+  String get name => declaredName ?? _nameOfPackage!;
+
+  HostedDetails(this.declaredName, this.url);
 
   factory HostedDetails.fromJson(Object data) {
     if (data is String) {
-      data = {'name': data};
+      data = {'url': data};
     }
 
     if (data is Map) {
diff --git a/pkgs/pubspec_parse/lib/src/dependency.g.dart b/pkgs/pubspec_parse/lib/src/dependency.g.dart
index 4e9d399..1a504f1 100644
--- a/pkgs/pubspec_parse/lib/src/dependency.g.dart
+++ b/pkgs/pubspec_parse/lib/src/dependency.g.dart
@@ -63,9 +63,10 @@
           disallowNullValues: const ['url'],
         );
         final val = HostedDetails(
-          $checkedConvert('name', (v) => v as String),
+          $checkedConvert('name', (v) => v as String?),
           $checkedConvert('url', (v) => parseGitUriOrNull(v as String?)),
         );
         return val;
       },
+      fieldKeyMap: const {'declaredName': 'name'},
     );
diff --git a/pkgs/pubspec_parse/pubspec.yaml b/pkgs/pubspec_parse/pubspec.yaml
index d19201e..90ae1fe 100644
--- a/pkgs/pubspec_parse/pubspec.yaml
+++ b/pkgs/pubspec_parse/pubspec.yaml
@@ -2,7 +2,7 @@
 description: >-
   Simple package for parsing pubspec.yaml files with a type-safe API and rich
   error reporting.
-version: 1.1.1-dev
+version: 1.2.0-dev
 repository: https://github.com/dart-lang/pubspec_parse
 
 environment:
diff --git a/pkgs/pubspec_parse/test/dependency_test.dart b/pkgs/pubspec_parse/test/dependency_test.dart
index 20053a8..da7badc 100644
--- a/pkgs/pubspec_parse/test/dependency_test.dart
+++ b/pkgs/pubspec_parse/test/dependency_test.dart
@@ -120,6 +120,21 @@
     expect(dep.toString(), 'HostedDependency: ^1.0.0');
   });
 
+  test('map /w hosted as a map without name', () {
+    final dep = _dependency<HostedDependency>(
+      {
+        'version': '^1.0.0',
+        'hosted': {'url': 'https://hosted_url'}
+      },
+      skipTryPub: true, // todo: Unskip once pub supports this syntax
+    );
+    expect(dep.version.toString(), '^1.0.0');
+    expect(dep.hosted!.declaredName, isNull);
+    expect(dep.hosted!.name, 'dep');
+    expect(dep.hosted!.url.toString(), 'https://hosted_url');
+    expect(dep.toString(), 'HostedDependency: ^1.0.0');
+  });
+
   test('map w/ bad version value', () {
     _expectThrows(
       {
@@ -153,19 +168,22 @@
 
   test('map w/ version and hosted as String', () {
     final dep = _dependency<HostedDependency>(
-      {'version': '^1.0.0', 'hosted': 'hosted_name'},
+      {'version': '^1.0.0', 'hosted': 'hosted_url'},
+      skipTryPub: true, // todo: Unskip once put supports this
     );
     expect(dep.version.toString(), '^1.0.0');
-    expect(dep.hosted!.name, 'hosted_name');
-    expect(dep.hosted!.url, isNull);
+    expect(dep.hosted!.declaredName, isNull);
+    expect(dep.hosted!.name, 'dep');
+    expect(dep.hosted!.url, Uri.parse('hosted_url'));
     expect(dep.toString(), 'HostedDependency: ^1.0.0');
   });
 
   test('map w/ hosted as String', () {
-    final dep = _dependency<HostedDependency>({'hosted': 'hosted_name'});
+    final dep = _dependency<HostedDependency>({'hosted': 'hosted_url'});
     expect(dep.version, VersionConstraint.any);
-    expect(dep.hosted!.name, 'hosted_name');
-    expect(dep.hosted!.url, isNull);
+    expect(dep.hosted!.declaredName, isNull);
+    expect(dep.hosted!.name, 'dep');
+    expect(dep.hosted!.url, Uri.parse('hosted_url'));
     expect(dep.toString(), 'HostedDependency: any');
   });