parseJsonSync static method
Creates a single hitmap from a raw json object.
Note that when checkIgnoredLines
is true
all files will be
read to get ignore comments. This will add some overhead.
To combat this when calling this function multiple times from the
same source (e.g. test runs of different files) a cache is taken
via ignoredLinesInFilesCache
. If this cache contains the parsed
data for the specific file already, the file will not be read and
parsed again.
Throws away all entries that are not resolvable.
Implementation
static Map<String, HitMap> parseJsonSync(
List<Map<String, dynamic>> jsonResult, {
required bool checkIgnoredLines,
required Map<String, List<List<int>>?> ignoredLinesInFilesCache,
required Resolver resolver,
}) {
final loader = Loader();
// Map of source file to map of line to hit count for that line.
final globalHitMap = <String, HitMap>{};
for (var e in jsonResult) {
final source = e['source'] as String?;
if (source == null) {
// Couldn't resolve import, so skip this entry.
continue;
}
var ignoredLinesList = <List<int>>[];
if (checkIgnoredLines) {
if (ignoredLinesInFilesCache.containsKey(source)) {
final cacheHit = ignoredLinesInFilesCache[source];
if (cacheHit == null) {
// Null-entry indicates that the whole file was ignored.
continue;
}
ignoredLinesList = cacheHit;
} else {
final path = resolver.resolve(source);
if (path != null) {
final lines = loader.loadSync(path) ?? [];
ignoredLinesList = getIgnoredLines(path, lines);
// Ignore the whole file.
if (ignoredLinesList.length == 1 &&
ignoredLinesList[0][0] == 0 &&
ignoredLinesList[0][1] == lines.length) {
// Null-entry indicates that the whole file was ignored.
ignoredLinesInFilesCache[source] = null;
continue;
}
ignoredLinesInFilesCache[source] = ignoredLinesList;
} else {
// Couldn't resolve source. Allow cache to answer next time
// anyway.
ignoredLinesInFilesCache[source] = ignoredLinesList;
}
}
}
// Move to the first ignore range.
final ignoredLines = ignoredLinesList.iterator;
var hasCurrent = ignoredLines.moveNext();
bool shouldIgnoreLine(Iterator<List<int>> ignoredRanges, int line) {
if (!hasCurrent || ignoredRanges.current.isEmpty) {
return false;
}
if (line < ignoredRanges.current[0]) return false;
while (hasCurrent &&
ignoredRanges.current.isNotEmpty &&
ignoredRanges.current[1] < line) {
hasCurrent = ignoredRanges.moveNext();
}
if (hasCurrent &&
ignoredRanges.current.isNotEmpty &&
ignoredRanges.current[0] <= line &&
line <= ignoredRanges.current[1]) {
return true;
}
return false;
}
void addToMap(Map<int, int> map, int line, int count) {
final oldCount = map.putIfAbsent(line, () => 0);
map[line] = count + oldCount;
}
void fillHitMap(List hits, Map<int, int> hitMap) {
// Ignore line annotations require hits to be sorted.
hits = _sortHits(hits);
// hits is a flat array of the following format:
// [ <line|linerange>, <hitcount>,...]
// line: number.
// linerange: '<line>-<line>'.
for (var i = 0; i < hits.length; i += 2) {
final k = hits[i];
if (k is int) {
// Single line.
if (shouldIgnoreLine(ignoredLines, k)) continue;
addToMap(hitMap, k, hits[i + 1] as int);
} else if (k is String) {
// Linerange. We expand line ranges to actual lines at this point.
final splitPos = k.indexOf('-');
final start = int.parse(k.substring(0, splitPos));
final end = int.parse(k.substring(splitPos + 1));
for (var j = start; j <= end; j++) {
if (shouldIgnoreLine(ignoredLines, j)) continue;
addToMap(hitMap, j, hits[i + 1] as int);
}
} else {
throw StateError('Expected value of type int or String');
}
}
}
final sourceHitMap = globalHitMap.putIfAbsent(source, HitMap.new);
fillHitMap(e['hits'] as List, sourceHitMap.lineHits);
if (e.containsKey('funcHits')) {
sourceHitMap.funcHits ??= <int, int>{};
fillHitMap(e['funcHits'] as List, sourceHitMap.funcHits!);
}
if (e.containsKey('funcNames')) {
sourceHitMap.funcNames ??= <int, String>{};
final funcNames = e['funcNames'] as List;
for (var i = 0; i < funcNames.length; i += 2) {
sourceHitMap.funcNames![funcNames[i] as int] =
funcNames[i + 1] as String;
}
}
if (e.containsKey('branchHits')) {
sourceHitMap.branchHits ??= <int, int>{};
fillHitMap(e['branchHits'] as List, sourceHitMap.branchHits!);
}
}
return globalHitMap;
}