Skip to content

Commit 75fa633

Browse files
committed
[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.
1 parent 5640cd6 commit 75fa633

3 files changed

Lines changed: 63 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
### New features
66

7+
* [#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.
78
* [#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.
89
* Add project root detection for ClojureCLR (`deps-clr.edn`).
910

clojure-mode.el

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,22 @@ The prefixes are used to generate the correct namespace."
267267
:risky t
268268
:package-version '(clojure-mode . "5.7.0"))
269269

270+
(defcustom clojure-preferred-build-tool nil
271+
"Preferred build tool file to identify the project root.
272+
When multiple build tool files are found in the directory hierarchy,
273+
this setting controls which one takes precedence.
274+
275+
When nil (the default), prefer directories that also contain a
276+
version-control marker (`.git'). If that doesn't break the tie,
277+
fall back to the most nested match.
278+
279+
When set to a string (e.g., \"deps.edn\"), prefer the directory
280+
containing that specific file."
281+
:type '(choice (const :tag "Auto-detect" nil)
282+
(string :tag "Build tool filename"))
283+
:safe (lambda (value) (or (null value) (stringp value)))
284+
:package-version '(clojure-mode . "5.22.0"))
285+
270286
(defcustom clojure-refactor-map-prefix (kbd "C-c C-r")
271287
"Clojure refactor keymap prefix."
272288
:type 'string
@@ -2076,8 +2092,16 @@ Return nil if not inside a project."
20762092
(mapcar (lambda (fname)
20772093
(locate-dominating-file dir-name fname))
20782094
clojure-build-tool-files))))
2079-
(when (> (length choices) 0)
2080-
(car (sort choices #'file-in-directory-p)))))
2095+
(when choices
2096+
(if clojure-preferred-build-tool
2097+
;; When a preferred build tool is set, look for it specifically.
2098+
(or (locate-dominating-file dir-name clojure-preferred-build-tool)
2099+
(car (sort choices #'file-in-directory-p)))
2100+
;; Otherwise, prefer candidates that contain a .git directory.
2101+
(or (car (seq-filter (lambda (dir)
2102+
(file-directory-p (expand-file-name ".git" dir)))
2103+
(sort choices #'file-in-directory-p)))
2104+
(car choices))))))
20812105

20822106
(defun clojure-project-relative-path (path)
20832107
"Denormalize PATH by making it relative to the project root."

test/clojure-mode-util-test.el

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,42 @@
4747
(write-region "{}" nil bb-edn)
4848
(make-directory bb-edn-src)
4949
(expect (expand-file-name (clojure-project-dir bb-edn-src))
50-
:to-equal (file-name-as-directory temp-dir))))))
50+
:to-equal (file-name-as-directory temp-dir)))))
51+
52+
(it "preferred build tool selects matching directory"
53+
(with-temp-dir temp-dir
54+
(let* ((root (file-name-as-directory temp-dir))
55+
(subdir (expand-file-name "src/project.clj" temp-dir))
56+
(clojure-preferred-build-tool "deps.edn"))
57+
(write-region "{}" nil (expand-file-name "deps.edn" temp-dir))
58+
(make-directory (expand-file-name "src" temp-dir))
59+
(write-region "" nil subdir)
60+
(expect (expand-file-name
61+
(clojure-project-root-path (expand-file-name "src/" temp-dir)))
62+
:to-equal root))))
63+
64+
(it "VCS tiebreaker prefers directory with .git"
65+
(with-temp-dir temp-dir
66+
(let* ((root (file-name-as-directory temp-dir))
67+
(clojure-preferred-build-tool nil))
68+
(write-region "{}" nil (expand-file-name "deps.edn" temp-dir))
69+
(make-directory (expand-file-name ".git" temp-dir))
70+
(make-directory (expand-file-name "src" temp-dir))
71+
(write-region "" nil (expand-file-name "src/project.clj" temp-dir))
72+
(expect (expand-file-name
73+
(clojure-project-root-path (expand-file-name "src/" temp-dir)))
74+
:to-equal root))))
75+
76+
(it "no preference and no VCS falls back to most nested"
77+
(with-temp-dir temp-dir
78+
(let* ((subdir (file-name-as-directory (expand-file-name "src" temp-dir)))
79+
(clojure-preferred-build-tool nil))
80+
(write-region "{}" nil (expand-file-name "deps.edn" temp-dir))
81+
(make-directory (expand-file-name "src" temp-dir))
82+
(write-region "" nil (expand-file-name "src/project.clj" temp-dir))
83+
(expect (expand-file-name
84+
(clojure-project-root-path (expand-file-name "src/" temp-dir)))
85+
:to-equal subdir)))))
5186

5287
(describe "clojure-project-relative-path"
5388
(cl-letf (((symbol-function 'clojure-project-dir) (lambda () project-dir)))

0 commit comments

Comments
 (0)