4ac4ce71d1
Change-Id: I36836eaa4ce7c2d56fb5072054ceb344ea9e1225 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/442344 Reviewed-by: Samuel Rawlins <srawlins@google.com> Reviewed-by: Konstantin Shcheglov <scheglov@google.com> Commit-Queue: Samuel Rawlins <srawlins@google.com>
159 lines
4.1 KiB
Dart
159 lines
4.1 KiB
Dart
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
import 'dart:math';
|
|
|
|
/// Generates a "focused" unified diff, showing only the changed lines
|
|
/// plus a few lines of context around them.
|
|
List<String> generateFocusedDiff(
|
|
String expected,
|
|
String actual, {
|
|
int contextLines = 3,
|
|
}) {
|
|
var fullDiff = _createDiff(expected, actual);
|
|
var outputLines = <String>[];
|
|
|
|
var indicesToInclude = <int>{};
|
|
|
|
for (var (i, result) in fullDiff.indexed) {
|
|
if (result.type != _DiffType.common) {
|
|
indicesToInclude.add(i);
|
|
for (
|
|
int j = max(0, i - contextLines);
|
|
j < min(fullDiff.length, i + contextLines + 1);
|
|
j++
|
|
) {
|
|
indicesToInclude.add(j);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (indicesToInclude.isEmpty) {
|
|
return ['No differences found.'];
|
|
}
|
|
|
|
int lastIncludedIndex = -1;
|
|
var sortedIndices = indicesToInclude.toList()..sort();
|
|
|
|
for (int i in sortedIndices) {
|
|
if (lastIncludedIndex != -1 && i > lastIncludedIndex + 1) {
|
|
outputLines.add('...');
|
|
}
|
|
|
|
outputLines.add(_formatLine(fullDiff[i]));
|
|
lastIncludedIndex = i;
|
|
}
|
|
|
|
return outputLines;
|
|
}
|
|
|
|
/// Generates a "full" unified diff, showing every line from the comparison.
|
|
List<String> generateFullDiff(String expected, String actual) {
|
|
var diffResults = _createDiff(expected, actual);
|
|
return diffResults.map(_formatLine).toList();
|
|
}
|
|
|
|
void printPrettyDiff(String expected, String actual, {int context = 3}) {
|
|
var full = generateFullDiff(expected, actual);
|
|
var short = generateFocusedDiff(expected, actual);
|
|
|
|
if (full.length * 0.3 >= short.length) {
|
|
print('-------- Short diff --------');
|
|
print(short.join('\n'));
|
|
}
|
|
print('-------- Full diff ---------');
|
|
print(full.join('\n'));
|
|
print('---------- Actual ----------');
|
|
print(actual.trimRight());
|
|
print('----------------------------');
|
|
}
|
|
|
|
/// Backtracks through the LCS table to build the list of diff results.
|
|
List<_DiffResult> _backtrack(
|
|
List<List<int>> table,
|
|
List<String> list1,
|
|
List<String> list2,
|
|
) {
|
|
var diff = <_DiffResult>[];
|
|
int i = list1.length;
|
|
int j = list2.length;
|
|
|
|
while (i > 0 || j > 0) {
|
|
if (i == 0) {
|
|
diff.add(_DiffResult(_DiffType.added, list2[j - 1]));
|
|
j--;
|
|
continue;
|
|
}
|
|
if (j == 0) {
|
|
diff.add(_DiffResult(_DiffType.removed, list1[i - 1]));
|
|
i--;
|
|
continue;
|
|
}
|
|
if (list1[i - 1] == list2[j - 1]) {
|
|
diff.add(_DiffResult(_DiffType.common, list1[i - 1]));
|
|
i--;
|
|
j--;
|
|
} else if (table[i - 1][j] >= table[i][j - 1]) {
|
|
diff.add(_DiffResult(_DiffType.removed, list1[i - 1]));
|
|
i--;
|
|
} else {
|
|
diff.add(_DiffResult(_DiffType.added, list2[j - 1]));
|
|
j--;
|
|
}
|
|
}
|
|
|
|
return diff.reversed.toList();
|
|
}
|
|
|
|
List<List<int>> _computeLcsTable(List<String> list1, List<String> list2) {
|
|
var n = list1.length;
|
|
var m = list2.length;
|
|
|
|
var table = List.generate(n + 1, (_) => List.filled(m + 1, 0));
|
|
|
|
for (int i = 1; i <= n; i++) {
|
|
for (int j = 1; j <= m; j++) {
|
|
if (list1[i - 1] == list2[j - 1]) {
|
|
table[i][j] = table[i - 1][j - 1] + 1;
|
|
} else {
|
|
table[i][j] = max(table[i - 1][j], table[i][j - 1]);
|
|
}
|
|
}
|
|
}
|
|
return table;
|
|
}
|
|
|
|
/// Returns a line by line difference between [actual] and [expected].
|
|
///
|
|
/// It uses Longest Common Sequence Algorithm to generate a table
|
|
/// (https://en.wikipedia.org/wiki/Longest_common_subsequence)
|
|
List<_DiffResult> _createDiff(String expected, String actual) {
|
|
var expectedLines = expected.split('\n');
|
|
var actualLines = actual.split('\n');
|
|
|
|
var lcsTable = _computeLcsTable(expectedLines, actualLines);
|
|
|
|
return _backtrack(lcsTable, expectedLines, actualLines);
|
|
}
|
|
|
|
String _formatLine(_DiffResult result) {
|
|
switch (result.type) {
|
|
case _DiffType.added:
|
|
return '+ ${result.line}';
|
|
case _DiffType.removed:
|
|
return '- ${result.line}';
|
|
case _DiffType.common:
|
|
return ' ${result.line}';
|
|
}
|
|
}
|
|
|
|
class _DiffResult {
|
|
final _DiffType type;
|
|
final String line;
|
|
|
|
_DiffResult(this.type, this.line);
|
|
}
|
|
|
|
enum _DiffType { added, removed, common }
|