aboutsummaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-04-23 01:07:52 -0500
committerCraig Jennings <c@cjennings.net>2026-04-23 01:07:52 -0500
commit3fa47b1a3b7dd8d3a4f00f117e6f97caf55a3c5d (patch)
tree41a88535e7dfed8f48107936468bf44adc5825a4 /modules
parenta97266c0e89ef8560824789063512d2613849fc9 (diff)
downloaddotemacs-3fa47b1a3b7dd8d3a4f00f117e6f97caf55a3c5d.tar.gz
dotemacs-3fa47b1a3b7dd8d3a4f00f117e6f97caf55a3c5d.zip
feat(coverage): add format-report helper for the report buffer
Pure helper that renders intersect records into the text shown in the coverage report buffer. Takes the list of per-file plists from cj/--coverage-intersect and a scope label, returns the formatted string. Output has three sections depending on what's present: - "Uncovered lines" — one line per uncovered line, formatted as "<path>:<line>: uncovered" so compilation-mode's default regex picks them up for next-error navigation. - "Not tracked" — files changed in the diff but absent from the coverage data (READMEs, test files, config). - "Fully covered" — tracked files where every changed line is covered. Files with empty :changed-lines (deletion-only hunks) are omitted. Summary counts cover only tracked files, so an all-README change shows "0 of 0" rather than a misleading percentage over nothing. Tests cover Normal (partial, fully covered, mixed sections), Boundary (empty records, 100% coverage with no uncovered section, only-not-tracked case, deletion-only exclusion), and the output format that next-error relies on.
Diffstat (limited to 'modules')
-rw-r--r--modules/coverage-core.el70
1 files changed, 70 insertions, 0 deletions
diff --git a/modules/coverage-core.el b/modules/coverage-core.el
index 2209b2f7..bda90612 100644
--- a/modules/coverage-core.el
+++ b/modules/coverage-core.el
@@ -227,5 +227,75 @@ can be classified as covered or uncovered."
records)))
(nreverse records)))
+(defun cj/--coverage-format-report (records scope-label)
+ "Render RECORDS as a text report for SCOPE-LABEL.
+RECORDS is the list of plists produced by `cj/--coverage-intersect'.
+SCOPE-LABEL is the human-readable scope name (e.g. \"Staged\").
+Returns a string ready to insert into a compilation-mode buffer.
+
+Uncovered-line entries use the format \"<path>:<line>: uncovered\"
+so `compilation-error-regexp-alist' picks them up for
+`next-error' / `previous-error' navigation.
+
+Files with an empty :changed-lines (deletion-only hunks) are
+omitted from the display. The summary counts only tracked files."
+ (if (null records)
+ (format "Coverage Report — %s\n\nNo changes in this scope; nothing to report.\n"
+ scope-label)
+ (let (partial fully-covered not-tracked
+ (total-covered 0)
+ (total-tracked 0))
+ (dolist (rec records)
+ (let ((changed (plist-get rec :changed-lines))
+ (tracked (plist-get rec :tracked))
+ (uncovered (plist-get rec :uncovered-lines))
+ (covered (plist-get rec :covered-lines)))
+ (cond
+ ((null changed) nil) ; deletion-only; skip
+ ((not tracked)
+ (push rec not-tracked))
+ (uncovered
+ (push rec partial)
+ (setq total-covered (+ total-covered (length covered))
+ total-tracked (+ total-tracked (length changed))))
+ (t
+ (push rec fully-covered)
+ (setq total-covered (+ total-covered (length covered))
+ total-tracked (+ total-tracked (length changed)))))))
+ (setq partial (nreverse partial)
+ fully-covered (nreverse fully-covered)
+ not-tracked (nreverse not-tracked))
+ (with-temp-buffer
+ (let* ((header (format "Coverage Report — %s" scope-label))
+ (pct (if (> total-tracked 0)
+ (/ (* 100.0 total-covered) total-tracked)
+ 0.0)))
+ (insert header "\n")
+ (insert (make-string (length header) ?=) "\n\n")
+ (insert (format "Summary: %d of %d changed lines covered (%.1f%%)\n\n"
+ total-covered total-tracked pct)))
+ (when partial
+ (insert "Uncovered lines:\n")
+ (dolist (rec partial)
+ (dolist (line (plist-get rec :uncovered-lines))
+ (insert (format " %s:%d: uncovered\n"
+ (plist-get rec :path) line))))
+ (insert "\n"))
+ (when not-tracked
+ (insert "Not tracked (coverage data unavailable):\n")
+ (dolist (rec not-tracked)
+ (insert (format " %s (%d lines changed)\n"
+ (plist-get rec :path)
+ (length (plist-get rec :changed-lines)))))
+ (insert "\n"))
+ (when fully-covered
+ (insert "Fully covered:\n")
+ (dolist (rec fully-covered)
+ (let ((cnt (length (plist-get rec :covered-lines))))
+ (insert (format " %s (%d/%d)\n"
+ (plist-get rec :path) cnt cnt))))
+ (insert "\n"))
+ (buffer-string)))))
+
(provide 'coverage-core)
;;; coverage-core.el ends here