aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-07-10 18:45:49 -0500
committerCraig Jennings <c@cjennings.net>2026-07-10 18:45:49 -0500
commitdfdb35800b613f69678de712f96a91cda8f30c32 (patch)
treec124f162d79c6392e82745cbc5fa92ea84a571b7
parent326ec0198b57855ed61523ea2f476b8e88cd3d50 (diff)
downloaddotemacs-dfdb35800b613f69678de712f96a91cda8f30c32.tar.gz
dotemacs-dfdb35800b613f69678de712f96a91cda8f30c32.zip
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.
-rw-r--r--modules/prog-general.el105
-rw-r--r--modules/prog-lsp.el110
-rw-r--r--tests/test-prog-general-lsp.el148
-rw-r--r--tests/test-prog-lsp--add-file-watch-ignored-extras.el116
-rw-r--r--tests/test-prog-lsp.el66
5 files changed, 242 insertions, 303 deletions
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 <c@cjennings.net>
-
-;;; 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 "\\_<lsp-eldoc-hook\\_>" 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 "\\_<lsp-eldoc-hook\\_>" 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