From 1d657ed45ac1d58f398b8cba6587cf6b9408a1eb Mon Sep 17 00:00:00 2001 From: Yash Date: Tue, 6 May 2025 12:04:23 +0530 Subject: [PATCH] issue 529 --- pkgs/coverage/bin/format_coverage.dart | 16 ++- pkgs/coverage/lib/src/formatter.dart | 155 +++++++++++++++++++++++++ pkgs/coverage/pubspec.yaml | 1 + 3 files changed, 169 insertions(+), 3 deletions(-) diff --git a/pkgs/coverage/bin/format_coverage.dart b/pkgs/coverage/bin/format_coverage.dart index c2d8283abc..8da34d08ca 100644 --- a/pkgs/coverage/bin/format_coverage.dart +++ b/pkgs/coverage/bin/format_coverage.dart @@ -30,6 +30,7 @@ class Environment { required this.sdkRoot, required this.verbose, required this.workers, + required this.includeUncovered, }); String? baseDirectory; @@ -49,6 +50,7 @@ class Environment { String? sdkRoot; bool verbose; int workers; + bool includeUncovered; } Future main(List arguments) async { @@ -97,13 +99,15 @@ Future main(List arguments) async { reportOn: env.reportOn, ignoreGlobs: ignoreGlobs, reportFuncs: env.prettyPrintFunc, - reportBranches: env.prettyPrintBranch); + reportBranches: env.prettyPrintBranch, + includeUncovered: (String path) => env.includeUncovered); } else { assert(env.lcov); output = hitmap.formatLcov(resolver, reportOn: env.reportOn, ignoreGlobs: ignoreGlobs, - basePath: env.baseDirectory); + basePath: env.baseDirectory, + includeUncovered: (String path) => env.includeUncovered); } final outputSink = @@ -196,6 +200,8 @@ Environment parseArgs(List arguments, CoverageOptions defaultOptions) { help: 'check for coverage ignore comments.' ' Not supported in web coverage.', ) + ..addFlag('include-uncovered', + negatable: false, help: 'Include uncovered Dart files in the output') ..addMultiOption( 'ignore-files', defaultsTo: [], @@ -308,6 +314,9 @@ Environment parseArgs(List arguments, CoverageOptions defaultOptions) { final checkIgnore = args['check-ignore'] as bool; final ignoredGlobs = args['ignore-files'] as List; final verbose = args['verbose'] as bool; + final includeUncovered = args['include-uncovered'] as bool; + print('includeUncovered: $includeUncovered'); + return Environment( baseDirectory: baseDirectory, bazel: bazel, @@ -325,7 +334,8 @@ Environment parseArgs(List arguments, CoverageOptions defaultOptions) { ignoreFiles: ignoredGlobs, sdkRoot: sdkRoot, verbose: verbose, - workers: workers); + workers: workers, + includeUncovered: includeUncovered); } /// Given an absolute path absPath, this function returns a [List] of files diff --git a/pkgs/coverage/lib/src/formatter.dart b/pkgs/coverage/lib/src/formatter.dart index a37df73b7f..fa840bf82c 100644 --- a/pkgs/coverage/lib/src/formatter.dart +++ b/pkgs/coverage/lib/src/formatter.dart @@ -2,8 +2,10 @@ // 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:io'; import 'package:glob/glob.dart'; import 'package:path/path.dart' as p; +import 'package:yaml/yaml.dart'; import 'hitmap.dart'; import 'resolver.dart'; @@ -78,11 +80,13 @@ 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(); for (final entry in entries) { final v = entry.value; @@ -129,6 +133,65 @@ extension FileHitMapsFormatter on Map { buf.write('end_of_record\n'); } + if (includeUncovered != null) { + // Step 1: Identify all Dart files + final allFiles = _findAllDartFiles(reportOn: reportOn); + print('detected files: $allFiles'); + + // Step 2: Identify covered files + final coveredFiles = Map.fromEntries(entries + .where((entry) => entry.value.lineHits.values.any((hit) => hit > 0))); + + // check if the file is covered or no. + final packageName = getPackageName(); + + final uncoveredFiles = []; + for (final file in allFiles) { + final pkgUri = toPackageUri(file, packageName); + if (!coveredFiles.containsKey(pkgUri)) { + uncoveredFiles.add(file); + } + } + + print('Uncovered Dart files:'); + for (final file in uncoveredFiles) { + print(file); + } + + //formatlcov for including uncovered. + final uncoveredBuf = StringBuffer(); + + for (final file in uncoveredFiles) { + if (!pathFilter(file)) continue; + + final lines = File(file).readAsLinesSync(); + var displayPath = file; + if (basePath != null) { + displayPath = p.relative(file, from: basePath); + } + displayPath = + displayPath.replaceAll('\\', '/'); // For Windows compatibility + + uncoveredBuf.writeln('SF:$displayPath'); + + var lineNumber = 1; + var realLines = 0; + for (final line in lines) { + final trimmed = line.trim(); + if (trimmed.isNotEmpty && !trimmed.startsWith('//')) { + uncoveredBuf.writeln('DA:$lineNumber,0'); + realLines++; + } + lineNumber++; + } + + uncoveredBuf.writeln('LF:$realLines'); + uncoveredBuf.writeln('LH:0'); + uncoveredBuf.writeln('end_of_record'); + } + + buf.write(uncoveredBuf.toString()); + } return buf.toString(); } @@ -141,9 +204,11 @@ extension FileHitMapsFormatter on Map { Resolver resolver, Loader loader, { List? reportOn, + String? basePath, Set? ignoreGlobs, bool reportFuncs = false, bool reportBranches = false, + bool Function(String path)? includeUncovered, }) async { final pathFilter = _getPathFilter( reportOn: reportOn, @@ -193,8 +258,98 @@ extension FileHitMapsFormatter on Map { } } + if (includeUncovered != null) { + // Step 1: Identify all Dart files + final allFiles = _findAllDartFiles(reportOn: reportOn); + print('detected files: $allFiles'); + + // Step 2: Identify covered files + final coveredFiles = Map.fromEntries(entries + .where((entry) => entry.value.lineHits.values.any((hit) => hit > 0))); + + // check if the file is covered or no. + final packageName = getPackageName(); + + final uncoveredFiles = []; + for (final file in allFiles) { + final pkgUri = toPackageUri(file, packageName); + if (!coveredFiles.containsKey(pkgUri)) { + uncoveredFiles.add(file); + } + } + + print('Uncovered Dart files:'); + for (final file in uncoveredFiles) { + print(file); + } + + //formatlcov for including uncovered. + final uncoveredBuf = StringBuffer(); + + for (final file in uncoveredFiles) { + if (!pathFilter(file)) continue; + + final lines = File(file).readAsLinesSync(); + var displayPath = file; + if (basePath != null) { + displayPath = p.relative(file, from: basePath); + } + displayPath = + displayPath.replaceAll('\\', '/'); // For Windows compatibility + + uncoveredBuf.writeln('SF:$displayPath'); + + var lineNumber = 1; + var realLines = 0; + for (final line in lines) { + final trimmed = line.trim(); + if (trimmed.isNotEmpty && !trimmed.startsWith('//')) { + uncoveredBuf.writeln('DA:$lineNumber,0'); + realLines++; + } + lineNumber++; + } + + uncoveredBuf.writeln('LF:$realLines'); + uncoveredBuf.writeln('LH:0'); + uncoveredBuf.writeln('end_of_record'); + } + + buf.write(uncoveredBuf.toString()); + } + return buf.toString(); } + + List _findAllDartFiles({List? reportOn}) { + final files = []; + final roots = reportOn ?? ['lib/src']; + for (final root in roots) { + final dir = Directory(root); + if (!dir.existsSync()) continue; + final dartFiles = dir + .listSync(recursive: true) + .whereType() + .where((file) => file.path.endsWith('.dart')); + files.addAll(dartFiles.map((f) => f.path)); + } + return files; + } + + String getPackageName() { + final pubspecFile = File('pubspec.yaml'); + final doc = loadYaml(pubspecFile.readAsStringSync()); + return doc['name'] as String; + } + + String toPackageUri(String filePath, String packageName) { + if (filePath.startsWith('lib${Platform.pathSeparator}')) { + final relativePath = p.relative(filePath, from: 'lib'); + return 'package:$packageName/$relativePath'.replaceAll('\\', '/'); + } + return filePath.replaceAll( + '\\', '/'); // fallback (or handle `src/`, etc. if needed) + } } const _prefix = ' '; diff --git a/pkgs/coverage/pubspec.yaml b/pkgs/coverage/pubspec.yaml index 6bd452470f..d0df4ebf9b 100644 --- a/pkgs/coverage/pubspec.yaml +++ b/pkgs/coverage/pubspec.yaml @@ -18,6 +18,7 @@ dependencies: source_maps: ^0.10.10 stack_trace: ^1.10.0 vm_service: '>=12.0.0 <16.0.0' + yaml: ^3.1.3 dev_dependencies: benchmark_harness: ^2.2.0