diff --git a/CHANGELOG.md b/CHANGELOG.md index 3276c010..c4ba9241 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) @@ -15,6 +17,7 @@ All notable changes of the PHP Mode 1.19.1 release series are documented in this * 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 @@ -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 @@ -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 diff --git a/Makefile b/Makefile index 97922d2a..45dc5c44 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 00000000..3c71e1a7 --- /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 +;; 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 . + +;;; 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 diff --git a/lisp/php-mode.el b/lisp/php-mode.el index 6843b3a7..d7603131 100644 --- a/lisp/php-mode.el +++ b/lisp/php-mode.el @@ -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) @@ -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'. @@ -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." @@ -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) @@ -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) @@ -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))) @@ -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) diff --git a/lisp/php.el b/lisp/php.el index 3a79a97a..ce70b279 100644 --- a/lisp/php.el +++ b/lisp/php.el @@ -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