blob: a7a96c1d673709c670760a2f45c509517f955839 [file] [log] [blame]
// Copyright (c) 2014, 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 'package:path/path.dart' as p;
import 'hitmap.dart';
import 'resolver.dart';
@Deprecated('Migrate to FileHitMapsFormatter')
abstract class Formatter {
/// Returns the formatted coverage data.
Future<String> format(Map<String, Map<int, int>> hitmap);
}
/// Converts the given hitmap to lcov format and appends the result to
/// env.output.
///
/// Returns a [Future] that completes as soon as all map entries have been
/// emitted.
@Deprecated('Migrate to FileHitMapsFormatter.formatLcov')
class LcovFormatter implements Formatter {
/// Creates a LCOV formatter.
///
/// If [reportOn] is provided, coverage report output is limited to files
/// prefixed with one of the paths included. If [basePath] is provided, paths
/// are reported relative to that path.
LcovFormatter(this.resolver, {this.reportOn, this.basePath});
final Resolver resolver;
final String? basePath;
final List<String>? reportOn;
@override
Future<String> format(Map<String, Map<int, int>> hitmap) {
return Future.value(hitmap
.map((key, value) => MapEntry(key, HitMap(value)))
.formatLcov(resolver, basePath: basePath, reportOn: reportOn));
}
}
/// Converts the given hitmap to a pretty-print format and appends the result
/// to env.output.
///
/// Returns a [Future] that completes as soon as all map entries have been
/// emitted.
@Deprecated('Migrate to FileHitMapsFormatter.prettyPrint')
class PrettyPrintFormatter implements Formatter {
/// Creates a pretty-print formatter.
///
/// If [reportOn] is provided, coverage report output is limited to files
/// prefixed with one of the paths included.
PrettyPrintFormatter(this.resolver, this.loader,
{this.reportOn, this.reportFuncs = false});
final Resolver resolver;
final Loader loader;
final List<String>? reportOn;
final bool reportFuncs;
@override
Future<String> format(Map<String, Map<int, int>> hitmap) {
return hitmap.map((key, value) => MapEntry(key, HitMap(value))).prettyPrint(
resolver, loader,
reportOn: reportOn, reportFuncs: reportFuncs);
}
}
extension FileHitMapsFormatter on Map<String, HitMap> {
/// Converts the given hitmap to lcov format.
///
/// If [reportOn] is provided, coverage report output is limited to files
/// prefixed with one of the paths included. If [basePath] is provided, paths
/// are reported relative to that path.
String formatLcov(
Resolver resolver, {
String? basePath,
List<String>? reportOn,
}) {
final pathFilter = _getPathFilter(reportOn);
final buf = StringBuffer();
for (final entry in entries) {
final v = entry.value;
final lineHits = v.lineHits;
final funcHits = v.funcHits;
final funcNames = v.funcNames;
final branchHits = v.branchHits;
var source = resolver.resolve(entry.key);
if (source == null) {
continue;
}
if (!pathFilter(source)) {
continue;
}
if (basePath != null) {
source = p.relative(source, from: basePath);
}
buf.write('SF:$source\n');
if (funcHits != null && funcNames != null) {
for (final k in funcNames.keys.toList()..sort()) {
buf.write('FN:$k,${funcNames[k]}\n');
}
for (final k in funcHits.keys.toList()..sort()) {
if (funcHits[k]! != 0) {
buf.write('FNDA:${funcHits[k]},${funcNames[k]}\n');
}
}
buf.write('FNF:${funcNames.length}\n');
buf.write('FNH:${funcHits.values.where((v) => v > 0).length}\n');
}
for (final k in lineHits.keys.toList()..sort()) {
buf.write('DA:$k,${lineHits[k]}\n');
}
buf.write('LF:${lineHits.length}\n');
buf.write('LH:${lineHits.values.where((v) => v > 0).length}\n');
if (branchHits != null) {
for (final k in branchHits.keys.toList()..sort()) {
buf.write('BRDA:$k,0,0,${branchHits[k]}\n');
}
}
buf.write('end_of_record\n');
}
return buf.toString();
}
/// Converts the given hitmap to a pretty-print format.
///
/// If [reportOn] is provided, coverage report output is limited to files
/// prefixed with one of the paths included. If [reportFuncs] is provided,
/// only function coverage information will be shown.
Future<String> prettyPrint(
Resolver resolver,
Loader loader, {
List<String>? reportOn,
bool reportFuncs = false,
bool reportBranches = false,
}) async {
final pathFilter = _getPathFilter(reportOn);
final buf = StringBuffer();
for (final entry in entries) {
final v = entry.value;
if (reportFuncs && v.funcHits == null) {
throw StateError(
'Function coverage formatting was requested, but the hit map is '
'missing function coverage information. Did you run '
'collect_coverage with the --function-coverage flag?',
);
}
if (reportBranches && v.branchHits == null) {
throw StateError(
'Branch coverage formatting was requested, but the hit map is '
'missing branch coverage information. Did you run '
'collect_coverage with the --branch-coverage flag?');
}
final hits = reportFuncs
? v.funcHits!
: reportBranches
? v.branchHits!
: v.lineHits;
final source = resolver.resolve(entry.key);
if (source == null) {
continue;
}
if (!pathFilter(source)) {
continue;
}
final lines = await loader.load(source);
if (lines == null) {
continue;
}
buf.writeln(source);
for (var line = 1; line <= lines.length; line++) {
var prefix = _prefix;
if (hits.containsKey(line)) {
prefix = hits[line].toString().padLeft(_prefix.length);
}
buf.writeln('$prefix|${lines[line - 1]}');
}
}
return buf.toString();
}
}
const _prefix = ' ';
typedef _PathFilter = bool Function(String path);
_PathFilter _getPathFilter(List<String>? reportOn) {
if (reportOn == null) return (String path) => true;
final absolutePaths = reportOn.map(p.canonicalize).toList();
return (String path) =>
absolutePaths.any((item) => p.canonicalize(path).startsWith(item));
}