[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[nongnu] elpa/projectile 6b3a73fddc 1/2: [Fix #1765] Re-add defaults for
From: |
ELPA Syncer |
Subject: |
[nongnu] elpa/projectile 6b3a73fddc 1/2: [Fix #1765] Re-add defaults for src-dir and test-dir |
Date: |
Tue, 19 Apr 2022 01:58:38 -0400 (EDT) |
branch: elpa/projectile
commit 6b3a73fddc04031ea36f677c26cc85af4c25e8fe
Author: lWarne <laurencewarne@gmail.com>
Commit: Bozhidar Batsov <bozhidar@batsov.dev>
[Fix #1765] Re-add defaults for src-dir and test-dir
Use "src/" and "test/" as defaults for the src-dir and test-dir
attributes of a project type when toggling between implementation and
test files.
Improve error messages for implementation and test file toggling
---
projectile.el | 189 ++++++++++++++++++++++++++++++++++++------------
test/projectile-test.el | 80 +++++++++++++-------
2 files changed, 197 insertions(+), 72 deletions(-)
diff --git a/projectile.el b/projectile.el
index 7db137f93a..a70a8021f3 100644
--- a/projectile.el
+++ b/projectile.el
@@ -530,6 +530,22 @@ See also `projectile-update-mode-line'."
:type 'function
:package-version '(projectile . "2.0.0"))
+(defcustom projectile-default-src-directory "src/"
+ "The default value of a project's src-dir property.
+
+It's used as a fallback in the case the property is not set for a project
+type when `projectile-toggle-between-implementation-and-test' is used."
+ :group 'projectile
+ :type 'string)
+
+(defcustom projectile-default-test-directory "test/"
+ "The default value of a project's test-dir property.
+
+It's used as a fallback in the case the property is not set for a project
+type when `projectile-toggle-between-implementation-and-test' is used."
+ :group 'projectile
+ :type 'string)
+
;;; Idle Timer
(defvar projectile-idle-timer nil
@@ -3426,7 +3442,7 @@ IMPL-FILE-PATH may be a absolute path, relative path or a
file name."
(cond
(test-prefix (concat test-prefix impl-file-name "." impl-file-ext))
(test-suffix (concat impl-file-name test-suffix "." impl-file-ext))
- (t (error "Project type `%s' not supported!" project-type)))))
+ (t (error "Cannot determine a test file name, one of \"test-suffix\" or
\"test-prefix\" must be set for project type `%s'" project-type)))))
(defun projectile--impl-name-for-test-name (test-file-path)
"Determine the name of the implementation file for TEST-FILE-PATH.
@@ -3442,7 +3458,7 @@ TEST-FILE-PATH may be a absolute path, relative path or a
file name."
(concat (string-remove-prefix test-prefix test-file-name) "."
test-file-ext))
(test-suffix
(concat (string-remove-suffix test-suffix test-file-name) "."
test-file-ext))
- (t (error "Project type `%s' not supported!" project-type)))))
+ (t (error "Cannot determine an implementation file name, one of
\"test-suffix\" or \"test-prefix\" must be set for project type `%s'"
project-type)))))
(defun projectile--test-to-impl-dir (test-dir-path)
"Return the directory path of an impl file with test file in TEST-DIR-PATH.
@@ -3452,12 +3468,50 @@ string) are replaced with the current project type's
src-dir property
(which should be a string) to obtain the new directory.
Nil is returned if either the src-dir or test-dir properties are not strings."
- (let ((test-dir (projectile-project-type-attribute
- (projectile-project-type) 'test-dir))
- (impl-dir (projectile-project-type-attribute
- (projectile-project-type) 'src-dir)))
+ (let* ((project-type (projectile-project-type))
+ (test-dir (projectile-project-type-attribute project-type 'test-dir))
+ (impl-dir (projectile-project-type-attribute project-type 'src-dir)))
(when (and (stringp test-dir) (stringp impl-dir))
- (projectile-complementary-dir test-dir-path test-dir impl-dir))))
+ (if (not (string-match-p test-dir (file-name-directory test-dir-path)))
+ (error "Attempted to find a implementation file by switching this
project type's (%s) test-dir property \"%s\" with this project type's src-dir
property \"%s\", but %s does not contain \"%s\""
+ project-type test-dir impl-dir test-dir-path test-dir)
+ (projectile-complementary-dir test-dir-path test-dir impl-dir)))))
+
+(defun projectile--impl-to-test-dir-fallback (impl-dir-path)
+ "Return the test file for IMPL-DIR-PATH by guessing a test directory.
+
+Occurrences of the `projectile-default-src-directory' in the directory of
+IMPL-DIR-PATH are replaced with `projectile-default-test-directory'. Nil is
+returned if `projectile-default-src-directory' is not a substring of
+IMPL-DIR-PATH."
+ (when-let ((file (projectile--complementary-file
+ impl-dir-path
+ (lambda (f)
+ (when (string-match-p projectile-default-src-directory f)
+ (projectile-complementary-dir
+ impl-dir-path
+ projectile-default-src-directory
+ projectile-default-test-directory)))
+ #'projectile--test-name-for-impl-name)))
+ (file-relative-name file (projectile-project-root))))
+
+(defun projectile--test-to-impl-dir-fallback (test-dir-path)
+ "Return the impl file for TEST-DIR-PATH by guessing a source directory.
+
+Occurrences of `projectile-default-test-directory' in the directory of
+TEST-DIR-PATH are replaced with `projectile-default-src-directory'. Nil is
+returned if `projectile-default-test-directory' is not a substring of
+TEST-DIR-PATH."
+ (when-let ((file (projectile--complementary-file
+ test-dir-path
+ (lambda (f)
+ (when (string-match-p projectile-default-test-directory
f)
+ (projectile-complementary-dir
+ test-dir-path
+ projectile-default-test-directory
+ projectile-default-src-directory)))
+ #'projectile--impl-name-for-test-name)))
+ (file-relative-name file (projectile-project-root))))
(defun projectile--impl-to-test-dir (impl-dir-path)
"Return the directory path of a test whose impl file resides in
IMPL-DIR-PATH.
@@ -3466,13 +3520,19 @@ Occurrences of the current project type's src-dir
property (which should be a
string) are replaced with the current project type's test-dir property
(which should be a string) to obtain the new directory.
+If the src-dir property is set and IMPL-DIR-PATH does not contain (as a
+substring) the src-dir property of the current project type, an error is
+signalled.
+
Nil is returned if either the src-dir or test-dir properties are not strings."
- (let ((test-dir (projectile-project-type-attribute
- (projectile-project-type) 'test-dir))
- (impl-dir (projectile-project-type-attribute
- (projectile-project-type) 'src-dir)))
+ (let* ((project-type (projectile-project-type))
+ (test-dir (projectile-project-type-attribute project-type 'test-dir))
+ (impl-dir (projectile-project-type-attribute project-type 'src-dir)))
(when (and (stringp test-dir) (stringp impl-dir))
- (projectile-complementary-dir impl-dir-path impl-dir test-dir))))
+ (if (not (string-match-p impl-dir (file-name-directory impl-dir-path)))
+ (error "Attempted to find a test file by switching this project
type's (%s) src-dir property \"%s\" with this project type's test-dir property
\"%s\", but %s does not contain \"%s\""
+ project-type impl-dir test-dir impl-dir-path impl-dir)
+ (projectile-complementary-dir impl-dir-path impl-dir test-dir)))))
(defun projectile-complementary-dir (dir-path string replacement)
"Return the \"complementary\" directory of DIR-PATH.
@@ -3516,25 +3576,38 @@ test file."
(projectile-create-missing-test-files
(projectile--create-directories-for expanded-test-file)
expanded-test-file)
- (t (progn (error error-msg)))))))
+ (t (error "Determined test file to be \"%s\", which does not
exist. Set `projectile-create-missing-test-files' to allow
`projectile-find-implementation-or-test' to create new files" test-file))))))
;;;###autoload
(defun projectile-find-implementation-or-test-other-window ()
- "Open matching implementation or test file in other window."
+ "Open matching implementation or test file in other window.
+
+See the documentation of `projectile--find-matching-file' and
+`projectile--find-matching-test' for how implementation and test files
+are determined."
(interactive)
(find-file-other-window
(projectile-find-implementation-or-test (buffer-file-name))))
;;;###autoload
(defun projectile-find-implementation-or-test-other-frame ()
- "Open matching implementation or test file in other frame."
+ "Open matching implementation or test file in other frame.
+
+See the documentation of `projectile--find-matching-file' and
+`projectile--find-matching-test' for how implementation and test files
+are determined."
(interactive)
(find-file-other-frame
(projectile-find-implementation-or-test (buffer-file-name))))
;;;###autoload
(defun projectile-toggle-between-implementation-and-test ()
- "Toggle between an implementation file and its test file."
+ "Toggle between an implementation file and its test file.
+
+
+See the documentation of `projectile--find-matching-file' and
+`projectile--find-matching-test' for how implementation and test files
+are determined."
(interactive)
(find-file
(projectile-find-implementation-or-test (buffer-file-name))))
@@ -3562,11 +3635,13 @@ Fallback to DEFAULT-VALUE for missing attributes."
(defun projectile-src-directory (project-type)
"Find default src directory based on PROJECT-TYPE."
- (projectile-project-type-attribute project-type 'src-dir "src/"))
+ (projectile-project-type-attribute
+ project-type 'src-dir projectile-default-src-directory))
(defun projectile-test-directory (project-type)
"Find default test directory based on PROJECT-TYPE."
- (projectile-project-type-attribute project-type 'test-dir "test/"))
+ (projectile-project-type-attribute
+ project-type 'test-dir projectile-default-test-directory))
(defun projectile-dirname-matching-count (a b)
"Count matching dirnames ascending file paths in A and B."
@@ -3674,6 +3749,25 @@ to a custom function, else return nil."
#'projectile--test-name-for-impl-name)
(projectile-project-root)))))
+(defmacro projectile--acond (&rest clauses)
+ "Like `cond', but the result of each condition is bound to `it'.
+
+The variable `it' is available within the remainder of each of CLAUSES.
+
+CLAUSES are otherwise as documented for `cond'. This is copied from
+anaphora.el."
+ (declare (debug cond))
+ (if (null clauses)
+ nil
+ (let ((cl1 (car clauses))
+ (sym (cl-gensym)))
+ `(let ((,sym ,(car cl1)))
+ (if ,sym
+ (if (null ',(cdr cl1))
+ ,sym
+ (let ((it ,sym)) ,@(cdr cl1)))
+ (projectile--acond ,@(cdr clauses)))))))
+
(defun projectile--find-matching-test (impl-file)
"Return a list of test files for IMPL-FILE.
@@ -3682,18 +3776,21 @@ The precendence for determining test files to return is:
1. Use the project type's test-dir property if it's set to a function
2. Use the project type's related-files-fn property if set
3. Use the project type's test-dir property if it's set to a string
-4. Default to a fallback which matches all project files against
- `projectile--impl-to-test-predicate'"
- (if-let ((test-file-from-test-dir-fn
- (projectile--test-file-from-test-dir-fn impl-file)))
- (list test-file-from-test-dir-fn)
- (if-let ((plist (projectile--related-files-plist-by-kind impl-file :test)))
- (projectile--related-files-from-plist plist)
- (if-let ((test-file (projectile--test-file-from-test-dir-str impl-file)))
- (list test-file)
- (when-let ((predicate (projectile--impl-to-test-predicate impl-file)))
- (projectile--best-or-all-candidates-based-on-parents-dirs
- impl-file (cl-remove-if-not predicate
(projectile-current-project-files))))))))
+4. Attempt to find a file by matching all project files against
+ `projectile--impl-to-test-predicate'
+5. Fallback to swapping \"src\" for \"test\" in IMPL-FILE if \"src\"
+ is a substring of IMPL-FILE."
+ (projectile--acond
+ ((projectile--test-file-from-test-dir-fn impl-file) (list it))
+ ((projectile--related-files-plist-by-kind impl-file :test)
+ (projectile--related-files-from-plist it))
+ ((projectile--test-file-from-test-dir-str impl-file) (list it))
+ ((projectile--best-or-all-candidates-based-on-parents-dirs
+ impl-file (cl-remove-if-not
+ (projectile--impl-to-test-predicate impl-file)
+ (projectile-current-project-files))) it)
+ ((projectile--impl-to-test-dir-fallback impl-file)
+ (list it))))
(defun projectile--test-to-impl-predicate (test-file)
"Return a predicate, which returns t for any impl files for TEST-FILE."
@@ -3714,17 +3811,19 @@ The precendence for determining implementation files to
return is:
2. Use the project type's related-files-fn property if set
3. Use the project type's src-dir property if it's set to a string
4. Default to a fallback which matches all project files against
- `projectile--test-to-impl-predicate'"
- (if-let ((impl-file-from-src-dir-fn
- (projectile--impl-file-from-src-dir-fn test-file)))
- (list impl-file-from-src-dir-fn)
- (if-let ((plist (projectile--related-files-plist-by-kind test-file :impl)))
- (projectile--related-files-from-plist plist)
- (if-let ((impl-file (projectile--impl-file-from-src-dir-str test-file)))
- (list impl-file)
- (when-let ((predicate (projectile--test-to-impl-predicate test-file)))
- (projectile--best-or-all-candidates-based-on-parents-dirs
- test-file (cl-remove-if-not predicate
(projectile-current-project-files))))))))
+ `projectile--test-to-impl-predicate'
+5. Fallback to swapping \"test\" for \"src\" in TEST-FILE if \"test\"
+ is a substring of TEST-FILE."
+ (projectile--acond
+ ((projectile--impl-file-from-src-dir-fn test-file) (list it))
+ ((projectile--related-files-plist-by-kind test-file :impl)
+ (projectile--related-files-from-plist it))
+ ((projectile--impl-file-from-src-dir-str test-file) (list it))
+ ((projectile--best-or-all-candidates-based-on-parents-dirs
+ test-file (cl-remove-if-not
+ (projectile--test-to-impl-predicate test-file)
+ (projectile-current-project-files))) it)
+ ((projectile--test-to-impl-dir-fallback test-file) (list it))))
(defun projectile--choose-from-candidates (candidates)
"Choose one item from CANDIDATES."
@@ -3734,13 +3833,13 @@ The precendence for determining implementation files to
return is:
(defun projectile-find-matching-test (impl-file)
"Compute the name of the test matching IMPL-FILE."
- (if-let ((candidates (projectile--find-matching-test impl-file)))
- (projectile--choose-from-candidates candidates)))
+ (when-let ((candidates (projectile--find-matching-test impl-file)))
+ (projectile--choose-from-candidates candidates)))
(defun projectile-find-matching-file (test-file)
"Compute the name of a file matching TEST-FILE."
- (if-let ((candidates (projectile--find-matching-file test-file)))
- (projectile--choose-from-candidates candidates)))
+ (when-let ((candidates (projectile--find-matching-file test-file)))
+ (projectile--choose-from-candidates candidates)))
(defun projectile-grep-default-files ()
"Try to find a default pattern for `projectile-grep'.
diff --git a/test/projectile-test.el b/test/projectile-test.el
index bd86763e55..e3d5a7feef 100644
--- a/test/projectile-test.el
+++ b/test/projectile-test.el
@@ -1328,7 +1328,23 @@ Just delegates OPERATION and ARGS for all operations
except for`shell-command`'.
(expect (projectile--find-matching-file
(projectile-expand-root "test/foo/FooTest.cpp"))
:to-equal
- (list "src/foo/Foo.cpp"))))))
+ (list "src/foo/Foo.cpp")))))
+
+ (it "defers to a fallback using \"src\" and \"test\""
+ (projectile-test-with-sandbox
+ (projectile-test-with-files-using-custom-project
+ ("project.clj"
+ "src/example/core.clj"
+ "test/example/core2_test.clj")
+ (:test-suffix "_test")
+ (expect (projectile--find-matching-test
+ (projectile-expand-root "src/example/core.clj"))
+ :to-equal
+ (list "test/example/core_test.clj"))
+ (expect (projectile--find-matching-file
+ (projectile-expand-root "test/example/core2_test.clj"))
+ :to-equal
+ (list "src/example/core2.clj"))))))
(describe "projectile--related-files"
(it "returns related files for the given file"
@@ -1887,34 +1903,44 @@ projectile-process-current-project-buffers-current to
have similar behaviour"
:to-equal "foo/test/dir/Foo.test"))))
(describe "projectile--impl-to-test-dir"
- :var ((mock-projectile-project-types
- '((foo test-dir "test" src-dir "src")
- (bar test-dir identity src-dir "src"))))
- (it "replaces occurrences of src-dir with test-dir"
- (cl-letf (((symbol-function 'projectile-project-root) (lambda () "foo"))
- ((symbol-function 'projectile-project-type) (lambda () 'foo))
- (projectile-project-types mock-projectile-project-types))
- (expect (projectile--impl-to-test-dir "/foo/src/Foo") :to-equal
"/foo/test/")))
- (it "nil returned when test-dir property is not a string"
- (cl-letf (((symbol-function 'projectile-project-root) (lambda () "bar"))
- ((symbol-function 'projectile-project-type) (lambda () 'bar))
- (projectile-project-types mock-projectile-project-types))
- (expect (projectile--impl-to-test-dir "/bar/src/bar") :to-be nil))))
+ :var ((mock-projectile-project-types
+ '((foo test-dir "test" src-dir "src")
+ (bar test-dir identity src-dir "src"))))
+ (it "replaces occurrences of src-dir with test-dir"
+ (cl-letf (((symbol-function 'projectile-project-root) (lambda () "foo"))
+ ((symbol-function 'projectile-project-type) (lambda () 'foo))
+ (projectile-project-types mock-projectile-project-types))
+ (expect (projectile--impl-to-test-dir "/foo/src/Foo") :to-equal
"/foo/test/")))
+ (it "nil returned when test-dir property is not a string"
+ (cl-letf (((symbol-function 'projectile-project-root) (lambda () "bar"))
+ ((symbol-function 'projectile-project-type) (lambda () 'bar))
+ (projectile-project-types mock-projectile-project-types))
+ (expect (projectile--impl-to-test-dir "/bar/src/bar") :to-be nil)))
+ (it "error when src-dir not a substring of impl file"
+ (cl-letf (((symbol-function 'projectile-project-root) (lambda () "foo"))
+ ((symbol-function 'projectile-project-type) (lambda () 'foo))
+ (projectile-project-types mock-projectile-project-types))
+ (expect (projectile--impl-to-test-dir "/bar/other/bar") :to-throw))))
(describe "projectile--test-to-impl-dir"
- :var ((mock-projectile-project-types
- '((foo test-dir "test" src-dir "src")
- (bar test-dir "test" src-dir identity))))
- (it "replaces occurrences of test-dir with src-dir"
- (cl-letf (((symbol-function 'projectile-project-root) (lambda () "foo"))
- ((symbol-function 'projectile-project-type) (lambda () 'foo))
- (projectile-project-types mock-projectile-project-types))
- (expect (projectile--test-to-impl-dir "/foo/test/Foo") :to-equal
"/foo/src/")))
- (it "nil returned when src-dir property is not a string"
- (cl-letf (((symbol-function 'projectile-project-root) (lambda () "bar"))
- ((symbol-function 'projectile-project-type) (lambda () 'bar))
- (projectile-project-types mock-projectile-project-types))
- (expect (projectile--test-to-impl-dir "/bar/test/bar") :to-be nil))))
+ :var ((mock-projectile-project-types
+ '((foo test-dir "test" src-dir "src")
+ (bar test-dir "test" src-dir identity))))
+ (it "replaces occurrences of test-dir with src-dir"
+ (cl-letf (((symbol-function 'projectile-project-root) (lambda () "foo"))
+ ((symbol-function 'projectile-project-type) (lambda () 'foo))
+ (projectile-project-types mock-projectile-project-types))
+ (expect (projectile--test-to-impl-dir "/foo/test/Foo") :to-equal
"/foo/src/")))
+ (it "nil returned when src-dir property is not a string"
+ (cl-letf (((symbol-function 'projectile-project-root) (lambda () "bar"))
+ ((symbol-function 'projectile-project-type) (lambda () 'bar))
+ (projectile-project-types mock-projectile-project-types))
+ (expect (projectile--test-to-impl-dir "/bar/test/bar") :to-be nil)))
+ (it "error when test-dir not a substring of test file"
+ (cl-letf (((symbol-function 'projectile-project-root) (lambda () "foo"))
+ ((symbol-function 'projectile-project-type) (lambda () 'foo))
+ (projectile-project-types mock-projectile-project-types))
+ (expect (projectile--test-to-impl-dir "/bar/other/bar") :to-throw))))
(describe "projectile-run-shell-command-in-root"
(describe "when called directly in elisp"