1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
|
;;; php-flymake.el --- Flymake backend for PHP -*- lexical-binding: t; -*-
;; Copyright (C) 2023 Friends of Emacs-PHP development
;; Author: USAMI Kenta <tadsan@zonu.me>
;; Created: 5 Mar 2022
;; Version: 1.26.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-and-compile
(require 'flymake-proc))
(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
|