Skip to content

Commit b616f5f

Browse files
authored
Merge pull request #53 from emacs-php/feature/phpstan-insert-ignore
Add phpstan-insert-ignore command
2 parents 5dbbabd + 5549c10 commit b616f5f

File tree

3 files changed

+141
-37
lines changed

3 files changed

+141
-37
lines changed

README.org

+6-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
#+END_HTML
66
Emacs interface to [[https://github.com/phpstan/phpstan][PHPStan]], includes checker for [[http://www.flycheck.org/en/latest/][Flycheck]].
77
** Support version
8-
- Emacs 24+
8+
- Emacs 25+
99
- PHPStan latest/dev-master (NOT support 0.9 seriese)
1010
- PHP 7.1+ or Docker runtime
1111
** How to install
@@ -99,7 +99,12 @@ Add ~\PHPStan\dumpType(...);~ to your PHP code and analyze it to make PHPStan di
9999
By default, if you press ~C-u~ before invoking the command, ~\PHPStan\dumpPhpDocType()~ will be inserted.
100100

101101
This feature was added in *PHPStan 1.12.7* and will dump types compatible with the ~@param~ and ~@return~ PHPDoc tags.
102+
*** Command ~phpstan-insert-ignore~
103+
Insert a ~@phpstan-ignore~ tag to suppress any PHPStan errors on the current line.
102104

105+
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.
106+
107+
If there is no existing tag and ~C-u~ is pressed before the command, it will be inserted at the end of the line.
103108
** API
104109
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]].
105110

flycheck-phpstan.el

+31-35
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@
4545
(defvar flycheck-phpstan-executable)
4646
(defvar flycheck-phpstan--temp-buffer-name "*Flycheck PHPStan*")
4747

48-
4948
(defcustom flycheck-phpstan-ignore-metadata-list nil
5049
"Set of metadata items to ignore in PHPStan messages for Flycheck."
5150
:type '(set (const identifier)
@@ -76,45 +75,42 @@
7675

7776
(defun flycheck-phpstan-parse-output (output &optional _checker _buffer)
7877
"Parse PHPStan errors from OUTPUT."
79-
(with-current-buffer (flycheck-phpstan--temp-buffer)
80-
(erase-buffer)
81-
(insert output))
82-
(flycheck-phpstan-parse-json (flycheck-phpstan--temp-buffer)))
78+
(let* ((json-buffer (with-current-buffer (flycheck-phpstan--temp-buffer)
79+
(erase-buffer)
80+
(insert output)
81+
(current-buffer)))
82+
(data (phpstan--parse-json json-buffer))
83+
(errors (phpstan--plist-to-alist (plist-get data :files))))
84+
(unless phpstan-disable-buffer-errors
85+
(phpstan-update-ignorebale-errors-from-json-buffer errors))
86+
(flycheck-phpstan--build-errors errors)))
8387

8488
(defun flycheck-phpstan--temp-buffer ()
8589
"Return a temporary buffer for decode JSON."
8690
(get-buffer-create flycheck-phpstan--temp-buffer-name))
8791

88-
(defun flycheck-phpstan-parse-json (json-buffer)
89-
"Parse PHPStan errors from JSON-BUFFER."
90-
(let ((data (phpstan--parse-json json-buffer)))
91-
(cl-loop for (file . entry) in (flycheck-phpstan--plist-to-alist (plist-get data :files))
92-
append (cl-loop for messages in (plist-get entry :messages)
93-
for text = (let* ((msg (plist-get messages :message))
94-
(ignorable (plist-get messages :ignorable))
95-
(identifier (unless (memq 'identifier flycheck-phpstan-ignore-metadata-list)
96-
(plist-get messages :identifier)))
97-
(tip (unless (memq 'tip flycheck-phpstan-ignore-metadata-list)
98-
(plist-get messages :tip)))
99-
(lines (list (when (and identifier ignorable)
100-
(concat phpstan-identifier-prefix identifier))
101-
(when tip
102-
(concat phpstan-tip-message-prefix tip))))
103-
(lines (cl-remove-if #'null lines)))
104-
(if (null lines)
105-
msg
106-
(concat msg flycheck-phpstan-metadata-separator
107-
(mapconcat #'identity lines "\n"))))
108-
collect (flycheck-error-new-at (plist-get messages :line)
109-
nil 'error text
110-
:filename file)))))
111-
112-
(defun flycheck-phpstan--plist-to-alist (plist)
113-
"Convert PLIST to association list."
114-
(let (alist)
115-
(while plist
116-
(push (cons (substring-no-properties (symbol-name (pop plist)) 1) (pop plist)) alist))
117-
(nreverse alist)))
92+
(defun flycheck-phpstan--build-errors (errors)
93+
"Build Flycheck errors from PHPStan ERRORS."
94+
(cl-loop for (file . entry) in errors
95+
append (cl-loop for messages in (plist-get entry :messages)
96+
for text = (let* ((msg (plist-get messages :message))
97+
(ignorable (plist-get messages :ignorable))
98+
(identifier (unless (memq 'identifier flycheck-phpstan-ignore-metadata-list)
99+
(plist-get messages :identifier)))
100+
(tip (unless (memq 'tip flycheck-phpstan-ignore-metadata-list)
101+
(plist-get messages :tip)))
102+
(lines (list (when (and identifier ignorable)
103+
(concat phpstan-identifier-prefix identifier))
104+
(when tip
105+
(concat phpstan-tip-message-prefix tip))))
106+
(lines (cl-remove-if #'null lines)))
107+
(if (null lines)
108+
msg
109+
(concat msg flycheck-phpstan-metadata-separator
110+
(mapconcat #'identity lines "\n"))))
111+
collect (flycheck-error-new-at (plist-get messages :line)
112+
nil 'error text
113+
:filename file))))
118114

119115
(flycheck-define-checker phpstan
120116
"PHP static analyzer based on PHPStan."

phpstan.el

+104-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
;; Version: 0.7.2
88
;; Keywords: tools, php
99
;; Homepage: https://github.com/emacs-php/phpstan.el
10-
;; Package-Requires: ((emacs "24.3") (compat "29") (php-mode "1.22.3") (php-runtime "0.2"))
10+
;; Package-Requires: ((emacs "25.1") (compat "29") (php-mode "1.22.3") (php-runtime "0.2"))
1111
;; License: GPL-3.0-or-later
1212

1313
;; This program is free software; you can redistribute it and/or modify
@@ -56,6 +56,7 @@
5656
(require 'cl-lib)
5757
(require 'php-project)
5858
(require 'php-runtime)
59+
(require 'seq)
5960

6061
(eval-when-compile
6162
(require 'compat nil t)
@@ -154,8 +155,19 @@ have unexpected behaviors or performance implications."
154155
:type '(cons string string)
155156
:group 'phpstan)
156157

158+
(defcustom phpstan-disable-buffer-errors nil
159+
"If T, don't keep errors per buffer to save memory."
160+
:type 'boolean
161+
:group 'phpstan)
162+
163+
(defcustom phpstan-not-ignorable-identifiers '("ignore.parseError")
164+
"A list of identifiers that are prohibited from being added to the @phpstan-ignore tag."
165+
:type '(repeat string))
166+
157167
(defvar-local phpstan--use-xdebug-option nil)
158168

169+
(defvar-local phpstan--ignorable-errors '())
170+
159171
;;;###autoload
160172
(progn
161173
(defvar phpstan-working-dir nil
@@ -271,6 +283,18 @@ NIL
271283
(and (stringp (car v)) (listp (cdr v))))
272284
(or (eq 'docker v) (null v) (stringp v))))))
273285

286+
;; Utilities:
287+
(defun phpstan--plist-to-alist (plist)
288+
"Convert PLIST to association list."
289+
(let (alist)
290+
(while plist
291+
(push (cons (substring-no-properties (symbol-name (pop plist)) 1) (pop plist)) alist))
292+
(nreverse alist)))
293+
294+
(defsubst phpstan--current-line ()
295+
"Return the current buffer line at point. The first line is 1."
296+
(line-number-at-pos nil t))
297+
274298
;; Functions:
275299
(defun phpstan-get-working-dir ()
276300
"Return path to working directory of PHPStan."
@@ -489,6 +513,85 @@ it returns the value of `SOURCE' as it is."
489513
options
490514
(and args (cons "--" args)))))
491515

516+
(defun phpstan-update-ignorebale-errors-from-json-buffer (errors)
517+
"Update `phpstan--ignorable-errors' variable by ERRORS."
518+
(let ((identifiers
519+
(cl-loop for (_ . entry) in errors
520+
append (cl-loop for message in (plist-get entry :messages)
521+
if (plist-get message :ignorable)
522+
collect (cons (plist-get message :line)
523+
(plist-get message :identifier))))))
524+
(setq phpstan--ignorable-errors
525+
(mapcar (lambda (v) (cons (car v) (mapcar #'cdr (cdr v)))) (seq-group-by #'car identifiers)))))
526+
527+
(defconst phpstan--re-ignore-tag
528+
(eval-when-compile
529+
(rx (* (syntax whitespace)) "//" (* (syntax whitespace))
530+
(group "@phpstan-ignore")
531+
(* (syntax whitespace))
532+
(* (not "("))
533+
(group (? (+ (syntax whitespace) "("))))))
534+
535+
(cl-defun phpstan--check-existing-ignore-tag (&key in-previous)
536+
"Check existing @phpstan-ignore PHPDoc tag.
537+
If IN-PREVIOUS is NIL, check the previous line for the tag."
538+
(let ((new-position (if in-previous 'previous-line 'this-line))
539+
(line-end (line-end-position))
540+
new-point append)
541+
(save-excursion
542+
(save-match-data
543+
(if (re-search-forward phpstan--re-ignore-tag line-end t)
544+
(progn
545+
(setq new-point (match-beginning 2))
546+
(goto-char new-point)
547+
(when (eq (char-syntax (char-before)) ?\ )
548+
(left-char)
549+
(setq new-point (point)))
550+
(setq append (not (eq (match-end 1) (match-beginning 2))))
551+
(cl-values new-position new-point append))
552+
(if in-previous
553+
(cl-values nil nil nil)
554+
(previous-logical-line)
555+
(beginning-of-line)
556+
(phpstan--check-existing-ignore-tag :in-previous t)))))))
557+
558+
;;;###autoload
559+
(defun phpstan-insert-ignore (position)
560+
"Insert an @phpstan-ignore comment at the specified POSITION.
561+
562+
POSITION determines where to insert the comment and can be either `this-line' or
563+
`previous-line'.
564+
565+
- If POSITION is `this-line', the comment is inserted at the end of
566+
the current line.
567+
- If POSITION is `previous-line', the comment is inserted on a new line above
568+
the current line."
569+
(interactive
570+
(list (if current-prefix-arg 'this-line 'previous-line)))
571+
(save-restriction
572+
(widen)
573+
(let ((pos (point))
574+
(identifiers (cl-set-difference (alist-get (phpstan--current-line) phpstan--ignorable-errors) phpstan-not-ignorable-identifiers :test #'equal))
575+
(padding (if (eq position 'this-line) " " ""))
576+
new-position new-point delete-region)
577+
(cl-multiple-value-setq (new-position new-point append) (phpstan--check-existing-ignore-tag :in-previous nil))
578+
(when new-position
579+
(setq position new-position))
580+
(unless (and append (null identifiers))
581+
(if (not new-point)
582+
(cond
583+
((eq position 'this-line) (end-of-line))
584+
((eq position 'previous-line) (progn
585+
(previous-logical-line)
586+
(end-of-line)
587+
(newline-and-indent)))
588+
((error "Unexpected position: %s" position)))
589+
(setq padding "")
590+
(goto-char new-point))
591+
(insert (concat padding
592+
(if new-position (if append ", " " ") "// @phpstan-ignore ")
593+
(mapconcat #'identity identifiers ", ")))))))
594+
492595
;;;###autoload
493596
(defun phpstan-insert-dumptype (&optional expression prefix-num)
494597
"Insert PHPStan\\dumpType() expression-statement by EXPRESSION and PREFIX-NUM."

0 commit comments

Comments
 (0)