Ensure .dir-locals-2.el behavior as documented (bug#75890)

* lisp/files.el
(dir-locals--all-files): New &optional 'base-el-only' argument.
(dir-locals--base-file): New function.
(dir-locals-find-file): 'locate-dominating-file' only for the base
.dir-locals.el.

* test/lisp/files-tests.el
(files-test-dir-locals-2-solo): New test.
* test/lisp/files-resources/dir-locals-2-solo: New test support.
(files-test-dir-locals-2-paired): New test.
* test/lisp/files-resources/dir-locals-and-2: New test support.
This commit is contained in:
shipmints 2025-02-16 14:30:45 -05:00 committed by Stefan Monnier
parent 893c40c63e
commit 81c21d89ed
7 changed files with 38 additions and 8 deletions

View file

@ -4713,21 +4713,22 @@ the \".dir-locals.el\".
See Info node `(elisp)Directory Local Variables' for details.")
(defun dir-locals--all-files (directory)
(defun dir-locals--all-files (directory &optional base-el-only)
"Return a list of all readable dir-locals files in DIRECTORY.
The returned list is sorted by increasing priority. That is,
values specified in the last file should take precedence over
those in the first."
(when (file-readable-p directory)
(let* ((file-1 (expand-file-name (if (eq system-type 'ms-dos)
(dosified-file-name dir-locals-file)
dir-locals-file)
directory))
(dosified-file-name dir-locals-file)
dir-locals-file)
directory))
(file-2 (when (string-match "\\.el\\'" file-1)
(replace-match "-2.el" t nil file-1)))
(out nil))
;; The order here is important.
(dolist (f (list file-2 file-1))
out)
(dolist (f (or (and base-el-only (list file-1))
;; The order here is important.
(list file-2 file-1)))
(when (and f
(file-readable-p f)
;; FIXME: Aren't file-regular-p and
@ -4737,6 +4738,10 @@ those in the first."
(push f out)))
out)))
(defun dir-locals--base-file (directory)
"Return readable `dir-locals-file' in DIRECTORY, or nil."
(dir-locals--all-files directory 'base-el-only))
(defun dir-locals-find-file (file)
"Find the directory-local variables for FILE.
This searches upward in the directory tree from FILE.
@ -4758,7 +4763,7 @@ This function returns either:
entry."
(setq file (expand-file-name file))
(let* ((locals-dir (locate-dominating-file (file-name-directory file)
#'dir-locals--all-files))
#'dir-locals--base-file))
dir-elt)
;; `locate-dominating-file' may have abbreviated the name.
(when locals-dir

View file

@ -0,0 +1 @@
((nil . ((dir-locals-2-loaded . t))))

View file

@ -0,0 +1,3 @@
# Used by files-test.el.
# Due to solo .dir-locals-2.el, the local variable `dir-locals-2-loaded'
# should be undefined.

View file

@ -0,0 +1 @@
((nil . ((dir-locals-2-loaded . t))))

View file

@ -0,0 +1 @@
((nil . ((dir-locals-loaded . t))))

View file

@ -0,0 +1,4 @@
# Used by files-test.el.
# .dir-locals.el and .dir-locals-2.el should define:
# local variable `dir-locals-loaded'
# local variable `dir-locals-2-loaded'

View file

@ -1782,6 +1782,21 @@ set to."
;; Invocation through env, with modified environment.
(files-tests--check-shebang "#!/usr/bin/env -S PYTHONPATH=/...:${PYTHONPATH} python" 'python-base-mode))
(ert-deftest files-test-dir-locals-2-solo ()
"Ensure that solo `.dir-locals-2.el' is ignored."
(with-current-buffer
(find-file-noselect (ert-resource-file
(concat "dir-locals-2-solo/dir-locals-2-solo.txt")))
(should-not (local-variable-p 'dir-locals-2-loaded))))
(ert-deftest files-test-dir-locals-2-paired ()
"Ensure that `.dir-locals-2.el' is loaded, if paired."
(let ((enable-local-variables :all))
(with-current-buffer (find-file-noselect
(ert-resource-file (concat "dir-locals-and-2/dir-locals-and-2.txt")))
(should (local-variable-p 'dir-locals-loaded))
(should (local-variable-p 'dir-locals-2-loaded)))))
(ert-deftest files-test-dir-locals-auto-mode-alist ()
"Test an `auto-mode-alist' entry in `.dir-locals.el'"
(find-file (ert-resource-file "whatever.quux"))