diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 41fa23bb..d3f9e107 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -20,10 +20,10 @@ jobs:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
emacs-version:
- - "26.3"
- "27.2"
- "28.2"
- "29.4"
+ - "30.1"
experimental: [false]
include:
- os: ubuntu-latest
@@ -36,8 +36,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..115ef78d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,7 +2,46 @@
All notable changes of the PHP Mode 1.19.1 release series are documented in this file using the [Keep a CHANGELOG](https://keepachangelog.com/) principles.
-
+## Unreleased
+
+### Changed
+
+ * Add `readonly` class modifier to [Imenu] ([#802])
+ * Add `enum` support to `php-current-class` ([#802])
+ * Remove hardcoding of implicit paths in `php` that are not guaranteed to exist ([#803])
+
+[Imenu]: https://www.gnu.org/software/emacs/manual/html_node/emacs/Imenu.html
+[#802]: https://github.com/emacs-php/php-mode/pull/802
+[#803]: https://github.com/emacs-php/php-mode/pull/803
+
+## [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
diff --git a/Eask b/Eask
index 2a90ee52..88e00bf2 100644
--- a/Eask
+++ b/Eask
@@ -1,3 +1,5 @@
+;; -*- mode: eask; lexical-binding: t -*-
+
(package "php-mode"
"1.26.1"
"Major mode for editing PHP code")
diff --git a/README.ja.md b/README.ja.md
index 5c0dd082..81c0f510 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][discussions-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
+[discussions-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..490b3f08 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,28 +17,35 @@ 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 [open a discussion][discussions-emacs30] if you have any issues upgrading to Emacs 30.
+
+> [!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
+[discussions-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].
+On Emacs 28 or later, you can install it simply by running:
```
M-x package-install php-mode
```
-By [adding MELPA to `package-archives`][melpa-getting-started], you can extend Emacs with many packages from the web.
+By [adding MELPA to `package-archives`][melpa-getting-started], you can extend Emacs with numerous packages from the web.
-If you don't want to depend on a package manager, you can install Lisp files directly in the traditional way. See [Manual installation][wiki-manual-installation] for our recommended setup method.
+If you prefer not to rely on a package manager, you can install the Lisp files directly in the traditional manner. See [Manual installation][wiki-manual-installation] for our recommended method.
## Configuration
### Personal Settings
-You can add configurations for PHP mode in the .emacs file (`~/.emacs.d/init.el`):
+You can add configurations for PHP Mode in your `.emacs` file (`~/.emacs.d/init.el`):
```lisp
(defun my-php-mode-init ()
@@ -63,7 +69,7 @@ You can add configurations for PHP mode in the .emacs file (`~/.emacs.d/init.el`
### Project Setting
-You can add project-specific settings by creating a `.dir-locals.el` or `.dir-locals-2.el` file in the project's root directory. It is recommended not to put these files under version control, as they depend on the packages installed in each user's Emacs.
+You can add project-specific settings by creating a `.dir-locals.el` or `.dir-locals-2.el` file in the project's root directory. It is recommended not to put these files under version control, as they depend on the packages installed on each user's Emacs.
```lisp
((nil
@@ -73,13 +79,12 @@ You can add project-specific settings by creating a `.dir-locals.el` or `.dir-lo
## Reporting Bugs
-When reporting a bug please run the function `M-x php-mode-debug` and include its output in your bug report. This helps up reproduce any problem you may have.
+When reporting a bug, please run `M-x php-mode-debug` and include its output in your bug report. This helps us reproduce any issues you may be experiencing.
## How to Contribute
Please see [CONTRIBUTING.md](CONTRIBUTING.md#english).
-
## Copyright
PHP Mode is licensed under [GNU General Public License Version 3][gpl-v3] (GPLv3).
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-local-manual.el b/lisp/php-local-manual.el
index ba4f854e..eec5cc3a 100644
--- a/lisp/php-local-manual.el
+++ b/lisp/php-local-manual.el
@@ -194,6 +194,7 @@ current `tags-file-name'."
(setq php-local-manual--completion-table php-table))))
(defun php-local-manual-build-table-from-file (filename)
+ "Build a table of PHP function names from FILENAME."
(let ((table (make-vector 1022 0))
(buf (find-file-noselect filename)))
(with-current-buffer buf
@@ -208,10 +209,11 @@ current `tags-file-name'."
(defun php-local-manual-build-table-from-path (path)
"Return list of PHP function name from `PATH' directory."
- (cl-loop for file in (directory-files path nil "^function\\..+\\.html$")
- if (string-match "\\.\\([-a-zA-Z_0-9]+\\)\\.html$" file)
- collect (replace-regexp-in-string
- "-" "_" (substring file (match-beginning 1) (match-end 1)) t)))
+ (save-match-data
+ (cl-loop for file in (directory-files path nil "^function\\..+\\.html$")
+ if (string-match "\\.\\([-a-zA-Z_0-9]+\\)\\.html$" file)
+ collect (replace-regexp-in-string
+ "-" "_" (substring file (match-beginning 1) (match-end 1)) t))))
(provide 'php-local-manual)
;;; php-local-manual.el ends here
diff --git a/lisp/php-mode.el b/lisp/php-mode.el
index d9b73264..afd51294 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'.
@@ -627,8 +627,7 @@ but only if the setting is enabled."
((assq 'defun-block-intro c-syntactic-context) nil)
((assq 'defun-close c-syntactic-context) nil)
((assq 'statement-cont c-syntactic-context) nil)
- (t
- (save-excursion
+ ((save-excursion
(beginning-of-line)
(let ((beginning-of-langelem (cdr langelem))
(beginning-of-current-line (point))
@@ -651,8 +650,10 @@ but only if the setting is enabled."
(skip-chars-backward " \r\n")
(backward-char 1))
(and (not (eq (point) beginning-of-current-line))
+ (not (php-in-string-or-comment-p))
(not (looking-at-p ","))
- (not (php-in-string-or-comment-p))))
+ (save-excursion
+ (backward-char) (not (looking-at-p ",")))))
'+)
(t nil)))))))
@@ -1038,17 +1039,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 +1302,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 +1520,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..ef46272c 100644
--- a/lisp/php.el
+++ b/lisp/php.el
@@ -49,12 +49,24 @@
:link '(url-link :tag "Official Site" "https://github.com/emacs-php/php-mode")
:link '(url-link :tag "PHP Mode Wiki" "https://github.com/emacs-php/php-mode/wiki"))
-(defcustom php-executable (or (executable-find "php") "/usr/bin/php")
+(defcustom php-executable (or (executable-find "php") "php")
"The location of the PHP executable."
:group 'php
:tag "PHP Executable"
:type 'string)
+(defcustom php-phpdbg-executable (list "phpdbg")
+ "The location of the PHPDBG executable."
+ :group 'php
+ :tag "PHP PHPDBG Executable"
+ :type '(repeat string))
+
+(defcustom php-php-parse-executabe nil
+ "The location of the php-parse executable."
+ :group 'php
+ :tag "PHP php-parse Executable"
+ :type '(repeat string))
+
(defcustom php-site-url "https://www.php.net/"
"Default PHP.net site URL.
@@ -99,8 +111,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
@@ -325,12 +337,18 @@ can be used to match against definitions for that classlike."
;; First see if 'abstract' or 'final' appear, although really these
;; are not valid for all values of `type' that the function
;; accepts.
- "^\\s-*\\(?:\\(?:abstract\\|final\\)\\s-+\\)?"
+ (eval-when-compile
+ (rx line-start
+ (* (syntax whitespace))
+ (? (or "abstract" "final" "readonly")
+ (+ (syntax whitespace)))))
;; The classlike type
type
;; Its name, which is the first captured group in the regexp. We
;; allow backslashes in the name to handle namespaces, but again
;; this is not necessarily correct for all values of `type'.
+ ;; (rx (+ (syntax whitespace))
+ ;; (group (+ (or (syntax word) "\\" (syntax symbol)))))
"\\s-+\\(\\(?:\\sw\\|\\\\\\|\\s_\\)+\\)")))
(defconst php-imenu-generic-expression-default
@@ -453,7 +471,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)
@@ -465,7 +483,7 @@ can be used to match against definitions for that classlike."
(defconst php--re-classlike-pattern
(eval-when-compile
- (php-create-regexp-for-classlike (regexp-opt '("class" "interface" "trait")))))
+ (php-create-regexp-for-classlike (regexp-opt '("class" "interface" "trait" "enum")))))
(defvar php--analysis-syntax-table
(eval-when-compile
@@ -553,15 +571,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.
@@ -665,17 +682,15 @@ Currently there are `php-mode' and `php-ts-mode'."
(defun php-current-class ()
"Insert current class name if cursor in class context."
(interactive)
- (let ((matched (php-get-current-element php--re-classlike-pattern)))
- (when matched
- (insert (concat matched php-class-suffix-when-insert)))))
+ (when-let* ((matched (php-get-current-element php--re-classlike-pattern)))
+ (insert (concat matched php-class-suffix-when-insert))))
;;;###autoload
(defun php-current-namespace ()
"Insert current namespace if cursor in namespace context."
(interactive)
- (let ((matched (php-get-current-element php--re-namespace-pattern)))
- (when matched
- (insert (concat matched php-namespace-suffix-when-insert)))))
+ (when-let* ((matched (php-get-current-element php--re-namespace-pattern)))
+ (insert (concat matched php-namespace-suffix-when-insert))))
;;;###autoload
(defun php-copyit-fqsen ()
@@ -801,5 +816,29 @@ When `DOCUMENT-ROOT' is NIL, the document root is obtained from `ROUTER-OR-DIR'.
#'file-exists-p))))
(find-file file))
+
+(defun php-phpdbg-disassemble-file (file)
+ "Read PHP FILE and print opcodes."
+ (interactive (list (if (or buffer-file-name (zerop (prefix-numeric-value current-prefix-arg)))
+ buffer-file-name
+ (expand-file-name
+ (read-file-name "Select PHP file: " default-directory buffer-file-name)))))
+ (let ((args `(,@php-phpdbg-executable "-dopcache.enable_cli=1" "-p*" ,file)))
+ (compile (mapconcat #'shell-quote-argument args " "))))
+
+(defun php-parse-file (file)
+ "Parse PHP FILE and print node tree."
+ (interactive (list (if (or buffer-file-name (zerop (prefix-numeric-value current-prefix-arg)))
+ buffer-file-name
+ (expand-file-name
+ (read-file-name "Select PHP file: " default-directory buffer-file-name)))))
+ (let* ((project-dir (php-project-get-root-dir))
+ (executable (or php-php-parse-executabe
+ (file-executable-p (expand-file-name "vendor/bin/php-parse" project-dir))
+ (executable-find "php-parse")
+ (user-error "`php-parse' command not found")))
+ (args `(,@(if (listp executable) executable (list executable)) ,file)))
+ (compile (mapconcat #'shell-quote-argument args " "))))
+
(provide 'php)
;;; php.el ends here
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/indent/issue-793.php b/tests/indent/issue-793.php
new file mode 100644
index 00000000..917666d4
--- /dev/null
+++ b/tests/indent/issue-793.php
@@ -0,0 +1,9 @@
+ 1, // ###php-mode-test### ((indent 4))
+ 'bar' => 2,
+
+'buz' => 3, // ###php-mode-test### ((indent 4))
+'buzbuz' => 4, // ###php-mode-test### ((indent 4))
+];
diff --git a/tests/php-mode-test.el b/tests/php-mode-test.el
index 9890e0c8..6881ec54 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
@@ -644,14 +644,14 @@ Meant for `php-mode-test-issue-503'."
(goto-char (point-min))
(should (eq (php-mode-test-in-function-p nil) nil))))
-(ert-deftest php-mode-test-issue-623 ()
- "Proper alignment object -> accessor."
- (with-php-mode-test ("indent/issue-623.php" :indent t :magic t)))
-
-(ert-deftest php-mode-test-issue-702 ()
- "Proper alignment arglist."
+(ert-deftest php-mode-test-indentation-issues ()
+ ;; Proper alignment object -> accessor.
+ (with-php-mode-test ("indent/issue-623.php" :indent t :magic t))
+ ;; Proper alignment arglist.
(with-php-mode-test ("indent/issue-702.php" :indent t :magic t))
- (with-php-mode-test ("indent/issue-726.php" :indent t :magic t)))
+ (with-php-mode-test ("indent/issue-726.php" :indent t :magic t))
+ ;; Proper alignment arglist that contains empty lines.
+ (with-php-mode-test ("indent/issue-793.php" :indent t :magic t)))
(ert-deftest php-mode-test-php74 ()
"Test highlighting language constructs added in PHP 7.4."
@@ -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))