branch: elpa/php-mode
commit 78e2c8b9a21bb0f4db3f22dbcba42b283cd5b2ef
Merge: deab4812fd d89e90d401
Author: USAMI Kenta <[email protected]>
Commit: GitHub <[email protected]>
Merge pull request #817 from emacs-php/fix/blade-fallback
Fall back on an HTML mode for Blade when web-mode is missing
---
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))