From e90ed7c4521c825633c3c5aef4e047afd03c010d Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Tue, 8 Apr 2025 01:12:42 +0900 Subject: [PATCH 01/14] Requires Emacs 25 or higher --- flycheck-phpstan.el | 4 ++-- phpstan.el | 33 +++++++++++++++------------------ 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/flycheck-phpstan.el b/flycheck-phpstan.el index 4a6242b..b56243a 100644 --- a/flycheck-phpstan.el +++ b/flycheck-phpstan.el @@ -7,7 +7,7 @@ ;; Version: 0.8.2 ;; Keywords: tools, php ;; Homepage: https://github.com/emacs-php/phpstan.el -;; Package-Requires: ((emacs "24.3") (flycheck "26") (phpstan "0.8.2")) +;; Package-Requires: ((emacs "25.1") (flycheck "26") (phpstan "0.8.2")) ;; License: GPL-3.0-or-later ;; This program is free software; you can redistribute it and/or modify @@ -107,7 +107,7 @@ (if (null lines) msg (concat msg flycheck-phpstan-metadata-separator - (mapconcat #'identity lines "\n")))) + (string-join lines "\n")))) collect (flycheck-error-new-at (plist-get messages :line) nil 'error text :filename file)))) diff --git a/phpstan.el b/phpstan.el index bb95533..765aff6 100644 --- a/phpstan.el +++ b/phpstan.el @@ -158,7 +158,7 @@ have unexpected behaviors or performance implications." :group 'phpstan) (defcustom phpstan-disable-buffer-errors nil - "If T, don't keep errors per buffer to save memory." + "If non-NIL, don't keep errors per buffer to save memory." :type 'boolean :group 'phpstan) @@ -172,7 +172,7 @@ have unexpected behaviors or performance implications." ;;;###autoload (progn - (defvar phpstan-working-dir nil + (defvar-local phpstan-working-dir nil "Path to working directory of PHPStan. *NOTICE*: This is different from the project root. @@ -185,7 +185,6 @@ STRING NIL Use (php-project-get-root-dir) as working directory.") - (make-variable-buffer-local 'phpstan-working-dir) (put 'phpstan-working-dir 'safe-local-variable #'(lambda (v) (if (consp v) (and (eq 'root (car v)) (stringp (cdr v))) @@ -193,7 +192,7 @@ NIL ;;;###autoload (progn - (defvar phpstan-config-file nil + (defvar-local phpstan-config-file nil "Path to project specific configuration file of PHPStan. STRING @@ -204,7 +203,6 @@ STRING NIL Search phpstan.neon(.dist) in (phpstan-get-working-dir).") - (make-variable-buffer-local 'phpstan-config-file) (put 'phpstan-config-file 'safe-local-variable #'(lambda (v) (if (consp v) (and (eq 'root (car v)) (stringp (cdr v))) @@ -243,25 +241,24 @@ max NIL Use rule level specified in `phpstan' configuration file.") (put 'phpstan-level 'safe-local-variable - #'(lambda (v) (or (null v) - (integerp v) - (eq 'max v) - (and (stringp v) - (string= "max" v) - (string-match-p "\\`[0-9]\\'" v)))))) + (lambda (v) (or (null v) + (integerp v) + (eq 'max v) + (and (stringp v) + (or (string= "max" v) + (string-match-p "\\`[0-9]\\'" v))))))) ;;;###autoload (progn - (defvar phpstan-replace-path-prefix) - (make-variable-buffer-local 'phpstan-replace-path-prefix) + (defvar-local phpstan-replace-path-prefix nil) (put 'phpstan-replace-path-prefix 'safe-local-variable - #'(lambda (v) (or (null v) (stringp v))))) + (lambda (v) (or (null v) (stringp v))))) (defconst phpstan-docker-executable "docker") ;;;###autoload (progn - (defvar phpstan-executable nil + (defvar-local phpstan-executable nil "PHPStan excutable file. STRING @@ -278,7 +275,6 @@ STRING NIL Auto detect `phpstan' executable file.") - (make-variable-buffer-local 'phpstan-executable) (put 'phpstan-executable 'safe-local-variable #'(lambda (v) (if (consp v) (or (and (eq 'root (car v)) (stringp (cdr v))) @@ -349,7 +345,8 @@ it returns the value of `SOURCE' as it is." (cond ((eq 'docker phpstan-executable) "/app") ((and (consp phpstan-executable) - (string= "docker" (car phpstan-executable))) "/app"))))) + (string= "docker" (car phpstan-executable))) + "/app"))))) (if prefix (expand-file-name (replace-regexp-in-string (concat "\\`" (regexp-quote root-directory)) @@ -591,7 +588,7 @@ POSITION determines where to insert the comment and can be either `this-line' or (goto-char new-point)) (insert (concat padding (if new-position (if append ", " " ") "// @phpstan-ignore ") - (mapconcat #'identity identifiers ", "))))))) + (string-join identifiers ", "))))))) ;;;###autoload (defun phpstan-insert-dumptype (&optional expression prefix-num) From ca1bc43f53eb2fa3a647737cd047e85e21910821 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Tue, 8 Apr 2025 02:19:45 +0900 Subject: [PATCH 02/14] Add phpstan-copy-dumped-type command --- CHANGELOG.md | 6 +++++- README.org | 8 ++++++++ flycheck-phpstan.el | 1 + phpstan.el | 25 +++++++++++++++++++++++++ 4 files changed, 39 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 072c5d5..4b4b524 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,11 @@ All notable changes of the `phpstan.el` are documented in this file using the [Keep a Changelog](https://keepachangelog.com/) principles. - +## Unreleased + +### Added + +* Add `phpstan-copy-dumped-type` command to copy the nearest dumped type from `PHPStan\dumpType()` or `PHPStan\dumpPhpDocType()` messages. ## [0.8.2] diff --git a/README.org b/README.org index 9a6efb1..f75efff 100644 --- a/README.org +++ b/README.org @@ -105,6 +105,14 @@ Insert a ~@phpstan-ignore~ tag to suppress any PHPStan errors on the current lin By default it inserts the tag on the previous line, but if there is already a tag at the end of the current line or on the previous line, the identifiers will be appended there. If there is no existing tag and ~C-u~ is pressed before the command, it will be inserted at the end of the line. +*** Command ~phpstan-copy-dumped-type~ +Copy the nearest dumped type message from PHPStan's output. + +This command looks for messages like ~Dumped type: int|string|null~ reported by ~PHPStan\dumpType()~ or ~PHPStan\dumpPhpDocType()~, and copies the type string to the kill ring. + +If there are multiple dumped types in the buffer, it selects the one closest to the current line. + +If no dumped type messages are found, the command signals an error. ** API Most variables defined in this package are buffer local. If you want to set it for multiple projects, use [[https://www.gnu.org/software/emacs/manual/html_node/elisp/Default-Value.html][setq-default]]. diff --git a/flycheck-phpstan.el b/flycheck-phpstan.el index b56243a..d004744 100644 --- a/flycheck-phpstan.el +++ b/flycheck-phpstan.el @@ -83,6 +83,7 @@ (errors (phpstan--plist-to-alist (plist-get data :files)))) (unless phpstan-disable-buffer-errors (phpstan-update-ignorebale-errors-from-json-buffer errors)) + (phpstan-update-dumped-types errors) (flycheck-phpstan--build-errors errors))) (defun flycheck-phpstan--temp-buffer () diff --git a/phpstan.el b/phpstan.el index 765aff6..d080adb 100644 --- a/phpstan.el +++ b/phpstan.el @@ -169,6 +169,7 @@ have unexpected behaviors or performance implications." (defvar-local phpstan--use-xdebug-option nil) (defvar-local phpstan--ignorable-errors '()) +(defvar-local phpstan--dumped-types '()) ;;;###autoload (progn @@ -523,6 +524,17 @@ it returns the value of `SOURCE' as it is." (setq phpstan--ignorable-errors (mapcar (lambda (v) (cons (car v) (mapcar #'cdr (cdr v)))) (seq-group-by #'car identifiers))))) +(defun phpstan-update-dumped-types (errors) + "Update `phpstan--dumped-types' variable by ERRORS." + (save-match-data + (setq phpstan--dumped-types + (cl-loop for (_ . entry) in errors + append (cl-loop for message in (plist-get entry :messages) + for msg = (plist-get message :message) + if (string-match (eval-when-compile (rx bos "Dumped type: ")) msg) + collect (cons (plist-get message :line) + (substring-no-properties msg (match-end 0)))))))) + (defconst phpstan--re-ignore-tag (eval-when-compile (rx (* (syntax whitespace)) "//" (* (syntax whitespace)) @@ -590,6 +602,19 @@ POSITION determines where to insert the comment and can be either `this-line' or (if new-position (if append ", " " ") "// @phpstan-ignore ") (string-join identifiers ", "))))))) +;;;###autoload +(defun phpstan-copy-dumped-type () + "Copy a dumped PHPStan type." + (interactive) + (if phpstan--dumped-types + (let ((type (if (eq 1 (length phpstan--dumped-types)) + (cdar phpstan--dumped-types) + (let ((linum (line-number-at-pos))) + (cdar (seq-sort-by (lambda (elm) (abs (- linum (car elm)))) #'< phpstan--dumped-types)))))) + (kill-new type) + (message "Copied %s" type)) + (user-error "No dumped PHPStan types"))) + ;;;###autoload (defun phpstan-insert-dumptype (&optional expression prefix-num) "Insert PHPStan\\dumpType() expression-statement by EXPRESSION and PREFIX-NUM." From f289114d9c7897665381dad2f01673fd04c78afd Mon Sep 17 00:00:00 2001 From: Thomas Fini Hansen Date: Mon, 7 Apr 2025 20:39:59 +0200 Subject: [PATCH 03/14] Handle "No files" message (#43) Co-authored-by: USAMI Kenta --- flycheck-phpstan.el | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/flycheck-phpstan.el b/flycheck-phpstan.el index d004744..a63771e 100644 --- a/flycheck-phpstan.el +++ b/flycheck-phpstan.el @@ -79,7 +79,9 @@ (erase-buffer) (insert output) (current-buffer))) - (data (phpstan--parse-json json-buffer)) + (data (if (string-prefix-p "{" output) + (phpstan--parse-json json-buffer) + (list (flycheck-error-new-at 1 1 'warning (string-trim output))))) (errors (phpstan--plist-to-alist (plist-get data :files)))) (unless phpstan-disable-buffer-errors (phpstan-update-ignorebale-errors-from-json-buffer errors)) From 3b0a83a68c1ed1955344b0787fc28337b2a54623 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Tue, 8 Apr 2025 20:52:07 +0900 Subject: [PATCH 04/14] Prevent Flycheck from showing errors due to deleted temp files during editing Suppress unnecessary Flycheck errors when PHPStan reports "[ERROR] No files found to analyse." This happens because temporary files are deleted while editing, causing PHPStan to fail to find files. An around-advice on `flycheck-finish-checker-process` has been added to ignore such cases, preventing misleading errors from being shown in modified buffers. --- flycheck-phpstan.el | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/flycheck-phpstan.el b/flycheck-phpstan.el index a63771e..b3c60f5 100644 --- a/flycheck-phpstan.el +++ b/flycheck-phpstan.el @@ -44,6 +44,8 @@ ;; Usually it is defined dynamically by flycheck (defvar flycheck-phpstan-executable) (defvar flycheck-phpstan--temp-buffer-name "*Flycheck PHPStan*") +(defvar flycheck-phpstan--output-filter-added nil) +(defconst flycheck-phpstan--nofiles-message (eval-when-compile (regexp-quote "[ERROR] No files found to analyse."))) (defcustom flycheck-phpstan-ignore-metadata-list nil "Set of metadata items to ignore in PHPStan messages for Flycheck." @@ -57,6 +59,24 @@ :safe #'stringp :group 'phpstan) +(defun flycheck-phpstan--suppress-no-files-error (next checker exit-status files output callback cwd) + "Suppress Flycheck errors if PHPStan reports no files in a modified buffer. + +This function is intended to be used as an :around advice for +`flycheck-finish-checker-process'. + +It prevents Flycheck from displaying an error when: +- CHECKER is `phpstan', +- the current buffer is modified, +- and OUTPUT contains the message `flycheck-phpstan--nofiles-message'. + +NEXT, EXIT-STATUS, FILES, OUTPUT, CALLBACK, and CWD are the original arguments +passed to `flycheck-finish-checker-process'." + (unless (and (eq checker 'phpstan) + (buffer-modified-p) + (string-match-p flycheck-phpstan--nofiles-message output)) + (funcall next checker exit-status files output callback cwd))) + (defun flycheck-phpstan--enabled-and-set-variable () "Return path to phpstan configure file, and set buffer execute in side effect." (let ((enabled (phpstan-enabled))) @@ -71,6 +91,10 @@ (and (stringp (car-safe phpstan-executable)) (listp (cdr-safe phpstan-executable))) (null phpstan-executable))) + (unless flycheck-phpstan--output-filter-added + (advice-add 'flycheck-finish-checker-process + :around #'flycheck-phpstan--suppress-no-files-error) + (setq flycheck-phpstan--output-filter-added t)) (setq-local flycheck-phpstan-executable (car (phpstan-get-executable-and-args))))))) (defun flycheck-phpstan-parse-output (output &optional _checker _buffer) From a91ef35cee18141d48f30148018555152cd1e6d1 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Tue, 8 Apr 2025 21:05:22 +0900 Subject: [PATCH 05/14] Fix position --- flycheck-phpstan.el | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/flycheck-phpstan.el b/flycheck-phpstan.el index b3c60f5..c8ba4ab 100644 --- a/flycheck-phpstan.el +++ b/flycheck-phpstan.el @@ -81,6 +81,10 @@ passed to `flycheck-finish-checker-process'." "Return path to phpstan configure file, and set buffer execute in side effect." (let ((enabled (phpstan-enabled))) (prog1 enabled + (unless flycheck-phpstan--output-filter-added + (advice-add 'flycheck-finish-checker-process + :around #'flycheck-phpstan--suppress-no-files-error) + (setq flycheck-phpstan--output-filter-added t)) (when (and enabled phpstan-flycheck-auto-set-executable (null (bound-and-true-p flycheck-phpstan-executable)) @@ -91,10 +95,6 @@ passed to `flycheck-finish-checker-process'." (and (stringp (car-safe phpstan-executable)) (listp (cdr-safe phpstan-executable))) (null phpstan-executable))) - (unless flycheck-phpstan--output-filter-added - (advice-add 'flycheck-finish-checker-process - :around #'flycheck-phpstan--suppress-no-files-error) - (setq flycheck-phpstan--output-filter-added t)) (setq-local flycheck-phpstan-executable (car (phpstan-get-executable-and-args))))))) (defun flycheck-phpstan-parse-output (output &optional _checker _buffer) From 715fef5e83519fbacf26ec8a51e838afd82c5ad7 Mon Sep 17 00:00:00 2001 From: Jen-Chieh Shen Date: Tue, 22 Apr 2025 17:20:22 -0700 Subject: [PATCH 06/14] fix: Warning missing lexical-binding cookie --- Eask | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Eask b/Eask index bc9f916..6e4d937 100644 --- a/Eask +++ b/Eask @@ -1,3 +1,5 @@ +;; -*- mode: eask; lexical-binding: t -*- + (package "phpstan" "0.8.2" "Interface to PHPStan (PHP static analyzer)") From 3b59a5b275f3dad911212b54006d89b5d6b47ce6 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Wed, 21 May 2025 23:44:47 +0900 Subject: [PATCH 07/14] Support PHPStan editor-mode --- README.org | 7 ++++++- flycheck-phpstan.el | 25 +++++++++++++++++----- flymake-phpstan.el | 34 +++++++++++++++++++++++------- phpstan.el | 51 ++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 103 insertions(+), 14 deletions(-) diff --git a/README.org b/README.org index f75efff..0c09935 100644 --- a/README.org +++ b/README.org @@ -5,9 +5,14 @@ #+END_HTML Emacs interface to [[https://github.com/phpstan/phpstan][PHPStan]], includes checker for [[http://www.flycheck.org/en/latest/][Flycheck]]. ** Support version -- Emacs 25+ +- Emacs 26+ - PHPStan latest/dev-master (NOT support 0.9 seriese) - PHP 7.1+ or Docker runtime + +> [!TIP] +> This package provides support for the [editor mode](https://phpstan.org/user-guide/editor-mode) that will be added in PHPStan 2.1.17 and 1.12.27. +> **We strongly recommend that you always update to the latest PHPStan.** + ** How to install *** Install from MELPA 1. If you have not set up MELPA, see [[https://melpa.org/#/getting-started][Getting Started - MELPA]]. diff --git a/flycheck-phpstan.el b/flycheck-phpstan.el index c8ba4ab..c96837e 100644 --- a/flycheck-phpstan.el +++ b/flycheck-phpstan.el @@ -77,6 +77,11 @@ passed to `flycheck-finish-checker-process'." (string-match-p flycheck-phpstan--nofiles-message output)) (funcall next checker exit-status files output callback cwd))) +(defcustom flycheck-phpstan-fallback-to-original-analysis-if-editor-mode-unavailable t + "If non-NIL, analyze the original file when PHPStan editor mode is unavailable." + :type 'boolean + :safe #'booleanp) + (defun flycheck-phpstan--enabled-and-set-variable () "Return path to phpstan configure file, and set buffer execute in side effect." (let ((enabled (phpstan-enabled))) @@ -139,13 +144,23 @@ passed to `flycheck-finish-checker-process'." nil 'error text :filename file)))) +(defun flycheck-phpstan-analyze-original (original) + "Return non-NIL if ORIGINAL is NIL, fallback is enabled, and buffer is modified." + (and (null original) + flycheck-phpstan-fallback-to-original-analysis-if-editor-mode-unavailable + (buffer-modified-p))) + (flycheck-define-checker phpstan "PHP static analyzer based on PHPStan." - :command ("php" (eval (phpstan-get-command-args :format "json")) - (eval (if (or (buffer-modified-p) (not buffer-file-name)) - (phpstan-normalize-path - (flycheck-save-buffer-to-temp #'flycheck-temp-file-inplace)) - buffer-file-name))) + :command ("php" + (eval + (phpstan-get-command-args + :format "json" + :editor (list + :analyze-original #'flycheck-phpstan-analyze-original + :original-file buffer-file-name + :temp-file (lambda () (flycheck-save-buffer-to-temp #'flycheck-temp-file-system)) + :inplace (lambda () (flycheck-save-buffer-to-temp #'flycheck-temp-file-inplace)))))) :working-directory (lambda (_) (phpstan-get-working-dir)) :enabled (lambda () (flycheck-phpstan--enabled-and-set-variable)) :error-parser flycheck-phpstan-parse-output diff --git a/flymake-phpstan.el b/flymake-phpstan.el index 3f1c5d2..efa0cf1 100644 --- a/flymake-phpstan.el +++ b/flymake-phpstan.el @@ -52,6 +52,11 @@ :type 'boolean :group 'flymake-phpstan) +(defcustom flycheck-phpstan-fallback-to-original-analysis-if-editor-mode-unavailable t + "If non-NIL, analyze the original file when PHPStan editor mode is unavailable." + :type 'boolean + :safe #'booleanp) + (defvar-local flymake-phpstan--proc nil) (defun flymake-phpstan-make-process (root command-args report-fn source) @@ -88,6 +93,17 @@ (kill-buffer (process-buffer proc)))) (code (user-error "PHPStan error (exit status: %s)" code))))))) +(defun flymake-phpstan-analyze-original (original) + "Return non-NIL if ORIGINAL is NIL, fallback is enabled, and buffer is modified." + (and (null original) + flymake-phpstan-fallback-to-original-analysis-if-editor-mode-unavailable + (buffer-modified-p))) + +(defun flymake-phpstan--create-temp-file () + "Create temp file and return the path." + (phpstan-normalize-path + (flymake-proc-init-create-temp-buffer-copy 'flymake-proc-create-temp-inplace))) + (defun flymake-phpstan (report-fn &rest _ignored-args) "Flymake backend for PHPStan report using REPORT-FN." (let ((command-args (phpstan-get-command-args :include-executable t))) @@ -95,14 +111,18 @@ (user-error "Cannot find a phpstan executable command")) (when (process-live-p flymake-phpstan--proc) (kill-process flymake-phpstan--proc)) - (let ((source (current-buffer)) - (target-path (if (or (buffer-modified-p) (not buffer-file-name)) - (phpstan-normalize-path - (flycheck-save-buffer-to-temp #'flycheck-temp-file-inplace)) - buffer-file-name))) + (let* ((source (current-buffer)) + (args (phpstan-get-command-args + :include-executable t + :format "raw" + :editor (list + :analyze-original #'flymake-phpstan-analyze-original + :original-file buffer-file-name + :temp-file #'flymake-phpstan--create-temp-file + :inplace #'flymake-phpstan--create-temp-file)))) (save-restriction (widen) - (setq flymake-phpstan--proc (flymake-phpstan-make-process (php-project-get-root-dir) (append command-args (list "--" target-path)) report-fn source)) + (setq flymake-phpstan--proc (flymake-phpstan-make-process (php-project-get-root-dir) args report-fn source)) (process-send-region flymake-phpstan--proc (point-min) (point-max)) (process-send-eof flymake-phpstan--proc))))) @@ -115,7 +135,7 @@ (flymake-mode 1) (when flymake-phpstan-disable-c-mode-hooks (remove-hook 'flymake-diagnostic-functions #'flymake-cc t)) - (add-hook 'flymake-diagnostic-functions #'flymake-phpstan nil t)))) + (add-hook 'flymake-diagnostic-functions #'flymake-phpstan nil 'local)))) (provide 'flymake-phpstan) ;;; flymake-phpstan.el ends here diff --git a/phpstan.el b/phpstan.el index d080adb..f514121 100644 --- a/phpstan.el +++ b/phpstan.el @@ -166,11 +166,20 @@ have unexpected behaviors or performance implications." "Lists identifiers prohibited from being added to @phpstan-ignore tags." :type '(repeat string)) +(defcustom phpstan-activate-editor-mode nil + "Controls how PHPStan's editor mode is activated." + :local t + :type '(choice (const :tag "Automatic (based on version)" nil) + (const :tag "Editor mode will be actively enabled, regardless of the PHPStan version." 'enabled) + (const :tag "Editor mode will be explicitly disabled." 'disabled))) + (defvar-local phpstan--use-xdebug-option nil) (defvar-local phpstan--ignorable-errors '()) (defvar-local phpstan--dumped-types '()) +(defvar phpstan-executable-versions-alist '()) + ;;;###autoload (progn (defvar-local phpstan-working-dir nil @@ -479,7 +488,7 @@ it returns the value of `SOURCE' as it is." ((executable-find "phpstan") (list (executable-find "phpstan"))) (t (error "PHPStan executable not found"))))))) -(cl-defun phpstan-get-command-args (&key include-executable use-pro args format options config verbose) +(cl-defun phpstan-get-command-args (&key include-executable use-pro args format options config verbose editor) "Return command line argument for PHPStan." (let ((executable-and-args (phpstan-get-executable-and-args)) (config (or config (phpstan-normalize-path (phpstan-get-config-file)))) @@ -510,6 +519,15 @@ it returns the value of `SOURCE' as it is." "--xdebug")) (list phpstan--use-xdebug-option)) (phpstan-use-xdebug-option (list "--xdebug"))) + (when editor + (let ((original-file (plist-get editor :original-file))) + (if (phpstan-editor-mode-available-p (car (phpstan-get-executable-and-args))) + (list "--tmp-file" (funcall (plist-get editor :temp-file)) + "--instead-of" original-file + "--" original-file) + (if (funcall (plist-get editor :analyze-original) original-file) + (list "--" original-file) + (list "--" (funcall (plist-get editor :inplace))))))) options (and args (cons "--" args))))) @@ -535,6 +553,37 @@ it returns the value of `SOURCE' as it is." collect (cons (plist-get message :line) (substring-no-properties msg (match-end 0)))))))) +(defun phpstan-version (executable) + "Return the PHPStan version of EXECUTABLE." + (if-let* ((cached-entry (assoc executable phpstan-executable-versions-alist))) + (cdr cached-entry) + (let* ((version (thread-first + (mapconcat #'shell-quote-argument (list executable "--version") " ") + (shell-command-to-string) + (string-trim-right) + (split-string " ") + (last) + (car-safe)))) + (prog1 version + (push (cons executable version) phpstan-executable-versions-alist))))) + +(defun phpstan-editor-mode-available-p (executable) + "Check if the specified PHPStan EXECUTABLE supports editor mode. + +If a cached result for EXECUTABLE exists, it is returned directly. +Otherwise, this function attempts to determine support by retrieving +the PHPStan version using 'phpstan --version' command." + (pcase phpstan-activate-editor-mode + ('enabled t) + ('disabled nil) + ('nil + (let* ((version (phpstan-version executable))) + (if (string-match-p (eval-when-compile (regexp-quote "-dev@")) version) + t + (pcase (elt version 0) + (?1 (version<= "1.12.27" version)) + (?2 (version<= "2.1.17" version)))))))) + (defconst phpstan--re-ignore-tag (eval-when-compile (rx (* (syntax whitespace)) "//" (* (syntax whitespace)) From 2f278be2e397c547be0f48bdc04ca841c1b2fedc Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Thu, 22 May 2025 00:02:03 +0900 Subject: [PATCH 08/14] Fix README --- README.org | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.org b/README.org index 0c09935..d13f5eb 100644 --- a/README.org +++ b/README.org @@ -9,9 +9,11 @@ Emacs interface to [[https://github.com/phpstan/phpstan][PHPStan]], includes che - PHPStan latest/dev-master (NOT support 0.9 seriese) - PHP 7.1+ or Docker runtime -> [!TIP] -> This package provides support for the [editor mode](https://phpstan.org/user-guide/editor-mode) that will be added in PHPStan 2.1.17 and 1.12.27. -> **We strongly recommend that you always update to the latest PHPStan.** +#+BEGIN_QUOTE +[!TIP] +This package provides support for the [[https://phpstan.org/user-guide/editor-mode][Editor Mode]] that will be added in PHPStan 2.1.17 and 1.12.27. +**We strongly recommend that you always update to the latest PHPStan.** +#+END_QUOTE ** How to install *** Install from MELPA From b20fb8173930365bcbedf78901ce526b8bb4dc5d Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Thu, 22 May 2025 00:03:56 +0900 Subject: [PATCH 09/14] Fix README --- README.org | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.org b/README.org index d13f5eb..a03d16a 100644 --- a/README.org +++ b/README.org @@ -11,8 +11,8 @@ Emacs interface to [[https://github.com/phpstan/phpstan][PHPStan]], includes che #+BEGIN_QUOTE [!TIP] -This package provides support for the [[https://phpstan.org/user-guide/editor-mode][Editor Mode]] that will be added in PHPStan 2.1.17 and 1.12.27. -**We strongly recommend that you always update to the latest PHPStan.** +This package provides support for the [[https://phpstan.org/user-guide/editor-mode][Editor Mode]] that will be added in PHPStan 2.1.17 and 1.12.27.\\ +*We strongly recommend that you always update to the latest PHPStan.* #+END_QUOTE ** How to install From 6d0581ca39374759a3a2a8b941b8e84f3104f8bf Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Thu, 22 May 2025 10:10:18 +0900 Subject: [PATCH 10/14] Editor mode GA --- README.org | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.org b/README.org index a03d16a..3b7e48f 100644 --- a/README.org +++ b/README.org @@ -11,7 +11,7 @@ Emacs interface to [[https://github.com/phpstan/phpstan][PHPStan]], includes che #+BEGIN_QUOTE [!TIP] -This package provides support for the [[https://phpstan.org/user-guide/editor-mode][Editor Mode]] that will be added in PHPStan 2.1.17 and 1.12.27.\\ +This package provides support for the [[https://phpstan.org/user-guide/editor-mode][Editor Mode]] introduced in PHPStan [[https://github.com/phpstan/phpstan/releases/tag/2.1.17][2.1.17]] and [[https://github.com/phpstan/phpstan/releases/tag/1.12.27][1.12.27]].\\ *We strongly recommend that you always update to the latest PHPStan.* #+END_QUOTE From 491b0a7147cd12b282d017c23ef587a49962cf97 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Thu, 22 May 2025 11:44:40 +0900 Subject: [PATCH 11/14] Do not use Editor Mode if the buffer is not being edited --- flycheck-phpstan.el | 11 ++--------- flymake-phpstan.el | 11 ++--------- phpstan.el | 15 ++++++++------- 3 files changed, 12 insertions(+), 25 deletions(-) diff --git a/flycheck-phpstan.el b/flycheck-phpstan.el index c96837e..db650ae 100644 --- a/flycheck-phpstan.el +++ b/flycheck-phpstan.el @@ -77,11 +77,6 @@ passed to `flycheck-finish-checker-process'." (string-match-p flycheck-phpstan--nofiles-message output)) (funcall next checker exit-status files output callback cwd))) -(defcustom flycheck-phpstan-fallback-to-original-analysis-if-editor-mode-unavailable t - "If non-NIL, analyze the original file when PHPStan editor mode is unavailable." - :type 'boolean - :safe #'booleanp) - (defun flycheck-phpstan--enabled-and-set-variable () "Return path to phpstan configure file, and set buffer execute in side effect." (let ((enabled (phpstan-enabled))) @@ -145,10 +140,8 @@ passed to `flycheck-finish-checker-process'." :filename file)))) (defun flycheck-phpstan-analyze-original (original) - "Return non-NIL if ORIGINAL is NIL, fallback is enabled, and buffer is modified." - (and (null original) - flycheck-phpstan-fallback-to-original-analysis-if-editor-mode-unavailable - (buffer-modified-p))) + "Return non-NIL if ORIGINAL is non-NIL and buffer is not modified." + (and original (not (buffer-modified-p)))) (flycheck-define-checker phpstan "PHP static analyzer based on PHPStan." diff --git a/flymake-phpstan.el b/flymake-phpstan.el index efa0cf1..3e24f48 100644 --- a/flymake-phpstan.el +++ b/flymake-phpstan.el @@ -52,11 +52,6 @@ :type 'boolean :group 'flymake-phpstan) -(defcustom flycheck-phpstan-fallback-to-original-analysis-if-editor-mode-unavailable t - "If non-NIL, analyze the original file when PHPStan editor mode is unavailable." - :type 'boolean - :safe #'booleanp) - (defvar-local flymake-phpstan--proc nil) (defun flymake-phpstan-make-process (root command-args report-fn source) @@ -94,10 +89,8 @@ (code (user-error "PHPStan error (exit status: %s)" code))))))) (defun flymake-phpstan-analyze-original (original) - "Return non-NIL if ORIGINAL is NIL, fallback is enabled, and buffer is modified." - (and (null original) - flymake-phpstan-fallback-to-original-analysis-if-editor-mode-unavailable - (buffer-modified-p))) + "Return non-NIL if ORIGINAL is non-NIL and buffer is not modified." + (and original (not (buffer-modified-p)))) (defun flymake-phpstan--create-temp-file () "Create temp file and return the path." diff --git a/phpstan.el b/phpstan.el index f514121..0c730cf 100644 --- a/phpstan.el +++ b/phpstan.el @@ -521,13 +521,14 @@ it returns the value of `SOURCE' as it is." (phpstan-use-xdebug-option (list "--xdebug"))) (when editor (let ((original-file (plist-get editor :original-file))) - (if (phpstan-editor-mode-available-p (car (phpstan-get-executable-and-args))) - (list "--tmp-file" (funcall (plist-get editor :temp-file)) - "--instead-of" original-file - "--" original-file) - (if (funcall (plist-get editor :analyze-original) original-file) - (list "--" original-file) - (list "--" (funcall (plist-get editor :inplace))))))) + (cond + ((funcall (plist-get editor :analyze-original) original-file) + (list "--" original-file)) + ((phpstan-editor-mode-available-p (car (phpstan-get-executable-and-args))) + (list "--tmp-file" (funcall (plist-get editor :temp-file)) + "--instead-of" original-file + "--" original-file)) + ((list "--" (funcall (plist-get editor :inplace))))))) options (and args (cons "--" args))))) From a35f991356bbf49fe364dc0c47c229652bf10825 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Thu, 22 May 2025 12:29:25 +0900 Subject: [PATCH 12/14] Explicitly set the custom variable to :local t --- phpstan.el | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/phpstan.el b/phpstan.el index 0c730cf..d373920 100644 --- a/phpstan.el +++ b/phpstan.el @@ -77,13 +77,11 @@ (defcustom phpstan-flycheck-auto-set-executable t "Set flycheck phpstan-executable automatically." - :type 'boolean - :group 'phpstan) + :type 'boolean) (defcustom phpstan-enable-on-no-config-file t "If T, activate config from composer even when `phpstan.neon' is not found." - :type 'boolean - :group 'phpstan) + :type 'boolean) (defcustom phpstan-memory-limit nil "Set --memory-limit option." @@ -92,7 +90,7 @@ :link '(url-link :tag "PHP Manual" "https://www.php.net/manual/ini.core.php#ini.memory-limit") :safe (lambda (v) (or (null v) (stringp v))) - :group 'phpstan) + :local t) (defcustom phpstan-docker-image "ghcr.io/phpstan/phpstan" "Docker image URL or Docker Hub image name or NIL." @@ -104,7 +102,7 @@ :link '(url-link :tag "GitHub Container Registry" "https://github.com/orgs/phpstan/packages/container/package/phpstan") :safe (lambda (v) (or (null v) (stringp v))) - :group 'phpstan) + :local t) (defcustom phpstan-use-xdebug-option nil "Set --xdebug option." @@ -112,31 +110,31 @@ (const :tag "Add --xdebug option" t) (const :tag "No --xdebug option" nil)) :safe #'symbolp - :group 'phpstan) + :local t) (defcustom phpstan-generate-baseline-options '("--generate-baseline" "--allow-empty-baseline") "Command line options for generating PHPStan baseline." :type '(repeat string) :safe #'listp - :group 'phpstan) + :local t) (defcustom phpstan-baseline-file "phpstan-baseline.neon" "File name of PHPStan baseline file." :type 'string :safe #'stringp - :group 'phpstan) + :local t) (defcustom phpstan-tip-message-prefix "💡 " "Prefix of PHPStan tip message." :type 'string :safe #'stringp - :group 'phpstan) + :local t) (defcustom phpstan-identifier-prefix "🪪 " "Prefix of PHPStan error identifier." :type 'string :safe #'stringp - :group 'phpstan) + :local t) (defcustom phpstan-enable-remote-experimental nil "Enable PHPStan analysis remotely by TRAMP. @@ -146,7 +144,7 @@ This feature is experimental and should be used with caution as it may have unexpected behaviors or performance implications." :type 'boolean :safe #'booleanp - :group 'phpstan) + :local t) (defconst phpstan-template-dump-type "\\PHPStan\\dumpType();") (defconst phpstan-template-dump-phpdoc-type "\\PHPStan\\dumpPhpDocType();") @@ -154,13 +152,11 @@ have unexpected behaviors or performance implications." (defcustom phpstan-intert-dump-type-templates (cons phpstan-template-dump-type phpstan-template-dump-phpdoc-type) "Default template of PHPStan dumpType insertion." - :type '(cons string string) - :group 'phpstan) + :type '(cons string string)) (defcustom phpstan-disable-buffer-errors nil "If non-NIL, don't keep errors per buffer to save memory." - :type 'boolean - :group 'phpstan) + :type 'boolean) (defcustom phpstan-not-ignorable-identifiers '("ignore.parseError") "Lists identifiers prohibited from being added to @phpstan-ignore tags." @@ -168,10 +164,11 @@ have unexpected behaviors or performance implications." (defcustom phpstan-activate-editor-mode nil "Controls how PHPStan's editor mode is activated." - :local t :type '(choice (const :tag "Automatic (based on version)" nil) - (const :tag "Editor mode will be actively enabled, regardless of the PHPStan version." 'enabled) - (const :tag "Editor mode will be explicitly disabled." 'disabled))) + (const :tag "Editor mode will be actively enabled, regardless of the PHPStan version." enabled) + (const :tag "Editor mode will be explicitly disabled." disabled)) + :safe (lambda (v) (memq v '(nil enabled disabled))) + :local t) (defvar-local phpstan--use-xdebug-option nil) @@ -573,7 +570,7 @@ it returns the value of `SOURCE' as it is." If a cached result for EXECUTABLE exists, it is returned directly. Otherwise, this function attempts to determine support by retrieving -the PHPStan version using 'phpstan --version' command." +the PHPStan version using `phpstan --version' command." (pcase phpstan-activate-editor-mode ('enabled t) ('disabled nil) From 3f8783c61db642e7c52a3d0ea49b751138fc46f3 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Thu, 22 May 2025 12:32:44 +0900 Subject: [PATCH 13/14] Add require flymake-proc --- flymake-phpstan.el | 1 + 1 file changed, 1 insertion(+) diff --git a/flymake-phpstan.el b/flymake-phpstan.el index 3e24f48..8c54f12 100644 --- a/flymake-phpstan.el +++ b/flymake-phpstan.el @@ -38,6 +38,7 @@ (require 'cl-lib) (require 'php-project) (require 'flymake) +(require 'flymake-proc) (require 'phpstan) (eval-when-compile (require 'pcase)) From 8da92fbe92dd88ba0e7add4f62da61af9a760721 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Thu, 22 May 2025 12:43:14 +0900 Subject: [PATCH 14/14] Bump version 0.9.0 --- CHANGELOG.md | 19 +++++++++++++++++-- Eask | 2 +- README.org | 7 +++++++ flycheck-phpstan.el | 2 +- flymake-phpstan.el | 2 +- phpstan.el | 2 +- 6 files changed, 28 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b4b524..53ffd34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,26 @@ All notable changes of the `phpstan.el` are documented in this file using the [Keep a Changelog](https://keepachangelog.com/) principles. -## Unreleased + + +## [0.9.0] ### Added -* Add `phpstan-copy-dumped-type` command to copy the nearest dumped type from `PHPStan\dumpType()` or `PHPStan\dumpPhpDocType()` messages. +* Add `phpstan-copy-dumped-type` command to copy the nearest dumped type from `PHPStan\dumpType()` or `PHPStan\dumpPhpDocType()` messages +* Add support for PHPStan [Editor Mode](https://phpstan.org/user-guide/editor-mode) + +### Changed + +* Improved error handling when no JSON response is returned + +### Fixed + +* Fixed erroneous dependency from `flymake-phpstan` to Flycheck functions + +### Removed + +* Drop support for Emacs 25.3 ## [0.8.2] diff --git a/Eask b/Eask index 6e4d937..dfec247 100644 --- a/Eask +++ b/Eask @@ -1,7 +1,7 @@ ;; -*- mode: eask; lexical-binding: t -*- (package "phpstan" - "0.8.2" + "0.9.0" "Interface to PHPStan (PHP static analyzer)") (website-url "https://github.com/emacs-php/phpstan.el") diff --git a/README.org b/README.org index 3b7e48f..bbaf1e8 100644 --- a/README.org +++ b/README.org @@ -162,6 +162,13 @@ Use phpstan memory limit option when non-NIL. - ex) ~"1G"~ - ~nil~ :: Use memory limit in php.ini +*** Custom variable ~phpstan-activate-editor-mode~ +Determines whether PHPStan Editor Mode is available. + +- ~nil~ (default) :: Dynamically checks the PHPStan version by getting the path of the installed PHPStan executable. +- ~'enabled~ :: Always use Editor Mode (this will cause an error in older versions of PHPStan) +- ~'disabled~ :: Never use Editor Mode (no support for editors provided) + *** Custom variable ~phpstan-docker-image~ Docker image URL or Docker Hub image name or NIL. Default as ~"ghcr.io/phpstan/phpstan"~. See [[https://phpstan.org/user-guide/docker][Docker - PHPStan Documentation]] and [[https://github.com/orgs/phpstan/packages/container/package/phpstan][GitHub Container Registory - Package phpstan]]. diff --git a/flycheck-phpstan.el b/flycheck-phpstan.el index db650ae..5fd42b9 100644 --- a/flycheck-phpstan.el +++ b/flycheck-phpstan.el @@ -4,7 +4,7 @@ ;; Author: USAMI Kenta ;; Created: 15 Mar 2018 -;; Version: 0.8.2 +;; Version: 0.9.0 ;; Keywords: tools, php ;; Homepage: https://github.com/emacs-php/phpstan.el ;; Package-Requires: ((emacs "25.1") (flycheck "26") (phpstan "0.8.2")) diff --git a/flymake-phpstan.el b/flymake-phpstan.el index 8c54f12..9e89c5f 100644 --- a/flymake-phpstan.el +++ b/flymake-phpstan.el @@ -4,7 +4,7 @@ ;; Author: USAMI Kenta ;; Created: 31 Mar 2020 -;; Version: 0.8.2 +;; Version: 0.9.0 ;; Keywords: tools, php ;; Homepage: https://github.com/emacs-php/phpstan.el ;; Package-Requires: ((emacs "26.1") (phpstan "0.8.2")) diff --git a/phpstan.el b/phpstan.el index d373920..b0135d8 100644 --- a/phpstan.el +++ b/phpstan.el @@ -4,7 +4,7 @@ ;; Author: USAMI Kenta ;; Created: 15 Mar 2018 -;; Version: 0.8.2 +;; Version: 0.9.0 ;; Keywords: tools, php ;; Homepage: https://github.com/emacs-php/phpstan.el ;; Package-Requires: ((emacs "25.1") (compat "30") (php-mode "1.22.3") (php-runtime "0.2"))