branch: elpa/php-mode commit dc67eaecf15d3dc1be16d30d635aa9f551a231aa Author: USAMI Kenta <tad...@zonu.me> Commit: USAMI Kenta <tad...@zonu.me>
Add php-flymake.el --- CHANGELOG.md | 2 + Makefile | 2 +- lisp/php-flymake.el | 140 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 143 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d860e573fe..6e19a36d1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ 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) diff --git a/Makefile b/Makefile index 97922d2a50..45dc5c44bf 100644 --- a/Makefile +++ b/Makefile @@ -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) diff --git a/lisp/php-flymake.el b/lisp/php-flymake.el new file mode 100644 index 0000000000..3c71e1a7bb --- /dev/null +++ b/lisp/php-flymake.el @@ -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 <tad...@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