format method

  1. @override
String format(
  1. ComparisonResult result,
  2. ExtractionResult extracted, {
  3. bool verbose = false,
})
override

Generate output for the given comparison result.

Implementation

@override
String format(
  ComparisonResult result,
  ExtractionResult extracted, {
  bool verbose = false,
}) {
  final buffer = StringBuffer();
  final stats = result.stats;

  // Header
  buffer.writeln('shard_i18n extract - Key Analysis Report');
  buffer.writeln('=' * 44);
  buffer.writeln();

  // Statistics
  buffer.writeln('Statistics:');
  buffer.writeln('  Files scanned:     ${stats.filesScanned}');
  buffer.writeln('  JSON files:        ${stats.jsonFilesLoaded}');
  buffer.writeln('  Keys in code:      ${stats.totalKeysInCode}');
  buffer.writeln('  Keys in JSON:      ${stats.totalKeysInJson}');
  buffer.writeln(
    '  Matched:           ${stats.matchedCount} (${stats.coveragePercent.toStringAsFixed(1)}%)',
  );
  buffer.writeln('  Missing in JSON:   ${stats.missingCount}');
  buffer.writeln('  Orphaned in JSON:  ${stats.orphanedCount}');
  buffer.writeln();

  // Missing keys
  if (result.missingInJson.isNotEmpty) {
    buffer.writeln('Missing Keys (in code, not in JSON):');
    buffer.writeln('-' * 40);

    final sortedMissing = result.missingInJson.toList()..sort();
    for (final key in sortedMissing) {
      buffer.writeln('  + $key');

      // Find locations for this key
      final locations = extracted.keys.where((k) => k.key == key);
      for (final loc in locations.take(verbose ? 10 : 2)) {
        buffer.writeln('      ${loc.filePath}:${loc.line}');
        if (loc.snippet != null) {
          buffer.writeln('      ${loc.snippet}');
        }
      }
      if (!verbose && locations.length > 2) {
        buffer.writeln('      ... and ${locations.length - 2} more');
      }
      buffer.writeln();
    }
  }

  // Orphaned keys
  if (result.orphanedInJson.isNotEmpty) {
    buffer.writeln('Orphaned Keys (in JSON, not in code):');
    buffer.writeln('-' * 40);

    final sortedOrphaned = result.orphanedInJson.toList()..sort();
    for (final key in sortedOrphaned) {
      buffer.writeln('  - $key');
    }
    buffer.writeln();
  }

  // Placeholder mismatches
  if (result.placeholderMismatches.isNotEmpty) {
    buffer.writeln('Placeholder Mismatches:');
    buffer.writeln('-' * 40);

    for (final mismatch in result.placeholderMismatches.values) {
      buffer.writeln('  ~ ${mismatch.key}');
      buffer.writeln('      Code: {${mismatch.inCode.join('}, {')}}');
      buffer.writeln('      JSON: {${mismatch.inJson.join('}, {')}}');
      buffer.writeln();
    }
  }

  // Plural form issues
  if (result.pluralIssues.isNotEmpty) {
    buffer.writeln('Plural Form Issues:');
    buffer.writeln('-' * 40);

    for (final issue in result.pluralIssues.values) {
      buffer.writeln('  ! ${issue.key}');
      buffer.writeln('      ${issue.message}');
      if (issue.availableForms != null) {
        buffer.writeln('      Available forms: ${issue.availableForms}');
      }
      buffer.writeln();
    }
  }

  // Verbose: per-file breakdown
  if (verbose && extracted.byFile.isNotEmpty) {
    buffer.writeln('Per-File Breakdown:');
    buffer.writeln('-' * 40);

    final sortedFiles = extracted.byFile.keys.toList()..sort();
    for (final file in sortedFiles) {
      final keys = extracted.byFile[file]!;
      buffer.writeln('  $file (${keys.length} keys)');
      for (final key in keys) {
        final status = result.missingInJson.contains(key.key)
            ? '+'
            : result.matchedKeys.contains(key.key)
            ? ' '
            : '?';
        buffer.writeln('    [$status] ${key.key} (line ${key.line})');
      }
      buffer.writeln();
    }
  }

  // Summary
  if (result.hasDiscrepancies) {
    buffer.writeln('Run with --fix to auto-generate missing entries.');
    buffer.writeln('Run with --prune to remove orphaned keys.');
    buffer.writeln('Run with --verbose for per-file breakdown.');
  } else {
    buffer.writeln('All keys are in sync!');
  }

  return buffer.toString();
}