From 7bf0f231a48f2e47efc65edaf8abc6aa434283a1 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Wed, 11 Apr 2018 23:48:54 +0900 Subject: [PATCH 01/25] composer require --dev phpstan/phpstan:dev-master --- .gitignore | 2 ++ composer.json | 8 ++++++++ 2 files changed, 10 insertions(+) create mode 100644 composer.json diff --git a/.gitignore b/.gitignore index 176df11..52cfd9a 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ /.cask +/composer.lock +/vendor diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..6bca841 --- /dev/null +++ b/composer.json @@ -0,0 +1,8 @@ +{ + "name": "emacs-php/phpstan.el", + "description": "Emacs interface to PHPStan", + "license": "GPL-3.0-or-later", + "require-dev": { + "phpstan/phpstan": "dev-master" + } +} From ba7dfc80e0027a3a96698b34e6981704a5c8c9cd Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Thu, 12 Apr 2018 00:33:45 +0900 Subject: [PATCH 02/25] Add dependency php-mode in development --- Cask | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Cask b/Cask index 267ffdf..bac0818 100644 --- a/Cask +++ b/Cask @@ -3,4 +3,5 @@ (package-file "phpstan.el") (development - (depends-on "flycheck")) + (depends-on "flycheck") + (depends-on "php-mode")) From 526f0b7acc86a2ec3e6e4f17a0e4fe45069786ea Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Thu, 12 Apr 2018 00:45:15 +0900 Subject: [PATCH 03/25] Add file local variables and getter functions --- phpstan.el | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/phpstan.el b/phpstan.el index c761731..5c941eb 100644 --- a/phpstan.el +++ b/phpstan.el @@ -28,8 +28,72 @@ ;; https://github.com/phpstan/phpstan ;;; Code: +(require 'php-project) (require 'flycheck nil) + +;; Variables: + +;;;###autoload +(progn + (defvar phpstan-configure-file nil) + (make-variable-buffer-local 'phpstan-configure-file) + (put 'phpstan-configure-file 'safe-local-variable + #'(lambda (v) (if (consp v) + (and (eq 'root (car v)) (stringp (cdr v))) + (null v) (stringp v))))) + +;;;###autoload +(progn + (defvar phpstan-level "0") + (make-variable-buffer-local 'phpstan-level) + (put 'phpstan-level 'safe-local-variable + #'(lambda (v) (or (null v) + (integerp v) + (and (stringp v) + (string-match-p "\\`[1-9][0-9]*\\'" v)))))) + +;;;###autoload +(progn + (defvar phpstan-executable nil) + (make-variable-buffer-local 'phpstan-executable) + (put 'phpstan-executable 'safe-local-variable + #'(lambda (v) (if (consp v) + (and (eq 'root (car v)) (stringp (cdr v))) + (null v) (stringp v))))) + +;; Functions: +(defun phpstan-get-configure-file () + "Return path to phpstan configure file or `NIL'." + (if phpstan-configure-file + (if (and (consp phpstan-configure-file) + (eq 'root (car phpstan-configure-file))) + (expand-file-name (cdr phpstan-configure-file) (php-project-get-root-dir)) + phpstan-configure-file) + (let ((dir (locate-dominating-file "phpstan.neon" default-directory))) + (when dir + (expand-file-name "phpstan.neon" dir))))) + +(defun phpstan-get-level () + "Return path to phpstan configure file or `NIL'." + (cond + ((null phpstan-level) "0") + ((integerp phpstan-level) (int-to-string phpstan-level)) + (t phpstan-level))) + +(defun phpstan-get-executable () + "Return PHPStan excutable file." + (let ((executable (or phpstan-executable '(root . "vendor/bin/phpstan")))) + (when (and (consp executable) + (eq 'root (car executable))) + (setq executable + (expand-file-name (cdr executable) (php-project-get-root-dir)))) + (if (file-exists-p executable) + executable + (if (executable-find "phpstan") + "phpstan" + (error "PHPStan executable not found"))))) + ;;;###autoload (when (featurep 'flycheck) (flycheck-define-checker phpstan-checker From 968b8fe21da480703a923c73a0fc1405f5bb4dfb Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Thu, 12 Apr 2018 00:55:21 +0900 Subject: [PATCH 04/25] Consider the existence of "phpstan.neon.dist" --- phpstan.el | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/phpstan.el b/phpstan.el index 5c941eb..41c8c29 100644 --- a/phpstan.el +++ b/phpstan.el @@ -70,9 +70,14 @@ (eq 'root (car phpstan-configure-file))) (expand-file-name (cdr phpstan-configure-file) (php-project-get-root-dir)) phpstan-configure-file) - (let ((dir (locate-dominating-file "phpstan.neon" default-directory))) + (let ((dir (or (locate-dominating-file "phpstan.neon" default-directory) + (locate-dominating-file "phpstan.neon.dist" default-directory))) + file) (when dir - (expand-file-name "phpstan.neon" dir))))) + (setq file (expand-file-name "phpstan.neon.dist" dir)) + (if (file-exists-p file) + file + (expand-file-name "phpstan.neon" dir)))))) (defun phpstan-get-level () "Return path to phpstan configure file or `NIL'." From c48251b53d32dcd0d122de1c5b4d72bfe44087a6 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Thu, 12 Apr 2018 01:02:27 +0900 Subject: [PATCH 05/25] Refactor using cl-loop --- phpstan.el | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/phpstan.el b/phpstan.el index 41c8c29..65dd683 100644 --- a/phpstan.el +++ b/phpstan.el @@ -70,14 +70,11 @@ (eq 'root (car phpstan-configure-file))) (expand-file-name (cdr phpstan-configure-file) (php-project-get-root-dir)) phpstan-configure-file) - (let ((dir (or (locate-dominating-file "phpstan.neon" default-directory) - (locate-dominating-file "phpstan.neon.dist" default-directory))) - file) - (when dir - (setq file (expand-file-name "phpstan.neon.dist" dir)) - (if (file-exists-p file) - file - (expand-file-name "phpstan.neon" dir)))))) + (cl-loop for name in '("phpstan.neon" "phpstan.neon.dist") + for file = nil + for dir = (locate-dominating-file default-directory name) + if dir + return (expand-file-name name dir)))) (defun phpstan-get-level () "Return path to phpstan configure file or `NIL'." From a5f17115a4c8e30f22e7719340de342fc889256a Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Thu, 12 Apr 2018 01:02:55 +0900 Subject: [PATCH 06/25] Modify flycheck checker --- phpstan.el | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/phpstan.el b/phpstan.el index 65dd683..6d15ab6 100644 --- a/phpstan.el +++ b/phpstan.el @@ -100,13 +100,13 @@ (when (featurep 'flycheck) (flycheck-define-checker phpstan-checker "PHP static analyzer based on PHPStan." - :command ("phpstan" - "analyze" - "--no-progress" - "--errorFormat=raw" + :command ("php" (eval (phpstan-get-executable)) + "analyze" "--errorFormat=raw" "--no-progress" "--no-interaction" + "-c" (eval (phpstan-get-configure-file)) + "-l" (eval (phpstan-get-level)) source) :working-directory (lambda (_) (php-project-get-root-dir)) - :enabled (lambda () (locate-dominating-file "phpstan.neon" default-directory)) + :enabled (lambda () (phpstan-get-configure-file)) :error-patterns ((error line-start (1+ (not (any ":"))) ":" line ":" (message) line-end)) :modes (php-mode) From 45ba77943906cd64a1578f96d62a6d3d30c5fcd2 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Thu, 12 Apr 2018 01:04:02 +0900 Subject: [PATCH 07/25] Rename flycheck checker --- phpstan.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpstan.el b/phpstan.el index 6d15ab6..fd72c9f 100644 --- a/phpstan.el +++ b/phpstan.el @@ -98,7 +98,7 @@ ;;;###autoload (when (featurep 'flycheck) - (flycheck-define-checker phpstan-checker + (flycheck-define-checker phpstan "PHP static analyzer based on PHPStan." :command ("php" (eval (phpstan-get-executable)) "analyze" "--errorFormat=raw" "--no-progress" "--no-interaction" From bc56031795df97a1dadf15b85712915ac22461d8 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Thu, 12 Apr 2018 01:06:00 +0900 Subject: [PATCH 08/25] Add test files --- phpstan.neon.dist | 1 + test.php | 13 +++++++++++++ tests/bootstrap.php | 5 +++++ tests/phpstan.neon | 2 ++ 4 files changed, 21 insertions(+) create mode 100644 phpstan.neon.dist create mode 100644 test.php create mode 100644 tests/bootstrap.php create mode 100644 tests/phpstan.neon diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..9ce06a8 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1 @@ +# dummy diff --git a/test.php b/test.php new file mode 100644 index 0000000..55088ed --- /dev/null +++ b/test.php @@ -0,0 +1,13 @@ + Date: Thu, 12 Apr 2018 01:08:01 +0900 Subject: [PATCH 09/25] Use config-file instead of configure-file --- phpstan.el | 22 +++++++++++----------- test.php | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/phpstan.el b/phpstan.el index fd72c9f..33d6f1a 100644 --- a/phpstan.el +++ b/phpstan.el @@ -36,9 +36,9 @@ ;;;###autoload (progn - (defvar phpstan-configure-file nil) - (make-variable-buffer-local 'phpstan-configure-file) - (put 'phpstan-configure-file 'safe-local-variable + (defvar phpstan-config-file nil) + (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))) (null v) (stringp v))))) @@ -63,13 +63,13 @@ (null v) (stringp v))))) ;; Functions: -(defun phpstan-get-configure-file () +(defun phpstan-get-config-file () "Return path to phpstan configure file or `NIL'." - (if phpstan-configure-file - (if (and (consp phpstan-configure-file) - (eq 'root (car phpstan-configure-file))) - (expand-file-name (cdr phpstan-configure-file) (php-project-get-root-dir)) - phpstan-configure-file) + (if phpstan-config-file + (if (and (consp phpstan-config-file) + (eq 'root (car phpstan-config-file))) + (expand-file-name (cdr phpstan-config-file) (php-project-get-root-dir)) + phpstan-config-file) (cl-loop for name in '("phpstan.neon" "phpstan.neon.dist") for file = nil for dir = (locate-dominating-file default-directory name) @@ -102,11 +102,11 @@ "PHP static analyzer based on PHPStan." :command ("php" (eval (phpstan-get-executable)) "analyze" "--errorFormat=raw" "--no-progress" "--no-interaction" - "-c" (eval (phpstan-get-configure-file)) + "-c" (eval (phpstan-get-config-file)) "-l" (eval (phpstan-get-level)) source) :working-directory (lambda (_) (php-project-get-root-dir)) - :enabled (lambda () (phpstan-get-configure-file)) + :enabled (lambda () (phpstan-get-config-file)) :error-patterns ((error line-start (1+ (not (any ":"))) ":" line ":" (message) line-end)) :modes (php-mode) diff --git a/test.php b/test.php index 55088ed..b1cc8b3 100644 --- a/test.php +++ b/test.php @@ -8,6 +8,6 @@ echo FOO; // Local Variables: -// phpstan-configure-file: (root . "tests/phpstan.neon") +// phpstan-config-file: (root . "tests/phpstan.neon") // phpstan-level: 7 // End: From 6fa077bd9421db1a13cdd054824712a81218d91c Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Thu, 12 Apr 2018 10:14:14 +0900 Subject: [PATCH 10/25] Remove unused variable --- phpstan.el | 1 - 1 file changed, 1 deletion(-) diff --git a/phpstan.el b/phpstan.el index 33d6f1a..4039f6b 100644 --- a/phpstan.el +++ b/phpstan.el @@ -71,7 +71,6 @@ (expand-file-name (cdr phpstan-config-file) (php-project-get-root-dir)) phpstan-config-file) (cl-loop for name in '("phpstan.neon" "phpstan.neon.dist") - for file = nil for dir = (locate-dominating-file default-directory name) if dir return (expand-file-name name dir)))) From 6840f6c14017b8ce1177953c470fcb4630865ae5 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Thu, 12 Apr 2018 10:22:10 +0900 Subject: [PATCH 11/25] Fix phpstan executable --- phpstan.el | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/phpstan.el b/phpstan.el index 4039f6b..507ff8d 100644 --- a/phpstan.el +++ b/phpstan.el @@ -91,9 +91,8 @@ (expand-file-name (cdr executable) (php-project-get-root-dir)))) (if (file-exists-p executable) executable - (if (executable-find "phpstan") - "phpstan" - (error "PHPStan executable not found"))))) + (or (executable-find "phpstan") + (error "PHPStan executable not found"))))) ;;;###autoload (when (featurep 'flycheck) From a198897fb88aedbf2662378425c972cca99a4372 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Thu, 12 Apr 2018 20:06:55 +0900 Subject: [PATCH 12/25] Fix pattern of level (accepts 1 digit number) --- phpstan.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpstan.el b/phpstan.el index 507ff8d..93450cc 100644 --- a/phpstan.el +++ b/phpstan.el @@ -51,7 +51,7 @@ #'(lambda (v) (or (null v) (integerp v) (and (stringp v) - (string-match-p "\\`[1-9][0-9]*\\'" v)))))) + (string-match-p "\\`[0-9]\\'" v)))))) ;;;###autoload (progn From 63fd9eb4633c03363ed47d187cb4dd8df90270c4 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Thu, 12 Apr 2018 23:02:30 +0900 Subject: [PATCH 13/25] Add defgroup phpstan and custom variable phpstan-flycheck-auto-set-executable --- phpstan.el | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/phpstan.el b/phpstan.el index 93450cc..0964173 100644 --- a/phpstan.el +++ b/phpstan.el @@ -34,6 +34,20 @@ ;; Variables: +(defgroup phpstan nil + "Interaface to PHPStan" + :tag "PHPStan" + :prefix "phpstan-" + :group 'tools + :group 'php + :link '(url-link :tag "PHPStan" "https://github.com/phpstan/phpstan") + :link '(url-link :tag "phpstan.el" "https://github.com/emacs-php/phpstan.el")) + +(defcustom phpstan-flycheck-auto-set-executable t + "Set flycheck phpstan-executable automatically." + :type 'boolean + :group 'phpstan) + ;;;###autoload (progn (defvar phpstan-config-file nil) From 4555592f129bee7c32d4f73243666c6223538c8f Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Thu, 12 Apr 2018 23:03:48 +0900 Subject: [PATCH 14/25] Modify phpstan-executable specification --- phpstan.el | 45 +++++++++++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/phpstan.el b/phpstan.el index 0964173..07b5d51 100644 --- a/phpstan.el +++ b/phpstan.el @@ -69,12 +69,26 @@ ;;;###autoload (progn - (defvar phpstan-executable nil) + (defvar phpstan-executable nil + "PHPStan excutable file. + +STRING + Absolute path to `phpstan' executable file. + +`(root . STRING)' + Relative path to `phpstan' executable file. + +`(STRING . (ARGUMENTS ...))' + Command name and arguments. + +NIL + Auto detect `phpstan' executable file.") (make-variable-buffer-local 'phpstan-executable) (put 'phpstan-executable 'safe-local-variable #'(lambda (v) (if (consp v) - (and (eq 'root (car v)) (stringp (cdr v))) - (null v) (stringp v))))) + (or (and (eq 'root (car v)) (stringp (cdr v))) + (and (stringp (car v)) (listp (cdr v)))) + (or (null v) (stringp v)))))) ;; Functions: (defun phpstan-get-config-file () @@ -98,15 +112,22 @@ (defun phpstan-get-executable () "Return PHPStan excutable file." - (let ((executable (or phpstan-executable '(root . "vendor/bin/phpstan")))) - (when (and (consp executable) - (eq 'root (car executable))) - (setq executable - (expand-file-name (cdr executable) (php-project-get-root-dir)))) - (if (file-exists-p executable) - executable - (or (executable-find "phpstan") - (error "PHPStan executable not found"))))) + (cond + ((and (consp phpstan-executable) + (eq 'root (car phpstan-executable))) + (expand-file-name (cdr phpstan-executable) (php-project-get-root-dir))) + ((and phpstan-flycheck-auto-set-executable + (listp phpstan-executable) + (stringp (car phpstan-executable)) + (listp (cdr phpstan-executable))) + (cdr phpstan-executable)) + ((null phpstan-executable) + (let ((vendor-phpstan (expand-file-name "vendor/bin/phpstan" + (php-project-get-root-dir)))) + (cond + ((file-exists-p vendor-phpstan) vendor-phpstan) + ((executable-find "phpstan") (executable-find "phpstan")) + (t (error "PHPStan executable not found"))))))) ;;;###autoload (when (featurep 'flycheck) From d559ee1e74b872176ed2f5c14bb24590fa1f5887 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Thu, 12 Apr 2018 23:14:37 +0900 Subject: [PATCH 15/25] Add phpstan-get-config-file-and-set-flycheck-variable as side effect --- phpstan.el | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/phpstan.el b/phpstan.el index 07b5d51..c393fc1 100644 --- a/phpstan.el +++ b/phpstan.el @@ -67,6 +67,9 @@ (and (stringp v) (string-match-p "\\`[0-9]\\'" v)))))) +;; Usually it is defined dynamically by flycheck +(defvar flycheck-phpstan-executable) + ;;;###autoload (progn (defvar phpstan-executable nil @@ -103,6 +106,17 @@ NIL if dir return (expand-file-name name dir)))) +(defun phpstan-get-config-file-and-set-flycheck-variable () + "Return path to phpstan configure file, and set buffer execute in side effect." + (prog1 (phpstan-get-config-file) + (when (and phpstan-flycheck-auto-set-executable + (not (and (boundp 'flycheck-phpstan-executable) + (symbol-value 'flycheck-phpstan-executable))) + (stringp (car phpstan-executable)) + (listp (cdr phpstan-executable))) + (set (make-local-variable 'flycheck-phpstan-executable) + (car phpstan-executable))))) + (defun phpstan-get-level () "Return path to phpstan configure file or `NIL'." (cond @@ -139,7 +153,7 @@ NIL "-l" (eval (phpstan-get-level)) source) :working-directory (lambda (_) (php-project-get-root-dir)) - :enabled (lambda () (phpstan-get-config-file)) + :enabled (lambda () (phpstan-get-config-file-and-set-flycheck-variable)) :error-patterns ((error line-start (1+ (not (any ":"))) ":" line ":" (message) line-end)) :modes (php-mode) From 05c9f761bd65bc0977f87b77417de9a980def0bc Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Thu, 12 Apr 2018 23:22:36 +0900 Subject: [PATCH 16/25] Add phpstan-normalize-path and support Docker --- phpstan.el | 30 +++++++++++++++++++++++++++++- test-docker.php | 14 ++++++++++++++ tests/phpstan-docker.neon | 2 ++ 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 test-docker.php create mode 100644 tests/phpstan-docker.neon diff --git a/phpstan.el b/phpstan.el index c393fc1..2ba7225 100644 --- a/phpstan.el +++ b/phpstan.el @@ -67,6 +67,13 @@ (and (stringp v) (string-match-p "\\`[0-9]\\'" v)))))) +;;;###autoload +(progn + (defvar phpstan-replace-path-prefix) + (make-variable-buffer-local 'phpstan-replace-path-prefix) + (put 'phpstan-replace-path-prefix 'safe-local-variable + #'(lambda (v) (or (null v) (stringp v))))) + ;; Usually it is defined dynamically by flycheck (defvar flycheck-phpstan-executable) @@ -117,6 +124,25 @@ NIL (set (make-local-variable 'flycheck-phpstan-executable) (car phpstan-executable))))) +(defun phpstan-normalize-path (source-original source) + "Return normalized source file path to pass by `SOURCE-ORIGINAL' OR `SOURCE'. + +If neither `phpstan-replace-path-prefix' nor executable docker is set, +it returns the value of `SOURCE' as it is." + (let ((working-directory (expand-file-name (php-project-get-root-dir))) + (prefix + (cond + ((not (null phpstan-replace-path-prefix)) phpstan-replace-path-prefix) + ((and (consp phpstan-executable) + (string= "docker" (car phpstan-executable))) "/app")))) + (if prefix + (expand-file-name + (replace-regexp-in-string (concat "\\`" (regexp-quote working-directory)) + "" + source-original t t) + prefix) + source))) + (defun phpstan-get-level () "Return path to phpstan configure file or `NIL'." (cond @@ -151,7 +177,9 @@ NIL "analyze" "--errorFormat=raw" "--no-progress" "--no-interaction" "-c" (eval (phpstan-get-config-file)) "-l" (eval (phpstan-get-level)) - source) + (eval (phpstan-normalize-path + (flycheck-save-buffer-to-temp #'flycheck-temp-file-inplace) + (flycheck-save-buffer-to-temp #'flycheck-temp-file-system)))) :working-directory (lambda (_) (php-project-get-root-dir)) :enabled (lambda () (phpstan-get-config-file-and-set-flycheck-variable)) :error-patterns diff --git a/test-docker.php b/test-docker.php new file mode 100644 index 0000000..b28cf5a --- /dev/null +++ b/test-docker.php @@ -0,0 +1,14 @@ + Date: Thu, 12 Apr 2018 23:40:04 +0900 Subject: [PATCH 17/25] Add 'docker keyword --- phpstan.el | 22 ++++++++++++++++++---- test-docker.php | 2 +- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/phpstan.el b/phpstan.el index 2ba7225..3b25e9d 100644 --- a/phpstan.el +++ b/phpstan.el @@ -74,6 +74,8 @@ (put 'phpstan-replace-path-prefix 'safe-local-variable #'(lambda (v) (or (null v) (stringp v))))) +(defconst phpstan-docker-executable "docker") + ;; Usually it is defined dynamically by flycheck (defvar flycheck-phpstan-executable) @@ -85,6 +87,9 @@ STRING Absolute path to `phpstan' executable file. +`docker' + Use Docker using phpstan/docker-image. + `(root . STRING)' Relative path to `phpstan' executable file. @@ -98,7 +103,7 @@ NIL #'(lambda (v) (if (consp v) (or (and (eq 'root (car v)) (stringp (cdr v))) (and (stringp (car v)) (listp (cdr v)))) - (or (null v) (stringp v)))))) + (or (eq 'docker v) (null v) (stringp v)))))) ;; Functions: (defun phpstan-get-config-file () @@ -119,10 +124,14 @@ NIL (when (and phpstan-flycheck-auto-set-executable (not (and (boundp 'flycheck-phpstan-executable) (symbol-value 'flycheck-phpstan-executable))) - (stringp (car phpstan-executable)) - (listp (cdr phpstan-executable))) + (or (eq 'docker phpstan-executable) + (and (consp phpstan-executable) + (stringp (car phpstan-executable)) + (listp (cdr phpstan-executable))))) (set (make-local-variable 'flycheck-phpstan-executable) - (car phpstan-executable))))) + (if (eq 'docker phpstan-executable) + phpstan-docker-executable + (car phpstan-executable)))))) (defun phpstan-normalize-path (source-original source) "Return normalized source file path to pass by `SOURCE-ORIGINAL' OR `SOURCE'. @@ -133,6 +142,7 @@ it returns the value of `SOURCE' as it is." (prefix (cond ((not (null phpstan-replace-path-prefix)) phpstan-replace-path-prefix) + ((eq 'docker phpstan-executable) "/app") ((and (consp phpstan-executable) (string= "docker" (car phpstan-executable))) "/app")))) (if prefix @@ -153,6 +163,10 @@ it returns the value of `SOURCE' as it is." (defun phpstan-get-executable () "Return PHPStan excutable file." (cond + ((eq 'docker phpstan-executable) + (list "run" "--rm" "-v" + (concat (expand-file-name (php-project-get-root-dir)) ":/app") + "phpstan/phpstan")) ((and (consp phpstan-executable) (eq 'root (car phpstan-executable))) (expand-file-name (cdr phpstan-executable) (php-project-get-root-dir))) diff --git a/test-docker.php b/test-docker.php index b28cf5a..57f7958 100644 --- a/test-docker.php +++ b/test-docker.php @@ -8,7 +8,7 @@ echo FOO; // Local Variables: -// phpstan-executable: ("docker" "run" "--rm" "-v" "/Users/megurine/repo/emacs/phpstan.el/:/app" "phpstan/phpstan") +// phpstan-executable: docker // phpstan-config-file: "/app/tests/phpstan-docker.neon" // phpstan-level: 7 // End: From 23d1860f76e170e50a69fa77e240967643a9eee3 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Fri, 13 Apr 2018 00:07:03 +0900 Subject: [PATCH 18/25] Normalize phpstan-config-file for Docker --- phpstan.el | 6 +++--- test-docker.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/phpstan.el b/phpstan.el index 3b25e9d..431222a 100644 --- a/phpstan.el +++ b/phpstan.el @@ -133,7 +133,7 @@ NIL phpstan-docker-executable (car phpstan-executable)))))) -(defun phpstan-normalize-path (source-original source) +(defun phpstan-normalize-path (source-original &optional source) "Return normalized source file path to pass by `SOURCE-ORIGINAL' OR `SOURCE'. If neither `phpstan-replace-path-prefix' nor executable docker is set, @@ -151,7 +151,7 @@ it returns the value of `SOURCE' as it is." "" source-original t t) prefix) - source))) + (or source source-original)))) (defun phpstan-get-level () "Return path to phpstan configure file or `NIL'." @@ -189,7 +189,7 @@ it returns the value of `SOURCE' as it is." "PHP static analyzer based on PHPStan." :command ("php" (eval (phpstan-get-executable)) "analyze" "--errorFormat=raw" "--no-progress" "--no-interaction" - "-c" (eval (phpstan-get-config-file)) + "-c" (eval (phpstan-normalize-path (phpstan-get-config-file))) "-l" (eval (phpstan-get-level)) (eval (phpstan-normalize-path (flycheck-save-buffer-to-temp #'flycheck-temp-file-inplace) diff --git a/test-docker.php b/test-docker.php index 57f7958..f738509 100644 --- a/test-docker.php +++ b/test-docker.php @@ -9,6 +9,6 @@ // Local Variables: // phpstan-executable: docker -// phpstan-config-file: "/app/tests/phpstan-docker.neon" +// phpstan-config-file: (root . "tests/phpstan-docker.neon") // phpstan-level: 7 // End: From dffcad74cfb260cf8093c0a7d26c546ff3e9d8d5 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Fri, 13 Apr 2018 00:45:42 +0900 Subject: [PATCH 19/25] Add 'max keyword to phpstan-level --- phpstan.el | 3 +++ 1 file changed, 3 insertions(+) diff --git a/phpstan.el b/phpstan.el index 431222a..114537d 100644 --- a/phpstan.el +++ b/phpstan.el @@ -64,7 +64,9 @@ (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)))))) ;;;###autoload @@ -158,6 +160,7 @@ it returns the value of `SOURCE' as it is." (cond ((null phpstan-level) "0") ((integerp phpstan-level) (int-to-string phpstan-level)) + ((symbolp phpstan-level) (symbol-name phpstan-level)) (t phpstan-level))) (defun phpstan-get-executable () From 3748533a0f644e3cd4215400b8c9544b10f06766 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Fri, 13 Apr 2018 01:51:45 +0900 Subject: [PATCH 20/25] Add phpstan-working-dir for working directory of PHPStan --- phpstan.el | 28 +++++++++++++++++++++++++++- test-docker.php | 1 + tests/phpstan-docker.neon | 2 +- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/phpstan.el b/phpstan.el index 114537d..f79e2b5 100644 --- a/phpstan.el +++ b/phpstan.el @@ -48,6 +48,15 @@ :type 'boolean :group 'phpstan) +;;;###autoload +(progn + (defvar phpstan-working-dir nil) + (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))) + (null v) (stringp v))))) + ;;;###autoload (progn (defvar phpstan-config-file nil) @@ -108,6 +117,23 @@ NIL (or (eq 'docker v) (null v) (stringp v)))))) ;; Functions: +(defun phpstan-get-working-dir () + "Return working directory of PHPStan. + +This is different from the project root. + +STRING + Absolute path to `phpstan' working directory. + +`(root . STRING)' + Relative path to `phpstan' working directory. + +NIL + Use (php-project-get-root-dir) as working directory." + (if (and phpstan-working-dir (consp phpstan-working-dir) (eq 'root (car phpstan-working-dir))) + (expand-file-name (cdr phpstan-working-dir) (php-project-get-root-dir)) + (php-project-get-root-dir))) + (defun phpstan-get-config-file () "Return path to phpstan configure file or `NIL'." (if phpstan-config-file @@ -197,7 +223,7 @@ it returns the value of `SOURCE' as it is." (eval (phpstan-normalize-path (flycheck-save-buffer-to-temp #'flycheck-temp-file-inplace) (flycheck-save-buffer-to-temp #'flycheck-temp-file-system)))) - :working-directory (lambda (_) (php-project-get-root-dir)) + :working-directory (lambda (_) (phpstan-get-working-dir)) :enabled (lambda () (phpstan-get-config-file-and-set-flycheck-variable)) :error-patterns ((error line-start (1+ (not (any ":"))) ":" line ":" (message) line-end)) diff --git a/test-docker.php b/test-docker.php index f738509..2fa9634 100644 --- a/test-docker.php +++ b/test-docker.php @@ -9,6 +9,7 @@ // Local Variables: // phpstan-executable: docker +// phpstan-working-dir: (root . "tests/") // phpstan-config-file: (root . "tests/phpstan-docker.neon") // phpstan-level: 7 // End: diff --git a/tests/phpstan-docker.neon b/tests/phpstan-docker.neon index dded03c..356d55c 100644 --- a/tests/phpstan-docker.neon +++ b/tests/phpstan-docker.neon @@ -1,2 +1,2 @@ parameters: - bootstrap: %rootDir%/../../../../app/tests/bootstrap.php + bootstrap: %currentWorkingDirectory%/../app/tests/bootstrap.php From 4d53e6093b56871fae8eb84fa1ab9ba27f2d4cca Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Fri, 13 Apr 2018 01:52:36 +0900 Subject: [PATCH 21/25] Rename root-directory local variable --- phpstan.el | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/phpstan.el b/phpstan.el index f79e2b5..334d9ab 100644 --- a/phpstan.el +++ b/phpstan.el @@ -166,7 +166,7 @@ NIL If neither `phpstan-replace-path-prefix' nor executable docker is set, it returns the value of `SOURCE' as it is." - (let ((working-directory (expand-file-name (php-project-get-root-dir))) + (let ((root-directory (expand-file-name (php-project-get-root-dir))) (prefix (cond ((not (null phpstan-replace-path-prefix)) phpstan-replace-path-prefix) @@ -175,7 +175,7 @@ it returns the value of `SOURCE' as it is." (string= "docker" (car phpstan-executable))) "/app")))) (if prefix (expand-file-name - (replace-regexp-in-string (concat "\\`" (regexp-quote working-directory)) + (replace-regexp-in-string (concat "\\`" (regexp-quote root-directory)) "" source-original t t) prefix) From 56355b050cc7210393d6d11050b66c11f5db9d9e Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Fri, 13 Apr 2018 01:58:38 +0900 Subject: [PATCH 22/25] Search phpstan.neon from (phpstan-get-working-dir) --- phpstan.el | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/phpstan.el b/phpstan.el index 334d9ab..6947c79 100644 --- a/phpstan.el +++ b/phpstan.el @@ -139,12 +139,14 @@ NIL (if phpstan-config-file (if (and (consp phpstan-config-file) (eq 'root (car phpstan-config-file))) + ;; Use (php-project-get-root-dir), not phpstan-working-dir. (expand-file-name (cdr phpstan-config-file) (php-project-get-root-dir)) phpstan-config-file) - (cl-loop for name in '("phpstan.neon" "phpstan.neon.dist") - for dir = (locate-dominating-file default-directory name) - if dir - return (expand-file-name name dir)))) + (let ((working-directory (phpstan-get-working-dir))) + (cl-loop for name in '("phpstan.neon" "phpstan.neon.dist") + for dir = (locate-dominating-file working-directory name) + if dir + return (expand-file-name name dir))))) (defun phpstan-get-config-file-and-set-flycheck-variable () "Return path to phpstan configure file, and set buffer execute in side effect." From 21cf970d3b43478e035e59199fbae02875a41d68 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Fri, 13 Apr 2018 03:05:07 +0900 Subject: [PATCH 23/25] Modify doc comment and README --- README.org | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ phpstan.el | 48 ++++++++++++++++++--------- 2 files changed, 129 insertions(+), 15 deletions(-) diff --git a/README.org b/README.org index e2bddd9..0ba6b45 100644 --- a/README.org +++ b/README.org @@ -1,2 +1,98 @@ * phpstan.el Emacs interface to [[https://github.com/phpstan/phpstan][PHPStan]], includes checker for [[http://www.flycheck.org/en/latest/][Flycheck]]. +** Support version +- Emacs 24+ +- PHPStan latest/dev-master (NOT support 0.9 seriese) +** How to install +*** Install from MELPA +/TBD/ +** How to use +*** For Flycheck user +/TBD/ +*** For Flymake user +The function for flymake will be implemented soon. You do not have to depend on flycheck. +*** Using Docker (phpstan/docker-image) +Install [[https://www.docker.com/community-edition][Docker CE]] and [[https://github.com/phpstan/docker-image][phpstan/docker-image]](latest). + +If you always use Docker for PHPStan, add the following into your ~.emacs~ file (~~/.emacs.d/init.el~) +#+BEGIN_SRC emacs-lisp +(setq-default phpstan-executable 'docker) +#+END_SRC + +Put the following into ~.dir-locals.el~ files on the root directory of project. +#+BEGIN_SRC emacs-lisp +((nil . ((php-project-root . git) + (phpstan-executable . docker) + (phpstan-working-dir . (root . "path/to/dir")) + (phpstan-config-file . (root . "path/to/dir/phpstan-docker.neon")) + (phpstan-level . 7)))) +#+END_SRC + +*** Using composer (project specific) +If your project Composer relies on phpstan, you do not need to set anything. +#+BEGIN_SRC emacs-lisp +((nil . ((php-project-root . git) + (phpstan-executable . docker) + (phpstan-working-dir . (root . "path/to/dir")) + (phpstan-config-file . (root . "path/to/dir/phpstan-docker.neon")) + (phpstan-level . 7)))) +#+END_SRC +*** Using PHAR archive +*NOTICE*: ~phpstan.el~ is incompatible with the [[https://github.com/phpstan/phpstan/releases][released versions]] of PHPStan. It will probably be distributed in the form of the Phar archive when the current development version is officially released in the near future. + +If you want to use the Phar archive you built yourself, set the Phar archive path to phpstan-executable. + +** Settings +Variables for phpstan are mainly controlled by [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Directory-Variables.html][directory variables]] (~.dir-locals.el~). + +Frequently ~(root. "path/to/file")~ notation appears in these variables. It is relative to the top level directory of the project. In general, the directory containing one of ~.projectile~, ~composer.json~, ~.git~ file (or directory) is at the top level. + +Please be aware that the root directory of the PHP project may *NOT* match either of PHPStan's ~%rootDir%~ and/or ~%currentWorkingDirectory%~. + +Typically, you would set the following ~.dir-locals.el~. + +#+BEGIN_SRC emacs-lisp +((nil . ((php-project-root . auto) + (phpstan-executable . docker) + (phpstan-working-dir . (root . "path/to/dir/")) + (phpstan-config-file . (root . "path/to/dir/phpstan-custom.neon")) + (phpstan-level . max)))) +#+END_SRC + +If there is a ~phpstan.neon~ file in the root directory of the project, you do not need to set both ~phpstan-working-dir~ and ~phpstan-config-file~. + +** 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]]. + +*** Local variable ~phpstan-working-dir~ +Path to working directory of PHPStan. + +- STRING :: Absolute path to `phpstan' working directory. + - ex) ~"/path/to/phpstan.phar"~ +- ~(root . STRING)~ :: Relative path to `phpstan' working directory from project root directory. + - ex) ~(root . "path/to/dir")~ +- ~nil~ :: Use ~(php-project-get-root-dir)~ as working directory. + +*** Local variable ~phpstan-config-file~ +Path to project specific configuration file of PHPStan. + +- STRING :: Absolute path to ~phpstan~ configuration file. +- ~(root . STRING)~ :: Relative path to ~phpstan~ configuration file from project root directory. +- NIL :: Search ~phpstan.neon(.dist)~ in ~(phpstan-get-working-dir)~. + +*** Local variable ~phpstan-level~ +Rule level of PHPStan analysis. Please see [[https://github.com/phpstan/phpstan/blob/master/README.md#rule-levels][README #Rule levels of PHPStan]]. +~0~ is the loosest and you can also use ~max~ as an alias for the highest level. Default level is ~0~. + +*** Local variable ~phpstan-executable~ +- STRING :: Absolute path to `phpstan' executable file. + - ex) ~"/path/to/phpstan.phar"~ +- SYMBOL ~docker~ :: Use Docker using phpstan/docker-image. +- ~(root . STRING)~ :: Relative path to `phpstan' executable file from project root directory. + - ex) ~(root . "script/phpstan")~ +- ~(STRING . (ARGUMENTS ...))~ :: Command name and arguments. + - ex) ~("docker" "run" "--rm" "-v" "/path/to/project-dir/:/app" "your/docker-image")~ +- ~nil~ :: Auto detect ~phpstan~ executable file by composer dependencies of the project or executable command in ~PATH~ environment variable. + +*** Custom variable ~phpstan-flycheck-auto-set-executable~ +Set flycheck phpstan-executable automatically when non-NIL. diff --git a/phpstan.el b/phpstan.el index 6947c79..c8542f4 100644 --- a/phpstan.el +++ b/phpstan.el @@ -50,7 +50,19 @@ ;;;###autoload (progn - (defvar phpstan-working-dir nil) + (defvar phpstan-working-dir nil + "Path to working directory of PHPStan. + +*NOTICE*: This is different from the project root. + +STRING + Absolute path to `phpstan' working directory. + +`(root . STRING)' + Relative path to `phpstan' working directory from project root directory. + +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) @@ -59,7 +71,17 @@ ;;;###autoload (progn - (defvar phpstan-config-file nil) + (defvar phpstan-config-file nil + "Path to project specific configuration file of PHPStan. + +STRING + Absolute path to `phpstan' configuration file. + +`(root . STRING)' + Relative path to `phpstan' configuration file from project root directory. + +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) @@ -68,7 +90,14 @@ ;;;###autoload (progn - (defvar phpstan-level "0") + (defvar phpstan-level "0" + "Rule level of PHPStan. + +INTEGER or STRING + Number of PHPStan rule level. + +max + The highest of PHPStan rule level.") (make-variable-buffer-local 'phpstan-level) (put 'phpstan-level 'safe-local-variable #'(lambda (v) (or (null v) @@ -118,18 +147,7 @@ NIL ;; Functions: (defun phpstan-get-working-dir () - "Return working directory of PHPStan. - -This is different from the project root. - -STRING - Absolute path to `phpstan' working directory. - -`(root . STRING)' - Relative path to `phpstan' working directory. - -NIL - Use (php-project-get-root-dir) as working directory." + "Return path to working directory of PHPStan." (if (and phpstan-working-dir (consp phpstan-working-dir) (eq 'root (car phpstan-working-dir))) (expand-file-name (cdr phpstan-working-dir) (php-project-get-root-dir)) (php-project-get-root-dir))) From 8d6379892e06fd6ce8ef57921d61ad61baa714c3 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Sat, 14 Apr 2018 00:34:43 +0900 Subject: [PATCH 24/25] Modify phpstan-enabled-and-set-flycheck-variable --- phpstan.el | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/phpstan.el b/phpstan.el index c8542f4..2a73fdd 100644 --- a/phpstan.el +++ b/phpstan.el @@ -166,20 +166,21 @@ NIL if dir return (expand-file-name name dir))))) -(defun phpstan-get-config-file-and-set-flycheck-variable () +(defun phpstan-enabled-and-set-flycheck-variable () "Return path to phpstan configure file, and set buffer execute in side effect." - (prog1 (phpstan-get-config-file) - (when (and phpstan-flycheck-auto-set-executable - (not (and (boundp 'flycheck-phpstan-executable) - (symbol-value 'flycheck-phpstan-executable))) - (or (eq 'docker phpstan-executable) - (and (consp phpstan-executable) - (stringp (car phpstan-executable)) - (listp (cdr phpstan-executable))))) - (set (make-local-variable 'flycheck-phpstan-executable) - (if (eq 'docker phpstan-executable) - phpstan-docker-executable - (car phpstan-executable)))))) + (let ((enabled (not (null (or phpstan-working-dir (phpstan-get-config-file)))))) + (prog1 enabled + (when (and phpstan-flycheck-auto-set-executable + (not (and (boundp 'flycheck-phpstan-executable) + (symbol-value 'flycheck-phpstan-executable))) + (or (eq 'docker phpstan-executable) + (and (consp phpstan-executable) + (stringp (car phpstan-executable)) + (listp (cdr phpstan-executable))))) + (set (make-local-variable 'flycheck-phpstan-executable) + (if (eq 'docker phpstan-executable) + phpstan-docker-executable + (car phpstan-executable))))))) (defun phpstan-normalize-path (source-original &optional source) "Return normalized source file path to pass by `SOURCE-ORIGINAL' OR `SOURCE'. @@ -244,7 +245,7 @@ it returns the value of `SOURCE' as it is." (flycheck-save-buffer-to-temp #'flycheck-temp-file-inplace) (flycheck-save-buffer-to-temp #'flycheck-temp-file-system)))) :working-directory (lambda (_) (phpstan-get-working-dir)) - :enabled (lambda () (phpstan-get-config-file-and-set-flycheck-variable)) + :enabled (lambda () (phpstan-enabled-and-set-flycheck-variable)) :error-patterns ((error line-start (1+ (not (any ":"))) ":" line ":" (message) line-end)) :modes (php-mode) From 05083728cd67441626563f748f05f4d0159598d4 Mon Sep 17 00:00:00 2001 From: USAMI Kenta Date: Sat, 14 Apr 2018 01:07:39 +0900 Subject: [PATCH 25/25] phpstan-get-executable return always executable with arguments --- phpstan.el | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/phpstan.el b/phpstan.el index 2a73fdd..a08b41f 100644 --- a/phpstan.el +++ b/phpstan.el @@ -211,7 +211,7 @@ it returns the value of `SOURCE' as it is." (t phpstan-level))) (defun phpstan-get-executable () - "Return PHPStan excutable file." + "Return PHPStan excutable file and arguments." (cond ((eq 'docker phpstan-executable) (list "run" "--rm" "-v" @@ -229,18 +229,27 @@ it returns the value of `SOURCE' as it is." (let ((vendor-phpstan (expand-file-name "vendor/bin/phpstan" (php-project-get-root-dir)))) (cond - ((file-exists-p vendor-phpstan) vendor-phpstan) - ((executable-find "phpstan") (executable-find "phpstan")) + ((file-exists-p vendor-phpstan) (list vendor-phpstan)) + ((executable-find "phpstan") (list (executable-find "phpstan"))) (t (error "PHPStan executable not found"))))))) +(defun phpstan-get-command-args () + "Return command line argument for PHPStan." + (let ((executable (phpstan-get-executable)) + (args (list "analyze" "--errorFormat=raw" "--no-progress" "--no-interaction")) + (path (phpstan-normalize-path (phpstan-get-config-file))) + (level (phpstan-get-level))) + (when path + (setq args (append args (list "-c" path)))) + (when level + (setq args (append args (list "-l" level)))) + (append executable args))) + ;;;###autoload (when (featurep 'flycheck) (flycheck-define-checker phpstan "PHP static analyzer based on PHPStan." - :command ("php" (eval (phpstan-get-executable)) - "analyze" "--errorFormat=raw" "--no-progress" "--no-interaction" - "-c" (eval (phpstan-normalize-path (phpstan-get-config-file))) - "-l" (eval (phpstan-get-level)) + :command ("php" (eval (phpstan-get-command-args)) (eval (phpstan-normalize-path (flycheck-save-buffer-to-temp #'flycheck-temp-file-inplace) (flycheck-save-buffer-to-temp #'flycheck-temp-file-system))))