Skip to content
Open
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
4 changes: 4 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- **Package resource discovery from service providers.** Config keys, view templates, translation keys, and named routes registered by installed packages are now discovered automatically. The scanner reads `mergeConfigFrom()`, `loadViewsFrom()`, `loadTranslationsFrom()`, `loadJsonTranslationsFrom()`, and `loadRoutesFrom()` calls in service providers (and their one-level-deep helper classes), resolves `__DIR__`-relative paths to the actual package files on disk, and feeds the results into the existing string key infrastructure. Completion now offers package config keys (e.g. `config('horizon.environments')`), namespaced view templates (`view('horizon::layout')`), namespaced translation keys (`trans('package::file.key')`), and package-defined named routes. Go-to-definition on any of these jumps to the exact key or file in the vendor package. Contributed by @calebdw.
- **Laravel string key completion (route, config, view, trans).** Typing inside the first string argument of `route()`, `to_route()`, `config()`, `Config::get()`, `view()`, `View::make()`, `__()`, `trans()`, `Lang::get()`, and related helpers now offers autocompletion from the project's actual route names, config keys, view templates, and translation keys. Route names are collected from `routes/*.php` (including group prefixes and `Route::group([], __DIR__ . '/sub.php')` file includes), `Route::resource()` and `Route::apiResource()` generate conventional named routes (`index`, `create`, `store`, `show`, `edit`, `update`, `destroy`) respecting `->only()` and `->except()` modifiers, config keys from `config/*.php` array declarations, view names from `resources/views/` file paths, and translation keys from `lang/` files. Go-to-definition on route names also follows file includes and resolves resource routes. Laravel container attributes (`#[Config('key')]`, `#[Database('conn')]`, `#[Cache('store')]`, `#[Log('channel')]`, `#[Storage('disk')]`, `#[Auth('guard')]`) offer completion from the relevant config sub-keys (e.g. `#[Database('')]` shows database connection names from `config/database.php`). Facade methods like `Auth::guard()`, `DB::connection()`, `Cache::store()`, `Log::channel()`, `Storage::disk()`, and the `auth()` helper also complete from their respective config sub-keys. Contributed by @calebdw.
- **Hover for Laravel string keys.** Hovering over a route name, config key, view name, or translation key string now shows the key kind, the key value, and where it's defined (e.g. `routes/web/ems.php`, `config/app.php`). Contributed by @calebdw.
- **Diagnostics for invalid Laravel string keys.** A typo in `route('dashbaord')`, `config('app.naem')`, `view('layouts.ap')`, or `__('auth.failedd')` now produces a warning: `Unknown route: 'dashbaord'`. Only plain string literals are flagged; dynamic keys with variables are ignored. Contributed by @calebdw.
- **Macro hover shows origin and inferred return types.** Hovering on a macro method call now displays a "macro" indicator instead of the generic "virtual" label, distinguishing `::macro()` registrations from `@method`/`@mixin` synthesized members. When the closure has no explicit return type hint, the return type is inferred from the closure body and shown with an "(inferred)" annotation. Bare `$this` / `self` / `static` returns preserve their keyword form, and method chains like `$this->transform(...)` use the last method's declared return type directly, preserving `$this`, `static`, and generic parameters that the general resolver would flatten to a bare class name. Regular (non-macro) methods with inferred return types also show the "(inferred)" annotation on hover. Contributed by @calebdw.
- **Return type mismatch diagnostics (`type_mismatch_return`).** Functions and methods with a declared return type are now checked against their `return` statements. Incompatible return values are flagged as errors. Void functions returning a value and bare `return;` in non-void functions are also flagged. Generators (functions using `yield`) are skipped. Uses the same conservative `is_type_compatible` policy as argument type checking to avoid false positives. Contributed by @calebdw.
- **Property type assignment diagnostics (`type_mismatch_property`).** Assignments to typed properties (`$this->prop = expr` and `self::$prop = expr`) are checked against the declared property type. Incompatible values are flagged as errors. Only plain `=` assignments are checked; compound operators (`+=`, `.=`, etc.) are skipped. Untyped and `mixed` properties are not flagged. Contributed by @calebdw.
Expand Down
49 changes: 0 additions & 49 deletions docs/todo/laravel.md
Original file line number Diff line number Diff line change
Expand Up @@ -589,55 +589,6 @@ The scanners already enumerate every valid key for go-to-definition. The
items below mostly wire that existing enumeration into three more LSP
endpoints rather than building new analysis.

#### L14. Diagnostics for Laravel string keys

**Impact: High · Effort: Medium**

No Laravel string key is currently validated. A typo in `route('dashbaord')`,
`config('app.naem')`, `env('DB_CONNCTION')`, `__('auth.failedd')`, or
`view('layouts.ap')` produces no warning — the bug surfaces only at runtime.
This is the single highest-value gap, because it catches a class of error
that PHP itself never reports.

Emit a warning when a string argument in a recognised context resolves to no
declaration. The candidate set is the same one the go-to-definition scanners
already build, so the diagnostic is "key not in the collected set." Each
construct reuses its existing scanner:

- `route('name')` → not in collected `->name()` declarations.
- `config('a.b.c')` → not in flattened `config/*.php` keys.
- `env('KEY')` → not in `.env` / `.env.example`.
- `__()`/`trans()`/`trans_choice()` → not in `lang/**` keys.
- `view('a.b')` → no matching file under `resources/views/`.

Pair each with a quick-fix where cheap: "create missing view file,"
"add missing key to `.env` (copy from `.env.example`)" (mirrors the
extension's two quick-fixes). Guard against false positives from genuinely
dynamic keys (e.g. `config($key)` with a variable, or
`route("admin.$section")` interpolation) by only flagging plain string
literals.

#### L15. Completion for Laravel string keys

**Impact: High · Effort: Medium**

Completion exists only for Eloquent relations/columns. Extend it to offer
route names, config keys (dot-notation drill-down), env var names,
translation keys, and view names inside the corresponding string contexts.
The candidate lists are exactly the declaration sets the go-to scanners
already produce; the work is detecting the string-literal cursor context
(the symbol-map already records these as `LaravelStringKey`) and returning
the collected keys as completion items.

#### L16. Hover for Laravel string keys

**Impact: Medium · Effort: Low-Medium**

`SymbolKind::LaravelStringKey` currently returns `None` from hover
(`hover/mod.rs`). Show the resolved target: the config/env/translation
*value*, the route's URI + action, or the view's file path. The data is
already loaded by the go-to scanners; this is formatting it as hover markdown.

#### L17. Additional string contexts without booting

**Impact: Medium · Effort: Medium**
Expand Down
19 changes: 18 additions & 1 deletion src/completion/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,6 @@ impl Backend {
let string_ctx =
crate::completion::comment_position::classify_string_context(&content, position);
use crate::completion::comment_position::StringContext;

// ── Array shape key completion ───────────────────────────
// Runs before `InStringLiteral` suppression because in
// normal code `$arr['` puts the scanner inside a
Expand All @@ -373,6 +372,24 @@ impl Backend {
return Ok(Some(response));
}

// ── Laravel string key completion (route/config/view/trans) ──
// Inside `route('|')`, `config('|')`, `view('|')`, `__('|')`,
// etc., offer matching key names from the project.
// NB: `is_laravel` is extracted to a `let` so the read lock
// on `resolved_class_cache` is dropped before calling
// `try_laravel_string_key_completion`, which may trigger
// `ensure_workspace_indexed` → `update_ast` → write lock.
let is_laravel = self.resolved_class_cache.read().is_laravel();
if is_laravel
&& matches!(
string_ctx,
StringContext::InStringLiteral | StringContext::NotInString
)
&& let Some(response) = self.try_laravel_string_key_completion(&content, position)
{
return Ok(Some(response));
}

// ── Eloquent relation/column string completion ──────────
// Like array shape completion, this triggers inside string
// literals where the cursor is in a method argument position
Expand Down
Loading
Loading