dependencyReach function

({Map<String, List<String>> reached, Set<String> unresolved}) dependencyReach(
  1. Pubspec root, {
  2. required bool isBanned(
    1. String package
    ),
  3. Map<String, Pubspec?>? pubspecs,
})

The packages isBanned matches that root reaches — each with the dependency chain that reaches it — and the hosted dependencies the walk could not follow because root was never resolved.

root and every path package are followed through all three declared blocks; a hosted package through its runtime dependencies only, read from the copy root's resolution names. pubspecs caches the reads across roots, which share most of their graph. A walk that reaches a banned package does not go further into it.

Implementation

({Map<String, List<String>> reached, Set<String> unresolved}) dependencyReach(
  Pubspec root, {
  required bool Function(String package) isBanned,
  Map<String, Pubspec?>? pubspecs,
}) {
  final cache = pubspecs ?? <String, Pubspec?>{};
  final reached = <String, List<String>>{};
  final unresolved = <String>{};
  final resolved = resolvedPackagesOf(root.directory);
  final seen = <String>{};
  void walk(Pubspec node, List<String> chain, {required bool declared}) {
    if (!seen.add(node.directory)) return;
    final edges = declared ? node.dependencies : node.runtimeDependencies;
    for (final dependency in edges) {
      if (isBanned(dependency)) {
        reached.putIfAbsent(dependency, () => [...chain, node.name]);
        continue;
      }
      final path = node.pathDependencies[dependency];
      final directory = path ?? resolved?[dependency];
      if (directory == null) {
        // Unresolved: an SDK package answers for itself, and a name missing
        // from a resolution that exists is a dev dependency of a non-root
        // package — never part of the graph.
        if (resolved == null && !node.sdkDependencies.contains(dependency)) {
          unresolved.add(dependency);
        }
        continue;
      }
      final next = cache.putIfAbsent(directory, () => Pubspec.read(directory));
      if (next != null) {
        walk(next, [...chain, node.name], declared: path != null);
      }
    }
  }

  walk(root, const [], declared: true);
  return (reached: reached, unresolved: unresolved);
}