Skip to content

Commit 8ae35c6

Browse files
committed
fix: highlight PHP attributes as decorators
Attribute usages need a semantic token distinct from normal class references so editor themes can color #[...] separately from classes used in types or expressions. Reuse the existing attribute class-reference context to emit the standard decorator token.\n\nCloses #117
1 parent 1928849 commit 8ae35c6

3 files changed

Lines changed: 28 additions & 2 deletions

File tree

docs/CHANGELOG.md

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

4343
### Fixed
4444

45+
- **PHP attributes use decorator semantic highlighting.** Attribute class names in `#[...]` now emit the `decorator` semantic token instead of `class`, so editor themes can color attributes differently from normal class references. Contributed by @calebdw.
4546
- **Go-to-definition on overriding methods jumps to the parent declaration.** When the cursor is on a method definition that overrides a parent or implements an interface method, go-to-definition now navigates to the prototype declaration instead of returning call-site references. Similarly, go-to-definition on a class name jumps to the parent class when one exists. Methods and classes that don't override anything still show usages as before. Contributed by @calebdw.
4647
- **Go-to-definition on overridden properties and constants jumps to the prototype declaration.** Declaration-site navigation now follows overridden properties and constants to the nearest parent or trait declaration, matching the override behavior for methods. Contributed by @calebdw.
4748
- **Code lens navigation works on Cursor, VSCodium, Neovim, and other editors.** Clicking a code lens annotation (e.g. "overrides Parent::method") now uses the standard `window/showDocument` LSP request instead of editor-specific commands (`vscode.open`, `editor.action.showReferences`) that only worked on VS Code. Contributed by @calebdw.

src/semantic_tokens.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use tower_lsp::lsp_types::*;
2121

2222
use crate::Backend;
2323
use crate::diagnostics::unknown_members::member_exists;
24-
use crate::symbol_map::{SelfStaticParentKind, SymbolKind, SymbolMap, VarDefKind};
24+
use crate::symbol_map::{ClassRefContext, SelfStaticParentKind, SymbolKind, SymbolMap, VarDefKind};
2525
use crate::types::{ClassInfo, ClassLikeKind};
2626

2727
// ─── Token type indices ─────────────────────────────────────────────────────
@@ -258,7 +258,9 @@ impl Backend {
258258
// already highlights the import prolog per name segment;
259259
// a single token spanning the whole path (backslashes
260260
// included) would visibly override that coloring.
261-
if *context == crate::symbol_map::ClassRefContext::UseImport {
261+
if *context == ClassRefContext::Attribute {
262+
(TT_DECORATOR, 0)
263+
} else if *context == ClassRefContext::UseImport {
262264
if !is_blade {
263265
continue;
264266
}

tests/integration/semantic_tokens.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const TT_VARIABLE: u32 = 7;
3030
const TT_PROPERTY: u32 = 8;
3131
const TT_FUNCTION: u32 = 9;
3232
const TT_METHOD: u32 = 10;
33+
const TT_DECORATOR: u32 = 11;
3334
#[allow(dead_code)]
3435
const TT_ENUM_MEMBER: u32 = 12;
3536
#[allow(dead_code)]
@@ -227,6 +228,28 @@ function make() {
227228
);
228229
}
229230

231+
#[test]
232+
fn attribute_class_reference_uses_decorator_token() {
233+
let php = r#"<?php
234+
#[MyAttribute]
235+
class Example {}
236+
237+
new MyAttribute();
238+
239+
class MyAttribute {}
240+
"#;
241+
let tokens = get_tokens(php);
242+
let decoded = decode_tokens(&tokens);
243+
244+
let attribute = find_decoded(&decoded, 1, 2).expect("expected token for attribute class");
245+
assert_eq!(attribute.token_type, TT_DECORATOR);
246+
assert_eq!(attribute.length, 11);
247+
248+
let new_expression = find_decoded(&decoded, 4, 4).expect("expected token for class reference");
249+
assert_eq!(new_expression.token_type, TT_CLASS);
250+
assert_eq!(new_expression.length, 11);
251+
}
252+
230253
#[test]
231254
fn variable_tokens() {
232255
let php = r#"<?php

0 commit comments

Comments
 (0)