Skip to content

Commit ea941a8

Browse files
committed
Hover stands down at every declaration site, not just most of them
1 parent ec47f80 commit ea941a8

4 files changed

Lines changed: 65 additions & 32 deletions

File tree

docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
126126

127127
### Fixed
128128

129+
- **Hover stands down at every declaration site, not just most of them.** A class, interface, namespace, method, property, class constant, and enum case already answered nothing on their own declaration, since the signature and docblock are already on screen. A global function's own name, a top-level `const`, and a constructor-promoted parameter did not follow that rule: hovering `function helper()` repeated its signature, hovering `const LIMIT = 5;` repeated its value, and a promoted parameter (`__construct(public string $sku)`) hovered as a local variable rather than the property it actually declares. All three now stand down the same way the rest do.
129130
- **Hover no longer depends on indexing timing.** A hover, go-to-definition, or any other request that arrived before the file's first parse published its symbol map answered null (or fell back to poorer resolution), so the same request could succeed in one session and fail in the next depending on how far background indexing had gotten. A request for a file with no symbol map now parses it on the spot from the content it already fetched, so the first answer matches every later one. Closes #343.
130131
- **A raw Blade echo is read from its own opening brace.** `{!! $html !!}` was only recognised when written as `{{!! $html !!}}`, a spelling Blade does not use, so the expression inside a real raw echo was masked as HTML: a variable declared in a `<?php ?>` block and displayed through `{!! … !!}` was reported unused, and completion, hover, and go-to-definition inside the echo answered nothing. The raw echo now compiles the way Blade compiles it, to a plain `echo` with no `e()` escape around it, and each echo form only closes at its own terminator, so `!!}` no longer ends an escaped `{{ … }}` early. Closes #370.
131132
- **An echo opener with no terminator no longer swallows the rest of the template.** A `{{` with no `}}` anywhere after it, or a `{!!` with no `!!}` — `<script>if (a) {!!b}</script>` was enough — put the rest of the file into PHP mode: every later line was read as code instead of markup and the whole template stopped parsing. Such an opener is now closed at the end of its own line, so at most that line degrades and everything after it works as usual. An echo that is still being typed, or that spans lines with its terminator further down, stays open exactly as before, so completion inside a half-written echo keeps working.

docs/todo/bugs.md

Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -45,31 +45,4 @@ No outstanding items.
4545

4646
## Miscellaneous
4747

48-
### B178. Three declaration sites still hover
49-
50-
**Impact: Low · Complexity: Low**
51-
52-
Hover stands down at a definition site, because the signature is on the
53-
line under the cursor and the docblock is on the lines above it, so the
54-
popup would only repeat what is already on screen. Class, interface,
55-
namespace, method, property, class constant, and enum case declarations
56-
all follow that rule. Three do not:
57-
58-
- a global `function helper(): int` at its own name answers with its
59-
docblock and signature;
60-
- a global `const LIMIT = 5;` (and `define()`) at its own name answers
61-
with its value;
62-
- a constructor-promoted parameter (`__construct(public string $sku)`)
63-
answers as a local parameter, which is also the wrong reading of it:
64-
the name declares a property, not a variable.
65-
66-
All three go through `hover_from_symbol` in `src/hover/mod.rs`: the
67-
first two land in the `FunctionCall` / `ConstantReference` arms, which
68-
never check the `is_definition` flag their spans already carry, and the
69-
third lands in the `Variable` arm, where `VarDefKind::Parameter` is on
70-
the allow-list and no caller distinguishes a promoted parameter
71-
(`is_promoted_property_param` in `src/definition/resolve.rs` is the
72-
existing test for one).
73-
74-
Decide the rule once and apply it to all three rather than case by
75-
case: a hover on a *declaration* is either useful everywhere or nowhere.
48+
No outstanding items.

src/hover/mod.rs

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ impl Backend {
147147
// bindings, hover is useful to show the resolved type and
148148
// any docblock descriptions.
149149
if let Some(def_kind) = self.lookup_var_def_kind_at(uri, name, cursor_offset)
150-
&& !matches!(
150+
&& (!matches!(
151151
def_kind,
152152
VarDefKind::Assignment
153153
| VarDefKind::CompoundAssignment
@@ -156,8 +156,13 @@ impl Backend {
156156
| VarDefKind::Catch
157157
| VarDefKind::ArrayDestructuring
158158
| VarDefKind::ListDestructuring
159-
)
159+
) || (def_kind == VarDefKind::Parameter
160+
&& self.is_promoted_property_param(uri, symbol.start)))
160161
{
162+
// A constructor-promoted parameter declares a property,
163+
// not a local variable, so it follows the same
164+
// "already visible on screen" rule as any other
165+
// property declaration.
161166
return None;
162167
}
163168
self.hover_variable(name, uri, content, cursor_offset, current_class, &ctx)
@@ -403,9 +408,17 @@ impl Backend {
403408

404409
SymbolKind::FunctionCall {
405410
name,
411+
is_definition,
406412
is_docblock_reference,
407-
..
408413
} => {
414+
// The user is already at the function's own declaration —
415+
// showing hover here would just repeat the signature and
416+
// docblock that are already on screen, same as a class or
417+
// member declaration.
418+
if *is_definition {
419+
return None;
420+
}
421+
409422
// An unqualified `@see name()` describes the documented
410423
// class's own member first, as phpDocumentor reads it.
411424
if *is_docblock_reference
@@ -483,7 +496,18 @@ impl Backend {
483496
}
484497
}
485498

486-
SymbolKind::ConstantReference { name, .. } => {
499+
SymbolKind::ConstantReference {
500+
name,
501+
is_definition,
502+
} => {
503+
// The user is already at the `const NAME = ...;` declaration
504+
// — the value is on the same line, so showing hover here
505+
// would just repeat it, same as a class or member
506+
// declaration.
507+
if *is_definition {
508+
return None;
509+
}
510+
487511
// The name is resolved against the file the same way the
488512
// type engine resolves it, so a namespaced constant is
489513
// found through whichever spelling the reference used.

tests/integration/hover.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1542,6 +1542,41 @@ enum Status: string {
15421542
}
15431543
}
15441544

1545+
/// A global function's own name, a top-level constant's own name, and a
1546+
/// constructor-promoted parameter's own name all declare something whose
1547+
/// full signature/value is already on screen — same rule as
1548+
/// [`hover_member_declaration_sites_return_none`], applied to the three
1549+
/// declaration sites that used to slip through it.
1550+
#[test]
1551+
fn hover_global_declaration_sites_return_none() {
1552+
let backend = create_test_backend();
1553+
let uri = "file:///test.php";
1554+
let content = r#"<?php
1555+
/** Adds a shipping surcharge. */
1556+
function helper(): int {
1557+
return 1;
1558+
}
1559+
1560+
const LIMIT = 5;
1561+
1562+
class Product {
1563+
public function __construct(public string $sku) {}
1564+
}
1565+
"#;
1566+
1567+
for (what, line, character) in [
1568+
("function", 2u32, 10u32),
1569+
("top-level const", 6, 7),
1570+
("promoted property parameter", 9, 48),
1571+
] {
1572+
let hover = hover_at(&backend, uri, content, line, character);
1573+
assert!(
1574+
hover.is_none(),
1575+
"should not show hover on the {what} declaration site, got: {hover:?}"
1576+
);
1577+
}
1578+
}
1579+
15451580
/// A request can arrive before the file has ever been parsed: an editor
15461581
/// fires hover the instant it opens a file, and a raw LSP client can ask
15471582
/// about a file it never opened at all, ahead of background indexing.

0 commit comments

Comments
 (0)