aboutsummaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-04-22 17:12:41 -0500
committerCraig Jennings <c@cjennings.net>2026-04-22 17:12:41 -0500
commit071b25d43ad21eb1f5e7e74822bbf2cfa8f58b4e (patch)
treebd84c1ddcbfe53b1d5e7fb736b66bd7084599124 /modules
parent3d905270b48ab36f880d686a9c85f157c0460898 (diff)
downloaddotemacs-071b25d43ad21eb1f5e7e74822bbf2cfa8f58b4e.tar.gz
dotemacs-071b25d43ad21eb1f5e7e74822bbf2cfa8f58b4e.zip
feat(coverage): add backend registry
The coverage-core module now has a registry protocol so per-language backends can plug in without touching the core. A backend is a plist with :name, :detect, :run, and :lcov-path. cj/coverage-register-backend appends to cj/coverage-backends, or replaces an existing entry with the same :name at its original position (so first-registered wins on ties). cj/--coverage-backend-for-project resolves which backend applies to a project root. Resolution order: 1. An OVERRIDE argument (typically buffer-local cj/coverage-backend from .dir-locals.el) wins if supplied, and errors if it names an unregistered backend. 2. Otherwise, walk the registry in order and return the first backend whose :detect returns non-nil for the given root. Tests cover Normal (register and retrieve, re-register replaces in place, first detect wins), Boundary (empty registry, no match, override bypasses detect, detect receives the root), and Error (override names an unknown backend). With the registry in place, the elisp backend (and later python / typescript / go) can self-register on load without any changes to coverage-core.
Diffstat (limited to 'modules')
-rw-r--r--modules/coverage-core.el53
1 files changed, 53 insertions, 0 deletions
diff --git a/modules/coverage-core.el b/modules/coverage-core.el
index e7540434..f1e8ae88 100644
--- a/modules/coverage-core.el
+++ b/modules/coverage-core.el
@@ -12,6 +12,59 @@
;;; Code:
+(require 'seq)
+
+(defvar cj/coverage-backends nil
+ "Registry of coverage backends in priority order.
+Each entry is a plist with at least :name, :detect, :run, and :lcov-path.
+Use `cj/coverage-register-backend' to add or replace an entry.")
+
+(defvar-local cj/coverage-backend nil
+ "Override: name of the coverage backend to use for the current project.
+When nil (the default), resolution runs each registered backend's :detect
+function in registration order. Typically set buffer-locally via
+`.dir-locals.el' to pin a specific backend.")
+
+(defun cj/coverage-register-backend (backend)
+ "Register BACKEND, a plist with :name, :detect, :run, :lcov-path.
+Appends to `cj/coverage-backends' at the end, or replaces the existing
+entry with the same :name in its current position."
+ (let ((name (plist-get backend :name)))
+ (if (cj/--coverage-backend-by-name name)
+ (setq cj/coverage-backends
+ (mapcar (lambda (b)
+ (if (eq (plist-get b :name) name) backend b))
+ cj/coverage-backends))
+ (setq cj/coverage-backends
+ (append cj/coverage-backends (list backend))))))
+
+(defun cj/--coverage-backend-by-name (name)
+ "Return the registered backend whose :name equals NAME, or nil."
+ (seq-find (lambda (b) (eq name (plist-get b :name)))
+ cj/coverage-backends))
+
+(defun cj/--coverage-backend-for-project (root &optional override)
+ "Resolve the coverage backend to use for ROOT.
+OVERRIDE, if non-nil, is a backend name symbol (typically the value of
+`cj/coverage-backend' from .dir-locals.el). When given, the named
+backend is returned regardless of any :detect functions. Signals
+`user-error' when OVERRIDE names a backend that isn't registered.
+
+When OVERRIDE is nil, each backend's :detect is called in turn with
+ROOT as its sole argument; the first that returns non-nil wins.
+Returns the backend plist, or nil when no backend matches."
+ (cond
+ (override
+ (or (cj/--coverage-backend-by-name override)
+ (user-error
+ "Unknown coverage backend: %s (registered: %s)"
+ override
+ (mapcar (lambda (b) (plist-get b :name)) cj/coverage-backends))))
+ (t
+ (seq-find (lambda (backend)
+ (funcall (plist-get backend :detect) root))
+ cj/coverage-backends))))
+
(defun cj/--coverage-parse-lcov (file)
"Parse FILE as LCOV and return a hash table of covered lines.
Keys are source-file paths (strings). Values are hash tables whose