Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

### Bugs fixed

- [#2183](https://github.com/bbatsov/projectile/pull/2183): `projectile-run-test-at-point` now builds a usable file path when the project is reached through a symlink (anything under `/tmp` on macOS, or a symlinked `~/src`), instead of one that climbs out of the project and gets rejected by the test runner.
- [#2178](https://github.com/bbatsov/projectile/pull/2178): The file-extension filter of `projectile-replace` and friends is now shell-quoted. ag received it unquoted, so a `*.el` filter reached it as the regexp `.el$` and also matched files ending in `model` or `panel`; on the other tools an extension containing a quote broke the command outright.
- [#2177](https://github.com/bbatsov/projectile/pull/2177): `projectile-ripgrep` no longer dies under zsh with `no matches found`. The `ripgrep` package runs its command line through the shell, and the ignore exclusions went in unquoted - harmless until the generated-directory defaults introduced a pattern with a `*` in it.
- [#2172](https://github.com/bbatsov/projectile/pull/2172): Fix subproject detection in a Go workspace. A `go.work` names its modules as `./api`, and the leading `./` survived into the subproject name, which then matched none of the project's file paths - so `projectile-find-file-in-subproject` offered nothing at all in a Go monorepo. Members are now spelled relative to the project root whatever the manifest wrote.
Expand Down
30 changes: 25 additions & 5 deletions projectile.el
Original file line number Diff line number Diff line change
Expand Up @@ -13543,6 +13543,28 @@ enclosing node whose type is in RULE's `:node-types', call the rule's
name))

;;;###autoload
(defun projectile--test-at-point-file-name (file dir)
"Return FILE spelled relative to DIR for a test-at-point command.

Prefer the name as spelled. A file under a symlinked subdirectory of
the project resolves to somewhere outside it, so running it through
`file-truename' would produce a `../..' path for a file that is really
sitting right there.

The spelled name is the wrong one in the mirror-image case: the project
itself reached through a symlink that `projectile-compilation-dir' has
already resolved. Then the two spellings disagree about the root, the
relative name climbs out of the project, and the test runner is handed a
path it rejects - which is what every project under /tmp looks like on
macOS. Fall back to resolving both, and keep that only if it does stay
inside DIR."
(let ((spelled (file-relative-name file dir)))
(if (not (string-prefix-p ".." spelled))
spelled
(let ((resolved (file-relative-name (file-truename file)
(file-truename dir))))
(if (string-prefix-p ".." resolved) spelled resolved)))))

(defun projectile-run-test-at-point (arg)
"Run the test around point, if any.

Expand Down Expand Up @@ -13570,13 +13592,11 @@ a prefix ARG you can edit the command before it's run."
(let ((test-name (projectile--test-at-point-name rule)))
(unless test-name
(user-error "No test found at point"))
;; The command runs in the compilation directory, so the file
;; name is made relative to it as spelled - not through
;; `file-truename', which would escape the project for a file
;; under a symlinked subdirectory and yield a useless `../..' path.
;; The command runs in the compilation directory, so the file name
;; is made relative to it.
(let ((command (funcall (plist-get rule :command-fn)
test-name
(file-relative-name
(projectile--test-at-point-file-name
buffer-file-name
(projectile-compilation-dir))))
;; The command was derived from the test at point, so the
Expand Down
34 changes: 34 additions & 0 deletions test/projectile-test-at-point-test.el
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,40 @@
(expect (hash-table-count projectile-test-cmd-map) :to-equal 0)
(expect (hash-table-count projectile-project-command-history) :to-equal 0))))

(describe "projectile--test-at-point-file-name"
(it "spells the file relative to the compilation directory"
(expect (projectile--test-at-point-file-name
"/home/me/app/test/foo_test.go" "/home/me/app/")
:to-equal "test/foo_test.go"))

(it "keeps the spelling as given when the file is under a symlinked subdirectory"
;; `file-truename' would send this outside the project; the name as
;; spelled is both correct and what the runner can open.
(spy-on 'file-truename :and-call-fake
(lambda (f) (if (string-prefix-p "/home/me/app/vendor/" f)
(replace-regexp-in-string
"\\`/home/me/app/vendor/" "/elsewhere/" f)
f)))
(expect (projectile--test-at-point-file-name
"/home/me/app/vendor/lib/foo_test.go" "/home/me/app/")
:to-equal "vendor/lib/foo_test.go"))

(it "resolves both when the project root itself is reached through a symlink"
;; `projectile-compilation-dir' resolves the root, `buffer-file-name'
;; does not, so the spelled name climbs out of the project and the
;; runner is handed a path it rejects.
(spy-on 'file-truename :and-call-fake
(lambda (f) (replace-regexp-in-string "\\`/tmp/" "/private/tmp/" f)))
(expect (projectile--test-at-point-file-name
"/tmp/app/cart_test.go" "/private/tmp/app/")
:to-equal "cart_test.go"))

(it "keeps the spelled name when resolving does not help either"
(spy-on 'file-truename :and-call-fake #'identity)
(expect (projectile--test-at-point-file-name
"/somewhere/else/foo_test.go" "/home/me/app/")
:to-equal "../../../somewhere/else/foo_test.go")))

(provide 'projectile-test-at-point-test)

(describe "Ruby test-at-point"
Expand Down
Loading