diff --git a/pkgs/coverage/analysis_options.yaml b/pkgs/coverage/analysis_options.yaml index bb1afe05ab..62aab638e0 100644 --- a/pkgs/coverage/analysis_options.yaml +++ b/pkgs/coverage/analysis_options.yaml @@ -1,6 +1,8 @@ include: package:dart_flutter_team_lints/analysis_options.yaml analyzer: + errors: + unnecessary_this: ignore language: strict-casts: true diff --git a/pkgs/coverage/lib/src/collect.dart b/pkgs/coverage/lib/src/collect.dart index 76227bac9f..62e56146c6 100644 --- a/pkgs/coverage/lib/src/collect.dart +++ b/pkgs/coverage/lib/src/collect.dart @@ -42,8 +42,9 @@ const _debugTokenPositions = bool.fromEnvironment('DEBUG_COVERAGE'); /// If [scopedOutput] is non-empty, coverage will be restricted so that only /// scripts that start with any of the provided paths are considered. /// -/// If [isolateIds] is set, the coverage gathering will be restricted to only -/// those VM isolates. +/// If [isolateIds] is set, coverage gathering **will not be restricted** to +/// only those VM isolates. Instead, coverage will be collected for **all isolates +/// in the same isolate group** as the provided isolate(s). /// /// If [coverableLineCache] is set, the collector will avoid recompiling /// libraries it has already seen (see VmService.getSourceReport's diff --git a/pkgs/coverage/lib/src/formatter.dart b/pkgs/coverage/lib/src/formatter.dart index a37df73b7f..1d305c4bea 100644 --- a/pkgs/coverage/lib/src/formatter.dart +++ b/pkgs/coverage/lib/src/formatter.dart @@ -2,6 +2,8 @@ // 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. +// ignore_for_file: unnecessary_this + import 'package:glob/glob.dart'; import 'package:path/path.dart' as p; @@ -78,12 +80,19 @@ extension FileHitMapsFormatter on Map { String? basePath, List? reportOn, Set? ignoreGlobs, + bool Function(String path)? includeUncovered, }) { final pathFilter = _getPathFilter( reportOn: reportOn, ignoreGlobs: ignoreGlobs, ); final buf = StringBuffer(); + + // Get all Dart files in the project + final allDartFiles = resolver.listAllDartFiles().toSet(); + final coveredFiles = this.keys.toSet(); + final uncoveredFiles = allDartFiles.difference(coveredFiles); + for (final entry in entries) { final v = entry.value; final lineHits = v.lineHits; @@ -91,13 +100,7 @@ extension FileHitMapsFormatter on Map { final funcNames = v.funcNames; final branchHits = v.branchHits; var source = resolver.resolve(entry.key); - if (source == null) { - continue; - } - - if (!pathFilter(source)) { - continue; - } + if (source == null || !pathFilter(source)) continue; if (basePath != null) { source = p.relative(source, from: basePath); @@ -129,6 +132,21 @@ extension FileHitMapsFormatter on Map { buf.write('end_of_record\n'); } + // Add uncovered files if allowed + for (final file in uncoveredFiles) { + if (includeUncovered != null && !includeUncovered(file)) continue; + var source = resolver.resolve(file); + if (source == null || !pathFilter(source)) continue; + if (basePath != null) { + source = p.relative(source, from: basePath); + } + + buf.write('SF:$source\n'); + buf.write('LF:0\n'); + buf.write('LH:0\n'); + buf.write('end_of_record\n'); + } + return buf.toString(); } @@ -144,12 +162,19 @@ extension FileHitMapsFormatter on Map { Set? ignoreGlobs, bool reportFuncs = false, bool reportBranches = false, + bool Function(String path)? includeUncovered, }) async { final pathFilter = _getPathFilter( reportOn: reportOn, ignoreGlobs: ignoreGlobs, ); final buf = StringBuffer(); + + // Get all Dart files in the project + final allDartFiles = resolver.listAllDartFiles().toSet(); + final coveredFiles = this.keys.toSet(); + final uncoveredFiles = allDartFiles.difference(coveredFiles); + for (final entry in entries) { final v = entry.value; if (reportFuncs && v.funcHits == null) { @@ -171,18 +196,10 @@ extension FileHitMapsFormatter on Map { ? v.branchHits! : v.lineHits; final source = resolver.resolve(entry.key); - if (source == null) { - continue; - } - - if (!pathFilter(source)) { - continue; - } + if (source == null || !pathFilter(source)) continue; final lines = await loader.load(source); - if (lines == null) { - continue; - } + if (lines == null) continue; buf.writeln(source); for (var line = 1; line <= lines.length; line++) { var prefix = _prefix; @@ -193,6 +210,20 @@ extension FileHitMapsFormatter on Map { } } + // Add uncovered files if allowed + for (final file in uncoveredFiles) { + if (includeUncovered != null && !includeUncovered(file)) continue; + final source = resolver.resolve(file); + if (source == null || !pathFilter(source)) continue; + + final lines = await loader.load(source); + if (lines == null) continue; + buf.writeln(source); + for (final line in lines) { + buf.writeln(' |$line'); + } + } + return buf.toString(); } } @@ -221,3 +252,4 @@ _PathFilter _getPathFilter({List? reportOn, Set? ignoreGlobs}) { return true; }; } + diff --git a/pkgs/coverage/lib/src/resolver.dart b/pkgs/coverage/lib/src/resolver.dart index cb5b728894..263d2c17dc 100644 --- a/pkgs/coverage/lib/src/resolver.dart +++ b/pkgs/coverage/lib/src/resolver.dart @@ -9,6 +9,19 @@ import 'package:path/path.dart' as p; /// [Resolver] resolves imports with respect to a given environment. class Resolver { + /// Returns a list of all Dart files in the project. + List listAllDartFiles({String directoryPath = '.'}) { + final dir = Directory(directoryPath); + if (!dir.existsSync()) return []; + + return dir + .listSync(recursive: true) + .whereType() + .where((file) => file.path.endsWith('.dart')) + .map((file) => file.path) + .toList(); + } + @Deprecated('Use Resolver.create') Resolver({this.packagesPath, this.sdkRoot}) : _packages = packagesPath != null ? _parsePackages(packagesPath) : null, diff --git a/pkgs/coverage/test/run_and_collect_test.dart b/pkgs/coverage/test/run_and_collect_test.dart index e371f90005..6714b81744 100644 --- a/pkgs/coverage/test/run_and_collect_test.dart +++ b/pkgs/coverage/test/run_and_collect_test.dart @@ -69,6 +69,10 @@ class ThrowingResolver implements Resolver { @override String? get sdkRoot => throw UnimplementedError(); + + @override + List listAllDartFiles({String directoryPath = '.'}) => + throw UnimplementedError(); } void checkIgnoredLinesInFilesCache(