Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New feature: php-flymake #718

Merged
merged 6 commits into from
Nov 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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