Skip to content

Commit a89779d

Browse files
committed
Update readme and related docs
1 parent f70231e commit a89779d

4 files changed

Lines changed: 256 additions & 162 deletions

File tree

README.md

Lines changed: 66 additions & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -1,169 +1,86 @@
1-
# PHPantomLSP
1+
# PHPantom
22

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.
44

5-
> **Note:** This project is in active development.
5+
> [!NOTE]
6+
> PHPantom 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.
67
78
## Features
89

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" />
30-
31-
### Go to Definition
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 ...`
39-
40-
### Type Resolution
41-
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
48-
49-
### Type Narrowing
50-
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)`:
52-
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)
56-
- `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:
10+
PHPantom focuses on completion and go-to-definition and aims to do them really well. Here's where it stands:
11+
12+
| | PHPantom | Intelephense | PHP Tools | Phpactor | PHPStorm |
13+
|---|---|---|---|---|---|
14+
| Completion ||||||
15+
| Go-to-definition ||||||
16+
| `@mixin` completion || 💰 ||||
17+
| `@phpstan-assert` narrowing ||||| ⚠️ partial |
18+
| Conditional return types ||||||
19+
| Array shape inference ||||||
20+
| Object shape completion ||||||
21+
| `@phpstan-type` aliases ||||||
22+
| Hover ||||||
23+
| Signature help ||||||
24+
| Find references ||||||
25+
| Diagnostics |||| ⚠️ limited ||
26+
| Rename / refactoring || 💰 ||||
27+
| Time to ready | **10 ms** | 1 min 25 s | 3 min 17 s | 15 min 39 s | 19 min 38 s |
28+
| RAM usage | **7 MB** | 520 MB | 3.9 GB | 498 MB | 2.0 GB |
29+
| Disk cache | **0** | 45 MB | 0 | 4.1 GB | 551 MB |
30+
31+
<sub>Performance measured on a production codebase: 21K PHP files, 1.5M lines of code (vendor + application).</sub>
32+
33+
## Context-Aware Intelligence
34+
35+
- **Smart PHPDoc completion.** `@throws` detects uncaught exceptions in the method body, including those propagated from called methods. `@param` pre-fills with the name and type from the signature. Tags are filtered to context: `@var` only in property docblocks, `@param` only when there are undocumented parameters. Already-documented tags aren't suggested again.
36+
- **Array shape inference from code.** `$config = ['host' => 'localhost', 'port' => 3306]` offers key completion with no annotation. Incremental `$config['key'] = ...` assignments extend the shape. Nested access chains resolve through shapes and generics. `array_filter`, `array_map`, `array_pop`, `current`, etc. preserve the element type instead of losing it to `array`.
37+
- **Guard clause stacking.** Early return narrows subsequent code. Multiple guards stack to whittle a union down. Works in ternaries, `match(true)`, with `is_a()`, `assert()`.
38+
- **Generic collection foreach.** Iterating `Collection<User>`, `Generator<int, Item>`, or a class with `@implements IteratorAggregate<int, User>` resolves the loop variable to the element type. Keys too.
39+
- **Generics.** Class-level `@template` with substitution through inheritance (`@extends Base<User>`).
40+
- **Everything else you'd expect.** `foreach`, `clone`, `$arr[] = new Foo()`, destructuring with named keys, chained method calls in assignments.
41+
42+
## Project Awareness
43+
44+
PHPantom understands Composer projects out of the box:
45+
46+
- **PSR-4 autoloading.** Resolves classes across files on demand.
47+
- **Classmap and file autoloading.** `autoload_classmap.php` and `autoload_files.php`.
48+
- **Embedded PHP stubs** from [phpstorm-stubs](https://github.com/JetBrains/phpstorm-stubs) bundled in the binary, no runtime downloads needed.
49+
- **`require_once` discovery.** Functions from required files are available for completion.
50+
51+
> [!IMPORTANT]
52+
> Run `composer install -o` (or `composer dump-autoload -o`) in your project to generate the optimized autoload files PHPantom needs for cross-file class resolution.
7153
>
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.
101-
102-
After updating stubs (`composer update`), just rebuild — the `build.rs` script watches `composer.lock` and re-embeds everything automatically.
103-
104-
For more details on how symbol resolution and stub loading work, see [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
105-
106-
## Testing
107-
108-
Run the test suite:
54+
> If your project doesn't use Composer, you can create a minimal `composer.json`:
55+
> ```json
56+
> { "autoload": { "classmap": ["src/"] } }
57+
> ```
58+
> Then run `composer dump-autoload -o`.
10959
110-
```bash
111-
cargo test
112-
```
60+
## Getting Started
11361
114-
### Manual LSP Testing
62+
See **[docs/SETUP.md](docs/SETUP.md)** for editor-specific installation instructions (Zed, Neovim, and other editors).
11563
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):
64+
## Building from Source
11765
118-
```bash
119-
./test_lsp.sh
120-
```
66+
See **[docs/BUILDING.md](docs/BUILDING.md)** for build, test, and debug instructions.
12167
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.
133-
134-
## Editor Integration
135-
136-
PHPantomLSP communicates over stdin/stdout. Point your editor's LSP client at the binary:
137-
138-
- **Path:** `target/release/phpantom_lsp` (after `cargo build --release`)
139-
140-
### Zed
141-
142-
Install it as a dev extension from the `zed-extension/` directory in this repo:
68+
## Contributing
14369
144-
1. Open Zed
145-
2. Open the Extensions panel
146-
3. Click **Install Dev Extension**
147-
4. Select the `zed-extension/` directory
70+
See **[docs/CONTRIBUTING.md](docs/CONTRIBUTING.md)**.
14871
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.
72+
## Architecture
15073
151-
To configure PHPantom LSP as the default PHP language server in Zed, add the following to your Zed settings (`settings.json`):
74+
For details on how symbol resolution, stub loading, and inheritance merging work, see **[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)**.
15275
153-
```json
154-
{
155-
"languages": {
156-
"PHP": {
157-
"language_servers": ["phpantom_lsp", "!intelephense", "!phpactor", "!phptools", "..."]
158-
}
159-
}
160-
}
161-
```
76+
## Acknowledgements
16277
163-
## Contributing
78+
PHPantom stands on the shoulders of:
16479
165-
See [docs/CONTRIBUTING.md](docs/CONTRIBUTING.md).
80+
- **[Mago](https://github.com/carthage-software/mago):** the PHP parser that powers all of PHPantom's AST analysis.
81+
- **[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. PHPantom's author cut his teeth on PHPStan, which is why `@phpstan-*` annotations are a first-class citizen here.
82+
- **[JetBrains phpstorm-stubs](https://github.com/JetBrains/phpstorm-stubs):** type information for the entire PHP standard library, embedded directly into the binary.
16683
16784
## License
16885
169-
MIT - see [LICENSE](LICENSE).
86+
MIT. See [LICENSE](LICENSE).

docs/BUILDING.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Building & Development
2+
3+
## Quick Start
4+
5+
```bash
6+
composer install # fetch PHP stubs (requires Composer)
7+
cargo build --release # build the binary
8+
```
9+
10+
## Prerequisites
11+
12+
- [Rust](https://rustup.rs/) (stable toolchain)
13+
- [Composer](https://getcomposer.org/) (for PHP standard library stubs)
14+
15+
## Build
16+
17+
The PHP stubs are managed as a Composer dependency in `stubs/`. The `build.rs` script embeds the [JetBrains phpstorm-stubs](https://github.com/JetBrains/phpstorm-stubs) directly into the binary, giving the LSP full knowledge of built-in PHP classes, functions, and constants with no runtime dependencies.
18+
19+
> [!NOTE]
20+
> 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.
21+
22+
After updating stubs (`composer update`), just rebuild. `build.rs` watches `composer.lock` and re-embeds everything automatically.
23+
24+
For details on how symbol resolution and stub loading work, see [ARCHITECTURE.md](ARCHITECTURE.md).
25+
26+
## Testing
27+
28+
Run the full test suite:
29+
30+
```bash
31+
cargo test
32+
```
33+
34+
### CI Checks
35+
36+
Before submitting changes, run exactly what CI runs:
37+
38+
```bash
39+
cargo test
40+
cargo clippy -- -D warnings
41+
cargo clippy --tests -- -D warnings
42+
cargo fmt --check
43+
php -l example.php
44+
```
45+
46+
All five must pass with zero warnings and zero failures.
47+
48+
### Manual LSP Testing
49+
50+
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):
51+
52+
```bash
53+
./test_lsp.sh
54+
```
55+
56+
This is useful for verifying end-to-end behavior outside of an editor.
57+
58+
## Debugging
59+
60+
Enable logging by setting the `RUST_LOG` environment variable:
61+
62+
```bash
63+
RUST_LOG=debug cargo run 2>phpantom.log
64+
```
65+
66+
Logs are written to stderr, so redirect as needed.
67+
68+
For editor setup instructions, see [SETUP.md](SETUP.md).

docs/CONTRIBUTING.md

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,37 +5,43 @@ Thanks for your interest in contributing!
55
## Getting Started
66

77
1. Fork and clone the repository
8-
2. Install Rust via [rustup](https://rustup.rs/)
9-
3. Install [Composer](https://getcomposer.org/) (PHP dependency manager)
10-
4. Run `composer install` to fetch the PHP standard library stubs
11-
5. Run `cargo build` to verify everything compiles
12-
13-
> **Note:** The `composer install` step downloads [JetBrains phpstorm-stubs](https://github.com/JetBrains/phpstorm-stubs) into `stubs/`, which are then embedded into the binary at compile time. Without this step the build will succeed, but the LSP won't know about built-in PHP symbols like `Iterator`, `Countable`, `UnitEnum`, etc.
8+
2. Follow the [build instructions](BUILDING.md) to get a working development environment
9+
3. Read [ARCHITECTURE.md](ARCHITECTURE.md) for an overview of how the codebase is structured
1410

1511
## Before Submitting a PR
1612

17-
Please make sure all checks pass:
13+
All five CI checks must pass with zero warnings and zero failures:
1814

1915
```bash
2016
cargo test
21-
cargo clippy
17+
cargo clippy -- -D warnings
18+
cargo clippy --tests -- -D warnings
2219
cargo fmt --check
20+
php -l example.php
2321
```
2422

23+
Note that clippy runs twice, once for library code and once including test code.
24+
2525
## Code Style
2626

2727
- Run `cargo fmt` before committing
28-
- Address any `cargo clippy` warnings
29-
- Add tests for new functionality in `tests/integration_tests.rs`
28+
- Fix clippy warnings rather than suppressing them. Avoid `#[allow(clippy::...)]` unless truly necessary.
29+
- Add `///` doc comments to all public functions and struct fields
3030

3131
## Testing
3232

33-
- Unit/integration tests: `cargo test`
34-
- Manual LSP testing: `./test_lsp.sh` (sends JSON-RPC messages over stdin/stdout)
33+
- Integration tests go in `tests/completion_*.rs` or `tests/definition_*.rs`, one file per feature area
34+
- Use `create_test_backend()` from `tests/common/mod.rs` for same-file tests
35+
- Use `create_psr4_workspace()` for cross-file / PSR-4 tests
36+
- Test the happy path, edge cases, and interactions with existing features
37+
- When adding a feature, update `example.php` with working examples (and verify with `php -l example.php`)
38+
39+
See [BUILDING.md](BUILDING.md) for more on running tests and manual LSP testing.
3540

3641
## Reporting Issues
3742

3843
Open an issue on GitHub with:
44+
3945
- What you expected to happen
4046
- What actually happened
41-
- Steps to reproduce
47+
- Steps to reproduce (a minimal PHP snippet is ideal)

0 commit comments

Comments
 (0)