Skip to content

Commit b2142f1

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

4 files changed

Lines changed: 265 additions & 147 deletions

File tree

README.md

Lines changed: 75 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -1,169 +1,110 @@
11
# PHPantomLSP
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+
> 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" />
69

710
## Features
811

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
3030

3131
### Go to Definition
3232

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
3938

4039
### Type Resolution
4140

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
4855

4956
### Type Narrowing
5057

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)`:
5259

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 ===`
5662
- `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`
10165

102-
After updating stubs (`composer update`), just rebuild — the `build.rs` script watches `composer.lock` and re-embeds everything automatically.
66+
## Project Awareness
10367

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:
10569

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
10774

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`.
13383
134-
## Editor Integration
84+
## Getting Started
13585
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).
13787
138-
- **Path:** `target/release/phpantom_lsp` (after `cargo build --release`)
88+
## Building from Source
13989
140-
### Zed
90+
See **[docs/BUILDING.md](docs/BUILDING.md)** for build, test, and debug instructions.
14191
142-
Install it as a dev extension from the `zed-extension/` directory in this repo:
92+
## Contributing
14393
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)**.
14895
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
15097
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)**.
15299
153-
```json
154-
{
155-
"languages": {
156-
"PHP": {
157-
"language_servers": ["phpantom_lsp", "!intelephense", "!phpactor", "!phptools", "..."]
158-
}
159-
}
160-
}
161-
```
100+
## Acknowledgements
162101
163-
## Contributing
102+
PHPantomLSP stands on the shoulders of:
164103
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
166107
167108
## License
168109
169-
MIT - see [LICENSE](LICENSE).
110+
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 without any 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)