sortPubspec function

String sortPubspec(
  1. String pubspecContent
)

Alphabetically sorts the dependency sections of a pubspec.yaml.

Sorts dependencies, dev_dependencies, and dependency_overrides by key (case-insensitive). This is a line-based sort: each dependency keeps its nested value lines (e.g. a git:/version: block) and any comment lines directly above it, so comments and formatting are preserved. See issue import_sorter#89.

Returns the rewritten YAML with a trailing newline. If nothing needs reordering, the content is returned unchanged (trailing newline ensured).

Implementation

String sortPubspec(String pubspecContent) {
  final lines = pubspecContent.split('\n');
  final output = <String>[];

  var i = 0;
  while (i < lines.length) {
    final line = lines[i];
    final section = _sectionHeader(line);

    if (section == null) {
      output.add(line);
      i++;
      continue;
    }

    // Keep the `dependencies:` header line itself.
    output.add(line);
    i++;

    // Gather entry blocks until the section ends (a non-indented, non-blank
    // line) or the file ends.
    final blocks = <_Entry>[];
    final pendingComments = <String>[];

    while (i < lines.length) {
      final current = lines[i];

      // Blank line: attach to pending comments so it travels with the next
      // entry, unless we are at the very start (then it separates sections).
      if (current.trim().isEmpty) {
        // A blank line ends the section only if the next meaningful line is
        // not part of this section's entries.
        if (_endsSection(lines, i)) break;
        pendingComments.add(current);
        i++;
        continue;
      }

      // A line that is not indented ends the section.
      if (!current.startsWith('  ')) break;

      final key = _entryKey(current);
      if (key != null) {
        // Start of a new entry: collect its continuation lines.
        final blockLines = <String>[...pendingComments, current];
        pendingComments.clear();
        i++;
        // Continuation lines are nested values, indented deeper than the key
        // (3+ spaces). A comment aligned with entries (2 spaces) is treated as
        // a leading comment of the *next* entry instead.
        while (i < lines.length &&
            lines[i].startsWith('   ') &&
            lines[i].trim().isNotEmpty) {
          blockLines.add(lines[i]);
          i++;
        }
        blocks.add(_Entry(key, blockLines));
      } else {
        // Indented but not a recognizable entry key — leave as-is.
        pendingComments.add(current);
        i++;
      }
    }

    blocks.sort(
      (a, b) => a.key.toLowerCase().compareTo(b.key.toLowerCase()),
    );
    for (final block in blocks) {
      output.addAll(block.lines);
    }
    // Re-emit any trailing comments/blank lines that were not attached.
    output.addAll(pendingComments);
  }

  final result = output.join('\n');
  return result.endsWith('\n') ? result : '$result\n';
}