Skip to content

Commit 2a7e335

Browse files
AbyssWaIkerAJenbo
authored andcommitted
Add find references and rename for PHPDoc @property/@method
1 parent 479a666 commit 2a7e335

7 files changed

Lines changed: 696 additions & 7 deletions

File tree

docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4040
- **Machine-readable CLI output.** Both `analyze` and `fix` accept a `--format` flag with `table`, `github`, and `json` options. When `GITHUB_ACTIONS` is set, table output automatically includes GitHub annotations.
4141
- **Magic property diagnostics.** New `report-magic-properties` option under `[diagnostics]` in `.phpantom.toml`. When enabled, classes with `__get` that also have virtual properties (from `@property` docblock tags, Laravel Eloquent column inference, or other providers) will flag unknown property access instead of silently allowing it.
4242
- **Inline diagnostic suppression.** `// @phpantom-ignore code` on the same line or the line above suppresses the specified diagnostic. Multiple codes can be comma-separated. A bare `// @phpantom-ignore` suppresses all diagnostics on the target line.
43+
- **Find references and rename for PHPDoc virtual members.** `@property`, `@property-read`, `@property-write`, and `@method` declarations in docblocks are now included in find-references and rename results alongside their runtime usages. (thanks @AbyssWaIker)
4344

4445
### Changed
4546

examples/laravel/app/Demo.php

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ public function customCollection(): void
120120
$top = $reviews->topRated(); // custom method from ReviewCollection
121121
$avg = $reviews->averageRating(); // custom method from ReviewCollection
122122
$reviews->first(); // inherited — returns Review|null
123+
echo count($top), $avg;
123124

124125
// Relationship properties also use the custom collection
125126
$review = new Review();
@@ -133,7 +134,8 @@ public function eloquentClosure(): void
133134
{
134135
// Eloquent chunk — $orders inferred as Collection
135136
BlogAuthor::where('active', true)->chunk(100, function ($orders) {
136-
$total = $orders->count(); // resolves to Eloquent Collection
137+
$count = $orders->count(); // resolves to Eloquent Collection
138+
echo $count;
137139
});
138140

139141
// Explicit bare type hint inherits inferred generic args for foreach
@@ -213,7 +215,24 @@ public function laravelNavigation(): void
213215
}
214216

215217

216-
// ── Laravel Config (definition & references) ────────────────────────────
218+
// ── PHPDoc Virtual Member References & Rename ───────────────────────────
219+
// Try: right-click "displayName" or "bio" below and use
220+
// • Find All References — includes the @property/@method declaration
221+
// • Rename Symbol — renames in the docblock AND all usage sites
222+
223+
public function phpdocVirtualMembers(): void
224+
{
225+
$author = new BlogAuthor();
226+
$author->displayName; // @property-read on BlogAuthor
227+
$author->bio(); // @method on BlogAuthor
228+
229+
$found = BlogAuthor::where('active', true)->first();
230+
$found->displayName;
231+
$found->bio();
232+
}
233+
234+
235+
// ── Laravel Config (definition & references) ────────────────────────
217236

218237
public function laravelConfig(): void
219238
{

examples/laravel/app/Models/BlogAuthor.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@
88
use Illuminate\Database\Eloquent\Relations\HasMany;
99
use Illuminate\Database\Eloquent\Relations\HasOne;
1010

11-
#[CollectedBy(AuthorCollection::class)]
12-
1311
/**
12+
* @property-read string $displayName
13+
* @method string bio()
1414
* @method static Builder<static> withTrashed(bool $withTrashed = true)
1515
* @method static Builder<static> onlyTrashed()
1616
*/
17+
#[CollectedBy(AuthorCollection::class)]
1718
class BlogAuthor extends Model
1819
{
1920
protected $fillable = ['name', 'email', 'genre'];

src/references/mod.rs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -774,8 +774,19 @@ impl Backend {
774774
// Check if the enclosing class is in the hierarchy.
775775
if let Some(hier) = hierarchy {
776776
let ctx = file_ctx_cell.get_or_init(|| self.file_context(file_uri));
777-
if let Some(enclosing) = find_class_at_offset(&ctx.classes, span.start)
778-
{
777+
let enclosing =
778+
find_class_at_offset(&ctx.classes, span.start).or_else(|| {
779+
// Docblock MemberDeclaration spans are before the
780+
// opening brace; fall back to the nearest class.
781+
ctx.classes
782+
.iter()
783+
.map(|c| c.as_ref())
784+
.filter(|c| {
785+
c.keyword_offset > 0 && span.start < c.start_offset
786+
})
787+
.min_by_key(|c| c.start_offset)
788+
});
789+
if let Some(enclosing) = enclosing {
779790
let fqn = enclosing.fqn().to_string();
780791
if !hier.contains(&fqn) {
781792
continue;
@@ -1045,7 +1056,16 @@ impl Backend {
10451056
.get(uri)
10461057
.cloned()
10471058
.unwrap_or_default();
1048-
let current_class = find_class_at_offset(&classes, offset)?;
1059+
let current_class = find_class_at_offset(&classes, offset).or_else(|| {
1060+
// Fallback: offset may be in a class docblock (before the opening
1061+
// brace). Find the nearest class whose body starts past the
1062+
// offset, meaning its docblock region likely contains the offset.
1063+
classes
1064+
.iter()
1065+
.map(|c| c.as_ref())
1066+
.filter(|c| c.keyword_offset > 0 && offset < c.start_offset)
1067+
.min_by_key(|c| c.start_offset)
1068+
})?;
10491069
let fqn = current_class.fqn().to_string();
10501070
Some(self.collect_hierarchy_for_fqns(&[fqn]))
10511071
}

0 commit comments

Comments
 (0)