diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 41fa23bb..cc9294f5 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -20,7 +20,6 @@ jobs:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
emacs-version:
- - "26.3"
- "27.2"
- "28.2"
- "29.4"
@@ -36,8 +35,6 @@ jobs:
emacs-version: snapshot
experimental: true
exclude:
- - os: macos-latest
- emacs-version: "26.3"
- os: macos-latest
emacs-version: "27.2"
steps:
diff --git a/AUTHORS.md b/AUTHORS.md
index 2c25aaee..6aceea50 100644
--- a/AUTHORS.md
+++ b/AUTHORS.md
@@ -84,6 +84,7 @@ Names Sorted Alphabetically:
- Norio Suzuki
- Olaf The Viking
- Peter Oliver
+- Phil Sainty
- Philippe Ivaldi
- Piotr Kwiecinski
- Rex McMaster
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2408957b..1aa2e9cc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,35 @@ All notable changes of the PHP Mode 1.19.1 release series are documented in this
+## [1.27.0] - 2024-12-20
+
+### Added
+
+ * Support PHP 8.4 property-hooks ([#797])
+
+### Changed
+
+ * Improve `php-syntax-propertize-extend-region` efficiency ([#789], thanks [@phil-s]!)
+ * Update `php-phpdoc-type-names` to support [PHPStan 2.0.4] ([#795])
+
+### Fixed
+
+ * Fix Emacs 30 byte-compile errors ([#792])
+ * Use `when-let*` instead of `when-let` to enhance Emacs 30 compatibility ([#796])
+
+### Removed
+
+ * Drop support for Emacs 26 ([#788])
+
+[@phil-s]: https://github.com/phil-s
+[PHPStan 2.0.4]: https://github.com/phpstan/phpstan/releases/tag/2.0.4
+[#788]: https://github.com/emacs-php/php-mode/pull/788
+[#789]: https://github.com/emacs-php/php-mode/pull/789
+[#792]: https://github.com/emacs-php/php-mode/pull/792
+[#795]: https://github.com/emacs-php/php-mode/pull/795
+[#796]: https://github.com/emacs-php/php-mode/pull/796
+[#797]: https://github.com/emacs-php/php-mode/pull/797
+
## [1.26.1] - 2024-09-13
### Added
diff --git a/README.ja.md b/README.ja.md
index 5c0dd082..f9312302 100644
--- a/README.ja.md
+++ b/README.ja.md
@@ -1,9 +1,8 @@
Emacs PHP Mode
-[](https://www.gnu.org/software/emacs/)
-[](https://php.net/manual/migration83.php)
-[](https://php.net/downloads.php)
+[](https://www.gnu.org/software/emacs/)
+[](https://www.php.net/releases/8.4/)
[](https://github.com/emacs-php/php-mode/actions)
[][gpl-v3]
[![NonGNU ELPA][nongnu-elpa-badge]][nongnu-elpa]
@@ -18,14 +17,19 @@ A powerful and flexible Emacs major mode for editing PHP scripts
[GitHubプロジェクト][php-mode]にissueを作成してバグ報告や機能リクエストを送ってください。
> [!NOTE]
-> [最新版][releases]のPHP ModeはEmacs 29をサポートしています。
アップグレードに伴うトラブルは[Discussions][disscussions-emacs29]に気軽に書き込んでください。
+> [最新版][releases]のPHP ModeはEmacs 30をサポートしています。
アップグレードに伴うトラブルは[Discussions][disscussions-emacs30]に気軽に書き込んでください。
+
+> [!WARNING]
+> Emacsをアップグレードした直後に初めてPHPファイルを開いたときに、CC Mode関連のエラーが発生する可能性があります。これは以前のバージョンのEmacsでバイトコンパイルされたPHP Modeがディスクにキャッシュされているために起こるので、PHP Modeの再インストールによって解決します。
+>
+> **`M-x php-mode-debug-reinstall`** または **`M-x package-reinstall php-mode`** コマンドをお試しください。
[releases]: https://github.com/emacs-php/php-mode/releases
-[disscussions-emacs29]: https://github.com/emacs-php/php-mode/discussions/751
+[disscussions-emacs30]: https://github.com/emacs-php/php-mode/discussions/798
## インストール
-**PHP ModeはEmacs 26.1以降で動作します**。対応バージョンの詳細は[Supported Version]をお読みください。Emacs 28以降では単に以下のコマンドを実行するだけでインストールできます。
+**PHP ModeはEmacs 27.1以降で動作します**。対応バージョンの詳細は[Supported Version]をお読みください。Emacs 28以降では単に以下のコマンドを実行するだけでインストールできます。
```
M-x package-install php-mode
diff --git a/README.md b/README.md
index b2726c5d..8ba8a04b 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,8 @@
Emacs PHP Mode
-[](https://www.gnu.org/software/emacs/)
-[](https://www.php.net/manual/migration83.php)
-[](https://www.php.net/downloads.php)
+[](https://www.gnu.org/software/emacs/)
+[](https://www.php.net/releases/8.4/)
[](https://github.com/emacs-php/php-mode/actions)
[][gpl-v3]
[![NonGNU ELPA][nongnu-elpa-badge]][nongnu-elpa]
@@ -18,14 +17,19 @@ English | [日本語](README.ja.md)
Please submit any bug reports or feature requests by creating issues on [the GitHub page for PHP Mode][php-mode].
> [!NOTE]
-> The [latest version][releases] of PHP Mode supports Emacs 29.
Please feel free to [write to disucuss][disscussions-emacs29] if you have problems upgrading to Emacs 29.
+> The [latest version][releases] of PHP Mode supports Emacs 30.
Please feel free to [write to disucuss][disscussions-emacs30] if you have problems upgrading to Emacs 29.
+
+> [!WARNING]
+> After upgrading Emacs, when you open a PHP file for the first time, you may encounter errors related to CC Mode. These errors occur because a previously byte-compiled version of PHP Mode, cached on your disk, differs from the newly installed one. Reinstalling PHP Mode should resolve the issue.
+>
+> Try running **`M-x php-mode-debug-reinstall`** or **`M-x package-reinstall php-mode`**.
[releases]: https://github.com/emacs-php/php-mode/releases
-[disscussions-emacs29]: https://github.com/emacs-php/php-mode/discussions/751
+[disscussions-emacs30]: https://github.com/emacs-php/php-mode/discussions/798
## Installation
-**PHP Mode works with Emacs 26.1 or later**. For details on supported versions, see [Supported Version]. Emacs 28 or later can be installed simply by running the following command.
+**PHP Mode works with Emacs 27.1 or later**. For details on supported versions, see [Supported Version]. Emacs 28 or later can be installed simply by running the following command.
```
M-x package-install php-mode
diff --git a/lisp/php-format.el b/lisp/php-format.el
index cfa71a57..578ee52b 100644
--- a/lisp/php-format.el
+++ b/lisp/php-format.el
@@ -176,7 +176,7 @@
files)
return sym))
(setq-local php-format-command cmd))
- (when-let (tup (plist-get (cdr-safe (assq cmd php-format-formatter-alist)) :command))
+ (when-let* ((tup (plist-get (cdr-safe (assq cmd php-format-formatter-alist)) :command)))
(setq executable (car tup))
(setq args (cdr tup))
(setq vendor (expand-file-name executable (expand-file-name php-format-command-dir default-directory)))
diff --git a/lisp/php-ide-phpactor.el b/lisp/php-ide-phpactor.el
index f0a98c82..109cd461 100644
--- a/lisp/php-ide-phpactor.el
+++ b/lisp/php-ide-phpactor.el
@@ -30,8 +30,7 @@
(require 'phpactor nil t)
(require 'popup nil t)
(require 'smart-jump nil t)
-(eval-when-compile
- (require 'cl-lib))
+(require 'cl-lib)
(defvar-local php-ide-phpactor-buffer nil)
(defvar-local php-ide-phpactor-hover-last-pos nil)
@@ -55,10 +54,11 @@
(defcustom php-ide-phpactor-activate-features '(all)
"A set of Phpactor features you want to enable."
:tag "PHP-IDE Phpactor Activate Features"
- :type '(set (const all :tag "All")
+ :type '(set (const :tag "All" all)
(const hover)
(const navigation))
- :safe (lambda (v) (and (listp v)))
+ :safe (lambda (xs) (and (listp xs)
+ (cl-every (lambda (x) (memq x '(all hover navigation))) xs)))
:group 'php-ide-phpactor)
(defvar php-ide-phpactor-timer nil
diff --git a/lisp/php-ide.el b/lisp/php-ide.el
index a48cf1e0..4e1ec8b4 100644
--- a/lisp/php-ide.el
+++ b/lisp/php-ide.el
@@ -166,7 +166,7 @@
(cond
((stringp php-ide-eglot-executable) (list php-ide-eglot-executable))
((listp php-ide-eglot-executable) php-ide-eglot-executable)
- ((when-let (command (assq php-ide-eglot-executable php-ide-lsp-command-alist))
+ ((when-let* ((command (assq php-ide-eglot-executable php-ide-lsp-command-alist)))
(cond
((functionp command) (funcall command))
((listp command) command))))))
@@ -196,9 +196,9 @@ ACTIVATE: T is given when activeting, NIL when deactivating PHP-IDE."
"Minor mode for integrate IDE-like tools."
:lighter php-ide-mode-lighter
(let ((ide-features php-ide-features))
- (when-let (unavailable-features (cl-loop for feature in ide-features
- unless (assq feature php-ide-feature-alist)
- collect feature))
+ (when-let* ((unavailable-features (cl-loop for feature in ide-features
+ unless (assq feature php-ide-feature-alist)
+ collect feature)))
(user-error "%s includes unavailable PHP-IDE features. (available features are: %s)"
ide-features
(mapconcat (lambda (feature) (concat "'" (symbol-name feature)))
diff --git a/lisp/php-mode.el b/lisp/php-mode.el
index d9b73264..e1736b28 100644
--- a/lisp/php-mode.el
+++ b/lisp/php-mode.el
@@ -10,7 +10,7 @@
;; URL: https://github.com/emacs-php/php-mode
;; Keywords: languages php
;; Version: 1.26.1
-;; Package-Requires: ((emacs "26.1"))
+;; Package-Requires: ((emacs "27.1"))
;; License: GPL-3.0-or-later
(eval-and-compile
@@ -191,7 +191,7 @@ Turning this on will open it whenever `php-mode' is loaded."
#'php-flymake))
"Flymake function to replace, if NIL do not replace."
:tag "PHP Mode Replace Flymake Diag Function"
- :type '(choice '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")
@@ -253,7 +253,7 @@ mumamo-mode turned on. Detects if there are any HTML tags in the
buffer before warning, but this is is not very smart; e.g. if you
have any tags inside a PHP string, it will be fooled."
:tag "PHP Mode Warn If MuMaMo Off"
- :type '(choice (const :tag "Warn" t) (const "Don't warn" nil)))
+ :type '(choice (const :tag "Warn" t) (const :tag "Don't warn" nil)))
(defcustom php-mode-coding-style 'pear
"Select default coding style to use with `php-mode'.
@@ -1038,17 +1038,24 @@ HEREDOC-START."
(unwind-protect
(let (new-start new-end)
(goto-char start)
+ ;; Consider bounding this backwards search by `beginning-of-defun'.
+ ;; (Benchmarking for a wide range of cases may be needed to decide
+ ;; whether that's an improvement, as `php-beginning-of-defun' also
+ ;; uses `re-search-backward'.)
(when (re-search-backward php-heredoc-start-re nil t)
(let ((maybe (point)))
(when (and (re-search-forward (php-heredoc-end-re (match-string 0)) nil t)
(> (point) start))
- (setq new-start maybe))))
- (goto-char end)
- (when (re-search-backward php-heredoc-start-re nil t)
- (if (re-search-forward (php-heredoc-end-re (match-string 0)) nil t)
+ (setq new-start maybe)
(when (> (point) end)
- (setq new-end (point)))
- (setq new-end (point-max))))
+ (setq new-end (point))))))
+ (unless new-end
+ (goto-char end)
+ (when (re-search-backward php-heredoc-start-re start t)
+ (if (re-search-forward (php-heredoc-end-re (match-string 0)) nil t)
+ (when (> (point) end)
+ (setq new-end (point)))
+ (setq new-end (point-max)))))
(when (or new-start new-end)
(cons (or new-start start) (or new-end end))))
;; Cleanup
@@ -1294,18 +1301,20 @@ for \\[find-tag] (which see)."
;; Font Lock
(defconst php-phpdoc-type-names
- (list "string" "integer" "int" "boolean" "bool" "float"
- "double" "object" "mixed" "array" "resource"
- "void" "null" "false" "true" "self" "static"
- "callable" "iterable" "number"
- ;; PHPStan and Psalm types
- "array-key" "associative-array" "callable-array" "callable-object"
- "callable-string" "class-string" "empty" "enum-string" "list"
- "literal-string" "negative-int" "non-positive-int" "non-negative-int"
- "never" "never-return" "never-returns" "no-return" "non-empty-array"
- "non-empty-list" "non-empty-string" "non-falsy-string"
- "numeric" "numeric-string" "positive-int" "scalar"
- "trait-string" "truthy-string" "key-of" "value-of")
+ '(;; PHPStan and Psalm types
+ "__stringandstringable" "array" "array-key" "associative-array" "bool" "boolean"
+ "callable" "callable-array" "callable-object" "callable-string" "class-string"
+ "closed-resource" "double" "empty" "empty-scalar" "enum-string" "false" "float"
+ "int" "integer" "interface-string" "iterable" "list" "literal-string" "lowercase-string"
+ "mixed" "negative-int" "never" "never-return" "never-returns" "no-return" "non-empty-array"
+ "non-empty-list" "non-empty-literal-string" "non-empty-lowercase-string" "non-empty-mixed"
+ "non-empty-scalar" "non-empty-string" "non-empty-uppercase-string" "non-falsy-string"
+ "non-negative-int" "non-positive-int" "non-zero-int" "noreturn" "null" "number" "numeric"
+ "numeric-string" "object" "open-resource" "parent" "positive-int" "pure-callable"
+ "pure-closure" "resource" "scalar" "self" "static" "string" "trait-string" "true"
+ "truthy-string" "uppercase-string" "void"
+ ;; PHPStan Generic Types
+ "key-of" "value-of" "int-mask-of" "int-mask" "__benevolent" "template-type" "new")
"A list of type and pseudotype names that can be used in PHPDoc.")
(make-obsolete-variable 'php-phpdoc-type-keywords 'php-phpdoc-type-names "1.24.2")
@@ -1510,7 +1519,9 @@ for \\[find-tag] (which see)."
;; Not operator (!) is defined in "before cc-mode" section above.
("\\(&&\\|||\\)" 1 'php-logical-op)
;; string interpolation ("$var, ${var}, {$var}")
- (php-mode--string-interpolated-variable-font-lock-find 0 nil)))
+ (php-mode--string-interpolated-variable-font-lock-find 0 nil)
+ (,(rx symbol-start (group (or "get" "set")) (+ (syntax whitespace)) (or "{" "=>"))
+ 1 'php-builtin)))
"Detailed highlighting for PHP Mode.")
(defvar php-font-lock-keywords php-font-lock-keywords-3
diff --git a/lisp/php-project.el b/lisp/php-project.el
index 81899803..0b59e2ce 100644
--- a/lisp/php-project.el
+++ b/lisp/php-project.el
@@ -267,7 +267,7 @@ Typically it is `pear', `drupal', `wordpress', `symfony2' and `psr2'.")
This function is compatible with `project-find-functions'."
(let ((default-directory dir))
- (when-let (root (php-project-get-root-dir))
+ (when-let* ((root (php-project-get-root-dir)))
(if (file-exists-p (expand-file-name ".git" root))
(cons 'vc root)
(cons 'transient root)))))
diff --git a/lisp/php.el b/lisp/php.el
index aa09b5df..c08227b1 100644
--- a/lisp/php.el
+++ b/lisp/php.el
@@ -99,8 +99,8 @@ You can replace \"en\" with your ISO language code."
"Function to search PHP Manual at cursor position."
:group 'php
:tag "PHP Search Documentation Function"
- :type '(choice (const :tag "Use online documentation" #'php-search-web-documentation)
- (const :tag "Use local documentation" #'php-local-manual-search)
+ :type '(choice (const :tag "Use online documentation" php-search-web-documentation)
+ (const :tag "Use local documentation" php-local-manual-search)
(function :tag "Use other function")))
(defcustom php-search-documentation-browser-function nil
@@ -453,7 +453,7 @@ can be used to match against definitions for that classlike."
(defcustom php-imenu-generic-expression 'php-imenu-generic-expression-default
"Default Imenu generic expression for PHP Mode. See `imenu-generic-expression'."
- :type '(choice (alist :key-type string :value-type list)
+ :type '(choice (alist :key-type string :value-type (list string))
(const php-imenu-generic-expression-legacy)
(const php-imenu-generic-expression-simple)
variable)
@@ -553,15 +553,14 @@ The order is reversed by calling as follows:
(c-backward-token-2 1 nil))
collect
(cond
- ((when-let (bounds (php--thing-at-point-bounds-of-string-at-point))
+ ((when-let* ((bounds (php--thing-at-point-bounds-of-string-at-point)))
(prog1 (buffer-substring-no-properties (car bounds) (cdr bounds))
(goto-char (car bounds)))))
((looking-at php-re-token-symbols)
(prog1 (match-string-no-properties 0)
(goto-char (match-beginning 0))))
- (t
- (buffer-substring-no-properties (point)
- (save-excursion (php--c-end-of-token) (point))))))))))
+ ((buffer-substring-no-properties (point)
+ (save-excursion (php--c-end-of-token) (point))))))))))
(defun php-get-pattern ()
"Find the pattern we want to complete.
diff --git a/tests/8.4/property-hooks.php b/tests/8.4/property-hooks.php
new file mode 100644
index 00000000..aa50f965
--- /dev/null
+++ b/tests/8.4/property-hooks.php
@@ -0,0 +1,33 @@
+ $this->firstName . ' ' . $this->lastName;
+ }
+
+ // All write operations go through this hook, and the result is what is written.
+ // Read access happens normally.
+ public string $firstName {
+ set => ucfirst(strtolower($value));
+ }
+
+ // All write operations go through this hook, which has to write to the backing value itself.
+ // Read access happens normally.
+ public string $lastName {
+ set {
+ if (strlen($value) < 2) {
+ throw new \InvalidArgumentException('Too short');
+ }
+ $this->lastName = $value;
+ }
+ }
+}
+
+$p = new Person();
+
+$p->firstName = 'peter';
+print $p->firstName; // Prints "Peter"
+$p->lastName = 'Peterson';
+print $p->fullName; // Prints "Peter Peterson"
diff --git a/tests/8.4/property-hooks.php.faces b/tests/8.4/property-hooks.php.faces
new file mode 100644
index 00000000..89ec60a6
--- /dev/null
+++ b/tests/8.4/property-hooks.php.faces
@@ -0,0 +1,146 @@
+;; -*- mode: emacs-lisp -*-
+(("" . php-comparison-op)
+ (" ")
+ ("$" . php-this-sigil)
+ ("this" . php-this)
+ ("->" . php-object-op)
+ ("firstName" . php-property-name)
+ (" . ")
+ ("' '" . php-string)
+ (" . ")
+ ("$" . php-this-sigil)
+ ("this" . php-this)
+ ("->" . php-object-op)
+ ("lastName" . php-property-name)
+ (";\n }\n\n ")
+ ("// " . font-lock-comment-delimiter-face)
+ ("All write operations go through this hook, and the result is what is written.\n" . font-lock-comment-face)
+ (" ")
+ ("// " . font-lock-comment-delimiter-face)
+ ("Read access happens normally.\n" . font-lock-comment-face)
+ (" ")
+ ("public" . php-keyword)
+ (" ")
+ ("string" . php-class)
+ (" ")
+ ("$" . php-variable-sigil)
+ ("firstName" . php-variable-name)
+ (" {\n ")
+ ("set" . php-builtin)
+ (" ")
+ ("=" . php-assignment-op)
+ (">" . php-comparison-op)
+ (" ")
+ ("ucfirst" . php-function-call-traditional)
+ ("(")
+ ("strtolower" . php-function-call-traditional)
+ ("(")
+ ("$" . php-variable-sigil)
+ ("value" . php-variable-name)
+ ("));\n }\n\n ")
+ ("// " . font-lock-comment-delimiter-face)
+ ("All write operations go through this hook, which has to write to the backing value itself.\n" . font-lock-comment-face)
+ (" ")
+ ("// " . font-lock-comment-delimiter-face)
+ ("Read access happens normally.\n" . font-lock-comment-face)
+ (" ")
+ ("public" . php-keyword)
+ (" ")
+ ("string" . php-class)
+ (" ")
+ ("$" . php-variable-sigil)
+ ("lastName" . php-variable-name)
+ (" {\n ")
+ ("set" . php-builtin)
+ (" {\n ")
+ ("if" . php-keyword)
+ (" (")
+ ("strlen" . php-function-call-traditional)
+ ("(")
+ ("$" . php-variable-sigil)
+ ("value" . php-variable-name)
+ (") ")
+ ("<" . php-comparison-op)
+ (" 2) {\n ")
+ ("throw" . php-keyword)
+ (" ")
+ ("new" . php-keyword)
+ (" ")
+ ("\\InvalidArgumentException" . font-lock-type-face)
+ ("(")
+ ("'Too short'" . php-string)
+ (");\n }\n ")
+ ("$" . php-this-sigil)
+ ("this" . php-this)
+ ("->" . php-object-op)
+ ("lastName" . php-property-name)
+ (" ")
+ ("=" . php-assignment-op)
+ (" ")
+ ("$" . php-variable-sigil)
+ ("value" . php-variable-name)
+ (";\n }\n }\n}\n\n")
+ ("$" . php-variable-sigil)
+ ("p" . php-variable-name)
+ (" ")
+ ("=" . php-assignment-op)
+ (" ")
+ ("new" . php-keyword)
+ (" ")
+ ("Person" . font-lock-type-face)
+ ("();\n\n")
+ ("$" . php-variable-sigil)
+ ("p" . php-variable-name)
+ ("->" . php-object-op)
+ ("firstName" . php-property-name)
+ (" ")
+ ("=" . php-assignment-op)
+ (" ")
+ ("'peter'" . php-string)
+ (";\n")
+ ("print" . php-keyword)
+ (" ")
+ ("$" . php-variable-sigil)
+ ("p" . php-variable-name)
+ ("->" . php-object-op)
+ ("firstName" . php-property-name)
+ ("; ")
+ ("// " . font-lock-comment-delimiter-face)
+ ("Prints \"Peter\"\n" . font-lock-comment-face)
+ ("$" . php-variable-sigil)
+ ("p" . php-variable-name)
+ ("->" . php-object-op)
+ ("lastName" . php-property-name)
+ (" ")
+ ("=" . php-assignment-op)
+ (" ")
+ ("'Peterson'" . php-string)
+ (";\n")
+ ("print" . php-keyword)
+ (" ")
+ ("$" . php-variable-sigil)
+ ("p" . php-variable-name)
+ ("->" . php-object-op)
+ ("fullName" . php-property-name)
+ ("; ")
+ ("// " . font-lock-comment-delimiter-face)
+ ("Prints \"Peter Peterson\"\n" . font-lock-comment-face))
diff --git a/tests/php-mode-test.el b/tests/php-mode-test.el
index 9890e0c8..4d36579b 100644
--- a/tests/php-mode-test.el
+++ b/tests/php-mode-test.el
@@ -1,6 +1,6 @@
-;;; php-mode-test.el --- Tests for php-mode
+;;; php-mode-test.el --- Tests for php-mode -*- lexical-binding: t -*-
-;; Copyright (C) 2018-2019 Friends of Emacs-PHP development
+;; Copyright (C) 2018-2024 Friends of Emacs-PHP development
;; Copyright (C) 2013 Daniel Hackney
;; 2014, 2015 Eric James Michael Ritz
@@ -669,6 +669,10 @@ Meant for `php-mode-test-issue-503'."
(with-php-mode-test ("8.1/enum.php" :faces t))
(with-php-mode-test ("8.1/readonly.php" :faces t)))
+(ert-deftest php-mode-test-php84 ()
+ "Test highlighting language constructs added in PHP 8.4."
+ (with-php-mode-test ("8.4/property-hooks.php" :faces t)))
+
(ert-deftest php-mode-test-lang ()
"Test highlighting for language constructs."
(with-php-mode-test ("lang/class/anonymous-class.php" :indent t :magic t :faces t))