Skip to content

Commit fec900e

Browse files
committed
Merge container fixes from #63 and #64
#63 and #64 were merged into their base branches rather than master: #62 landed on master first, which left the rest of the stack behind on feature/flycheck-generic-checker and fix/container-path-normalization. Bring in the four commits that never reached master.
2 parents d1f90cc + ee0e42f commit fec900e

4 files changed

Lines changed: 108 additions & 46 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ All notable changes of the `phpstan.el` are documented in this file using the [K
3030
* Fix `phpstan-executable` in the `(STRING . (ARGUMENTS ...))` form dropping the command name, which made the first *argument* run as the program (`("docker" "run" ...)` executed `run`).
3131
* Fix `phpstan-get-command-args` destructively modifying its inputs with `nconc`. Each call appended the PHPStan arguments onto the caller's own list, growing `phpstan-executable` in the `(STRING . (ARGUMENTS ...))` form on every check, and appending `"--"` to `phpstan-generate-baseline-options` on every `phpstan-generate-baseline`.
3232
* Fix Flycheck getting stuck on a syntax check when PHPStan reported no files to analyse in a modified buffer. The check now finishes with an empty result instead of never reporting a status.
33+
* Fix the analyzed file being passed to a containerized PHPStan as a host path. `flycheck-phpstan` reported no errors at all with `(phpstan-executable . docker)`, because PHPStan answered `Path /Users/... does not exist`. `flymake-phpstan` was affected for unmodified buffers.
34+
* Fix the `--tmp-file` copy being created in the system temporary directory when running containerized, where the container cannot see it.
35+
* Fix the JSON report being ignored when the container runtime prefixes it with progress output on STDERR, which made every check with `(phpstan-executable . container)` report no errors.
36+
* Fix `flycheck-phpstan` silently discarding the fallback warning when PHPStan produced no JSON report, hiding failures such as a broken configuration file behind a clean buffer.
37+
* Fix editor mode detection asking the wrong program for its version. Only the first element of the command line was probed, which is the container runtime for `(phpstan-executable . docker)` / `container` and `php` for a PHAR without the executable bit — so `docker --version` and `php --version` were parsed as PHPStan versions (`d1c06ef`, `Technologies`) and editor mode was silently disabled for every setup except a directly executable `phpstan`.
38+
* `phpstan-version` and `phpstan-editor-mode-available-p` now take the whole command line, as returned by `phpstan-get-executable-and-args`. A bare string is still accepted. `phpstan-version` no longer merges STDERR into the version string, which a container runtime pollutes with its progress report.
3339

3440
## [0.9.0]
3541

flycheck-phpstan.el

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -69,18 +69,26 @@
6969
;; Parsing PHPStan output:
7070
(defun flycheck-phpstan-parse-output (output &optional _checker _buffer)
7171
"Parse PHPStan errors from OUTPUT."
72-
(let* ((json-buffer (with-current-buffer (flycheck-phpstan--temp-buffer)
73-
(erase-buffer)
74-
(insert output)
75-
(current-buffer)))
76-
(data (if (string-prefix-p "{" output)
77-
(phpstan--parse-json json-buffer)
78-
(list (flycheck-error-new-at 1 1 'warning (string-trim output)))))
79-
(errors (phpstan--plist-to-alist (plist-get data :files))))
80-
(unless phpstan-disable-buffer-errors
81-
(phpstan-update-ignorebale-errors-from-json-buffer errors))
82-
(phpstan-update-dumped-types errors)
83-
(flycheck-phpstan--build-errors errors)))
72+
;; Look for a line starting with `{', the same condition
73+
;; `phpstan--parse-json' acts on: it skips everything before that line so
74+
;; that output written to STDERR is ignored, since the checker process
75+
;; merges STDERR into STDOUT. Anchoring at the start of OUTPUT instead
76+
;; would miss the JSON whenever the runtime prefixes it, as Apple container
77+
;; does with its progress report.
78+
(if (not (string-match-p "^{" output))
79+
;; PHPStan produced no report at all, so OUTPUT is a failure of some
80+
;; kind. Surface it rather than reporting a clean buffer.
81+
(list (flycheck-error-new-at 1 1 'warning (string-trim output)))
82+
(let* ((json-buffer (with-current-buffer (flycheck-phpstan--temp-buffer)
83+
(erase-buffer)
84+
(insert output)
85+
(current-buffer)))
86+
(data (phpstan--parse-json json-buffer))
87+
(errors (phpstan--plist-to-alist (plist-get data :files))))
88+
(unless phpstan-disable-buffer-errors
89+
(phpstan-update-ignorebale-errors-from-json-buffer errors))
90+
(phpstan-update-dumped-types errors)
91+
(flycheck-phpstan--build-errors errors))))
8492

8593
(defun flycheck-phpstan--temp-buffer ()
8694
"Return a temporary buffer for decode JSON."

flymake-phpstan.el

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,9 @@
9595

9696
(defun flymake-phpstan--create-temp-file ()
9797
"Create temp file and return the path."
98-
(phpstan-normalize-path
99-
(flymake-proc-init-create-temp-buffer-copy 'flymake-proc-create-temp-inplace)))
98+
;; `phpstan-get-command-args' translates the path for the container mount
99+
;; point, so return the path as it is on this side.
100+
(flymake-proc-init-create-temp-buffer-copy 'flymake-proc-create-temp-inplace))
100101

101102
(defun flymake-phpstan (report-fn &rest _ignored-args)
102103
"Flymake backend for PHPStan report using REPORT-FN."

phpstan.el

Lines changed: 79 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -568,15 +568,27 @@ it returns the value of `SOURCE' as it is."
568568
(phpstan-use-xdebug-option (list "--xdebug")))
569569
options
570570
(when editor
571-
(let ((original-file (plist-get editor :original-file)))
571+
(let* ((original-file (plist-get editor :original-file))
572+
;; PHPStan may see the project through a mount point, so
573+
;; every path handed to it has to be translated, exactly
574+
;; like the config file above.
575+
(target-file (phpstan-normalize-path original-file)))
572576
(cond
573577
((funcall (plist-get editor :analyze-original) original-file)
574-
(list "--" original-file))
575-
((phpstan-editor-mode-available-p (car (phpstan-get-executable-and-args)))
576-
(list "--tmp-file" (funcall (plist-get editor :temp-file))
577-
"--instead-of" original-file
578-
"--" original-file))
579-
((list "--" (funcall (plist-get editor :inplace)))))))
578+
(list "--" target-file))
579+
((phpstan-editor-mode-available-p executable-and-args)
580+
;; A container only sees the project, so the temporary copy
581+
;; has to be created inside it. `:temp-file' puts it in the
582+
;; system temporary directory, which is not mounted.
583+
(let ((temp-file (funcall (plist-get editor
584+
(if (phpstan--container-executable-p)
585+
:inplace
586+
:temp-file)))))
587+
(list "--tmp-file" (phpstan-normalize-path temp-file)
588+
"--instead-of" target-file
589+
"--" target-file)))
590+
((list "--" (phpstan-normalize-path
591+
(funcall (plist-get editor :inplace))))))))
580592
(if editor args (cons "--" args)))))
581593

582594
(defun phpstan-update-ignorebale-errors-from-json-buffer (errors)
@@ -601,36 +613,71 @@ it returns the value of `SOURCE' as it is."
601613
collect (cons (plist-get message :line)
602614
(substring-no-properties msg (match-end 0))))))))
603615

604-
(defun phpstan-version (executable)
605-
"Return the PHPStan version of EXECUTABLE."
606-
(if-let* ((cached-entry (assoc executable phpstan-executable-versions-alist)))
607-
(cdr cached-entry)
608-
(let* ((version (thread-first
609-
(mapconcat #'shell-quote-argument (list executable "--version") " ")
610-
(shell-command-to-string)
611-
(string-trim-right)
612-
(split-string " ")
613-
(last)
614-
(car-safe))))
615-
(prog1 version
616-
(push (cons executable version) phpstan-executable-versions-alist)))))
617-
618-
(defun phpstan-editor-mode-available-p (executable)
619-
"Check if the specified PHPStan EXECUTABLE supports editor mode.
620-
621-
If a cached result for EXECUTABLE exists, it is returned directly.
616+
(defun phpstan--version-output (command)
617+
"Run COMMAND with --version and return its standard output, or NIL.
618+
619+
STDERR is discarded rather than merged: a container runtime reports its
620+
progress there, and it would otherwise be read as part of the version."
621+
(with-temp-buffer
622+
(let ((status (apply #'process-file (car command) nil (list t nil) nil
623+
(append (cdr command) (list "--version")))))
624+
(when (eq 0 status)
625+
(buffer-string)))))
626+
627+
(defun phpstan--version-from-output (output)
628+
"Return the version number reported in OUTPUT, or NIL.
629+
630+
OUTPUT looks like \"PHPStan - PHP Static Analysis Tool 1.12.33\"."
631+
(when output
632+
(let ((last-line (car (last (split-string (string-trim output) "\n" t)))))
633+
(when last-line
634+
(car (last (split-string last-line " " t)))))))
635+
636+
(defun phpstan-version (command)
637+
"Return the PHPStan version of COMMAND.
638+
639+
COMMAND is the command line that runs PHPStan, as returned by
640+
`phpstan-get-executable-and-args'. A bare string is also accepted, and
641+
taken as the name of an executable.
642+
643+
Passing the whole command line matters: PHPStan is not always the program
644+
being executed. It is `docker' or `container' for a containerized PHPStan,
645+
and `php' for a PHAR without the executable bit, and asking either of those
646+
for its version answers a version that has nothing to do with PHPStan.
647+
648+
The result is cached in `phpstan-executable-versions-alist', keyed by the
649+
command line, because probing may have to start a container."
650+
(let* ((command (if (listp command) command (list command)))
651+
(cache-key (mapconcat #'identity command " ")))
652+
(if-let* ((cached-entry (assoc cache-key phpstan-executable-versions-alist)))
653+
(cdr cached-entry)
654+
(let ((version (phpstan--version-from-output
655+
(phpstan--version-output command))))
656+
(prog1 version
657+
(push (cons cache-key version) phpstan-executable-versions-alist))))))
658+
659+
(defun phpstan-editor-mode-available-p (command)
660+
"Check if the PHPStan invoked by COMMAND supports editor mode.
661+
662+
COMMAND is the command line that runs PHPStan, as returned by
663+
`phpstan-get-executable-and-args'. A bare string is also accepted, and
664+
taken as the name of an executable.
665+
666+
If a cached result for COMMAND exists, it is returned directly.
622667
Otherwise, this function attempts to determine support by retrieving
623-
the PHPStan version using `phpstan --version' command."
668+
the PHPStan version using `phpstan --version' command. Support is
669+
assumed to be absent when the version cannot be determined."
624670
(pcase phpstan-activate-editor-mode
625671
('enabled t)
626672
('disabled nil)
627673
('nil
628-
(let* ((version (phpstan-version executable)))
629-
(if (string-match-p (eval-when-compile (regexp-quote "-dev@")) version)
630-
t
631-
(pcase (elt version 0)
632-
(?1 (version<= "1.12.27" version))
633-
(?2 (version<= "2.1.17" version))))))))
674+
(let ((version (phpstan-version command)))
675+
(when (and version (not (string-empty-p version)))
676+
(if (string-match-p (eval-when-compile (regexp-quote "-dev@")) version)
677+
t
678+
(pcase (elt version 0)
679+
(?1 (version<= "1.12.27" version))
680+
(?2 (version<= "2.1.17" version)))))))))
634681

635682
(defconst phpstan--re-ignore-tag
636683
(eval-when-compile

0 commit comments

Comments
 (0)