From 3f46bda7047a3b6bb9fe778887174209b835d504 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Mon, 29 Jun 2026 04:08:19 -0400 Subject: refactor: normalize module package headers and enforce them The first-line header on 33 modules named the file without its .el extension (;;; font-config --- ... rather than ;;; font-config.el --- ...), the form checkdoc and package-lint expect and the other modules already use. I normalized all 33 to the canonical ;;; name.el --- summary shape. The change is line 1 only. A new test, test-meta-package-headers.el, locks the convention. It checks every module for the canonical first line, Commentary before Code, a provide footer, and no BOM, and unit-tests the checker against each malformed shape so the guard itself is proven. --- tests/test-meta-package-headers.el | 98 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 tests/test-meta-package-headers.el (limited to 'tests/test-meta-package-headers.el') diff --git a/tests/test-meta-package-headers.el b/tests/test-meta-package-headers.el new file mode 100644 index 000000000..f9b57cbfc --- /dev/null +++ b/tests/test-meta-package-headers.el @@ -0,0 +1,98 @@ +;;; test-meta-package-headers.el --- Enforce Elisp package-header conventions -*- lexical-binding: t; -*- + +;;; Commentary: +;; Checks that every owned active config module follows the standard Emacs +;; Library Header conventions -- the part test-init-module-headers.el does not +;; cover (it enforces the load-graph metadata block inside the Commentary): +;; +;; 1. First line is ;;; NAME.el --- SUMMARY -*- ... -*- (name carries the +;; .el, summary present, file-local-variable cookie present). +;; 2. ;;; Commentary: appears before ;;; Code:. +;; 3. A (provide 'NAME) footer, so the file is require-able. +;; 4. No UTF-8 BOM before the header. +;; +;; Scope is modules/*.el, the owned active module set. Vendored (custom/), +;; generated (themes/, browser-choice.el), archived (archive/), and private +;; (*.local.el) files are out of scope by design -- classifying those is the +;; file-class policy task, not this test. The checker reads files on disk +;; without loading them, so it adds no startup or package dependency. + +;;; Code: + +(require 'ert) + +(defconst test-pkg-header--exempt '() + "Basenames under modules/ exempt from the package-header checks. +Empty today. Add a basename with a comment when a module is intentionally +shaped differently, so the exemption is explicit rather than silent.") + +(defun test-pkg-header--check (name text) + "Return the list of violation symbols for module NAME given file TEXT. +NAME is the basename (e.g. \"font-config.el\"). An empty list means the +file is conformant. Possible symbols: `bom', `header', `markers', +`order', `provide'." + (let ((violations '())) + (when (string-prefix-p "" text) + (push 'bom violations)) + (let ((first-line (car (split-string text "\n")))) + (unless (string-match-p + (concat "\\`;;; " (regexp-quote name) " --- .+-\\*-.*-\\*-") + first-line) + (push 'header violations))) + (let ((commentary (string-match "^;;; Commentary:" text)) + (code (string-match "^;;; Code:" text))) + (cond ((or (null commentary) (null code)) (push 'markers violations)) + ((>= commentary code) (push 'order violations)))) + (let ((stem (file-name-sans-extension name))) + (unless (string-match-p (concat "^(provide '" (regexp-quote stem) ")") text) + (push 'provide violations))) + (nreverse violations))) + +(ert-deftest test-pkg-header-checker-flags-malformed () + "Error: the checker catches each malformed shape." + (should (memq 'bom + (test-pkg-header--check + "foo.el" + ";;; foo.el --- x -*- lexical-binding: t; -*-\n;;; Commentary:\n;;; Code:\n(provide 'foo)"))) + (should (memq 'header + (test-pkg-header--check + "foo.el" + ";;; foo --- x -*- lexical-binding: t; -*-\n;;; Commentary:\n;;; Code:\n(provide 'foo)"))) + (should (memq 'order + (test-pkg-header--check + "foo.el" + ";;; foo.el --- x -*- lexical-binding: t; -*-\n;;; Code:\n;;; Commentary:\n(provide 'foo)"))) + (should (memq 'provide + (test-pkg-header--check + "foo.el" + ";;; foo.el --- x -*- lexical-binding: t; -*-\n;;; Commentary:\n;;; Code:\n")))) + +(ert-deftest test-pkg-header-checker-passes-conformant () + "Normal: a well-formed module yields no violations." + (should-not (test-pkg-header--check + "foo.el" + ";;; foo.el --- A thing -*- lexical-binding: t; -*-\n;;; Commentary:\n;; doc\n;;; Code:\n(provide 'foo)\n"))) + +(ert-deftest test-pkg-header-checker-boundary-empty () + "Boundary: empty file text reports every applicable violation, no crash." + (let ((v (test-pkg-header--check "foo.el" ""))) + (should (memq 'header v)) + (should (memq 'markers v)) + (should (memq 'provide v)))) + +(ert-deftest test-pkg-header-all-modules-conform () + "Normal: every modules/*.el passes the package-header checks." + (let ((dir (expand-file-name "modules" user-emacs-directory)) + (bad '())) + (dolist (file (directory-files dir t "\\.el\\'")) + (let ((name (file-name-nondirectory file))) + (unless (member name test-pkg-header--exempt) + (let* ((text (with-temp-buffer + (insert-file-contents file) + (buffer-string))) + (violations (test-pkg-header--check name text))) + (when violations (push (cons name violations) bad)))))) + (should-not bad))) + +(provide 'test-meta-package-headers) +;;; test-meta-package-headers.el ends here -- cgit v1.2.3