Allow relative path-dependencies from git dependencies

diff --git a/lib/src/command/add.dart b/lib/src/command/add.dart
index 07f1c36..16cfa97 100644
--- a/lib/src/command/add.dart
+++ b/lib/src/command/add.dart
@@ -26,6 +26,7 @@
 import '../source/git.dart';
 import '../source/hosted.dart';
 import '../source/path.dart';
+import '../source/root.dart';
 import '../utils.dart';
 
 /// Handles the `add` pub command. Adds a dependency to `pubspec.yaml` and gets
@@ -272,6 +273,7 @@
             location: Uri.parse(entrypoint.pubspecPath),
             overridesFileContents: overridesFileContents,
             overridesLocation: Uri.file(overridesPath),
+            containingDescription: RootDescription(entrypoint.rootDir),
           ),
         )
         .acquireDependencies(
@@ -547,7 +549,11 @@
         PathDescription(p.absolute(path), p.isRelative(path)),
       );
     } else if (argResults.sdk != null) {
-      ref = cache.sdk.parseRef(packageName, argResults.sdk);
+      ref = cache.sdk.parseRef(
+        packageName,
+        argResults.sdk,
+        containingDescription: RootDescription(p.current),
+      );
     } else {
       ref = PackageRef(
         packageName,
@@ -634,7 +640,7 @@
             },
             cache.sources,
             // Resolve relative paths relative to current, not where the pubspec.yaml is.
-            location: p.toUri(p.join(p.current, 'descriptor')),
+            containingDescription: RootDescription(p.current),
           );
         } on FormatException catch (e) {
           usageException('Failed parsing package specification: ${e.message}');
diff --git a/lib/src/command/dependency_services.dart b/lib/src/command/dependency_services.dart
index f095722..5d66468 100644
--- a/lib/src/command/dependency_services.dart
+++ b/lib/src/command/dependency_services.dart
@@ -29,6 +29,7 @@
 import '../solver/version_solver.dart';
 import '../source/git.dart';
 import '../source/hosted.dart';
+import '../source/root.dart';
 import '../system_cache.dart';
 import '../utils.dart';
 
@@ -450,6 +451,7 @@
               updatedPubspec,
               cache.sources,
               location: toUri(entrypoint.pubspecPath),
+              containingDescription: RootDescription(entrypoint.rootDir),
             ),
             entrypoint.rootDir,
             entrypoint.root.workspaceChildren,
diff --git a/lib/src/command/lish.dart b/lib/src/command/lish.dart
index f6ca1a4..f716b1b 100644
--- a/lib/src/command/lish.dart
+++ b/lib/src/command/lish.dart
@@ -8,6 +8,7 @@
 import 'dart:typed_data';
 
 import 'package:http/http.dart' as http;
+import 'package:path/path.dart' as p;
 
 import '../ascii_tree.dart' as tree;
 import '../authentication/client.dart';
@@ -22,6 +23,7 @@
 import '../pubspec.dart';
 import '../solver/type.dart';
 import '../source/hosted.dart' show validateAndNormalizeHostedUrl;
+import '../source/root.dart';
 import '../utils.dart';
 import '../validator.dart';
 
@@ -346,6 +348,7 @@
           ),
         ),
         cache.sources,
+        containingDescription: RootDescription(p.dirname(archive)),
       );
     } on FormatException catch (e) {
       dataError('Failed to read pubspec.yaml from archive: ${e.message}');
diff --git a/lib/src/command/unpack.dart b/lib/src/command/unpack.dart
index 64f93b3..ef7df99 100644
--- a/lib/src/command/unpack.dart
+++ b/lib/src/command/unpack.dart
@@ -16,6 +16,7 @@
 import '../sdk.dart';
 import '../solver/type.dart';
 import '../source/hosted.dart';
+import '../source/root.dart';
 import '../utils.dart';
 
 class UnpackCommand extends PubCommand {
@@ -182,6 +183,7 @@
           cache.sources,
           // Resolve relative paths relative to current, not where the pubspec.yaml is.
           location: p.toUri(p.join(p.current, 'descriptor')),
+          containingDescription: RootDescription('.'),
         );
       } on FormatException catch (e) {
         usageException('Failed parsing package specification: ${e.message}');
diff --git a/lib/src/command/upgrade.dart b/lib/src/command/upgrade.dart
index cddd6c7..5fb176a 100644
--- a/lib/src/command/upgrade.dart
+++ b/lib/src/command/upgrade.dart
@@ -21,6 +21,7 @@
 import '../sdk.dart';
 import '../solver.dart';
 import '../source/hosted.dart';
+import '../source/root.dart';
 import '../utils.dart';
 
 /// Handles the `upgrade` pub command.
@@ -399,6 +400,7 @@
       location: Uri.parse(entrypoint.pubspecPath),
       overridesFileContents: overridesFileContents,
       overridesLocation: Uri.file(overridesPath),
+      containingDescription: RootDescription(entrypoint.rootDir),
     );
   }
 
diff --git a/lib/src/global_packages.dart b/lib/src/global_packages.dart
index cdd281c..fe82886 100644
--- a/lib/src/global_packages.dart
+++ b/lib/src/global_packages.dart
@@ -27,6 +27,7 @@
 import 'source/git.dart';
 import 'source/hosted.dart';
 import 'source/path.dart';
+import 'source/root.dart';
 import 'system_cache.dart';
 import 'utils.dart';
 
@@ -113,7 +114,7 @@
           if (path != null) 'path': path,
           if (ref != null) 'ref': ref,
         },
-        containingDir: '.',
+        containingDescription: RootDescription(p.current),
       );
     } on FormatException catch (e) {
       throw ApplicationException(e.message);
diff --git a/lib/src/package.dart b/lib/src/package.dart
index 286fc90..954b038 100644
--- a/lib/src/package.dart
+++ b/lib/src/package.dart
@@ -14,6 +14,7 @@
 import 'log.dart' as log;
 import 'package_name.dart';
 import 'pubspec.dart';
+import 'source/root.dart';
 import 'system_cache.dart';
 import 'utils.dart';
 
@@ -134,6 +135,7 @@
       sources,
       expectedName: name,
       allowOverridesFile: withPubspecOverrides,
+      containingDescription: RootDescription(dir),
     );
     final workspacePackages = pubspec.workspace
         .map((e) => Package.load(null, p.join(dir, e), sources))
diff --git a/lib/src/pubspec.dart b/lib/src/pubspec.dart
index 2bf9250..2811545 100644
--- a/lib/src/pubspec.dart
+++ b/lib/src/pubspec.dart
@@ -14,6 +14,8 @@
 import 'package_name.dart';
 import 'pubspec_parse.dart';
 import 'sdk.dart';
+import 'source.dart';
+import 'source/root.dart';
 import 'system_cache.dart';
 
 export 'pubspec_parse.dart' hide PubspecBase;
@@ -58,11 +60,9 @@
   /// This will be null if this was created using [Pubspec] or [Pubspec.empty].
   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;
+  /// It is used to resolve relative paths. And to resolve path-descriptions
+  /// from a git dependency as git-descriptions.
+  final Description _containingDescription;
 
   /// Directories of packages that should resolve together with this package.
   late List<String> workspace = () {
@@ -118,7 +118,7 @@
         _sources,
         languageVersion,
         _packageName,
-        _location,
+        _containingDescription,
       );
 
   Map<String, PackageRange>? _dependencies;
@@ -131,7 +131,7 @@
         _sources,
         languageVersion,
         _packageName,
-        _location,
+        _containingDescription,
       );
 
   Map<String, PackageRange>? _devDependencies;
@@ -163,7 +163,7 @@
           _sources,
           languageVersion,
           _packageName,
-          _location,
+          _containingDescription,
           fileType: _FileType.pubspecOverrides,
         );
       }
@@ -174,7 +174,7 @@
       _sources,
       languageVersion,
       _packageName,
-      _location,
+      _containingDescription,
     );
   }
 
@@ -263,6 +263,7 @@
     SourceRegistry sources, {
     String? expectedName,
     bool allowOverridesFile = false,
+    required Description containingDescription,
   }) {
     var pubspecPath = path.join(packageDir, pubspecYamlFilename);
     var overridesPath = path.join(packageDir, pubspecOverridesFilename);
@@ -287,6 +288,7 @@
       location: path.toUri(pubspecPath),
       overridesFileContents: overridesFileContents,
       overridesLocation: path.toUri(overridesPath),
+      containingDescription: containingDescription,
     );
   }
 
@@ -316,6 +318,9 @@
         _sources = sources ??
             ((String? name) => throw StateError('No source registry given')),
         _overridesFileFields = null,
+        // This is a dummy value.
+        // Dependencies should already be resolved, so we never need to do relative resolutions.
+        _containingDescription = RootDescription('.'),
         super(
           fields == null ? YamlMap() : YamlMap.wrap(fields),
           name: name,
@@ -335,11 +340,13 @@
     YamlMap? overridesFields,
     String? expectedName,
     Uri? location,
+    required Description containingDescription,
   })  : _overridesFileFields = overridesFields,
         _includeDefaultSdkConstraint = true,
         _givenSdkConstraints = null,
         dependencyOverridesFromOverridesFile = overridesFields != null &&
             overridesFields.containsKey('dependency_overrides'),
+        _containingDescription = containingDescription,
         super(
           fields is YamlMap
               ? fields
@@ -368,6 +375,7 @@
     Uri? location,
     String? overridesFileContents,
     Uri? overridesLocation,
+    required Description containingDescription,
   }) {
     late final YamlMap pubspecMap;
     YamlMap? overridesFileMap;
@@ -388,6 +396,7 @@
       overridesFields: overridesFileMap,
       expectedName: expectedName,
       location: location,
+      containingDescription: containingDescription,
     );
   }
 
@@ -484,7 +493,7 @@
   SourceRegistry sources,
   LanguageVersion languageVersion,
   String? packageName,
-  Uri? location, {
+  Description containingDescription, {
   _FileType fileType = _FileType.pubspec,
 }) {
   var dependencies = <String, PackageRange>{};
@@ -568,15 +577,10 @@
         'description',
         descriptionNode?.span,
         () {
-          String? pubspecDir;
-          if (location != null && _isFileUri(location)) {
-            pubspecDir = path.dirname(path.fromUri(location));
-          }
-
           return sources(sourceName).parseRef(
             name,
             descriptionNode?.value,
-            containingDir: pubspecDir,
+            containingDescription: containingDescription,
             languageVersion: languageVersion,
           );
         },
@@ -592,12 +596,6 @@
   return dependencies;
 }
 
-/// Returns whether [uri] is a file URI.
-///
-/// This is slightly more complicated than just checking if the scheme is
-/// 'file', since relative URIs also refer to the filesystem on the VM.
-bool _isFileUri(Uri uri) => uri.scheme == 'file' || uri.scheme == '';
-
 /// Parses [node] to a [VersionConstraint].
 ///
 /// If or [defaultUpperBoundConstraint] is specified then it will be set as the
diff --git a/lib/src/source.dart b/lib/src/source.dart
index 18ea453..67891ba 100644
--- a/lib/src/source.dart
+++ b/lib/src/source.dart
@@ -65,10 +65,8 @@
   /// hosted dependencies like `foo:` or `foo: ^1.2.3`, the [description] may
   /// also be `null`.
   ///
-  /// [containingDir] is the path to the directory of the pubspec where this
-  /// description appears. It may be `null` if the description is coming from
-  /// some in-memory source (such as pulling down a pubspec from
-  /// pub.dev).
+  /// [containingDescription] describes the location of the pubspec where this
+  /// description appears.
   ///
   /// [languageVersion] is the minimum Dart version parsed from the pubspec's
   /// `environment` field. Source implementations may use this parameter to only
@@ -81,7 +79,7 @@
   PackageRef parseRef(
     String name,
     Object? description, {
-    String? containingDir,
+    required Description containingDescription,
     required LanguageVersion languageVersion,
   });
 
diff --git a/lib/src/source/cached.dart b/lib/src/source/cached.dart
index 933b08c..a3d84b4 100644
--- a/lib/src/source/cached.dart
+++ b/lib/src/source/cached.dart
@@ -29,7 +29,12 @@
   Future<Pubspec> doDescribe(PackageId id, SystemCache cache) async {
     var packageDir = getDirectoryInCache(id, cache);
     if (fileExists(path.join(packageDir, 'pubspec.yaml'))) {
-      return Pubspec.load(packageDir, cache.sources, expectedName: id.name);
+      return Pubspec.load(
+        packageDir,
+        cache.sources,
+        expectedName: id.name,
+        containingDescription: id.description.description,
+      );
     }
 
     return await describeUncached(id, cache);
diff --git a/lib/src/source/git.dart b/lib/src/source/git.dart
index 8c69329..99bf59b 100644
--- a/lib/src/source/git.dart
+++ b/lib/src/source/git.dart
@@ -21,6 +21,8 @@
 import '../system_cache.dart';
 import '../utils.dart';
 import 'cached.dart';
+import 'path.dart';
+import 'root.dart';
 
 /// A package source that gets packages from Git repos.
 class GitSource extends CachedSource {
@@ -35,7 +37,7 @@
   PackageRef parseRef(
     String name,
     Object? description, {
-    String? containingDir,
+    Description? containingDescription,
     LanguageVersion? languageVersion,
   }) {
     String url;
@@ -70,6 +72,12 @@
       path = descriptionPath;
     }
 
+    final containingDir = switch (containingDescription) {
+      RootDescription(path: final path) => path,
+      PathDescription(path: final path) => path,
+      _ => null,
+    };
+
     return PackageRef(
       name,
       GitDescription(
@@ -234,6 +242,7 @@
       return Pubspec.parse(
         await _showFileAtRevision(resolvedDescription, 'pubspec.yaml', cache),
         cache.sources,
+        containingDescription: description,
       ).name;
     });
   }
@@ -338,6 +347,7 @@
       ),
       cache.sources,
       expectedName: ref.name,
+      containingDescription: ref.description,
     );
   }
 
@@ -755,7 +765,7 @@
   /// Represented as a relative url.
   final String path;
 
-  GitDescription._({
+  GitDescription.raw({
     required this.url,
     required this.relative,
     required String? ref,
@@ -770,7 +780,7 @@
     required String? containingDir,
   }) {
     final validatedUrl = GitSource._validatedUrl(url, containingDir);
-    return GitDescription._(
+    return GitDescription.raw(
       url: validatedUrl.url,
       relative: validatedUrl.wasRelative,
       ref: ref,
@@ -816,7 +826,7 @@
         other.path == path;
   }
 
-  GitDescription withRef(String newRef) => GitDescription._(
+  GitDescription withRef(String newRef) => GitDescription.raw(
         url: url,
         relative: relative,
         ref: newRef,
diff --git a/lib/src/source/hosted.dart b/lib/src/source/hosted.dart
index 608d43b..0439faf 100644
--- a/lib/src/source/hosted.dart
+++ b/lib/src/source/hosted.dart
@@ -31,6 +31,7 @@
 import '../system_cache.dart';
 import '../utils.dart';
 import 'cached.dart';
+import 'root.dart';
 
 const contentHashesDocumentationUrl = 'https://dart.dev/go/content-hashes';
 
@@ -225,7 +226,7 @@
   PackageRef parseRef(
     String name,
     Object? description, {
-    String? containingDir,
+    required Description containingDescription,
     required LanguageVersion languageVersion,
   }) {
     return PackageRef(
@@ -400,6 +401,7 @@
         cache.sources,
         expectedName: ref.name,
         location: location,
+        containingDescription: description,
       );
       final archiveSha256 = map['archive_sha256'];
       if (archiveSha256 != null && archiveSha256 is! String) {
@@ -1584,7 +1586,14 @@
       }
       final Pubspec pubspec;
       try {
-        pubspec = Pubspec.load(tempDir, cache.sources);
+        pubspec = Pubspec.load(
+          tempDir,
+          cache.sources,
+          containingDescription:
+              // Dummy description.
+              // As we never use the dependencies, they don't need to be resolved.
+              RootDescription('.'),
+        );
         final errors = pubspec.dependencyErrors;
         if (errors.isNotEmpty) {
           throw errors.first;
diff --git a/lib/src/source/path.dart b/lib/src/source/path.dart
index f881b8e..e0451ef 100644
--- a/lib/src/source/path.dart
+++ b/lib/src/source/path.dart
@@ -14,6 +14,9 @@
 import '../pubspec.dart';
 import '../source.dart';
 import '../system_cache.dart';
+import 'git.dart';
+import 'hosted.dart';
+import 'root.dart';
 
 /// A package [Source] that gets packages from a given local file path.
 class PathSource extends Source {
@@ -60,7 +63,7 @@
   PackageRef parseRef(
     String name,
     Object? description, {
-    String? containingDir,
+    required Description containingDescription,
     LanguageVersion? languageVersion,
   }) {
     if (description is! String) {
@@ -69,21 +72,70 @@
     var dir = description;
     // Resolve the path relative to the containing file path, and remember
     // whether the original path was relative or absolute.
-    var isRelative = p.isRelative(description);
-    if (isRelative) {
-      // Relative paths coming from pubspecs that are not on the local file
-      // system aren't allowed. This can happen if a hosted or git dependency
-      // has a path dependency.
-      if (containingDir == null) {
+    var isRelative = p.isRelative(dir);
+
+    if (containingDescription is PathDescription) {
+      return PackageRef(
+        name,
+        PathDescription(
+          isRelative
+              ? p.join(p.absolute(containingDescription.path), dir)
+              : dir,
+          isRelative,
+        ),
+      );
+    } else if (containingDescription is RootDescription) {
+      return PackageRef(
+        name,
+        PathDescription(
+          p.normalize(
+            p.join(
+              p.absolute(containingDescription.path),
+              description,
+            ),
+          ),
+          isRelative,
+        ),
+      );
+    } else if (containingDescription is GitDescription) {
+      if (!isRelative) {
+        throw FormatException(
+          '"$description" is an absolute path, it can\'t be referenced from a git pubspec.',
+        );
+      }
+      final resolvedPath = p.url.joinAll([
+        containingDescription.path,
+        ...p.posix.split(dir),
+      ]);
+      if (!p.isWithin('.', resolvedPath)) {
+        throw FormatException(
+          'the path "$description" cannot refer outside the git repository $resolvedPath.',
+        );
+      }
+      return PackageRef(
+        name,
+        GitDescription.raw(
+          url: containingDescription.url,
+          relative: containingDescription.relative,
+          ref: containingDescription.ref,
+          path: p.normalize(
+            p.join(
+              containingDescription.path,
+              dir,
+            ),
+          ),
+        ),
+      );
+    } else if (containingDescription is HostedDescription) {
+      if (isRelative) {
         throw FormatException('"$description" is a relative path, but this '
             'isn\'t a local pubspec.');
       }
-
-      dir = p.normalize(
-        p.absolute(p.join(containingDir, description)),
-      );
+      return PackageRef(name, PathDescription(dir, false));
+    } else {
+      throw FormatException('"$description" is a path, but this '
+          'isn\'t a local pubspec.');
     }
-    return PackageRef(name, PathDescription(dir, isRelative));
   }
 
   @override
@@ -168,7 +220,12 @@
       throw ArgumentError('Wrong source');
     }
     var dir = _validatePath(ref.name, description);
-    return Pubspec.load(dir, cache.sources, expectedName: ref.name);
+    return Pubspec.load(
+      dir,
+      cache.sources,
+      containingDescription: description,
+      expectedName: ref.name,
+    );
   }
 
   @override
diff --git a/lib/src/source/root.dart b/lib/src/source/root.dart
index 0936e11..2513b4d 100644
--- a/lib/src/source/root.dart
+++ b/lib/src/source/root.dart
@@ -69,7 +69,7 @@
   PackageRef parseRef(
     String name,
     Object? description, {
-    String? containingDir,
+    required Description containingDescription,
     required LanguageVersion languageVersion,
   }) {
     throw UnsupportedError('Trying to parse a root package description.');
diff --git a/lib/src/source/sdk.dart b/lib/src/source/sdk.dart
index d37c13e..878d6d5 100644
--- a/lib/src/source/sdk.dart
+++ b/lib/src/source/sdk.dart
@@ -29,7 +29,7 @@
   PackageRef parseRef(
     String name,
     Object? description, {
-    String? containingDir,
+    required Description containingDescription,
     LanguageVersion? languageVersion,
   }) {
     if (description is! String) {
@@ -93,6 +93,7 @@
         _verifiedPackagePath(ref),
         cache.sources,
         expectedName: ref.name,
+        containingDescription: ref.description,
       );
 
   /// Returns the path for the given [ref].
diff --git a/lib/src/source/unknown.dart b/lib/src/source/unknown.dart
index 83f2164..0402662 100644
--- a/lib/src/source/unknown.dart
+++ b/lib/src/source/unknown.dart
@@ -37,7 +37,7 @@
   PackageRef parseRef(
     String name,
     Object? description, {
-    String? containingDir,
+    required Description containingDescription,
     LanguageVersion? languageVersion,
   }) =>
       PackageRef(name, UnknownDescription(description, this));
diff --git a/test/get/git/check_out_transitive_test.dart b/test/get/git/check_out_transitive_test.dart
index 76833c6..4fed9b6 100644
--- a/test/get/git/check_out_transitive_test.dart
+++ b/test/get/git/check_out_transitive_test.dart
@@ -4,6 +4,7 @@
 
 import 'package:path/path.dart' as p;
 import 'package:pub/src/exit_codes.dart' as exit_codes;
+import 'package:pub/src/exit_codes.dart';
 import 'package:test/test.dart';
 
 import '../../descriptor.dart' as d;
@@ -95,12 +96,44 @@
     );
   });
 
-  test('cannot have relative path dependencies transitively from Git',
+  test('can have relative path dependencies transitively from Git', () async {
+    ensureGit();
+
+    await d.git('foo.git', [
+      d.dir('foo', [
+        d.libPubspec(
+          'foo',
+          '1.0.0',
+          deps: {
+            'bar': {'path': '../bar'},
+          },
+        ),
+      ]),
+      d.dir('bar', [d.libPubspec('bar', '1.0.0')]),
+    ]).create();
+
+    await d.appDir(
+      dependencies: {
+        'foo': {
+          'git': {
+            'url': p
+                .toUri(p.absolute(d.sandbox, appPath, '../foo.git'))
+                .toString(),
+            'path': 'foo/',
+          },
+        },
+      },
+    ).create();
+
+    await pubGet();
+  });
+
+  test(
+      'cannot have relative path dependencies transitively from Git to outside the repo',
       () async {
     ensureGit();
 
     await d.git('foo.git', [
-      d.libDir('foo'),
       d.libPubspec(
         'foo',
         '1.0.0',
@@ -110,8 +143,7 @@
       ),
     ]).create();
 
-    await d
-        .dir('bar', [d.libDir('bar'), d.libPubspec('bar', '1.0.0')]).create();
+    await d.dir('bar', [d.libPubspec('bar', '1.0.0')]).create();
 
     await d.appDir(
       dependencies: {
@@ -123,10 +155,11 @@
     ).create();
 
     await pubGet(
+      exitCode: DATA,
       error: contains(
-        '"../bar" is a relative path, but this isn\'t a local pubspec.',
+        'Invalid description in the "foo" pubspec on the "bar" dependency: '
+        'the path "../bar" cannot refer outside the git repository',
       ),
-      exitCode: exit_codes.DATA,
     );
   });
 }
diff --git a/test/pubspec_test.dart b/test/pubspec_test.dart
index a545627..e714f2e 100644
--- a/test/pubspec_test.dart
+++ b/test/pubspec_test.dart
@@ -6,10 +6,12 @@
 
 import 'package:pub/src/exceptions.dart';
 import 'package:pub/src/pubspec.dart';
+import 'package:pub/src/source.dart';
 import 'package:pub/src/source/hosted.dart';
+import 'package:pub/src/source/root.dart';
 import 'package:pub/src/system_cache.dart';
 import 'package:pub_semver/pub_semver.dart';
-import 'package:test/test.dart';
+import 'package:test/test.dart' hide Description;
 
 void main() {
   group('parse()', () {
@@ -20,9 +22,10 @@
 
     void expectPubspecException(
       String contents,
-      void Function(Pubspec) fn, [
+      void Function(Pubspec) fn, {
       String? expectedContains,
-    ]) {
+      Description? containingDescription,
+    }) {
       var expectation = const TypeMatcher<SourceSpanApplicationException>();
       if (expectedContains != null) {
         expectation = expectation.having(
@@ -32,20 +35,33 @@
         );
       }
 
-      var pubspec = Pubspec.parse(contents, sources);
+      var pubspec = Pubspec.parse(
+        contents,
+        sources,
+        containingDescription: containingDescription ?? RootDescription('.'),
+      );
       expect(() => fn(pubspec), throwsA(expectation));
     }
 
     test("doesn't eagerly throw an error for an invalid field", () {
       // Shouldn't throw an error.
-      Pubspec.parse('version: not a semver', sources);
+      Pubspec.parse(
+        'version: not a semver',
+        sources,
+        containingDescription: RootDescription('.'),
+      );
     });
 
     test(
         "eagerly throws an error if the pubspec name doesn't match the "
         'expected name', () {
       expect(
-        () => Pubspec.parse('name: foo', sources, expectedName: 'bar'),
+        () => Pubspec.parse(
+          'name: foo',
+          sources,
+          expectedName: 'bar',
+          containingDescription: RootDescription('.'),
+        ),
         throwsPubspecException,
       );
     });
@@ -54,7 +70,12 @@
         "eagerly throws an error if the pubspec doesn't have a name and an "
         'expected name is passed', () {
       expect(
-        () => Pubspec.parse('{}', sources, expectedName: 'bar'),
+        () => Pubspec.parse(
+          '{}',
+          sources,
+          expectedName: 'bar',
+          containingDescription: RootDescription('.'),
+        ),
         throwsPubspecException,
       );
     });
@@ -70,6 +91,7 @@
     version: ">=1.2.3 <3.4.5"
 ''',
         sources,
+        containingDescription: RootDescription('.'),
       );
 
       var foo = pubspec.dependencies['foo']!;
@@ -90,6 +112,7 @@
     version: ">=1.2.3 <0.0.0"
 ''',
         sources,
+        containingDescription: RootDescription('.'),
       );
 
       var foo = pubspec.dependencies['foo']!;
@@ -103,6 +126,7 @@
 dependencies:
 ''',
         sources,
+        containingDescription: RootDescription('.'),
       );
 
       expect(pubspec.dependencies, isEmpty);
@@ -119,6 +143,7 @@
     version: ">=1.2.3 <3.4.5"
 ''',
         sources,
+        containingDescription: RootDescription('.'),
       );
 
       var foo = pubspec.devDependencies['foo']!;
@@ -134,6 +159,7 @@
 dev_dependencies:
 ''',
         sources,
+        containingDescription: RootDescription('.'),
       );
 
       expect(pubspec.devDependencies, isEmpty);
@@ -150,6 +176,7 @@
     version: ">=1.2.3 <3.4.5"
 ''',
         sources,
+        containingDescription: RootDescription('.'),
       );
 
       var foo = pubspec.dependencyOverrides['foo']!;
@@ -165,6 +192,7 @@
 dependency_overrides:
 ''',
         sources,
+        containingDescription: RootDescription('.'),
       );
 
       expect(pubspec.dependencyOverrides, isEmpty);
@@ -178,6 +206,7 @@
     unknown: blah
 ''',
         sources,
+        containingDescription: RootDescription('.'),
       );
 
       var foo = pubspec.dependencies['foo']!;
@@ -193,6 +222,7 @@
     version: 1.2.3
 ''',
         sources,
+        containingDescription: RootDescription('.'),
       );
 
       var foo = pubspec.dependencies['foo']!;
@@ -285,24 +315,28 @@
       expectPubspecException(
         'version: [2, 0, 0]',
         (pubspec) => pubspec.version,
-        '"version" field must be a string',
+        expectedContains: '"version" field must be a string',
       );
     });
 
     test('throws if version is malformed (looking like a double)', () {
       expectPubspecException(
-          'version: 2.1',
-          (pubspec) => pubspec.version,
-          '"version" field must have three numeric components: major, minor, '
-              'and patch. Instead of "2.1", consider "2.1.0"');
+        'version: 2.1',
+        (pubspec) => pubspec.version,
+        expectedContains:
+            '"version" field must have three numeric components: major, minor, '
+            'and patch. Instead of "2.1", consider "2.1.0"',
+      );
     });
 
     test('throws if version is malformed (looking like an int)', () {
       expectPubspecException(
-          'version: 2',
-          (pubspec) => pubspec.version,
-          '"version" field must have three numeric components: major, minor, '
-              'and patch. Instead of "2", consider "2.0.0"');
+        'version: 2',
+        (pubspec) => pubspec.version,
+        expectedContains:
+            '"version" field must have three numeric components: major, minor, '
+            'and patch. Instead of "2", consider "2.0.0"',
+      );
     });
 
     test('throws if version is not a version', () {
@@ -321,6 +355,7 @@
 workspace: ['a', 'b', 'c']
 ''',
           sources,
+          containingDescription: RootDescription('.'),
         ).workspace,
         ['a', 'b', 'c'],
       );
@@ -335,6 +370,7 @@
 resolution: workspace
 ''',
           sources,
+          containingDescription: RootDescription('.'),
         ).resolution,
         Resolution.workspace,
       );
@@ -359,6 +395,7 @@
 resolution: workspace
 ''',
           sources,
+          containingDescription: RootDescription('.'),
         ).name,
         'foo',
       );
@@ -416,6 +453,7 @@
 # See https://dart.dev/tools/pub/cmd for details
 ''',
         sources,
+        containingDescription: RootDescription('.'),
       );
       expect(pubspec.version, equals(Version.none));
       expect(pubspec.dependencies, isEmpty);
@@ -423,15 +461,18 @@
 
     test('throws a useful error for unresolvable path dependencies', () {
       expectPubspecException(
-          '''
+        '''
 name: pkg
 dependencies:
   from_path: {path: non_local_path}
 ''',
-          (pubspec) => pubspec.dependencies,
-          'Invalid description in the "pkg" pubspec on the "from_path" '
-              'dependency: "non_local_path" is a relative path, but this isn\'t a '
-              'local pubspec.');
+        containingDescription: HostedDescription('foo', 'https://pub.dev'),
+        (pubspec) => pubspec.dependencies,
+        expectedContains:
+            'Invalid description in the "pkg" pubspec on the "from_path" '
+            'dependency: "non_local_path" is a relative path, but this isn\'t a '
+            'local pubspec.',
+      );
     });
 
     group('source dependencies', () {
@@ -446,6 +487,7 @@
       name: bar
 ''',
           sources,
+          containingDescription: RootDescription('.'),
         );
 
         var foo = pubspec.dependencies['foo']!;
@@ -474,6 +516,7 @@
       url: https://example.org/pub/
 ''',
           sources,
+          containingDescription: RootDescription('.'),
         );
 
         var foo = pubspec.dependencies['foo']!;
@@ -501,6 +544,7 @@
     hosted: https://example.org/pub/
 ''',
           sources,
+          containingDescription: RootDescription('.'),
         );
 
         var foo = pubspec.dependencies['foo']!;
@@ -528,6 +572,7 @@
     hosted: bar
 ''',
           sources,
+          containingDescription: RootDescription('.'),
         );
 
         var foo = pubspec.dependencies['foo']!;
@@ -557,6 +602,7 @@
     hosted: https://example.org/pub/
 ''',
             sources,
+            containingDescription: RootDescription('.'),
           );
 
           expect(
@@ -577,6 +623,7 @@
   foo:
 ''',
           sources,
+          containingDescription: RootDescription('.'),
         );
 
         var foo = pubspec.dependencies['foo']!;
@@ -596,16 +643,18 @@
       group('throws without a min SDK constraint', () {
         test('and without a name', () {
           expectPubspecException(
-              '''
+            '''
 name: pkg
 dependencies:
   foo:
     hosted:
       url: https://example.org/pub/
 ''',
-              (pubspec) => pubspec.dependencies,
-              "The 'name' key must have a string value without a minimum Dart "
-                  'SDK constraint of 2.15.');
+            (pubspec) => pubspec.dependencies,
+            expectedContains:
+                "The 'name' key must have a string value without a minimum Dart "
+                'SDK constraint of 2.15.',
+          );
         });
 
         test(
@@ -619,7 +668,8 @@
     hosted: http://pub.example.org
 ''',
               (pubspec) => pubspec.dependencies,
-              'Using `hosted: <url>` is only supported with a minimum SDK constraint of 2.15.',
+              expectedContains:
+                  'Using `hosted: <url>` is only supported with a minimum SDK constraint of 2.15.',
             );
           },
         );
@@ -680,7 +730,11 @@
 
     group('environment', () {
       test('allows an omitted environment', () {
-        var pubspec = Pubspec.parse('name: testing', sources);
+        var pubspec = Pubspec.parse(
+          'name: testing',
+          sources,
+          containingDescription: RootDescription('.'),
+        );
         expect(
           pubspec.dartSdkConstraint.effectiveConstraint,
           VersionConstraint.parse('<2.0.0'),
@@ -691,7 +745,11 @@
       });
 
       test('default SDK constraint can be omitted with empty environment', () {
-        var pubspec = Pubspec.parse('', sources);
+        var pubspec = Pubspec.parse(
+          '',
+          sources,
+          containingDescription: RootDescription('.'),
+        );
         expect(
           pubspec.dartSdkConstraint.effectiveConstraint,
           VersionConstraint.parse('<2.0.0'),
@@ -708,6 +766,7 @@
     sdk: ">1.0.0"
   ''',
           sources,
+          containingDescription: RootDescription('.'),
         );
         expect(
           pubspec.dartSdkConstraint.effectiveConstraint,
@@ -726,6 +785,7 @@
     sdk: ">3.0.0"
   ''',
           sources,
+          containingDescription: RootDescription('.'),
         );
         expect(
           pubspec.sdkConstraints,
@@ -754,6 +814,7 @@
   fuchsia: ^5.6.7
 ''',
           sources,
+          containingDescription: RootDescription('.'),
         );
         expect(
           pubspec.sdkConstraints,
@@ -814,7 +875,11 @@
 
     group('publishTo', () {
       test('defaults to null if omitted', () {
-        var pubspec = Pubspec.parse('', sources);
+        var pubspec = Pubspec.parse(
+          '',
+          sources,
+          containingDescription: RootDescription('.'),
+        );
         expect(pubspec.publishTo, isNull);
       });
 
@@ -831,6 +896,7 @@
 publish_to: http://example.com
 ''',
           sources,
+          containingDescription: RootDescription('.'),
         );
         expect(pubspec.publishTo, equals('http://example.com'));
       });
@@ -841,6 +907,7 @@
 publish_to: none
 ''',
           sources,
+          containingDescription: RootDescription('.'),
         );
         expect(pubspec.publishTo, equals('none'));
       });
@@ -862,7 +929,11 @@
 
     group('executables', () {
       test('defaults to an empty map if omitted', () {
-        var pubspec = Pubspec.parse('', sources);
+        var pubspec = Pubspec.parse(
+          '',
+          sources,
+          containingDescription: RootDescription('.'),
+        );
         expect(pubspec.executables, isEmpty);
       });
 
@@ -873,6 +944,7 @@
   abcDEF-123_: "abc DEF-123._"
 ''',
           sources,
+          containingDescription: RootDescription('.'),
         );
         expect(pubspec.executables['abcDEF-123_'], equals('abc DEF-123._'));
       });
@@ -926,6 +998,7 @@
   command:
 ''',
           sources,
+          containingDescription: RootDescription('.'),
         );
         expect(pubspec.executables['command'], equals('command'));
       });
@@ -944,6 +1017,7 @@
           sources,
           overridesFileContents: overridesContents,
           overridesLocation: Uri.parse('file:///pubspec_overrides.yaml'),
+          containingDescription: RootDescription('.'),
         );
       }
 
diff --git a/test/version_solver_test.dart b/test/version_solver_test.dart
index baddd53..bf3a4e0 100644
--- a/test/version_solver_test.dart
+++ b/test/version_solver_test.dart
@@ -9,6 +9,7 @@
 import 'package:pub/src/lock_file.dart';
 import 'package:pub/src/pubspec.dart';
 import 'package:pub/src/source/hosted.dart';
+import 'package:pub/src/source/root.dart';
 import 'package:pub/src/system_cache.dart';
 import 'package:test/test.dart';
 
@@ -1981,7 +1982,11 @@
   var registry = cache.sources;
   var lockFile =
       LockFile.load(p.join(d.sandbox, appPath, 'pubspec.lock'), registry);
-  var resultPubspec = Pubspec.fromMap({'dependencies': result}, registry);
+  var resultPubspec = Pubspec.fromMap(
+    {'dependencies': result},
+    registry,
+    containingDescription: RootDescription('.'),
+  );
 
   var ids = {...lockFile.packages};
   for (var dep in resultPubspec.dependencies.values) {