From dfdb35800b613f69678de712f96a91cda8f30c32 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Fri, 10 Jul 2026 18:45:49 -0500 Subject: refactor(lsp): make prog-general the single owner of LSP config prog-lsp.el was required by nothing. The (require 'prog-lsp) line was commented out and later deleted, so none of its config ever ran. prog-general's LSP block was the only policy in effect, which is why the build/cache file-watch ignore list added in April never applied and the "watch all files?" prompt kept firing. Fold prog-lsp's settings into prog-general and delete the module. This adopts its quiet, performance-first policy. Doc popups, symbol highlighting, snippets, signature help, and modeline diagnostics go off. Idle-delay drops to 0.5, the eldoc provider is stripped from the global hook, and read-process-output-max rises to 1MB. The file-watch ignore list now applies: the daemon carries all thirteen build/cache patterns, six of which lsp-mode already ships by default. Language-specific server variables and the lsp-deferred mode hooks stay in the per-language modules. Consolidate the two prog-lsp test files into one test-prog-general-lsp.el: twelve tests covering the file-watch and eldoc helpers across Normal, Boundary, and Error, plus the load-time invariants. --- modules/prog-general.el | 105 +++++++++++++-- modules/prog-lsp.el | 110 --------------- tests/test-prog-general-lsp.el | 148 +++++++++++++++++++++ ...test-prog-lsp--add-file-watch-ignored-extras.el | 116 ---------------- tests/test-prog-lsp.el | 66 --------- 5 files changed, 242 insertions(+), 303 deletions(-) delete mode 100644 modules/prog-lsp.el create mode 100644 tests/test-prog-general-lsp.el delete mode 100644 tests/test-prog-lsp--add-file-watch-ignored-extras.el delete mode 100644 tests/test-prog-lsp.el diff --git a/modules/prog-general.el b/modules/prog-general.el index 831f43cb..89771cbf 100644 --- a/modules/prog-general.el +++ b/modules/prog-general.el @@ -409,46 +409,129 @@ defer to `electric-pair-default-inhibit' for any other CHAR." (setq ws-butler-convert-leading-tabs-or-spaces t)) ;; ------------------------------------ LSP ------------------------------------ -;; Language Server Protocol for intelligent code completion and navigation -;; Works with multiple languages: C, Python, Go, Rust, JavaScript, etc. - -;; Forward declarations for LSP variables +;; Language Server Protocol for intelligent code completion and navigation. +;; Single owner of generic LSP policy (prog-lsp.el folded in and removed +;; 2026-07-10). Language-specific server variables and the lsp-deferred mode +;; hooks stay in the per-language modules. Reference for what to turn off: +;; https://emacs-lsp.github.io/lsp-mode/tutorials/how-to-turn-off/ + +;; Forward declarations for byte-compile. lsp-mode's defcustoms aren't loaded +;; under `make test' (no package-initialize) and use-package defers the package +;; via :commands, so these vars are unknown at compile time without declaring. +(defvar lsp-mode-map) +(defvar eldoc-documentation-functions) +(defvar lsp-file-watch-ignored-directories) +(defvar lsp-enable-remote) +(defvar lsp-auto-guess-root) +(defvar lsp-restart) (defvar lsp-idle-delay) (defvar lsp-log-io) (defvar lsp-enable-folding) +(defvar lsp-enable-imenu) (defvar lsp-enable-snippet) +(defvar lsp-enable-symbol-highlighting) +(defvar lsp-enable-on-type-formatting) +(defvar lsp-signature-auto-activate) +(defvar lsp-signature-render-documentation) +(defvar lsp-modeline-code-actions-enable) +(defvar lsp-modeline-diagnostics-enable) (defvar lsp-headerline-breadcrumb-enable) +(defvar lsp-semantic-tokens-enable) (defvar lsp-completion-provider) (defvar lsp-completion-show-detail) (defvar lsp-completion-show-kind) +(declare-function lsp-eldoc-function "lsp-mode") + +;; File-watch ignore patterns. lsp-mode prompts when a workspace exceeds +;; `lsp-file-watch-threshold' (1000) directories. Real source repos cross that +;; once node_modules, build outputs, and language caches are counted. These +;; extend the lsp-mode defaults (.git, .svn, .idea, ...) instead of replacing +;; them. A buffer-local override via `.dir-locals.el' doesn't work: lsp-mode +;; reads the global value at workspace init, not the buffer-local one, so the +;; defaults live here globally. +(defvar cj/lsp-file-watch-ignored-extras + '("[/\\\\]node_modules\\'" + "[/\\\\]\\.ruff_cache\\'" + "[/\\\\]dist\\'" + "[/\\\\]coverage\\'" + "[/\\\\]test-results\\'" + "[/\\\\]playwright-report\\'" + "[/\\\\]tf[/\\\\]\\.terraform\\'" + "[/\\\\]__pycache__\\'" + "[/\\\\]\\.venv\\'" + "[/\\\\]venv\\'" + "[/\\\\]\\.pytest_cache\\'" + "[/\\\\]\\.mypy_cache\\'" + "[/\\\\]target\\'") + "Build/cache directory patterns to add to `lsp-file-watch-ignored-directories'. +Each entry is an Emacs regex matching a path ending in the named directory.") + +(defun cj/lsp--add-file-watch-ignored-extras () + "Append `cj/lsp-file-watch-ignored-extras' to lsp-mode's ignore list. +Idempotent — `add-to-list' skips patterns already present." + (dolist (pattern cj/lsp-file-watch-ignored-extras) + (add-to-list 'lsp-file-watch-ignored-directories pattern))) + +(defun cj/lsp--remove-eldoc-provider-global () + "Remove lsp-mode's provider from the global `eldoc-documentation-functions'. +Run once after lsp-mode loads. The previous per-buffer removal raced +lsp's own buffer-local add: the buffer-local remove fired before lsp +populated the buffer-local hook (lsp inherits the global default and +mutates from there), so the buffer-local hook ended up holding the +provider anyway. Removing globally before lsp ever attaches a buffer +makes the absence stick for every subsequent lsp-managed buffer." + (remove-hook 'eldoc-documentation-functions #'lsp-eldoc-function)) (use-package lsp-mode :commands (lsp lsp-deferred) + :bind (:map lsp-mode-map + ("C-c d" . lsp-describe-thing-at-point) + ("C-c a" . lsp-execute-code-action)) :custom (lsp-keymap-prefix "C-c l") ;; LSP commands under C-c l prefix + :init + (setq lsp-enable-remote nil) ;; Don't start LSP on TRAMP files (slow, prompts for root) :config - ;; Performance optimizations - (setq lsp-idle-delay 0.1) + ;; Quiet, performance-first policy + (setq lsp-idle-delay 0.5) (setq lsp-log-io nil) + (setq lsp-auto-guess-root t) + (setq lsp-restart 'auto-restart) (setq lsp-enable-folding nil) - (setq lsp-enable-snippet t) + (setq lsp-enable-imenu nil) + (setq lsp-enable-snippet nil) + (setq lsp-enable-symbol-highlighting nil) + (setq lsp-enable-on-type-formatting nil) + (setq lsp-signature-auto-activate nil) + (setq lsp-signature-render-documentation nil) + (setq lsp-modeline-code-actions-enable nil) + (setq lsp-modeline-diagnostics-enable nil) (setq lsp-headerline-breadcrumb-enable nil) - - ;; Improve completion + (setq lsp-semantic-tokens-enable nil) + (setq read-process-output-max (* 1024 1024)) ;; 1MB + ;; Completion (setq lsp-completion-provider :capf) (setq lsp-completion-show-detail t) - (setq lsp-completion-show-kind t)) + (setq lsp-completion-show-kind t) + ;; Strip lsp's global eldoc provider once (see the helper for the race note). + (cj/lsp--remove-eldoc-provider-global) + (cj/lsp--add-file-watch-ignored-extras)) (use-package lsp-ui :after lsp-mode :commands lsp-ui-mode :custom - (lsp-ui-doc-enable t) + (lsp-ui-doc-enable nil) (lsp-ui-doc-position 'at-point) (lsp-ui-doc-delay 0.5) + (lsp-ui-doc-header t) + (lsp-ui-doc-include-signature t) + (lsp-ui-doc-border (face-foreground 'default)) (lsp-ui-sideline-enable t) (lsp-ui-sideline-show-diagnostics t) (lsp-ui-sideline-show-hover nil) + (lsp-ui-sideline-show-code-actions nil) + (lsp-ui-sideline-delay 0.05) (lsp-ui-peek-enable t) (lsp-ui-peek-show-directory t)) diff --git a/modules/prog-lsp.el b/modules/prog-lsp.el deleted file mode 100644 index 1c74bcc1..00000000 --- a/modules/prog-lsp.el +++ /dev/null @@ -1,110 +0,0 @@ -;;; prog-lsp.el --- Setup for LSP Mode -*- lexical-binding: t; coding: utf-8; -*- -;; author: Craig Jennings - -;;; Commentary: - -;; good reference as to what to enable/disable in lsp-mode -;; https://emacs-lsp.github.io/lsp-mode/tutorials/how-to-turn-off/ - -;;; Code: - -;; Forward declarations for byte-compile and let-binding under lexical scope. -;; Real definitions are lsp-mode's defcustoms. -(defvar eldoc-documentation-functions) -(defvar lsp-file-watch-ignored-directories) -(defvar lsp-enable-remote) - -(declare-function lsp-eldoc-function "lsp-mode") - -;;;;; --------------------- File-Watch Ignore Patterns --------------------- -;; lsp-mode prompts when a workspace exceeds `lsp-file-watch-threshold' (1000) -;; directories. Real source repos cross that line easily once node_modules, -;; build outputs, and language caches are counted. These patterns extend the -;; lsp-mode defaults (.git, .svn, .idea, ...) instead of replacing them, so the -;; built-in VC/IDE excludes still apply. Buffer-local overrides via -;; `.dir-locals.el' don't work — lsp-mode reads the global value at workspace -;; init, not the buffer-local one. Hence: global defaults here. - -(defvar cj/lsp-file-watch-ignored-extras - '("[/\\\\]node_modules\\'" - "[/\\\\]\\.ruff_cache\\'" - "[/\\\\]dist\\'" - "[/\\\\]coverage\\'" - "[/\\\\]test-results\\'" - "[/\\\\]playwright-report\\'" - "[/\\\\]tf[/\\\\]\\.terraform\\'" - "[/\\\\]__pycache__\\'" - "[/\\\\]\\.venv\\'" - "[/\\\\]venv\\'" - "[/\\\\]\\.pytest_cache\\'" - "[/\\\\]\\.mypy_cache\\'" - "[/\\\\]target\\'") - "Build/cache directory patterns to add to `lsp-file-watch-ignored-directories'. -Each entry is an Emacs regex matching a path ending in the named directory.") - -(defun cj/lsp--add-file-watch-ignored-extras () - "Append `cj/lsp-file-watch-ignored-extras' to lsp-mode's ignore list. -Idempotent — `add-to-list' skips patterns already present." - (dolist (pattern cj/lsp-file-watch-ignored-extras) - (add-to-list 'lsp-file-watch-ignored-directories pattern))) - -(defun cj/lsp--remove-eldoc-provider-global () - "Remove lsp-mode's provider from the global `eldoc-documentation-functions'. -Run once after lsp-mode loads. The previous per-buffer removal raced -lsp's own buffer-local add: the buffer-local remove fired before lsp -populated the buffer-local hook (lsp inherits the global default and -mutates from there), so the buffer-local hook ended up holding the -provider anyway. Removing globally before lsp ever attaches a buffer -makes the absence stick for every subsequent lsp-managed buffer." - (remove-hook 'eldoc-documentation-functions #'lsp-eldoc-function)) - -;;;;; ---------------------------- LSP Mode --------------------------- - -(use-package lsp-mode - :hook - ((c-mode c++-mode go-mode js-mode js-jsx-mode typescript-mode python-mode web-mode) . lsp-deferred) - :commands (lsp) - :bind (:map lsp-mode-map - ("C-c d" . lsp-describe-thing-at-point) - ("C-c a" . lsp-execute-code-action)) - :bind-keymap ("C-c L" . lsp-command-map) - :init - (setq lsp-enable-remote nil) ;; Don't start LSP on TRAMP files (slow, prompts for project root) - :config - (setq lsp-auto-guess-root t) - (setq lsp-log-io nil) - (setq lsp-restart 'auto-restart) - (setq lsp-enable-symbol-highlighting nil) - (setq lsp-enable-on-type-formatting nil) - (setq lsp-signature-auto-activate nil) - (setq lsp-signature-render-documentation nil) - ;; Strip lsp-mode's eldoc provider from the GLOBAL hook value once, - ;; not per buffer. See `cj/lsp--remove-eldoc-provider-global' for - ;; why per-buffer racing didn't stick. - (cj/lsp--remove-eldoc-provider-global) - (setq lsp-modeline-code-actions-enable nil) - (setq lsp-modeline-diagnostics-enable nil) - (setq lsp-headerline-breadcrumb-enable nil) - (setq lsp-semantic-tokens-enable nil) - (setq lsp-enable-folding nil) - (setq lsp-enable-imenu nil) - (setq lsp-enable-snippet nil) - (setq read-process-output-max (* 1024 1024)) ;; 1MB - (setq lsp-idle-delay 0.5) - (cj/lsp--add-file-watch-ignored-extras)) - -;;;;; ----------------------------- LSP UI ---------------------------- - -(use-package lsp-ui - :after lsp-mode - :commands lsp-ui-mode - :config - (setq lsp-ui-doc-enable nil) - (setq lsp-ui-doc-header t) - (setq lsp-ui-doc-include-signature t) - (setq lsp-ui-doc-border (face-foreground 'default)) - (setq lsp-ui-sideline-show-code-actions nil) ;; turn off code actions in sidebar - (setq lsp-ui-sideline-delay 0.05)) - -(provide 'prog-lsp) -;;; prog-lsp.el ends here diff --git a/tests/test-prog-general-lsp.el b/tests/test-prog-general-lsp.el new file mode 100644 index 00000000..e61e9a17 --- /dev/null +++ b/tests/test-prog-general-lsp.el @@ -0,0 +1,148 @@ +;;; test-prog-general-lsp.el --- LSP config + helper tests -*- lexical-binding: t; -*- + +;;; Commentary: +;; Tests for the LSP configuration in prog-general.el, the single owner of +;; generic LSP policy (prog-lsp.el was folded in and removed 2026-07-10). +;; +;; Two layers: +;; - Load-time invariants that hold the moment the config loads, before any +;; server starts: lsp-enable-remote stays nil (so TRAMP files don't auto-start +;; a slow LSP), and no mode accrues a duplicate lsp-deferred entry. +;; - The pure helpers cj/lsp--add-file-watch-ignored-extras and +;; cj/lsp--remove-eldoc-provider-global, exercised directly with Normal / +;; Boundary / Error cases. +;; +;; The quiet-UI :config defaults (snippet off, symbol highlighting off, +;; idle-delay 0.5, ...) are deferred to lsp-mode's own load (see the make-test +;; no-package-initialize note in CLAUDE.md), so they aren't asserted here; the +;; daemon verifies them. + +;;; Code: + +(require 'ert) +(require 'cl-lib) +(require 'use-package) + +;; Declare lsp-mode's defcustom / eldoc's hook as special so `let' binds them +;; dynamically. Real definitions load only when lsp-mode / eldoc activate; in +;; the test environment these provide the value cells the helpers read through. +(defvar lsp-file-watch-ignored-directories nil) +(defvar eldoc-documentation-functions nil) + +(defun lsp-eldoc-function (&rest _args) + "Stub lsp-mode Eldoc function for tests.") + +(require 'prog-general) + +;;; Load-time invariants + +(ert-deftest test-prog-general-lsp-enable-remote-nil () + "Normal: lsp-enable-remote is nil so LSP never auto-starts on TRAMP files." + (should (boundp 'lsp-enable-remote)) + (should (null lsp-enable-remote))) + +(ert-deftest test-prog-general-lsp-no-duplicate-mode-hook () + "Boundary: a mode never holds more than one lsp-deferred entry. +The per-language modules add lsp-deferred to their own mode hooks; add-hook +dedups identical symbols, and this pins that invariant so a future non-symbol +\(lambda) addition that breaks it gets caught." + (dolist (hook '(c-mode-hook python-mode-hook go-ts-mode-hook)) + (when (boundp hook) + (should (>= 1 (cl-count 'lsp-deferred (symbol-value hook))))))) + +;;; File-watch ignore helper — Normal + +(ert-deftest test-prog-general-lsp-file-watch-adds-all-patterns () + "Normal: every entry from `cj/lsp-file-watch-ignored-extras' lands in the list." + (let ((lsp-file-watch-ignored-directories nil)) + (cj/lsp--add-file-watch-ignored-extras) + (should (= (length lsp-file-watch-ignored-directories) + (length cj/lsp-file-watch-ignored-extras))) + (dolist (pattern cj/lsp-file-watch-ignored-extras) + (should (member pattern lsp-file-watch-ignored-directories))))) + +(ert-deftest test-prog-general-lsp-file-watch-extends-not-replaces () + "Normal: pre-existing entries (lsp-mode defaults) are preserved." + (let ((lsp-file-watch-ignored-directories + '("[/\\\\]\\.git\\'" "[/\\\\]\\.svn\\'" "[/\\\\]\\.idea\\'"))) + (cj/lsp--add-file-watch-ignored-extras) + (should (member "[/\\\\]\\.git\\'" lsp-file-watch-ignored-directories)) + (should (member "[/\\\\]\\.svn\\'" lsp-file-watch-ignored-directories)) + (should (member "[/\\\\]\\.idea\\'" lsp-file-watch-ignored-directories)) + (dolist (pattern cj/lsp-file-watch-ignored-extras) + (should (member pattern lsp-file-watch-ignored-directories))))) + +(ert-deftest test-prog-general-lsp-file-watch-key-patterns-present () + "Normal: specific expected directory names appear in the constant." + (dolist (name '("node_modules" "target" "__pycache__" ".venv" "venv" + "dist" "coverage" "test-results" "playwright-report" + ".terraform" ".ruff_cache" ".pytest_cache" ".mypy_cache")) + (should (cl-some (lambda (p) (string-match-p (regexp-quote name) p)) + cj/lsp-file-watch-ignored-extras)))) + +;;; File-watch ignore helper — Boundary + +(ert-deftest test-prog-general-lsp-file-watch-idempotent () + "Boundary: calling twice leaves each pattern present exactly once." + (let ((lsp-file-watch-ignored-directories nil)) + (cj/lsp--add-file-watch-ignored-extras) + (cj/lsp--add-file-watch-ignored-extras) + (should (= (length lsp-file-watch-ignored-directories) + (length cj/lsp-file-watch-ignored-extras))) + (dolist (pattern cj/lsp-file-watch-ignored-extras) + (should (= 1 (cl-count pattern lsp-file-watch-ignored-directories + :test #'equal)))))) + +(ert-deftest test-prog-general-lsp-file-watch-all-patterns-non-empty () + "Boundary: every pattern is a non-empty string." + (dolist (pattern cj/lsp-file-watch-ignored-extras) + (should (stringp pattern)) + (should (not (string-empty-p pattern))))) + +(ert-deftest test-prog-general-lsp-file-watch-all-patterns-valid-regex () + "Boundary: every pattern compiles as a valid Emacs regex." + (dolist (pattern cj/lsp-file-watch-ignored-extras) + (condition-case err + ;; string-match-p compiles the regex; invalid syntax raises invalid-regexp. + (string-match-p pattern "/some/sample/path") + (invalid-regexp + (ert-fail (format "Invalid regex %S: %s" + pattern (error-message-string err))))))) + +;;; File-watch ignore helper — Error + +(ert-deftest test-prog-general-lsp-file-watch-non-list-target () + "Error: non-list target value triggers `add-to-list' wrong-type-argument." + (let ((lsp-file-watch-ignored-directories "not-a-list")) + (should-error (cj/lsp--add-file-watch-ignored-extras) + :type 'wrong-type-argument))) + +;;; Eldoc provider helper + +(ert-deftest test-prog-general-lsp-eldoc-provider-removed-globally () + "Normal: remove lsp-mode's Eldoc provider from the global hook value. +The per-buffer removal this replaced raced lsp-mode's own buffer-local hook +population; removing globally before any LSP buffer attaches makes the absence +stick for every subsequent lsp-managed buffer." + (let ((eldoc-documentation-functions + (list #'lsp-eldoc-function 'eldoc-documentation-default))) + (cj/lsp--remove-eldoc-provider-global) + (should-not (memq #'lsp-eldoc-function eldoc-documentation-functions)) + (should (memq 'eldoc-documentation-default eldoc-documentation-functions)))) + +(ert-deftest test-prog-general-lsp-eldoc-provider-removal-idempotent () + "Boundary: re-running the removal after the provider is gone is a no-op." + (let ((eldoc-documentation-functions '(eldoc-documentation-default))) + (cj/lsp--remove-eldoc-provider-global) + (cj/lsp--remove-eldoc-provider-global) + (should (equal eldoc-documentation-functions + '(eldoc-documentation-default))))) + +(ert-deftest test-prog-general-lsp-no-obsolete-eldoc-hook-reference () + "Regression: prog-general should not reference obsolete `lsp-eldoc-hook'." + (with-temp-buffer + (insert-file-contents (expand-file-name "modules/prog-general.el" user-emacs-directory)) + (should-not (re-search-forward "\\_" nil t)))) + +(provide 'test-prog-general-lsp) +;;; test-prog-general-lsp.el ends here diff --git a/tests/test-prog-lsp--add-file-watch-ignored-extras.el b/tests/test-prog-lsp--add-file-watch-ignored-extras.el deleted file mode 100644 index 9b71cab8..00000000 --- a/tests/test-prog-lsp--add-file-watch-ignored-extras.el +++ /dev/null @@ -1,116 +0,0 @@ -;;; test-prog-lsp--add-file-watch-ignored-extras.el --- Tests for cj/lsp--add-file-watch-ignored-extras -*- lexical-binding: t; -*- - -;;; Commentary: -;; Tests for cj/lsp--add-file-watch-ignored-extras in prog-lsp.el. -;; The function adds project-agnostic build/cache directory patterns to -;; `lsp-file-watch-ignored-directories' without replacing lsp-mode's -;; defaults. Patterns are sourced from `cj/lsp-file-watch-ignored-extras'. - -;;; Code: - -(require 'ert) -(require 'cl-lib) - -;; Declare lsp-mode's defcustom as a special variable so `let' binds it -;; dynamically. Real definition is lsp-mode's; loaded only when use-package -;; activates lsp-mode. In the test environment, this stub provides the value -;; cell `add-to-list' needs. -(defvar lsp-file-watch-ignored-directories nil) -(defvar eldoc-documentation-functions nil) - -(defun lsp-eldoc-function (&rest _args) - "Stub lsp-mode Eldoc function for tests.") - -(require 'prog-lsp) - -;;; Normal Cases - -(ert-deftest test-prog-lsp--add-file-watch-ignored-extras-normal-adds-all-patterns () - "Normal: every entry from `cj/lsp-file-watch-ignored-extras' lands in the list." - (let ((lsp-file-watch-ignored-directories nil)) - (cj/lsp--add-file-watch-ignored-extras) - (should (= (length lsp-file-watch-ignored-directories) - (length cj/lsp-file-watch-ignored-extras))) - (dolist (pattern cj/lsp-file-watch-ignored-extras) - (should (member pattern lsp-file-watch-ignored-directories))))) - -(ert-deftest test-prog-lsp--add-file-watch-ignored-extras-normal-extends-not-replaces () - "Normal: pre-existing entries (lsp-mode defaults) are preserved." - (let ((lsp-file-watch-ignored-directories - '("[/\\\\]\\.git\\'" "[/\\\\]\\.svn\\'" "[/\\\\]\\.idea\\'"))) - (cj/lsp--add-file-watch-ignored-extras) - (should (member "[/\\\\]\\.git\\'" lsp-file-watch-ignored-directories)) - (should (member "[/\\\\]\\.svn\\'" lsp-file-watch-ignored-directories)) - (should (member "[/\\\\]\\.idea\\'" lsp-file-watch-ignored-directories)) - (dolist (pattern cj/lsp-file-watch-ignored-extras) - (should (member pattern lsp-file-watch-ignored-directories))))) - -(ert-deftest test-prog-lsp--add-file-watch-ignored-extras-normal-key-patterns-present () - "Normal: specific expected directory names appear in the constant." - (dolist (name '("node_modules" "target" "__pycache__" ".venv" "venv" - "dist" "coverage" "test-results" "playwright-report" - ".terraform" ".ruff_cache" ".pytest_cache" ".mypy_cache")) - (should (cl-some (lambda (p) (string-match-p (regexp-quote name) p)) - cj/lsp-file-watch-ignored-extras)))) - -;;; Boundary Cases - -(ert-deftest test-prog-lsp--add-file-watch-ignored-extras-boundary-idempotent () - "Boundary: calling twice doesn't duplicate entries." - (let ((lsp-file-watch-ignored-directories nil)) - (cj/lsp--add-file-watch-ignored-extras) - (cj/lsp--add-file-watch-ignored-extras) - (should (= (length lsp-file-watch-ignored-directories) - (length cj/lsp-file-watch-ignored-extras))))) - -(ert-deftest test-prog-lsp--add-file-watch-ignored-extras-boundary-all-patterns-non-empty () - "Boundary: every pattern is a non-empty string." - (dolist (pattern cj/lsp-file-watch-ignored-extras) - (should (stringp pattern)) - (should (not (string-empty-p pattern))))) - -(ert-deftest test-prog-lsp--add-file-watch-ignored-extras-boundary-all-patterns-valid-regex () - "Boundary: every pattern compiles as a valid Emacs regex." - (dolist (pattern cj/lsp-file-watch-ignored-extras) - (condition-case err - ;; string-match-p compiles the regex; invalid syntax raises invalid-regexp. - (string-match-p pattern "/some/sample/path") - (invalid-regexp - (ert-fail (format "Invalid regex %S: %s" - pattern (error-message-string err))))))) - -;;; Error Cases - -(ert-deftest test-prog-lsp--add-file-watch-ignored-extras-error-non-list-target () - "Error: non-list target value triggers `add-to-list' wrong-type-argument." - (let ((lsp-file-watch-ignored-directories "not-a-list")) - (should-error (cj/lsp--add-file-watch-ignored-extras) - :type 'wrong-type-argument))) - -(ert-deftest test-prog-lsp--remove-eldoc-provider-global-removes-from-default () - "Normal: remove lsp-mode's Eldoc provider from the global hook value. -The per-buffer removal that this replaced raced lsp-mode's own buffer- -local hook population; removing globally before any LSP buffer attaches -makes the absence stick for every subsequent lsp-managed buffer." - (let ((eldoc-documentation-functions - '(lsp-eldoc-function eldoc-documentation-default))) - (cj/lsp--remove-eldoc-provider-global) - (should-not (memq #'lsp-eldoc-function eldoc-documentation-functions)) - (should (memq 'eldoc-documentation-default eldoc-documentation-functions)))) - -(ert-deftest test-prog-lsp--remove-eldoc-provider-global-is-idempotent () - "Boundary: re-running the removal after the provider is gone is a no-op." - (let ((eldoc-documentation-functions '(eldoc-documentation-default))) - (cj/lsp--remove-eldoc-provider-global) - (cj/lsp--remove-eldoc-provider-global) - (should (equal eldoc-documentation-functions - '(eldoc-documentation-default))))) - -(ert-deftest test-prog-lsp--module-no-obsolete-lsp-eldoc-hook-reference () - "Regression: prog-lsp should not reference obsolete `lsp-eldoc-hook'." - (with-temp-buffer - (insert-file-contents (expand-file-name "modules/prog-lsp.el" user-emacs-directory)) - (should-not (re-search-forward "\\_" nil t)))) - -(provide 'test-prog-lsp--add-file-watch-ignored-extras) -;;; test-prog-lsp--add-file-watch-ignored-extras.el ends here diff --git a/tests/test-prog-lsp.el b/tests/test-prog-lsp.el deleted file mode 100644 index 7e38111d..00000000 --- a/tests/test-prog-lsp.el +++ /dev/null @@ -1,66 +0,0 @@ -;;; test-prog-lsp.el --- Startup smoke test for LSP config resolution -*- lexical-binding: t; -*- - -;;; Commentary: -;; A narrow smoke test of prog-lsp.el, the central LSP module. It pins the -;; invariants that should hold the moment the config loads, before any server -;; starts: lsp-enable-remote stays nil (so TRAMP files don't auto-start a slow -;; LSP), the file-watch-ignore defaults live in one idempotent place, the eldoc -;; provider is stripped from the global hook, and a mode never accrues a -;; duplicate lsp-deferred entry. The generic :config defaults are deferred to -;; lsp-mode's own load (see the make-test no-package-initialize note in -;; CLAUDE.md), so this tests the top-level :init and helper surface, which runs. - -;;; Code: - -(require 'ert) -(require 'cl-lib) -(require 'use-package) -(require 'prog-lsp) - -;; lsp-mode's defcustom isn't loaded under make test, and prog-lsp's bare -;; `(defvar lsp-file-watch-ignored-directories)' only marks it special within -;; that file's unit. Declare it special here too so the `let' bindings below -;; bind dynamically (the helper reads it through the symbol via add-to-list). -(defvar lsp-file-watch-ignored-directories nil) - -(ert-deftest test-prog-lsp-enable-remote-nil () - "Normal: lsp-enable-remote is nil so LSP never auto-starts on TRAMP files." - (should (boundp 'lsp-enable-remote)) - (should (null lsp-enable-remote))) - -(ert-deftest test-prog-lsp-file-watch-adds-extras () - "Normal: the build/cache ignore patterns get appended to lsp's watch-ignore list." - (let ((lsp-file-watch-ignored-directories '("[/\\\\]\\.git\\'"))) - (cj/lsp--add-file-watch-ignored-extras) - (dolist (pattern cj/lsp-file-watch-ignored-extras) - (should (member pattern lsp-file-watch-ignored-directories))) - (should (member "[/\\\\]\\.git\\'" lsp-file-watch-ignored-directories)))) - -(ert-deftest test-prog-lsp-file-watch-idempotent () - "Boundary: adding the extras twice leaves each pattern present exactly once." - (let ((lsp-file-watch-ignored-directories '())) - (cj/lsp--add-file-watch-ignored-extras) - (cj/lsp--add-file-watch-ignored-extras) - (dolist (pattern cj/lsp-file-watch-ignored-extras) - (should (= 1 (cl-count pattern lsp-file-watch-ignored-directories - :test #'equal)))))) - -(ert-deftest test-prog-lsp-eldoc-provider-removed-globally () - "Normal: the global eldoc provider is stripped so lsp can't reattach it." - (let ((eldoc-documentation-functions - (list #'lsp-eldoc-function #'ignore))) - (cj/lsp--remove-eldoc-provider-global) - (should-not (memq 'lsp-eldoc-function eldoc-documentation-functions)) - (should (memq 'ignore eldoc-documentation-functions)))) - -(ert-deftest test-prog-lsp-no-duplicate-mode-hook () - "Boundary: a mode prog-lsp wires never holds more than one lsp-deferred entry. -prog-lsp and the per-language modules both add lsp-deferred for some modes; -add-hook dedups identical symbols, and this pins that invariant so a future -non-symbol (lambda) addition that breaks it gets caught." - (dolist (hook '(c-mode-hook python-mode-hook go-ts-mode-hook)) - (when (boundp hook) - (should (>= 1 (cl-count 'lsp-deferred (symbol-value hook))))))) - -(provide 'test-prog-lsp) -;;; test-prog-lsp.el ends here -- cgit v1.2.3