branch: elpa/php-mode
commit d89e90d40196a95fc5fe58abf57fc94608fd41e6
Author: USAMI Kenta <[email protected]>
Commit: USAMI Kenta <[email protected]>
Fall back on an HTML mode for Blade when web-mode is missing
Opening a .blade.php file without web-mode installed did not warn and
carry on -- it failed. php-derivation-major-mode warned but left MODE
set to the unavailable web-mode, so php-mode-maybe went on to funcall it
and signalled void-function. Every other template zeroed MODE and fell
back on php-default-major-mode; Blade was the one case that could not
open at all.
Degrade instead. A Blade template is mostly HTML plus Blade's own
directives -- it is not PHP, so php-mode cannot read it either -- and an
HTML mode renders the bulk of it correctly. The new
php-blade-template-major-mode-fallback names the modes to try
(mhtml-mode then html-mode, both built in); set it to nil to opt out and
get php-default-major-mode as before. The warning now says which mode
was substituted rather than claiming Blade is unsupported.
The fallback deliberately applies to Blade only. Other templates are
PHP with HTML in them, which php-mode does read, and they share
php-html-template-major-mode with the auto-detection path --
php-project-php-file-as-template defaults to `auto', so any .php file
holding an HTML tag reaches it. Extending the fallback there would take
most PHP files away from php-mode for everyone without web-mode. A test
pins that behaviour down.
Uses cl-loop, as php-leading-tokens already does; php.el requires
cl-lib at compile time and cl-macs is loaded by the time it runs.
---
lisp/php.el | 36 ++++++++++++++++++++++++++++++++----
tests/php-mode-test.el | 49 +++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 81 insertions(+), 4 deletions(-)
diff --git a/lisp/php.el b/lisp/php.el
index 2e072376f2..b02fb41a85 100644
--- a/lisp/php.el
+++ b/lisp/php.el
@@ -180,6 +180,20 @@ a completion list."
:tag "PHP Blade Template Major Mode"
:type 'function)
+(defcustom php-blade-template-major-mode-fallback '(mhtml-mode html-mode)
+ "Major modes to fall back on for a Blade template.
+
+Used when `php-blade-template-major-mode' — `web-mode' by default — is
+not installed. The first entry whose function is defined wins; when
+none is, `php-default-major-mode' is used.
+
+A Blade template is mostly HTML plus Blade's own directives, so an HTML
+mode reads it far better than `php-mode' does. Set this to nil to opt
+out and get `php-default-major-mode' instead."
+ :group 'php
+ :tag "PHP Blade Template Major Mode Fallback"
+ :type '(repeat function))
+
(defcustom php-template-mode-alist
`(("\\.blade" . ,php-blade-template-major-mode)
("\\.phpt\\'" . ,(if (fboundp 'phpt-mode) 'phpt-mode
php-default-major-mode))
@@ -623,6 +637,11 @@ indentation."
(setq php--buffer-has-html-tag-cache (cons tick result))
result))))
+(defun php--blade-template-fallback-mode ()
+ "Return the first available mode of
`php-blade-template-major-mode-fallback'."
+ (cl-loop for mode in php-blade-template-major-mode-fallback
+ thereis (and (fboundp mode) mode)))
+
(defun php-derivation-major-mode ()
"Return major mode for PHP file by file-name and its content."
(let ((mode (assoc-default buffer-file-name
@@ -638,10 +657,19 @@ indentation."
(when (php-buffer-has-html-tag)
(setq mode php-html-template-major-mode)))))
(when (and mode (not (fboundp mode)))
- (if (string-match-p "\\.blade\\." buffer-file-name)
- (warn "php-mode is NOT support blade template. %s"
- "Please install `web-mode' package")
- (setq mode nil)))
+ ;; A Blade template is not PHP, so `php-default-major-mode' cannot
+ ;; read it at all; degrade to an HTML mode instead. Every other
+ ;; template is PHP with HTML in it, and keeps falling back on
+ ;; `php-default-major-mode' below.
+ (setq mode (when (string-match-p "\\.blade\\." buffer-file-name)
+ (let ((fallback (php--blade-template-fallback-mode)))
+ (warn "`%s' is not available for this Blade template; %s.
+Install the `web-mode' package for full Blade support."
+ mode
+ (if fallback
+ (format "using `%s' instead" fallback)
+ (format "falling back on `%s'"
php-default-major-mode)))
+ fallback))))
(or mode php-default-major-mode)))
;;;###autoload
diff --git a/tests/php-mode-test.el b/tests/php-mode-test.el
index da662856b1..09a33754f2 100644
--- a/tests/php-mode-test.el
+++ b/tests/php-mode-test.el
@@ -734,6 +734,55 @@ those going through `php-mode-maybe' did not: the same
buffer became
(set-buffer-modified-p nil)
(setq buffer-file-name nil))))))
+(defun php-mode-test--derive (file-name &rest body-text)
+ "Return the mode `php-derivation-major-mode' picks for FILE-NAME.
+BODY-TEXT is inserted into the buffer first."
+ (with-temp-buffer
+ (setq buffer-file-name (expand-file-name file-name
temporary-file-directory))
+ (unwind-protect
+ (progn
+ (apply #'insert body-text)
+ (php-derivation-major-mode))
+ (set-buffer-modified-p nil)
+ (setq buffer-file-name nil))))
+
+(ert-deftest php-mode-test-blade-template-fallback ()
+ "A Blade template degrades to an HTML mode when `web-mode' is missing.
+
+A `.blade.php' file is mostly HTML plus Blade directives, so it is not
+PHP and `php-default-major-mode' cannot read it. Before the fallback
+existed, `php-derivation-major-mode' returned the unavailable
+`web-mode' anyway and `php-mode-maybe' then failed with
+`void-function'."
+ (let ((php-blade-template-major-mode 'php-mode-test--absent-web-mode)
+ (php-template-mode-alist '(("\\.blade" .
php-mode-test--absent-web-mode))))
+ (should-not (fboundp 'php-mode-test--absent-web-mode))
+ ;; The chosen fallback must be usable: this is what `php-mode-maybe'
+ ;; funcalls, so an unavailable mode would signal `void-function'.
+ (let ((php-blade-template-major-mode-fallback '(html-mode)))
+ (should (eq 'html-mode (php-mode-test--derive "welcome.blade.php"
"@extends('x')\n"))))
+ ;; Unavailable entries are skipped.
+ (let ((php-blade-template-major-mode-fallback
'(php-mode-test--absent-web-mode html-mode)))
+ (should (eq 'html-mode (php-mode-test--derive "welcome.blade.php"
"@extends('x')\n"))))
+ ;; Opting out falls back on `php-default-major-mode'.
+ (let ((php-blade-template-major-mode-fallback nil)
+ (php-default-major-mode 'php-mode))
+ (should (eq 'php-mode (php-mode-test--derive "welcome.blade.php"
"@extends('x')\n"))))))
+
+(ert-deftest php-mode-test-html-template-fallback-unchanged ()
+ "A missing `php-html-template-major-mode' still derives to PHP.
+
+Only Blade degrades to an HTML mode. `php-project-php-file-as-template'
+defaults to `auto', so any .php file holding an HTML tag reaches this
+path; sending those to an HTML mode would take most PHP files away from
+`php-mode' for anyone without `web-mode'."
+ (let ((php-html-template-major-mode 'php-mode-test--absent-web-mode)
+ (php-project-php-file-as-template 'auto)
+ (php-default-major-mode 'php-mode)
+ (php-template-mode-alist nil))
+ (should-not (fboundp 'php-mode-test--absent-web-mode))
+ (should (eq 'php-mode (php-mode-test--derive "page.php" "<div>\n<?php echo
'hi'; ?>\n</div>\n")))))
+
(ert-deftest php-mode-test-php74 ()
"Test highlighting language constructs added in PHP 7.4."
(with-php-mode-test ("7.4/arrow-function.php" :faces t))