|
1 | 1 | # PHPantomLSP |
2 | 2 |
|
3 | | -A fast, lightweight PHP language server that stays out of your way. Using only a few MB of RAM regardless of project size and fully usable in milliseconds without requiring high-end hardware. |
| 3 | +A fast, lightweight PHP language server written in Rust. Uses only a few MB of RAM regardless of project size and is fully responsive in milliseconds — no indexing phase, no background workers, no waiting. |
4 | 4 |
|
5 | | -> **Note:** This project is in active development. |
| 5 | +> [!NOTE] |
| 6 | +> PHPantomLSP is in active development. Completion and go-to-definition are solid and used daily. More LSP features (hover, signature help, find references) are on the roadmap. |
| 7 | +
|
| 8 | +<img width="683" height="339" alt="Completion demo" src="https://github.com/user-attachments/assets/65e8220d-5d94-466f-aea7-2f239a8d4b19" /> |
6 | 9 |
|
7 | 10 | ## Features |
8 | 11 |
|
9 | | -### Completion |
10 | | - |
11 | | -- **Instance members** via `->` — methods, properties, constructor-promoted properties |
12 | | -- **Static members** via `::` — static methods, static properties, constants, enum cases |
13 | | -- **`parent::`** — inherited and overridden members (excludes private) |
14 | | -- **Traits and interfaces** — trait members appear on the using class; interface contracts are resolved |
15 | | -- **`@mixin` classes** — members from `@mixin` annotations are included |
16 | | -- **Magic members** — `@property`, `@property-read`, `@method` from PHPDoc |
17 | | -- **Method chaining** — return types are followed through arbitrarily long chains |
18 | | -- **Null-safe chaining** — `?->` is handled identically to `->` |
19 | | -- **Full signatures** in completion labels (parameters, types, return type) |
20 | | -- Magic methods (`__construct`, `__call`, etc.) are only suggested interally |
21 | | -- **Named argument completion** — when typing inside call parentheses, parameter names are suggested with a trailing `:`. |
22 | | -- **PHPDoc tag completion** — context-aware suggestions inside `/** … */` blocks. |
23 | | -- **Variable name completion** — typing `$` suggests variables that are in scope. |
24 | | -- **Class name completion** — unqualified and partially-qualified class names are completed from the current file |
25 | | -- **Function completion** — standalone functions are completed |
26 | | -- **Constant completion** — constants defined via `define()` are offered alongside class names |
27 | | -- **Deprecated detection** — members, classes, and functions marked with `@deprecated` in their PHPDoc are shown with strikethrough in the completion list |
28 | | - |
29 | | -<img width="683" height="339" alt="image" src="https://github.com/user-attachments/assets/65e8220d-5d94-466f-aea7-2f239a8d4b19" /> |
| 12 | +### Code Completion |
| 13 | + |
| 14 | +Full completion for PHP class members, functions, variables, and more: |
| 15 | + |
| 16 | +- **Instance and static members** — methods, properties, constants, and enum cases via `->`, `?->`, and `::` |
| 17 | +- **Inheritance-aware** — parent classes, traits, interfaces, and `@mixin` classes are fully resolved with correct precedence |
| 18 | +- **Magic members** — `@property`, `@property-read`, and `@method` from PHPDoc |
| 19 | +- **Method chaining** — follows return types through arbitrarily long `->` and `?->` chains |
| 20 | +- **Callable snippets** — methods and functions insert with parameter tab-stops |
| 21 | +- **Named arguments** — parameter names are suggested inside call parentheses |
| 22 | +- **Variable completion** — `$` triggers scope-aware variable suggestions |
| 23 | +- **Class and function completion** — with automatic `use` statement insertion |
| 24 | +- **Type hint completion** — parameter types, return types, and property types offer scalars and class names |
| 25 | +- **PHPDoc completion** — context-aware tag suggestions with smart `@throws` (detects uncaught exceptions) and `@param` pre-fill |
| 26 | +- **Array shape keys** — from annotations, literal array inference, and `$_SERVER` superglobals |
| 27 | +- **Object shape properties** — `object{name: string, age: int}` resolves to property completions |
| 28 | +- **`new` filtering** — interfaces, traits, and abstract classes are hidden or demoted |
| 29 | +- **Deprecated detection** — `@deprecated` members show with strikethrough |
30 | 30 |
|
31 | 31 | ### Go to Definition |
32 | 32 |
|
33 | | -- **Classes, interfaces, traits, enums** — same-file and cross-file |
34 | | -- **Methods, properties, constants** — resolves through inheritance, traits, and mixins |
35 | | -- **Standalone functions** — including PHP built-ins via embedded stubs |
36 | | -- **Variables** — jumps to the most recent assignment or declaration (assignment, parameter, `foreach`, `catch`, `static`/`global`) |
37 | | -- **Property type definition** — when the cursor is on a property or parameter at its declaration site, jumps to the class definition of its type hint |
38 | | -- **Namespace resolution** — fully-qualified, partially-qualified, and aliased names via `use ... as ...` |
| 33 | +- **Classes, interfaces, traits, enums** — same-file and cross-file via PSR-4 |
| 34 | +- **Members** — methods, properties, and constants resolved through the full inheritance chain |
| 35 | +- **Variables** — jumps to the assignment, parameter, `foreach`, `catch`, or other declaration site |
| 36 | +- **Type hints and docblock types** — click a class name in a parameter type, return type, `@param`, `@return`, `@throws`, etc. |
| 37 | +- **Functions and constants** — including PHP built-ins via embedded stubs |
39 | 38 |
|
40 | 39 | ### Type Resolution |
41 | 40 |
|
42 | | -PHPantom infers variable types from assignments, parameter hints, return types, and PHPDoc annotations, then uses them to power both completion and go-to-definition. |
43 | | - |
44 | | -- **Union types** (`A|B`) and **intersection types** (`A&B`), including PHP 8.2 DNF types like `(A&B)|C` |
45 | | -- **Conditional return types** — PHPStan-style `@return ($param is class-string<T> ? T : mixed)`, used by patterns like `app(User::class)->getEmail()` |
46 | | -- **`@var` overrides** — inline `/** @var Type $var */` docblocks refine variable types |
47 | | -- **Ambiguous variables** — when a variable is assigned different types in conditional branches, all candidates are offered |
| 41 | +PHPantomLSP infers variable types from assignments, type hints, return types, and PHPDoc — then uses them to power completion and go-to-definition. |
| 42 | + |
| 43 | +- **Union, intersection, and DNF types** — `A|B`, `A&B`, `(A&B)|C` |
| 44 | +- **Generics** — class-level `@template` with substitution through inheritance (`@extends Base<User>`) |
| 45 | +- **Conditional return types** — `@return ($param is class-string<T> ? T : mixed)` |
| 46 | +- **Type aliases** — `@phpstan-type` and `@phpstan-import-type` with full resolution |
| 47 | +- **Array shapes** — from annotations and inferred from literal arrays and incremental `$data['key'] = ...` assignments |
| 48 | +- **Object shapes** — `object{foo: int, bar: string}` with nested chaining |
| 49 | +- **Generators** — `Generator<TKey, TValue, TSend, TReturn>` yield type extraction |
| 50 | +- **Array functions** — `array_filter`, `array_map`, `array_pop`, etc. preserve element types |
| 51 | +- **Foreach** — resolves value and key types from annotations, generic collections, method calls, and property access |
| 52 | +- **Destructuring** — `['key' => $var] = $data` maps named keys to array shape entry types |
| 53 | +- **Clone, catch, list inference** — `clone $x`, `catch (E $e)`, and `$arr[] = new Foo()` all resolve correctly |
| 54 | +- **Multi-line docblocks** — broken `@return` types spanning multiple lines are joined and recovered |
48 | 55 |
|
49 | 56 | ### Type Narrowing |
50 | 57 |
|
51 | | -Completion results adapt to runtime type checks. PHPantomLSP narrows union types in both the positive and inverse branches of `if`/`else`, `while`, and `match(true)`: |
| 58 | +Completion adapts to runtime type checks in `if`/`else`, `while`, ternaries, and `match(true)`: |
52 | 59 |
|
53 | | -- `instanceof` and negated `!instanceof` |
54 | | -- `is_a($var, ClassName::class)` |
55 | | -- `get_class($var) === ClassName::class` and `$var::class === ClassName::class` (including `!==` and reversed operand order) |
| 60 | +- `instanceof` and `!instanceof` |
| 61 | +- `is_a()`, `get_class() ===`, `$var::class ===` |
56 | 62 | - `assert($var instanceof ClassName)` |
57 | | -- **Custom assertion functions** via `@phpstan-assert` / `@psalm-assert` annotations: |
58 | | - - `@phpstan-assert Type $param` — unconditional narrowing after the call |
59 | | - - `@phpstan-assert-if-true Type $param` — narrows in the then-branch |
60 | | - - `@phpstan-assert-if-false Type $param` — narrows in the else-branch |
61 | | - |
62 | | -### Composer & Project Awareness |
63 | | - |
64 | | -- Parses `composer.json` for PSR-4, classmap, and file autoload mappings |
65 | | -- Resolves cross-file class lookups on demand |
66 | | -- Discovers classes and functions from `require_once` files |
67 | | - |
68 | | -> **Note:** |
69 | | -> - Run `composer install -o` (or `composer dump-autoload -o`) to generate autoload files needed for full class completion. |
70 | | -> - If your project doesn’t use Composer, you can create a minimal `composer.json` to generate a classmap: |
71 | | -> |
72 | | -> ```json |
73 | | -> { |
74 | | -> "autoload": { |
75 | | -> "classmap": ["src/"] |
76 | | -> } |
77 | | -> } |
78 | | -> ``` |
79 | | -> |
80 | | -> Then run: |
81 | | -> ```bash |
82 | | -> composer dump-autoload -o |
83 | | -> ``` |
84 | | -
|
85 | | -## Building |
86 | | -
|
87 | | -The PHP stubs are managed as a Composer dependency in `stubs/`. Install them before building: |
88 | | -
|
89 | | -```bash |
90 | | -# Install the PHP stubs (requires Composer) |
91 | | -composer install |
92 | | -
|
93 | | -# Build |
94 | | -cargo build |
95 | | -
|
96 | | -# or for a release build |
97 | | -cargo build --release |
98 | | -``` |
99 | | -
|
100 | | -> **Note:** The build will succeed without `composer install`, but the resulting binary won't know about built-in PHP symbols like `Iterator`, `Countable`, `UnitEnum`, etc. Always run `composer install` first for a fully functional build. |
| 63 | +- **Guard clauses** — `if (!$var instanceof Foo) { return; }` narrows subsequent code |
| 64 | +- **Custom assertions** — `@phpstan-assert`, `@phpstan-assert-if-true`, `@phpstan-assert-if-false` |
101 | 65 |
|
102 | | -After updating stubs (`composer update`), just rebuild — the `build.rs` script watches `composer.lock` and re-embeds everything automatically. |
| 66 | +## Project Awareness |
103 | 67 |
|
104 | | -For more details on how symbol resolution and stub loading work, see [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md). |
| 68 | +PHPantomLSP understands Composer projects out of the box: |
105 | 69 |
|
106 | | -## Testing |
| 70 | +- **PSR-4 autoloading** — resolves classes across files on demand |
| 71 | +- **Classmap and file autoloading** — `autoload_classmap.php` and `autoload_files.php` |
| 72 | +- **Embedded PHP stubs** — ~1,450 built-in classes, ~5,000 functions, and ~2,000 constants from [phpstorm-stubs](https://github.com/JetBrains/phpstorm-stubs) are bundled in the binary. No runtime downloads needed. |
| 73 | +- **`require_once` discovery** — functions from required files are available for completion |
107 | 74 |
|
108 | | -Run the test suite: |
109 | | - |
110 | | -```bash |
111 | | -cargo test |
112 | | -``` |
113 | | - |
114 | | -### Manual LSP Testing |
115 | | - |
116 | | -The included `test_lsp.sh` script sends JSON-RPC messages to the server over stdin/stdout, exercising the full LSP protocol flow (initialize, open file, hover, completion, shutdown): |
117 | | - |
118 | | -```bash |
119 | | -./test_lsp.sh |
120 | | -``` |
121 | | - |
122 | | -This is useful for verifying end-to-end behavior outside of an editor. |
123 | | - |
124 | | -### Debugging |
125 | | - |
126 | | -Enable logging by setting the `RUST_LOG` environment variable: |
127 | | - |
128 | | -```bash |
129 | | -RUST_LOG=debug cargo run 2>phpantom.log |
130 | | -``` |
131 | | - |
132 | | -Logs are written to stderr, so redirect as needed. |
| 75 | +> [!IMPORTANT] |
| 76 | +> Run `composer install -o` (or `composer dump-autoload -o`) in your project to generate the optimized autoload files PHPantomLSP needs for cross-file class resolution. |
| 77 | +> |
| 78 | +> If your project doesn't use Composer, you can create a minimal `composer.json`: |
| 79 | +> ```json |
| 80 | +> { "autoload": { "classmap": ["src/"] } } |
| 81 | +> ``` |
| 82 | +> Then run `composer dump-autoload -o`. |
133 | 83 |
|
134 | | -## Editor Integration |
| 84 | +## Getting Started |
135 | 85 |
|
136 | | -PHPantomLSP communicates over stdin/stdout. Point your editor's LSP client at the binary: |
| 86 | +See **[docs/SETUP.md](docs/SETUP.md)** for editor-specific installation instructions (Zed, Neovim, and other editors). |
137 | 87 |
|
138 | | -- **Path:** `target/release/phpantom_lsp` (after `cargo build --release`) |
| 88 | +## Building from Source |
139 | 89 |
|
140 | | -### Zed |
| 90 | +See **[docs/BUILDING.md](docs/BUILDING.md)** for build, test, and debug instructions. |
141 | 91 |
|
142 | | -Install it as a dev extension from the `zed-extension/` directory in this repo: |
| 92 | +## Contributing |
143 | 93 |
|
144 | | -1. Open Zed |
145 | | -2. Open the Extensions panel |
146 | | -3. Click **Install Dev Extension** |
147 | | -4. Select the `zed-extension/` directory |
| 94 | +See **[docs/CONTRIBUTING.md](docs/CONTRIBUTING.md)**. |
148 | 95 |
|
149 | | -The extension automatically downloads the correct pre-built binary from GitHub releases for your platform. If you'd prefer to use a locally built binary, ensure `phpantom_lsp` is on your `PATH` and the extension will use it instead. |
| 96 | +## Architecture |
150 | 97 |
|
151 | | -To configure PHPantom LSP as the default PHP language server in Zed, add the following to your Zed settings (`settings.json`): |
| 98 | +For details on how symbol resolution, stub loading, and inheritance merging work, see **[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)**. |
152 | 99 |
|
153 | | -```json |
154 | | -{ |
155 | | - "languages": { |
156 | | - "PHP": { |
157 | | - "language_servers": ["phpantom_lsp", "!intelephense", "!phpactor", "!phptools", "..."] |
158 | | - } |
159 | | - } |
160 | | -} |
161 | | -``` |
| 100 | +## Acknowledgements |
162 | 101 |
|
163 | | -## Contributing |
| 102 | +PHPantomLSP stands on the shoulders of: |
164 | 103 |
|
165 | | -See [docs/CONTRIBUTING.md](docs/CONTRIBUTING.md). |
| 104 | +- **[Mago](https://github.com/carthage-software/mago)** — the PHP parser that powers all of PHPantomLSP's AST analysis |
| 105 | +- **[PHPStan](https://phpstan.org/)** and **[Psalm](https://psalm.dev/)** — whose combined work on static analysis for PHP transformed the language's type ecosystem. Generics, array shapes, conditional return types, assertion annotations — these tools pushed each other forward and pushed the community toward rigorous PHPDoc annotations that make a language server like this possible. PHPantomLSP's author cut his teeth on PHPStan, which is why `@phpstan-*` annotations are a first-class citizen here. |
| 106 | +- **[JetBrains phpstorm-stubs](https://github.com/JetBrains/phpstorm-stubs)** — type information for the entire PHP standard library, embedded directly into the binary |
166 | 107 |
|
167 | 108 | ## License |
168 | 109 |
|
169 | | -MIT - see [LICENSE](LICENSE). |
| 110 | +MIT — see [LICENSE](LICENSE). |
0 commit comments