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

-[![Emacs: 29.2](https://img.shields.io/badge/Emacs-29.2-blue.svg)](https://www.gnu.org/software/emacs/) -[![lang: PHP 8.3](https://img.shields.io/badge/lang-PHP%208.3-brightgreen.svg)](https://php.net/manual/migration83.php) -[![lang: PHP 7](https://img.shields.io/badge/lang-PHP%207-green.svg)](https://php.net/downloads.php) +[![Emacs: 30.0](https://img.shields.io/badge/Emacs-30.0-blue.svg)](https://www.gnu.org/software/emacs/) +[![lang: PHP 8.4](https://img.shields.io/badge/lang-PHP%208.4-brightgreen.svg)](https://www.php.net/releases/8.4/) [![Build Status](https://github.com/emacs-php/php-mode/workflows/CI/badge.svg)](https://github.com/emacs-php/php-mode/actions) [![GPL v3](https://img.shields.io/badge/license-GPL_v3-green.svg)][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

-[![Emacs: 29.2](https://img.shields.io/badge/Emacs-29.2-blue.svg)](https://www.gnu.org/software/emacs/) -[![lang: PHP 8.3](https://img.shields.io/badge/lang-PHP%208.3-brightgreen.svg)](https://www.php.net/manual/migration83.php) -[![lang: PHP 7](https://img.shields.io/badge/lang-PHP%207-green.svg)](https://www.php.net/downloads.php) +[![Emacs: 30.0](https://img.shields.io/badge/Emacs-30.0-blue.svg)](https://www.gnu.org/software/emacs/) +[![lang: PHP 8.4](https://img.shields.io/badge/lang-PHP%208.4-brightgreen.svg)](https://www.php.net/releases/8.4/) [![Build Status](https://github.com/emacs-php/php-mode/workflows/CI/badge.svg)](https://github.com/emacs-php/php-mode/actions) [![GPL v3](https://img.shields.io/badge/license-GPL_v3-green.svg)][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))