aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--doc/projects.md42
-rw-r--r--projectile.el213
-rw-r--r--test/projectile-test.el134
4 files changed, 321 insertions, 69 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7db0968..b2379a6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@
* Add `related-files-fn` option to use custom function to find test/impl/other files
* [#1019](https://github.com/bbatsov/projectile/issues/1019): Jump to a test named the same way but in a different directory.
* [#982](https://github.com/bbatsov/projectile/issues/982) Add heuristic for projectile-find-matching-test
+* Support a list of functions for `related-files-fn` options and helper functions
### Bugs fixed
diff --git a/doc/projects.md b/doc/projects.md
index 95eb3f0..4ce3693 100644
--- a/doc/projects.md
+++ b/doc/projects.md
@@ -141,9 +141,9 @@ on any directory path. `projectile-other-file-alist` variable can be also set to
find other files based on the extension.
For the full control of finding related files, `:related-files-fn` option with a
-custom function can be used. The custom function accepts the relative file name
-from the project root and it should return the related file information as plist
-with the following optional key/value pairs:
+custom function or a list of custom functions can be used. The custom function
+accepts the relative file name from the project root and it should return the
+related file information as plist with the following optional key/value pairs:
| Key | Value | Command applicable |
|--------|---------------------------------------------------------------|---------------------------------------------------------------------------------|
@@ -192,7 +192,7 @@ For example, "src/foo/abc.cpp" will match to "test/foo/abc.cpp" as test file and
#### Example - Different test prefix per extension
A custom function for the project using multiple programming languages with different test prefixes.
-```
+```el
(defun my/related-files(file)
(let ((ext-to-test-prefix '(("cpp" . "Test")
("py" . "test_"))))
@@ -213,6 +213,40 @@ related files of any kinds. For example, the custom function can specify the
related documents with ':doc' key. Note that `projectile-find-related-file` only
relies on `:related-files-fn` for now.
+### Related file custom function helper
+
+`:related-files-fn` can accept a list of custom functions to combine the result
+of each custom function. This allows users to write several custom functions
+and apply them differently to projects.
+
+Projectile includes a couple of helpers to generate commonly used custom functions.
+
+| Helper name and params | Purpose |
+|------------------------------------|-------------------------------------------------------------|
+| groups KIND GROUPS | Relates files in each group as the specified kind. |
+| extensions KIND EXTENSIONS | Relates files with extensions as the specified kind. |
+| tests-with-prefix EXTENSION PREFIX | Relates files with prefix and extension as :test and :impl. |
+| tests-with-suffix EXTENSION SUFFIX | Relates files with suffix and extension as :test and :impl. |
+
+Each helper means `projectile-related-files-fn-helper-name` function.
+
+#### Example usage of projectile-related-files-fn-helpers
+```el
+(setq my/related-files
+ (list
+ (projectile-related-files-fn-extensions :other '("cpp" "h" "hpp"))
+ (projectile-related-files-fn-test-with-prefix "cpp" "Test")
+ (projectile-related-files-fn-test-with-suffix "el" "_test")
+ (projectile-related-files-fn-groups
+ :doc
+ '(("doc/common.txt"
+ "src/foo.h"
+ "src/bar.h")))))
+
+(projectile-register-project-type
+ ;; ...
+ :related-files-fn #'my/related-files)
+```
## Customizing project root files
diff --git a/projectile.el b/projectile.el
index a906862..b393a74 100644
--- a/projectile.el
+++ b/projectile.el
@@ -1862,16 +1862,11 @@ https://github.com/abo-abo/swiper")))
The list depends on `:related-files-fn' project option and
`projectile-other-file-alist'. For the latter, FLEX-MATCHING can be used
to match any basename."
- (let* ((candidate-plist (projectile--get-related-file-candidates file-name :other))
- (predicate (plist-get candidate-plist :predicate)))
- (cond ((plist-member candidate-plist :paths)
- (plist-get candidate-plist :paths))
- (predicate
- (cl-remove-if-not predicate (projectile-current-project-files)))
- (t
- (projectile--get-other-extension-files file-name
- (projectile-current-project-files)
- flex-matching)))))
+ (if-let ((plist (projectile--related-files-plist-by-kind file-name :other)))
+ (projectile--related-files-from-plist plist)
+ (projectile--other-extension-files file-name
+ (projectile-current-project-files)
+ flex-matching)))
(defun projectile--find-other-file (&optional flex-matching ff-variant)
"Switch between files with the same name but different extensions.
@@ -1944,7 +1939,7 @@ If no associated other-file-extensions for the complete (nested) extension are f
(throw 'break associated-extensions))
(setq current-extensions (projectile--file-name-extensions current-extensions))))))
-(defun projectile--get-other-extension-files (current-file project-file-list &optional flex-matching)
+(defun projectile--other-extension-files (current-file project-file-list &optional flex-matching)
"Narrow to files with the same names but different extensions.
Returns a list of possible files for users to choose.
@@ -2270,46 +2265,89 @@ With a prefix arg INVALIDATE-CACHE invalidates the cache first."
"Return only the test FILES."
(cl-remove-if-not 'projectile-test-file-p files))
-(defun projectile--get-related-file-candidates (file kind)
- "Return a plist containing related information of KIND for FILE."
- (if-let ((custom-function (funcall projectile-related-files-fn-function (projectile-project-type)))
- (retval (funcall custom-function file))
- (has-kind? (plist-member retval kind)))
- (let ((kind-value (plist-get retval kind)))
- (if (functionp kind-value)
- (list :predicate kind-value)
- (let ((paths (if (stringp kind-value) (list kind-value) kind-value)))
- (list :paths (cl-remove-if-not
- (lambda (f)
- (projectile-file-exists-p (expand-file-name f (projectile-project-root))))
- paths)))))))
-
-(defun projectile--get-related-file-kinds(file)
- "Return a list of keywords meaning related kinds for FILE."
- (if-let ((custom-function (funcall projectile-related-files-fn-function (projectile-project-type)))
- (plist (funcall custom-function file)))
+(defun projectile--merge-related-files-fns (related-files-fns)
+ "Merge multiple RELATED-FILES-FNS into one function."
+ (lambda (path)
+ (let (merged-plist)
+ (dolist (fn related-files-fns merged-plist)
+ (let ((plist (funcall fn path)))
+ (cl-loop for (key value) on plist by #'cddr
+ do (let ((values (if (consp value) value (list value))))
+ (if (plist-member merged-plist key)
+ (nconc (plist-get merged-plist key) values)
+ (setq merged-plist (plist-put merged-plist key values))))))))))
+
+(defun projectile--related-files-plist (project-root file)
+ "Return a plist containing all related files information for FILE in PROJECT-ROOT."
+ (if-let ((rel-path (if (file-name-absolute-p file)
+ (file-relative-name file project-root)
+ file))
+ (custom-function (funcall projectile-related-files-fn-function (projectile-project-type))))
+ (funcall (cond ((functionp custom-function)
+ custom-function)
+ ((consp custom-function)
+ (projectile--merge-related-files-fns custom-function))
+ (t
+ (error "Unsupported value type of :related-files-fn")))
+ rel-path)))
+
+(defun projectile--related-files-plist-by-kind (file kind)
+ "Return a plist containing :paths and/or :predicate of KIND for FILE."
+ (if-let ((project-root (projectile-project-root))
+ (plist (projectile--related-files-plist project-root file))
+ (has-kind? (plist-member plist kind)))
+ (let* ((kind-value (plist-get plist kind))
+ (values (if (cl-typep kind-value '(or string function))
+ (list kind-value)
+ kind-value))
+ (paths (delete-dups (cl-remove-if-not 'stringp values)))
+ (predicates (delete-dups (cl-remove-if-not 'functionp values))))
+ (append
+ ;; Make sure that :paths exists even with nil if there is no predicates
+ (when (or paths (null predicates))
+ (list :paths (cl-remove-if-not
+ (lambda (f)
+ (projectile-file-exists-p (expand-file-name f project-root)))
+ paths)))
+ (when predicates
+ (list :predicate (if (= 1 (length predicates))
+ (car predicates)
+ (lambda (other-file)
+ (cl-some (lambda (predicate)
+ (funcall predicate other-file))
+ predicates)))))))))
+
+(defun projectile--related-files-from-plist (plist)
+ "Return a list of files matching to PLIST from current project files."
+ (let* ((predicate (plist-get plist :predicate))
+ (paths (plist-get plist :paths)))
+ (delete-dups (append
+ paths
+ (when predicate
+ (cl-remove-if-not predicate (projectile-current-project-files)))))))
+
+(defun projectile--related-files-kinds(file)
+ "Return a list o keywords meaning available related kinds for FILE."
+ (if-let ((project-root (projectile-project-root))
+ (plist (projectile--related-files-plist project-root file)))
(cl-loop for key in plist by #'cddr
collect key)))
-(defun projectile--get-related-files (file kind)
+(defun projectile--related-files (file kind)
"Return a list of related files of KIND for FILE."
- (let* ((candidate-plist (projectile--get-related-file-candidates file kind))
- (predicate (plist-get candidate-plist :predicate)))
- (if (plist-member candidate-plist :paths)
- (plist-get candidate-plist :paths)
- (cl-remove-if-not predicate (projectile-current-project-files)))))
+ (projectile--related-files-from-plist (projectile--related-files-plist-by-kind file kind)))
(defun projectile--find-related-file (file &optional kind)
"Choose a file from files related to FILE as KIND.
If KIND is not provided, a list of possible kinds can be chosen."
(unless kind
- (if-let ((available-kinds (projectile--get-related-file-kinds file)))
+ (if-let ((available-kinds (projectile--related-files-kinds file)))
(setq kind (if (= (length available-kinds) 1)
(car available-kinds)
(intern (projectile-completing-read "Kind :" available-kinds))))
(error "No related files found")))
- (if-let ((candidates (projectile--get-related-files file kind)))
+ (if-let ((candidates (projectile--related-files file kind)))
(projectile-expand-root (projectile--choose-from-candidates candidates))
(error
"No matching related file as `%s' found for project type `%s'"
@@ -2336,14 +2374,73 @@ If KIND is not provided, a list of possible kinds can be chosen."
(find-file
(projectile--find-related-file (buffer-file-name))))
+;;;###autoload
+(defun projectile-related-files-fn-groups(kind groups)
+ "Generate a related-files-fn which relates as KIND for files in each of GROUPS."
+ (lambda (path)
+ (if-let ((group-found (cl-find-if (lambda (group)
+ (member path group))
+ groups)))
+ (list kind (cl-remove path group-found :test 'equal)))))
+
+;;;###autoload
+(defun projectile-related-files-fn-extensions(kind extensions)
+ "Generate a related-files-fn which relates as KIND for files having EXTENSIONS."
+ (lambda (path)
+ (let* ((ext (file-name-extension path))
+ (basename (file-name-base path))
+ (basename-regexp (regexp-quote basename)))
+ (when (member ext extensions)
+ (list kind (lambda (other-path)
+ (and (string-match-p basename-regexp other-path)
+ (equal basename (file-name-base other-path))
+ (let ((other-ext (file-name-extension other-path)))
+ (and (member other-ext extensions)
+ (not (equal other-ext ext)))))))))))
+
+;;;###autoload
+(defun projectile-related-files-fn-test-with-prefix(extension test-prefix)
+ "Generate a related-files-fn which relates tests and impl for files with EXTENSION based on TEST-PREFIX."
+ (lambda (path)
+ (when (equal (file-name-extension path) extension)
+ (let* ((file-name (file-name-nondirectory path))
+ (find-impl? (string-prefix-p test-prefix file-name))
+ (file-name-to-find (if find-impl?
+ (substring file-name (length test-prefix))
+ (concat test-prefix file-name))))
+ (list (if find-impl? :impl :test)
+ (lambda (other-path)
+ (and (string-suffix-p file-name-to-find other-path)
+ (equal (file-name-nondirectory other-path) file-name-to-find))))))))
+
+;;;###autoload
+(defun projectile-related-files-fn-test-with-suffix(extension test-suffix)
+ "Generate a related-files-fn which relates tests and impl for files with EXTENSION based on TEST-SUFFIX."
+ (lambda (path)
+ (when (equal (file-name-extension path) extension)
+ (let* ((file-name (file-name-nondirectory path))
+ (dot-ext (concat "." extension))
+ (suffix-ext (concat test-suffix dot-ext))
+ (find-impl? (string-suffix-p suffix-ext file-name))
+ (file-name-to-find (if find-impl?
+ (concat (substring file-name 0 (- (length suffix-ext)))
+ dot-ext)
+ (concat (substring file-name 0 (- (length dot-ext)))
+ suffix-ext))))
+ (list (if find-impl? :impl :test)
+ (lambda (other-path)
+ (and (string-suffix-p file-name-to-find other-path)
+ (equal (file-name-nondirectory other-path) file-name-to-find))))))))
(defun projectile-test-file-p (file)
"Check if FILE is a test file."
- (or (when (projectile--get-related-file-candidates file :impl) t)
- (cl-some (lambda (pat) (string-prefix-p pat (file-name-nondirectory file)))
- (delq nil (list (funcall projectile-test-prefix-function (projectile-project-type)))))
- (cl-some (lambda (pat) (string-suffix-p pat (file-name-sans-extension (file-name-nondirectory file))))
- (delq nil (list (funcall projectile-test-suffix-function (projectile-project-type)))))))
+ (let ((kinds (projectile--related-files-kinds file)))
+ (cond ((member :impl kinds) t)
+ ((member :test kinds) nil)
+ (t (or (cl-some (lambda (pat) (string-prefix-p pat (file-name-nondirectory file)))
+ (delq nil (list (funcall projectile-test-prefix-function (projectile-project-type)))))
+ (cl-some (lambda (pat) (string-suffix-p pat (file-name-sans-extension (file-name-nondirectory file))))
+ (delq nil (list (funcall projectile-test-suffix-function (projectile-project-type))))))))))
(defun projectile-current-project-test-files ()
"Return a list of test files for the current project."
@@ -2817,14 +2914,14 @@ Fallback to DEFAULT-VALUE for missing attributes."
(nreverse result))))
(lambda (a b) (> (car a) (car b)))))
-(defun projectile--get-best-or-all-candidates-based-on-parents-dirs (file candidates)
+(defun projectile--best-or-all-candidates-based-on-parents-dirs (file candidates)
"Return a list containing the best one one for FILE from CANDIDATES or all CANDIDATES."
(let ((grouped-candidates (projectile-group-file-candidates file candidates)))
(if (= (length (car grouped-candidates)) 2)
(list (car (last (car grouped-candidates))))
(apply 'append (mapcar 'cdr grouped-candidates)))))
-(defun projectile--get-impl-to-test-predicate (impl-file)
+(defun projectile--impl-to-test-predicate (impl-file)
"Return a predicate, which returns t for any test files for IMPL-FILE."
(let* ((basename (file-name-sans-extension (file-name-nondirectory impl-file)))
(test-prefix (funcall projectile-test-prefix-function (projectile-project-type)))
@@ -2838,15 +2935,13 @@ Fallback to DEFAULT-VALUE for missing attributes."
(defun projectile--find-matching-test (impl-file)
"Return a list of test files for IMPL-FILE."
- (let* ((plist (projectile--get-related-file-candidates impl-file :test))
- (test-paths (plist-get plist :paths))
- (test-predicate (plist-get plist :predicate)))
- (or test-paths
- (if-let ((predicate (or test-predicate (projectile--get-impl-to-test-predicate impl-file))))
- (projectile--get-best-or-all-candidates-based-on-parents-dirs
- impl-file (cl-remove-if-not predicate (projectile-current-project-files)))))))
-
-(defun projectile--get-test-to-impl-predicate (test-file)
+ (if-let ((plist (projectile--related-files-plist-by-kind impl-file :test)))
+ (projectile--related-files-from-plist plist)
+ (if-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))))))
+
+(defun projectile--test-to-impl-predicate (test-file)
"Return a predicate, which returns t for any impl files for TEST-FILE."
(let* ((basename (file-name-sans-extension (file-name-nondirectory test-file)))
(test-prefix (funcall projectile-test-prefix-function (projectile-project-type)))
@@ -2858,13 +2953,11 @@ Fallback to DEFAULT-VALUE for missing attributes."
(defun projectile--find-matching-file (test-file)
"Return a list of impl files tested by TEST-FILE."
- (let* ((plist (projectile--get-related-file-candidates test-file :impl))
- (impl-paths (plist-get plist :paths))
- (impl-predicate (plist-get plist :predicate)))
- (or impl-paths
- (if-let ((predicate (or impl-predicate (projectile--get-test-to-impl-predicate test-file))))
- (projectile--get-best-or-all-candidates-based-on-parents-dirs
- test-file (cl-remove-if-not predicate (projectile-current-project-files)))))))
+ (if-let ((plist (projectile--related-files-plist-by-kind test-file :impl)))
+ (projectile--related-files-from-plist plist)
+ (if-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))))))
(defun projectile--choose-from-candidates (candidates)
"Choose one item from CANDIDATES."
diff --git a/test/projectile-test.el b/test/projectile-test.el
index e127ea9..0d56c57 100644
--- a/test/projectile-test.el
+++ b/test/projectile-test.el
@@ -1008,7 +1008,7 @@ You'd normally combine this with `projectile-test-with-sandbox'."
(expect (projectile--find-matching-test "src/Foo.cpp") :to-equal '("test/Foo.cpp"))
(expect (projectile--find-matching-file "test/Foo.cpp") :to-equal '("src/Foo.cpp"))))))
-(describe "projectile--get-related-files"
+(describe "projectile--related-files"
(it "returns related files for the given file"
(defun -my/related-files(file)
(cond ((equal file "src/Foo.c")
@@ -1022,10 +1022,134 @@ You'd normally combine this with `projectile-test-with-sandbox'."
"src/TestFoo.c"
"doc/Foo.txt")
(:related-files-fn #'-my/related-files)
- (expect (projectile--get-related-file-kinds "src/Foo.c") :to-equal '(:test :doc))
- (expect (projectile--get-related-file-kinds "src/TestFoo.c") :to-equal '(:impl))
- (expect (projectile--get-related-files "src/TestFoo.c" :impl) :to-equal '("src/Foo.c"))
- (expect (projectile--get-related-files "src/Foo.c" :doc) :to-equal '("doc/Foo.txt"))))))
+ (expect (projectile--related-files-kinds "src/Foo.c") :to-equal '(:test :doc))
+ (expect (projectile--related-files-kinds "src/TestFoo.c") :to-equal '(:impl))
+ (expect (projectile--related-files "src/TestFoo.c" :impl) :to-equal '("src/Foo.c"))
+ (expect (projectile--related-files "src/Foo.c" :doc) :to-equal '("doc/Foo.txt"))
+ ;; Support abspath
+ (expect (projectile--related-files-kinds (concat (projectile-project-root) "src/Foo.c")) :to-equal '(:test :doc))
+ (expect (projectile--related-files (concat (projectile-project-root) "src/Foo.c") :doc) :to-equal '("doc/Foo.txt"))))))
+
+(describe "projectile--merge-related-files-fns"
+ (it "returns a new function which returns the merged plist from each fn"
+ (defun -first-fn(file)
+ (list :foo "file1"))
+ (defun -second-fn(file)
+ (list :foo (list "file2" "file3")))
+ (defun -third-fn(file)
+ (list :bar "file4"))
+ (let ((fn (projectile--merge-related-files-fns '(-first-fn -second-fn))))
+ (expect (funcall fn "something") :to-equal '(:foo ("file1" "file2" "file3"))))
+ (let ((fn (projectile--merge-related-files-fns '(-first-fn -third-fn))))
+ (expect (funcall fn "something") :to-equal '(:foo ("file1") :bar ("file4"))))))
+
+(describe "projectile-related-files-fn-groups"
+ (it "generate related files fn which relates members of each group as a specified kind"
+ (let ((fn (projectile-related-files-fn-groups :foo '(("a.cpp" "req/a.txt" "doc/a.uml")
+ ("b.cpp" "req/b.txt")))))
+ (expect (funcall fn "a.cpp") :to-equal '(:foo ("req/a.txt" "doc/a.uml")))
+ (expect (funcall fn "req/a.txt") :to-equal '(:foo ("a.cpp" "doc/a.uml")))
+ (expect (funcall fn "b.cpp") :to-equal '(:foo ("req/b.txt")))
+ (expect (funcall fn "c.cpp") :to-equal nil))))
+
+(describe "projectile-related-files-fn-extensions"
+ (it "generate related files fn which relates files with the given extnsions"
+ (let* ((fn (projectile-related-files-fn-extensions :foo '("cpp" "h" "hpp")))
+ (plist (funcall fn "a.cpp"))
+ (predicate (plist-get plist :foo)))
+ (expect plist :to-contain :foo)
+ (expect (funcall predicate "a.h") :to-equal t)
+ (expect (funcall predicate "a.hpp") :to-equal t)
+ (expect (funcall predicate "b.cpp") :to-equal nil)
+ (expect (funcall predicate "a.cpp") :to-equal nil))))
+
+(describe "projectile-related-files-fn-tests-with-prefix"
+ (it "generate related files fn which relates tests and impl based on extension and prefix"
+ (let ((fn (projectile-related-files-fn-test-with-prefix "py" "test_")))
+ (let* ((plist (funcall fn "foo/a.py"))
+ (predicate (plist-get plist :test)))
+ (expect plist :to-contain :test)
+ (expect (funcall predicate "bar/test_a.py") :to-equal t)
+ (expect (funcall predicate "bar/test_a.cpp") :to-equal nil))
+ (let* ((plist (funcall fn "foo/test_a.py"))
+ (predicate (plist-get plist :impl)))
+ (expect plist :to-contain :impl)
+ (expect (funcall predicate "bar/a.py") :to-equal t)
+ (expect (funcall predicate "bar/a.cpp") :to-equal nil)
+ (expect (funcall predicate "bar/test_a.cpp") :to-equal nil)))))
+
+(describe "projectile-related-files-fn-tests-with-suffix"
+ (it "generate related files fn which relates tests and impl based on extension and suffix"
+ (let ((fn (projectile-related-files-fn-test-with-suffix "py" "-test")))
+ (let* ((plist (funcall fn "foo/a.py"))
+ (predicate (plist-get plist :test)))
+ (expect plist :to-contain :test)
+ (expect (funcall predicate "bar/a-test.py") :to-equal t)
+ (expect (funcall predicate "bar/a-test.cpp") :to-equal nil))
+ (let* ((plist (funcall fn "foo/a-test.py"))
+ (predicate (plist-get plist :impl)))
+ (expect plist :to-contain :impl)
+ (expect (funcall predicate "bar/a.py") :to-equal t)
+ (expect (funcall predicate "bar/a.cpp") :to-equal nil)
+ (expect (funcall predicate "bar/a-test.cpp") :to-equal nil)))))
+
+(describe "projectile--related-files-plist-by-kind"
+ (defun -sample-predicate (other-file)
+ (equal other-file "src/foo.c"))
+ (defun -sample-predicate2 (other-file)
+ (equal other-file "src/bar.c"))
+ (describe "when :related-files-fn returns paths"
+ (it "returns a plist containing :paths only with the existing files on file system without duplication"
+ (projectile-test-with-sandbox
+ (projectile-test-with-files-using-custom-project
+ ("src/foo.c")
+ (:related-files-fn (lambda (_)
+ (list :foo '("src/foo.c" "src/bar.c" "src/foo.c"))))
+ (expect (projectile--related-files-plist-by-kind "something" :foo)
+ :to-equal '(:paths ("src/foo.c")))))))
+ (describe "when :related-files-fn returns one predicate"
+ (it "returns a plist containing :predicate with the same predicate"
+ (projectile-test-with-sandbox
+ (projectile-test-with-files-using-custom-project
+ ("src/foo.c") ; Contents does not matter
+ (:related-files-fn (lambda (_)
+ (list :foo '-sample-predicate)))
+ (expect (projectile--related-files-plist-by-kind "something" :foo)
+ :to-equal '(:predicate -sample-predicate))))))
+ (describe "when :related-files-fn returns multiple predicates"
+ (it "returns a plist containing :predicate with a merging predicate"
+ (projectile-test-with-sandbox
+ (projectile-test-with-files-using-custom-project
+ ("src/foo.c") ; Contents does not matter
+ (:related-files-fn (lambda (_)
+ (list :foo (list '-sample-predicate '-sample-predicate2))))
+ (let* ((plist (projectile--related-files-plist-by-kind "something" :foo))
+ (predicate (plist-get plist :predicate)))
+ (expect plist :to-contain :predicate)
+ (expect (funcall predicate "src/foo.c") :to-equal t)
+ (expect (funcall predicate "src/bar.c") :to-equal t))))))
+ (describe "when :related-files-fn returns both paths and predicates"
+ (it "returns a plist containing both :paths and :predicates"
+ (projectile-test-with-sandbox
+ (projectile-test-with-files-using-custom-project
+ ("src/foo.c")
+ (:related-files-fn (lambda (_)
+ (list :foo '("src/foo.c" -sample-predicate))))
+ (expect (projectile--related-files-plist-by-kind "something" :foo)
+ :to-equal '(:paths ("src/foo.c") :predicate -sample-predicate))))))
+ (describe "when :related-files-fn is a list of functions"
+ (it "returns a plist containing the merged results"
+ (defun -sample-fn(file)
+ (list :foo "src/foo.c"))
+ (defun -sample-fn2(file)
+ (list :foo '-sample-predicate))
+ (projectile-test-with-sandbox
+ (projectile-test-with-files-using-custom-project
+ ("src/foo.c"
+ "src/bar.c")
+ (:related-files-fn (list '-sample-fn '-sample-fn2))
+ (expect (projectile--related-files-plist-by-kind "something" :foo)
+ :to-equal '(:paths ("src/foo.c") :predicate -sample-predicate)))))))
(describe "projectile-get-all-sub-projects"
(it "excludes out-of-project submodules"