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

-[![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][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

-[![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,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))