|
| 1 | +;;; php-flymake.el --- Flymake backend for PHP -*- lexical-binding: t; -*- |
| 2 | + |
| 3 | +;; Copyright (C) 2022 Friends of Emacs-PHP development |
| 4 | + |
| 5 | +;; Author: USAMI Kenta <[email protected]> |
| 6 | +;; Created: 5 Mar 2022 |
| 7 | +;; Version: 1.24.1 |
| 8 | +;; Keywords: tools, languages, php |
| 9 | + |
| 10 | +;; This program is free software; you can redistribute it and/or modify |
| 11 | +;; it under the terms of the GNU General Public License as published by |
| 12 | +;; the Free Software Foundation, either version 3 of the License, or |
| 13 | +;; (at your option) any later version. |
| 14 | + |
| 15 | +;; This program is distributed in the hope that it will be useful, |
| 16 | +;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 17 | +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 18 | +;; GNU General Public License for more details. |
| 19 | + |
| 20 | +;; You should have received a copy of the GNU General Public License |
| 21 | +;; along with this program. If not, see <https://www.gnu.org/licenses/>. |
| 22 | + |
| 23 | +;;; Commentary: |
| 24 | + |
| 25 | +;; Flymake backend for PHP. |
| 26 | + |
| 27 | +;;; Code: |
| 28 | +(require 'flymake) |
| 29 | +(require 'cl-lib) |
| 30 | +(eval-when-compile |
| 31 | + (require 'pcase) |
| 32 | + (require 'rx)) |
| 33 | + |
| 34 | +(defgroup php-flymake nil |
| 35 | + "Flymake backend for PHP." |
| 36 | + :tag "PHP Flymake" |
| 37 | + :group 'php) |
| 38 | + |
| 39 | +(defcustom php-flymake-executable-command-args nil |
| 40 | + "List of command and arguments for `php -l'." |
| 41 | + :group 'php-flymake |
| 42 | + :type '(repeat string) |
| 43 | + :safe (lambda (v) (and (listp v) (cl-every v #'stringp)))) |
| 44 | + |
| 45 | +(defconst php-flymake--diaggnostics-pattern |
| 46 | + (eval-when-compile |
| 47 | + (rx bol (? "PHP ") |
| 48 | + (group (or "Parse" "Fatal")) ;; 1: type, not used |
| 49 | + " error:" (+ (syntax whitespace)) |
| 50 | + (group (+? any)) ;; 2: msg |
| 51 | + " in " (group (+? any)) ;; 3: file, not used |
| 52 | + " on line " (group (+ num)) ;; 4: line |
| 53 | + eol))) |
| 54 | + |
| 55 | +(defvar-local php-flymake--proc nil) |
| 56 | + |
| 57 | +;;;###autoload |
| 58 | +(defun php-flymake (report-fn &rest args) |
| 59 | + "Flymake backend for PHP syntax check. |
| 60 | +
|
| 61 | +See `flymake-diagnostic-functions' about REPORT-FN and ARGS parameters." |
| 62 | + (setq-local flymake-proc-allowed-file-name-masks nil) |
| 63 | + (when (process-live-p php-flymake--proc) |
| 64 | + (if (plist-get args :interactive) |
| 65 | + (user-error "There's already a Flymake process running in this buffer") |
| 66 | + (kill-process php-flymake--proc))) |
| 67 | + (save-restriction |
| 68 | + (widen) |
| 69 | + (cl-multiple-value-bind (use-stdin skip) (php-flymake--buffer-status) |
| 70 | + (unless skip |
| 71 | + (setq php-flymake--proc (php-flymake--make-process report-fn buffer-file-name (current-buffer) use-stdin)) |
| 72 | + (when use-stdin |
| 73 | + (process-send-region php-flymake--proc (point-min) (point-max))) |
| 74 | + (process-send-eof php-flymake--proc))))) |
| 75 | + |
| 76 | +(defun php-flymake--buffer-status () |
| 77 | + "Return buffer status about \"use STDIN\" and \"Skip diagnostic\"." |
| 78 | + (let* ((use-stdin (or (null buffer-file-name) |
| 79 | + (buffer-modified-p (current-buffer)) |
| 80 | + (file-remote-p buffer-file-name))) |
| 81 | + (skip (and (not use-stdin) |
| 82 | + (save-excursion (goto-char (point-min)) (looking-at-p "#!"))))) |
| 83 | + (cl-values use-stdin skip))) |
| 84 | + |
| 85 | +(defun php-flymake--diagnostics (locus source) |
| 86 | + "Parse output of `php -l' command in SOURCE buffer. LOCUS means filename." |
| 87 | + (unless (eval-when-compile (and (fboundp 'flymake-make-diagnostic) |
| 88 | + (fboundp 'flymake-diag-region))) |
| 89 | + (error "`php-flymake' requires Emacs 26.1 or later")) |
| 90 | + (cl-loop |
| 91 | + while (search-forward-regexp php-flymake--diaggnostics-pattern nil t) |
| 92 | + for msg = (match-string 2) |
| 93 | + for line = (string-to-number (match-string 4)) |
| 94 | + for diag = (or (pcase-let ((`(,beg . ,end) |
| 95 | + (flymake-diag-region source line))) |
| 96 | + (flymake-make-diagnostic source beg end :error msg)) |
| 97 | + (flymake-make-diagnostic locus (cons line nil) nil :error msg)) |
| 98 | + return (list diag))) |
| 99 | + |
| 100 | +(defun php-flymake--build-command-line (file) |
| 101 | + "Return the command line for `php -l' FILE." |
| 102 | + (let* ((command-args (or php-flymake-executable-command-args |
| 103 | + (list (or (bound-and-true-p php-executable) "php")))) |
| 104 | + (cmd (car-safe command-args)) |
| 105 | + (default-directory (expand-file-name "~"))) |
| 106 | + (unless (or (file-executable-p cmd) |
| 107 | + (executable-find cmd)) |
| 108 | + (user-error "`%s' is not valid command" cmd)) |
| 109 | + (nconc command-args |
| 110 | + (list "-d" "display_errors=0") |
| 111 | + (when file (list "-f" file)) |
| 112 | + (list "-l")))) |
| 113 | + |
| 114 | +(defun php-flymake--make-process (report-fn locus source use-stdin) |
| 115 | + "Make PHP process for syntax check SOURCE buffer. |
| 116 | +
|
| 117 | +See `flymake-diagnostic-functions' about REPORT-FN parameter. |
| 118 | +See `flymake-make-diagnostic' about LOCUS parameter." |
| 119 | + (make-process |
| 120 | + :name "php-flymake" |
| 121 | + :buffer (generate-new-buffer "*flymake-php-flymake*") |
| 122 | + :command (php-flymake--build-command-line (unless use-stdin locus)) |
| 123 | + :noquery t :connection-type 'pipe |
| 124 | + :sentinel |
| 125 | + (lambda (p _ev) |
| 126 | + (unwind-protect |
| 127 | + (when (and (eq 'exit (process-status p)) |
| 128 | + (with-current-buffer source (eq p php-flymake--proc))) |
| 129 | + (with-current-buffer (process-buffer p) |
| 130 | + (goto-char (point-min)) |
| 131 | + (funcall report-fn |
| 132 | + (if (zerop (process-exit-status p)) |
| 133 | + nil |
| 134 | + (php-flymake--diagnostics locus source))))) |
| 135 | + (unless (process-live-p p) |
| 136 | + ;; (display-buffer (process-buffer p)) ; uncomment to debug |
| 137 | + (kill-buffer (process-buffer p))))))) |
| 138 | + |
| 139 | +(provide 'php-flymake) |
| 140 | +;;; php-flymake.el ends here |
0 commit comments