parsePubspecLock function

List<DependencyEntry> parsePubspecLock(
  1. String content
)

Parses a pubspec.lock file's content into every resolved package, sorted by name — this ordering is what makes the resulting SbomDocument reproducible: the same lock file always produces byte-identical JSON, regardless of the lock file's own key order or any Map iteration order.

A line-based parser rather than a real YAML parser — deliberately: this package has no yaml dependency (see pubspec.lock itself, verified before writing this), and pubspec.lock's own shape (fixed 2-space-indented top-level package keys, fixed field names) is simple enough that a small state machine is honest, not a fragile shortcut. A line this parser doesn't recognize is skipped, never thrown on — pub's own lock file format is a stable, first-party contract this package doesn't control, so surviving a future minor format tweak (an added field, say) matters more than being maximally strict.

Implementation

List<DependencyEntry> parsePubspecLock(String content) {
  final entries = <DependencyEntry>[];
  String? currentName;
  DependencyType? currentType;
  DependencySource? currentSource;
  String? currentVersion;

  void flush() {
    if (currentName != null && currentType != null && currentVersion != null) {
      entries.add(
        DependencyEntry(
          name: currentName!,
          version: currentVersion!,
          type: currentType!,
          source: currentSource ?? DependencySource.unknown,
        ),
      );
    }
    currentName = null;
    currentType = null;
    currentSource = null;
    currentVersion = null;
  }

  final topLevelKey = RegExp(r'^  (\S+):\s*$');
  final dependencyLine = RegExp(r'^\s{4}dependency:\s*(.+)$');
  final sourceLine = RegExp(r'^\s{4}source:\s*(\S+)$');
  final versionLine = RegExp(r'^\s{4}version:\s*"?([^"\n]+)"?$');

  for (final line in content.split('\n')) {
    final keyMatch = topLevelKey.firstMatch(line);
    if (keyMatch != null) {
      flush();
      currentName = keyMatch.group(1);
      continue;
    }
    final depMatch = dependencyLine.firstMatch(line);
    if (depMatch != null) {
      currentType = _parseDependencyType(depMatch.group(1)!.trim());
      continue;
    }
    final sourceMatch = sourceLine.firstMatch(line);
    if (sourceMatch != null) {
      currentSource = _parseSource(sourceMatch.group(1)!.trim());
      continue;
    }
    final versionMatch = versionLine.firstMatch(line);
    if (versionMatch != null) {
      currentVersion = versionMatch.group(1)!.trim();
      continue;
    }
  }
  flush();

  entries.sort((a, b) => a.name.compareTo(b.name));
  return entries;
}