dependencyReach function
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);
}