Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ jobs:
printf '[target.%s]\nrustflags = ["-C", "link-arg=-fuse-ld=mold"]\n' "$CARGO_BUILD_TARGET" >> .cargo/config.toml
- run: cargo fmt --check
- run: cargo clippy --all-targets -- -D warnings
# `semantic-export` is off by default, so nothing above compiles it.
# Without these steps a refactor of an internal API breaks the module
# and no job notices until someone builds with the feature on. They
# run here rather than in the test job so that job keeps measuring
# exactly the feature set the released binary is built with.
- run: cargo clippy --all-targets --features semantic-export -- -D warnings
- run: cargo test --features semantic-export --lib semantic_export

test:
name: Test
Expand Down
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@ mimalloc = ["dep:mimalloc", "dep:libmimalloc-sys", "dep:libc"]
# installs a counting global allocator that adds two atomics to every
# allocation, so it must never be enabled in a shipped build.
mem-audit = []
# Owned semantic records for non-LSP consumers. Kept out of default builds so
# the public surface and its code have zero cost for editor users.
semantic-export = []
# Prevent the build script from downloading phpstorm-stubs. Existing local
# stubs are still embedded; a build without them gets an empty stub index.
offline-stubs = []

# Download size is what matters in the browser, so the wasm module is built for
# size rather than speed. A separate profile keeps this away from the native
Expand Down
10 changes: 10 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ fn main() {
println!("cargo:rerun-if-changed=.");
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=stubs.lock");
println!("cargo:rerun-if-env-changed=CARGO_FEATURE_OFFLINE_STUBS");
// Re-run when HEAD moves (commit, checkout, tag) so the embedded
// version string stays current.
println!("cargo:rerun-if-changed=.git/HEAD");
Expand Down Expand Up @@ -154,6 +155,15 @@ fn main() {
}
}

if needs_fetch && env::var_os("CARGO_FEATURE_OFFLINE_STUBS").is_some() {
eprintln!(
"cargo:warning=Stubs are unavailable or stale; building without them because the offline-stubs feature is enabled."
);
println!("cargo:rustc-env=PHPANTOM_STUBS_VERSION=none");
write_empty_stubs();
return;
}

if needs_fetch {
let stubs_path = stubs_root.join(STUBS_DIR_REL);
if stubs_path.exists() {
Expand Down
27 changes: 26 additions & 1 deletion docs/BUILDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,29 @@ The stubs are downloaded on first build and cached in `stubs/`. To update to the

For details on how symbol resolution and stub loading work, see [ARCHITECTURE.md](ARCHITECTURE.md).

### Optional features

Both are off by default, so an editor build compiles neither.

`offline-stubs` keeps the build script from reaching the network: when
the stubs are missing or stale it embeds an empty stub index and
continues instead of downloading them. Stubs already present in
`stubs/` are still embedded, so a machine that has built once keeps its
standard library. A build without them resolves only the project's own
code, so use it when the build must not make network calls rather than
to save time.

`semantic-export` compiles `phpantom_lsp::semantic_export`, an API for
programs that embed PHPantom as a library rather than talking to it
over LSP. The caller supplies PHP documents as strings, and gets back
declarations, occurrences, calls, byte ranges, and diagnostics as owned
values that outlive the backend they came from. Nothing is read from
disk and no server is started.

```bash
cargo build --features semantic-export,offline-stubs
```

### Matching the released binary

The Linux binaries we publish are static musl builds using mimalloc as
Expand Down Expand Up @@ -68,6 +91,8 @@ Before submitting changes, run exactly what CI runs:
cargo test
cargo clippy -- -D warnings
cargo clippy --tests -- -D warnings
cargo clippy --all-targets --features semantic-export -- -D warnings
cargo test --features semantic-export --lib semantic_export
cargo fmt --check
find examples/php -name '*.php' -print0 | xargs -0 -n1 php -l
php -d zend.assertions=1 examples/php/scaffolding/assertions.php
Expand All @@ -80,7 +105,7 @@ CI additionally builds the WebAssembly target and runs
if your change touches dependencies, `Cargo.toml`, or anything in the
per-file request path. See [wasm.md](wasm.md).

All eight must pass with zero warnings and zero failures, except the
All ten must pass with zero warnings and zero failures, except the
final `analyze` run: `app/Demo.php` carries three deliberate mistakes,
each demonstrating a diagnostic. `Artisan::call('does:not-exist')`
demonstrates `invalid_laravel_command`, and one `view('welcome', …)`
Expand Down
1 change: 1 addition & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **Call hierarchy.** Editors can now ask who calls a function or method and what it calls in turn, walking the tree outward one level at a time. Incoming calls group every call site under the function or method containing it, so a method reached from twenty places in one class lists that class once with twenty ranges rather than twenty separate entries. Outgoing calls resolve each callee through the same engine go-to-definition uses, including the constructor a `new` expression runs, so a call that crosses into another file lands on the declaration there. Naming a callable without invoking it, a read of a same-named property or a `@see` tag pointing at the method, is not a call and stays out of both directions. Contributed by @sidux.
- **Reference CodeLens.** A clickable reference count sits above the classes, functions, methods, properties, and constants a PHP file declares, and following one opens the same list Find References gives. This replaces the count that used to be drawn at the end of the declaration line, which could only be read, so the number appears once and in a place you can act on. A declaration nothing in the workspace names is answered from the index straight away, and the receiver of every member access in a candidate file is resolved once and kept in a compact semantic layer, so opening a large file does not turn into one expensive search per lens. Clients that can be asked to re-pull lenses are shown a member's count once it is ready rather than being made to resolve each one in turn. A lens whose count is being worked out reads `- references` and keeps its line, so editing a file does not shuffle every line in it up and down as counts come and go, and an edit only counts again what it can actually have changed: typing inside a method body leaves every count in the file standing, and a burst of keystrokes is answered once rather than once per keystroke. The implementation count on an interface or abstract class is unchanged and still sits at the end of the declaration line. Contributed by @sidux.
- **Fully-qualified PHP classes navigate from YAML and XML.** Ctrl+Click a class name in any YAML key or value, or any XML attribute or text node, and PHPantom opens its PHP declaration without needing to know that file's schema. `Class::member` references navigate too. The same occurrences feed Find References and declaration CodeLens through the workspace reference index. Unknown and unqualified strings are left alone. Contributed by @sidux.
- **Headless consumers can export owned semantic records without starting an LSP transport.** The optional `semantic-export` feature accepts caller-supplied PHP documents, resolves them together, and returns deterministic declarations, occurrences, calls, byte ranges, and document diagnostics through batch or streaming APIs. An independent `offline-stubs` feature guarantees that missing stubs do not trigger a build-time download. Contributed by @aaaaaandrew.
- **`analyze` takes more than one path.** `phpantom_lsp analyze app/ lib/Helper.php tests/` scans the union of everything named, mixing directories and single files freely, so a pre-commit hook or a CI step can hand it exactly the paths that changed instead of running the whole project or invoking the binary once per path. Overlapping arguments are reported once, and a path that does not exist still stops the run with exit code 2. Naming no path scans the entire project, as before.
- **Blade directives a project registers itself.** A directive declared with `Blade::directive('priceTag', …)` or `Blade::if('bakeryOpen', …)` in a service provider is now read off that registration, so a template writing it gets the same treatment as one writing a directive Blade ships: the name is offered while it is being typed, and the expression the directive is handed stays real PHP whose types are checked, instead of the whole thing being masked as markup. `Blade::if()` registers four directives rather than one, and all four (`@bakeryOpen`, `@unlessbakeryOpen`, `@elsebakeryOpen`, `@endbakeryOpen`) are recognised as the block they form. A directive registered while the editor is open applies to the templates already open.
- **Your editor's own file settings now reach the index.** PHPantom accepts the `[indexing]` exclude and extension lists from the editor as well as from `.phpantom.toml`, so the folders you already hide and the extensions you already open as PHP no longer have to be written out a second time. In VS Code and Cursor this needs no setup: the extension reads `files.exclude` and `files.associations` for each workspace folder, translates them, and re-sends them when you change either. Zed and other clients can supply the same block through their language-server initialization options, documented under [Editor Setup](editor-setup.md). A change made mid-session is reconciled against the index that was already built, whether it comes from the editor or from a live `.phpantom.toml` edit: classes under a path you just hid leave the index, and files that a lifted exclude or a newly named extension brings into scope are picked up by a rescan a moment later, so neither direction waits for a restart. A file you have open is always served, whatever the filters say about it. Editor settings and `.phpantom.toml` are two layers of one filter set rather than one overriding the other: both lists apply, changing one never drops the other, and a `!` re-include in the project's config still wins over a path an individual contributor happens to hide.
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,8 @@ pub(crate) mod scope_collector;
mod selection_range;
#[cfg(not(target_arch = "wasm32"))]
pub mod self_update;
#[cfg(feature = "semantic-export")]
pub mod semantic_export;
mod semantic_tokens;
mod server;
mod signature_help;
Expand Down
Loading