| // 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 'resolver.dart'; |
| import 'hitmap.dart'; |
| |
| abstract class Formatter { |
| /// Returns the formatted coverage data. |
| Future<String> format(Map<String, HitMap> 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. |
| 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, HitMap> hitmap) async { |
| final pathFilter = _getPathFilter(reportOn); |
| final buf = StringBuffer(); |
| for (var key in hitmap.keys) { |
| final v = hitmap[key]!; |
| final lineHits = v.lineHits; |
| final funcHits = v.funcHits; |
| final funcNames = v.funcNames; |
| var source = resolver.resolve(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 (var k in funcNames.keys.toList()..sort()) { |
| buf.write('FN:$k,${funcNames[k]}\n'); |
| } |
| for (var 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 (var 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'); |
| buf.write('end_of_record\n'); |
| } |
| |
| return buf.toString(); |
| } |
| } |
| |
| /// 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. |
| 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, HitMap> hitmap) async { |
| final pathFilter = _getPathFilter(reportOn); |
| final buf = StringBuffer(); |
| for (var key in hitmap.keys) { |
| final v = hitmap[key]!; |
| if (reportFuncs && v.funcHits == null) { |
| throw 'Function coverage formatting was requested, but the hit map is ' |
| 'missing function coverage information. Did you run ' |
| 'collect_coverage with the --function-coverage flag?'; |
| } |
| final hits = reportFuncs ? v.funcHits! : v.lineHits; |
| final source = resolver.resolve(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.absolute).toList(); |
| return (String path) => absolutePaths.any((item) => path.startsWith(item)); |
| } |