diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 7aeaf5c4..50cce894 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:
- - "27.2"
- "28.2"
- "29.4"
- "30.2"
@@ -35,9 +34,6 @@ jobs:
- os: windows-latest
emacs-version: snapshot
experimental: true
- exclude:
- - os: macos-latest
- emacs-version: "27.2"
steps:
- uses: actions/setup-python@v6
with:
@@ -52,5 +48,7 @@ jobs:
- uses: actions/checkout@v7
- name: Run tests
run: 'make .eask test'
+ - name: Run php-cc-mode regression tests
+ run: 'make test-cc'
- name: Run tests (allow failure)
run: 'make .eask test || true'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 115ef78d..c5ee756c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,15 +4,44 @@ All notable changes of the PHP Mode 1.19.1 release series are documented in this
## Unreleased
+### Added
+
+ * **Rewrite `php-mode` as a CC Mode independent major mode**
+ * Indentation is handled by `php-indent.el`, a `syntax-ppss` based engine ported from `js.el` bundled with GNU Emacs
+ * Coding styles are handled by `php-style.el`, a variable-based style system that replaces CC Mode's `c-set-style`
+ * PHP language keyword tables are provided by `php-keywords.el`
+ * Preserve the legacy CC Mode based implementation as `php-cc-mode`
+ * `php-cc-mode` is in frozen maintenance: only regression fixes are accepted, and new development happens in the CC Mode independent `php-mode`
+ * For backward compatibility, `php-cc-mode` runs `php-mode-hook` in addition to its own `php-cc-mode-hook`
+ * Settings described in [the CC Mode manual] now apply only to `php-cc-mode`; see the commentary at the top of `lisp/php-cc-mode.el`
+
### Changed
+ * Change the default of `php-mode-coding-style` from `pear` to `per` ([PER Coding Style 2.0])
* 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])
+### Deprecated
+
+ * `c-basic-offset` is obsolete in `php-mode`; use `php-indent-offset` instead
+ * A migration layer still honors a buffer-local `c-basic-offset` (for example set via `.dir-locals.el`) by copying its value into `php-indent-offset` with a warning
+ * `php-mode-lineup-cascaded-calls` is obsolete; use `php-indent-chain-indent` instead
+ * `php-mode-enable-backup-style-variables` is obsolete; use `php-style-delete-trailing-whitespace` instead
+
+### Removed
+
+ * **Drop support for Emacs 27; PHP Mode now requires Emacs 28.1 or later** ([#811])
+ * Remove the `symfony2` coding style from `php-mode`, which is superseded by `per`
+ * The style remains available in `php-cc-mode`
+ * `php-enable-symfony2-coding-style` is now an obsolete alias for `php-enable-per-coding-style`
+
[Imenu]: https://www.gnu.org/software/emacs/manual/html_node/emacs/Imenu.html
+[PER Coding Style 2.0]: https://www.php-fig.org/per/coding-style/
+[the CC Mode manual]: https://www.gnu.org/software/emacs/manual/html_mono/ccmode.html
[#802]: https://github.com/emacs-php/php-mode/pull/802
[#803]: https://github.com/emacs-php/php-mode/pull/803
+[#811]: https://github.com/emacs-php/php-mode/issues/811
## [1.27.0] - 2024-12-20
diff --git a/Eask b/Eask
index deef9331..904fdbf2 100644
--- a/Eask
+++ b/Eask
@@ -10,11 +10,14 @@
(package-file "lisp/php-mode.el")
(files
"lisp/php.el"
+ "lisp/php-mode.el"
"lisp/php-core.el"
- "lisp/php-complete.el"
- "lisp/php-defs.el"
+ "lisp/php-cc-mode.el"
"lisp/php-indent.el"
+ "lisp/php-style.el"
"lisp/php-keywords.el"
+ "lisp/php-complete.el"
+ "lisp/php-defs.el"
"lisp/php-face.el"
"lisp/php-flymake.el"
"lisp/php-format.el"
@@ -30,7 +33,7 @@
(source 'melpa)
(source 'gnu)
-(depends-on "emacs" "27.1")
+(depends-on "emacs" "28.1")
(development
(depends-on "phpactor")
diff --git a/Makefile b/Makefile
index ff55d907..d912dc8d 100644
--- a/Makefile
+++ b/Makefile
@@ -52,4 +52,11 @@ test: clean all
$(EASK) install polymode web-mode
$(EASK) test ert ./tests/php-mode-test.el
-.PHONY: all authors autoloads clean test
+# Regression tests for the frozen CC Mode based `php-cc-mode'. Run in a
+# separate Eask invocation (process) from the `php-mode' tests so that
+# the two major modes, which still share several public symbol names, do
+# not clobber each other in a single Emacs session.
+test-cc: clean all
+ $(EASK) test ert ./tests/php-cc-mode-test.el
+
+.PHONY: all authors autoloads clean test test-cc
diff --git a/README.ja.md b/README.ja.md
index 2222cbb3..bdeec955 100644
--- a/README.ja.md
+++ b/README.ja.md
@@ -19,6 +19,9 @@ A powerful and flexible Emacs major mode for editing PHP scripts
> [!NOTE]
> [最新版][releases]のPHP ModeはEmacs 30をサポートしています。
アップグレードに伴うトラブルは[Discussions][discussions-emacs30]に気軽に書き込んでください。
+> [!IMPORTANT]
+> PHP Modeは書き直され、CC Modeに依存しなくなりました。インデントはGNU Emacsに同梱されている`js.el`に由来するエンジンで処理されます。従来のCC Modeベースの実装は`php-cc-mode`として引き続き利用できます。詳しくは[レガシーなCC Mode実装](#レガシーなcc-mode実装-php-cc-mode)をお読みください。
+
> [!WARNING]
> Emacsをアップグレードした直後に初めてPHPファイルを開いたときに、CC Mode関連のエラーが発生する可能性があります。これは以前のバージョンのEmacsでバイトコンパイルされたPHP Modeがディスクにキャッシュされているために起こるので、PHP Modeの再インストールによって解決します。
>
@@ -29,7 +32,7 @@ A powerful and flexible Emacs major mode for editing PHP scripts
## インストール
-**PHP ModeはEmacs 27.1以降で動作します**。対応バージョンの詳細は[Supported Version]をお読みください。Emacs 28以降では単に以下のコマンドを実行するだけでインストールできます。
+**PHP ModeはEmacs 28.1以降で動作します**([#811])。対応バージョンの詳細は[Supported Version]をお読みください。Emacs 28以降では単に以下のコマンドを実行するだけでインストールできます。
```
M-x package-install php-mode
@@ -75,6 +78,67 @@ M-x package-install php-mode
(php-project-coding-style . psr2)))
```
+### コーディングスタイル
+
+`php-mode`は`php-mode-coding-style`に従ってインデントや編集関連の変数を設定します。既定のスタイルは`per`([PER Coding Style 2.0][per-cs])です。以前のバージョンの既定は`pear`でした。利用できるスタイルは`per`、`psr2`、`pear`、`drupal`、`wordpress`です。
+
+`symfony2`スタイルは削除されました。現代のSymfonyが採用するコーディングスタイルはPERに置き換えられているためです。`php-cc-mode`では引き続き利用できます。
+
+インデントエンジンはもはやCC Modeを使わないため、`php-mode`では`c-basic-offset`はobsoleteです。かわりに`php-indent-offset`をカスタマイズしてください。後方互換のため、プロジェクトが`c-basic-offset`をバッファローカルに設定している場合(`.dir-locals.el`やファイルローカル変数など)は、`php-mode`がその値を`php-indent-offset`に引き継ぎ、警告を表示します。
+
+## 補完
+
+`php-complete.el`は、言語サーバーなしでも使える依存の軽い小さな`completion-at-point`関数(capf)をいくつか提供します。いずれも`M-x`コマンドとしても、[`cape`][cape]の`cape-capf-super`で合成する部品としても使えます。
+
+- `php-complete-complete-function` — 組み込み関数名。
+- `php-complete-complete-path` — `__DIR__ . '/...'`イディオムの中でパスを補完します。1階層ずつ辿り、起点は現在のファイルのディレクトリ(実行時に`__DIR__`が解決する先)です。
+
+名前付き関数から登録すると、あとから設定を変更しやすくなります。無名クロージャをフックに追加すると、削除や再定義が困難になります。
+
+```elisp
+(defun my-php-mode-setup-completion ()
+ "現在のバッファで `php-mode' の補完ソースを有効にする。"
+ (add-hook 'completion-at-point-functions
+ #'php-complete-complete-path nil t))
+
+(with-eval-after-load 'php-mode
+ (add-hook 'php-mode-hook #'my-php-mode-setup-completion))
+```
+
+`cape`で複数のオフラインソースを1つの super-capf に合成するには、同じ関数の中で`cape-capf-super`を登録します。
+
+```elisp
+(defun my-php-mode-setup-completion ()
+ "現在のバッファで `php-mode' の補完ソースを有効にする。"
+ (add-hook 'completion-at-point-functions
+ (cape-capf-super #'php-complete-complete-function
+ #'php-complete-complete-path)
+ nil t))
+```
+
+### 文脈依存の `.` キー
+
+`__DIR__`とパス補完を橋渡しする`. '/'`の挿入は、あえてcapfの仕事にはしていません。これは編集環境側に委ねます。そのための primitive が`php-dot-context`です。ポイントが文字列/コメント内か(`string-or-comment`)、文字列リテラルや`__DIR__`のようなマジック定数の直後か(`next-to-string`)、素のコードか(`code`)を返します。capf とこの primitive は「文字列」と「マジック定数」の定義を共有するため、キー挿入と補完の挙動が食い違いません。
+
+たとえば[smartchr][smartchr]では、`.`キーをコード中では`->` / `.` / `. `で循環させ、文字列内ではリテラルの`.`を挿入し、`__DIR__`の直後では`. `を優先(そのまま`php-complete-complete-path`につながる)といった設定が書けます。
+
+```elisp
+(defun my-php-smartchr-dot (code within-string next-to-string)
+ "`php-dot-context' を使って `.' キーの smartchr を作る。"
+ (let ((select (lambda ()
+ (pcase (php-dot-context)
+ ('string-or-comment within-string)
+ ('next-to-string next-to-string)
+ (_ code)))))
+ (smartchr-make-struct
+ :cleanup-fn (lambda () (delete-char (- (length (funcall select)))))
+ :insert-fn (lambda () (insert (funcall select))))))
+
+;; (smartchr (my-php-smartchr-dot "->" "." ". ")
+;; (my-php-smartchr-dot ". " ".." "..")
+;; "...")
+```
+
## HTMLとPHPが混在するファイルの編集
`php-mode`は純粋なPHPスクリプトのためのメジャーモードです。テンプレートのようにHTMLの中にPHPを埋め込んだファイルは、両方の言語を理解するメジャーモードで編集するほうが適しています。特にインデントは、HTML部分を素の`php-mode`で編集すると正しく動作しません。
@@ -107,6 +171,12 @@ M-x package-install php-mode
すでに`php-mode`でHTMLタグを含むファイルをインデントしようとすると、PHP Modeは警告し、`php-html-template-major-mode`への切り替えを尋ねます。このプロンプトを無効にするには`php-mode-warn-if-html-template`を`nil`に設定してください。
+## レガシーなCC Mode実装 (php-cc-mode)
+
+これまでのPHP ModeはEmacs組み込みのCC Modeの上に実装されていました。この実装は`php-cc-mode`として保存されており、凍結メンテナンス状態です。リグレッション修正のみ受け付け、新規の開発はCC Mode非依存の`php-mode`で行われます。レガシー実装でファイルを開くには`M-x php-cc-mode`を実行するか、自分で`auto-mode-alist`にエントリを追加してください。
+
+既存の設定が動き続けるように、`php-cc-mode`は自身の`php-cc-mode-hook`に加えて`php-mode-hook`も実行します。[CC Modeマニュアル][cc-mode-manual]に記載されている設定が適用されるのは`php-cc-mode`だけです。詳細は`lisp/php-cc-mode.el`冒頭のコメントをお読みください。
+
## 不具合を報告する
バグ報告の際には `M-x php-mode-debug` の出力を含めてください。この情報は問題の再現に役立ちます。
@@ -120,7 +190,7 @@ M-x package-install php-mode
PHP Modeは[GNU General Public License Version 3][gpl-v3] (GPLv3) でライセンスされています。
-このプロジェクトは1999年に[Turadg Aleahmad][@turadg]が書いた`php-mode.el`に起源を持ちます。2013年に[Daniel Hackney][@haxney]がEmacs組み込みのCC Modeをもとに書き直し始めました。PHPモードの改善に協力した貢献者のリストは[Authors]と[Contributors]に掲載されています。
+このプロジェクトは1999年に[Turadg Aleahmad][@turadg]が書いた`php-mode.el`に起源を持ちます。2013年に[Daniel Hackney][@haxney]がEmacs組み込みのCC Modeをもとに書き直し始めました。2026年にCC Modeへの依存を取り除くための書き直しが行われました。PHPモードの改善に協力した貢献者のリストは[Authors]と[Contributors]に掲載されています。
このプロジェクトは2017年まで[Eric James Michael Ritz][@ejmr]によりメンテナンスされていました。現在は[Friends of Emacs-PHP Development][@emacs-php]コミュニティが引き継いで開発しています。
@@ -151,7 +221,12 @@ PHP Modeは[GNU General Public License Version 3][gpl-v3] (GPLv3) でライセ
[Authors]: https://github.com/emacs-php/php-mode/wiki/Authors
[Contributors]: https://github.com/emacs-php/php-mode/graphs/contributors
[Supported Version]: https://github.com/emacs-php/php-mode/wiki/Supported-Version
+[cape]: https://github.com/minad/cape
+[cc-mode-manual]: https://www.gnu.org/software/emacs/manual/html_mono/ccmode.html
[gpl-v3]: https://www.gnu.org/licenses/gpl-3.0
+[per-cs]: https://www.php-fig.org/per/coding-style/
+[smartchr]: https://github.com/imakado/emacs-smartchr
+[#811]: https://github.com/emacs-php/php-mode/issues/811
[nongnu-elpa-badge]: https://elpa.nongnu.org/nongnu/php-mode.svg
[nongnu-elpa]: https://elpa.nongnu.org/nongnu/php-mode.html
[melpa-badge]: http://melpa.org/packages/php-mode-badge.svg
diff --git a/README.md b/README.md
index 94421fb8..00451445 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,9 @@ Please submit any bug reports or feature requests by creating issues on [the Git
> 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.
+> [!IMPORTANT]
+> PHP Mode has been rewritten and no longer depends on CC Mode. Indentation is now handled by an engine derived from `js.el`, which is bundled with GNU Emacs. The previous CC Mode based implementation is still available as `php-cc-mode`; see [Legacy CC Mode implementation](#legacy-cc-mode-implementation-php-cc-mode).
+
> [!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.
>
@@ -30,7 +33,7 @@ Please submit any bug reports or feature requests by creating issues on [the Git
## Installation
-**PHP Mode works with Emacs 27.1 or later.** For details on supported versions, see [Supported Version].
+**PHP Mode works with Emacs 28.1 or later** ([#811]). For details on supported versions, see [Supported Version].
On Emacs 28 or later, you can install it simply by running:
```
@@ -77,6 +80,67 @@ You can add project-specific settings by creating a `.dir-locals.el` or `.dir-lo
(php-project-coding-style . psr2)))
```
+### Coding Styles
+
+`php-mode` sets indentation and related editing variables according to `php-mode-coding-style`. The default style is `per` ([PER Coding Style 2.0][per-cs]); previous versions defaulted to `pear`. The available styles are `per`, `psr2`, `pear`, `drupal` and `wordpress`.
+
+The `symfony2` style has been removed, since PER supersedes the coding style used by modern Symfony. It remains available in `php-cc-mode`.
+
+Because the indentation engine no longer uses CC Mode, `c-basic-offset` is obsolete in `php-mode`; customize `php-indent-offset` instead. For backward compatibility, when a project sets `c-basic-offset` buffer-locally (for example via `.dir-locals.el` or file-local variables), `php-mode` copies that value into `php-indent-offset` and displays a warning.
+
+## Completion
+
+`php-complete.el` provides a few small, dependency-light `completion-at-point` functions (capfs) for use without a language server. Each is usable both as an `M-x` command and as a building block for [`cape`][cape]'s `cape-capf-super`:
+
+- `php-complete-complete-function` — built-in function names.
+- `php-complete-complete-path` — a filesystem path inside the `__DIR__ . '/...'` idiom, completed one component at a time and rooted at the directory of the current file (what `__DIR__` resolves to at runtime).
+
+Register them from a named function so the setup stays easy to change later; adding an anonymous closure to the hook makes it hard to remove or redefine.
+
+```elisp
+(defun my-php-mode-setup-completion ()
+ "Enable `php-mode' completion sources for the current buffer."
+ (add-hook 'completion-at-point-functions
+ #'php-complete-complete-path nil t))
+
+(with-eval-after-load 'php-mode
+ (add-hook 'php-mode-hook #'my-php-mode-setup-completion))
+```
+
+To compose several offline sources into one super-capf with `cape`, register `cape-capf-super` inside the same function instead:
+
+```elisp
+(defun my-php-mode-setup-completion ()
+ "Enable `php-mode' completion sources for the current buffer."
+ (add-hook 'completion-at-point-functions
+ (cape-capf-super #'php-complete-complete-function
+ #'php-complete-complete-path)
+ nil t))
+```
+
+### A context-sensitive `.` key
+
+Inserting the `. '/'` that bridges `__DIR__` into path completion is deliberately *not* the capf's job; it is left to your editing setup. `php-dot-context` is the primitive for that: it reports whether point is inside a string or comment (`string-or-comment`), directly after a string literal or a magic constant such as `__DIR__` (`next-to-string`), or in plain code (`code`). Because the capf and this primitive share the same notion of "string" and "magic constant", key-driven insertion and completion stay consistent.
+
+For example, with [smartchr][smartchr] the `.` key can cycle `->` / `.` / `. ` in code, insert a literal `.` inside strings, and prefer `. ` right after `__DIR__` (which then flows straight into `php-complete-complete-path`):
+
+```elisp
+(defun my-php-smartchr-dot (code within-string next-to-string)
+ "Build a smartchr for the `.' key using `php-dot-context'."
+ (let ((select (lambda ()
+ (pcase (php-dot-context)
+ ('string-or-comment within-string)
+ ('next-to-string next-to-string)
+ (_ code)))))
+ (smartchr-make-struct
+ :cleanup-fn (lambda () (delete-char (- (length (funcall select)))))
+ :insert-fn (lambda () (insert (funcall select))))))
+
+;; (smartchr (my-php-smartchr-dot "->" "." ". ")
+;; (my-php-smartchr-dot ". " ".." "..")
+;; "...")
+```
+
## Editing files that mix HTML and PHP
`php-mode` is designed for pure PHP scripts. Files that embed PHP inside HTML, such as templates, are better edited in a major mode that understands both languages. Indentation in particular is unreliable when the HTML part of a file is edited in plain `php-mode`.
@@ -109,6 +173,12 @@ Set `php-project-php-file-as-template` per project in `.dir-locals.el`:
If you are already in `php-mode` and indent a file that contains HTML tags, PHP Mode warns you and offers to switch to `php-html-template-major-mode`. Set `php-mode-warn-if-html-template` to `nil` to turn off this prompt.
+## Legacy CC Mode implementation (php-cc-mode)
+
+Until now, PHP Mode was implemented on top of Emacs' built-in CC Mode. That implementation is preserved as `php-cc-mode` and is in frozen maintenance: only regression fixes are accepted, and new development happens in the CC Mode independent `php-mode`. To open a file with the legacy implementation, run `M-x php-cc-mode` or add an entry to `auto-mode-alist` yourself.
+
+So that existing configurations keep working, `php-cc-mode` runs `php-mode-hook` in addition to its own `php-cc-mode-hook`. Settings described in [the CC Mode manual][cc-mode-manual] apply only to `php-cc-mode`; see the commentary at the top of `lisp/php-cc-mode.el` for details.
+
## Reporting Bugs
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.
@@ -121,7 +191,7 @@ Please see [CONTRIBUTING.md](CONTRIBUTING.md#english).
PHP Mode is licensed under [GNU General Public License Version 3][gpl-v3] (GPLv3).
-This project originated in `php-mode.el` written by [Turadg Aleahmad][@turadg] in 1999. In 2013 [Daniel Hackney][@haxney] began rewriting parts of PHP Mode in terms of Emacs' built-in CC Mode. Other contributors are listed in [Authors] and [Contributors].
+This project originated in `php-mode.el` written by [Turadg Aleahmad][@turadg] in 1999. In 2013 [Daniel Hackney][@haxney] began rewriting parts of PHP Mode in terms of Emacs' built-in CC Mode. In 2026 the mode was rewritten again to remove the CC Mode dependency. Other contributors are listed in [Authors] and [Contributors].
This project was maintained by [Eric James Michael Ritz][@ejmr] until 2017. Currently, the [Friends of Emacs-PHP Development][@emacs-php] community inherits PHP Mode.
@@ -152,7 +222,12 @@ This project was maintained by [Eric James Michael Ritz][@ejmr] until 2017. Curr
[Authors]: https://github.com/emacs-php/php-mode/wiki/Authors
[Contributors]: https://github.com/emacs-php/php-mode/graphs/contributors
[Supported Version]: https://github.com/emacs-php/php-mode/wiki/Supported-Version
+[cape]: https://github.com/minad/cape
+[cc-mode-manual]: https://www.gnu.org/software/emacs/manual/html_mono/ccmode.html
[gpl-v3]: https://www.gnu.org/licenses/gpl-3.0
+[smartchr]: https://github.com/imakado/emacs-smartchr
+[per-cs]: https://www.php-fig.org/per/coding-style/
+[#811]: https://github.com/emacs-php/php-mode/issues/811
[nongnu-elpa-badge]: https://elpa.nongnu.org/nongnu/php-mode.svg
[nongnu-elpa]: https://elpa.nongnu.org/nongnu/php-mode.html
[melpa-badge]: http://melpa.org/packages/php-mode-badge.svg
diff --git a/lisp/php-cc-mode.el b/lisp/php-cc-mode.el
new file mode 100644
index 00000000..dbf083a0
--- /dev/null
+++ b/lisp/php-cc-mode.el
@@ -0,0 +1,1620 @@
+;;; php-cc-mode.el --- CC Mode based major mode for editing PHP code -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2024 Friends of Emacs-PHP development
+;; Copyright (C) 1999, 2000, 2001, 2003, 2004 Turadg Aleahmad
+;; 2008 Aaron S. Hawley
+;; 2011, 2012, 2013, 2014, 2015, 2016, 2017 Eric James Michael Ritz
+
+;; Author: Eric James Michael Ritz
+;; Maintainer: USAMI Kenta
+;; URL: https://github.com/emacs-php/php-mode
+;; Keywords: languages php
+;; Version: 1.26.1
+;; Package-Requires: ((emacs "28.1"))
+;; License: GPL-3.0-or-later
+
+(eval-and-compile
+ (make-obsolete-variable
+ (defconst php-mode-version-number "1.26.1" "PHP Mode version number.")
+ "Please call (php-mode-version :as-number t) for compatibility." "1.24.2"))
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see .
+
+;;; Commentary:
+
+;; `php-cc-mode' is the legacy CC Mode based implementation of PHP Mode.
+;; It is kept for backward compatibility and is in frozen maintenance:
+;; only regression fixes are accepted here, new development happens in the
+;; cc-mode independent `php-mode' (lisp/php-mode.el).
+;;
+;; PHP Mode is a major mode for editing PHP script. It's an extension
+;; of CC mode; thus it inherits all C mode's navigation functionality.
+;; But it colors according to the PHP syntax and indents according to the
+;; PSR-2 coding guidelines. It also includes a couple handy IDE-type
+;; features such as documentation search and a source and class browser.
+;;
+;; For backward compatibility this mode runs `php-mode-hook' in addition
+;; to `php-cc-mode-hook' at the end of its initialization.
+
+;; Please read the manual for setting items compatible with CC Mode.
+;; https://www.gnu.org/software/emacs/manual/html_mono/ccmode.html
+
+;; This mode is designed for PHP scripts consisting of a single ")
+ (prefix "\\" "::")))
+
+(c-lang-defconst c-operators
+ php-cc `((prefix "new" "clone")
+ ,@(c-lang-const c-identifier-ops)
+ (postfix "->")
+ (postfix "++" "--" "[" "]" "(" ")")
+ (right-assoc "**")
+ (prefix "++" "--" "+" "-" "~" "(" ")" "@")
+ (prefix "instanceof")
+ (prefix "!")
+ (left-assoc "*" "/" "%")
+ (left-assoc "+" "-" ".")
+ (left-assoc "<<" ">>")
+ (left-assoc "<" ">" "<=" ">=")
+ (left-assoc "==" "!=" "===" "!==" "<>" "<=>")
+ (left-assoc "&")
+ (left-assoc "^")
+ (left-assoc "|")
+ (left-assoc "&&")
+ (left-assoc "||")
+ (right-assoc "??")
+ (left-assoc "?:")
+ (right-assoc-sequence "?" ":")
+ (right-assoc ,@(c-lang-const c-assignment-operators))
+ (left-assoc "and")
+ (left-assoc "xor")
+ (left-assoc "or")
+ (left-assoc ",")
+ (left-assoc "=>")))
+
+;; Allow '\' when scanning from open brace back to defining
+;; construct like class
+(c-lang-defconst c-block-prefix-disallowed-chars
+ php-cc (cl-set-difference (c-lang-const c-block-prefix-disallowed-chars)
+ '(?\\)))
+
+;; Allow $ so variables are recognized in cc-mode and remove @. This
+;; makes cc-mode highlight variables and their type hints in arglists.
+(c-lang-defconst c-symbol-start
+ php-cc (concat "[" c-alpha "_$]"))
+
+;; All string literals can possibly span multiple lines
+(c-lang-defconst c-multiline-string-start-char
+ php-cc t)
+
+(c-lang-defconst c-assignment-operators
+ php-cc '("=" "*=" "/=" "%=" "+=" "-=" ">>=" "<<=" "&=" "^=" "|=" ".=" "??="))
+
+(c-lang-defconst beginning-of-defun-function
+ php-cc 'php-cc-beginning-of-defun)
+
+(c-lang-defconst end-of-defun-function
+ php-cc 'php-cc-end-of-defun)
+
+(c-lang-defconst c-primitive-type-kwds
+ php-cc '("int" "integer" "bool" "boolean" "float" "double" "real"
+ "string" "object" "void" "mixed" "never"))
+
+(c-lang-defconst c-class-decl-kwds
+ "Keywords introducing declarations where the following block (if any)
+contains another declaration level that should be considered a class."
+ php-cc '("class" "trait" "interface" "enum"))
+
+(c-lang-defconst c-brace-list-decl-kwds
+ "Keywords introducing declarations where the following block (if
+any) is a brace list.
+
+PHP does not have an C-like \"enum\" keyword."
+ php-cc nil)
+
+;; cc-mode renamed `c-after-brace-list-decl-kwds' to `c-after-enum-list-kwds';
+;; keep the old name defined so cc-langs' transitional lookup stays quiet where
+;; the rename landed (see `c-after-enum-list-key' in cc-langs.el).
+(c-lang-defconst c-after-brace-list-decl-kwds
+ php-cc nil)
+
+(c-lang-defconst c-typeless-decl-kwds
+ php-cc (append (c-lang-const c-class-decl-kwds php-cc) '("function" "const")))
+
+(c-lang-defconst c-modifier-kwds
+ php-cc '("abstract" "final" "static" "case" "readonly"))
+
+(c-lang-defconst c-protection-kwds
+ "Access protection label keywords in classes."
+ php-cc '("private" "protected" "public"))
+
+(c-lang-defconst c-postfix-decl-spec-kwds
+ php-cc '("implements" "extends"))
+
+(c-lang-defconst c-type-list-kwds
+ php-cc '("@new" ;; @new is *NOT* language construct, it's workaround for coloring.
+ "new" "use" "implements" "extends" "namespace" "instanceof" "insteadof"))
+
+(c-lang-defconst c-ref-list-kwds
+ php-cc nil)
+
+(c-lang-defconst c-block-stmt-2-kwds
+ php-cc '("catch" "declare" "elseif" "for" "foreach" "if" "switch" "while"))
+
+(c-lang-defconst c-simple-stmt-kwds
+ php-cc '("break" "continue" "die" "echo" "exit" "goto" "return" "throw"
+ "include" "include_once" "print" "require" "require_once"))
+
+(c-lang-defconst c-constant-kwds
+ php-cc '("true" "false" "null"))
+
+(c-lang-defconst c-lambda-kwds
+ php-cc '("function" "use"))
+
+(c-lang-defconst c-inexpr-block-kwds
+ php-cc '("match"))
+
+(c-lang-defconst c-other-block-decl-kwds
+ php-cc '("namespace"))
+
+(c-lang-defconst c-other-kwds
+ "Keywords not accounted for by any other `*-kwds' language constant."
+ php-cc
+ '("__halt_compiler"
+ "and"
+ "array"
+ "as"
+ "break"
+ "catch"
+ "clone"
+ "default"
+ "empty"
+ "enddeclare"
+ "endfor"
+ "endforeach"
+ "endif"
+ "endswitch"
+ "endwhile"
+ "eval"
+ "fn" ;; NOT c-lambda-kwds
+ "global"
+ "isset"
+ "list"
+ "or"
+ "parent"
+ "static"
+ "unset"
+ "var"
+ "xor"
+ "yield"
+ "yield from"
+
+ ;; Below keywords are technically not reserved keywords, but
+ ;; threated no differently by php-mode from actual reserved
+ ;; keywords
+ ;;
+ ;;; declare directives:
+ "encoding"
+ "ticks"
+ "strict_types"
+
+ ;;; self for static references:
+ "self"))
+
+;; PHP does not have <> templates/generics
+(c-lang-defconst c-recognize-<>-arglists
+ php-cc nil)
+
+(c-lang-defconst c-<>-type-kwds
+ php-cc nil)
+
+(c-lang-defconst c-inside-<>-type-kwds
+ php-cc nil)
+
+(c-lang-defconst c-enums-contain-decls
+ php-cc nil)
+
+(c-lang-defconst c-nonlabel-token-key
+ "Regexp matching things that can't occur in generic colon labels.
+
+This overrides cc-mode `c-nonlabel-token-key' to support switching on
+double quoted strings and true/false/null.
+
+Note: this regexp is also applied to goto-labels, a future improvement
+might be to handle switch and goto labels differently."
+ php-cc (concat
+ ;; All keywords except `c-label-kwds' and `c-constant-kwds'.
+ (c-make-keywords-re t
+ (cl-set-difference (c-lang-const c-keywords)
+ (append (c-lang-const c-label-kwds)
+ (c-lang-const c-constant-kwds))
+ :test 'string-equal))))
+
+(c-lang-defconst c-basic-matchers-before
+ php-cc (cl-remove-if (lambda (elm) (and (listp elm) (equal (car elm) "\\s|")))
+ (c-lang-const c-basic-matchers-before php-cc)))
+
+(c-lang-defconst c-basic-matchers-after
+ php-cc (cl-remove-if (lambda (elm) (and (listp elm) (memq 'c-annotation-face elm)))
+ (c-lang-const c-basic-matchers-after php-cc)))
+
+(defconst php-mode--re-return-typed-closure
+ (eval-when-compile
+ (rx symbol-start "function" symbol-end
+ (* (syntax whitespace))
+ "(" (* (not (any "("))) ")"
+ (* (syntax whitespace))
+ (? symbol-start "use" symbol-end
+ (* (syntax whitespace))
+ "(" (* (not (any "("))) ")"
+ (* (syntax whitespace)))
+ ":" (+ (not (any "{}")))
+ (group "{"))))
+
+(defun php-c-lineup-arglist (langelem)
+ "Line up the current argument line under the first argument using
+`c-lineup-arglist' LANGELEM."
+ (let (in-return-typed-closure)
+ (when (and (consp langelem)
+ (eq 'arglist-cont-nonempty (car langelem)))
+ (save-excursion
+ (save-match-data
+ (when (re-search-backward php-mode--re-return-typed-closure (cdr langelem) t)
+ (goto-char (match-beginning 1))
+ (when (not (php-in-string-or-comment-p))
+ (setq in-return-typed-closure t))))))
+ (unless in-return-typed-closure
+ (c-lineup-arglist langelem))))
+
+(defun php-lineup-cascaded-calls (langelem)
+ "Line up chained methods using `c-lineup-cascaded-calls',
+but only if the setting is enabled."
+ (cond
+ (php-cc-mode-lineup-cascaded-calls (c-lineup-cascaded-calls langelem))
+ ((assq 'arglist-cont-nonempty c-syntactic-context) nil)
+ ((assq 'defun-block-intro c-syntactic-context) nil)
+ ((assq 'defun-close c-syntactic-context) nil)
+ ((assq 'statement-cont c-syntactic-context) nil)
+ ((save-excursion
+ (beginning-of-line)
+ (let ((beginning-of-langelem (cdr langelem))
+ (beginning-of-current-line (point))
+ start)
+ (skip-chars-forward " ")
+ (cond
+ ((looking-at-p "->") '+)
+ ((looking-at-p "[:?]") '+)
+ ((looking-at-p "[,;]") nil)
+ ((looking-at-p "//") nil)
+ ;; Is the previous line terminated with `,' ?
+ ((progn
+ (forward-line -1)
+ (end-of-line)
+ (skip-chars-backward " ")
+ (backward-char 1)
+ (while (and (< beginning-of-langelem (point))
+ (setq start (php-in-string-or-comment-p)))
+ (goto-char start)
+ (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 ","))
+ (save-excursion
+ (backward-char) (not (looking-at-p ",")))))
+ '+)
+ (t nil)))))))
+
+(defun php-c-looking-at-or-maybe-in-bracelist (orig-fun &optional containing-sexp lim &rest args)
+ "Replace `c-looking-at-or-maybe-in-bracelist'.
+
+CONTAINING-SEXP is the position of the brace/paren/bracket enclosing
+POINT, or nil if there is no such position, or we do not know it. LIM is
+a backward search limit."
+ (cond
+ ((not (derived-mode-p 'php-cc-mode)) (apply orig-fun containing-sexp lim args))
+ ((looking-at-p "{")
+ (save-excursion
+ (c-backward-token-2 2 t lim)
+ ;; PHP 8.0 match expression
+ ;; echo match ($var) |{
+ ;; ↑ matches ↑ initial position
+ (when (looking-at-p (eval-when-compile (rx symbol-start "match" symbol-end)))
+ (cons (point) t))))
+ (t nil)))
+
+(defun php-mode--anonymous-function-brace-p (pos)
+ "Return non-nil when the opening brace at POS starts a closure body.
+
+POS must be the position of a `{'. A closure is an anonymous
+`function (...) [use (...)] [: type] {' expression; named function
+declarations and other constructs (e.g. `match', `if') return nil."
+ (save-excursion
+ (goto-char pos)
+ (c-backward-syntactic-ws)
+ ;; Step back over an optional return type declaration `): Type {' so
+ ;; that point lands just after the parameter (or `use') list's `)'.
+ (unless (eq (char-before) ?\))
+ (let ((colon (save-excursion
+ (skip-chars-backward "^):;{}(" (point-min))
+ (and (eq (char-before) ?:) (1- (point))))))
+ (when colon
+ (goto-char colon)
+ (c-backward-syntactic-ws))))
+ ;; Walk backwards over the `)...(' list(s): the parameter list, plus an
+ ;; optional `use (...)' clause, until we reach `function' or give up.
+ (let ((result nil) (continue t))
+ (while (and continue (eq (char-before) ?\)))
+ (condition-case nil
+ (backward-list)
+ (error (setq continue nil)))
+ (when continue
+ (c-backward-syntactic-ws)
+ (if (zerop (c-backward-token-2))
+ (cond
+ ((looking-at-p "\\_") (setq result t continue nil))
+ ((looking-at-p "\\_