branch: elpa/clojure-mode
commit 75fa6336dbfba5cd29a479e59a7dc7ce7f7f4fc2
Author: Bozhidar Batsov <[email protected]>
Commit: Bozhidar Batsov <[email protected]>

    [Fix #687] Improve project root detection with preferred build tool and VCS 
tiebreaker
    
    When multiple build tool files exist in the directory hierarchy (e.g. a
    source file named project.clj inside a deps.edn project), the most nested
    match was always chosen, which could be incorrect.
    
    Add `clojure-preferred-build-tool` defcustom for explicit user preference.
    When unset, use `.git` presence as a tiebreaker before falling back to
    most nested.
---
 CHANGELOG.md                   |  1 +
 clojure-mode.el                | 28 ++++++++++++++++++++++++++--
 test/clojure-mode-util-test.el | 37 ++++++++++++++++++++++++++++++++++++-
 3 files changed, 63 insertions(+), 3 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index ee2a11e67d..24cb6fcf71 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@
 
 ### New features
 
+* [#687](https://github.com/clojure-emacs/clojure-mode/issues/687): Add 
`clojure-preferred-build-tool` to control project root detection when multiple 
build tool files exist. When unset, prefer directories containing `.git` as a 
tiebreaker.
 * [#688](https://github.com/clojure-emacs/clojure-mode/issues/688): Add 
`clojure-discard-face` for `#_` reader discard forms, allowing them to be 
styled differently from comments. Inherits from `font-lock-comment-face` by 
default.
 * Add project root detection for ClojureCLR (`deps-clr.edn`).
 
diff --git a/clojure-mode.el b/clojure-mode.el
index 093f590d94..903cf92ad1 100644
--- a/clojure-mode.el
+++ b/clojure-mode.el
@@ -267,6 +267,22 @@ The prefixes are used to generate the correct namespace."
   :risky t
   :package-version '(clojure-mode . "5.7.0"))
 
+(defcustom clojure-preferred-build-tool nil
+  "Preferred build tool file to identify the project root.
+When multiple build tool files are found in the directory hierarchy,
+this setting controls which one takes precedence.
+
+When nil (the default), prefer directories that also contain a
+version-control marker (`.git').  If that doesn't break the tie,
+fall back to the most nested match.
+
+When set to a string (e.g., \"deps.edn\"), prefer the directory
+containing that specific file."
+  :type '(choice (const :tag "Auto-detect" nil)
+                 (string :tag "Build tool filename"))
+  :safe (lambda (value) (or (null value) (stringp value)))
+  :package-version '(clojure-mode . "5.22.0"))
+
 (defcustom clojure-refactor-map-prefix (kbd "C-c C-r")
   "Clojure refactor keymap prefix."
   :type 'string
@@ -2076,8 +2092,16 @@ Return nil if not inside a project."
                         (mapcar (lambda (fname)
                                   (locate-dominating-file dir-name fname))
                                 clojure-build-tool-files))))
-    (when (> (length choices) 0)
-      (car (sort choices #'file-in-directory-p)))))
+    (when choices
+      (if clojure-preferred-build-tool
+          ;; When a preferred build tool is set, look for it specifically.
+          (or (locate-dominating-file dir-name clojure-preferred-build-tool)
+              (car (sort choices #'file-in-directory-p)))
+        ;; Otherwise, prefer candidates that contain a .git directory.
+        (or (car (seq-filter (lambda (dir)
+                               (file-directory-p (expand-file-name ".git" 
dir)))
+                             (sort choices #'file-in-directory-p)))
+            (car choices))))))
 
 (defun clojure-project-relative-path (path)
   "Denormalize PATH by making it relative to the project root."
diff --git a/test/clojure-mode-util-test.el b/test/clojure-mode-util-test.el
index 2b30cf3665..b0f7099d1c 100644
--- a/test/clojure-mode-util-test.el
+++ b/test/clojure-mode-util-test.el
@@ -47,7 +47,42 @@
           (write-region "{}" nil bb-edn)
           (make-directory bb-edn-src)
           (expect  (expand-file-name (clojure-project-dir bb-edn-src))
-                   :to-equal (file-name-as-directory temp-dir))))))
+                   :to-equal (file-name-as-directory temp-dir)))))
+
+    (it "preferred build tool selects matching directory"
+      (with-temp-dir temp-dir
+        (let* ((root (file-name-as-directory temp-dir))
+               (subdir (expand-file-name "src/project.clj" temp-dir))
+               (clojure-preferred-build-tool "deps.edn"))
+          (write-region "{}" nil (expand-file-name "deps.edn" temp-dir))
+          (make-directory (expand-file-name "src" temp-dir))
+          (write-region "" nil subdir)
+          (expect (expand-file-name
+                   (clojure-project-root-path (expand-file-name "src/" 
temp-dir)))
+                  :to-equal root))))
+
+    (it "VCS tiebreaker prefers directory with .git"
+      (with-temp-dir temp-dir
+        (let* ((root (file-name-as-directory temp-dir))
+               (clojure-preferred-build-tool nil))
+          (write-region "{}" nil (expand-file-name "deps.edn" temp-dir))
+          (make-directory (expand-file-name ".git" temp-dir))
+          (make-directory (expand-file-name "src" temp-dir))
+          (write-region "" nil (expand-file-name "src/project.clj" temp-dir))
+          (expect (expand-file-name
+                   (clojure-project-root-path (expand-file-name "src/" 
temp-dir)))
+                  :to-equal root))))
+
+    (it "no preference and no VCS falls back to most nested"
+      (with-temp-dir temp-dir
+        (let* ((subdir (file-name-as-directory (expand-file-name "src" 
temp-dir)))
+               (clojure-preferred-build-tool nil))
+          (write-region "{}" nil (expand-file-name "deps.edn" temp-dir))
+          (make-directory (expand-file-name "src" temp-dir))
+          (write-region "" nil (expand-file-name "src/project.clj" temp-dir))
+          (expect (expand-file-name
+                   (clojure-project-root-path (expand-file-name "src/" 
temp-dir)))
+                  :to-equal subdir)))))
 
   (describe "clojure-project-relative-path"
     (cl-letf (((symbol-function 'clojure-project-dir) (lambda () project-dir)))

Reply via email to