-
Notifications
You must be signed in to change notification settings - Fork 15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Variable configure file #4
Changes from all commits
7bf0f23
ba7dfc8
526f0b7
968b8fe
c48251b
a5f1711
45ba779
bc56031
0825ac8
6fa077b
6840f6c
a198897
63fd9eb
4555592
d559ee1
05c9f76
70cb256
23d1860
dffcad7
3748533
4d53e60
56355b0
21cf970
8d63798
0508372
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,3 @@ | ||
/.cask | ||
/composer.lock | ||
/vendor |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,19 +28,233 @@ | |
;; https://github.com/phpstan/phpstan | ||
|
||
;;; Code: | ||
(require 'php-project) | ||
(require 'flycheck nil) | ||
|
||
|
||
;; 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-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) | ||
(and (eq 'root (car v)) (stringp (cdr v))) | ||
(null v) (stringp v))))) | ||
|
||
;;;###autoload | ||
(progn | ||
(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) | ||
(and (eq 'root (car v)) (stringp (cdr v))) | ||
(null v) (stringp v))))) | ||
|
||
;;;###autoload | ||
(progn | ||
(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) | ||
(integerp v) | ||
(eq 'max v) | ||
(and (stringp v) | ||
(string= "max" 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))))) | ||
|
||
(defconst phpstan-docker-executable "docker") | ||
|
||
;; Usually it is defined dynamically by flycheck | ||
(defvar flycheck-phpstan-executable) | ||
|
||
;;;###autoload | ||
(progn | ||
(defvar phpstan-executable nil | ||
"PHPStan excutable file. | ||
|
||
STRING | ||
Absolute path to `phpstan' executable file. | ||
|
||
`docker' | ||
Use Docker using phpstan/docker-image. | ||
|
||
`(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) | ||
(or (and (eq 'root (car v)) (stringp (cdr v))) | ||
(and (stringp (car v)) (listp (cdr v)))) | ||
(or (eq 'docker v) (null v) (stringp v)))))) | ||
|
||
;; Functions: | ||
(defun phpstan-get-working-dir () | ||
"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))) | ||
|
||
(defun phpstan-get-config-file () | ||
"Return path to phpstan configure file or `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) | ||
(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-enabled-and-set-flycheck-variable () | ||
"Return path to phpstan configure file, and set buffer execute in side effect." | ||
(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'. | ||
|
||
If neither `phpstan-replace-path-prefix' nor executable docker is set, | ||
it returns the value of `SOURCE' as it is." | ||
(let ((root-directory (expand-file-name (php-project-get-root-dir))) | ||
(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 | ||
(expand-file-name | ||
(replace-regexp-in-string (concat "\\`" (regexp-quote root-directory)) | ||
"" | ||
source-original t t) | ||
prefix) | ||
(or source source-original)))) | ||
|
||
(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)) | ||
((symbolp phpstan-level) (symbol-name phpstan-level)) | ||
(t phpstan-level))) | ||
|
||
(defun phpstan-get-executable () | ||
"Return PHPStan excutable file and arguments." | ||
(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))) | ||
((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) (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-checker | ||
(flycheck-define-checker phpstan | ||
"PHP static analyzer based on PHPStan." | ||
:command ("phpstan" | ||
"analyze" | ||
"--no-progress" | ||
"--errorFormat=raw" | ||
source) | ||
:working-directory (lambda (_) (php-project-get-root-dir)) | ||
:enabled (lambda () (locate-dominating-file "phpstan.neon" default-directory)) | ||
:command ("php" (eval (phpstan-get-command-args)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How does this work when this starts There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Fuco1 This is an insane hack.
It was possible to explicitly write |
||
(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 (_) (phpstan-get-working-dir)) | ||
:enabled (lambda () (phpstan-enabled-and-set-flycheck-variable)) | ||
:error-patterns | ||
((error line-start (1+ (not (any ":"))) ":" line ":" (message) line-end)) | ||
:modes (php-mode) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# dummy |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<?php | ||
|
||
foo(); | ||
f(); | ||
foo(); | ||
|
||
echo Fooo; | ||
echo FOO; | ||
|
||
// Local Variables: | ||
// phpstan-executable: docker | ||
// phpstan-working-dir: (root . "tests/") | ||
// phpstan-config-file: (root . "tests/phpstan-docker.neon") | ||
// phpstan-level: 7 | ||
// End: |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<?php | ||
|
||
foo(); | ||
f(); | ||
foo(); | ||
|
||
echo Fooo; | ||
echo FOO; | ||
|
||
// Local Variables: | ||
// phpstan-config-file: (root . "tests/phpstan.neon") | ||
// phpstan-level: 7 | ||
// End: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test is not automated, but we can check flycheck's behavior manually. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
<?php | ||
|
||
const FOO = 'Foo'; | ||
|
||
function foo() {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not clear what the structure of
phpstan-config-file
should be. I think user options should at least be defined as defcustoms with a type annotation.