Skip to content

Commit

Permalink
Merge pull request #718 from emacs-php/feature/flymake-diagnostic-fun…
Browse files Browse the repository at this point in the history
…ctions

New feature: php-flymake
  • Loading branch information
zonuexe authored Nov 5, 2022
2 parents f5c4016 + a8a62f3 commit 56e5b67
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 15 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@ All notable changes of the PHP Mode 1.19.1 release series are documented in this

* **New feature: `php-complete`**
* Add `php-complete-complete-function` to autocomplete function names ([#708])
* **New feature: `php-flymake`**
* Add `php-flymake` as a flymake backend compatible with Emacs 26 and above ([#718])
* Supports PHPDoc tags and types for static analysis tools ([#710], [#715], [#716], [#717], thanks to [@takeokunn])
* Please refer to the article below
* PHPStan: [PHPDoc Types](https://phpstan.org/writing-php-code/phpdoc-types)
* PHPStan: [PHPDocs Basics](https://phpstan.org/writing-php-code/phpdocs-basics)
* Psalm: [Atomic Type Reference](https://psalm.dev/docs/annotating_code/type_syntax/atomic_types/)
* Psalm: [Supported Annotations](https://psalm.dev/docs/annotating_code/supported_annotations/)
* Psalm: [Template Annotations](https://psalm.dev/docs/annotating_code/templated_annotations/)
* Add `php-mode-replace-flymake-diag-function` custom variable and default activated it ([#718])

### Changed

Expand All @@ -24,11 +27,13 @@ All notable changes of the PHP Mode 1.19.1 release series are documented in this
* Make `php-mode-version` function include a Git tag and revision ([#713])
* Like `"1.23.4-56-xxxxxx"` for example.
* Change `php-phpdoc-type-keywords` to `php-phpdoc-type-names` to avoid confusion ([#717])
* Make `php-flymake-php-init` append to `flymake-allowed-file-name-masks` only in legacy Flymake ([#718])

### Deprecated

* Make obsolete `php-mode-version-number` contstant variable ([#712])
* `(php-mode-version :as-number t)` is provided for use cases comparing as versions, but generally SHOULD NOT be dependent on the PHP Mode version.
* Make obsolete `php-mode-disable-c-mode-hook` customize variable ([#718])

### Fixed

Expand All @@ -44,6 +49,7 @@ All notable changes of the PHP Mode 1.19.1 release series are documented in this
[#715]: https://github.com/emacs-php/php-mode/pull/715
[#716]: https://github.com/emacs-php/php-mode/pull/716
[#717]: https://github.com/emacs-php/php-mode/pull/717
[#718]: https://github.com/emacs-php/php-mode/pull/718

## [1.24.1] - 2022-10-08

Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
EMACS ?= emacs
CASK ?= cask
ELS = lisp/php.el lisp/php-align.el lisp/php-complete.el lisp/php-defs.el lisp/php-face.el lisp/php-project.el lisp/php-local-manual.el lisp/php-mode.el lisp/php-mode-debug.el
ELS = lisp/php.el lisp/php-align.el lisp/php-complete.el lisp/php-defs.el lisp/php-face.el lisp/php-flymake.el lisp/php-project.el lisp/php-local-manual.el lisp/php-mode.el lisp/php-mode-debug.el
AUTOLOADS = php-mode-autoloads.el
ELCS = $(ELS:.el=.elc)

Expand Down
140 changes: 140 additions & 0 deletions lisp/php-flymake.el
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
;;; php-flymake.el --- Flymake backend for PHP -*- lexical-binding: t; -*-

;; Copyright (C) 2022 Friends of Emacs-PHP development

;; Author: USAMI Kenta <tadsan@zonu.me>
;; Created: 5 Mar 2022
;; Version: 1.24.1
;; Keywords: tools, languages, php

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;; Flymake backend for PHP.

;;; Code:
(require 'flymake)
(require 'cl-lib)
(eval-when-compile
(require 'pcase)
(require 'rx))

(defgroup php-flymake nil
"Flymake backend for PHP."
:tag "PHP Flymake"
:group 'php)

(defcustom php-flymake-executable-command-args nil
"List of command and arguments for `php -l'."
:group 'php-flymake
:type '(repeat string)
:safe (lambda (v) (and (listp v) (cl-every v #'stringp))))

(defconst php-flymake--diaggnostics-pattern
(eval-when-compile
(rx bol (? "PHP ")
(group (or "Parse" "Fatal")) ;; 1: type, not used
" error:" (+ (syntax whitespace))
(group (+? any)) ;; 2: msg
" in " (group (+? any)) ;; 3: file, not used
" on line " (group (+ num)) ;; 4: line
eol)))

(defvar-local php-flymake--proc nil)

;;;###autoload
(defun php-flymake (report-fn &rest args)
"Flymake backend for PHP syntax check.
See `flymake-diagnostic-functions' about REPORT-FN and ARGS parameters."
(setq-local flymake-proc-allowed-file-name-masks nil)
(when (process-live-p php-flymake--proc)
(if (plist-get args :interactive)
(user-error "There's already a Flymake process running in this buffer")
(kill-process php-flymake--proc)))
(save-restriction
(widen)
(cl-multiple-value-bind (use-stdin skip) (php-flymake--buffer-status)
(unless skip
(setq php-flymake--proc (php-flymake--make-process report-fn buffer-file-name (current-buffer) use-stdin))
(when use-stdin
(process-send-region php-flymake--proc (point-min) (point-max)))
(process-send-eof php-flymake--proc)))))

(defun php-flymake--buffer-status ()
"Return buffer status about \"use STDIN\" and \"Skip diagnostic\"."
(let* ((use-stdin (or (null buffer-file-name)
(buffer-modified-p (current-buffer))
(file-remote-p buffer-file-name)))
(skip (and (not use-stdin)
(save-excursion (goto-char (point-min)) (looking-at-p "#!")))))
(cl-values use-stdin skip)))

(defun php-flymake--diagnostics (locus source)
"Parse output of `php -l' command in SOURCE buffer. LOCUS means filename."
(unless (eval-when-compile (and (fboundp 'flymake-make-diagnostic)
(fboundp 'flymake-diag-region)))
(error "`php-flymake' requires Emacs 26.1 or later"))
(cl-loop
while (search-forward-regexp php-flymake--diaggnostics-pattern nil t)
for msg = (match-string 2)
for line = (string-to-number (match-string 4))
for diag = (or (pcase-let ((`(,beg . ,end)
(flymake-diag-region source line)))
(flymake-make-diagnostic source beg end :error msg))
(flymake-make-diagnostic locus (cons line nil) nil :error msg))
return (list diag)))

(defun php-flymake--build-command-line (file)
"Return the command line for `php -l' FILE."
(let* ((command-args (or php-flymake-executable-command-args
(list (or (bound-and-true-p php-executable) "php"))))
(cmd (car-safe command-args))
(default-directory (expand-file-name "~")))
(unless (or (file-executable-p cmd)
(executable-find cmd))
(user-error "`%s' is not valid command" cmd))
(nconc command-args
(list "-d" "display_errors=0")
(when file (list "-f" file))
(list "-l"))))

(defun php-flymake--make-process (report-fn locus source use-stdin)
"Make PHP process for syntax check SOURCE buffer.
See `flymake-diagnostic-functions' about REPORT-FN parameter.
See `flymake-make-diagnostic' about LOCUS parameter."
(make-process
:name "php-flymake"
:buffer (generate-new-buffer "*flymake-php-flymake*")
:command (php-flymake--build-command-line (unless use-stdin locus))
:noquery t :connection-type 'pipe
:sentinel
(lambda (p _ev)
(unwind-protect
(when (and (eq 'exit (process-status p))
(with-current-buffer source (eq p php-flymake--proc)))
(with-current-buffer (process-buffer p)
(goto-char (point-min))
(funcall report-fn
(if (zerop (process-exit-status p))
nil
(php-flymake--diagnostics locus source)))))
(unless (process-live-p p)
;; (display-buffer (process-buffer p)) ; uncomment to debug
(kill-buffer (process-buffer p)))))))

(provide 'php-flymake)
;;; php-flymake.el ends here
43 changes: 34 additions & 9 deletions lisp/php-mode.el
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@
(eval-when-compile
(require 'rx)
(require 'cl-lib)
(require 'flymake)
(require 'php-flymake)
(require 'regexp-opt)
(defvar add-log-current-defun-header-regexp)
(defvar add-log-current-defun-function)
Expand Down Expand Up @@ -179,6 +181,15 @@ Turning this on will open it whenever `php-mode' is loaded."
:tag "PHP Mode Page Delimiter"
:type 'regexp)

(defcustom php-mode-replace-flymake-diag-function
(eval-when-compile (when (boundp 'flymake-diagnostic-functions)
#'php-flymake))
"Flymake function to replace, if NIL do not replace."
:group 'php-mode
:tag "PHP Mode Replace Flymake Diag Function"
:type '(choice 'function
(const :tag "Disable to replace" nil)))

(define-obsolete-variable-alias 'php-do-not-use-semantic-imenu 'php-mode-do-not-use-semantic-imenu "1.20.0")
(defcustom php-mode-do-not-use-semantic-imenu t
"Customize `imenu-create-index-function' for `php-mode'.
Expand Down Expand Up @@ -301,6 +312,7 @@ In that case set to `NIL'."
:group 'php-mode
:tag "PHP Mode Disable C Mode Hook"
:type 'boolean)
(make-obsolete-variable 'php-mode-disable-c-mode-hook nil "1.24.2")

(defcustom php-mode-enable-project-local-variable t
"When set to `T', apply project local variable to buffer local variable."
Expand Down Expand Up @@ -1147,6 +1159,14 @@ After setting the stylevars run hooks according to STYLENAME
(php-project-apply-local-variables)
(remove-hook 'hack-local-variables-hook #'php-mode-set-local-variable-delay))

(defun php-mode-neutralize-cc-mode-effect ()
"Reset PHP-irrelevant variables set by Cc Mode initialization."
(setq-local c-mode-hook nil)
(setq-local java-mode-hook nil)
(when (eval-when-compile (boundp 'flymake-diagnostic-functions))
(remove-hook 'flymake-diagnostic-functions 'flymake-cc t))
t)

(defvar php-mode-syntax-table
(let ((table (make-syntax-table)))
(c-populate-syntax-table table)
Expand All @@ -1173,9 +1193,12 @@ After setting the stylevars run hooks according to STYLENAME
"Please run `M-x package-reinstall php-mode' command."
"Please byte recompile PHP Mode files.")))

(when php-mode-disable-c-mode-hook
(setq-local c-mode-hook nil)
(setq-local java-mode-hook nil))
(if php-mode-disable-c-mode-hook
(php-mode-neutralize-cc-mode-effect)
(display-warning 'php-mode
"`php-mode-disable-c-mode-hook' will be removed. Do not depends on this variable."
:warning))

(c-initialize-cc-mode t)
(c-init-language-vars php-mode)
(c-common-init 'php-mode)
Expand Down Expand Up @@ -1249,6 +1272,10 @@ After setting the stylevars run hooks according to STYLENAME
(setq-local add-log-current-defun-function nil)
(setq-local add-log-current-defun-header-regexp php-beginning-of-defun-regexp)

(when (and (eval-when-compile (boundp 'flymake-diagnostic-functions))
php-mode-replace-flymake-diag-function)
(add-hook 'flymake-diagnostic-functions php-mode-replace-flymake-diag-function nil t))

(when (fboundp 'c-looking-at-or-maybe-in-bracelist)
(advice-add #'c-looking-at-or-maybe-in-bracelist
:override 'php-c-looking-at-or-maybe-in-bracelist '(local)))
Expand Down Expand Up @@ -1515,12 +1542,10 @@ for \\[find-tag] (which see)."
(defvar php-font-lock-keywords php-font-lock-keywords-3
"Default expressions to highlight in PHP Mode.")

(add-to-list
(eval-when-compile
(if (boundp 'flymake-proc-allowed-file-name-masks)
'flymake-proc-allowed-file-name-masks
'flymake-allowed-file-name-masks))
'("\\.php[345s]?\\'" php-flymake-php-init))
(eval-when-compile
(unless (boundp 'flymake-proc-allowed-file-name-masks)
(add-to-list 'flymake-allowed-file-name-masks
'("\\.php[345s]?\\'" php-flymake-php-init))))


(defun php-send-region (start end)
Expand Down
8 changes: 3 additions & 5 deletions lisp/php.el
Original file line number Diff line number Diff line change
Expand Up @@ -550,11 +550,9 @@ The order is reversed by calling as follows:
This is an alternative function of `flymake-php-init'.
Look at the `php-executable' variable instead of the constant \"php\" command."
(let* ((init (funcall (eval-when-compile
(if (fboundp 'flymake-proc-php-init)
'flymake-proc-php-init
'flymake-php-init)))))
(list php-executable (cdr init))))
(let ((init (with-no-warnings (flymake-php-init))))
(setf (car init) php-executable)
init))

(defconst php-re-detect-html-tag-aggressive
(eval-when-compile
Expand Down

0 comments on commit 56e5b67

Please sign in to comment.