Skip to content

Commit 8a95d94

Browse files
authored
Merge pull request #4 from emacs-php/feature/variable-configure-file
Variable configure file
2 parents 3131d00 + 0508372 commit 8a95d94

File tree

11 files changed

+368
-9
lines changed

11 files changed

+368
-9
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
/.cask
2+
/composer.lock
3+
/vendor

Cask

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33

44
(package-file "phpstan.el")
55
(development
6-
(depends-on "flycheck"))
6+
(depends-on "flycheck")
7+
(depends-on "php-mode"))

README.org

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,98 @@
11
* phpstan.el
22
Emacs interface to [[https://github.com/phpstan/phpstan][PHPStan]], includes checker for [[http://www.flycheck.org/en/latest/][Flycheck]].
3+
** Support version
4+
- Emacs 24+
5+
- PHPStan latest/dev-master (NOT support 0.9 seriese)
6+
** How to install
7+
*** Install from MELPA
8+
/TBD/
9+
** How to use
10+
*** For Flycheck user
11+
/TBD/
12+
*** For Flymake user
13+
The function for flymake will be implemented soon. You do not have to depend on flycheck.
14+
*** Using Docker (phpstan/docker-image)
15+
Install [[https://www.docker.com/community-edition][Docker CE]] and [[https://github.com/phpstan/docker-image][phpstan/docker-image]](latest).
16+
17+
If you always use Docker for PHPStan, add the following into your ~.emacs~ file (~~/.emacs.d/init.el~)
18+
#+BEGIN_SRC emacs-lisp
19+
(setq-default phpstan-executable 'docker)
20+
#+END_SRC
21+
22+
Put the following into ~.dir-locals.el~ files on the root directory of project.
23+
#+BEGIN_SRC emacs-lisp
24+
((nil . ((php-project-root . git)
25+
(phpstan-executable . docker)
26+
(phpstan-working-dir . (root . "path/to/dir"))
27+
(phpstan-config-file . (root . "path/to/dir/phpstan-docker.neon"))
28+
(phpstan-level . 7))))
29+
#+END_SRC
30+
31+
*** Using composer (project specific)
32+
If your project Composer relies on phpstan, you do not need to set anything.
33+
#+BEGIN_SRC emacs-lisp
34+
((nil . ((php-project-root . git)
35+
(phpstan-executable . docker)
36+
(phpstan-working-dir . (root . "path/to/dir"))
37+
(phpstan-config-file . (root . "path/to/dir/phpstan-docker.neon"))
38+
(phpstan-level . 7))))
39+
#+END_SRC
40+
*** Using PHAR archive
41+
*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.
42+
43+
If you want to use the Phar archive you built yourself, set the Phar archive path to phpstan-executable.
44+
45+
** Settings
46+
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~).
47+
48+
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.
49+
50+
Please be aware that the root directory of the PHP project may *NOT* match either of PHPStan's ~%rootDir%~ and/or ~%currentWorkingDirectory%~.
51+
52+
Typically, you would set the following ~.dir-locals.el~.
53+
54+
#+BEGIN_SRC emacs-lisp
55+
((nil . ((php-project-root . auto)
56+
(phpstan-executable . docker)
57+
(phpstan-working-dir . (root . "path/to/dir/"))
58+
(phpstan-config-file . (root . "path/to/dir/phpstan-custom.neon"))
59+
(phpstan-level . max))))
60+
#+END_SRC
61+
62+
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~.
63+
64+
** API
65+
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]].
66+
67+
*** Local variable ~phpstan-working-dir~
68+
Path to working directory of PHPStan.
69+
70+
- STRING :: Absolute path to `phpstan' working directory.
71+
- ex) ~"/path/to/phpstan.phar"~
72+
- ~(root . STRING)~ :: Relative path to `phpstan' working directory from project root directory.
73+
- ex) ~(root . "path/to/dir")~
74+
- ~nil~ :: Use ~(php-project-get-root-dir)~ as working directory.
75+
76+
*** Local variable ~phpstan-config-file~
77+
Path to project specific configuration file of PHPStan.
78+
79+
- STRING :: Absolute path to ~phpstan~ configuration file.
80+
- ~(root . STRING)~ :: Relative path to ~phpstan~ configuration file from project root directory.
81+
- NIL :: Search ~phpstan.neon(.dist)~ in ~(phpstan-get-working-dir)~.
82+
83+
*** Local variable ~phpstan-level~
84+
Rule level of PHPStan analysis. Please see [[https://github.com/phpstan/phpstan/blob/master/README.md#rule-levels][README #Rule levels of PHPStan]].
85+
~0~ is the loosest and you can also use ~max~ as an alias for the highest level. Default level is ~0~.
86+
87+
*** Local variable ~phpstan-executable~
88+
- STRING :: Absolute path to `phpstan' executable file.
89+
- ex) ~"/path/to/phpstan.phar"~
90+
- SYMBOL ~docker~ :: Use Docker using phpstan/docker-image.
91+
- ~(root . STRING)~ :: Relative path to `phpstan' executable file from project root directory.
92+
- ex) ~(root . "script/phpstan")~
93+
- ~(STRING . (ARGUMENTS ...))~ :: Command name and arguments.
94+
- ex) ~("docker" "run" "--rm" "-v" "/path/to/project-dir/:/app" "your/docker-image")~
95+
- ~nil~ :: Auto detect ~phpstan~ executable file by composer dependencies of the project or executable command in ~PATH~ environment variable.
96+
97+
*** Custom variable ~phpstan-flycheck-auto-set-executable~
98+
Set flycheck phpstan-executable automatically when non-NIL.

composer.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"name": "emacs-php/phpstan.el",
3+
"description": "Emacs interface to PHPStan",
4+
"license": "GPL-3.0-or-later",
5+
"require-dev": {
6+
"phpstan/phpstan": "dev-master"
7+
}
8+
}

phpstan.el

Lines changed: 222 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,233 @@
2828
;; https://github.com/phpstan/phpstan
2929

3030
;;; Code:
31+
(require 'php-project)
3132
(require 'flycheck nil)
3233

34+
35+
;; Variables:
36+
37+
(defgroup phpstan nil
38+
"Interaface to PHPStan"
39+
:tag "PHPStan"
40+
:prefix "phpstan-"
41+
:group 'tools
42+
:group 'php
43+
:link '(url-link :tag "PHPStan" "https://github.com/phpstan/phpstan")
44+
:link '(url-link :tag "phpstan.el" "https://github.com/emacs-php/phpstan.el"))
45+
46+
(defcustom phpstan-flycheck-auto-set-executable t
47+
"Set flycheck phpstan-executable automatically."
48+
:type 'boolean
49+
:group 'phpstan)
50+
51+
;;;###autoload
52+
(progn
53+
(defvar phpstan-working-dir nil
54+
"Path to working directory of PHPStan.
55+
56+
*NOTICE*: This is different from the project root.
57+
58+
STRING
59+
Absolute path to `phpstan' working directory.
60+
61+
`(root . STRING)'
62+
Relative path to `phpstan' working directory from project root directory.
63+
64+
NIL
65+
Use (php-project-get-root-dir) as working directory.")
66+
(make-variable-buffer-local 'phpstan-working-dir)
67+
(put 'phpstan-working-dir 'safe-local-variable
68+
#'(lambda (v) (if (consp v)
69+
(and (eq 'root (car v)) (stringp (cdr v)))
70+
(null v) (stringp v)))))
71+
72+
;;;###autoload
73+
(progn
74+
(defvar phpstan-config-file nil
75+
"Path to project specific configuration file of PHPStan.
76+
77+
STRING
78+
Absolute path to `phpstan' configuration file.
79+
80+
`(root . STRING)'
81+
Relative path to `phpstan' configuration file from project root directory.
82+
83+
NIL
84+
Search phpstan.neon(.dist) in (phpstan-get-working-dir).")
85+
(make-variable-buffer-local 'phpstan-config-file)
86+
(put 'phpstan-config-file 'safe-local-variable
87+
#'(lambda (v) (if (consp v)
88+
(and (eq 'root (car v)) (stringp (cdr v)))
89+
(null v) (stringp v)))))
90+
91+
;;;###autoload
92+
(progn
93+
(defvar phpstan-level "0"
94+
"Rule level of PHPStan.
95+
96+
INTEGER or STRING
97+
Number of PHPStan rule level.
98+
99+
max
100+
The highest of PHPStan rule level.")
101+
(make-variable-buffer-local 'phpstan-level)
102+
(put 'phpstan-level 'safe-local-variable
103+
#'(lambda (v) (or (null v)
104+
(integerp v)
105+
(eq 'max v)
106+
(and (stringp v)
107+
(string= "max" v)
108+
(string-match-p "\\`[0-9]\\'" v))))))
109+
110+
;;;###autoload
111+
(progn
112+
(defvar phpstan-replace-path-prefix)
113+
(make-variable-buffer-local 'phpstan-replace-path-prefix)
114+
(put 'phpstan-replace-path-prefix 'safe-local-variable
115+
#'(lambda (v) (or (null v) (stringp v)))))
116+
117+
(defconst phpstan-docker-executable "docker")
118+
119+
;; Usually it is defined dynamically by flycheck
120+
(defvar flycheck-phpstan-executable)
121+
122+
;;;###autoload
123+
(progn
124+
(defvar phpstan-executable nil
125+
"PHPStan excutable file.
126+
127+
STRING
128+
Absolute path to `phpstan' executable file.
129+
130+
`docker'
131+
Use Docker using phpstan/docker-image.
132+
133+
`(root . STRING)'
134+
Relative path to `phpstan' executable file.
135+
136+
`(STRING . (ARGUMENTS ...))'
137+
Command name and arguments.
138+
139+
NIL
140+
Auto detect `phpstan' executable file.")
141+
(make-variable-buffer-local 'phpstan-executable)
142+
(put 'phpstan-executable 'safe-local-variable
143+
#'(lambda (v) (if (consp v)
144+
(or (and (eq 'root (car v)) (stringp (cdr v)))
145+
(and (stringp (car v)) (listp (cdr v))))
146+
(or (eq 'docker v) (null v) (stringp v))))))
147+
148+
;; Functions:
149+
(defun phpstan-get-working-dir ()
150+
"Return path to working directory of PHPStan."
151+
(if (and phpstan-working-dir (consp phpstan-working-dir) (eq 'root (car phpstan-working-dir)))
152+
(expand-file-name (cdr phpstan-working-dir) (php-project-get-root-dir))
153+
(php-project-get-root-dir)))
154+
155+
(defun phpstan-get-config-file ()
156+
"Return path to phpstan configure file or `NIL'."
157+
(if phpstan-config-file
158+
(if (and (consp phpstan-config-file)
159+
(eq 'root (car phpstan-config-file)))
160+
;; Use (php-project-get-root-dir), not phpstan-working-dir.
161+
(expand-file-name (cdr phpstan-config-file) (php-project-get-root-dir))
162+
phpstan-config-file)
163+
(let ((working-directory (phpstan-get-working-dir)))
164+
(cl-loop for name in '("phpstan.neon" "phpstan.neon.dist")
165+
for dir = (locate-dominating-file working-directory name)
166+
if dir
167+
return (expand-file-name name dir)))))
168+
169+
(defun phpstan-enabled-and-set-flycheck-variable ()
170+
"Return path to phpstan configure file, and set buffer execute in side effect."
171+
(let ((enabled (not (null (or phpstan-working-dir (phpstan-get-config-file))))))
172+
(prog1 enabled
173+
(when (and phpstan-flycheck-auto-set-executable
174+
(not (and (boundp 'flycheck-phpstan-executable)
175+
(symbol-value 'flycheck-phpstan-executable)))
176+
(or (eq 'docker phpstan-executable)
177+
(and (consp phpstan-executable)
178+
(stringp (car phpstan-executable))
179+
(listp (cdr phpstan-executable)))))
180+
(set (make-local-variable 'flycheck-phpstan-executable)
181+
(if (eq 'docker phpstan-executable)
182+
phpstan-docker-executable
183+
(car phpstan-executable)))))))
184+
185+
(defun phpstan-normalize-path (source-original &optional source)
186+
"Return normalized source file path to pass by `SOURCE-ORIGINAL' OR `SOURCE'.
187+
188+
If neither `phpstan-replace-path-prefix' nor executable docker is set,
189+
it returns the value of `SOURCE' as it is."
190+
(let ((root-directory (expand-file-name (php-project-get-root-dir)))
191+
(prefix
192+
(cond
193+
((not (null phpstan-replace-path-prefix)) phpstan-replace-path-prefix)
194+
((eq 'docker phpstan-executable) "/app")
195+
((and (consp phpstan-executable)
196+
(string= "docker" (car phpstan-executable))) "/app"))))
197+
(if prefix
198+
(expand-file-name
199+
(replace-regexp-in-string (concat "\\`" (regexp-quote root-directory))
200+
""
201+
source-original t t)
202+
prefix)
203+
(or source source-original))))
204+
205+
(defun phpstan-get-level ()
206+
"Return path to phpstan configure file or `NIL'."
207+
(cond
208+
((null phpstan-level) "0")
209+
((integerp phpstan-level) (int-to-string phpstan-level))
210+
((symbolp phpstan-level) (symbol-name phpstan-level))
211+
(t phpstan-level)))
212+
213+
(defun phpstan-get-executable ()
214+
"Return PHPStan excutable file and arguments."
215+
(cond
216+
((eq 'docker phpstan-executable)
217+
(list "run" "--rm" "-v"
218+
(concat (expand-file-name (php-project-get-root-dir)) ":/app")
219+
"phpstan/phpstan"))
220+
((and (consp phpstan-executable)
221+
(eq 'root (car phpstan-executable)))
222+
(expand-file-name (cdr phpstan-executable) (php-project-get-root-dir)))
223+
((and phpstan-flycheck-auto-set-executable
224+
(listp phpstan-executable)
225+
(stringp (car phpstan-executable))
226+
(listp (cdr phpstan-executable)))
227+
(cdr phpstan-executable))
228+
((null phpstan-executable)
229+
(let ((vendor-phpstan (expand-file-name "vendor/bin/phpstan"
230+
(php-project-get-root-dir))))
231+
(cond
232+
((file-exists-p vendor-phpstan) (list vendor-phpstan))
233+
((executable-find "phpstan") (list (executable-find "phpstan")))
234+
(t (error "PHPStan executable not found")))))))
235+
236+
(defun phpstan-get-command-args ()
237+
"Return command line argument for PHPStan."
238+
(let ((executable (phpstan-get-executable))
239+
(args (list "analyze" "--errorFormat=raw" "--no-progress" "--no-interaction"))
240+
(path (phpstan-normalize-path (phpstan-get-config-file)))
241+
(level (phpstan-get-level)))
242+
(when path
243+
(setq args (append args (list "-c" path))))
244+
(when level
245+
(setq args (append args (list "-l" level))))
246+
(append executable args)))
247+
33248
;;;###autoload
34249
(when (featurep 'flycheck)
35-
(flycheck-define-checker phpstan-checker
250+
(flycheck-define-checker phpstan
36251
"PHP static analyzer based on PHPStan."
37-
:command ("phpstan"
38-
"analyze"
39-
"--no-progress"
40-
"--errorFormat=raw"
41-
source)
42-
:working-directory (lambda (_) (php-project-get-root-dir))
43-
:enabled (lambda () (locate-dominating-file "phpstan.neon" default-directory))
252+
:command ("php" (eval (phpstan-get-command-args))
253+
(eval (phpstan-normalize-path
254+
(flycheck-save-buffer-to-temp #'flycheck-temp-file-inplace)
255+
(flycheck-save-buffer-to-temp #'flycheck-temp-file-system))))
256+
:working-directory (lambda (_) (phpstan-get-working-dir))
257+
:enabled (lambda () (phpstan-enabled-and-set-flycheck-variable))
44258
:error-patterns
45259
((error line-start (1+ (not (any ":"))) ":" line ":" (message) line-end))
46260
:modes (php-mode)

phpstan.neon.dist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# dummy

test-docker.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
foo();
4+
f();
5+
foo();
6+
7+
echo Fooo;
8+
echo FOO;
9+
10+
// Local Variables:
11+
// phpstan-executable: docker
12+
// phpstan-working-dir: (root . "tests/")
13+
// phpstan-config-file: (root . "tests/phpstan-docker.neon")
14+
// phpstan-level: 7
15+
// End:

test.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
foo();
4+
f();
5+
foo();
6+
7+
echo Fooo;
8+
echo FOO;
9+
10+
// Local Variables:
11+
// phpstan-config-file: (root . "tests/phpstan.neon")
12+
// phpstan-level: 7
13+
// End:

tests/bootstrap.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php
2+
3+
const FOO = 'Foo';
4+
5+
function foo() {}

0 commit comments

Comments
 (0)